Index: Makefile.in ================================================================== --- Makefile.in +++ Makefile.in @@ -815,12 +815,11 @@ mv vdbe.new tsrc/vdbe.c cp fts5.c fts5.h tsrc touch .target_source sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl src-verify has_tclsh84 - $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_LINE_MACROS) \ - $(EXTRA_SRC) + $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_LINE_MACROS) $(EXTRA_SRC) cp tsrc/sqlite3ext.h . cp $(TOP)/ext/session/sqlite3session.h . sqlite3r.h: sqlite3.h has_tclsh84 $(TCLSH_CMD) $(TOP)/tool/mksqlite3h.tcl $(TOP) --enable-recover >sqlite3r.h @@ -827,12 +826,11 @@ sqlite3r.c: sqlite3.c sqlite3r.h has_tclsh84 cp $(TOP)/ext/recover/sqlite3recover.c tsrc/ cp $(TOP)/ext/recover/sqlite3recover.h tsrc/ cp $(TOP)/ext/recover/dbdata.c tsrc/ - $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl --enable-recover \ - $(AMALGAMATION_LINE_MACROS) $(EXTRA_SRC) + $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl --enable-recover $(AMALGAMATION_LINE_MACROS) $(EXTRA_SRC) sqlite3ext.h: .target_source cp tsrc/sqlite3ext.h . tclsqlite3.c: sqlite3.c @@ -1312,13 +1310,13 @@ testfixture$(TEXE): has_tclsh85 $(TESTFIXTURE_SRC) $(LTLINK) -DSQLITE_NO_SYNC=1 $(TEMP_STORE) $(TESTFIXTURE_FLAGS) \ -o $@ $(TESTFIXTURE_SRC) $(LIBTCL) $(TLIBS) -coretestprogs: $(TESTPROGS) +coretestprogs: testfixture$(BEXE) sqlite3$(BEXE) -testprogs: coretestprogs srcck1$(BEXE) fuzzcheck$(TEXE) sessionfuzz$(TEXE) +testprogs: $(TESTPROGS) srcck1$(BEXE) fuzzcheck$(TEXE) sessionfuzz$(TEXE) # A very detailed test running most or all test cases fulltest: alltest fuzztest # Run most or all tcl test cases Index: Makefile.msc ================================================================== --- Makefile.msc +++ Makefile.msc @@ -2494,13 +2494,13 @@ .\testfixture.exe $(TOP)\test\loadext.test $(TESTOPTS) tool-zip: testfixture.exe sqlite3.exe sqldiff.exe sqlite3_analyzer.exe $(TOP)\tool\mktoolzip.tcl .\testfixture.exe $(TOP)\tool\mktoolzip.tcl -coretestprogs: $(TESTPROGS) +coretestprogs: testfixture.exe sqlite3.exe -testprogs: coretestprogs srcck1.exe fuzzcheck.exe sessionfuzz.exe +testprogs: $(TESTPROGS) srcck1.exe fuzzcheck.exe sessionfuzz.exe fulltest: alltest fuzztest alltest: $(TESTPROGS) @set PATH=$(LIBTCLPATH);$(PATH) @@ -2551,11 +2551,11 @@ mdevtest: $(TCLSH_CMD) $(TOP)\test\testrunner.tcl mdevtest # Testing for a release # -releasetest: testfixture.exe fuzztest +releasetest: testfixture.exe testfixture.exe $(TOP)\test\testrunner.tcl release smoketest: $(TESTPROGS) @set PATH=$(LIBTCLPATH);$(PATH) Index: VERSION ================================================================== --- VERSION +++ VERSION @@ -1,1 +1,1 @@ -3.46.0 +3.46.1 Index: autoconf/tea/configure.ac ================================================================== --- autoconf/tea/configure.ac +++ autoconf/tea/configure.ac @@ -17,11 +17,11 @@ # so you can encode the package version directly into the source files. # This will also define a special symbol for Windows (BUILD_ # so that we create the export library with the dll. #----------------------------------------------------------------------- -AC_INIT([sqlite],[3.46.0]) +AC_INIT([sqlite],[3.46.1]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. # This will define a ${TEA_PLATFORM} variable == "unix" or "windows" # as well as PKG_LIB_FILE and PKG_STUB_LIB_FILE. 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.46.0. +# Generated by GNU Autoconf 2.69 for sqlite 3.46.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.46.0' -PACKAGE_STRING='sqlite 3.46.0' +PACKAGE_VERSION='3.46.1' +PACKAGE_STRING='sqlite 3.46.1' PACKAGE_BUGREPORT='' PACKAGE_URL='' # Factoring default headers for most tests. ac_includes_default="\ @@ -1470,11 +1470,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.46.0 to adapt to many kinds of systems. +\`configure' configures sqlite 3.46.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. @@ -1535,11 +1535,11 @@ _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.46.0:";; + short | recursive ) echo "Configuration of sqlite 3.46.1:";; esac cat <<\_ACEOF Optional Features: --disable-option-checking ignore unrecognized --enable/--with options @@ -1666,11 +1666,11 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.46.0 +sqlite configure 3.46.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. @@ -2085,11 +2085,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.46.0, which was +It was created by sqlite $as_me 3.46.1, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ _ACEOF @@ -12479,11 +12479,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.46.0, which was +This file was extended by sqlite $as_me 3.46.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 @@ -12545,11 +12545,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.46.0 +sqlite config.status 3.46.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: doc/testrunner.md ================================================================== --- doc/testrunner.md +++ doc/testrunner.md @@ -15,11 +15,10 @@
  • 3.1. Commands to Run SQLite Tests
  • 3.2. Running ZipVFS Tests
  • 3.3. Investigating Source Code Test Failures
  • 4. Extra testrunner.tcl Options -# 4. Extra testrunner.tcl Options
  • 5. Controlling CPU Core Utilization # 1. Overview @@ -27,16 +26,22 @@ testrunner.tcl is a Tcl script used to run multiple SQLite tests using multiple jobs. It supports the following types of tests: * Tcl test scripts. - * Tests run with [make] commands. Specifically, at time of writing, - [make fuzztest], [make mptest], [make sourcetest] and [make threadtest]. + * Tests run with `make` commands. Examples: + - `make mdevtest` + - `make releasetest` + - `make sdevtest` + - `make testrunner` testrunner.tcl pipes the output of all tests and builds run into log file -**testrunner.log**, created in the cwd directory. Searching this file for -"failed" is a good way to find the output of a failed test. +**testrunner.log**, created in the current working directory. Search this +file to find details of errors. Suggested search commands: + + * `grep "^!" testrunner.log` + * `grep failed testrunner.log` testrunner.tcl also populates SQLite database **testrunner.db**. This database contains details of all tests run, running and to be run. A useful query might be: @@ -58,21 +63,21 @@ watch ./testfixture $(TESTDIR)/testrunner.tcl status ``` in another terminal is a good way to keep an eye on a long running test. -Sometimes testrunner.tcl uses the [testfixture] binary that it is run with +Sometimes testrunner.tcl uses the `testfixture` binary that it is run with to run tests (see "Binary Tests" below). Sometimes it builds testfixture and other binaries in specific configurations to test (see "Source Tests"). # 2. Binary Tests The commands described in this section all run various combinations of the Tcl -test scripts using the [testfixture] binary used to run the testrunner.tcl +test scripts using the `testfixture` binary used to run the testrunner.tcl script (i.e. they do not invoke the compiler to build new binaries, or the -[make] command to run tests that are not Tcl scripts). The procedure to run +`make` command to run tests that are not Tcl scripts). The procedure to run these tests is therefore: 1. Build the "testfixture" (or "testfixture.exe" for windows) binary using whatever method seems convenient. @@ -191,11 +196,11 @@ ## 3.1. Commands to Run SQLite Tests The **mdevtest** command is equivalent to running the veryquick tests and -the [make fuzztest] target once for each of two --enable-all builds - one +the `make fuzztest` target once for each of two --enable-all builds - one with debugging enabled and one without: ``` tclsh $TESTDIR/testrunner.tcl mdevtest ``` @@ -281,11 +286,11 @@ # Create a script that recreates build configuration "Have-Not" on Windows: tclsh $TESTDIR/testrunner.tcl script Have-Not > make.bat ``` The generated bash or \*.bat file script accepts a single argument - a makefile -target to build. This may be used either to run a [make] command test directly, +target to build. This may be used either to run a `make` command test directly, or else to build a testfixture (or testfixture.exe) binary with which to run a Tcl test script, as described above. # 4. Extra testrunner.tcl Options @@ -307,10 +312,20 @@ ``` # Log the shell commmands that make up the mdevtest test. tclsh $TESTDIR/testrunner.tcl --dryrun mdevtest" ``` + +The **--explain** option is similar to --dryrun in that it prevents testrunner.tcl +from building any binaries or running any tests. The difference is that --explain +prints on standard output a human-readable summary of all the builds and tests that +would have been run. + +``` + # Show what builds and tests would have been run + tclsh $TESTDIR/testrunner.tcl --explain mdevtest +``` # 5. Controlling CPU Core Utilization When running either binary or source code tests, testrunner.tcl reports the @@ -337,8 +352,5 @@ testrunner.log and testrunner.db files: ``` $ ./testfixture $TESTDIR/testrunner.tcl njob $NEW_NUMBER_OF_JOBS ``` - - - Index: ext/expert/expert1.test ================================================================== --- ext/expert/expert1.test +++ ext/expert/expert1.test @@ -6,10 +6,11 @@ # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # The focus of this file is testing the CLI shell tool. Specifically, # the ".recommend" command. # # Index: ext/expert/sqlite3expert.c ================================================================== --- ext/expert/sqlite3expert.c +++ ext/expert/sqlite3expert.c @@ -1946,11 +1946,11 @@ /* Copy the entire schema of database [db] into [dbm]. */ if( rc==SQLITE_OK ){ sqlite3_stmt *pSql = 0; rc = idxPrintfPrepareStmt(pNew->db, &pSql, pzErrmsg, "SELECT sql FROM sqlite_schema WHERE name NOT LIKE 'sqlite_%%'" - " AND sql NOT LIKE 'CREATE VIRTUAL %%'" + " AND sql NOT LIKE 'CREATE VIRTUAL %%' ORDER BY rowid" ); while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ const char *zSql = (const char*)sqlite3_column_text(pSql, 0); if( zSql ) rc = sqlite3_exec(pNew->dbm, zSql, 0, 0, pzErrmsg); } Index: ext/fts3/fts3_snippet.c ================================================================== --- ext/fts3/fts3_snippet.c +++ ext/fts3/fts3_snippet.c @@ -444,11 +444,11 @@ }else{ iScore += 1000; } mCover |= mPhrase; - for(j=0; jnToken; j++){ + for(j=0; jnToken && jnSnippet; j++){ mHighlight |= (mPos>>j); } if( 0==(*pCsr & 0x0FE) ) break; fts3GetDeltaPosition(&pCsr, &iCsr); Index: ext/fts5/fts5.h ================================================================== --- ext/fts5/fts5.h +++ ext/fts5/fts5.h @@ -53,12 +53,12 @@ /* ** EXTENSION API FUNCTIONS ** ** xUserData(pFts): -** Return a copy of the context pointer the extension function was -** registered with. +** Return a copy of the pUserData pointer passed to the xCreateFunction() +** API when the extension function was registered. ** ** xColumnTotalSize(pFts, iCol, pnToken): ** If parameter iCol is less than zero, set output variable *pnToken ** to the total number of tokens in the FTS5 table. Or, if iCol is ** non-negative but less than the number of columns in the table, return Index: ext/fts5/fts5_main.c ================================================================== --- ext/fts5/fts5_main.c +++ ext/fts5/fts5_main.c @@ -1698,10 +1698,11 @@ "'delete' may not be used with a contentless_delete=1 table" ); rc = SQLITE_ERROR; }else{ rc = fts5SpecialDelete(pTab, apVal); + bUpdateOrDelete = 1; } }else{ rc = fts5SpecialInsert(pTab, z, apVal[2 + pConfig->nCol + 1]); } }else{ Index: ext/fts5/fts5_tokenize.c ================================================================== --- ext/fts5/fts5_tokenize.c +++ ext/fts5/fts5_tokenize.c @@ -77,11 +77,11 @@ rc = SQLITE_NOMEM; }else{ int i; memset(p, 0, sizeof(AsciiTokenizer)); memcpy(p->aTokenChar, aAsciiTokenChar, sizeof(aAsciiTokenChar)); - for(i=0; rc==SQLITE_OK && iaFold==0 ){ rc = SQLITE_NOMEM; } /* Search for a "categories" argument */ - for(i=0; rc==SQLITE_OK && ibFold = 1; pNew->iFoldParam = 0; - for(i=0; rc==SQLITE_OK && iiFoldParam!=0 && pNew->bFold==0 ){ rc = SQLITE_ERROR; } Index: ext/fts5/test/fts5secure8.test ================================================================== --- ext/fts5/test/fts5secure8.test +++ ext/fts5/test/fts5secure8.test @@ -39,10 +39,26 @@ } do_execsql_test 1.2 { PRAGMA integrity_check; } {ok} + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE xyz USING fts5 ( + name, + content='' + ); + + INSERT INTO xyz(xyz, rank) VALUES('secure-delete', 1); + INSERT INTO xyz (rowid, name) VALUES(1, 'A'); + INSERT INTO xyz (rowid, name) VALUES(2, 'A'); + INSERT INTO xyz(xyz, rowid, name) VALUES('delete', 2, 'A'); +} + +do_execsql_test 2.1 { + pragma quick_check; +} {ok} Index: ext/fts5/test/fts5tokenizer2.test ================================================================== --- ext/fts5/test/fts5tokenizer2.test +++ ext/fts5/test/fts5tokenizer2.test @@ -82,8 +82,28 @@ } {AAdontBB>mess<} do_execsql_test 1.7 { SELECT highlight(t1, 0, '>', '<') FROM t1('BB mess'); } {AAdont>BBmess<} + +# 2024-08-06 https://sqlite.org/forum/forumpost/171bcc2bcd +# Error handling of tokenize= arguments. +# +foreach {n tkz} { + 1 {ascii none} + 2 {unicode61 none} + 3 {porter none} + 4 {trigram none} + 5 {ascii none 0} + 6 {unicode61 none 0} + 7 {porter none 0} + 8 {trigram none 0} +} { + db eval {DROP TABLE IF EXISTS t2;} + do_catchsql_test 2.$n " + DROP TABLE IF EXISTS t2; + CREATE VIRTUAL TABLE t2 USING fts5(a,b,c,tokenize='$tkz'); + " {1 {error in tokenizer constructor}} +} finish_test Index: ext/fts5/test/fts5trigram.test ================================================================== --- ext/fts5/test/fts5trigram.test +++ ext/fts5/test/fts5trigram.test @@ -67,10 +67,13 @@ do_execsql_test 2.0 { CREATE VIRTUAL TABLE t1 USING fts5(y, tokenize="trigram case_sensitive 1"); INSERT INTO t1 VALUES('abcdefghijklm'); INSERT INTO t1 VALUES('กรุงเทพมหานคร'); } +do_catchsql_test 2.0.1 { + CREATE VIRTUAL TABLE t2 USING fts5(z, tokenize='trigram case_sensitive'); +} {1 {error in tokenizer constructor}} foreach {tn s res} { 1 abc "(abc)defghijklm" 2 defgh "abc(defgh)ijklm" 3 abcdefghijklm "(abcdefghijklm)" @@ -204,11 +207,11 @@ INSERT INTO f (rowid, filename) VALUES (10, "giraffe.png"), (20, "жираф.png"), (30, "cat.png"), (40, "кот.png"), - (50, "misic-🎵-.mp3"); + (50, "misic-🎵-.mp3"); } do_execsql_test 7.1 { SELECT rowid FROM f WHERE +filename GLOB '*ир*'; } {20} do_execsql_test 7.2 { Index: ext/fts5/test/fts5trigram2.test ================================================================== --- ext/fts5/test/fts5trigram2.test +++ ext/fts5/test/fts5trigram2.test @@ -19,10 +19,13 @@ do_execsql_test 1.0 " CREATE VIRTUAL TABLE t1 USING fts5(y, tokenize='trigram remove_diacritics 1'); INSERT INTO t1 VALUES('abc\u0303defghijklm'); INSERT INTO t1 VALUES('a\u0303b\u0303c\u0303defghijklm'); " +do_catchsql_test 1.0.1 { + CREATE VIRTUAL TABLE t2 USING fts5(z, tokenize='trigram remove_diacritics'); +} {1 {error in tokenizer constructor}} do_execsql_test 1.1 { SELECT highlight(t1, 0, '(', ')') FROM t1('abc'); } [list \ "(abc\u0303)defghijklm" \ Index: ext/icu/icu.c ================================================================== --- ext/icu/icu.c +++ ext/icu/icu.c @@ -469,11 +469,11 @@ const char *zLocale; /* Locale identifier - (eg. "jp_JP") */ const char *zName; /* SQL Collation sequence name (eg. "japanese") */ UCollator *pUCollator; /* ICU library collation object */ int rc; /* Return code from sqlite3_create_collation_x() */ - assert(nArg==2); + assert(nArg==2 || nArg==3); (void)nArg; /* Unused parameter */ zLocale = (const char *)sqlite3_value_text(apArg[0]); zName = (const char *)sqlite3_value_text(apArg[1]); if( !zLocale || !zName ){ @@ -484,11 +484,43 @@ if( !U_SUCCESS(status) ){ icuFunctionError(p, "ucol_open", status); return; } assert(p); - + if(nArg==3){ + const char *zOption = (const char*)sqlite3_value_text(apArg[2]); + static const struct { + const char *zName; + UColAttributeValue val; + } aStrength[] = { + { "PRIMARY", UCOL_PRIMARY }, + { "SECONDARY", UCOL_SECONDARY }, + { "TERTIARY", UCOL_TERTIARY }, + { "DEFAULT", UCOL_DEFAULT_STRENGTH }, + { "QUARTERNARY", UCOL_QUATERNARY }, + { "IDENTICAL", UCOL_IDENTICAL }, + }; + unsigned int i; + for(i=0; i=sizeof(aStrength)/sizeof(aStrength[0]) ){ + sqlite3_str *pStr = sqlite3_str_new(sqlite3_context_db_handle(p)); + sqlite3_str_appendf(pStr, + "unknown collation strength \"%s\" - should be one of:", + zOption); + for(i=0; i=512 /* (1) */ - && p->verifyCksm /* (2) */ - && !p->inCkpt /* (3) */ + if( iAmt>=512 && (iAmt & (iAmt-1))==0 /* (1) */ + && p->verifyCksm /* (2) */ + && !p->inCkpt /* (3) */ ){ u8 cksum[8]; cksmCompute((u8*)zBuf, iAmt-8, cksum); if( memcmp((u8*)zBuf+iAmt-8, cksum, 8)!=0 ){ sqlite3_log(SQLITE_IOERR_DATA, Index: ext/misc/fileio.c ================================================================== --- ext/misc/fileio.c +++ ext/misc/fileio.c @@ -370,11 +370,13 @@ ){ if( zFile==0 ) return 1; #if !defined(_WIN32) && !defined(WIN32) if( S_ISLNK(mode) ){ const char *zTo = (const char*)sqlite3_value_text(pData); - if( zTo==0 || symlink(zTo, zFile)<0 ) return 1; + if( zTo==0 ) return 1; + unlink(zFile); + if( symlink(zTo, zFile)<0 ) return 1; }else #endif { if( S_ISDIR(mode) ){ if( mkdir(zFile, mode) ){ @@ -456,17 +458,23 @@ times[1].tv_sec = mtime; if( utimensat(AT_FDCWD, zFile, times, AT_SYMLINK_NOFOLLOW) ){ return 1; } #else - /* Legacy unix */ - struct timeval times[2]; - times[0].tv_usec = times[1].tv_usec = 0; - times[0].tv_sec = time(0); - times[1].tv_sec = mtime; - if( utimes(zFile, times) ){ - return 1; + /* Legacy unix. + ** + ** Do not use utimes() on a symbolic link - it sees through the link and + ** modifies the timestamps on the target. Or fails if the target does + ** not exist. */ + if( 0==S_ISLNK(mode) ){ + struct timeval times[2]; + times[0].tv_usec = times[1].tv_usec = 0; + times[0].tv_sec = time(0); + times[1].tv_sec = mtime; + if( utimes(zFile, times) ){ + return 1; + } } #endif } return 0; Index: ext/misc/series.c ================================================================== --- ext/misc/series.c +++ ext/misc/series.c @@ -374,17 +374,17 @@ ** ** The query plan selected by seriesBestIndex is passed in the idxNum ** parameter. (idxStr is not used in this implementation.) idxNum ** is a bitmask showing which constraints are available: ** -** 1: start=VALUE -** 2: stop=VALUE -** 4: step=VALUE -** -** Also, if bit 8 is set, that means that the series should be output -** in descending order rather than in ascending order. If bit 16 is -** set, then output must appear in ascending order. +** 0x01: start=VALUE +** 0x02: stop=VALUE +** 0x04: step=VALUE +** 0x08: descending order +** 0x10: ascending order +** 0x20: LIMIT VALUE +** 0x40: OFFSET VALUE ** ** This routine should initialize the cursor and position it so that it ** is pointing at the first row, or pointing off the end of the table ** (so that seriesEof() will return true) if the table is empty. */ @@ -394,29 +394,47 @@ int argc, sqlite3_value **argv ){ series_cursor *pCur = (series_cursor *)pVtabCursor; int i = 0; (void)idxStrUnused; - if( idxNum & 1 ){ + if( idxNum & 0x01 ){ pCur->ss.iBase = sqlite3_value_int64(argv[i++]); }else{ pCur->ss.iBase = 0; } - if( idxNum & 2 ){ + if( idxNum & 0x02 ){ pCur->ss.iTerm = sqlite3_value_int64(argv[i++]); }else{ pCur->ss.iTerm = 0xffffffff; } - if( idxNum & 4 ){ + if( idxNum & 0x04 ){ pCur->ss.iStep = sqlite3_value_int64(argv[i++]); if( pCur->ss.iStep==0 ){ pCur->ss.iStep = 1; }else if( pCur->ss.iStep<0 ){ - if( (idxNum & 16)==0 ) idxNum |= 8; + if( (idxNum & 0x10)==0 ) idxNum |= 0x08; } }else{ pCur->ss.iStep = 1; + } + if( idxNum & 0x20 ){ + sqlite3_int64 iLimit = sqlite3_value_int64(argv[i++]); + sqlite3_int64 iTerm; + if( idxNum & 0x40 ){ + sqlite3_int64 iOffset = sqlite3_value_int64(argv[i++]); + if( iOffset>0 ){ + pCur->ss.iBase += pCur->ss.iStep*iOffset; + } + } + if( iLimit>=0 ){ + iTerm = pCur->ss.iBase + (iLimit - 1)*pCur->ss.iStep; + if( pCur->ss.iStep<0 ){ + if( iTerm>pCur->ss.iTerm ) pCur->ss.iTerm = iTerm; + }else{ + if( iTermss.iTerm ) pCur->ss.iTerm = iTerm; + } + } } for(i=0; iss.iTerm = 0; pCur->ss.iStep = 1; break; } } - if( idxNum & 8 ){ + if( idxNum & 0x08 ){ pCur->ss.isReversing = pCur->ss.iStep > 0; }else{ pCur->ss.isReversing = pCur->ss.iStep < 0; } setupSequence( &pCur->ss ); @@ -444,54 +462,85 @@ ** In this implementation idxNum is used to represent the ** query plan. idxStr is unused. ** ** The query plan is represented by bits in idxNum: ** -** (1) start = $value -- constraint exists -** (2) stop = $value -- constraint exists -** (4) step = $value -- constraint exists -** (8) output in descending order +** 0x01 start = $value -- constraint exists +** 0x02 stop = $value -- constraint exists +** 0x04 step = $value -- constraint exists +** 0x08 output is in descending order +** 0x10 output is in ascending order +** 0x20 LIMIT $value -- constraint exists +** 0x40 OFFSET $value -- constraint exists */ static int seriesBestIndex( sqlite3_vtab *pVTab, sqlite3_index_info *pIdxInfo ){ int i, j; /* Loop over constraints */ int idxNum = 0; /* The query plan bitmask */ +#ifndef ZERO_ARGUMENT_GENERATE_SERIES int bStartSeen = 0; /* EQ constraint seen on the START column */ +#endif int unusableMask = 0; /* Mask of unusable constraints */ int nArg = 0; /* Number of arguments that seriesFilter() expects */ - int aIdx[3]; /* Constraints on start, stop, and step */ + int aIdx[5]; /* Constraints on start, stop, step, LIMIT, OFFSET */ const struct sqlite3_index_constraint *pConstraint; /* This implementation assumes that the start, stop, and step columns ** are the last three columns in the virtual table. */ assert( SERIES_COLUMN_STOP == SERIES_COLUMN_START+1 ); assert( SERIES_COLUMN_STEP == SERIES_COLUMN_START+2 ); - aIdx[0] = aIdx[1] = aIdx[2] = -1; + aIdx[0] = aIdx[1] = aIdx[2] = aIdx[3] = aIdx[4] = -1; pConstraint = pIdxInfo->aConstraint; for(i=0; inConstraint; i++, pConstraint++){ int iCol; /* 0 for start, 1 for stop, 2 for step */ int iMask; /* bitmask for those column */ + int op = pConstraint->op; + if( op>=SQLITE_INDEX_CONSTRAINT_LIMIT + && op<=SQLITE_INDEX_CONSTRAINT_OFFSET + ){ + if( pConstraint->usable==0 ){ + /* do nothing */ + }else if( op==SQLITE_INDEX_CONSTRAINT_LIMIT ){ + aIdx[3] = i; + idxNum |= 0x20; + }else{ + assert( op==SQLITE_INDEX_CONSTRAINT_OFFSET ); + aIdx[4] = i; + idxNum |= 0x40; + } + continue; + } if( pConstraint->iColumniColumn - SERIES_COLUMN_START; assert( iCol>=0 && iCol<=2 ); iMask = 1 << iCol; - if( iCol==0 ) bStartSeen = 1; +#ifndef ZERO_ARGUMENT_GENERATE_SERIES + if( iCol==0 && op==SQLITE_INDEX_CONSTRAINT_EQ ){ + bStartSeen = 1; + } +#endif if( pConstraint->usable==0 ){ unusableMask |= iMask; continue; - }else if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){ + }else if( op==SQLITE_INDEX_CONSTRAINT_EQ ){ idxNum |= iMask; aIdx[iCol] = i; } } - for(i=0; i<3; i++){ + if( aIdx[3]==0 ){ + /* Ignore OFFSET if LIMIT is omitted */ + idxNum &= ~0x60; + aIdx[4] = 0; + } + for(i=0; i<5; i++){ if( (j = aIdx[i])>=0 ){ pIdxInfo->aConstraintUsage[j].argvIndex = ++nArg; - pIdxInfo->aConstraintUsage[j].omit = !SQLITE_SERIES_CONSTRAINT_VERIFY; + pIdxInfo->aConstraintUsage[j].omit = + !SQLITE_SERIES_CONSTRAINT_VERIFY || i>=3; } } /* The current generate_column() implementation requires at least one ** argument (the START value). Legacy versions assumed START=0 if the ** first argument was omitted. Compile with -DZERO_ARGUMENT_GENERATE_SERIES @@ -508,23 +557,26 @@ /* The start, stop, and step columns are inputs. Therefore if there ** are unusable constraints on any of start, stop, or step then ** this plan is unusable */ return SQLITE_CONSTRAINT; } - if( (idxNum & 3)==3 ){ + if( (idxNum & 0x03)==0x03 ){ /* Both start= and stop= boundaries are available. This is the ** the preferred case */ pIdxInfo->estimatedCost = (double)(2 - ((idxNum&4)!=0)); pIdxInfo->estimatedRows = 1000; if( pIdxInfo->nOrderBy>=1 && pIdxInfo->aOrderBy[0].iColumn==0 ){ if( pIdxInfo->aOrderBy[0].desc ){ - idxNum |= 8; + idxNum |= 0x08; }else{ - idxNum |= 16; + idxNum |= 0x10; } pIdxInfo->orderByConsumed = 1; } + }else if( (idxNum & 0x21)==0x21 ){ + /* We have start= and LIMIT */ + pIdxInfo->estimatedRows = 2500; }else{ /* If either boundary is missing, we have to generate a huge span ** of numbers. Make this case very expensive so that the query ** planner will work hard to avoid it. */ pIdxInfo->estimatedRows = 2147483647; Index: ext/misc/sqlar.c ================================================================== --- ext/misc/sqlar.c +++ ext/misc/sqlar.c @@ -79,26 +79,27 @@ sqlite3_context *context, int argc, sqlite3_value **argv ){ uLong nData; - uLongf sz; + sqlite3_int64 sz; assert( argc==2 ); sz = sqlite3_value_int(argv[1]); if( sz<=0 || sz==(nData = sqlite3_value_bytes(argv[0])) ){ sqlite3_result_value(context, argv[0]); }else{ + uLongf szf = sz; const Bytef *pData= sqlite3_value_blob(argv[0]); Bytef *pOut = sqlite3_malloc(sz); if( pOut==0 ){ sqlite3_result_error_nomem(context); - }else if( Z_OK!=uncompress(pOut, &sz, pData, nData) ){ + }else if( Z_OK!=uncompress(pOut, &szf, pData, nData) ){ sqlite3_result_error(context, "error in uncompress()", -1); }else{ - sqlite3_result_blob(context, pOut, sz, SQLITE_TRANSIENT); + sqlite3_result_blob(context, pOut, szf, SQLITE_TRANSIENT); } sqlite3_free(pOut); } } Index: ext/misc/vtablog.c ================================================================== --- ext/misc/vtablog.c +++ ext/misc/vtablog.c @@ -36,12 +36,13 @@ ** serve as the underlying representation of a vtablog virtual table */ typedef struct vtablog_vtab vtablog_vtab; struct vtablog_vtab { sqlite3_vtab base; /* Base class - must be first */ + char *zDb; /* Schema name. argv[1] of xConnect/xCreate */ + char *zName; /* Table name. argv[2] of xConnect/xCreate */ int nRow; /* Number of rows in the table */ - int iInst; /* Instance number for this vtablog table */ int nCursor; /* Number of cursors created */ }; /* vtablog_cursor is a subclass of sqlite3_vtab_cursor which will ** serve as the underlying representation of a cursor that scans @@ -165,19 +166,18 @@ int argc, const char *const*argv, sqlite3_vtab **ppVtab, char **pzErr, int isCreate ){ - static int nInst = 0; vtablog_vtab *pNew; int i; int rc; - int iInst = ++nInst; char *zSchema = 0; char *zNRow = 0; - printf("vtablog%s(tab=%d):\n", isCreate ? "Create" : "Connect", iInst); + printf("%s.%s.%s():\n", argv[1], argv[2], + isCreate ? "xCreate" : "xConnect"); printf(" argc=%d\n", argc); for(i=0; inRow = 10; if( zNRow ) pNew->nRow = atoi(zNRow); - pNew->iInst = iInst; + printf(" nrow = %d\n", pNew->nRow); + pNew->zDb = sqlite3_mprintf("%s", argv[1]); + pNew->zName = sqlite3_mprintf("%s", argv[2]); } + +vtablog_end_connect: + sqlite3_free(zSchema); + sqlite3_free(zNRow); return rc; } static int vtablogCreate( sqlite3 *db, void *pAux, @@ -235,21 +242,25 @@ /* ** This method is the destructor for vtablog_cursor objects. */ static int vtablogDisconnect(sqlite3_vtab *pVtab){ vtablog_vtab *pTab = (vtablog_vtab*)pVtab; - printf("vtablogDisconnect(%d)\n", pTab->iInst); + printf("%s.%s.xDisconnect()\n", pTab->zDb, pTab->zName); + sqlite3_free(pTab->zDb); + sqlite3_free(pTab->zName); sqlite3_free(pVtab); return SQLITE_OK; } /* ** This method is the destructor for vtablog_cursor objects. */ static int vtablogDestroy(sqlite3_vtab *pVtab){ vtablog_vtab *pTab = (vtablog_vtab*)pVtab; - printf("vtablogDestroy(%d)\n", pTab->iInst); + printf("%s.%s.xDestroy()\n", pTab->zDb, pTab->zName); + sqlite3_free(pTab->zDb); + sqlite3_free(pTab->zName); sqlite3_free(pVtab); return SQLITE_OK; } /* @@ -256,11 +267,12 @@ ** Constructor for a new vtablog_cursor object. */ static int vtablogOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ vtablog_vtab *pTab = (vtablog_vtab*)p; vtablog_cursor *pCur; - printf("vtablogOpen(tab=%d, cursor=%d)\n", pTab->iInst, ++pTab->nCursor); + printf("%s.%s.xOpen(cursor=%d)\n", pTab->zDb, pTab->zName, + ++pTab->nCursor); pCur = sqlite3_malloc( sizeof(*pCur) ); if( pCur==0 ) return SQLITE_NOMEM; memset(pCur, 0, sizeof(*pCur)); pCur->iCursor = pTab->nCursor; *ppCursor = &pCur->base; @@ -271,11 +283,11 @@ ** Destructor for a vtablog_cursor. */ static int vtablogClose(sqlite3_vtab_cursor *cur){ vtablog_cursor *pCur = (vtablog_cursor*)cur; vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab; - printf("vtablogClose(tab=%d, cursor=%d)\n", pTab->iInst, pCur->iCursor); + printf("%s.%s.xClose(cursor=%d)\n", pTab->zDb, pTab->zName, pCur->iCursor); sqlite3_free(cur); return SQLITE_OK; } @@ -283,12 +295,13 @@ ** Advance a vtablog_cursor to its next row of output. */ static int vtablogNext(sqlite3_vtab_cursor *cur){ vtablog_cursor *pCur = (vtablog_cursor*)cur; vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab; - printf("vtablogNext(tab=%d, cursor=%d) rowid %d -> %d\n", - pTab->iInst, pCur->iCursor, (int)pCur->iRowid, (int)pCur->iRowid+1); + printf("%s.%s.xNext(cursor=%d) rowid %d -> %d\n", + pTab->zDb, pTab->zName, pCur->iCursor, + (int)pCur->iRowid, (int)pCur->iRowid+1); pCur->iRowid++; return SQLITE_OK; } /* @@ -308,12 +321,12 @@ sqlite3_snprintf(sizeof(zVal),zVal,"%c%d", "abcdefghijklmnopqrstuvwyz"[i], pCur->iRowid); }else{ sqlite3_snprintf(sizeof(zVal),zVal,"{%d}%d", i, pCur->iRowid); } - printf("vtablogColumn(tab=%d, cursor=%d, i=%d): [%s]\n", - pTab->iInst, pCur->iCursor, i, zVal); + printf("%s.%s.xColumn(cursor=%d, i=%d): [%s]\n", + pTab->zDb, pTab->zName, pCur->iCursor, i, zVal); sqlite3_result_text(ctx, zVal, -1, SQLITE_TRANSIENT); return SQLITE_OK; } /* @@ -321,12 +334,12 @@ ** rowid is the same as the output value. */ static int vtablogRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ vtablog_cursor *pCur = (vtablog_cursor*)cur; vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab; - printf("vtablogRowid(tab=%d, cursor=%d): %d\n", - pTab->iInst, pCur->iCursor, (int)pCur->iRowid); + printf("%s.%s.xRowid(cursor=%d): %d\n", + pTab->zDb, pTab->zName, pCur->iCursor, (int)pCur->iRowid); *pRowid = pCur->iRowid; return SQLITE_OK; } /* @@ -335,12 +348,12 @@ */ static int vtablogEof(sqlite3_vtab_cursor *cur){ vtablog_cursor *pCur = (vtablog_cursor*)cur; vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab; int rc = pCur->iRowid >= pTab->nRow; - printf("vtablogEof(tab=%d, cursor=%d): %d\n", - pTab->iInst, pCur->iCursor, rc); + printf("%s.%s.xEof(cursor=%d): %d\n", + pTab->zDb, pTab->zName, pCur->iCursor, rc); return rc; } /* ** Output an sqlite3_value object's value as an SQL literal. @@ -415,11 +428,11 @@ int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ vtablog_cursor *pCur = (vtablog_cursor *)cur; vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab; - printf("vtablogFilter(tab=%d, cursor=%d):\n", pTab->iInst, pCur->iCursor); + printf("%s.%s.xFilter(cursor=%d):\n", pTab->zDb, pTab->zName, pCur->iCursor); pCur->iRowid = 0; return SQLITE_OK; } /* @@ -428,16 +441,41 @@ ** a query plan for each invocation and compute an estimated cost for that ** plan. */ static int vtablogBestIndex( sqlite3_vtab *tab, - sqlite3_index_info *pIdxInfo + sqlite3_index_info *p ){ vtablog_vtab *pTab = (vtablog_vtab*)tab; - printf("vtablogBestIndex(tab=%d):\n", pTab->iInst); - pIdxInfo->estimatedCost = (double)500; - pIdxInfo->estimatedRows = 500; + int i; + printf("%s.%s.xBestIndex():\n", pTab->zDb, pTab->zName); + printf(" colUsed: 0x%016llx\n", p->colUsed); + printf(" nConstraint: %d\n", p->nConstraint); + for(i=0; inConstraint; i++){ + printf( + " constraint[%d]: col=%d termid=%d op=%d usabled=%d collseq=%s\n", + i, + p->aConstraint[i].iColumn, + p->aConstraint[i].iTermOffset, + p->aConstraint[i].op, + p->aConstraint[i].usable, + sqlite3_vtab_collation(p,i)); + } + printf(" nOrderBy: %d\n", p->nOrderBy); + for(i=0; inOrderBy; i++){ + printf(" orderby[%d]: col=%d desc=%d\n", + i, + p->aOrderBy[i].iColumn, + p->aOrderBy[i].desc); + } + p->estimatedCost = (double)500; + p->estimatedRows = 500; + printf(" idxNum=%d\n", p->idxNum); + printf(" idxStr=NULL\n"); + printf(" orderByConsumed=%d\n", p->orderByConsumed); + printf(" estimatedCost=%g\n", p->estimatedCost); + printf(" estimatedRows=%lld\n", p->estimatedRows); return SQLITE_OK; } /* ** SQLite invokes this method to INSERT, UPDATE, or DELETE content from @@ -452,26 +490,102 @@ sqlite3_value **argv, sqlite_int64 *pRowid ){ vtablog_vtab *pTab = (vtablog_vtab*)tab; int i; - printf("vtablogUpdate(tab=%d):\n", pTab->iInst); + printf("%s.%s.xUpdate():\n", pTab->zDb, pTab->zName); printf(" argc=%d\n", argc); for(i=0; izDb, pTab->zName); + return SQLITE_OK; +} +static int vtablogSync(sqlite3_vtab *tab){ + vtablog_vtab *pTab = (vtablog_vtab*)tab; + printf("%s.%s.xSync()\n", pTab->zDb, pTab->zName); + return SQLITE_OK; +} +static int vtablogCommit(sqlite3_vtab *tab){ + vtablog_vtab *pTab = (vtablog_vtab*)tab; + printf("%s.%s.xCommit()\n", pTab->zDb, pTab->zName); + return SQLITE_OK; +} +static int vtablogRollback(sqlite3_vtab *tab){ + vtablog_vtab *pTab = (vtablog_vtab*)tab; + printf("%s.%s.xRollback()\n", pTab->zDb, pTab->zName); + return SQLITE_OK; +} +static int vtablogSavepoint(sqlite3_vtab *tab, int N){ + vtablog_vtab *pTab = (vtablog_vtab*)tab; + printf("%s.%s.xSavepoint(%d)\n", pTab->zDb, pTab->zName, N); + return SQLITE_OK; +} +static int vtablogRelease(sqlite3_vtab *tab, int N){ + vtablog_vtab *pTab = (vtablog_vtab*)tab; + printf("%s.%s.xRelease(%d)\n", pTab->zDb, pTab->zName, N); + return SQLITE_OK; +} +static int vtablogRollbackTo(sqlite3_vtab *tab, int N){ + vtablog_vtab *pTab = (vtablog_vtab*)tab; + printf("%s.%s.xRollbackTo(%d)\n", pTab->zDb, pTab->zName, N); + return SQLITE_OK; +} + +static int vtablogFindMethod( + sqlite3_vtab *tab, + int nArg, + const char *zName, + void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), + void **ppArg +){ + vtablog_vtab *pTab = (vtablog_vtab*)tab; + printf("%s.%s.xFindMethod(nArg=%d, zName=%s)\n", + pTab->zDb, pTab->zName, nArg, zName); + return SQLITE_OK; +} +static int vtablogRename(sqlite3_vtab *tab, const char *zNew){ + vtablog_vtab *pTab = (vtablog_vtab*)tab; + printf("%s.%s.xRename('%s')\n", pTab->zDb, pTab->zName, zNew); + sqlite3_free(pTab->zName); + pTab->zName = sqlite3_mprintf("%s", zNew); + return SQLITE_OK; +} + +/* Any table name that contains the text "shadow" is seen as a +** shadow table. Nothing else is. +*/ +static int vtablogShadowName(const char *zName){ + printf("vtablog.xShadowName('%s')\n", zName); + return sqlite3_strglob("*shadow*", zName)==0; +} + +static int vtablogIntegrity( + sqlite3_vtab *tab, + const char *zSchema, + const char *zTabName, + int mFlags, + char **pzErr +){ + vtablog_vtab *pTab = (vtablog_vtab*)tab; + printf("%s.%s.xIntegrity(mFlags=0x%x)\n", pTab->zDb, pTab->zName, mFlags); + return 0; +} /* ** This following structure defines all the methods for the ** vtablog virtual table. */ static sqlite3_module vtablogModule = { - 0, /* iVersion */ + 4, /* iVersion */ vtablogCreate, /* xCreate */ vtablogConnect, /* xConnect */ vtablogBestIndex, /* xBestIndex */ vtablogDisconnect, /* xDisconnect */ vtablogDestroy, /* xDestroy */ @@ -481,21 +595,21 @@ vtablogNext, /* xNext - advance a cursor */ vtablogEof, /* xEof - check for end of scan */ vtablogColumn, /* xColumn - read data */ vtablogRowid, /* xRowid - read data */ vtablogUpdate, /* xUpdate */ - 0, /* xBegin */ - 0, /* xSync */ - 0, /* xCommit */ - 0, /* xRollback */ - 0, /* xFindMethod */ - 0, /* xRename */ - 0, /* xSavepoint */ - 0, /* xRelease */ - 0, /* xRollbackTo */ - 0, /* xShadowName */ - 0 /* xIntegrity */ + vtablogBegin, /* xBegin */ + vtablogSync, /* xSync */ + vtablogCommit, /* xCommit */ + vtablogRollback, /* xRollback */ + vtablogFindMethod, /* xFindMethod */ + vtablogRename, /* xRename */ + vtablogSavepoint, /* xSavepoint */ + vtablogRelease, /* xRelease */ + vtablogRollbackTo, /* xRollbackTo */ + vtablogShadowName, /* xShadowName */ + vtablogIntegrity /* xIntegrity */ }; #ifdef _WIN32 __declspec(dllexport) #endif Index: ext/rbu/sqlite3rbu.c ================================================================== --- ext/rbu/sqlite3rbu.c +++ ext/rbu/sqlite3rbu.c @@ -197,10 +197,11 @@ #if !defined(SQLITE_AMALGAMATION) typedef unsigned int u32; typedef unsigned short u16; typedef unsigned char u8; typedef sqlite3_int64 i64; +typedef sqlite3_uint64 u64; #endif /* ** These values must match the values defined in wal.c for the equivalent ** locks. These are not magic numbers as they are part of the SQLite file @@ -883,10 +884,11 @@ pIter->bCleanup = 0; rc = sqlite3_step(pIter->pTblIter); if( rc!=SQLITE_ROW ){ rc = resetAndCollectError(pIter->pTblIter, &p->zErrmsg); pIter->zTbl = 0; + pIter->zDataTbl = 0; }else{ pIter->zTbl = (const char*)sqlite3_column_text(pIter->pTblIter, 0); pIter->zDataTbl = (const char*)sqlite3_column_text(pIter->pTblIter,1); rc = (pIter->zDataTbl && pIter->zTbl) ? SQLITE_OK : SQLITE_NOMEM; } @@ -2977,11 +2979,11 @@ if( p->rc==SQLITE_OK ){ sqlite3_file *pDb = p->pTargetFd->pReal; u32 volatile *ptr; p->rc = pDb->pMethods->xShmMap(pDb, 0, 32*1024, 0, (void volatile**)&ptr); if( p->rc==SQLITE_OK ){ - iRet = ((i64)ptr[10] << 32) + ptr[11]; + iRet = (i64)(((u64)ptr[10] << 32) + ptr[11]); } } return iRet; } Index: ext/recover/dbdata.c ================================================================== --- ext/recover/dbdata.c +++ ext/recover/dbdata.c @@ -86,10 +86,19 @@ #define DBDATA_PADDING_BYTES 100 typedef struct DbdataTable DbdataTable; typedef struct DbdataCursor DbdataCursor; +typedef struct DbdataBuffer DbdataBuffer; + +/* +** Buffer type. +*/ +struct DbdataBuffer { + u8 *aBuf; + sqlite3_int64 nBuf; +}; /* Cursor object */ struct DbdataCursor { sqlite3_vtab_cursor base; /* Base class. Must be first */ sqlite3_stmt *pStmt; /* For fetching database pages */ @@ -102,11 +111,11 @@ int bOnePage; /* True to stop after one page */ int szDb; sqlite3_int64 iRowid; /* Only for the sqlite_dbdata table */ - u8 *pRec; /* Buffer containing current record */ + DbdataBuffer rec; sqlite3_int64 nRec; /* Size of pRec[] in bytes */ sqlite3_int64 nHdr; /* Size of header in bytes */ int iField; /* Current field number */ u8 *pHdrPtr; u8 *pPtr; @@ -146,10 +155,35 @@ "CREATE TABLE x(" \ " pgno INTEGER," \ " child INTEGER," \ " schema TEXT HIDDEN" \ ")" + +/* +** Ensure the buffer passed as the first argument is at least nMin bytes +** in size. If an error occurs while attempting to resize the buffer, +** SQLITE_NOMEM is returned. Otherwise, SQLITE_OK. +*/ +static int dbdataBufferSize(DbdataBuffer *pBuf, sqlite3_int64 nMin){ + if( nMin>pBuf->nBuf ){ + sqlite3_int64 nNew = nMin+16384; + u8 *aNew = (u8*)sqlite3_realloc64(pBuf->aBuf, nNew); + + if( aNew==0 ) return SQLITE_NOMEM; + pBuf->aBuf = aNew; + pBuf->nBuf = nNew; + } + return SQLITE_OK; +} + +/* +** Release the allocation managed by buffer pBuf. +*/ +static void dbdataBufferFree(DbdataBuffer *pBuf){ + sqlite3_free(pBuf->aBuf); + memset(pBuf, 0, sizeof(*pBuf)); +} /* ** Connect to an sqlite_dbdata (pAux==0) or sqlite_dbptr (pAux!=0) virtual ** table. */ @@ -287,13 +321,13 @@ pCsr->iPgno = 1; pCsr->iCell = 0; pCsr->iField = 0; pCsr->bOnePage = 0; sqlite3_free(pCsr->aPage); - sqlite3_free(pCsr->pRec); - pCsr->pRec = 0; + dbdataBufferFree(&pCsr->rec); pCsr->aPage = 0; + pCsr->nRec = 0; } /* ** Close an sqlite_dbdata or sqlite_dbptr cursor. */ @@ -431,70 +465,91 @@ u32 enc, int eType, u8 *pData, sqlite3_int64 nData ){ - if( eType>=0 && dbdataValueBytes(eType)<=nData ){ - switch( eType ){ - case 0: - case 10: - case 11: - sqlite3_result_null(pCtx); - break; - - case 8: - sqlite3_result_int(pCtx, 0); - break; - case 9: - sqlite3_result_int(pCtx, 1); - break; - - case 1: case 2: case 3: case 4: case 5: case 6: case 7: { - sqlite3_uint64 v = (signed char)pData[0]; - pData++; - switch( eType ){ - case 7: - case 6: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2; - case 5: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2; - case 4: v = (v<<8) + pData[0]; pData++; - case 3: v = (v<<8) + pData[0]; pData++; - case 2: v = (v<<8) + pData[0]; pData++; - } - - if( eType==7 ){ - double r; - memcpy(&r, &v, sizeof(r)); - sqlite3_result_double(pCtx, r); - }else{ - sqlite3_result_int64(pCtx, (sqlite3_int64)v); - } - break; - } - - default: { - int n = ((eType-12) / 2); - if( eType % 2 ){ - switch( enc ){ -#ifndef SQLITE_OMIT_UTF16 - case SQLITE_UTF16BE: - sqlite3_result_text16be(pCtx, (void*)pData, n, SQLITE_TRANSIENT); - break; - case SQLITE_UTF16LE: - sqlite3_result_text16le(pCtx, (void*)pData, n, SQLITE_TRANSIENT); - break; -#endif - default: - sqlite3_result_text(pCtx, (char*)pData, n, SQLITE_TRANSIENT); - break; - } - }else{ - sqlite3_result_blob(pCtx, pData, n, SQLITE_TRANSIENT); - } - } - } - } -} + if( eType>=0 ){ + if( dbdataValueBytes(eType)<=nData ){ + switch( eType ){ + case 0: + case 10: + case 11: + sqlite3_result_null(pCtx); + break; + + case 8: + sqlite3_result_int(pCtx, 0); + break; + case 9: + sqlite3_result_int(pCtx, 1); + break; + + case 1: case 2: case 3: case 4: case 5: case 6: case 7: { + sqlite3_uint64 v = (signed char)pData[0]; + pData++; + switch( eType ){ + case 7: + case 6: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2; + case 5: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2; + case 4: v = (v<<8) + pData[0]; pData++; + case 3: v = (v<<8) + pData[0]; pData++; + case 2: v = (v<<8) + pData[0]; pData++; + } + + if( eType==7 ){ + double r; + memcpy(&r, &v, sizeof(r)); + sqlite3_result_double(pCtx, r); + }else{ + sqlite3_result_int64(pCtx, (sqlite3_int64)v); + } + break; + } + + default: { + int n = ((eType-12) / 2); + if( eType % 2 ){ + switch( enc ){ + #ifndef SQLITE_OMIT_UTF16 + case SQLITE_UTF16BE: + sqlite3_result_text16be(pCtx, (void*)pData, n, SQLITE_TRANSIENT); + break; + case SQLITE_UTF16LE: + sqlite3_result_text16le(pCtx, (void*)pData, n, SQLITE_TRANSIENT); + break; + #endif + default: + sqlite3_result_text(pCtx, (char*)pData, n, SQLITE_TRANSIENT); + break; + } + }else{ + sqlite3_result_blob(pCtx, pData, n, SQLITE_TRANSIENT); + } + } + } + }else{ + if( eType==7 ){ + sqlite3_result_double(pCtx, 0.0); + }else if( eType<7 ){ + sqlite3_result_int(pCtx, 0); + }else if( eType%2 ){ + sqlite3_result_text(pCtx, "", 0, SQLITE_STATIC); + }else{ + sqlite3_result_blob(pCtx, "", 0, SQLITE_STATIC); + } + } + } +} + +/* This macro is a copy of the MX_CELL() macro in the SQLite core. Given +** a page-size, it returns the maximum number of cells that may be present +** on the page. */ +#define DBDATA_MX_CELL(pgsz) ((pgsz-8)/6) + +/* Maximum number of fields that may appear in a single record. This is +** the "hard-limit", according to comments in sqliteLimit.h. */ +#define DBDATA_MX_FIELD 32676 /* ** Move an sqlite_dbdata or sqlite_dbptr cursor to the next entry. */ static int dbdataNext(sqlite3_vtab_cursor *pCursor){ @@ -520,10 +575,13 @@ } assert( iOff+3+2<=pCsr->nPage ); pCsr->iCell = pTab->bPtr ? -2 : 0; pCsr->nCell = get_uint16(&pCsr->aPage[iOff+3]); + if( pCsr->nCell>DBDATA_MX_CELL(pCsr->nPage) ){ + pCsr->nCell = DBDATA_MX_CELL(pCsr->nPage); + } } if( pTab->bPtr ){ if( pCsr->aPage[iOff]!=0x02 && pCsr->aPage[iOff]!=0x05 ){ pCsr->iCell = pCsr->nCell; @@ -537,11 +595,12 @@ }else{ return SQLITE_OK; } }else{ /* If there is no record loaded, load it now. */ - if( pCsr->pRec==0 ){ + assert( pCsr->rec.aBuf!=0 || pCsr->nRec==0 ); + if( pCsr->nRec==0 ){ int bHasRowid = 0; int nPointer = 0; sqlite3_int64 nPayload = 0; sqlite3_int64 nHdr = 0; int iHdr; @@ -564,27 +623,28 @@ } if( pCsr->iCell>=pCsr->nCell ){ bNextPage = 1; }else{ + int iCellPtr = iOff + 8 + nPointer + pCsr->iCell*2; - iOff += 8 + nPointer + pCsr->iCell*2; - if( iOff>pCsr->nPage ){ + if( iCellPtr>pCsr->nPage ){ bNextPage = 1; }else{ - iOff = get_uint16(&pCsr->aPage[iOff]); + iOff = get_uint16(&pCsr->aPage[iCellPtr]); } /* For an interior node cell, skip past the child-page number */ iOff += nPointer; /* Load the "byte of payload including overflow" field */ - if( bNextPage || iOff>pCsr->nPage ){ + if( bNextPage || iOff>pCsr->nPage || iOff<=iCellPtr ){ bNextPage = 1; }else{ iOff += dbdataGetVarintU32(&pCsr->aPage[iOff], &nPayload); if( nPayload>0x7fffff00 ) nPayload &= 0x3fff; + if( nPayload==0 ) nPayload = 1; } /* If this is a leaf intkey cell, load the rowid */ if( bHasRowid && !bNextPage && iOffnPage ){ iOff += dbdataGetVarint(&pCsr->aPage[iOff], &pCsr->iIntkey); @@ -615,17 +675,16 @@ }else{ /* Allocate space for payload. And a bit more to catch small buffer ** overruns caused by attempting to read a varint or similar from ** near the end of a corrupt record. */ - pCsr->pRec = (u8*)sqlite3_malloc64(nPayload+DBDATA_PADDING_BYTES); - if( pCsr->pRec==0 ) return SQLITE_NOMEM; - memset(pCsr->pRec, 0, nPayload+DBDATA_PADDING_BYTES); - pCsr->nRec = nPayload; + rc = dbdataBufferSize(&pCsr->rec, nPayload+DBDATA_PADDING_BYTES); + if( rc!=SQLITE_OK ) return rc; + assert( nPayload!=0 ); /* Load the nLocal bytes of payload */ - memcpy(pCsr->pRec, &pCsr->aPage[iOff], nLocal); + memcpy(pCsr->rec.aBuf, &pCsr->aPage[iOff], nLocal); iOff += nLocal; /* Load content from overflow pages */ if( nPayload>nLocal ){ sqlite3_int64 nRem = nPayload - nLocal; @@ -639,61 +698,64 @@ if( rc!=SQLITE_OK ) return rc; if( aOvfl==0 ) break; nCopy = U-4; if( nCopy>nRem ) nCopy = nRem; - memcpy(&pCsr->pRec[nPayload-nRem], &aOvfl[4], nCopy); + memcpy(&pCsr->rec.aBuf[nPayload-nRem], &aOvfl[4], nCopy); nRem -= nCopy; pgnoOvfl = get_uint32(aOvfl); sqlite3_free(aOvfl); } + nPayload -= nRem; } + memset(&pCsr->rec.aBuf[nPayload], 0, DBDATA_PADDING_BYTES); + pCsr->nRec = nPayload; - iHdr = dbdataGetVarintU32(pCsr->pRec, &nHdr); + iHdr = dbdataGetVarintU32(pCsr->rec.aBuf, &nHdr); if( nHdr>nPayload ) nHdr = 0; pCsr->nHdr = nHdr; - pCsr->pHdrPtr = &pCsr->pRec[iHdr]; - pCsr->pPtr = &pCsr->pRec[pCsr->nHdr]; + pCsr->pHdrPtr = &pCsr->rec.aBuf[iHdr]; + pCsr->pPtr = &pCsr->rec.aBuf[pCsr->nHdr]; pCsr->iField = (bHasRowid ? -1 : 0); } } }else{ pCsr->iField++; if( pCsr->iField>0 ){ sqlite3_int64 iType; - if( pCsr->pHdrPtr>&pCsr->pRec[pCsr->nRec] ){ + if( pCsr->pHdrPtr>=&pCsr->rec.aBuf[pCsr->nRec] + || pCsr->iField>=DBDATA_MX_FIELD + ){ bNextPage = 1; }else{ int szField = 0; pCsr->pHdrPtr += dbdataGetVarintU32(pCsr->pHdrPtr, &iType); szField = dbdataValueBytes(iType); - if( (pCsr->nRec - (pCsr->pPtr - pCsr->pRec))pPtr = &pCsr->pRec[pCsr->nRec]; + if( (pCsr->nRec - (pCsr->pPtr - pCsr->rec.aBuf))pPtr = &pCsr->rec.aBuf[pCsr->nRec]; }else{ pCsr->pPtr += szField; } } } } if( bNextPage ){ sqlite3_free(pCsr->aPage); - sqlite3_free(pCsr->pRec); pCsr->aPage = 0; - pCsr->pRec = 0; + pCsr->nRec = 0; if( pCsr->bOnePage ) return SQLITE_OK; pCsr->iPgno++; }else{ - if( pCsr->iField<0 || pCsr->pHdrPtr<&pCsr->pRec[pCsr->nHdr] ){ + if( pCsr->iField<0 || pCsr->pHdrPtr<&pCsr->rec.aBuf[pCsr->nHdr] ){ return SQLITE_OK; } /* Advance to the next cell. The next iteration of the loop will load ** the record and so on. */ - sqlite3_free(pCsr->pRec); - pCsr->pRec = 0; + pCsr->nRec = 0; pCsr->iCell++; } } } @@ -879,16 +941,16 @@ sqlite3_result_int(ctx, pCsr->iField); break; case DBDATA_COLUMN_VALUE: { if( pCsr->iField<0 ){ sqlite3_result_int64(ctx, pCsr->iIntkey); - }else if( &pCsr->pRec[pCsr->nRec] >= pCsr->pPtr ){ + }else if( &pCsr->rec.aBuf[pCsr->nRec] >= pCsr->pPtr ){ sqlite3_int64 iType; dbdataGetVarintU32(pCsr->pHdrPtr, &iType); dbdataValue( ctx, pCsr->enc, iType, pCsr->pPtr, - &pCsr->pRec[pCsr->nRec] - pCsr->pPtr + &pCsr->rec.aBuf[pCsr->nRec] - pCsr->pPtr ); } break; } } Index: ext/recover/recover1.test ================================================================== --- ext/recover/recover1.test +++ ext/recover/recover1.test @@ -340,27 +340,38 @@ INSERT INTO t1 VALUES('abc', 'def'); PRAGMA writable_schema = 1; DELETE FROM sqlite_schema WHERE name='t1'; } - proc my_sql_hook {sql} { + proc my_sql_hook2 {sql} { if {[string match "INSERT INTO lostandfound*" $sql]} { lappend ::script $sql } return 0 } do_test 18.$enc.2 { set ::script [list] - set R [sqlite3_recover_init_sql db main my_sql_hook] + set R [sqlite3_recover_init_sql db main my_sql_hook2] $R config lostandfound lostandfound $R run $R finish set ::script } {{INSERT INTO lostandfound VALUES(2, 2, 2, 1, 'abc', 'def')}} } +#------------------------------------------------------------------------- +reset_db +do_execsql_test 19.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT); + INSERT INTO t1 VALUES(1, 'one'); + INSERT INTO t1 VALUES(2, 'two'); + + ALTER TABLE t1 ADD COLUMN c NOT NULL DEFAULT 13; + INSERT INTO t1 VALUES(3, 'three', 'hello world'); +} +do_recover_test 19.1 finish_test Index: ext/recover/recovercorrupt2.test ================================================================== --- ext/recover/recovercorrupt2.test +++ ext/recover/recovercorrupt2.test @@ -521,8 +521,36 @@ 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}} + +reset_db +breakpoint +do_test 8.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +| size 8192 pagesize 4096 filename db.sqlite +| page 1 offset 0 +| 0: ac ae b3 76 74 65 20 66 6f 72 6d 61 74 20 33 00 ...vte format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 01 00 00 00 02 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................ +| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ +| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 ................ +| 96: 00 2e 76 8a 0d ff ff ff 1e 0f cb 00 0f cb 00 00 ..v............. +| 4032: 00 00 00 00 00 00 00 00 00 00 00 33 01 06 17 19 ...........3.... +| 4048: 19 01 43 74 61 62 6c 65 54 61 62 6c 65 30 54 61 ..CtableTable0Ta +| 4064: 62 6c 65 30 02 43 52 45 41 54 45 20 54 41 42 4c ble0.CREATE TABL +| 4080: 45 20 54 61 62 6c 65 30 20 28 43 6f 6c 30 20 29 E Table0 (Col0 ) +| page 2 offset 4096 +| 0: 0d 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 ................ +| end db.sqlite +}]} {} + +do_test 8.1 { + set R [sqlite3_recover_init db main test.db2] + catch { $R run } + list [catch { $R finish } msg] $msg +} {0 {}} finish_test ADDED ext/recover/recovercorrupt3.test Index: ext/recover/recovercorrupt3.test ================================================================== --- /dev/null +++ ext/recover/recovercorrupt3.test @@ -0,0 +1,549 @@ +# 2024 May 1 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# + +source [file join [file dirname [info script]] recover_common.tcl] +set testprefix recovercorrupt3 + +#| 0: d5 d5 9b d5 d5 d5 d5 d5 d5 d5 d5 d5 d5 d5 d5 d5 ................ +#| 16: 04 00 00 00 1d 00 00 00 00 00 00 00 5f 5f 5f 5f ............____ +#| 32: 5f 5f 5f 5f 5f 5f 5f 5f 71 5f 5f 5f 02 02 02 02 ________q___.... +#| 48: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +#| 64: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +#| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +#| 96: 02 02 02 02 + +#------------------------------------------------------------------------- +reset_db +do_test 1.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +| size 3821 pagesize 1024 filename clusterfuzz-testcase-sql_recovery_fuzzer-5803962339885056 +| 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 01 00 00 00 02 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................ +| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ +| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 ................ +| 96: 00 2e 7a 70 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 112: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 128: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 144: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 160: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 176: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 192: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 208: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 224: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 240: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 256: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 272: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 288: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 352: 02 02 02 02 a0 02 02 02 02 02 02 02 02 02 02 02 ................ +| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 29 29 ..............)) +| 464: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 480: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 496: 29 29 29 dd dd dd dd dd dd dd dd dd dd dd dd dd )))............. +| 512: dd dd dd dd dd dd dd dd dd 6e 69 d2 e9 e9 e9 d2 .........ni..... +| 528: d2 d2 d2 d2 dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 544: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 560: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 576: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 592: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 608: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 624: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 640: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 656: dd dd dd dd dd dd dd da dd dd dd dd dd dd dd dd ................ +| 672: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 688: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 704: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 720: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 736: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 752: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 768: dd dd dd dd dd dd dd dd dd dd dd dd dd 29 29 29 .............))) +| 784: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 800: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 816: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 832: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 848: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 864: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 880: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 896: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 912: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 928: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 944: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 960: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 976: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 992: 29 29 29 29 29 29 29 29 29 29 dd dd dd dd dd dd ))))))))))...... +| 1008: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| page 2 offset 1024 +| 0: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 16: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 32: dd dd 6e 69 d2 e9 e9 e9 d2 d2 d2 d2 d2 dd dd dd ..ni............ +| 48: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 64: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 80: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 96: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 112: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 128: dd dd dd dd dd dd 29 29 29 29 29 29 29 29 29 29 ......)))))))))) +| 144: 29 29 29 29 29 29 29 29 29 ad a5 29 29 29 29 00 )))))))))..)))). +| 160: 75 9c 11 00 5b e5 64 28 7c ca 09 69 28 2d 69 00 u...[.d(|..i(-i. +| 176: 85 88 6c 81 48 83 a0 93 c0 c0 82 8b 81 84 85 f9 ..l.H........... +| 192: 88 7a 00 7f 00 96 40 7b 12 4b 84 75 a0 00 99 a0 .z....@..K.u.... +| 208: df a0 7e 81 c6 90 8f 7f 84 85 cc 84 82 90 88 60 ..~............` +| 224: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 240: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 256: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 272: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 288: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 02 02 ................ +| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 352: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 464: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 480: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 496: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 512: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 528: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 544: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 560: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 576: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 592: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 608: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 624: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 640: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 656: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 672: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 688: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 704: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 720: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 736: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 752: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 768: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 784: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 800: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 816: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 832: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 848: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 864: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 880: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 896: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 912: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 928: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 944: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 960: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 976: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 992: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 1008: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| page 3 offset 2048 +| 0: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 16: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 32: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 48: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 64: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 96: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 112: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 128: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 144: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 160: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 176: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 192: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 208: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 224: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 240: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 256: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 272: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 288: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 352: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 464: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 480: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 496: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 512: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 528: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 544: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 560: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 576: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 592: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 608: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 624: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 640: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 656: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 672: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 688: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 704: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 720: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 736: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 752: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 768: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 784: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 800: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 816: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 832: 02 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 848: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 864: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 880: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 896: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 912: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 928: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 944: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 960: 80 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 976: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 992: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 1008: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| page 4 offset 3072 +| 0: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 16: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 32: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 48: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 64: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 96: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 112: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 128: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 144: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 160: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 176: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 192: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 208: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 224: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 240: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 256: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 272: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 288: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 352: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 464: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 480: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 496: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 512: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 528: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 544: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 560: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 576: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 592: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 608: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 624: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 640: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 656: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 672: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 688: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 704: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 720: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 736: 5f 5f 5f 5f 5f 5f 5f 00 d5 fe fe fe 08 00 00 00 _______......... +| end clusterfuzz-testcase-sql_recovery_fuzzer-5803962339885056 +}]} {} + +sqlite3_dbdata_init db +do_execsql_test 1.1 { + PRAGMA writable_schema = 1; +} + +do_test 1.2 { + set R [sqlite3_recover_init db main test.db2] + $R run + $R finish +} {} + +#------------------------------------------------------------------------- +reset_db +do_test 2.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +| size 3821 pagesize 1024 filename clusterfuzz-testcase-sql_recovery_fuzzer-5803962339885056 +| 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: 04 00 01 01 00 40 20 20 00 00 00 01 00 00 00 02 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................ +| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ +| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 ................ +| 96: 00 2e 7a 70 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 112: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 128: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 144: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 160: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 176: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 192: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 208: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 224: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 240: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 256: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 272: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 288: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 352: 02 02 02 02 a0 02 02 02 02 02 02 02 02 02 02 02 ................ +| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 29 29 ..............)) +| 464: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 480: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 496: 29 29 29 dd dd dd dd dd dd dd dd dd dd dd dd dd )))............. +| 512: dd dd dd dd dd dd dd dd dd 6e 69 d2 e9 e9 e9 d2 .........ni..... +| 528: d2 d2 d2 d2 dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 544: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 560: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 576: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 592: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 608: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 624: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 640: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 656: dd dd dd dd dd dd dd da dd dd dd dd dd dd dd dd ................ +| 672: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 688: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 704: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 720: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 736: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 752: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 768: dd dd dd dd dd dd dd dd dd dd dd dd dd 29 29 29 .............))) +| 784: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 800: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 816: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 832: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 848: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 864: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 880: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 896: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 912: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 928: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 944: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 960: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 976: 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 )))))))))))))))) +| 992: 29 29 29 29 29 29 29 29 29 29 dd dd dd dd dd dd ))))))))))...... +| 1008: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| page 2 offset 1024 +| 0: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 16: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 32: dd dd 6e 69 d2 e9 e9 e9 d2 d2 d2 d2 d2 dd dd dd ..ni............ +| 48: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 64: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 80: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 96: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 112: dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ................ +| 128: dd dd dd dd dd dd 29 29 29 29 29 29 29 29 29 29 ......)))))))))) +| 144: 29 29 29 29 29 29 29 29 29 ad a5 29 29 29 29 00 )))))))))..)))). +| 160: 75 9c 11 00 5b e5 64 28 7c ca 09 69 28 2d 69 00 u...[.d(|..i(-i. +| 176: 85 88 6c 81 48 83 a0 93 c0 c0 82 8b 81 84 85 f9 ..l.H........... +| 192: 88 7a 00 7f 00 96 40 7b 12 4b 84 75 a0 00 99 a0 .z....@..K.u.... +| 208: df a0 7e 81 c6 90 8f 7f 84 85 cc 84 82 90 88 60 ..~............` +| 224: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 240: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 256: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 272: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 288: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 02 02 ................ +| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 352: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 464: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 480: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 496: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 512: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 528: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 544: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 560: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 576: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 592: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 608: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 624: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 640: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 656: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 672: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 688: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 704: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 720: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 736: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 752: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 768: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 784: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 800: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 816: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 832: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 848: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 864: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 880: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 896: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 912: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 928: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 944: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 960: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 976: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 992: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 1008: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| page 3 offset 2048 +| 0: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 16: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 32: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 48: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 64: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 96: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 112: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 128: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 144: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 160: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 176: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 192: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 208: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 224: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 240: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 256: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 272: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 288: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 352: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 464: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 480: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 496: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 512: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 528: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 544: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 560: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 576: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 592: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 608: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 624: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 640: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 656: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 672: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 688: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 704: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 720: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 736: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 752: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 768: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 784: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 800: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 816: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 832: 02 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 848: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 864: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 880: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 896: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 912: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 928: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 944: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 960: 80 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 976: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 992: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 1008: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| page 4 offset 3072 +| 0: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 16: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 32: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 48: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 64: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 96: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 112: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 128: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 144: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 160: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 176: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 192: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 208: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 224: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 240: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 256: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 272: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 288: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 304: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 320: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 336: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 352: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 368: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 384: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 400: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 416: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 432: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 448: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 464: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 480: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 496: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 512: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 528: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 544: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 560: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 576: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 592: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 608: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 624: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 640: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 656: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 672: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 688: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 704: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 720: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 ................ +| 736: 5f 5f 5f 5f 5f 5f 5f 00 d5 fe fe fe 08 00 00 00 _______......... +| end clusterfuzz-testcase-sql_recovery_fuzzer-5803962339885056 +}]} {} + +sqlite3_dbdata_init db +do_execsql_test 2.1 { + PRAGMA writable_schema = 1; +} + +do_test 2.2 { + set R [sqlite3_recover_init db main test.db2] + $R run + $R finish +} {} + +finish_test + ADDED ext/recover/recovercorrupt4.test Index: ext/recover/recovercorrupt4.test ================================================================== --- /dev/null +++ ext/recover/recovercorrupt4.test @@ -0,0 +1,64 @@ +# 2024 May 15 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# + +source [file join [file dirname [info script]] recover_common.tcl] +set testprefix recovercorrupt4 + +database_may_be_corrupt + +if {[permutation]!="inmemory_journal"} { + # This test cannot be run with the inmemory_journal permutation, as it + # must open a truncated, corrupt, database file. With the inmemory_journal + # permutation, this fails (SQLITE_CORRUPT error) when the [sqlite3] wrapper + # executes "PRAGMA journal_mode = memory". + do_execsql_test 1.0 { + CREATE TABLE rows(indexed INTEGER NOT NULL, unindexed INTEGER NOT NULL, filler BLOB NOT NULL DEFAULT 13); + -- CREATE UNIQUE INDEX rows_index ON rows(indexed); + INSERT INTO rows(indexed, unindexed, filler) VALUES(1, 1, x'31'); + INSERT INTO rows(indexed, unindexed, filler) VALUES(2, 2, x'32'); + INSERT INTO rows(indexed, unindexed, filler) VALUES(4, 4, x'34'); + INSERT INTO rows(indexed, unindexed, filler) VALUES(8, 8, randomblob(2048)); + } + + db close + + do_test 1.1 { + set sz [expr [file size test.db] - 1024] + set fd [open test.db] + fconfigure $fd -encoding binary -translation binary + + set data [read $fd $sz] + set fd2 [open test.db2 w] + fconfigure $fd2 -encoding binary -translation binary + puts -nonewline $fd2 $data + close $fd2 + set {} {} + } {} + + do_test 1.2 { + forcedelete test.db3 + sqlite3 db test.db2 + set R [sqlite3_recover_init db main test.db3] + $R run + $R finish + } {} + + do_test 1.3 { + sqlite3 db test.db3 + execsql { + SELECT indexed, unindexed FROM rows + } + } {1 1 2 2 4 4 8 8} +} + +finish_test + Index: ext/recover/sqlite3recover.c ================================================================== --- ext/recover/sqlite3recover.c +++ ext/recover/sqlite3recover.c @@ -1187,11 +1187,11 @@ if( rc==SQLITE_OK ){ recoverSqlCallback(p, zSql); if( bTable && !bVirtual ){ if( SQLITE_ROW==sqlite3_step(pTblname) ){ const char *zTbl = (const char*)sqlite3_column_text(pTblname, 0); - recoverAddTable(p, zTbl, iRoot); + if( zTbl ) recoverAddTable(p, zTbl, iRoot); } recoverReset(p, pTblname); } }else if( rc!=SQLITE_ERROR ){ recoverDbError(p, p->dbOut); ADDED ext/session/sessionchange.test Index: ext/session/sessionchange.test ================================================================== --- /dev/null +++ ext/session/sessionchange.test @@ -0,0 +1,101 @@ +# 2011 March 07 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] session_common.tcl] +source $testdir/tester.tcl +ifcapable !session {finish_test; return} + +set testprefix sessionchange + +do_execsql_test 1.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); +} + +do_test 1.1 { + set C [changeset_from_sql { + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(10, 20, 30); + }] + + set res [list] + set iter [sqlite3changeset_start $C] + while {[$iter next]=="SQLITE_ROW"} { + lappend res [$iter data] + } + $iter finalize + + set res +} [list \ + {INSERT t1 0 X.. {} {i 10 i 20 i 30}} \ + {INSERT t1 0 X.. {} {i 1 i 2 i 3}} \ +] + + +do_test 1.2 { + sqlite3changegroup grp + set iter [sqlite3changeset_start $C] + while {[$iter next]=="SQLITE_ROW"} { + grp add_change $iter + } + $iter finalize + + set res [list] + grp output + sqlite3session_foreach c [grp output] { lappend res $c } + grp delete + set res +} [list \ + {INSERT t1 0 X.. {} {i 10 i 20 i 30}} \ + {INSERT t1 0 X.. {} {i 1 i 2 i 3}} \ +] + +do_test 1.3.1 { + set iter [sqlite3changeset_start $C] + sqlite3changegroup grp + list [catch { grp add_change $iter } msg] $msg +} {1 SQLITE_ERROR} +do_test 1.3.2 { + while {[$iter next]=="SQLITE_ROW"} { } + list [catch { grp add_change $iter } msg] $msg +} {1 SQLITE_ERROR} +grp delete +$iter finalize + +do_test 1.4 { + set res [list] + set iter [sqlite3changeset_start -invert $C] + while {[$iter next]=="SQLITE_ROW"} { + lappend res [$iter data] + } + $iter finalize + set res +} [list \ + {DELETE t1 0 X.. {i 10 i 20 i 30} {}} \ + {DELETE t1 0 X.. {i 1 i 2 i 3} {}} \ +] + +do_test 1.5 { + sqlite3changegroup grp + set iter [sqlite3changeset_start -invert $C] + $iter next + list [catch { grp add_change $iter } msg] $msg +} {1 SQLITE_ERROR} +$iter finalize +grp delete + + + +finish_test ADDED ext/session/sessionconflict.test Index: ext/session/sessionconflict.test ================================================================== --- /dev/null +++ ext/session/sessionconflict.test @@ -0,0 +1,76 @@ +# 2011 March 07 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] session_common.tcl] +source $testdir/tester.tcl +ifcapable !session {finish_test; return} + +set testprefix sessionconflict + +db close +sqlite3_shutdown +test_sqlite3_log log +proc log {code msg} { puts "LOG $code $msg" } +sqlite3 db test.db + +forcedelete test.db2 +sqlite3 db2 test.db2 + +do_test 1.0 { + do_common_sql { + CREATE TABLE t1(a PRIMARY KEY, b, c UNIQUE); + INSERT INTO t1 VALUES(1, 1, 1); + INSERT INTO t1 VALUES(2, 2, 2); + INSERT INTO t1 VALUES(3, 3, 3); + } +} {} + +do_execsql_test -db db2 1.1 { + INSERT INTO t1 VALUES(6, 6, 6); +} + +proc xConflict {args} { + return "ABORT" +} + + +do_test 1.2 { + set chng [changeset_from_sql { + UPDATE t1 SET b=10, c=10 WHERE a=1; + UPDATE t1 SET b=444 WHERE a=2; + INSERT INTO t1 VALUES(4, 4, 4); + INSERT INTO t1 VALUES(5, 5, 5); + INSERT INTO t1 VALUES(6, 6, 6); + }] + + execsql BEGIN db2 + set res [list [catch { sqlite3changeset_apply db2 $chng xConflict } msg] $msg] + execsql ROLLBACK db2 + set res +} {1 SQLITE_ABORT} + +do_execsql_test -db db2 1.3 { + SELECT * FROM t1; +} { + 1 1 1 + 2 2 2 + 3 3 3 + 6 6 6 +} + + + +finish_test Index: ext/session/sqlite3session.c ================================================================== --- ext/session/sqlite3session.c +++ ext/session/sqlite3session.c @@ -3683,18 +3683,18 @@ ** sufficient either for the 'T' or 'P' byte and the varint that follows ** it, or for the two single byte values otherwise. */ p->rc = sessionInputBuffer(&p->in, 2); if( p->rc!=SQLITE_OK ) return p->rc; + sessionDiscardData(&p->in); + p->in.iCurrent = p->in.iNext; + /* If the iterator is already at the end of the changeset, return DONE. */ if( p->in.iNext>=p->in.nData ){ return SQLITE_DONE; } - sessionDiscardData(&p->in); - p->in.iCurrent = p->in.iNext; - op = p->in.aData[p->in.iNext++]; while( op=='T' || op=='P' ){ if( pbNew ) *pbNew = 1; p->bPatchset = (op=='P'); if( sessionChangesetReadTblhdr(p) ) return p->rc; @@ -5425,10 +5425,11 @@ */ struct sqlite3_changegroup { int rc; /* Error code */ int bPatch; /* True to accumulate patchsets */ SessionTable *pList; /* List of tables in current patch */ + SessionBuffer rec; sqlite3 *db; /* Configured by changegroup_schema() */ char *zDb; /* Configured by changegroup_schema() */ }; @@ -5723,112 +5724,132 @@ return rc; } /* -** Add all changes in the changeset traversed by the iterator passed as -** the first argument to the changegroup hash tables. +** Locate or create a SessionTable object that may be used to add the +** change currently pointed to by iterator pIter to changegroup pGrp. +** If successful, set output variable (*ppTab) to point to the table +** object and return SQLITE_OK. Otherwise, if some error occurs, return +** an SQLite error code and leave (*ppTab) set to NULL. +*/ +static int sessionChangesetFindTable( + sqlite3_changegroup *pGrp, + const char *zTab, + sqlite3_changeset_iter *pIter, + SessionTable **ppTab +){ + int rc = SQLITE_OK; + SessionTable *pTab = 0; + int nTab = (int)strlen(zTab); + u8 *abPK = 0; + int nCol = 0; + + *ppTab = 0; + sqlite3changeset_pk(pIter, &abPK, &nCol); + + /* Search the list for an existing table */ + for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){ + if( 0==sqlite3_strnicmp(pTab->zName, zTab, nTab+1) ) break; + } + + /* If one was not found above, create a new table now */ + if( !pTab ){ + SessionTable **ppNew; + + pTab = sqlite3_malloc64(sizeof(SessionTable) + nCol + nTab+1); + if( !pTab ){ + return SQLITE_NOMEM; + } + memset(pTab, 0, sizeof(SessionTable)); + pTab->nCol = nCol; + pTab->abPK = (u8*)&pTab[1]; + memcpy(pTab->abPK, abPK, nCol); + pTab->zName = (char*)&pTab->abPK[nCol]; + memcpy(pTab->zName, zTab, nTab+1); + + if( pGrp->db ){ + pTab->nCol = 0; + rc = sessionInitTable(0, pTab, pGrp->db, pGrp->zDb); + if( rc ){ + assert( pTab->azCol==0 ); + sqlite3_free(pTab); + return rc; + } + } + + /* The new object must be linked on to the end of the list, not + ** simply added to the start of it. This is to ensure that the + ** tables within the output of sqlite3changegroup_output() are in + ** the right order. */ + for(ppNew=&pGrp->pList; *ppNew; ppNew=&(*ppNew)->pNext); + *ppNew = pTab; + } + + /* Check that the table is compatible. */ + if( !sessionChangesetCheckCompat(pTab, nCol, abPK) ){ + rc = SQLITE_SCHEMA; + } + + *ppTab = pTab; + return rc; +} + +/* +** Add the change currently indicated by iterator pIter to the hash table +** belonging to changegroup pGrp. */ -static int sessionChangesetToHash( - sqlite3_changeset_iter *pIter, /* Iterator to read from */ - sqlite3_changegroup *pGrp, /* Changegroup object to add changeset to */ - int bRebase /* True if hash table is for rebasing */ +static int sessionOneChangeToHash( + sqlite3_changegroup *pGrp, + sqlite3_changeset_iter *pIter, + int bRebase ){ - u8 *aRec; - int nRec; int rc = SQLITE_OK; + int nCol = 0; + int op = 0; + int iHash = 0; + int bIndirect = 0; + SessionChange *pChange = 0; + SessionChange *pExist = 0; + SessionChange **pp = 0; SessionTable *pTab = 0; - SessionBuffer rec = {0, 0, 0}; - - while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, 0) ){ - const char *zNew; - int nCol; - int op; - int iHash; - int bIndirect; - SessionChange *pChange; - SessionChange *pExist = 0; - SessionChange **pp; - - /* Ensure that only changesets, or only patchsets, but not a mixture - ** of both, are being combined. It is an error to try to combine a - ** changeset and a patchset. */ - if( pGrp->pList==0 ){ - pGrp->bPatch = pIter->bPatchset; - }else if( pIter->bPatchset!=pGrp->bPatch ){ - rc = SQLITE_ERROR; - break; - } - - sqlite3changeset_op(pIter, &zNew, &nCol, &op, &bIndirect); - if( !pTab || sqlite3_stricmp(zNew, pTab->zName) ){ - /* Search the list for a matching table */ - int nNew = (int)strlen(zNew); - u8 *abPK; - - sqlite3changeset_pk(pIter, &abPK, 0); - for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){ - if( 0==sqlite3_strnicmp(pTab->zName, zNew, nNew+1) ) break; - } - if( !pTab ){ - SessionTable **ppTab; - - pTab = sqlite3_malloc64(sizeof(SessionTable) + nCol + nNew+1); - if( !pTab ){ - rc = SQLITE_NOMEM; - break; - } - memset(pTab, 0, sizeof(SessionTable)); - pTab->nCol = nCol; - pTab->abPK = (u8*)&pTab[1]; - memcpy(pTab->abPK, abPK, nCol); - pTab->zName = (char*)&pTab->abPK[nCol]; - memcpy(pTab->zName, zNew, nNew+1); - - if( pGrp->db ){ - pTab->nCol = 0; - rc = sessionInitTable(0, pTab, pGrp->db, pGrp->zDb); - if( rc ){ - assert( pTab->azCol==0 ); - sqlite3_free(pTab); - break; - } - } - - /* The new object must be linked on to the end of the list, not - ** simply added to the start of it. This is to ensure that the - ** tables within the output of sqlite3changegroup_output() are in - ** the right order. */ - for(ppTab=&pGrp->pList; *ppTab; ppTab=&(*ppTab)->pNext); - *ppTab = pTab; - } - - if( !sessionChangesetCheckCompat(pTab, nCol, abPK) ){ - rc = SQLITE_SCHEMA; - break; - } - } - - if( nColnCol ){ - assert( pGrp->db ); - rc = sessionChangesetExtendRecord(pGrp, pTab, nCol, op, aRec, nRec, &rec); - if( rc ) break; - aRec = rec.aBuf; - nRec = rec.nBuf; - } - - if( sessionGrowHash(0, pIter->bPatchset, pTab) ){ - rc = SQLITE_NOMEM; - break; - } + u8 *aRec = &pIter->in.aData[pIter->in.iCurrent + 2]; + int nRec = (pIter->in.iNext - pIter->in.iCurrent) - 2; + + /* Ensure that only changesets, or only patchsets, but not a mixture + ** of both, are being combined. It is an error to try to combine a + ** changeset and a patchset. */ + if( pGrp->pList==0 ){ + pGrp->bPatch = pIter->bPatchset; + }else if( pIter->bPatchset!=pGrp->bPatch ){ + rc = SQLITE_ERROR; + } + + if( rc==SQLITE_OK ){ + const char *zTab = 0; + sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect); + rc = sessionChangesetFindTable(pGrp, zTab, pIter, &pTab); + } + + if( rc==SQLITE_OK && nColnCol ){ + SessionBuffer *pBuf = &pGrp->rec; + rc = sessionChangesetExtendRecord(pGrp, pTab, nCol, op, aRec, nRec, pBuf); + aRec = pBuf->aBuf; + nRec = pBuf->nBuf; + assert( pGrp->db ); + } + + if( rc==SQLITE_OK && sessionGrowHash(0, pIter->bPatchset, pTab) ){ + rc = SQLITE_NOMEM; + } + + if( rc==SQLITE_OK ){ + /* Search for existing entry. If found, remove it from the hash table. + ** Code below may link it back in. */ iHash = sessionChangeHash( pTab, (pIter->bPatchset && op==SQLITE_DELETE), aRec, pTab->nChange ); - - /* Search for existing entry. If found, remove it from the hash table. - ** Code below may link it back in. - */ for(pp=&pTab->apChange[iHash]; *pp; pp=&(*pp)->pNext){ int bPkOnly1 = 0; int bPkOnly2 = 0; if( pIter->bPatchset ){ bPkOnly1 = (*pp)->op==SQLITE_DELETE; @@ -5839,23 +5860,45 @@ *pp = (*pp)->pNext; pTab->nEntry--; break; } } + } + if( rc==SQLITE_OK ){ rc = sessionChangeMerge(pTab, bRebase, pIter->bPatchset, pExist, op, bIndirect, aRec, nRec, &pChange ); - if( rc ) break; - if( pChange ){ - pChange->pNext = pTab->apChange[iHash]; - pTab->apChange[iHash] = pChange; - pTab->nEntry++; - } + } + if( rc==SQLITE_OK && pChange ){ + pChange->pNext = pTab->apChange[iHash]; + pTab->apChange[iHash] = pChange; + pTab->nEntry++; + } + + if( rc==SQLITE_OK ) rc = pIter->rc; + return rc; +} + +/* +** Add all changes in the changeset traversed by the iterator passed as +** the first argument to the changegroup hash tables. +*/ +static int sessionChangesetToHash( + sqlite3_changeset_iter *pIter, /* Iterator to read from */ + sqlite3_changegroup *pGrp, /* Changegroup object to add changeset to */ + int bRebase /* True if hash table is for rebasing */ +){ + u8 *aRec; + int nRec; + int rc = SQLITE_OK; + + while( SQLITE_ROW==(sessionChangesetNext(pIter, &aRec, &nRec, 0)) ){ + rc = sessionOneChangeToHash(pGrp, pIter, bRebase); + if( rc!=SQLITE_OK ) break; } - sqlite3_free(rec.aBuf); if( rc==SQLITE_OK ) rc = pIter->rc; return rc; } /* @@ -5978,10 +6021,27 @@ rc = sessionChangesetToHash(pIter, pGrp, 0); } sqlite3changeset_finalize(pIter); return rc; } + +/* +** Add a single change to a changeset-group. +*/ +int sqlite3changegroup_add_change( + sqlite3_changegroup *pGrp, + sqlite3_changeset_iter *pIter +){ + if( pIter->in.iCurrent==pIter->in.iNext + || pIter->rc!=SQLITE_OK + || pIter->bInvert + ){ + /* Iterator does not point to any valid entry or is an INVERT iterator. */ + return SQLITE_ERROR; + } + return sessionOneChangeToHash(pGrp, pIter, 0); +} /* ** Obtain a buffer containing a changeset representing the concatenation ** of all changesets added to the group so far. */ @@ -6028,10 +6088,11 @@ */ void sqlite3changegroup_delete(sqlite3_changegroup *pGrp){ if( pGrp ){ sqlite3_free(pGrp->zDb); sessionDeleteTable(0, pGrp->pList); + sqlite3_free(pGrp->rec.aBuf); sqlite3_free(pGrp); } } /* @@ -6429,10 +6490,11 @@ ** Destroy a rebaser object */ void sqlite3rebaser_delete(sqlite3_rebaser *p){ if( p ){ sessionDeleteTable(0, p->grp.pList); + sqlite3_free(p->grp.rec.aBuf); sqlite3_free(p); } } /* Index: ext/session/sqlite3session.h ================================================================== --- ext/session/sqlite3session.h +++ ext/session/sqlite3session.h @@ -1054,10 +1054,34 @@ ** ** In all cases, if an error occurs the state of the final contents of the ** changegroup is undefined. If no error occurs, SQLITE_OK is returned. */ int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); + +/* +** CAPI3REF: Add A Single Change To A Changegroup +** METHOD: sqlite3_changegroup +** +** This function adds the single change currently indicated by the iterator +** passed as the second argument to the changegroup object. The rules for +** adding the change are just as described for [sqlite3changegroup_add()]. +** +** If the change is successfully added to the changegroup, SQLITE_OK is +** returned. Otherwise, an SQLite error code is returned. +** +** The iterator must point to a valid entry when this function is called. +** If it does not, SQLITE_ERROR is returned and no change is added to the +** changegroup. Additionally, the iterator must not have been opened with +** the SQLITE_CHANGESETAPPLY_INVERT flag. In this case SQLITE_ERROR is also +** returned. +*/ +int sqlite3changegroup_add_change( + sqlite3_changegroup*, + sqlite3_changeset_iter* +); + + /* ** CAPI3REF: Obtain A Composite Changeset From A Changegroup ** METHOD: sqlite3_changegroup ** Index: ext/session/test_session.c ================================================================== --- ext/session/test_session.c +++ ext/session/test_session.c @@ -1035,10 +1035,68 @@ Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n)); } sqlite3_free(sOut.p); 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; ipGrp, pIter->pIter); + if( rc!=SQLITE_OK ){ + rc = test_session_error(interp, rc, 0); + } + break; + }; default: { /* delete */ assert( iSub==3 ); Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); break; @@ -1582,18 +1606,128 @@ test_changegroup_del ); Tcl_SetObjResult(interp, objv[1]); return TCL_OK; } + +extern const char *sqlite3ErrName(int); + +/* +** Destructor for Tcl iterator command object. +*/ +static void test_iter_del(void *clientData){ + TestChangeIter *p = (TestChangeIter*)clientData; + sqlite3changeset_finalize(p->pIter); + ckfree(p); +} + +static int SQLITE_TCLAPI test_iter_cmd( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + static const char *aSub[] = { + "next", /* 0 */ + "data", /* 1 */ + "finalize", /* 2 */ + 0 + }; + int iSub = 0; + + TestChangeIter *p = (TestChangeIter*)clientData; + int rc = SQLITE_OK; + + if( objc<2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "CMD"); + return TCL_ERROR; + } + + if( Tcl_GetIndexFromObj(interp, objv[1], aSub, "sub-command", 0, &iSub) ){ + return TCL_ERROR; + } + switch( iSub ){ + case 0: + rc = sqlite3changeset_next(p->pIter); + Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); + break; + case 1: + Tcl_SetObjResult(interp, testIterData(p->pIter)); + break; + case 2: + rc = sqlite3changeset_finalize(p->pIter); + p->pIter = 0; + Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); + Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); + break; + default: + assert( 0 ); + break; + } + + return TCL_OK; +} + +/* +** Tclcmd: sqlite3changeset_start ?-invert? CHANGESET +*/ +static int SQLITE_TCLAPI test_sqlite3changeset_start( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int isInvert = 0; + void *pChangeset = 0; /* Buffer containing changeset */ + int nChangeset = 0; /* Size of buffer aChangeset in bytes */ + TestChangeIter *pNew = 0; + sqlite3_changeset_iter *pIter = 0; + int flags = 0; + int rc = SQLITE_OK; + + static int iCmd = 1; + char zCmd[64]; + + if( objc==3 ){ + int n = 0; + const char *z = Tcl_GetStringFromObj(objv[1], &n); + isInvert = (n>=2 && sqlite3_strnicmp(z, "-invert", n)==0); + } + + if( objc!=2 && (objc!=3 || !isInvert) ){ + Tcl_WrongNumArgs(interp, 1, objv, "?-invert? CHANGESET"); + return TCL_ERROR; + } + + flags = isInvert ? SQLITE_CHANGESETSTART_INVERT : 0; + pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[objc-1], &nChangeset); + rc = sqlite3changeset_start_v2(&pIter, nChangeset, pChangeset, flags); + if( rc!=SQLITE_OK ){ + char *zErr = sqlite3_mprintf( + "error in sqlite3changeset_start_v2() - %d", rc + ); + Tcl_AppendResult(interp, zErr, (char*)0); + return TCL_ERROR; + } + + pNew = (TestChangeIter*)ckalloc(sizeof(TestChangeIter)); + pNew->pIter = pIter; + + sprintf(zCmd, "csiter%d", iCmd++); + Tcl_CreateObjCommand(interp, zCmd, test_iter_cmd, (void*)pNew, test_iter_del); + Tcl_SetObjResult(interp, Tcl_NewStringObj(zCmd, -1)); + return TCL_OK; +} int TestSession_Init(Tcl_Interp *interp){ struct Cmd { const char *zCmd; Tcl_ObjCmdProc *xProc; } aCmd[] = { { "sqlite3session", test_sqlite3session }, { "sqlite3changegroup", test_sqlite3changegroup }, + { "sqlite3changeset_start", test_sqlite3changeset_start }, { "sqlite3session_foreach", test_sqlite3session_foreach }, { "sqlite3changeset_invert", test_sqlite3changeset_invert }, { "sqlite3changeset_concat", test_sqlite3changeset_concat }, { "sqlite3changeset_apply", test_sqlite3changeset_apply }, { "sqlite3changeset_apply_v2", test_sqlite3changeset_apply_v2 }, Index: ext/wasm/GNUmakefile ================================================================== --- ext/wasm/GNUmakefile +++ ext/wasm/GNUmakefile @@ -287,10 +287,16 @@ ######################################################################### # bin.version-info = binary to output various sqlite3 version info for # embedding in the JS files and in building the distribution zip file. # It must NOT be in $(dir.tmp) because we need it to survive the # cleanup process for the dist build to work properly. +# +# Slight caveat: this uses the version info from the in-tree +# sqlite3.c/h, which may diff from a user-provided $(sqlite3.c). The +# end result is that the generated JS files may have static version +# info from $(bin.version-info) which differ from their runtime-emited +# version info (e.g. from sqlite3_libversion()). bin.version-info := $(dir.top)/version-info .NOTPARALLEL: $(bin.version-info) $(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile $(MAKE) -C $(dir.top) version-info @@ -333,23 +339,30 @@ # builds but is maintained as a standalone project: # https://fossil.wanderinghorse.net/r/c-pp # # Note that the SQLITE_... build flags used here have NO EFFECT on the # JS/WASM build. They are solely for use with $(bin.c-pp) itself. +# +# -D... flags which should be included in all invocations should be +# appended to $(C-PP.FILTER.global). bin.c-pp := ./c-pp $(bin.c-pp): c-pp.c $(sqlite3.c) $(MAKEFILE) $(CC) -O0 -o $@ c-pp.c $(sqlite3.c) '-DCMPP_DEFAULT_DELIM="//#"' -I$(dir.top) \ -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_UTF16 \ -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_WAL -DSQLITE_THREADSAFE=0 \ -DSQLITE_TEMP_STORE=3 +C-PP.FILTER.global ?= +ifeq (1,$(SQLITE_C_IS_SEE)) + C-PP.FILTER.global += -Denable-see +endif define C-PP.FILTER # Create $2 from $1 using $(bin.c-pp) # $1 = Input file: c-pp -f $(1).js # $2 = Output file: c-pp -o $(2).js # $3 = optional c-pp -D... flags $(2): $(1) $$(MAKEFILE) $$(bin.c-pp) - $$(bin.c-pp) -f $(1) -o $$@ $(3) + $$(bin.c-pp) -f $(1) -o $$@ $(3) $(C-PP.FILTER.global) CLEAN_FILES += $(2) endef # /end C-PP.FILTER ######################################################################## Index: ext/wasm/api/sqlite3-api-glue.js ================================================================== --- ext/wasm/api/sqlite3-api-glue.js +++ ext/wasm/api/sqlite3-api-glue.js @@ -327,11 +327,12 @@ build-time function-export list does not currently take optional features into account. */ wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]); } - if(wasm.exports.sqlite3_activate_see instanceof Function){ +//#if enable-see + if(wasm.exports.sqlite3_key_v2 instanceof Function){ /** This code is capable of using an SEE build but note that an SEE WASM build is generally incompatible with SEE's license conditions. It is permitted for use internally in organizations which have licensed SEE, but not for public sites because @@ -344,10 +345,12 @@ ["sqlite3_rekey", "int", "sqlite3*", "string", "int"], ["sqlite3_rekey_v2", "int", "sqlite3*", "string", "*", "int"], ["sqlite3_activate_see", undefined, "string"] ); } +//#endif enable-see + /** Functions which require BigInt (int64) support are separated from the others because we need to conditionally bind them or apply dummy impls, depending on the capabilities of the environment. (That said: we never actually build without BigInt support, @@ -622,14 +625,18 @@ no clients "should" be depending on the old names. */ wasm.bindingSignatures.wasmInternal = [ ["sqlite3__wasm_db_reset", "int", "sqlite3*"], ["sqlite3__wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"], - ["sqlite3__wasm_vfs_create_file", "int", - "sqlite3_vfs*","string","*", "int"], + [/* DO NOT USE. This is deprecated since 2023-08-11 because it can + trigger assert() in debug builds when used with file sizes + which are not sizes to a multiple of a valid db page size. */ + "sqlite3__wasm_vfs_create_file", "int", "sqlite3_vfs*","string","*", "int" + ], ["sqlite3__wasm_posix_create_file", "int", "string","*", "int"], - ["sqlite3__wasm_vfs_unlink", "int", "sqlite3_vfs*","string"] + ["sqlite3__wasm_vfs_unlink", "int", "sqlite3_vfs*","string"], + ["sqlite3__wasm_qfmt_token","string:dealloc", "string","int"] ]; /** Install JS<->C struct bindings for the non-opaque struct types we need... */ @@ -1545,10 +1552,14 @@ case capi.SQLITE_CONFIG_SERIALIZED: // 3 /* nil */ case capi.SQLITE_CONFIG_SINGLETHREAD: // 1 /* nil */: case capi.SQLITE_CONFIG_SQLLOG: // 21 /* xSqllog, void* */ case capi.SQLITE_CONFIG_WIN32_HEAPSIZE: // 23 /* int nByte */ default: + /* maintenance note: we specifically do not include + SQLITE_CONFIG_ROWID_IN_VIEW here, on the grounds that + it's only for legacy support and no apps written with + this API require that. */ return capi.SQLITE_NOTFOUND; } }; }/* sqlite3_config() */ Index: ext/wasm/api/sqlite3-api-oo1.js ================================================================== --- ext/wasm/api/sqlite3-api-oo1.js +++ ext/wasm/api/sqlite3-api-oo1.js @@ -85,10 +85,108 @@ VFS. In the latter case, the call signature is (theDbObject,sqlite3Namespace) and the callback is expected to throw on error. */ const __vfsPostOpenSql = Object.create(null); +//#if enable-see + /** + Converts ArrayBuffer or Uint8Array ba into a string of hex + digits. + */ + const byteArrayToHex = function(ba){ + if( ba instanceof ArrayBuffer ){ + ba = new Uint8Array(ba); + } + const li = []; + const digits = "0123456789abcdef"; + for( const d of ba ){ + li.push( digits[(d & 0xf0) >> 4], digits[d & 0x0f] ); + } + return li.join(''); + }; + + /** + Internal helper to apply an SEE key to a just-opened + database. Requires that db be-a DB object which has just been + opened, opt be the options object processed by its ctor, and opt + must have either the key, hexkey, or textkey properties, either + as a string, an ArrayBuffer, or a Uint8Array. + + This is a no-op in non-SEE builds. It throws on error and returns + without side effects if none of the key/textkey/hexkey options + are set. It throws if more than one is set or if any are set to + values of an invalid type. + + Returns true if it applies the key, else an unspecified falsy value. + */ + const dbCtorApplySEEKey = function(db,opt){ + if( !capi.sqlite3_key_v2 ) return; + let keytype; + let key; + const check = (opt.key ? 1 : 0) + (opt.hexkey ? 1 : 0) + (opt.textkey ? 1 : 0); + if( !check ) return; + else if( check>1 ){ + toss3(capi.SQLITE_MISUSE, + "Only ONE of (key, hexkey, textkey) may be provided."); + } + if( opt.key ){ + /* It is not legal to bind an argument to PRAGMA key=?, so we + convert it to a hexkey... */ + keytype = 'key'; + key = opt.key; + if('string'===typeof key){ + key = new TextEncoder('utf-8').encode(key); + } + if((key instanceof ArrayBuffer) || (key instanceof Uint8Array)){ + key = byteArrayToHex(key); + keytype = 'hexkey'; + }else{ + toss3(capi.SQLITE_MISUSE, + "Invalid value for the 'key' option. Expecting a string,", + "ArrayBuffer, or Uint8Array."); + return; + } + }else if( opt.textkey ){ + /* For textkey we need it to be in string form, so convert it to + a string if it's a byte array... */ + keytype = 'textkey'; + key = opt.textkey; + if(key instanceof ArrayBuffer){ + key = new Uint8Array(key); + } + if(key instanceof Uint8Array){ + key = new TextDecoder('utf-8').decode(key); + }else if('string'!==typeof key){ + toss3(capi.SQLITE_MISUSE, + "Invalid value for the 'textkey' option. Expecting a string,", + "ArrayBuffer, or Uint8Array."); + } + }else if( opt.hexkey ){ + keytype = 'hexkey'; + key = opt.hexkey; + if((key instanceof ArrayBuffer) || (key instanceof Uint8Array)){ + key = byteArrayToHex(key); + }else if('string'!==typeof key){ + toss3(capi.SQLITE_MISUSE, + "Invalid value for the 'hexkey' option. Expecting a string,", + "ArrayBuffer, or Uint8Array."); + } + /* else assume it's valid hex codes */ + }else{ + return; + } + let stmt; + try{ + stmt = db.prepare("PRAGMA "+keytype+"="+util.sqlite3__wasm_qfmt_token(key, 1)); + stmt.step(); + }finally{ + if(stmt) stmt.finalize(); + } + return true; + }; +//#endif enable-see + /** A proxy for DB class constructors. It must be called with the being-construct DB object as its "this". See the DB constructor for the argument docs. This is split into a separate function in order to enable simple creation of special-case DB constructors, @@ -173,20 +271,32 @@ } this.filename = fnJs; __ptrMap.set(this, pDb); __stmtMap.set(this, Object.create(null)); try{ +//#if enable-see + dbCtorApplySEEKey(this,opt); +//#endif // Check for per-VFS post-open SQL/callback... - const pVfs = capi.sqlite3_js_db_vfs(pDb); - if(!pVfs) toss3("Internal error: cannot get VFS for new db handle."); + const pVfs = capi.sqlite3_js_db_vfs(pDb) + || toss3("Internal error: cannot get VFS for new db handle."); const postInitSql = __vfsPostOpenSql[pVfs]; - if(postInitSql instanceof Function){ - postInitSql(this, sqlite3); - }else if(postInitSql){ - checkSqlite3Rc( - pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0) - ); + 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; } @@ -278,19 +388,50 @@ - `filename`: database file name - `flags`: open-mode flags - `vfs`: the VFS fname +//#if enable-see + + SEE-capable builds optionally support ONE of the following + additional options: + + - `key`, `hexkey`, or `textkey`: encryption key as a string, + ArrayBuffer, or Uint8Array. These flags function as documented + for the SEE pragmas of the same names. Using a byte array for + `hexkey` is equivalent to the same series of hex codes in + string form, so `'666f6f'` is equivalent to + `Uint8Array([0x66,0x6f,0x6f])`. A `textkey` byte array is + assumed to be UTF-8. A `key` string is transformed into a UTF-8 + byte array, and a `key` byte array is transformed into a + `hexkey` with the same bytes. + + In non-SEE builds, these options are ignored. In SEE builds, + `PRAGMA key/textkey/hexkey=X` is executed immediately after + opening the db. If more than one of the options is provided, + or any option has an invalid argument type, an exception is + thrown. + + Note that some DB subclasses may run post-initialization SQL + code, e.g. to set a busy-handler timeout or tweak the page cache + size. Such code is run _after_ the SEE key is applied. If no key + 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 C-strings allocated via WASM. `flags` is required to be a JS string (because it's specific to this API, which is specific to JS). For purposes of passing a DB instance to C-style sqlite3 functions, the DB object's read-only `pointer` property holds its `sqlite3*` pointer value. That property can also be used to check - whether this DB instance is still open. + whether this DB instance is still open: it will evaluate to + `undefined` after the DB object's close() method is called. In the main window thread, the filenames `":localStorage:"` and `":sessionStorage:"` are special: they cause the db to use either localStorage or sessionStorage for storing the database using the kvvfs. If one of these names are used, they trump @@ -431,44 +572,60 @@ if(!opt.callback && !opt.returnValue && undefined!==opt.rowMode){ if(!opt.resultRows) opt.resultRows = []; out.returnVal = ()=>opt.resultRows; } if(opt.callback || opt.resultRows){ - switch((undefined===opt.rowMode) - ? 'array' : opt.rowMode) { - case 'object': out.cbArg = (stmt)=>stmt.get(Object.create(null)); break; - case 'array': out.cbArg = (stmt)=>stmt.get([]); break; - case 'stmt': - if(Array.isArray(opt.resultRows)){ - toss3("exec(): invalid rowMode for a resultRows array: must", - "be one of 'array', 'object',", - "a result column number, or column name reference."); - } - out.cbArg = (stmt)=>stmt; - break; - default: - if(util.isInt32(opt.rowMode)){ - out.cbArg = (stmt)=>stmt.get(opt.rowMode); - break; - }else if('string'===typeof opt.rowMode - && opt.rowMode.length>1 - && '$'===opt.rowMode[0]){ - /* "$X": fetch column named "X" (case-sensitive!). Prior - to 2022-12-14 ":X" and "@X" were also permitted, but - having so many options is unnecessary and likely to - cause confusion. */ - const $colName = opt.rowMode.substr(1); - out.cbArg = (stmt)=>{ - const rc = stmt.get(Object.create(null))[$colName]; - return (undefined===rc) - ? toss3(capi.SQLITE_NOTFOUND, - "exec(): unknown result column:",$colName) - : rc; - }; - break; - } - toss3("Invalid rowMode:",opt.rowMode); + switch((undefined===opt.rowMode) ? 'array' : opt.rowMode) { + case 'object': + out.cbArg = (stmt,cache)=>{ + if( !cache.columnNames ) cache.columnNames = stmt.getColumnNames([]); + /* https://sqlite.org/forum/forumpost/3632183d2470617d: + conversion of rows to objects (key/val pairs) is + somewhat expensive for large data sets because of the + native-to-JS conversion of the column names. If we + instead cache the names and build objects from that + list of strings, it can run twice as fast. The + difference is not noticeable for small data sets but + becomes human-perceivable when enough rows are + involved. */ + const row = stmt.get([]); + const rv = Object.create(null); + for( const i in cache.columnNames ) rv[cache.columnNames[i]] = row[i]; + return rv; + }; + break; + case 'array': out.cbArg = (stmt)=>stmt.get([]); break; + case 'stmt': + if(Array.isArray(opt.resultRows)){ + toss3("exec(): invalid rowMode for a resultRows array: must", + "be one of 'array', 'object',", + "a result column number, or column name reference."); + } + out.cbArg = (stmt)=>stmt; + break; + default: + if(util.isInt32(opt.rowMode)){ + out.cbArg = (stmt)=>stmt.get(opt.rowMode); + break; + }else if('string'===typeof opt.rowMode + && opt.rowMode.length>1 + && '$'===opt.rowMode[0]){ + /* "$X": fetch column named "X" (case-sensitive!). Prior + to 2022-12-14 ":X" and "@X" were also permitted, but + having so many options is unnecessary and likely to + cause confusion. */ + const $colName = opt.rowMode.substr(1); + out.cbArg = (stmt)=>{ + const rc = stmt.get(Object.create(null))[$colName]; + return (undefined===rc) + ? toss3(capi.SQLITE_NOTFOUND, + "exec(): unknown result column:",$colName) + : rc; + }; + break; + } + toss3("Invalid rowMode:",opt.rowMode); } } return out; }; @@ -882,14 +1039,19 @@ a schema change between the prepare() and step(), via another connection, may invalidate the column count and names. */) ? 0 : 1; evalFirstResult = false; if(arg.cbArg || resultRows){ + const cbArgCache = Object.create(null) + /* 2nd arg for arg.cbArg, used by (at least) row-to-object + converter */; for(; stmt.step(); stmt._lockedByExec = false){ - if(0===gotColNames++) stmt.getColumnNames(opt.columnNames); + if(0===gotColNames++){ + stmt.getColumnNames(cbArgCache.columnNames = (opt.columnNames || [])); + } stmt._lockedByExec = true; - const row = arg.cbArg(stmt); + const row = arg.cbArg(stmt,cbArgCache); if(resultRows) resultRows.push(row); if(callback && false === callback.call(opt, row, stmt)){ break; } } @@ -1520,11 +1682,11 @@ - Numbers are bound as either doubles or integers: doubles if they are larger than 32 bits, else double or int32, depending on whether they have a fractional part. Booleans are bound as integer 0 or 1. It is not expected the distinction of binding - doubles which have no fractional parts is integers is + doubles which have no fractional parts and integers is significant for the majority of clients due to sqlite3's data typing model. If [BigInt] support is enabled then this routine will bind BigInt values as 64-bit integers if they'll fit in 64 bits. If that support disabled, it will store the BigInt as an int32 or a double if it can do so without loss @@ -1704,11 +1866,11 @@ Requires that step() has just returned a truthy value, else an exception is thrown. By default it will determine the data type of the result - automatically. If passed a second arugment, it must be one + automatically. If passed a second argument, it must be one of the enumeration values for sqlite3 types, which are defined as members of the sqlite3 module: SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB. Any other value, except for undefined, will trigger an exception. Passing undefined is the same as not passing a value. It is legal @@ -1904,20 +2066,30 @@ if(util.isUIThread()){ /** Functionally equivalent to DB(storageName,'c','kvvfs') except that it throws if the given storage name is not one of 'local' or 'session'. + + As of version 3.46, the argument may optionally be an options + object in the form: + + { + filename: 'session'|'local', + ... etc. (all options supported by the DB ctor) + } + + noting that the 'vfs' option supported by main DB + constructor is ignored here: the vfs is always 'kvvfs'. */ sqlite3.oo1.JsStorageDb = function(storageName='session'){ + const opt = dbCtorHelper.normalizeArgs(...arguments); + storageName = opt.filename; if('session'!==storageName && 'local'!==storageName){ toss3("JsStorageDb db name must be one of 'session' or 'local'."); } - dbCtorHelper.call(this, { - filename: storageName, - flags: 'c', - vfs: "kvvfs" - }); + opt.vfs = 'kvvfs'; + dbCtorHelper.call(this, opt); }; const jdb = sqlite3.oo1.JsStorageDb; jdb.prototype = Object.create(DB.prototype); /** Equivalent to sqlite3_js_kvvfs_clear(). */ jdb.clearStorage = capi.sqlite3_js_kvvfs_clear; Index: ext/wasm/api/sqlite3-api-prologue.js ================================================================== --- ext/wasm/api/sqlite3-api-prologue.js +++ ext/wasm/api/sqlite3-api-prologue.js @@ -243,11 +243,11 @@ that object is treated as the 2nd argument to the parent constructor. The exception's message is created by concatenating its arguments with a space between each, except for the - two-args-with-an-objec form and that the first argument will + two-args-with-an-object form and that the first argument will get coerced to a string, as described above, if it's an integer. If passed an integer first argument, the error object's `resultCode` member will be set to the given integer value, @@ -1836,11 +1836,11 @@ }; /** Calls either sqlite3_result_error_nomem(), if e is-a WasmAllocError, or sqlite3_result_error(). In the latter case, - the second arugment is coerced to a string to create the error + the second argument is coerced to a string to create the error message. The first argument is a (sqlite3_context*). Returns void. Does not throw. */ Index: ext/wasm/api/sqlite3-api-worker1.js ================================================================== --- ext/wasm/api/sqlite3-api-worker1.js +++ ext/wasm/api/sqlite3-api-worker1.js @@ -457,15 +457,10 @@ const getDefaultDbId = function(){ return wState.dbList[0] && getDbId(wState.dbList[0]); }; - const guessVfs = function(filename){ - const m = /^file:.+(vfs=(\w+))/.exec(filename); - return sqlite3.capi.sqlite3_vfs_find(m ? m[2] : 0); - }; - const isSpecialDbFilename = (n)=>{ return ""===n || ':'===n[0]; }; /** @@ -483,40 +478,12 @@ const oargs = Object.create(null), args = (ev.args || Object.create(null)); if(args.simulateError){ // undocumented internal testing option toss("Throwing because of simulateError flag."); } const rc = Object.create(null); - let byteArray, pVfs; oargs.vfs = args.vfs; - if(isSpecialDbFilename(args.filename)){ - oargs.filename = args.filename || ""; - }else{ - oargs.filename = args.filename; - byteArray = args.byteArray; - if(byteArray) pVfs = guessVfs(args.filename); - } - if(pVfs){ - /* 2022-11-02: this feature is as-yet untested except that - sqlite3__wasm_vfs_create_file() has been tested from the - browser dev console. */ - let pMem; - try{ - pMem = sqlite3.wasm.allocFromTypedArray(byteArray); - const rc = util.sqlite3__wasm_vfs_create_file( - pVfs, oargs.filename, pMem, byteArray.byteLength - ); - if(rc) sqlite3.SQLite3Error.toss(rc); - }catch(e){ - throw new sqlite3.SQLite3Error( - e.name+' creating '+args.filename+": "+e.message, { - cause: e - } - ); - }finally{ - if(pMem) sqlite3.wasm.dealloc(pMem); - } - } + oargs.filename = args.filename || ""; const db = wState.open(oargs); rc.filename = db.filename; rc.persistent = !!sqlite3.capi.sqlite3_js_db_uses_vfs(db.pointer, "opfs"); rc.dbId = getDbId(db); rc.vfs = db.dbVfsName(); Index: ext/wasm/api/sqlite3-opfs-async-proxy.js ================================================================== --- ext/wasm/api/sqlite3-opfs-async-proxy.js +++ ext/wasm/api/sqlite3-opfs-async-proxy.js @@ -49,11 +49,11 @@ versions (approximately) 104-107 are extinct) should change our usage of those methods to remove the "await". */ "use strict"; const wPost = (type,...args)=>postMessage({type, payload:args}); -const installAsyncProxy = function(self){ +const installAsyncProxy = function(){ const toss = function(...args){throw new Error(args.join(' '))}; if(globalThis.window === globalThis){ toss("This code cannot run from the main thread.", "Load it as a Worker from a separate Worker."); }else if(!navigator?.storage?.getDirectory){ @@ -85,39 +85,10 @@ if(state.verbose>level) loggers[level]("OPFS asyncer:",...args); }; const log = (...args)=>logImpl(2, ...args); const warn = (...args)=>logImpl(1, ...args); const error = (...args)=>logImpl(0, ...args); - const metrics = Object.create(null); - metrics.reset = ()=>{ - let k; - const r = (m)=>(m.count = m.time = m.wait = 0); - for(k in state.opIds){ - r(metrics[k] = Object.create(null)); - } - let s = metrics.s11n = Object.create(null); - s = s.serialize = Object.create(null); - s.count = s.time = 0; - s = metrics.s11n.deserialize = Object.create(null); - s.count = s.time = 0; - }; - metrics.dump = ()=>{ - let k, n = 0, t = 0, w = 0; - for(k in state.opIds){ - const m = metrics[k]; - n += m.count; - t += m.time; - w += m.wait; - m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0; - } - console.log(globalThis?.location?.href, - "metrics for",globalThis?.location?.href,":\n", - metrics, - "\nTotal of",n,"op(s) for",t,"ms", - "approx",w,"ms spent waiting on OPFS APIs."); - console.log("Serialization metrics:",metrics.s11n); - }; /** __openFiles is a map of sqlite3_file pointers (integers) to metadata related to a given OPFS file handles. The pointers are, in this side of the interface, opaque file handle IDs provided by the @@ -263,27 +234,38 @@ cause: errorObject }); this.name = 'GetSyncHandleError'; } }; + + /** + Attempts to find a suitable SQLITE_xyz result code for Error + object e. Returns either such a translation or rc if if it does + not know how to translate the exception. + */ GetSyncHandleError.convertRc = (e,rc)=>{ - if(1){ - return ( - e instanceof GetSyncHandleError - && ((e.cause.name==='NoModificationAllowedError') - /* Inconsistent exception.name from Chrome/ium with the - same exception.message text: */ - || (e.cause.name==='DOMException' - && 0===e.cause.message.indexOf('Access Handles cannot'))) - ) ? ( - /*console.warn("SQLITE_BUSY",e),*/ - state.sq3Codes.SQLITE_BUSY - ) : rc; - }else{ - return rc; - } - } + if( e instanceof GetSyncHandleError ){ + if( e.cause.name==='NoModificationAllowedError' + /* Inconsistent exception.name from Chrome/ium with the + same exception.message text: */ + || (e.cause.name==='DOMException' + && 0===e.cause.message.indexOf('Access Handles cannot')) ){ + return state.sq3Codes.SQLITE_BUSY; + }else if( 'NotFoundError'===e.cause.name ){ + /** + Maintenance reminder: SQLITE_NOTFOUND, though it looks like + a good match, has different semantics than NotFoundError + and is not suitable here. + */ + return state.sq3Codes.SQLITE_CANTOPEN; + } + }else if( 'NotFoundError'===e?.name ){ + return state.sq3Codes.SQLITE_CANTOPEN; + } + return rc; + }; + /** Returns the sync access handle associated with the given file handle object (which must be a valid handle object, as created by xOpen()), lazily opening it if needed. @@ -345,41 +327,10 @@ */ const affirmNotRO = function(opName,fh){ if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs); }; - /** - We track 2 different timers: the "metrics" timer records how much - time we spend performing work. The "wait" timer records how much - time we spend waiting on the underlying OPFS timer. See the calls - to mTimeStart(), mTimeEnd(), wTimeStart(), and wTimeEnd() - throughout this file to see how they're used. - */ - const __mTimer = Object.create(null); - __mTimer.op = undefined; - __mTimer.start = undefined; - const mTimeStart = (op)=>{ - __mTimer.start = performance.now(); - __mTimer.op = op; - //metrics[op] || toss("Maintenance required: missing metrics for",op); - ++metrics[op].count; - }; - const mTimeEnd = ()=>( - metrics[__mTimer.op].time += performance.now() - __mTimer.start - ); - const __wTimer = Object.create(null); - __wTimer.op = undefined; - __wTimer.start = undefined; - const wTimeStart = (op)=>{ - __wTimer.start = performance.now(); - __wTimer.op = op; - //metrics[op] || toss("Maintenance required: missing metrics for",op); - }; - const wTimeEnd = ()=>( - metrics[__wTimer.op].wait += performance.now() - __wTimer.start - ); - /** Gets set to true by the 'opfs-async-shutdown' command to quit the wait loop. This is only intended for debugging purposes: we cannot inspect this file's state while the tight waitLoop() is running and need a way to stop that loop for introspection purposes. @@ -386,41 +337,28 @@ */ let flagAsyncShutdown = false; /** Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods - methods, as well as helpers like mkdir(). Maintenance reminder: - members are in alphabetical order to simplify finding them. + methods, as well as helpers like mkdir(). */ const vfsAsyncImpls = { - 'opfs-async-metrics': async ()=>{ - mTimeStart('opfs-async-metrics'); - metrics.dump(); - storeAndNotify('opfs-async-metrics', 0); - mTimeEnd(); - }, 'opfs-async-shutdown': async ()=>{ flagAsyncShutdown = true; storeAndNotify('opfs-async-shutdown', 0); }, mkdir: async (dirname)=>{ - mTimeStart('mkdir'); let rc = 0; - wTimeStart('mkdir'); try { await getDirForFilename(dirname+"/filepart", true); }catch(e){ state.s11n.storeException(2,e); rc = state.sq3Codes.SQLITE_IOERR; - }finally{ - wTimeEnd(); } storeAndNotify('mkdir', rc); - mTimeEnd(); }, xAccess: async (filename)=>{ - mTimeStart('xAccess'); /* OPFS cannot support the full range of xAccess() queries sqlite3 calls for. We can essentially just tell if the file is accessible, but if it is then it's automatically writable (unless it's locked, which we cannot(?) know without trying to open it). OPFS does not have the notion of read-only. @@ -429,30 +367,24 @@ xAccess semantics because we are limited in what we can communicate back to our synchronous communication partner: 0 = accessible, non-0 means not accessible. */ let rc = 0; - wTimeStart('xAccess'); try{ const [dh, fn] = await getDirForFilename(filename); await dh.getFileHandle(fn); }catch(e){ state.s11n.storeException(2,e); rc = state.sq3Codes.SQLITE_IOERR; - }finally{ - wTimeEnd(); } storeAndNotify('xAccess', rc); - mTimeEnd(); }, xClose: async function(fid/*sqlite3_file pointer*/){ const opName = 'xClose'; - mTimeStart(opName); __implicitLocks.delete(fid); const fh = __openFiles[fid]; let rc = 0; - wTimeStart(opName); if(fh){ delete __openFiles[fid]; await closeSyncHandle(fh); if(fh.deleteOnClose){ try{ await fh.dirHandle.removeEntry(fh.filenamePart) } @@ -460,19 +392,15 @@ } }else{ state.s11n.serialize(); rc = state.sq3Codes.SQLITE_NOTFOUND; } - wTimeEnd(); storeAndNotify(opName, rc); - mTimeEnd(); }, xDelete: async function(...args){ - mTimeStart('xDelete'); const rc = await vfsAsyncImpls.xDeleteNoWait(...args); storeAndNotify('xDelete', rc); - mTimeEnd(); }, xDeleteNoWait: async function(filename, syncDir = 0, recursive = false){ /* The syncDir flag is, for purposes of the VFS API's semantics, ignored here. However, if it has the value 0x1234 then: after deleting the given file, recursively try to delete any empty @@ -484,11 +412,10 @@ however, a "recursive" flag which defaults to false, so presumably it will fail if the dir is not empty and that flag is false. */ let rc = 0; - wTimeStart('xDelete'); try { while(filename){ const [hDir, filenamePart] = await getDirForFilename(filename, false); if(!filenamePart) break; await hDir.removeEntry(filenamePart, {recursive}); @@ -500,72 +427,66 @@ } }catch(e){ state.s11n.storeException(2,e); rc = state.sq3Codes.SQLITE_IOERR_DELETE; } - wTimeEnd(); return rc; }, xFileSize: async function(fid/*sqlite3_file pointer*/){ - mTimeStart('xFileSize'); const fh = __openFiles[fid]; let rc = 0; - wTimeStart('xFileSize'); try{ const sz = await (await getSyncHandle(fh,'xFileSize')).getSize(); state.s11n.serialize(Number(sz)); }catch(e){ state.s11n.storeException(1,e); rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR); } await releaseImplicitLock(fh); - wTimeEnd(); storeAndNotify('xFileSize', rc); - mTimeEnd(); }, xLock: async function(fid/*sqlite3_file pointer*/, lockType/*SQLITE_LOCK_...*/){ - mTimeStart('xLock'); const fh = __openFiles[fid]; let rc = 0; const oldLockType = fh.xLock; fh.xLock = lockType; if( !fh.syncHandle ){ - wTimeStart('xLock'); try { await getSyncHandle(fh,'xLock'); __implicitLocks.delete(fid); }catch(e){ state.s11n.storeException(1,e); rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_LOCK); fh.xLock = oldLockType; } - wTimeEnd(); } storeAndNotify('xLock',rc); - mTimeEnd(); }, xOpen: async function(fid/*sqlite3_file pointer*/, filename, flags/*SQLITE_OPEN_...*/, opfsFlags/*OPFS_...*/){ const opName = 'xOpen'; - mTimeStart(opName); const create = (state.sq3Codes.SQLITE_OPEN_CREATE & flags); - wTimeStart('xOpen'); try{ let hDir, filenamePart; try { [hDir, filenamePart] = await getDirForFilename(filename, !!create); }catch(e){ state.s11n.storeException(1,e); storeAndNotify(opName, state.sq3Codes.SQLITE_NOTFOUND); - mTimeEnd(); - wTimeEnd(); return; + } + if( state.opfsFlags.OPFS_UNLINK_BEFORE_OPEN & opfsFlags ){ + try{ + await hDir.removeEntry(filenamePart); + }catch(e){ + /* ignoring */ + //warn("Ignoring failed Unlink of",filename,":",e); + } } const hFile = await hDir.getFileHandle(filenamePart, {create}); - wTimeEnd(); const fh = Object.assign(Object.create(null),{ fid: fid, filenameAbs: filename, filenamePart: filenamePart, dirHandle: hDir, @@ -576,116 +497,82 @@ deleteOnClose: !!(state.sq3Codes.SQLITE_OPEN_DELETEONCLOSE & flags) }); fh.releaseImplicitLocks = (opfsFlags & state.opfsFlags.OPFS_UNLOCK_ASAP) || state.opfsFlags.defaultUnlockAsap; - if(0 /* this block is modelled after something wa-sqlite - does but it leads to immediate contention on journal files. - Update: this approach reportedly only works for DELETE journal - mode. */ - && (0===(flags & state.sq3Codes.SQLITE_OPEN_MAIN_DB))){ - /* sqlite does not lock these files, so go ahead and grab an OPFS - lock. */ - fh.xLock = "xOpen"/* Truthy value to keep entry from getting - flagged as auto-locked. String value so - that we can easily distinguish is later - if needed. */; - await getSyncHandle(fh,'xOpen'); - } __openFiles[fid] = fh; storeAndNotify(opName, 0); }catch(e){ - wTimeEnd(); error(opName,e); state.s11n.storeException(1,e); storeAndNotify(opName, state.sq3Codes.SQLITE_IOERR); } - mTimeEnd(); }, xRead: async function(fid/*sqlite3_file pointer*/,n,offset64){ - mTimeStart('xRead'); let rc = 0, nRead; const fh = __openFiles[fid]; try{ - wTimeStart('xRead'); nRead = (await getSyncHandle(fh,'xRead')).read( fh.sabView.subarray(0, n), {at: Number(offset64)} ); - wTimeEnd(); if(nRead < n){/* Zero-fill remaining bytes */ fh.sabView.fill(0, nRead, n); rc = state.sq3Codes.SQLITE_IOERR_SHORT_READ; } }catch(e){ - if(undefined===nRead) wTimeEnd(); error("xRead() failed",e,fh); state.s11n.storeException(1,e); rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_READ); } await releaseImplicitLock(fh); storeAndNotify('xRead',rc); - mTimeEnd(); }, xSync: async function(fid/*sqlite3_file pointer*/,flags/*ignored*/){ - mTimeStart('xSync'); const fh = __openFiles[fid]; let rc = 0; if(!fh.readOnly && fh.syncHandle){ try { - wTimeStart('xSync'); await fh.syncHandle.flush(); }catch(e){ state.s11n.storeException(2,e); rc = state.sq3Codes.SQLITE_IOERR_FSYNC; } - wTimeEnd(); } storeAndNotify('xSync',rc); - mTimeEnd(); }, xTruncate: async function(fid/*sqlite3_file pointer*/,size){ - mTimeStart('xTruncate'); let rc = 0; const fh = __openFiles[fid]; - wTimeStart('xTruncate'); try{ affirmNotRO('xTruncate', fh); await (await getSyncHandle(fh,'xTruncate')).truncate(size); }catch(e){ error("xTruncate():",e,fh); state.s11n.storeException(2,e); rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_TRUNCATE); } await releaseImplicitLock(fh); - wTimeEnd(); storeAndNotify('xTruncate',rc); - mTimeEnd(); }, xUnlock: async function(fid/*sqlite3_file pointer*/, lockType/*SQLITE_LOCK_...*/){ - mTimeStart('xUnlock'); let rc = 0; const fh = __openFiles[fid]; if( state.sq3Codes.SQLITE_LOCK_NONE===lockType && fh.syncHandle ){ - wTimeStart('xUnlock'); try { await closeSyncHandle(fh) } catch(e){ state.s11n.storeException(1,e); rc = state.sq3Codes.SQLITE_IOERR_UNLOCK; } - wTimeEnd(); } storeAndNotify('xUnlock',rc); - mTimeEnd(); }, xWrite: async function(fid/*sqlite3_file pointer*/,n,offset64){ - mTimeStart('xWrite'); let rc; const fh = __openFiles[fid]; - wTimeStart('xWrite'); try{ affirmNotRO('xWrite', fh); rc = ( n === (await getSyncHandle(fh,'xWrite')) .write(fh.sabView.subarray(0, n), @@ -695,13 +582,11 @@ error("xWrite():",e,fh); state.s11n.storeException(1,e); rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_WRITE); } await releaseImplicitLock(fh); - wTimeEnd(); storeAndNotify('xWrite',rc); - mTimeEnd(); } }/*vfsAsyncImpls*/; const initS11n = ()=>{ /** @@ -731,12 +616,10 @@ case TypeIds.string.id: return TypeIds.string; default: toss("Invalid type ID:",tid); } }; state.s11n.deserialize = function(clear=false){ - ++metrics.s11n.deserialize.count; - const t = performance.now(); const argc = viewU8[0]; const rc = argc ? [] : null; if(argc){ const typeIds = []; let offset = 1, i, n, v; @@ -757,16 +640,13 @@ rc.push(v); } } if(clear) viewU8[0] = 0; //log("deserialize:",argc, rc); - metrics.s11n.deserialize.time += performance.now() - t; return rc; }; state.s11n.serialize = function(...args){ - const t = performance.now(); - ++metrics.s11n.serialize.count; if(args.length){ //log("serialize():",args); const typeIds = []; let i = 0, offset = 1; viewU8[0] = args.length & 0xff /* header = # of args */; @@ -793,11 +673,10 @@ } //log("serialize() result:",viewU8.slice(0,offset)); }else{ viewU8[0] = 0; } - metrics.s11n.serialize.time += performance.now() - t; }; state.s11n.storeException = state.asyncS11nExceptions ? ((priority,e)=>{ if(priority<=state.asyncS11nExceptions){ @@ -875,11 +754,10 @@ if(!Number.isFinite(state.opIds[k])){ toss("Maintenance required: missing state.opIds[",k,"]"); } }); initS11n(); - metrics.reset(); log("init state",state); wPost('opfs-async-inited'); waitLoop(); break; } @@ -888,13 +766,10 @@ warn("Restarting after opfs-async-shutdown. Might or might not work."); flagAsyncShutdown = false; waitLoop(); } break; - case 'opfs-async-metrics': - metrics.dump(); - break; } }; wPost('opfs-async-loaded'); }).catch((e)=>error("error initializing OPFS asyncer:",e)); }/*installAsyncProxy()*/; @@ -909,7 +784,7 @@ !globalThis.FileSystemFileHandle || !globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle || !navigator?.storage?.getDirectory){ wPost('opfs-unavailable',"Missing required OPFS APIs."); }else{ - installAsyncProxy(self); + installAsyncProxy(); } Index: ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js ================================================================== --- ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js +++ ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js @@ -1270,11 +1270,11 @@ }/*extend sqlite3.oo1*/ thePool.log("VFS initialized."); return poolUtil; }).catch(async (e)=>{ await thePool.removeVfs().catch(()=>{}); - return e; + throw e; }); }).catch((err)=>{ //error("rejecting promise:",err); return initPromises[vfsName] = Promise.reject(err); }); Index: ext/wasm/api/sqlite3-vfs-opfs.c-pp.js ================================================================== --- ext/wasm/api/sqlite3-vfs-opfs.c-pp.js +++ ext/wasm/api/sqlite3-vfs-opfs.c-pp.js @@ -390,10 +390,11 @@ state.sq3Codes = Object.create(null); [ 'SQLITE_ACCESS_EXISTS', 'SQLITE_ACCESS_READWRITE', 'SQLITE_BUSY', + 'SQLITE_CANTOPEN', 'SQLITE_ERROR', 'SQLITE_IOERR', 'SQLITE_IOERR_ACCESS', 'SQLITE_IOERR_CLOSE', 'SQLITE_IOERR_DELETE', @@ -421,17 +422,32 @@ toss("Maintenance required: not found:",k); } }); state.opfsFlags = Object.assign(Object.create(null),{ /** - Flag for use with xOpen(). "opfs-unlock-asap=1" enables - this. See defaultUnlockAsap, below. + Flag for use with xOpen(). URI flag "opfs-unlock-asap=1" + enables this. See defaultUnlockAsap, below. */ OPFS_UNLOCK_ASAP: 0x01, + /** + Flag for use with xOpen(). URI flag "delete-before-open=1" + tells the VFS to delete the db file before attempting to open + it. This can be used, e.g., to replace a db which has been + corrupted (without forcing us to expose a delete/unlink() + function in the public API). + + Failure to unlink the file is ignored but may lead to + downstream errors. An unlink can fail if, e.g., another tab + has the handle open. + + It goes without saying that deleting a file out from under another + instance results in Undefined Behavior. + */ + OPFS_UNLINK_BEFORE_OPEN: 0x02, /** If true, any async routine which implicitly acquires a sync - access handle (i.e. an OPFS lock) will release that locks at + access handle (i.e. an OPFS lock) will release that lock at the end of the call which acquires it. If false, such "autolocks" are not released until the VFS is idle for some brief amount of time. The benefit of enabling this is much higher concurrency. The @@ -454,13 +470,26 @@ Atomics.store(state.sabOPView, state.opIds.rc, -1); Atomics.store(state.sabOPView, state.opIds.whichOp, opNdx); Atomics.notify(state.sabOPView, state.opIds.whichOp) /* async thread will take over here */; const t = performance.now(); - Atomics.wait(state.sabOPView, state.opIds.rc, -1) - /* When this wait() call returns, the async half will have - completed the operation and reported its results. */; + while('not-equal'!==Atomics.wait(state.sabOPView, state.opIds.rc, -1)){ + /* + The reason for this loop is buried in the details of a long + discussion at: + + https://github.com/sqlite/sqlite-wasm/issues/12 + + Summary: in at least one browser flavor, under high loads, + the wait()/notify() pairings can get out of sync. Calling + wait() here until it returns 'not-equal' gets them back in + sync. + */ + } + /* When the above wait() call returns 'not-equal', the async + half will have completed the operation and reported its results + in the state.opIds.rc slot of the SAB. */ const rc = Atomics.load(state.sabOPView, state.opIds.rc); metrics[op].wait += performance.now() - t; if(rc && state.asyncS11nExceptions){ const err = state.s11n.deserialize(); if(err) error(op+"() async error:",...err); @@ -703,13 +732,22 @@ connection has a lock except by trying to obtain and (on success) release a sync-handle for it, but doing so would involve an inherent race condition. For the time being, pending a better solution, we simply report whether the given pFile is open. + + Update 2024-06-12: based on forum discussions, this + function now always sets pOut to 0 (false): + + https://sqlite.org/forum/forumpost/a2f573b00cda1372 */ - const f = __openFiles[pFile]; - wasm.poke(pOut, f.lockType ? 1 : 0, 'i32'); + if(1){ + wasm.poke(pOut, 0, 'i32'); + }else{ + const f = __openFiles[pFile]; + wasm.poke(pOut, f.lockType ? 1 : 0, 'i32'); + } return 0; }, xClose: function(pFile){ mTimeStart('xClose'); let rc = 0; @@ -721,11 +759,10 @@ } mTimeEnd(); return rc; }, xDeviceCharacteristics: function(pFile){ - //debug("xDeviceCharacteristics(",pFile,")"); return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN; }, xFileControl: function(pFile, opId, pArg){ /*mTimeStart('xFileControl'); mTimeEnd();*/ @@ -873,17 +910,21 @@ xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){ mTimeStart('xOpen'); let opfsFlags = 0; if(0===zName){ zName = randomFilename(); - }else if('number'===typeof zName){ + }else if(wasm.isPtr(zName)){ if(capi.sqlite3_uri_boolean(zName, "opfs-unlock-asap", 0)){ /* -----------------------^^^^^ MUST pass the untranslated C-string here. */ opfsFlags |= state.opfsFlags.OPFS_UNLOCK_ASAP; } + if(capi.sqlite3_uri_boolean(zName, "delete-before-open", 0)){ + opfsFlags |= state.opfsFlags.OPFS_UNLINK_BEFORE_OPEN; + } zName = wasm.cstrToJs(zName); + //warn("xOpen zName =",zName, "opfsFlags =",opfsFlags); } const fh = Object.create(null); fh.fid = pFile; fh.filename = zName; fh.sab = new SharedArrayBuffer(state.fileBufferSize); Index: ext/wasm/api/sqlite3-wasm.c ================================================================== --- ext/wasm/api/sqlite3-wasm.c +++ ext/wasm/api/sqlite3-wasm.c @@ -543,10 +543,14 @@ DefInt(SQLITE_CONFIG_PMASZ); DefInt(SQLITE_CONFIG_STMTJRNL_SPILL); DefInt(SQLITE_CONFIG_SMALL_MALLOC); DefInt(SQLITE_CONFIG_SORTERREF_SIZE); DefInt(SQLITE_CONFIG_MEMDB_MAXSIZE); + /* maintenance note: we specifically do not include + SQLITE_CONFIG_ROWID_IN_VIEW here, on the grounds that + it's only for legacy support and no apps written with + this API require that. */ } _DefGroup; DefGroup(dataTypes) { DefInt(SQLITE_INTEGER); DefInt(SQLITE_FLOAT); @@ -1672,39 +1676,28 @@ SQLITE_WASM_EXPORT int sqlite3__wasm_config_j(int op, sqlite3_int64 arg){ return sqlite3_config(op, arg); } -#if 0 -// Pending removal after verification of a workaround discussed in the -// forum post linked to below. /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. ** -** Returns a pointer to sqlite3_free(). In compliant browsers the -** return value, when passed to sqlite3.wasm.exports.functionEntry(), -** must resolve to the same function as -** sqlite3.wasm.exports.sqlite3_free. i.e. from a dev console where -** sqlite3 is exported globally, the following must be true: -** -** ``` -** sqlite3.wasm.functionEntry( -** sqlite3.wasm.exports.sqlite3__wasm_ptr_to_sqlite3_free() -** ) === sqlite3.wasm.exports.sqlite3_free -** ``` -** -** Using a function to return this pointer, as opposed to exporting it -** via sqlite3__wasm_enum_json(), is an attempt to work around a -** Safari-specific quirk covered at -** https://sqlite.org/forum/info/e5b20e1feb37a19a. -**/ +** If z is not NULL, returns the result of passing z to +** sqlite3_mprintf()'s %Q modifier (if addQuotes is true) or %q (if +** addQuotes is 0). Returns NULL if z is NULL or on OOM. +*/ SQLITE_WASM_EXPORT -void * sqlite3__wasm_ptr_to_sqlite3_free(void){ - return (void*)sqlite3_free; +char * sqlite3__wasm_qfmt_token(char *z, int addQuotes){ + char * rc = 0; + if( z ){ + rc = addQuotes + ? sqlite3_mprintf("%Q", z) + : sqlite3_mprintf("%q", z); + } + return rc; } -#endif #if defined(__EMSCRIPTEN__) && defined(SQLITE_ENABLE_WASMFS) #include /* Index: ext/wasm/api/sqlite3-worker1-promiser.c-pp.js ================================================================== --- ext/wasm/api/sqlite3-worker1-promiser.c-pp.js +++ ext/wasm/api/sqlite3-worker1-promiser.c-pp.js @@ -205,11 +205,11 @@ msg = Object.create(null); msg.type = arguments[0]; msg.args = arguments[1]; msg.dbId = msg.args.dbId; }else{ - toss("Invalid arugments for sqlite3Worker1Promiser()-created factory."); + toss("Invalid arguments for sqlite3Worker1Promiser()-created factory."); } if(!msg.dbId && msg.type!=='open') msg.dbId = dbId; msg.messageId = genMsgId(msg); msg.departureTime = performance.now(); const proxy = Object.create(null); @@ -283,11 +283,11 @@ , onerror: (...args)=>console.error('worker1 promiser error',...args) }/*defaultConfig*/; /** - sqlite3Worker1Promiser.v2() works identically to + sqlite3Worker1Promiser.v2(), added in 3.46, works identically to sqlite3Worker1Promiser() except that it returns a Promise instead of relying an an onready callback in the config object. The Promise resolves to the same factory function which sqlite3Worker1Promiser() returns. Index: ext/wasm/common/whwasmutil.js ================================================================== --- ext/wasm/common/whwasmutil.js +++ ext/wasm/common/whwasmutil.js @@ -1379,20 +1379,24 @@ target.exports. If found, it is called, passed all remaining arguments, and its return value is returned to xCall's caller. If not found, an exception is thrown. This function does no conversion of argument or return types, but see xWrap() and xCallWrapped() for variants which do. + + If the first argument is a function is is assumed to be + a WASM-bound function and is used as-is instead of looking up + the function via xGet(). As a special case, if passed only 1 argument after the name and that argument in an Array, that array's entries become the function arguments. (This is not an ambiguous case because it's not legal to pass an Array object to a WASM function.) */ target.xCall = function(fname, ...args){ - const f = target.xGet(fname); + const f = (fname instanceof Function) ? fname : target.xGet(fname); if(!(f instanceof Function)) toss("Exported symbol",fname,"is not a function."); - if(f.length!==args.length) __argcMismatch(fname,f.length) + if(f.length!==args.length) __argcMismatch(((f===fname) ? f.name : fname),f.length) /* This is arguably over-pedantic but we want to help clients keep from shooting themselves in the foot when calling C APIs. */; return (2===arguments.length && Array.isArray(arguments[1])) ? f.apply(null, arguments[1]) : f.apply(null, args); @@ -1535,11 +1539,11 @@ - signature: a function signature string compatible with jsFuncToWasm(). - bindScope (string): one of ('transient', 'context', - 'singleton'). Bind scopes are: + 'singleton', 'permanent'). Bind scopes are: - 'transient': it will convert JS functions to WASM only for the duration of the xWrap()'d function call, using scopedInstallFunction(). Before that call returns, the WASM-side binding will be uninstalled. @@ -1785,15 +1789,33 @@ (t)=>xArg.get(t) || toss("Argument adapter not found:",t); const __xResultAdapterCheck = (t)=>xResult.get(t) || toss("Result adapter not found:",t); + /** + Fetches the xWrap() argument adapter mapped to t, calls it, + passing in all remaining arguments, and returns the result. + Throws if t is not mapped to an argument converter. + */ cache.xWrap.convertArg = (t,...args)=>__xArgAdapterCheck(t)(...args); + /** + Identical to convertArg() except that it does not perform + an is-defined check on the mapping to t before invoking it. + */ cache.xWrap.convertArgNoCheck = (t,...args)=>xArg.get(t)(...args); + /** + Fetches the xWrap() result adapter mapped to t, calls it, passing + it v, and returns the result. Throws if t is not mapped to an + argument converter. + */ cache.xWrap.convertResult = (t,v)=>(null===t ? v : (t ? __xResultAdapterCheck(t)(v) : undefined)); + /** + Identical to convertResult() except that it does not perform an + is-defined check on the mapping to t before invoking it. + */ cache.xWrap.convertResultNoCheck = (t,v)=>(null===t ? v : (t ? xResult.get(t)(v) : undefined)); /** Creates a wrapper for another function which converts the arguments @@ -1901,19 +1923,19 @@ - `string` or `utf8` (results): treats the result value as a const C-string, encoded as UTF-8, copies it to a JS string, and returns that JS string. - - `string:dealloc` or `utf8:dealloc) (results): treats the result value - as a non-const UTF-8 C-string, ownership of which has just been - transfered to the caller. It copies the C-string to a JS - string, frees the C-string, and returns the JS string. If such - a result value is NULL, the JS result is `null`. Achtung: when - using an API which returns results from a specific allocator, - e.g. `my_malloc()`, this conversion _is not legal_. Instead, an - equivalent conversion which uses the appropriate deallocator is - required. For example: + - `string:dealloc` or `utf8:dealloc` (results): treats the result + value as a non-const UTF-8 C-string, ownership of which has + just been transfered to the caller. It copies the C-string to a + JS string, frees the C-string, and returns the JS string. If + such a result value is NULL, the JS result is `null`. Achtung: + when using an API which returns results from a specific + allocator, e.g. `my_malloc()`, this conversion _is not + legal_. Instead, an equivalent conversion which uses the + appropriate deallocator is required. For example: ```js target.xWrap.resultAdapter('string:my_free',(i)=>{ try { return i ? target.cstrToJs(i) : null } finally{ target.exports.my_free(i) } @@ -2010,12 +2032,16 @@ an implementation detail and subject to change. i.e. the public interface of 1 argument is stable. The fact that any arguments may be passed in after that one, and what those arguments are, is _not_ part of the public interface and is _not_ stable. + + Maintenance reminder: the Ember framework modifies the core + Array type, breaking for-in loops. */ - for(const i in args) args[i] = cxw.convertArgNoCheck( + let i = 0; + for(; i < args.length; ++i) args[i] = cxw.convertArgNoCheck( argTypes[i], args[i], args, i ); return cxw.convertResultNoCheck(resultType, xf.apply(null,args)); }finally{ target.scopedAllocPop(scope); Index: ext/wasm/dist.make ================================================================== --- ext/wasm/dist.make +++ ext/wasm/dist.make @@ -17,14 +17,19 @@ # Chicken/egg situation: we need $(bin.version-info) to get the # version info for the archive name, but that binary may not yet be # built, and won't be built until we expand the dependencies. Thus we # have to use a temporary name for the archive until we can get # that binary built. +ifeq (1,$(SQLITE_C_IS_SEE)) +dist-name-extra := -see +else +dist-name-extra := +endif ifeq (,$(filter snapshot,$(MAKECMDGOALS))) -dist-name-prefix := sqlite-wasm +dist-name-prefix := sqlite-wasm$(dist-name-extra) else -dist-name-prefix := sqlite-wasm-snapshot-$(shell /usr/bin/date +%Y%m%d) +dist-name-prefix := sqlite-wasm$(dist-name-extra)-snapshot-$(shell /usr/bin/date +%Y%m%d) endif dist-name := $(dist-name-prefix)-TEMP ######################################################################## # dist.build must be the name of a target which triggers the build of Index: ext/wasm/tester1.c-pp.js ================================================================== --- ext/wasm/tester1.c-pp.js +++ ext/wasm/tester1.c-pp.js @@ -1480,11 +1480,11 @@ T.assert(1===ndx); st.bindAsBlob(ndx, "ima blob") /*step() skipped intentionally*/.reset(true); } finally { T.assert(0===st.finalize()) - .assert(undefined===st.finalize()); + .assert(undefined===st.finalize()); } try { db.prepare("/*empty SQL*/"); toss("Must not be reached."); @@ -2585,11 +2585,11 @@ test: function(sqlite3){ const filename = this.kvvfsDbFile = 'session'; const pVfs = capi.sqlite3_vfs_find('kvvfs'); T.assert(pVfs); const JDb = this.JDb = sqlite3.oo1.JsStorageDb; - const unlink = this.kvvfsUnlink = ()=>{JDb.clearStorage(filename)}; + const unlink = this.kvvfsUnlink = ()=>JDb.clearStorage(this.kvvfsDbFile); unlink(); let db = new JDb(filename); try { db.exec([ 'create table kvvfs(a);', @@ -2603,10 +2603,64 @@ }finally{ db.close(); } } }/*kvvfs sanity checks*/) +//#if enable-see + .t({ + name: 'kvvfs with SEE encryption', + predicate: ()=>(isUIThread() + || "Only available in main thread."), + test: function(sqlite3){ + this.kvvfsUnlink(); + let db; + const encOpt1 = 1 + ? {textkey: 'foo'} + : {key: 'foo'}; + const encOpt2 = encOpt1.textkey + ? encOpt1 + : {hexkey: new Uint8Array([0x66,0x6f,0x6f]/*==>"foo"*/)} + try{ + db = new this.JDb({ + filename: this.kvvfsDbFile, + ...encOpt1 + }); + db.exec([ + "create table t(a,b);", + "insert into t(a,b) values(1,2),(3,4)" + ]); + db.close(); + let err; + try{ + db = new this.JDb({ + filename: this.kvvfsDbFile, + flags: 'ct' + }); + T.assert(db) /* opening is fine, but... */; + db.exec("select 1 from sqlite_schema"); + console.warn("sessionStorage =",sessionStorage); + }catch(e){ + err = e; + }finally{ + db.close(); + } + T.assert(err,"Expecting an exception") + .assert(sqlite3.capi.SQLITE_NOTADB==err.resultCode, + "Expecting NOTADB"); + db = new sqlite3.oo1.DB({ + filename: this.kvvfsDbFile, + vfs: 'kvvfs', + ...encOpt2 + }); + T.assert( 4===db.selectValue('select sum(a) from t') ); + }finally{ + if( db ) db.close(); + this.kvvfsUnlink(); + } + } + })/*kvvfs with SEE*/ +//#endif enable-see ;/* end kvvfs tests */ //////////////////////////////////////////////////////////////////////// T.g('Hook APIs') .t({ @@ -2886,22 +2940,21 @@ (sqlite3)=>(sqlite3.capi.sqlite3_vfs_find("opfs") || 'requires "opfs" VFS')) .t({ name: 'OPFS db sanity checks', test: async function(sqlite3){ + T.assert(capi.sqlite3_vfs_find('opfs')); + const opfs = sqlite3.opfs; const filename = this.opfsDbFile = '/dir/sqlite3-tester1.db'; - const pVfs = this.opfsVfs = capi.sqlite3_vfs_find('opfs'); - T.assert(pVfs); - const unlink = this.opfsUnlink = - (fn=filename)=>{sqlite3.util.sqlite3__wasm_vfs_unlink(pVfs,fn)}; - unlink(); - let db = new sqlite3.oo1.OpfsDb(filename); + const fileUri = 'file://'+filename+'?delete-before-open=1'; + const initSql = [ + 'create table p(a);', + 'insert into p(a) values(1),(2),(3)' + ]; + let db = new sqlite3.oo1.OpfsDb(fileUri); try { - db.exec([ - 'create table p(a);', - 'insert into p(a) values(1),(2),(3)' - ]); + db.exec(initSql); T.assert(3 === db.selectValue('select count(*) from p')); db.close(); db = new sqlite3.oo1.OpfsDb(filename); db.exec('insert into p(a) values(4),(5),(6)'); T.assert(6 === db.selectValue('select count(*) from p')); @@ -2909,27 +2962,36 @@ T.assert(this.opfsDbExport instanceof Uint8Array) .assert(this.opfsDbExport.byteLength>0 && 0===this.opfsDbExport.byteLength % 512); }finally{ db.close(); - unlink(); + } + T.assert(await opfs.entryExists(filename)); + try { + db = new sqlite3.oo1.OpfsDb(fileUri); + db.exec(initSql) /* will throw if delete-before-open did not work */; + T.assert(3 === db.selectValue('select count(*) from p')); + }finally{ + if(db) db.close(); } } }/*OPFS db sanity checks*/) .t({ name: 'OPFS import', test: async function(sqlite3){ let db; + const filename = this.opfsDbFile; try { const exp = this.opfsDbExport; - const filename = this.opfsDbFile; delete this.opfsDbExport; this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(filename, exp); db = new sqlite3.oo1.OpfsDb(this.opfsDbFile); T.assert(6 === db.selectValue('select count(*) from p')). assert( this.opfsImportSize == exp.byteLength ); db.close(); + const unlink = this.opfsUnlink = + (fn=filename)=>sqlite3.util.sqlite3__wasm_vfs_unlink("opfs",fn); this.opfsUnlink(filename); T.assert(!(await sqlite3.opfs.entryExists(filename))); // Try again with a function as an input source: let cursor = 0; const blockSize = 512, end = exp.byteLength; @@ -2952,15 +3014,13 @@ }/*OPFS export/import*/) .t({ name: '(Internal-use) OPFS utility APIs', test: async function(sqlite3){ const filename = this.opfsDbFile; - const pVfs = this.opfsVfs; const unlink = this.opfsUnlink; - T.assert(filename && pVfs && !!unlink); + T.assert(filename && !!unlink); delete this.opfsDbFile; - delete this.opfsVfs; delete this.opfsUnlink; /************************************************************** ATTENTION CLIENT-SIDE USERS: sqlite3.opfs is NOT intended for client-side use. It is only for this project's own internal use. Its APIs are subject to change or removal at Index: src/alter.c ================================================================== --- src/alter.c +++ src/alter.c @@ -2260,11 +2260,16 @@ regOut = reg+1+nField; } if( i==pTab->iPKey ){ sqlite3VdbeAddOp2(v, OP_Null, 0, regOut); }else{ + char aff = pTab->aCol[i].affinity; + if( aff==SQLITE_AFF_REAL ){ + pTab->aCol[i].affinity = SQLITE_AFF_NUMERIC; + } sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, i, regOut); + pTab->aCol[i].affinity = aff; } nField++; } } if( nField==0 ){ Index: src/btree.c ================================================================== --- src/btree.c +++ src/btree.c @@ -5141,10 +5141,16 @@ } } memset(pCur->aOverflow, 0, nOvfl*sizeof(Pgno)); pCur->curFlags |= BTCF_ValidOvfl; }else{ + /* Sanity check the validity of the overflow page cache */ + assert( pCur->aOverflow[0]==nextPage + || pCur->aOverflow[0]==0 + || CORRUPT_DB ); + assert( pCur->aOverflow[0]!=0 || pCur->aOverflow[offset/ovflSize]==0 ); + /* If the overflow page-list cache has been allocated and the ** entry for the first required overflow page is valid, skip ** directly to it. */ if( pCur->aOverflow[offset/ovflSize] ){ @@ -5621,10 +5627,27 @@ *pRes = 1; rc = SQLITE_OK; } 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. +** +** This routine is is called from within assert() statements only. +** It is an internal verification routine and does not appear in production +** builds. +*/ +static int cursorIsAtLastEntry(BtCursor *pCur){ + int ii; + for(ii=0; iiiPage; ii++){ + if( pCur->aiIdx[ii]!=pCur->apPage[ii]->nCell ) return 0; + } + return pCur->ix==pCur->pPage->nCell-1 && pCur->pPage->leaf!=0; +} +#endif /* Move the cursor to the last entry in the table. Return SQLITE_OK ** on success. Set *pRes to 0 if the cursor actually points to something ** or set *pRes to 1 if the table is empty. */ @@ -5650,22 +5673,11 @@ assert( cursorOwnsBtShared(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); /* If the cursor already points to the last entry, this is a no-op. */ if( CURSOR_VALID==pCur->eState && (pCur->curFlags & BTCF_AtLast)!=0 ){ -#ifdef SQLITE_DEBUG - /* This block serves to assert() that the cursor really does point - ** to the last entry in the b-tree. */ - int ii; - for(ii=0; iiiPage; ii++){ - assert( pCur->aiIdx[ii]==pCur->apPage[ii]->nCell ); - } - assert( pCur->ix==pCur->pPage->nCell-1 || CORRUPT_DB ); - testcase( pCur->ix!=pCur->pPage->nCell-1 ); - /* ^-- dbsqlfuzz b92b72e4de80b5140c30ab71372ca719b8feb618 */ - assert( pCur->pPage->leaf ); -#endif + assert( cursorIsAtLastEntry(pCur) || CORRUPT_DB ); *pRes = 0; return SQLITE_OK; } return btreeLast(pCur, pRes); } @@ -5714,10 +5726,11 @@ *pRes = 0; return SQLITE_OK; } if( pCur->info.nKeycurFlags & BTCF_AtLast)!=0 ){ + assert( cursorIsAtLastEntry(pCur) || CORRUPT_DB ); *pRes = -1; return SQLITE_OK; } /* If the requested key is one more than the previous key, then ** try to get there using sqlite3BtreeNext() rather than a full @@ -9519,11 +9532,11 @@ dropCell(pPage, idx, info.nSize, &rc); if( rc ) goto end_insert; }else if( loc<0 && pPage->nCell>0 ){ assert( pPage->leaf ); idx = ++pCur->ix; - pCur->curFlags &= ~BTCF_ValidNKey; + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); }else{ assert( pPage->leaf ); } rc = insertCellFast(pPage, idx, newCell, szNew); assert( pPage->nOverflow==0 || rc==SQLITE_OK ); @@ -9549,11 +9562,11 @@ ** larger than the largest existing key, it is possible to insert the ** row without seeking the cursor. This can be a big performance boost. */ if( pPage->nOverflow ){ assert( rc==SQLITE_OK ); - pCur->curFlags &= ~(BTCF_ValidNKey); + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); rc = balance(pCur); /* Must make sure nOverflow is reset to zero even if the balance() ** fails. Internal data structure corruption will result otherwise. ** Also, set the cursor state to invalid. This stops saveCursorPosition() Index: src/build.c ================================================================== --- src/build.c +++ src/build.c @@ -2826,24 +2826,24 @@ int addrTop; /* Top of the co-routine */ int regRec; /* A record to be insert into the new table */ int regRowid; /* Rowid of the next row to insert */ int addrInsLoop; /* Top of the loop for inserting rows */ Table *pSelTab; /* A table that describes the SELECT results */ + int iCsr; /* Write cursor on the new table */ if( IN_SPECIAL_PARSE ){ pParse->rc = SQLITE_ERROR; pParse->nErr++; return; } + iCsr = pParse->nTab++; regYield = ++pParse->nMem; regRec = ++pParse->nMem; regRowid = ++pParse->nMem; - assert(pParse->nTab==1); sqlite3MayAbort(pParse); - sqlite3VdbeAddOp3(v, OP_OpenWrite, 1, pParse->regRoot, iDb); + sqlite3VdbeAddOp3(v, OP_OpenWrite, iCsr, pParse->regRoot, iDb); sqlite3VdbeChangeP5(v, OPFLAG_P2ISREG); - pParse->nTab = 2; addrTop = sqlite3VdbeCurrentAddr(v) + 1; sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop); if( pParse->nErr ) return; pSelTab = sqlite3ResultSetOfSelect(pParse, pSelect, SQLITE_AFF_BLOB); if( pSelTab==0 ) return; @@ -2860,15 +2860,15 @@ sqlite3VdbeJumpHere(v, addrTop - 1); addrInsLoop = sqlite3VdbeAddOp1(v, OP_Yield, dest.iSDParm); VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_MakeRecord, dest.iSdst, dest.nSdst, regRec); sqlite3TableAffinity(v, p, 0); - sqlite3VdbeAddOp2(v, OP_NewRowid, 1, regRowid); - sqlite3VdbeAddOp3(v, OP_Insert, 1, regRec, regRowid); + sqlite3VdbeAddOp2(v, OP_NewRowid, iCsr, regRowid); + sqlite3VdbeAddOp3(v, OP_Insert, iCsr, regRec, regRowid); sqlite3VdbeGoto(v, addrInsLoop); sqlite3VdbeJumpHere(v, addrInsLoop); - sqlite3VdbeAddOp1(v, OP_Close, 1); + sqlite3VdbeAddOp1(v, OP_Close, iCsr); } /* Compute the complete text of the CREATE statement */ if( pSelect ){ zStmt = createTableStmt(db, p); @@ -2925,13 +2925,10 @@ if( p->tabFlags & TF_HasGenerated ){ sqlite3VdbeAddOp4(v, OP_SqlExec, 0x0001, 0, 0, sqlite3MPrintf(db, "SELECT*FROM\"%w\".\"%w\"", db->aDb[iDb].zDbSName, p->zName), P4_DYNAMIC); } - sqlite3VdbeAddOp4(v, OP_SqlExec, 0x0001, 0, 0, - sqlite3MPrintf(db, "PRAGMA \"%w\".integrity_check(%Q)", - db->aDb[iDb].zDbSName, p->zName), P4_DYNAMIC); } /* Add the table to the in-memory representation of the database. */ if( db->init.busy ){ @@ -3004,13 +3001,16 @@ /* Legacy versions of SQLite allowed the use of the magic "rowid" column ** on a view, even though views do not have rowids. The following flag ** setting fixes this problem. But the fix can be disabled by compiling ** with -DSQLITE_ALLOW_ROWID_IN_VIEW in case there are legacy apps that - ** depend upon the old buggy behavior. */ -#ifndef SQLITE_ALLOW_ROWID_IN_VIEW - p->tabFlags |= TF_NoVisibleRowid; + ** depend upon the old buggy behavior. The ability can also be toggled + ** using sqlite3_config(SQLITE_CONFIG_ROWID_IN_VIEW,...) */ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + p->tabFlags |= sqlite3Config.mNoVisibleRowid; /* Optional. Allow by default */ +#else + p->tabFlags |= TF_NoVisibleRowid; /* Never allow rowid in view */ #endif sqlite3TwoPartName(pParse, pName1, pName2, &pName); iDb = sqlite3SchemaToIndex(db, p->pSchema); sqlite3FixInit(&sFix, pParse, iDb, "view", pName); @@ -3062,12 +3062,13 @@ #endif /* SQLITE_OMIT_VIEW */ #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) /* ** The Table structure pTable is really a VIEW. Fill in the names of -** the columns of the view in the pTable structure. Return the number -** of errors. If an error is seen leave an error message in pParse->zErrMsg. +** the columns of the view in the pTable structure. Return non-zero if +** there are errors. If an error is seen an error message is left +** in pParse->zErrMsg. */ static SQLITE_NOINLINE int viewGetColumnNames(Parse *pParse, Table *pTable){ Table *pSelTab; /* A fake table from which we get the result set */ Select *pSel; /* Copy of the SELECT that implements the view */ int nErr = 0; /* Number of errors encountered */ @@ -3186,11 +3187,11 @@ pTable->pSchema->schemaFlags |= DB_UnresetViews; if( db->mallocFailed ){ sqlite3DeleteColumnNames(db, pTable); } #endif /* SQLITE_OMIT_VIEW */ - return nErr; + return nErr + pParse->nErr; } int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ assert( pTable!=0 ); if( !IsVirtual(pTable) && pTable->nCol>0 ) return 0; return viewGetColumnNames(pParse, pTable); Index: src/ctime.c ================================================================== --- src/ctime.c +++ src/ctime.c @@ -62,10 +62,13 @@ #endif #ifdef SQLITE_ALLOW_COVERING_INDEX_SCAN # if SQLITE_ALLOW_COVERING_INDEX_SCAN != 1 "ALLOW_COVERING_INDEX_SCAN=" CTIMEOPT_VAL(SQLITE_ALLOW_COVERING_INDEX_SCAN), # endif +#endif +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + "ALLOW_ROWID_IN_VIEW", #endif #ifdef SQLITE_ALLOW_URI_AUTHORITY "ALLOW_URI_AUTHORITY", #endif #ifdef SQLITE_ATOMIC_INTRINSICS Index: src/expr.c ================================================================== --- src/expr.c +++ src/expr.c @@ -216,13 +216,14 @@ if( ExprHasProperty(pExpr, EP_Unlikely) ){ assert( ExprUseXList(pExpr) ); assert( pExpr->x.pList->nExpr>0 ); assert( pExpr->op==TK_FUNCTION ); pExpr = pExpr->x.pList->a[0].pExpr; - }else{ - assert( pExpr->op==TK_COLLATE ); + }else if( pExpr->op==TK_COLLATE ){ pExpr = pExpr->pLeft; + }else{ + break; } } return pExpr; } @@ -1365,10 +1366,11 @@ ** Recursively delete an expression tree. */ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){ assert( p!=0 ); assert( db!=0 ); +exprDeleteRestart: assert( !ExprUseUValue(p) || p->u.iValue>=0 ); assert( !ExprUseYWin(p) || !ExprUseYSub(p) ); assert( !ExprUseYWin(p) || p->y.pWin!=0 || db->mallocFailed ); assert( p->op!=TK_FUNCTION || !ExprUseYSub(p) ); #ifdef SQLITE_DEBUG @@ -1380,11 +1382,10 @@ } #endif if( !ExprHasProperty(p, (EP_TokenOnly|EP_Leaf)) ){ /* The Expr.x union is never used at the same time as Expr.pRight */ assert( (ExprUseXList(p) && p->x.pList==0) || p->pRight==0 ); - if( p->pLeft && p->op!=TK_SELECT_COLUMN ) sqlite3ExprDeleteNN(db, p->pLeft); if( p->pRight ){ assert( !ExprHasProperty(p, EP_WinFunc) ); sqlite3ExprDeleteNN(db, p->pRight); }else if( ExprUseXSelect(p) ){ assert( !ExprHasProperty(p, EP_WinFunc) ); @@ -1394,10 +1395,23 @@ #ifndef SQLITE_OMIT_WINDOWFUNC if( ExprHasProperty(p, EP_WinFunc) ){ sqlite3WindowDelete(db, p->y.pWin); } #endif + } + if( p->pLeft && p->op!=TK_SELECT_COLUMN ){ + Expr *pLeft = p->pLeft; + if( !ExprHasProperty(p, EP_Static) + && !ExprHasProperty(pLeft, EP_Static) + ){ + /* Avoid unnecessary recursion on unary operators */ + sqlite3DbNNFreeNN(db, p); + p = pLeft; + goto exprDeleteRestart; + }else{ + sqlite3ExprDeleteNN(db, pLeft); + } } } if( !ExprHasProperty(p, EP_Static) ){ sqlite3DbNNFreeNN(db, p); } @@ -1427,15 +1441,15 @@ ** This is similar to sqlite3ExprDelete() except that the delete is ** deferred until the pParse is deleted. ** ** The pExpr might be deleted immediately on an OOM error. ** -** The deferred delete is (currently) implemented by adding the -** pExpr to the pParse->pConstExpr list with a register number of 0. +** Return 0 if the delete was successfully deferred. Return non-zero +** if the delete happened immediately because of an OOM. */ -void sqlite3ExprDeferredDelete(Parse *pParse, Expr *pExpr){ - sqlite3ParserAddCleanup(pParse, sqlite3ExprDeleteGeneric, pExpr); +int sqlite3ExprDeferredDelete(Parse *pParse, Expr *pExpr){ + return 0==sqlite3ParserAddCleanup(pParse, sqlite3ExprDeleteGeneric, pExpr); } /* Invoke sqlite3RenameExprUnmap() and sqlite3ExprDelete() on the ** expression. */ @@ -1867,21 +1881,23 @@ pNewItem->zAlias = sqlite3DbStrDup(db, pOldItem->zAlias); pNewItem->fg = pOldItem->fg; pNewItem->iCursor = pOldItem->iCursor; pNewItem->addrFillSub = pOldItem->addrFillSub; pNewItem->regReturn = pOldItem->regReturn; + pNewItem->regResult = pOldItem->regResult; if( pNewItem->fg.isIndexedBy ){ pNewItem->u1.zIndexedBy = sqlite3DbStrDup(db, pOldItem->u1.zIndexedBy); + }else if( pNewItem->fg.isTabFunc ){ + pNewItem->u1.pFuncArg = + sqlite3ExprListDup(db, pOldItem->u1.pFuncArg, flags); + }else{ + pNewItem->u1.nRow = pOldItem->u1.nRow; } pNewItem->u2 = pOldItem->u2; if( pNewItem->fg.isCte ){ pNewItem->u2.pCteUse->nUse++; } - if( pNewItem->fg.isTabFunc ){ - pNewItem->u1.pFuncArg = - sqlite3ExprListDup(db, pOldItem->u1.pFuncArg, flags); - } pTab = pNewItem->pTab = pOldItem->pTab; if( pTab ){ pTab->nTabRef++; } pNewItem->pSelect = sqlite3SelectDup(db, pOldItem->pSelect, flags); @@ -2343,10 +2359,58 @@ } } return pExpr; } +/* +** 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: +** +** (1) It is a scalar function (not an aggregate or window function) +** (2) It has either the SQLITE_FUNC_CONSTANT or SQLITE_FUNC_SLOCHNG +** property. +** (3) All of its arguments are constants +** +** This routine sets pWalker->eCode to 0 if pExpr is not a constant. +** It makes no changes to pWalker->eCode if pExpr is constant. In +** every case, it returns WRC_Abort. +** +** Called as a service subroutine from exprNodeIsConstant(). +*/ +static SQLITE_NOINLINE int exprNodeIsConstantFunction( + Walker *pWalker, + Expr *pExpr +){ + int n; /* Number of arguments */ + ExprList *pList; /* List of arguments */ + FuncDef *pDef; /* The function */ + sqlite3 *db; /* The database */ + + assert( pExpr->op==TK_FUNCTION ); + if( ExprHasProperty(pExpr, EP_TokenOnly) + || (pList = pExpr->x.pList)==0 + ){; + n = 0; + }else{ + n = pList->nExpr; + sqlite3WalkExprList(pWalker, pList); + if( pWalker->eCode==0 ) return WRC_Abort; + } + db = pWalker->pParse->db; + pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0); + if( pDef==0 + || pDef->xFinalize!=0 + || (pDef->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0 + || ExprHasProperty(pExpr, EP_WinFunc) + ){ + pWalker->eCode = 0; + return WRC_Abort; + } + return WRC_Prune; +} + /* ** These routines are Walker callbacks used to check expressions to ** see if they are "constant" for some definition of constant. The ** Walker.eCode value determines the type of "constant" we are looking @@ -2371,10 +2435,11 @@ ** contain a bound parameter because they were generated by older versions ** of SQLite to be parsed by newer versions of SQLite without raising a ** malformed schema error. */ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){ + assert( pWalker->eCode>0 ); /* If pWalker->eCode is 2 then any term of the expression that comes from ** the ON or USING clauses of an outer join disqualifies the expression ** from being considered constant. */ if( pWalker->eCode==2 && ExprHasProperty(pExpr, EP_OuterON) ){ @@ -2390,10 +2455,12 @@ if( (pWalker->eCode>=4 || ExprHasProperty(pExpr,EP_ConstFunc)) && !ExprHasProperty(pExpr, EP_WinFunc) ){ if( pWalker->eCode==5 ) ExprSetProperty(pExpr, EP_FromDDL); return WRC_Continue; + }else if( pWalker->pParse ){ + return exprNodeIsConstantFunction(pWalker, pExpr); }else{ pWalker->eCode = 0; return WRC_Abort; } case TK_ID: @@ -2418,13 +2485,15 @@ } /* no break */ deliberate_fall_through case TK_IF_NULL_ROW: case TK_REGISTER: case TK_DOT: + case TK_RAISE: testcase( pExpr->op==TK_REGISTER ); testcase( pExpr->op==TK_IF_NULL_ROW ); testcase( pExpr->op==TK_DOT ); + testcase( pExpr->op==TK_RAISE ); pWalker->eCode = 0; return WRC_Abort; case TK_VARIABLE: if( pWalker->eCode==5 ){ /* Silently convert bound parameters that appear inside of CREATE @@ -2442,19 +2511,19 @@ testcase( pExpr->op==TK_SELECT ); /* sqlite3SelectWalkFail() disallows */ testcase( pExpr->op==TK_EXISTS ); /* sqlite3SelectWalkFail() disallows */ return WRC_Continue; } } -static int exprIsConst(Expr *p, int initFlag, int iCur){ +static int exprIsConst(Parse *pParse, Expr *p, int initFlag){ Walker w; w.eCode = initFlag; + w.pParse = pParse; w.xExprCallback = exprNodeIsConstant; w.xSelectCallback = sqlite3SelectWalkFail; #ifdef SQLITE_DEBUG w.xSelectCallback2 = sqlite3SelectWalkAssert2; #endif - w.u.iCur = iCur; sqlite3WalkExpr(&w, p); return w.eCode; } /* @@ -2462,13 +2531,19 @@ ** and 0 if it involves variables or function calls. ** ** For the purposes of this function, a double-quoted string (ex: "abc") ** is considered a variable but a single-quoted string (ex: 'abc') is ** a constant. +** +** The pParse parameter may be NULL. But if it is NULL, there is no way +** to determine if function calls are constant or not, and hence all +** function calls will be considered to be non-constant. If pParse is +** not NULL, then a function call might be constant, depending on the +** function and on its parameters. */ -int sqlite3ExprIsConstant(Expr *p){ - return exprIsConst(p, 1, 0); +int sqlite3ExprIsConstant(Parse *pParse, Expr *p){ + return exprIsConst(pParse, p, 1); } /* ** Walk an expression tree. Return non-zero if ** @@ -2480,22 +2555,55 @@ ** ** When this routine returns true, it indicates that the expression ** can be added to the pParse->pConstExpr list and evaluated once when ** the prepared statement starts up. See sqlite3ExprCodeRunJustOnce(). */ -int sqlite3ExprIsConstantNotJoin(Expr *p){ - return exprIsConst(p, 2, 0); +static int sqlite3ExprIsConstantNotJoin(Parse *pParse, Expr *p){ + return exprIsConst(pParse, p, 2); +} + +/* +** This routine examines sub-SELECT statements as an expression is being +** walked as part of sqlite3ExprIsTableConstant(). Sub-SELECTs are considered +** constant as long as they are uncorrelated - meaning that they do not +** contain any terms from outer contexts. +*/ +static int exprSelectWalkTableConstant(Walker *pWalker, Select *pSelect){ + assert( pSelect!=0 ); + assert( pWalker->eCode==3 || pWalker->eCode==0 ); + if( (pSelect->selFlags & SF_Correlated)!=0 ){ + pWalker->eCode = 0; + return WRC_Abort; + } + return WRC_Prune; } /* ** Walk an expression tree. Return non-zero if the expression is constant ** for any single row of the table with cursor iCur. In other words, the ** expression must not refer to any non-deterministic function nor any ** table other than iCur. +** +** Consider uncorrelated subqueries to be constants if the bAllowSubq +** parameter is true. */ -int sqlite3ExprIsTableConstant(Expr *p, int iCur){ - return exprIsConst(p, 3, iCur); +static int sqlite3ExprIsTableConstant(Expr *p, int iCur, int bAllowSubq){ + Walker w; + w.eCode = 3; + w.pParse = 0; + w.xExprCallback = exprNodeIsConstant; + if( bAllowSubq ){ + w.xSelectCallback = exprSelectWalkTableConstant; + }else{ + w.xSelectCallback = sqlite3SelectWalkFail; +#ifdef SQLITE_DEBUG + w.xSelectCallback2 = sqlite3SelectWalkAssert2; +#endif + } + w.u.iCur = iCur; + sqlite3WalkExpr(&w, p); + return w.eCode; } /* ** Check pExpr to see if it is an constraint on the single data source ** pSrc = &pSrcList->a[iSrc]. In other words, check to see if pExpr @@ -2509,11 +2617,14 @@ ** ** To be an single-source constraint, the following must be true: ** ** (1) pExpr cannot refer to any table other than pSrc->iCursor. ** -** (2) pExpr cannot use subqueries or non-deterministic functions. +** (2a) pExpr cannot use subqueries unless the bAllowSubq parameter is +** true and the subquery is non-correlated +** +** (2b) pExpr cannot use non-deterministic functions. ** ** (3) pSrc cannot be part of the left operand for a RIGHT JOIN. ** (Is there some way to relax this constraint?) ** ** (4) If pSrc is the right operand of a LEFT JOIN, then... @@ -2538,11 +2649,12 @@ ** on push-down. */ int sqlite3ExprIsSingleTableConstraint( Expr *pExpr, /* The constraint */ const SrcList *pSrcList, /* Complete FROM clause */ - int iSrc /* Which element of pSrcList to use */ + int iSrc, /* Which element of pSrcList to use */ + int bAllowSubq /* Allow non-correlated subqueries */ ){ const SrcItem *pSrc = &pSrcList->a[iSrc]; if( pSrc->fg.jointype & JT_LTORJ ){ return 0; /* rule (3) */ } @@ -2563,11 +2675,12 @@ } break; } } } - return sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor); /* rules (1), (2) */ + /* Rules (1), (2a), and (2b) handled by the following: */ + return sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor, bAllowSubq); } /* ** sqlite3WalkExpr() callback used by sqlite3ExprIsConstantOrGroupBy(). @@ -2648,11 +2761,11 @@ ** is considered a variable but a single-quoted string (ex: 'abc') is ** a constant. */ int sqlite3ExprIsConstantOrFunction(Expr *p, u8 isInit){ assert( isInit==0 || isInit==1 ); - return exprIsConst(p, 4+isInit, 0); + return exprIsConst(0, p, 4+isInit); } #ifdef SQLITE_ENABLE_CURSOR_HINTS /* ** Walk an expression tree. Return 1 if the expression contains a @@ -2738,13 +2851,16 @@ case TK_FLOAT: case TK_BLOB: return 0; case TK_COLUMN: assert( ExprUseYTab(p) ); - return ExprHasProperty(p, EP_CanBeNull) || - NEVER(p->y.pTab==0) || /* Reference to column of index on expr */ - (p->iColumn>=0 + return ExprHasProperty(p, EP_CanBeNull) + || NEVER(p->y.pTab==0) /* Reference to column of index on expr */ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + || (p->iColumn==XN_ROWID && IsView(p->y.pTab)) +#endif + || (p->iColumn>=0 && p->y.pTab->aCol!=0 /* Possible due to prior error */ && ALWAYS(p->iColumny.pTab->nCol) && p->y.pTab->aCol[p->iColumn].notNull==0); default: return 1; @@ -2893,17 +3009,17 @@ #ifndef SQLITE_OMIT_SUBQUERY /* ** The argument is an IN operator with a list (not a subquery) on the ** right-hand side. Return TRUE if that list is constant. */ -static int sqlite3InRhsIsConstant(Expr *pIn){ +static int sqlite3InRhsIsConstant(Parse *pParse, Expr *pIn){ Expr *pLHS; int res; assert( !ExprHasProperty(pIn, EP_xIsSelect) ); pLHS = pIn->pLeft; pIn->pLeft = 0; - res = sqlite3ExprIsConstant(pIn); + res = sqlite3ExprIsConstant(pParse, pIn); pIn->pLeft = pLHS; return res; } #endif @@ -3168,11 +3284,11 @@ ** the IN operator so return IN_INDEX_NOOP. */ if( eType==0 && (inFlags & IN_INDEX_NOOP_OK) && ExprUseXList(pX) - && (!sqlite3InRhsIsConstant(pX) || pX->x.pList->nExpr<=2) + && (!sqlite3InRhsIsConstant(pParse,pX) || pX->x.pList->nExpr<=2) ){ pParse->nTab--; /* Back out the allocation of the unused cursor */ iTab = -1; /* Cursor is not allocated */ eType = IN_INDEX_NOOP; } @@ -3451,11 +3567,11 @@ /* If the expression is not constant then we will need to ** disable the test that was generated above that makes sure ** this code only executes once. Because for a non-constant ** expression we need to rerun this code each time. */ - if( addrOnce && !sqlite3ExprIsConstant(pE2) ){ + if( addrOnce && !sqlite3ExprIsConstant(pParse, pE2) ){ sqlite3VdbeChangeToNoop(v, addrOnce-1); sqlite3VdbeChangeToNoop(v, addrOnce); ExprClearProperty(pExpr, EP_Subrtn); addrOnce = 0; } @@ -4788,11 +4904,13 @@ if( ExprHasProperty(pExpr, EP_WinFunc) ){ return pExpr->y.pWin->regResult; } #endif - if( ConstFactorOk(pParse) && sqlite3ExprIsConstantNotJoin(pExpr) ){ + if( ConstFactorOk(pParse) + && sqlite3ExprIsConstantNotJoin(pParse,pExpr) + ){ /* SQL functions can be expensive. So try to avoid running them ** multiple times if we know they always give the same result */ return sqlite3ExprCodeRunJustOnce(pParse, pExpr, -1); } assert( !ExprHasProperty(pExpr, EP_TokenOnly) ); @@ -4819,11 +4937,11 @@ }else if( pDef->funcFlags & (SQLITE_FUNC_DIRECT|SQLITE_FUNC_UNSAFE) ){ sqlite3ExprFunctionUsable(pParse, pExpr, pDef); } for(i=0; ia[i].pExpr) ){ + if( i<32 && sqlite3ExprIsConstant(pParse, pFarg->a[i].pExpr) ){ testcase( i==31 ); constMask |= MASKBIT32(i); } if( (pDef->funcFlags & SQLITE_FUNC_NEEDCOLL)!=0 && !pColl ){ pColl = sqlite3ExprCollSeq(pParse, pFarg->a[i].pExpr); @@ -4961,12 +5079,13 @@ } case TK_COLLATE: { if( !ExprHasProperty(pExpr, EP_Collate) ){ /* A TK_COLLATE Expr node without the EP_Collate tag is a so-called ** "SOFT-COLLATE" that is added to constraints that are pushed down - ** from outer queries into sub-queries by the push-down optimization. - ** Clear subtypes as subtypes may not cross a subquery boundary. + ** from outer queries into sub-queries by the WHERE-clause push-down + ** optimization. Clear subtypes as subtypes may not cross a subquery + ** boundary. */ assert( pExpr->pLeft ); sqlite3ExprCode(pParse, pExpr->pLeft, target); sqlite3VdbeAddOp1(v, OP_ClrSubtype, target); return target; @@ -5286,11 +5405,11 @@ int r2; pExpr = sqlite3ExprSkipCollateAndLikely(pExpr); if( ConstFactorOk(pParse) && ALWAYS(pExpr!=0) && pExpr->op!=TK_REGISTER - && sqlite3ExprIsConstantNotJoin(pExpr) + && sqlite3ExprIsConstantNotJoin(pParse, pExpr) ){ *pReg = 0; r2 = sqlite3ExprCodeRunJustOnce(pParse, pExpr, -1); }else{ int r1 = sqlite3GetTempReg(pParse); @@ -5350,11 +5469,11 @@ ** results in register target. The results are guaranteed to appear ** in register target. If the expression is constant, then this routine ** might choose to code the expression at initialization time. */ void sqlite3ExprCodeFactorable(Parse *pParse, Expr *pExpr, int target){ - if( pParse->okConstFactor && sqlite3ExprIsConstantNotJoin(pExpr) ){ + if( pParse->okConstFactor && sqlite3ExprIsConstantNotJoin(pParse,pExpr) ){ sqlite3ExprCodeRunJustOnce(pParse, pExpr, target); }else{ sqlite3ExprCodeCopy(pParse, pExpr, target); } } @@ -5409,11 +5528,11 @@ n--; }else{ sqlite3VdbeAddOp2(v, copyOp, j+srcReg-1, target+i); } }else if( (flags & SQLITE_ECEL_FACTOR)!=0 - && sqlite3ExprIsConstantNotJoin(pExpr) + && sqlite3ExprIsConstantNotJoin(pParse,pExpr) ){ sqlite3ExprCodeRunJustOnce(pParse, pExpr, target+i); }else{ int inReg = sqlite3ExprCodeTarget(pParse, pExpr, target+i); if( inReg!=target+i ){ @@ -6560,24 +6679,22 @@ if( pExpr->op!=TK_AGG_FUNCTION ){ if( iAggnColumn && pAggInfo->aCol[iAgg].pCExpr==pExpr ){ pExpr = sqlite3ExprDup(db, pExpr, 0); - if( pExpr ){ + if( pExpr && !sqlite3ExprDeferredDelete(pParse, pExpr) ){ pAggInfo->aCol[iAgg].pCExpr = pExpr; - sqlite3ExprDeferredDelete(pParse, pExpr); } } }else{ assert( pExpr->op==TK_AGG_FUNCTION ); if( ALWAYS(iAggnFunc) && pAggInfo->aFunc[iAgg].pFExpr==pExpr ){ pExpr = sqlite3ExprDup(db, pExpr, 0); - if( pExpr ){ + if( pExpr && !sqlite3ExprDeferredDelete(pParse, pExpr) ){ pAggInfo->aFunc[iAgg].pFExpr = pExpr; - sqlite3ExprDeferredDelete(pParse, pExpr); } } } } return WRC_Continue; Index: src/func.c ================================================================== --- src/func.c +++ src/func.c @@ -1890,11 +1890,11 @@ p = sqlite3_aggregate_context(context, 0); if( p && p->cnt>0 ){ if( p->approx ){ if( p->ovrfl ){ sqlite3_result_error(context,"integer overflow",-1); - }else if( !sqlite3IsNaN(p->rErr) ){ + }else if( !sqlite3IsOverflow(p->rErr) ){ sqlite3_result_double(context, p->rSum+p->rErr); }else{ sqlite3_result_double(context, p->rSum); } }else{ @@ -1907,11 +1907,11 @@ p = sqlite3_aggregate_context(context, 0); if( p && p->cnt>0 ){ double r; if( p->approx ){ r = p->rSum; - if( !sqlite3IsNaN(p->rErr) ) r += p->rErr; + if( !sqlite3IsOverflow(p->rErr) ) r += p->rErr; }else{ r = (double)(p->iSum); } sqlite3_result_double(context, r/(double)p->cnt); } @@ -1921,11 +1921,11 @@ double r = 0.0; p = sqlite3_aggregate_context(context, 0); if( p ){ if( p->approx ){ r = p->rSum; - if( !sqlite3IsNaN(p->rErr) ) r += p->rErr; + if( !sqlite3IsOverflow(p->rErr) ) r += p->rErr; }else{ r = (double)(p->iSum); } } sqlite3_result_double(context, r); @@ -2204,10 +2204,12 @@ StrAccum *pAccum = &pGCC->str; if( pAccum->accError==SQLITE_TOOBIG ){ sqlite3_result_error_toobig(context); }else if( pAccum->accError==SQLITE_NOMEM ){ sqlite3_result_error_nomem(context); + }else if( pGCC->nAccum>0 && pAccum->nChar==0 ){ + sqlite3_result_text(context, "", 1, SQLITE_STATIC); }else{ const char *zText = sqlite3_str_value(pAccum); sqlite3_result_text(context, zText, pAccum->nChar, SQLITE_TRANSIENT); } } Index: src/global.c ================================================================== --- src/global.c +++ src/global.c @@ -287,10 +287,13 @@ SQLITE_MEMDB_DEFAULT_MAXSIZE, /* mxMemdbSize */ #endif #ifndef SQLITE_UNTESTABLE 0, /* xTestCallback */ #endif +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + 0, /* mNoVisibleRowid. 0 == allow rowid-in-view */ +#endif 0, /* bLocaltimeFault */ 0, /* xAltLocaltime */ 0x7ffffffe, /* iOnceResetThreshold */ SQLITE_DEFAULT_SORTERREF_SIZE, /* szSorterRef */ 0, /* iPrngSeed */ Index: src/insert.c ================================================================== --- src/insert.c +++ src/insert.c @@ -575,10 +575,199 @@ */ # define autoIncBegin(A,B,C) (0) # define autoIncStep(A,B,C) #endif /* SQLITE_OMIT_AUTOINCREMENT */ +/* +** If argument pVal is a Select object returned by an sqlite3MultiValues() +** that was able to use the co-routine optimization, finish coding the +** co-routine. +*/ +void sqlite3MultiValuesEnd(Parse *pParse, Select *pVal){ + if( ALWAYS(pVal) && pVal->pSrc->nSrc>0 ){ + SrcItem *pItem = &pVal->pSrc->a[0]; + sqlite3VdbeEndCoroutine(pParse->pVdbe, pItem->regReturn); + sqlite3VdbeJumpHere(pParse->pVdbe, pItem->addrFillSub - 1); + } +} + +/* +** Return true if all expressions in the expression-list passed as the +** only argument are constant. +*/ +static int exprListIsConstant(Parse *pParse, ExprList *pRow){ + int ii; + for(ii=0; iinExpr; ii++){ + if( 0==sqlite3ExprIsConstant(pParse, pRow->a[ii].pExpr) ) return 0; + } + return 1; +} + +/* +** Return true if all expressions in the expression-list passed as the +** only argument are both constant and have no affinity. +*/ +static int exprListIsNoAffinity(Parse *pParse, ExprList *pRow){ + int ii; + if( exprListIsConstant(pParse,pRow)==0 ) return 0; + for(ii=0; iinExpr; ii++){ + Expr *pExpr = pRow->a[ii].pExpr; + assert( pExpr->op!=TK_RAISE ); + assert( pExpr->affExpr==0 ); + if( 0!=sqlite3ExprAffinity(pExpr) ) return 0; + } + return 1; + +} + +/* +** This function is called by the parser for the second and subsequent +** rows of a multi-row VALUES clause. Argument pLeft is the part of +** the VALUES clause already parsed, argument pRow is the vector of values +** for the new row. The Select object returned represents the complete +** VALUES clause, including the new row. +** +** There are two ways in which this may be achieved - by incremental +** coding of a co-routine (the "co-routine" method) or by returning a +** Select object equivalent to the following (the "UNION ALL" method): +** +** "pLeft UNION ALL SELECT pRow" +** +** If the VALUES clause contains a lot of rows, this compound Select +** object may consume a lot of memory. +** +** When the co-routine method is used, each row that will be returned +** by the VALUES clause is coded into part of a co-routine as it is +** passed to this function. The returned Select object is equivalent to: +** +** SELECT * FROM ( +** Select object to read co-routine +** ) +** +** The co-routine method is used in most cases. Exceptions are: +** +** a) If the current statement has a WITH clause. This is to avoid +** statements like: +** +** WITH cte AS ( VALUES('x'), ('y') ... ) +** SELECT * FROM cte AS a, cte AS b; +** +** This will not work, as the co-routine uses a hard-coded register +** for its OP_Yield instructions, and so it is not possible for two +** cursors to iterate through it concurrently. +** +** b) The schema is currently being parsed (i.e. the VALUES clause is part +** of a schema item like a VIEW or TRIGGER). In this case there is no VM +** being generated when parsing is taking place, and so generating +** a co-routine is not possible. +** +** c) There are non-constant expressions in the VALUES clause (e.g. +** the VALUES clause is part of a correlated sub-query). +** +** d) One or more of the values in the first row of the VALUES clause +** has an affinity (i.e. is a CAST expression). This causes problems +** because the complex rules SQLite uses (see function +** sqlite3SubqueryColumnTypes() in select.c) to determine the effective +** affinity of such a column for all rows require access to all values in +** the column simultaneously. +*/ +Select *sqlite3MultiValues(Parse *pParse, Select *pLeft, ExprList *pRow){ + + if( pParse->bHasWith /* condition (a) above */ + || pParse->db->init.busy /* condition (b) above */ + || exprListIsConstant(pParse,pRow)==0 /* condition (c) above */ + || (pLeft->pSrc->nSrc==0 && + exprListIsNoAffinity(pParse,pLeft->pEList)==0) /* condition (d) above */ + || IN_SPECIAL_PARSE + ){ + /* The co-routine method cannot be used. Fall back to UNION ALL. */ + Select *pSelect = 0; + int f = SF_Values | SF_MultiValue; + if( pLeft->pSrc->nSrc ){ + sqlite3MultiValuesEnd(pParse, pLeft); + f = SF_Values; + }else if( pLeft->pPrior ){ + /* In this case set the SF_MultiValue flag only if it was set on pLeft */ + f = (f & pLeft->selFlags); + } + pSelect = sqlite3SelectNew(pParse, pRow, 0, 0, 0, 0, 0, f, 0); + pLeft->selFlags &= ~SF_MultiValue; + if( pSelect ){ + pSelect->op = TK_ALL; + pSelect->pPrior = pLeft; + pLeft = pSelect; + } + }else{ + SrcItem *p = 0; /* SrcItem that reads from co-routine */ + + if( pLeft->pSrc->nSrc==0 ){ + /* Co-routine has not yet been started and the special Select object + ** that accesses the co-routine has not yet been created. This block + ** does both those things. */ + Vdbe *v = sqlite3GetVdbe(pParse); + Select *pRet = sqlite3SelectNew(pParse, 0, 0, 0, 0, 0, 0, 0, 0); + + /* Ensure the database schema has been read. This is to ensure we have + ** the correct text encoding. */ + if( (pParse->db->mDbFlags & DBFLAG_SchemaKnownOk)==0 ){ + sqlite3ReadSchema(pParse); + } + + if( pRet ){ + SelectDest dest; + pRet->pSrc->nSrc = 1; + pRet->pPrior = pLeft->pPrior; + pRet->op = pLeft->op; + pLeft->pPrior = 0; + pLeft->op = TK_SELECT; + assert( pLeft->pNext==0 ); + assert( pRet->pNext==0 ); + p = &pRet->pSrc->a[0]; + p->pSelect = pLeft; + p->fg.viaCoroutine = 1; + p->addrFillSub = sqlite3VdbeCurrentAddr(v) + 1; + p->regReturn = ++pParse->nMem; + p->iCursor = -1; + p->u1.nRow = 2; + sqlite3VdbeAddOp3(v,OP_InitCoroutine,p->regReturn,0,p->addrFillSub); + sqlite3SelectDestInit(&dest, SRT_Coroutine, p->regReturn); + + /* Allocate registers for the output of the co-routine. Do so so + ** that there are two unused registers immediately before those + ** used by the co-routine. This allows the code in sqlite3Insert() + ** to use these registers directly, instead of copying the output + ** of the co-routine to a separate array for processing. */ + dest.iSdst = pParse->nMem + 3; + dest.nSdst = pLeft->pEList->nExpr; + pParse->nMem += 2 + dest.nSdst; + + pLeft->selFlags |= SF_MultiValue; + sqlite3Select(pParse, pLeft, &dest); + p->regResult = dest.iSdst; + assert( pParse->nErr || dest.iSdst>0 ); + pLeft = pRet; + } + }else{ + p = &pLeft->pSrc->a[0]; + assert( !p->fg.isTabFunc && !p->fg.isIndexedBy ); + p->u1.nRow++; + } + + if( pParse->nErr==0 ){ + assert( p!=0 ); + if( p->pSelect->pEList->nExpr!=pRow->nExpr ){ + sqlite3SelectWrongNumTermsError(pParse, p->pSelect); + }else{ + sqlite3ExprCodeExprList(pParse, pRow, p->regResult, 0, 0); + sqlite3VdbeAddOp1(pParse->pVdbe, OP_Yield, p->regReturn); + } + } + sqlite3ExprListDelete(pParse->db, pRow); + } + + return pLeft; +} /* Forward declaration */ static int xferOptimization( Parse *pParse, /* Parser context */ Table *pDest, /* The table we are inserting into */ @@ -911,29 +1100,44 @@ ** co-routine is the common header to the 3rd and 4th templates. */ if( pSelect ){ /* Data is coming from a SELECT or from a multi-row VALUES clause. ** Generate a co-routine to run the SELECT. */ - int regYield; /* Register holding co-routine entry-point */ - int addrTop; /* Top of the co-routine */ int rc; /* Result code */ - regYield = ++pParse->nMem; - addrTop = sqlite3VdbeCurrentAddr(v) + 1; - sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop); - sqlite3SelectDestInit(&dest, SRT_Coroutine, regYield); - dest.iSdst = bIdListInOrder ? regData : 0; - dest.nSdst = pTab->nCol; - rc = sqlite3Select(pParse, pSelect, &dest); - regFromSelect = dest.iSdst; - assert( db->pParse==pParse ); - if( rc || pParse->nErr ) goto insert_cleanup; - assert( db->mallocFailed==0 ); - sqlite3VdbeEndCoroutine(v, regYield); - sqlite3VdbeJumpHere(v, addrTop - 1); /* label B: */ - assert( pSelect->pEList ); - nColumn = pSelect->pEList->nExpr; + if( pSelect->pSrc->nSrc==1 + && pSelect->pSrc->a[0].fg.viaCoroutine + && pSelect->pPrior==0 + ){ + SrcItem *pItem = &pSelect->pSrc->a[0]; + dest.iSDParm = pItem->regReturn; + regFromSelect = pItem->regResult; + nColumn = pItem->pSelect->pEList->nExpr; + ExplainQueryPlan((pParse, 0, "SCAN %S", pItem)); + if( bIdListInOrder && nColumn==pTab->nCol ){ + regData = regFromSelect; + regRowid = regData - 1; + regIns = regRowid - (IsVirtual(pTab) ? 1 : 0); + } + }else{ + int addrTop; /* Top of the co-routine */ + int regYield = ++pParse->nMem; + addrTop = sqlite3VdbeCurrentAddr(v) + 1; + sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop); + sqlite3SelectDestInit(&dest, SRT_Coroutine, regYield); + dest.iSdst = bIdListInOrder ? regData : 0; + dest.nSdst = pTab->nCol; + rc = sqlite3Select(pParse, pSelect, &dest); + regFromSelect = dest.iSdst; + assert( db->pParse==pParse ); + if( rc || pParse->nErr ) goto insert_cleanup; + assert( db->mallocFailed==0 ); + sqlite3VdbeEndCoroutine(v, regYield); + sqlite3VdbeJumpHere(v, addrTop - 1); /* label B: */ + assert( pSelect->pEList ); + nColumn = pSelect->pEList->nExpr; + } /* Set useTempTable to TRUE if the result of the SELECT statement ** should be written into a temporary table (template 4). Set to ** FALSE if each output row of the SELECT can be written directly into ** the destination table (template 3). @@ -1084,11 +1288,11 @@ pNx->pUpsertSrc = pTabList; pNx->regData = regData; pNx->iDataCur = iDataCur; pNx->iIdxCur = iIdxCur; if( pNx->pUpsertTarget ){ - if( sqlite3UpsertAnalyzeTarget(pParse, pTabList, pNx) ){ + if( sqlite3UpsertAnalyzeTarget(pParse, pTabList, pNx, pUpsert) ){ goto insert_cleanup; } } pNx = pNx->pNextUpsert; }while( pNx!=0 ); @@ -2976,11 +3180,14 @@ ** further downstream. */ return 0; /* Corrupt schema - two indexes on the same btree */ } } #ifndef SQLITE_OMIT_CHECK - if( pDest->pCheck && sqlite3ExprListCompare(pSrc->pCheck,pDest->pCheck,-1) ){ + if( pDest->pCheck + && (db->mDbFlags & DBFLAG_Vacuum)==0 + && sqlite3ExprListCompare(pSrc->pCheck,pDest->pCheck,-1) + ){ return 0; /* Tables have different CHECK constraints. Ticket #2252 */ } #endif #ifndef SQLITE_OMIT_FOREIGN_KEY /* Disallow the transfer optimization if the destination table contains Index: src/json.c ================================================================== --- src/json.c +++ src/json.c @@ -3793,17 +3793,10 @@ } if( !eErr ) sqlite3_result_int64(ctx, cnt); jsonParseFree(p); } -/* True if the string is all digits */ -static int jsonAllDigits(const char *z, int n){ - int i; - for(i=0; i $[NUMBER] // PG compatible ** LABEL ==> $.LABEL // PG compatible ** [NUMBER] ==> $[NUMBER] // Not PG. Purely for convenience */ jsonStringInit(&jx, ctx); - if( jsonAllDigits(zPath, nPath) ){ + if( sqlite3_value_type(argv[i])==SQLITE_INTEGER ){ jsonAppendRawNZ(&jx, "[", 1); jsonAppendRaw(&jx, zPath, nPath); jsonAppendRawNZ(&jx, "]", 2); }else if( jsonAllAlphanum(zPath, nPath) ){ jsonAppendRawNZ(&jx, ".", 1); Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -762,10 +762,22 @@ case SQLITE_CONFIG_MEMDB_MAXSIZE: { sqlite3GlobalConfig.mxMemdbSize = va_arg(ap, sqlite3_int64); break; } #endif /* SQLITE_OMIT_DESERIALIZE */ + + case SQLITE_CONFIG_ROWID_IN_VIEW: { + int *pVal = va_arg(ap,int*); +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + if( 0==*pVal ) sqlite3GlobalConfig.mNoVisibleRowid = TF_NoVisibleRowid; + if( 1==*pVal ) sqlite3GlobalConfig.mNoVisibleRowid = 0; + *pVal = (sqlite3GlobalConfig.mNoVisibleRowid==0); +#else + *pVal = 0; +#endif + break; + } default: { rc = SQLITE_ERROR; break; } Index: src/os_unix.c ================================================================== --- src/os_unix.c +++ src/os_unix.c @@ -6396,16 +6396,23 @@ /* If unable to create a journal because the directory is not ** writable, change the error code to indicate that. */ rc = SQLITE_READONLY_DIRECTORY; }else if( errno!=EISDIR && isReadWrite ){ /* Failed to open the file for read/write access. Try read-only. */ + UnixUnusedFd *pReadonly = 0; flags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); openFlags &= ~(O_RDWR|O_CREAT); flags |= SQLITE_OPEN_READONLY; openFlags |= O_RDONLY; isReadonly = 1; - fd = robust_open(zName, openFlags, openMode); + pReadonly = findReusableFd(zName, flags); + if( pReadonly ){ + fd = pReadonly->fd; + sqlite3_free(pReadonly); + }else{ + fd = robust_open(zName, openFlags, openMode); + } } } if( fd<0 ){ int rc2 = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zName); if( rc==SQLITE_OK ) rc = rc2; Index: src/pager.c ================================================================== --- src/pager.c +++ src/pager.c @@ -7085,11 +7085,11 @@ /* ** Return the file handle for the journal file (if it exists). ** This will be either the rollback journal or the WAL file. */ sqlite3_file *sqlite3PagerJrnlFile(Pager *pPager){ -#if SQLITE_OMIT_WAL +#ifdef SQLITE_OMIT_WAL return pPager->jfd; #else return pPager->pWal ? sqlite3WalFile(pPager->pWal) : pPager->jfd; #endif } Index: src/parse.y ================================================================== --- src/parse.y +++ src/parse.y @@ -563,10 +563,11 @@ %ifndef SQLITE_OMIT_CTE select(A) ::= WITH wqlist(W) selectnowith(X). {A = attachWithToSelect(pParse,X,W);} select(A) ::= WITH RECURSIVE wqlist(W) selectnowith(X). {A = attachWithToSelect(pParse,X,W);} + %endif /* SQLITE_OMIT_CTE */ select(A) ::= selectnowith(A). { Select *p = A; if( p ){ parserDoubleLinkSelect(pParse, p); @@ -620,28 +621,31 @@ } } %endif -oneselect(A) ::= values(A). - +// Single row VALUES clause. +// %type values {Select*} +oneselect(A) ::= values(A). %destructor values {sqlite3SelectDelete(pParse->db, $$);} values(A) ::= VALUES LP nexprlist(X) RP. { A = sqlite3SelectNew(pParse,X,0,0,0,0,0,SF_Values,0); } -values(A) ::= values(A) COMMA LP nexprlist(Y) RP. { - Select *pRight, *pLeft = A; - pRight = sqlite3SelectNew(pParse,Y,0,0,0,0,0,SF_Values|SF_MultiValue,0); - if( ALWAYS(pLeft) ) pLeft->selFlags &= ~SF_MultiValue; - if( pRight ){ - pRight->op = TK_ALL; - pRight->pPrior = pLeft; - A = pRight; - }else{ - A = pLeft; - } + +// Multiple row VALUES clause. +// +%type mvalues {Select*} +oneselect(A) ::= mvalues(A). { + sqlite3MultiValuesEnd(pParse, A); +} +%destructor mvalues {sqlite3SelectDelete(pParse->db, $$);} +mvalues(A) ::= values(A) COMMA LP nexprlist(Y) RP. { + A = sqlite3MultiValues(pParse, A, Y); +} +mvalues(A) ::= mvalues(A) COMMA LP nexprlist(Y) RP. { + A = sqlite3MultiValues(pParse, A, Y); } // The "distinct" nonterminal is true (1) if the DISTINCT keyword is // present and false (0) if it is not. // @@ -1276,12 +1280,21 @@ expr(A) ::= NOT(B) expr(X). {A = sqlite3PExpr(pParse, @B, X, 0);/*A-overwrites-B*/} expr(A) ::= BITNOT(B) expr(X). {A = sqlite3PExpr(pParse, @B, X, 0);/*A-overwrites-B*/} expr(A) ::= PLUS|MINUS(B) expr(X). [BITNOT] { - A = sqlite3PExpr(pParse, @B==TK_PLUS ? TK_UPLUS : TK_UMINUS, X, 0); - /*A-overwrites-B*/ + Expr *p = X; + u8 op = @B + (TK_UPLUS-TK_PLUS); + assert( TK_UPLUS>TK_PLUS ); + assert( TK_UMINUS == TK_MINUS + (TK_UPLUS - TK_PLUS) ); + if( p && p->op==TK_UPLUS ){ + p->op = op; + A = p; + }else{ + A = sqlite3PExpr(pParse, op, p, 0); + /*A-overwrites-B*/ + } } expr(A) ::= expr(B) PTR(C) expr(D). { ExprList *pList = sqlite3ExprListAppend(pParse, 0, B); pList = sqlite3ExprListAppend(pParse, pList, D); @@ -1319,11 +1332,11 @@ sqlite3ExprUnmapAndDelete(pParse, A); A = sqlite3Expr(pParse->db, TK_STRING, N ? "true" : "false"); if( A ) sqlite3ExprIdToTrueFalse(A); }else{ Expr *pRHS = Y->a[0].pExpr; - if( Y->nExpr==1 && sqlite3ExprIsConstant(pRHS) && A->op!=TK_VECTOR ){ + if( Y->nExpr==1 && sqlite3ExprIsConstant(pParse,pRHS) && A->op!=TK_VECTOR ){ Y->a[0].pExpr = 0; sqlite3ExprListDelete(pParse->db, Y); pRHS = sqlite3PExpr(pParse, TK_UPLUS, pRHS, 0); A = sqlite3PExpr(pParse, TK_EQ, A, pRHS); }else if( Y->nExpr==1 && pRHS->op==TK_SELECT ){ @@ -1759,13 +1772,14 @@ %type wqas {u8} wqas(A) ::= AS. {A = M10d_Any;} wqas(A) ::= AS MATERIALIZED. {A = M10d_Yes;} wqas(A) ::= AS NOT MATERIALIZED. {A = M10d_No;} -wqitem(A) ::= nm(X) eidlist_opt(Y) wqas(M) LP select(Z) RP. { +wqitem(A) ::= withnm(X) eidlist_opt(Y) wqas(M) LP select(Z) RP. { A = sqlite3CteNew(pParse, &X, Y, Z, M); /*A-overwrites-X*/ } +withnm(A) ::= nm(A). {pParse->bHasWith = 1;} wqlist(A) ::= wqitem(X). { A = sqlite3WithAdd(pParse, 0, X); /*A-overwrites-X*/ } wqlist(A) ::= wqlist(A) COMMA wqitem(X). { A = sqlite3WithAdd(pParse, A, X); @@ -1922,12 +1936,12 @@ AGG_FUNCTION /* An aggregate function */ AGG_COLUMN /* An aggregated column */ TRUEFALSE /* True or false keyword */ ISNOT /* Combination of IS and NOT */ FUNCTION /* A function invocation */ - UMINUS /* Unary minus */ UPLUS /* Unary plus */ + UMINUS /* Unary minus */ TRUTH /* IS TRUE or IS FALSE or IS NOT TRUE or IS NOT FALSE */ REGISTER /* Reference to a VDBE register */ VECTOR /* Vector */ SELECT_COLUMN /* Choose a single column from a multi-column SELECT */ IF_NULL_ROW /* the if-null-row operator */ Index: src/pragma.c ================================================================== --- src/pragma.c +++ src/pragma.c @@ -1701,11 +1701,11 @@ pParse->nMem = 6; /* Set the maximum error count */ mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX; if( zRight ){ - if( sqlite3GetInt32(zRight, &mxErr) ){ + if( sqlite3GetInt32(pValue->z, &mxErr) ){ if( mxErr<=0 ){ mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX; } }else{ pObjTab = sqlite3LocateTable(pParse, 0, zRight, @@ -2868,13 +2868,13 @@ if( pTab->nHidden==0 ){ return SQLITE_OK; } pConstraint = pIdxInfo->aConstraint; seen[0] = 0; seen[1] = 0; for(i=0; inConstraint; i++, pConstraint++){ - if( pConstraint->usable==0 ) continue; + if( pConstraint->iColumn < pTab->iHidden ) continue; if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; - if( pConstraint->iColumn < pTab->iHidden ) continue; + if( pConstraint->usable==0 ) return SQLITE_CONSTRAINT; j = pConstraint->iColumn - pTab->iHidden; assert( j < 2 ); seen[j] = i+1; } if( seen[0]==0 ){ @@ -2883,16 +2883,17 @@ return SQLITE_OK; } j = seen[0]-1; pIdxInfo->aConstraintUsage[j].argvIndex = 1; pIdxInfo->aConstraintUsage[j].omit = 1; - if( seen[1]==0 ) return SQLITE_OK; pIdxInfo->estimatedCost = (double)20; pIdxInfo->estimatedRows = 20; - j = seen[1]-1; - pIdxInfo->aConstraintUsage[j].argvIndex = 2; - pIdxInfo->aConstraintUsage[j].omit = 1; + if( seen[1] ){ + j = seen[1]-1; + pIdxInfo->aConstraintUsage[j].argvIndex = 2; + pIdxInfo->aConstraintUsage[j].omit = 1; + } return SQLITE_OK; } /* Create a new cursor for the pragma virtual table */ static int pragmaVtabOpen(sqlite3_vtab *pVtab, sqlite3_vtab_cursor **ppCursor){ @@ -2908,10 +2909,11 @@ /* Clear all content from pragma virtual table cursor. */ static void pragmaVtabCursorClear(PragmaVtabCursor *pCsr){ int i; sqlite3_finalize(pCsr->pPragma); pCsr->pPragma = 0; + pCsr->iRowid = 0; for(i=0; iazArg); i++){ sqlite3_free(pCsr->azArg[i]); pCsr->azArg[i] = 0; } } Index: src/prepare.c ================================================================== --- src/prepare.c +++ src/prepare.c @@ -631,11 +631,17 @@ void *sqlite3ParserAddCleanup( Parse *pParse, /* Destroy when this Parser finishes */ void (*xCleanup)(sqlite3*,void*), /* The cleanup routine */ void *pPtr /* Pointer to object to be cleaned up */ ){ - ParseCleanup *pCleanup = sqlite3DbMallocRaw(pParse->db, sizeof(*pCleanup)); + ParseCleanup *pCleanup; + if( sqlite3FaultSim(300) ){ + pCleanup = 0; + sqlite3OomFault(pParse->db); + }else{ + pCleanup = sqlite3DbMallocRaw(pParse->db, sizeof(*pCleanup)); + } if( pCleanup ){ pCleanup->pNext = pParse->pCleanup; pParse->pCleanup = pCleanup; pCleanup->pPtr = pPtr; pCleanup->xCleanup = xCleanup; Index: src/printf.c ================================================================== --- src/printf.c +++ src/printf.c @@ -855,13 +855,17 @@ sqlite3_str_appendall(pAccum, pItem->zName); }else if( pItem->zAlias ){ sqlite3_str_appendall(pAccum, pItem->zAlias); }else{ Select *pSel = pItem->pSelect; - assert( pSel!=0 ); + assert( pSel!=0 ); /* Because of tag-20240424-1 */ if( pSel->selFlags & SF_NestedFrom ){ sqlite3_str_appendf(pAccum, "(join-%u)", pSel->selId); + }else if( pSel->selFlags & SF_MultiValue ){ + assert( !pItem->fg.isTabFunc && !pItem->fg.isIndexedBy ); + sqlite3_str_appendf(pAccum, "%u-ROW VALUES CLAUSE", + pItem->u1.nRow); }else{ sqlite3_str_appendf(pAccum, "(subquery-%u)", pSel->selId); } } length = width = 0; Index: src/resolve.c ================================================================== --- src/resolve.c +++ src/resolve.c @@ -467,12 +467,41 @@ } break; } } if( 0==cnt && VisibleRowid(pTab) ){ + /* pTab is a potential ROWID match. Keep track of it and match + ** the ROWID later if that seems appropriate. (Search for "cntTab" + ** to find related code.) Only allow a ROWID match if there is + ** a single ROWID match candidate. + */ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + /* In SQLITE_ALLOW_ROWID_IN_VIEW mode, allow a ROWID match + ** if there is a single VIEW candidate or if there is a single + ** non-VIEW candidate plus multiple VIEW candidates. In other + ** words non-VIEW candidate terms take precedence over VIEWs. + */ + if( cntTab==0 + || (cntTab==1 + && ALWAYS(pMatch!=0) + && ALWAYS(pMatch->pTab!=0) + && (pMatch->pTab->tabFlags & TF_Ephemeral)!=0 + && (pTab->tabFlags & TF_Ephemeral)==0) + ){ + cntTab = 1; + pMatch = pItem; + }else{ + cntTab++; + } +#else + /* The (much more common) non-SQLITE_ALLOW_ROWID_IN_VIEW case is + ** simpler since we require exactly one candidate, which will + ** always be a non-VIEW + */ cntTab++; pMatch = pItem; +#endif } } if( pMatch ){ pExpr->iTable = pMatch->iCursor; assert( ExprUseYTab(pExpr) ); @@ -497,11 +526,12 @@ int op = pParse->eTriggerOp; assert( op==TK_DELETE || op==TK_UPDATE || op==TK_INSERT ); if( pParse->bReturning ){ if( (pNC->ncFlags & NC_UBaseReg)!=0 && ALWAYS(zTab==0 - || sqlite3StrICmp(zTab,pParse->pTriggerTab->zName)==0) + || sqlite3StrICmp(zTab,pParse->pTriggerTab->zName)==0 + || isValidSchemaTableName(zTab, pParse->pTriggerTab, 0)) ){ pExpr->iTable = op!=TK_DELETE; pTab = pParse->pTriggerTab; } }else if( op!=TK_DELETE && zTab && sqlite3StrICmp("new",zTab) == 0 ){ @@ -594,17 +624,22 @@ /* ** Perhaps the name is a reference to the ROWID */ if( cnt==0 - && cntTab==1 + && cntTab>=1 && pMatch && (pNC->ncFlags & (NC_IdxExpr|NC_GenCol))==0 && sqlite3IsRowid(zCol) && ALWAYS(VisibleRowid(pMatch->pTab) || pMatch->fg.isNestedFrom) ){ - cnt = 1; + cnt = cntTab; +#if SQLITE_ALLOW_ROWID_IN_VIEW+0==2 + if( pMatch->pTab!=0 && IsView(pMatch->pTab) ){ + eNewExprOp = TK_NULL; + } +#endif if( pMatch->fg.isNestedFrom==0 ) pExpr->iColumn = -1; pExpr->affExpr = SQLITE_AFF_INTEGER; } /* @@ -791,12 +826,16 @@ ** (See ticket [b92e5e8ec2cdbaa1]). ** ** If a generated column is referenced, set bits for every column ** of the table. */ - if( pExpr->iColumn>=0 && cnt==1 && pMatch!=0 ){ - pMatch->colUsed |= sqlite3ExprColUsed(pExpr); + if( pMatch ){ + if( pExpr->iColumn>=0 ){ + pMatch->colUsed |= sqlite3ExprColUsed(pExpr); + }else{ + pMatch->fg.rowidUsed = 1; + } } pExpr->op = eNewExprOp; lookupname_end: if( cnt==1 ){ @@ -969,10 +1008,23 @@ ** If this optimization occurs, also restore the NameContext ref-counts ** to the state they where in before the "column" LHS expression was ** resolved. This prevents "column" from being counted as having been ** referenced, which might prevent a SELECT from being erroneously ** marked as correlated. + ** + ** 2024-03-28: Beware of aggregates. A bare column of aggregated table + ** can still evaluate to NULL even though it is marked as NOT NULL. + ** Example: + ** + ** CREATE TABLE t1(a INT NOT NULL); + ** SELECT a, a IS NULL, a IS NOT NULL, count(*) FROM t1; + ** + ** The "a IS NULL" and "a IS NOT NULL" expressions cannot be optimized + ** here because at the time this case is hit, we do not yet know whether + ** or not t1 is being aggregated. We have to assume the worst and omit + ** the optimization. The only time it is safe to apply this optimization + ** is within the WHERE clause. */ case TK_NOTNULL: case TK_ISNULL: { int anRef[8]; NameContext *p; @@ -979,23 +1031,40 @@ int i; for(i=0, p=pNC; p && ipNext, i++){ anRef[i] = p->nRef; } sqlite3WalkExpr(pWalker, pExpr->pLeft); - if( 0==sqlite3ExprCanBeNull(pExpr->pLeft) && !IN_RENAME_OBJECT ){ - testcase( ExprHasProperty(pExpr, EP_OuterON) ); - assert( !ExprHasProperty(pExpr, EP_IntValue) ); - pExpr->u.iValue = (pExpr->op==TK_NOTNULL); - pExpr->flags |= EP_IntValue; - pExpr->op = TK_INTEGER; - - for(i=0, p=pNC; p && ipNext, i++){ - p->nRef = anRef[i]; - } - sqlite3ExprDelete(pParse->db, pExpr->pLeft); - pExpr->pLeft = 0; - } + if( IN_RENAME_OBJECT ) return WRC_Prune; + if( sqlite3ExprCanBeNull(pExpr->pLeft) ){ + /* The expression can be NULL. So the optimization does not apply */ + return WRC_Prune; + } + + for(i=0, p=pNC; p; p=p->pNext, i++){ + if( (p->ncFlags & NC_Where)==0 ){ + return WRC_Prune; /* Not in a WHERE clause. Unsafe to optimize. */ + } + } + testcase( ExprHasProperty(pExpr, EP_OuterON) ); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x80000 ){ + sqlite3DebugPrintf( + "NOT NULL strength reduction converts the following to %d:\n", + pExpr->op==TK_NOTNULL + ); + sqlite3ShowExpr(pExpr); + } +#endif /* TREETRACE_ENABLED */ + pExpr->u.iValue = (pExpr->op==TK_NOTNULL); + pExpr->flags |= EP_IntValue; + pExpr->op = TK_INTEGER; + for(i=0, p=pNC; p && ipNext, i++){ + p->nRef = anRef[i]; + } + sqlite3ExprDelete(pParse->db, pExpr->pLeft); + pExpr->pLeft = 0; return WRC_Prune; } /* A column name: ID ** Or table name and column name: ID.ID @@ -1214,15 +1283,13 @@ #else pNC->ncFlags &= ~NC_AllowAgg; #endif } } -#ifndef SQLITE_OMIT_WINDOWFUNC - else if( ExprHasProperty(pExpr, EP_WinFunc) ){ + else if( ExprHasProperty(pExpr, EP_WinFunc) || pExpr->pLeft ){ is_agg = 1; } -#endif sqlite3WalkExprList(pWalker, pList); if( is_agg ){ if( pExpr->pLeft ){ assert( pExpr->pLeft->op==TK_ORDER ); assert( ExprUseXList(pExpr->pLeft) ); @@ -1288,18 +1355,20 @@ int nRef = pNC->nRef; testcase( pNC->ncFlags & NC_IsCheck ); testcase( pNC->ncFlags & NC_PartIdx ); testcase( pNC->ncFlags & NC_IdxExpr ); testcase( pNC->ncFlags & NC_GenCol ); + assert( pExpr->x.pSelect ); if( pNC->ncFlags & NC_SelfRef ){ notValidImpl(pParse, pNC, "subqueries", pExpr, pExpr); }else{ sqlite3WalkSelect(pWalker, pExpr->x.pSelect); } assert( pNC->nRef>=nRef ); if( nRef!=pNC->nRef ){ ExprSetProperty(pExpr, EP_VarSelect); + pExpr->x.pSelect->selFlags |= SF_Correlated; } pNC->ncFlags |= NC_Subquery; } break; } @@ -1821,10 +1890,11 @@ /* Recursively resolve names in all subqueries in the FROM clause */ if( pOuterNC ) pOuterNC->nNestedSelect++; for(i=0; ipSrc->nSrc; i++){ SrcItem *pItem = &p->pSrc->a[i]; + assert( pItem->zName!=0 || pItem->pSelect!=0 );/* Test of tag-20240424-1*/ if( pItem->pSelect && (pItem->pSelect->selFlags & SF_Resolved)==0 ){ int nRef = pOuterNC ? pOuterNC->nRef : 0; const char *zSavedContext = pParse->zAuthContext; if( pItem->zName ) pParse->zAuthContext = pItem->zName; @@ -1889,11 +1959,13 @@ sqlite3ErrorMsg(pParse, "HAVING clause on a non-aggregate query"); return WRC_Abort; } if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort; } + sNC.ncFlags |= NC_Where; if( sqlite3ResolveExprNames(&sNC, p->pWhere) ) return WRC_Abort; + sNC.ncFlags &= ~NC_Where; /* Resolve names in table-valued-function arguments */ for(i=0; ipSrc->nSrc; i++){ SrcItem *pItem = &p->pSrc->a[i]; if( pItem->fg.isTabFunc Index: src/select.c ================================================================== --- src/select.c +++ src/select.c @@ -1671,13 +1671,20 @@ struct ExprList_item *aOutEx = p->pEList->a; #ifdef SQLITE_ENABLE_STMT_SCANSTATUS int addrExplain; /* Address of OP_Explain instruction */ #endif - ExplainQueryPlan2(addrExplain, (pParse, 0, - "USE TEMP B-TREE FOR %sORDER BY", pSort->nOBSat>0?"RIGHT PART OF ":"") - ); + nKey = pOrderBy->nExpr - pSort->nOBSat; + if( pSort->nOBSat==0 || nKey==1 ){ + ExplainQueryPlan2(addrExplain, (pParse, 0, + "USE TEMP B-TREE FOR %sORDER BY", pSort->nOBSat?"LAST TERM OF ":"" + )); + }else{ + ExplainQueryPlan2(addrExplain, (pParse, 0, + "USE TEMP B-TREE FOR LAST %d TERMS OF ORDER BY", nKey + )); + } sqlite3VdbeScanStatusRange(v, addrExplain,pSort->addrPush,pSort->addrPushEnd); sqlite3VdbeScanStatusCounters(v, addrExplain, addrExplain, pSort->addrPush); assert( addrBreak<0 ); @@ -1711,11 +1718,10 @@ nColumn = 0; }else{ regRow = sqlite3GetTempRange(pParse, nColumn); } } - nKey = pOrderBy->nExpr - pSort->nOBSat; if( pSort->sortFlags & SORTFLAG_UseSorter ){ int regSortOut = ++pParse->nMem; iSortTab = pParse->nTab++; if( pSort->labelBkOut ){ addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); @@ -1951,15 +1957,11 @@ /* The "table" is actually a sub-select or a view in the FROM clause ** of the SELECT statement. Return the declaration type and origin ** data for the result-set column of the sub-select. */ if( iColpEList->nExpr -#ifdef SQLITE_ALLOW_ROWID_IN_VIEW - && iCol>=0 -#else - && ALWAYS(iCol>=0) -#endif + && (!ViewCanHaveRowid || iCol>=0) ){ /* If iCol is less than zero, then the expression requests the ** rowid of the sub-select or view. This expression is legal (see ** test case misc2.2.2) - it always evaluates to NULL. */ @@ -2320,12 +2322,11 @@ Expr *p; struct ExprList_item *a; NameContext sNC; assert( pSelect!=0 ); - testcase( (pSelect->selFlags & SF_Resolved)==0 ); - assert( (pSelect->selFlags & SF_Resolved)!=0 || IN_RENAME_OBJECT ); + assert( (pSelect->selFlags & SF_Resolved)!=0 ); assert( pTab->nCol==pSelect->pEList->nExpr || pParse->nErr>0 ); assert( aff==SQLITE_AFF_NONE || aff==SQLITE_AFF_BLOB ); if( db->mallocFailed || IN_RENAME_OBJECT ) return; while( pSelect->pPrior ) pSelect = pSelect->pPrior; a = pSelect->pEList->a; @@ -2332,21 +2333,26 @@ memset(&sNC, 0, sizeof(sNC)); sNC.pSrcList = pSelect->pSrc; for(i=0, pCol=pTab->aCol; inCol; i++, pCol++){ const char *zType; i64 n; + int m = 0; + Select *pS2 = pSelect; pTab->tabFlags |= (pCol->colFlags & COLFLAG_NOINSERT); p = a[i].pExpr; /* pCol->szEst = ... // Column size est for SELECT tables never used */ pCol->affinity = sqlite3ExprAffinity(p); + while( pCol->affinity<=SQLITE_AFF_NONE && pS2->pNext!=0 ){ + m |= sqlite3ExprDataType(pS2->pEList->a[i].pExpr); + pS2 = pS2->pNext; + pCol->affinity = sqlite3ExprAffinity(pS2->pEList->a[i].pExpr); + } if( pCol->affinity<=SQLITE_AFF_NONE ){ pCol->affinity = aff; } - if( pCol->affinity>=SQLITE_AFF_TEXT && pSelect->pNext ){ - int m = 0; - Select *pS2; - for(m=0, pS2=pSelect->pNext; pS2; pS2=pS2->pNext){ + if( pCol->affinity>=SQLITE_AFF_TEXT && (pS2->pNext || pS2!=pSelect) ){ + for(pS2=pS2->pNext; pS2; pS2=pS2->pNext){ m |= sqlite3ExprDataType(pS2->pEList->a[i].pExpr); } if( pCol->affinity==SQLITE_AFF_TEXT && (m&0x01)!=0 ){ pCol->affinity = SQLITE_AFF_BLOB; }else @@ -2372,16 +2378,16 @@ } } } } if( zType ){ - i64 m = sqlite3Strlen30(zType); + const i64 k = sqlite3Strlen30(zType); n = sqlite3Strlen30(pCol->zCnName); - pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+m+2); + pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+k+2); pCol->colFlags &= ~(COLFLAG_HASTYPE|COLFLAG_HASCOLL); if( pCol->zCnName ){ - memcpy(&pCol->zCnName[n+1], zType, m+1); + memcpy(&pCol->zCnName[n+1], zType, k+1); pCol->colFlags |= COLFLAG_HASTYPE; } } pColl = sqlite3ExprCollSeq(pParse, p); if( pColl ){ @@ -4774,11 +4780,11 @@ Expr *pValue, /* The VALUE part of the constraint */ Expr *pExpr /* Overall expression: COLUMN=VALUE or VALUE=COLUMN */ ){ int i; assert( pColumn->op==TK_COLUMN ); - assert( sqlite3ExprIsConstant(pValue) ); + assert( sqlite3ExprIsConstant(pConst->pParse, pValue) ); if( ExprHasProperty(pColumn, EP_FixedCol) ) return; if( sqlite3ExprAffinity(pValue)!=0 ) return; if( !sqlite3IsBinary(sqlite3ExprCompareCollSeq(pConst->pParse,pExpr)) ){ return; @@ -4832,14 +4838,14 @@ if( pExpr->op!=TK_EQ ) return; pRight = pExpr->pRight; pLeft = pExpr->pLeft; assert( pRight!=0 ); assert( pLeft!=0 ); - if( pRight->op==TK_COLUMN && sqlite3ExprIsConstant(pLeft) ){ + if( pRight->op==TK_COLUMN && sqlite3ExprIsConstant(pConst->pParse, pLeft) ){ constInsert(pConst,pRight,pLeft,pExpr); } - if( pLeft->op==TK_COLUMN && sqlite3ExprIsConstant(pRight) ){ + if( pLeft->op==TK_COLUMN && sqlite3ExprIsConstant(pConst->pParse, pRight) ){ constInsert(pConst,pLeft,pRight,pExpr); } } /* @@ -5055,10 +5061,22 @@ ** SELECT * FROM (SELECT a AS x, c-d AS y FROM t1 WHERE a=5 AND c-d=10) ** WHERE x=5 AND y=10; ** ** The hope is that the terms added to the inner query will make it more ** efficient. +** +** NAME AMBIGUITY +** +** This optimization is called the "WHERE-clause push-down optimization". +** +** Do not confuse this optimization with another unrelated optimization +** with a similar name: The "MySQL push-down optimization" causes WHERE +** clause terms that can be evaluated using only the index and without +** reference to the table are run first, so that if they are false, +** unnecessary table seeks are avoided. +** +** RULES ** ** Do not attempt this optimization if: ** ** (1) (** This restriction was removed on 2017-09-29. We used to ** disallow this optimization for aggregate subqueries, but now @@ -5121,18 +5139,22 @@ ** (9b) The subquery is to the right of the ON/USING clause ** ** (9c) There is a RIGHT JOIN (or FULL JOIN) in between the ON/USING ** clause and the subquery. ** -** Without this restriction, the push-down optimization might move -** the ON/USING filter expression from the left side of a RIGHT JOIN -** over to the right side, which leads to incorrect answers. See -** also restriction (6) in sqlite3ExprIsSingleTableConstraint(). +** Without this restriction, the WHERE-clause push-down optimization +** might move the ON/USING filter expression from the left side of a +** RIGHT JOIN over to the right side, which leads to incorrect answers. +** See also restriction (6) in sqlite3ExprIsSingleTableConstraint(). ** ** (10) The inner query is not the right-hand table of a RIGHT JOIN. ** ** (11) The subquery is not a VALUES clause +** +** (12) The WHERE clause is not "rowid ISNULL" or the equivalent. This +** case only comes up if SQLite is compiled using +** SQLITE_ALLOW_ROWID_IN_VIEW. ** ** Return 0 if no changes are made and non-zero if one or more WHERE clause ** terms are duplicated into the subquery. */ static int pushDownWhereTerms( @@ -5240,11 +5262,23 @@ ){ return 0; /* restriction (5) */ } #endif - if( sqlite3ExprIsSingleTableConstraint(pWhere, pSrcList, iSrc) ){ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + if( ViewCanHaveRowid && (pWhere->op==TK_ISNULL || pWhere->op==TK_NOTNULL) ){ + Expr *pLeft = pWhere->pLeft; + if( ALWAYS(pLeft) + && pLeft->op==TK_COLUMN + && pLeft->iColumn < 0 + ){ + return 0; /* Restriction (12) */ + } + } +#endif + + if( sqlite3ExprIsSingleTableConstraint(pWhere, pSrcList, iSrc, 1) ){ nChng++; pSubq->selFlags |= SF_PushDown; while( pSubq ){ SubstContext x; pNew = sqlite3ExprDup(pParse->db, pWhere, 0); @@ -5867,16 +5901,18 @@ pTab->zName = sqlite3MPrintf(pParse->db, "%!S", pFrom); } while( pSel->pPrior ){ pSel = pSel->pPrior; } sqlite3ColumnsFromExprList(pParse, pSel->pEList,&pTab->nCol,&pTab->aCol); pTab->iPKey = -1; + pTab->eTabType = TABTYP_VIEW; pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); #ifndef SQLITE_ALLOW_ROWID_IN_VIEW /* The usual case - do not allow ROWID on a subquery */ pTab->tabFlags |= TF_Ephemeral | TF_NoVisibleRowid; #else - pTab->tabFlags |= TF_Ephemeral; /* Legacy compatibility mode */ + /* Legacy compatibility mode */ + pTab->tabFlags |= TF_Ephemeral | sqlite3Config.mNoVisibleRowid; #endif return pParse->nErr ? SQLITE_ERROR : SQLITE_OK; } @@ -6140,11 +6176,11 @@ if( pFrom->fg.isNestedFrom ){ assert( pFrom->pSelect!=0 ); pNestedFrom = pFrom->pSelect->pEList; assert( pNestedFrom!=0 ); assert( pNestedFrom->nExpr==pTab->nCol ); - assert( VisibleRowid(pTab)==0 ); + assert( VisibleRowid(pTab)==0 || ViewCanHaveRowid ); }else{ if( zTName && sqlite3StrICmp(zTName, zTabName)!=0 ){ continue; } pNestedFrom = 0; @@ -6172,11 +6208,12 @@ } }else{ pUsing = 0; } - nAdd = pTab->nCol + (VisibleRowid(pTab) && (selFlags&SF_NestedFrom)); + nAdd = pTab->nCol; + if( VisibleRowid(pTab) && (selFlags & SF_NestedFrom)!=0 ) nAdd++; for(j=0; jnCol ){ @@ -6254,11 +6291,12 @@ break; /* OOM */ } pX = &pNew->a[pNew->nExpr-1]; assert( pX->zEName==0 ); if( (selFlags & SF_NestedFrom)!=0 && !IN_RENAME_OBJECT ){ - if( pNestedFrom ){ + if( pNestedFrom && (!ViewCanHaveRowid || jnExpr) ){ + assert( jnExpr ); pX->zEName = sqlite3DbStrDup(db, pNestedFrom->a[j].zEName); testcase( pX->zEName==0 ); }else{ pX->zEName = sqlite3MPrintf(db, "%s.%s.%s", zSchemaName, zTabName, zName); @@ -6371,12 +6409,11 @@ SrcItem *pFrom; if( p->selFlags & SF_HasTypeInfo ) return; p->selFlags |= SF_HasTypeInfo; pParse = pWalker->pParse; - testcase( (p->selFlags & SF_Resolved)==0 ); - assert( (p->selFlags & SF_Resolved) || IN_RENAME_OBJECT ); + assert( (p->selFlags & SF_Resolved) ); pTabList = p->pSrc; for(i=0, pFrom=pTabList->a; inSrc; i++, pFrom++){ Table *pTab = pFrom->pTab; assert( pTab!=0 ); if( (pTab->tabFlags & TF_Ephemeral)!=0 ){ @@ -7634,11 +7671,11 @@ #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) /* Generate code for all sub-queries in the FROM clause */ pSub = pItem->pSelect; - if( pSub==0 ) continue; + if( pSub==0 || pItem->addrFillSub!=0 ) continue; /* The code for a subquery should only be generated once. */ assert( pItem->addrFillSub==0 ); /* Increment Parse.nHeight by the height of the largest expression @@ -7665,11 +7702,11 @@ sqlite3TreeViewSelect(0, p, 0); } #endif assert( pItem->pSelect && (pItem->pSelect->selFlags & SF_PushDown)!=0 ); }else{ - TREETRACE(0x4000,pParse,p,("Push-down not possible\n")); + TREETRACE(0x4000,pParse,p,("WHERE-lcause push-down not possible\n")); } /* Convert unused result columns of the subquery into simple NULL ** expressions, to avoid unneeded searching and computation. */ Index: src/shell.c.in ================================================================== --- src/shell.c.in +++ src/shell.c.in @@ -7938,30 +7938,37 @@ int iId; /* ID that triggers a simulated fault. -1 means "any" */ int iErr; /* The error code to return on a fault */ int iCnt; /* Trigger the fault only if iCnt is already zero */ int iInterval; /* Reset iCnt to this value after each fault */ int eVerbose; /* When to print output */ -} faultsim_state = {-1, 0, 0, 0, 0}; + int nHit; /* Number of hits seen so far */ + int nRepeat; /* Turn off after this many hits. 0 for never */ + int nSkip; /* Skip this many before first fault */ +} faultsim_state = {-1, 0, 0, 0, 0, 0, 0, 0}; /* ** This is the fault-sim callback */ static int faultsim_callback(int iArg){ if( faultsim_state.iId>0 && faultsim_state.iId!=iArg ){ return SQLITE_OK; } - if( faultsim_state.iCnt>0 ){ - faultsim_state.iCnt--; + if( faultsim_state.iCnt ){ + if( faultsim_state.iCnt>0 ) faultsim_state.iCnt--; if( faultsim_state.eVerbose>=2 ){ oputf("FAULT-SIM id=%d no-fault (cnt=%d)\n", iArg, faultsim_state.iCnt); } return SQLITE_OK; } if( faultsim_state.eVerbose>=1 ){ oputf("FAULT-SIM id=%d returns %d\n", iArg, faultsim_state.iErr); } faultsim_state.iCnt = faultsim_state.iInterval; + faultsim_state.nHit++; + if( faultsim_state.nRepeat>0 && faultsim_state.nRepeat<=faultsim_state.nHit ){ + faultsim_state.iCnt = -1; + } return faultsim_state.iErr; } /* ** If an input line begins with "." then invoke this routine to @@ -8776,20 +8783,19 @@ }else #ifndef SQLITE_SHELL_FIDDLE if( c=='i' && cli_strncmp(azArg[0], "import", n)==0 ){ char *zTable = 0; /* Insert data into this table */ - char *zSchema = 0; /* within this schema (may default to "main") */ + char *zSchema = 0; /* Schema of zTable */ char *zFile = 0; /* Name of file to extra content from */ sqlite3_stmt *pStmt = NULL; /* A statement */ int nCol; /* Number of columns in the table */ - int nByte; /* Number of bytes in an SQL string */ + i64 nByte; /* Number of bytes in an SQL string */ int i, j; /* Loop counters */ int needCommit; /* True to COMMIT or ROLLBACK at end */ int nSep; /* Number of bytes in p->colSeparator[] */ - char *zSql; /* An SQL statement */ - char *zFullTabName; /* Table name with schema if applicable */ + char *zSql = 0; /* An SQL statement */ ImportCtx sCtx; /* Reader context */ char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */ int eVerbose = 0; /* Larger for more console output */ int nSkip = 0; /* Initial lines to skip */ int useOutputMode = 1; /* Use output mode to determine separators */ @@ -8919,28 +8925,18 @@ } /* Below, resources must be freed before exit. */ while( (nSkip--)>0 ){ while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){} } - if( zSchema!=0 ){ - zFullTabName = sqlite3_mprintf("\"%w\".\"%w\"", zSchema, zTable); - }else{ - zFullTabName = sqlite3_mprintf("\"%w\"", zTable); - } - zSql = sqlite3_mprintf("SELECT * FROM %s", zFullTabName); - if( zSql==0 || zFullTabName==0 ){ - import_cleanup(&sCtx); - shell_out_of_memory(); - } - nByte = strlen30(zSql); - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ - if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){ + if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0) ){ + /* Table does not exist. Create it. */ sqlite3 *dbCols = 0; char *zRenames = 0; char *zColDefs; - zCreate = sqlite3_mprintf("CREATE TABLE %s", zFullTabName); + zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"", + zSchema ? zSchema : "main", zTable); while( xRead(&sCtx) ){ zAutoColumn(sCtx.z, &dbCols, 0); if( sCtx.cTerm!=sCtx.cColSep ) break; } zColDefs = zAutoColumn(0, &dbCols, &zRenames); @@ -8951,65 +8947,93 @@ sqlite3_free(zRenames); } assert(dbCols==0); if( zColDefs==0 ){ eputf("%s: empty file\n", sCtx.zFile); - import_fail: - sqlite3_free(zCreate); - sqlite3_free(zSql); - sqlite3_free(zFullTabName); import_cleanup(&sCtx); rc = 1; goto meta_command_exit; } zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs); + if( zCreate==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } if( eVerbose>=1 ){ oputf("%s\n", zCreate); } rc = sqlite3_exec(p->db, zCreate, 0, 0, 0); + sqlite3_free(zCreate); + zCreate = 0; if( rc ){ eputf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db)); - goto import_fail; + import_cleanup(&sCtx); + rc = 1; + goto meta_command_exit; } - sqlite3_free(zCreate); - zCreate = 0; - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); } + zSql = sqlite3_mprintf("SELECT count(*) FROM pragma_table_info(%Q,%Q);", + zTable, zSchema); + if( zSql==0 ){ + import_cleanup(&sCtx); + shell_out_of_memory(); + } + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + zSql = 0; if( rc ){ if (pStmt) sqlite3_finalize(pStmt); eputf("Error: %s\n", sqlite3_errmsg(p->db)); - goto import_fail; + import_cleanup(&sCtx); + rc = 1; + goto meta_command_exit; } - sqlite3_free(zSql); - nCol = sqlite3_column_count(pStmt); + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + nCol = sqlite3_column_int(pStmt, 0); + }else{ + nCol = 0; + } sqlite3_finalize(pStmt); pStmt = 0; if( nCol==0 ) return 0; /* no columns, no error */ - zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 ); + + nByte = 64 /* space for "INSERT INTO", "VALUES(", ")\0" */ + + (zSchema ? strlen(zSchema)*2 + 2: 0) /* Quoted schema name */ + + strlen(zTable)*2 + 2 /* Quoted table name */ + + nCol*2; /* Space for ",?" for each column */ + zSql = sqlite3_malloc64( nByte ); if( zSql==0 ){ import_cleanup(&sCtx); shell_out_of_memory(); } - sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName); + if( zSchema ){ + sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\".\"%w\" VALUES(?", + zSchema, zTable); + }else{ + sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\" VALUES(?", zTable); + } j = strlen30(zSql); for(i=1; i=2 ){ oputf("Insert using: %s\n", zSql); } rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + zSql = 0; if( rc ){ eputf("Error: %s\n", sqlite3_errmsg(p->db)); if (pStmt) sqlite3_finalize(pStmt); - goto import_fail; + import_cleanup(&sCtx); + rc = 1; + goto meta_command_exit; } - sqlite3_free(zSql); - sqlite3_free(zFullTabName); needCommit = sqlite3_get_autocommit(p->db); if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0); do{ int startLine = sCtx.nLine; for(i=0; i0 ) faultsim_state.eVerbose--; }else if( cli_strcmp(z,"-id")==0 && kk+1SQLITE_CONFIG_ROWID_IN_VIEW +**
    The SQLITE_CONFIG_ROWID_IN_VIEW option enables or disables the ability +** for VIEWs to have a ROWID. The capability can only be enabled if SQLite is +** compiled with -DSQLITE_ALLOW_ROWID_IN_VIEW, in which case the capability +** defaults to on. This configuration option queries the current setting or +** changes the setting to off or on. The argument is a pointer to an integer. +** If that integer initially holds a value of 1, then the ability for VIEWs to +** have ROWIDs is activated. If the integer initially holds zero, then the +** ability is deactivated. Any other initial value for the integer leaves the +** setting unchanged. After changes, if any, the integer is written with +** a 1 or 0, if the ability for VIEWs to have ROWIDs is on or off. If SQLite +** is compiled without -DSQLITE_ALLOW_ROWID_IN_VIEW (which is the usual and +** recommended case) then the integer is always filled with zero, regardless +** if its initial value. ** */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ #define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ #define SQLITE_CONFIG_SERIALIZED 3 /* nil */ @@ -2172,10 +2188,11 @@ #define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */ #define SQLITE_CONFIG_STMTJRNL_SPILL 26 /* int nByte */ #define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */ #define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */ #define SQLITE_CONFIG_MEMDB_MAXSIZE 29 /* sqlite3_int64 */ +#define SQLITE_CONFIG_ROWID_IN_VIEW 30 /* int* */ /* ** CAPI3REF: Database Connection Configuration Options ** ** These constants are the available integer configuration options that @@ -3286,12 +3303,12 @@ #define SQLITE_SAVEPOINT 32 /* Operation Savepoint Name */ #define SQLITE_COPY 0 /* No longer used */ #define SQLITE_RECURSIVE 33 /* NULL NULL */ /* -** CAPI3REF: Tracing And Profiling Functions -** METHOD: sqlite3 +** CAPI3REF: Deprecated Tracing And Profiling Functions +** DEPRECATED ** ** These routines are deprecated. Use the [sqlite3_trace_v2()] interface ** instead of the routines described here. ** ** These routines register callback functions that can be used for @@ -6867,10 +6884,16 @@ ** is not invoked when conflicting rows are deleted because of an ** [ON CONFLICT | ON CONFLICT REPLACE] clause. ^Nor is the update hook ** invoked when rows are deleted using the [truncate optimization]. ** The exceptions defined in this paragraph might change in a future ** release of SQLite. +** +** Whether the update hook is invoked before or after the +** corresponding change is currently unspecified and may differ +** depending on the type of change. Do not rely on the order of the +** hook call with regards to the final result of the operation which +** triggers the hook. ** ** The update hook implementation must not do anything that will modify ** the database connection that invoked the update hook. Any actions ** to modify the database connection must be deferred until after the ** completion of the [sqlite3_step()] call that triggered the update hook. @@ -8338,11 +8361,11 @@ ** by enclosing in double-quotes) so as not to confuse the parser. ** ** The sqlite3_keyword_count() interface returns the number of distinct ** keywords understood by SQLite. ** -** The sqlite3_keyword_name(N,Z,L) interface finds the N-th keyword and +** The sqlite3_keyword_name(N,Z,L) interface finds the 0-based N-th keyword and ** makes *Z point to that keyword expressed as UTF8 and writes the number ** of bytes in the keyword into *L. The string that *Z points to is not ** zero-terminated. The sqlite3_keyword_name(N,Z,L) routine returns ** SQLITE_OK if N is within bounds and SQLITE_ERROR if not. If either Z ** or L are NULL or invalid pointers then calls to @@ -9917,27 +9940,48 @@ ** "aOrderBy" field are adjacent.)^ This mode is used when the query planner ** is doing a GROUP BY. **
  • ** ^(If the sqlite3_vtab_distinct() interface returns 2, that means ** that the query planner does not need the rows returned in any particular -** order, as long as rows with the same values in all "aOrderBy" columns -** are adjacent.)^ ^(Furthermore, only a single row for each particular -** combination of values in the columns identified by the "aOrderBy" field -** needs to be returned.)^ ^It is always ok for two or more rows with the same -** values in all "aOrderBy" columns to be returned, as long as all such rows -** are adjacent. ^The virtual table may, if it chooses, omit extra rows -** that have the same value for all columns identified by "aOrderBy". -** ^However omitting the extra rows is optional. +** order, as long as rows with the same values in all columns identified +** by "aOrderBy" are adjacent.)^ ^(Furthermore, when two or more rows +** contain the same values for all columns identified by "colUsed", all but +** one such row may optionally be omitted from the result.)^ +** The virtual table is not required to omit rows that are duplicates +** over the "colUsed" columns, but if the virtual table can do that without +** too much extra effort, it could potentially help the query to run faster. ** This mode is used for a DISTINCT query. **

  • -** ^(If the sqlite3_vtab_distinct() interface returns 3, that means -** that the query planner needs only distinct rows but it does need the -** rows to be sorted.)^ ^The virtual table implementation is free to omit -** rows that are identical in all aOrderBy columns, if it wants to, but -** it is not required to omit any rows. This mode is used for queries +** ^(If the sqlite3_vtab_distinct() interface returns 3, that means the +** virtual table must return rows in the order defined by "aOrderBy" as +** if the sqlite3_vtab_distinct() interface had returned 0. However if +** two or more rows in the result have the same values for all columns +** identified by "colUsed", then all but one such row may optionally be +** omitted.)^ Like when the return value is 2, the virtual table +** is not required to omit rows that are duplicates over the "colUsed" +** columns, but if the virtual table can do that without +** too much extra effort, it could potentially help the query to run faster. +** This mode is used for queries ** that have both DISTINCT and ORDER BY clauses. ** +** +**

    The following table summarizes the conditions under which the +** virtual table is allowed to set the "orderByConsumed" flag based on +** the value returned by sqlite3_vtab_distinct(). This table is a +** restatement of the previous four paragraphs: +** +** +** +**
    sqlite3_vtab_distinct() return value +** Rows are returned in aOrderBy order +** Rows with the same value in all aOrderBy columns are adjacent +** Duplicates over all colUsed columns may be omitted +**
    0yesyesno +**
    1noyesno +**
    2noyesyes +**
    3yesyesyes +**
    ** ** ^For the purposes of comparing virtual table output values to see if the ** values are same value for sorting purposes, two NULL values are considered ** to be the same. In other words, the comparison operator is "IS" ** (or "IS NOT DISTINCT FROM") and not "==". Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -1123,10 +1123,11 @@ ** 0x00004000 Push-down optimization ** 0x00008000 After all FROM-clause analysis ** 0x00010000 Beginning of DELETE/INSERT/UPDATE processing ** 0x00020000 Transform DISTINCT into GROUP BY ** 0x00040000 SELECT tree dump after all code has been generated +** 0x00080000 NOT NULL strength reduction */ /* ** Macros for "wheretrace" */ @@ -1153,11 +1154,11 @@ ** 0x00000008 WhereLoop inserts ** ** 0x00000010 Display sqlite3_index_info xBestIndex calls ** 0x00000020 Range an equality scan metrics ** 0x00000040 IN operator decisions -** 0x00000080 WhereLoop cost adjustements +** 0x00000080 WhereLoop cost adjustments ** 0x00000100 ** 0x00000200 Covering index decisions ** 0x00000400 OR optimization ** 0x00000800 Index scanner ** 0x00001000 More details associated with code generation @@ -1903,11 +1904,11 @@ #define SQLITE_OmitNoopJoin 0x00000100 /* Omit unused tables in joins */ #define SQLITE_CountOfView 0x00000200 /* The count-of-view optimization */ #define SQLITE_CursorHints 0x00000400 /* Add OP_CursorHint opcodes */ #define SQLITE_Stat4 0x00000800 /* Use STAT4 data */ /* TH3 expects this value ^^^^^^^^^^ to be 0x0000800. Don't change it */ -#define SQLITE_PushDown 0x00001000 /* The push-down optimization */ +#define SQLITE_PushDown 0x00001000 /* WHERE-clause push-down opt */ #define SQLITE_SimplifyJoin 0x00002000 /* Convert LEFT JOIN to JOIN */ #define SQLITE_SkipScan 0x00004000 /* Skip-scans */ #define SQLITE_PropagateConst 0x00008000 /* The constant propagation opt */ #define SQLITE_MinMaxOpt 0x00010000 /* The min/max optimization */ #define SQLITE_SeekScan 0x00020000 /* The OP_SeekScan optimization */ @@ -2531,10 +2532,19 @@ /* Does the table have a rowid */ #define HasRowid(X) (((X)->tabFlags & TF_WithoutRowid)==0) #define VisibleRowid(X) (((X)->tabFlags & TF_NoVisibleRowid)==0) + +/* Macro is true if the SQLITE_ALLOW_ROWID_IN_VIEW (mis-)feature is +** available. By default, this macro is false +*/ +#ifndef SQLITE_ALLOW_ROWID_IN_VIEW +# define ViewCanHaveRowid 0 +#else +# define ViewCanHaveRowid (sqlite3Config.mNoVisibleRowid==0) +#endif /* ** Each foreign key constraint is an instance of the following structure. ** ** A foreign key is associated with two tables. The "from" table is @@ -3267,14 +3277,16 @@ ** In the colUsed field, the high-order bit (bit 63) is set if the table ** contains more than 63 columns and the 64-th or later column is used. ** ** Union member validity: ** -** u1.zIndexedBy fg.isIndexedBy && !fg.isTabFunc -** u1.pFuncArg fg.isTabFunc && !fg.isIndexedBy -** u2.pIBIndex fg.isIndexedBy && !fg.isCte -** u2.pCteUse fg.isCte && !fg.isIndexedBy +** u1.zIndexedBy fg.isIndexedBy && !fg.isTabFunc +** u1.pFuncArg fg.isTabFunc && !fg.isIndexedBy +** u1.nRow !fg.isTabFunc && !fg.isIndexedBy +** +** u2.pIBIndex fg.isIndexedBy && !fg.isCte +** u2.pCteUse fg.isCte && !fg.isIndexedBy */ struct SrcItem { Schema *pSchema; /* Schema to which this item is fixed */ char *zDatabase; /* Name of database holding this table */ char *zName; /* Name of the table */ @@ -3298,10 +3310,11 @@ unsigned notCte :1; /* This item may not match a CTE */ unsigned isUsing :1; /* u3.pUsing is valid */ unsigned isOn :1; /* u3.pOn was once valid and non-NULL */ unsigned isSynthUsing :1; /* u3.pUsing is synthesized from NATURAL */ unsigned isNestedFrom :1; /* pSelect is a SF_NestedFrom subquery */ + unsigned rowidUsed :1; /* The ROWID of this table is referenced */ } fg; int iCursor; /* The VDBE cursor number used to access this table */ union { Expr *pOn; /* fg.isUsing==0 => The ON clause of a join */ IdList *pUsing; /* fg.isUsing==1 => The USING clause of a join */ @@ -3308,10 +3321,11 @@ } u3; Bitmask colUsed; /* Bit N set if column N used. Details above for N>62 */ union { char *zIndexedBy; /* Identifier from "INDEXED BY " clause */ ExprList *pFuncArg; /* Arguments to table-valued-function */ + u32 nRow; /* Number of rows in a VALUES clause */ } u1; union { Index *pIBIndex; /* Index structure corresponding to u1.zIndexedBy */ CteUse *pCteUse; /* CTE Usage info when fg.isCte is true */ } u2; @@ -3372,11 +3386,11 @@ #define WHERE_WANT_DISTINCT 0x0100 /* All output needs to be distinct */ #define WHERE_SORTBYGROUP 0x0200 /* Support sqlite3WhereIsSorted() */ #define WHERE_AGG_DISTINCT 0x0400 /* Query is "SELECT agg(DISTINCT ...)" */ #define WHERE_ORDERBY_LIMIT 0x0800 /* ORDERBY+LIMIT on the inner loop */ #define WHERE_RIGHT_JOIN 0x1000 /* Processing a RIGHT JOIN */ - /* 0x2000 not currently used */ +#define WHERE_KEEP_ALL_JOINS 0x2000 /* Do not do the omit-noop-join opt */ #define WHERE_USE_LIMIT 0x4000 /* Use the LIMIT in cost estimates */ /* 0x8000 not currently used */ /* Allowed return values from sqlite3WhereIsDistinct() */ @@ -3451,10 +3465,11 @@ #define NC_HasWin 0x008000 /* One or more window functions seen */ #define NC_IsDDL 0x010000 /* Resolving names in a CREATE statement */ #define NC_InAggFunc 0x020000 /* True if analyzing arguments to an agg func */ #define NC_FromDDL 0x040000 /* SQL text comes from sqlite_schema */ #define NC_NoSelect 0x080000 /* Do not descend into sub-selects */ +#define NC_Where 0x100000 /* Processing WHERE clause of a SELECT */ #define NC_OrderAgg 0x8000000 /* Has an aggregate other than count/min/max */ /* ** An instance of the following object describes a single ON CONFLICT ** clause in an upsert. @@ -3474,10 +3489,11 @@ Expr *pUpsertTargetWhere; /* WHERE clause for partial index targets */ ExprList *pUpsertSet; /* The SET clause from an ON CONFLICT UPDATE */ Expr *pUpsertWhere; /* WHERE clause for the ON CONFLICT UPDATE */ Upsert *pNextUpsert; /* Next ON CONFLICT clause in the list */ u8 isDoUpdate; /* True for DO UPDATE. False for DO NOTHING */ + u8 isDup; /* True if 2nd or later with same pUpsertIdx */ /* Above this point is the parse tree for the ON CONFLICT clauses. ** The next group of fields stores intermediate data. */ void *pToFree; /* Free memory when deleting the Upsert object */ /* All fields above are owned by the Upsert object and must be freed ** when the Upsert is destroyed. The fields below are used to transfer @@ -3563,15 +3579,16 @@ #define SF_WhereBegin 0x0080000 /* Really a WhereBegin() call. Debug Only */ #define SF_WinRewrite 0x0100000 /* Window function rewrite accomplished */ #define SF_View 0x0200000 /* SELECT statement is a view */ #define SF_NoopOrderBy 0x0400000 /* ORDER BY is ignored for this query */ #define SF_UFSrcCheck 0x0800000 /* Check pSrc as required by UPDATE...FROM */ -#define SF_PushDown 0x1000000 /* SELECT has be modified by push-down opt */ +#define SF_PushDown 0x1000000 /* Modified by WHERE-clause push-down opt */ #define SF_MultiPart 0x2000000 /* Has multiple incompatible PARTITIONs */ #define SF_CopyCte 0x4000000 /* SELECT statement is a copy of a CTE */ #define SF_OrderByReqd 0x8000000 /* The ORDER BY clause may not be omitted */ #define SF_UpdateFrom 0x10000000 /* Query originates with UPDATE FROM */ +#define SF_Correlated 0x20000000 /* True if references the outer context */ /* True if S exists and has SF_NestedFrom */ #define IsNestedFrom(S) ((S)!=0 && ((S)->selFlags&SF_NestedFrom)!=0) /* @@ -3807,10 +3824,11 @@ u8 hasCompound; /* Need to invoke convertCompoundSelectToSubquery() */ u8 okConstFactor; /* OK to factor out constants */ u8 disableLookaside; /* Number of times lookaside has been disabled */ u8 prepFlags; /* SQLITE_PREPARE_* flags */ u8 withinRJSubrtn; /* Nesting level for RIGHT JOIN body subroutines */ + u8 bHasWith; /* True if statement contains WITH */ #if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) u8 earlyCleanup; /* OOM inside sqlite3ParserAddCleanup() */ #endif #ifdef SQLITE_DEBUG u8 ifNotExists; /* Might be true if IF NOT EXISTS. Assert()s only */ @@ -4245,10 +4263,15 @@ sqlite3_int64 mxMemdbSize; /* Default max memdb size */ #endif #ifndef SQLITE_UNTESTABLE int (*xTestCallback)(int); /* Invoked by sqlite3FaultSim() */ #endif +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + u32 mNoVisibleRowid; /* TF_NoVisibleRowid if the ROWID_IN_VIEW + ** feature is disabled. 0 if rowids can + ** occur in views. */ +#endif int bLocaltimeFault; /* True to fail localtime() calls */ int (*xAltLocaltime)(const void*,void*); /* Alternative localtime() routine */ int iOnceResetThreshold; /* When to reset OP_Once counters */ u32 szSorterRef; /* Min size in bytes to use sorter-refs */ unsigned int iPrngSeed; /* Alternative fixed seed for the PRNG */ @@ -4481,10 +4504,13 @@ int regEndRowid; u8 bExprArgs; /* Defer evaluation of window function arguments ** due to the SQLITE_SUBTYPE flag */ }; +Select *sqlite3MultiValues(Parse *pParse, Select *pLeft, ExprList *pRow); +void sqlite3MultiValuesEnd(Parse *pParse, Select *pVal); + #ifndef SQLITE_OMIT_WINDOWFUNC void sqlite3WindowDelete(sqlite3*, Window*); void sqlite3WindowUnlinkFromSelect(Window*); void sqlite3WindowListDelete(sqlite3 *db, Window *p); Window *sqlite3WindowAlloc(Parse*, int, int, Expr*, int , Expr*, u8); @@ -4700,14 +4726,17 @@ #ifndef SQLITE_OMIT_FLOATING_POINT # define EXP754 (((u64)0x7ff)<<52) # define MAN754 ((((u64)1)<<52)-1) # define IsNaN(X) (((X)&EXP754)==EXP754 && ((X)&MAN754)!=0) +# define IsOvfl(X) (((X)&EXP754)==EXP754) int sqlite3IsNaN(double); + int sqlite3IsOverflow(double); #else -# define IsNaN(X) 0 -# define sqlite3IsNaN(X) 0 +# define IsNaN(X) 0 +# define sqlite3IsNaN(X) 0 +# define sqlite3IsOVerflow(X) 0 #endif /* ** An instance of the following structure holds information about SQL ** functions arguments that are the parameters to the printf() function. @@ -4826,11 +4855,11 @@ void sqlite3ExprOrderByAggregateError(Parse*,Expr*); void sqlite3ExprFunctionUsable(Parse*,const Expr*,const FuncDef*); void sqlite3ExprAssignVarNumber(Parse*, Expr*, u32); void sqlite3ExprDelete(sqlite3*, Expr*); void sqlite3ExprDeleteGeneric(sqlite3*,void*); -void sqlite3ExprDeferredDelete(Parse*, Expr*); +int sqlite3ExprDeferredDelete(Parse*, Expr*); void sqlite3ExprUnmapAndDelete(Parse*, Expr*); ExprList *sqlite3ExprListAppend(Parse*,ExprList*,Expr*); ExprList *sqlite3ExprListAppendVector(Parse*,ExprList*,IdList*,Expr*); Select *sqlite3ExprListToValues(Parse*, int, ExprList*); void sqlite3ExprListSetSortOrder(ExprList*,int,int); @@ -5049,16 +5078,14 @@ void sqlite3CloseSavepoints(sqlite3 *); void sqlite3LeaveMutexAndCloseZombie(sqlite3*); u32 sqlite3IsTrueOrFalse(const char*); int sqlite3ExprIdToTrueFalse(Expr*); int sqlite3ExprTruthValue(const Expr*); -int sqlite3ExprIsConstant(Expr*); -int sqlite3ExprIsConstantNotJoin(Expr*); +int sqlite3ExprIsConstant(Parse*,Expr*); int sqlite3ExprIsConstantOrFunction(Expr*, u8); int sqlite3ExprIsConstantOrGroupBy(Parse*, Expr*, ExprList*); -int sqlite3ExprIsTableConstant(Expr*,int); -int sqlite3ExprIsSingleTableConstraint(Expr*,const SrcList*,int); +int sqlite3ExprIsSingleTableConstraint(Expr*,const SrcList*,int,int); #ifdef SQLITE_ENABLE_CURSOR_HINTS int sqlite3ExprContainsSubquery(Expr*); #endif int sqlite3ExprIsInteger(const Expr*, int*); int sqlite3ExprCanBeNull(const Expr*); @@ -5239,11 +5266,13 @@ int sqlite3DecOrHexToI64(const char*, i64*); void sqlite3ErrorWithMsg(sqlite3*, int, const char*,...); void sqlite3Error(sqlite3*,int); void sqlite3ErrorClear(sqlite3*); void sqlite3SystemError(sqlite3*,int); +#if !defined(SQLITE_OMIT_BLOB_LITERAL) void *sqlite3HexToBlob(sqlite3*, const char *z, int n); +#endif u8 sqlite3HexToInt(int h); int sqlite3TwoPartName(Parse *, Token *, Token *, Token **); #if defined(SQLITE_NEED_ERR_NAME) const char *sqlite3ErrName(int); @@ -5550,11 +5579,11 @@ #endif #ifndef SQLITE_OMIT_UPSERT Upsert *sqlite3UpsertNew(sqlite3*,ExprList*,Expr*,ExprList*,Expr*,Upsert*); void sqlite3UpsertDelete(sqlite3*,Upsert*); Upsert *sqlite3UpsertDup(sqlite3*,Upsert*); - int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*); + int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*,Upsert*); void sqlite3UpsertDoUpdate(Parse*,Upsert*,Table*,Index*,int); Upsert *sqlite3UpsertOfIndex(Upsert*,Index*); int sqlite3UpsertNextIsIPK(Upsert*); #else #define sqlite3UpsertNew(u,v,w,x,y,z) ((Upsert*)0) Index: src/test_bestindex.c ================================================================== --- src/test_bestindex.c +++ src/test_bestindex.c @@ -213,10 +213,13 @@ if( rc!=TCL_OK ){ *pzErr = sqlite3_mprintf("%s", Tcl_GetStringResult(interp)); rc = SQLITE_ERROR; }else{ rc = sqlite3_declare_vtab(db, Tcl_GetStringResult(interp)); + if( rc!=SQLITE_OK ){ + *pzErr = sqlite3_mprintf("declare_vtab: %s", sqlite3_errmsg(db)); + } } if( rc!=SQLITE_OK ){ sqlite3_free(pTab); pTab = 0; @@ -224,11 +227,11 @@ }else{ rc = SQLITE_NOMEM; } sqlite3_free(zCmd); - *ppVtab = &pTab->base; + *ppVtab = pTab ? &pTab->base : 0; return rc; } /* The xDisconnect and xDestroy methods are also the same */ static int tclDisconnect(sqlite3_vtab *pVtab){ Index: src/test_config.c ================================================================== --- src/test_config.c +++ src/test_config.c @@ -56,10 +56,18 @@ #ifdef SQLITE_32BIT_ROWID Tcl_SetVar2(interp, "sqlite_options", "rowid32", "1", TCL_GLOBAL_ONLY); #else Tcl_SetVar2(interp, "sqlite_options", "rowid32", "0", TCL_GLOBAL_ONLY); #endif + +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + Tcl_SetVar2( + interp, "sqlite_options", "allow_rowid_in_view", "1", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2( + interp, "sqlite_options", "allow_rowid_in_view", "0", TCL_GLOBAL_ONLY); +#endif #ifdef SQLITE_CASE_SENSITIVE_LIKE Tcl_SetVar2(interp, "sqlite_options","casesensitivelike","1",TCL_GLOBAL_ONLY); #else Tcl_SetVar2(interp, "sqlite_options","casesensitivelike","0",TCL_GLOBAL_ONLY); Index: src/treeview.c ================================================================== --- src/treeview.c +++ src/treeview.c @@ -192,12 +192,14 @@ char zLine[1000]; sqlite3StrAccumInit(&x, 0, zLine, sizeof(zLine), 0); x.printfFlags |= SQLITE_PRINTF_INTERNAL; sqlite3_str_appendf(&x, "{%d:*} %!S", pItem->iCursor, pItem); if( pItem->pTab ){ - sqlite3_str_appendf(&x, " tab=%Q nCol=%d ptr=%p used=%llx", - pItem->pTab->zName, pItem->pTab->nCol, pItem->pTab, pItem->colUsed); + sqlite3_str_appendf(&x, " tab=%Q nCol=%d ptr=%p used=%llx%s", + pItem->pTab->zName, pItem->pTab->nCol, pItem->pTab, + pItem->colUsed, + pItem->fg.rowidUsed ? "+rowid" : ""); } if( (pItem->fg.jointype & (JT_LEFT|JT_RIGHT))==(JT_LEFT|JT_RIGHT) ){ sqlite3_str_appendf(&x, " FULL-OUTER-JOIN"); }else if( pItem->fg.jointype & JT_LEFT ){ sqlite3_str_appendf(&x, " LEFT-JOIN"); @@ -233,16 +235,18 @@ if( pItem->fg.isUsing ) n++; if( pItem->fg.isUsing ){ sqlite3TreeViewIdList(pView, pItem->u3.pUsing, (--n)>0, "USING"); } if( pItem->pSelect ){ + sqlite3TreeViewPush(&pView, i+1nSrc); if( pItem->pTab ){ Table *pTab = pItem->pTab; sqlite3TreeViewColumnList(pView, pTab->aCol, pTab->nCol, 1); } assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem->pSelect) ); sqlite3TreeViewSelect(pView, pItem->pSelect, (--n)>0); + sqlite3TreeViewPop(&pView); } if( pItem->fg.isTabFunc ){ sqlite3TreeViewExprList(pView, pItem->u1.pFuncArg, 0, "func-args:"); } sqlite3TreeViewPop(&pView); @@ -342,11 +346,11 @@ } if( p->pLimit ){ sqlite3TreeViewItem(pView, "LIMIT", (n--)>0); sqlite3TreeViewExpr(pView, p->pLimit->pLeft, p->pLimit->pRight!=0); if( p->pLimit->pRight ){ - sqlite3TreeViewItem(pView, "OFFSET", (n--)>0); + sqlite3TreeViewItem(pView, "OFFSET", 0); sqlite3TreeViewExpr(pView, p->pLimit->pRight, 0); sqlite3TreeViewPop(&pView); } sqlite3TreeViewPop(&pView); } Index: src/trigger.c ================================================================== --- src/trigger.c +++ src/trigger.c @@ -948,10 +948,76 @@ } } } return pNew; } + +/* If the Expr node is a subquery or an EXISTS operator or an IN operator that +** uses a subquery, and if the subquery is SF_Correlated, then mark the +** expression as EP_VarSelect. +*/ +static int sqlite3ReturningSubqueryVarSelect(Walker *NotUsed, Expr *pExpr){ + UNUSED_PARAMETER(NotUsed); + if( ExprUseXSelect(pExpr) + && (pExpr->x.pSelect->selFlags & SF_Correlated)!=0 + ){ + testcase( ExprHasProperty(pExpr, EP_VarSelect) ); + ExprSetProperty(pExpr, EP_VarSelect); + } + return WRC_Continue; +} + + +/* +** If the SELECT references the table pWalker->u.pTab, then do two things: +** +** (1) Mark the SELECT as as SF_Correlated. +** (2) Set pWalker->eCode to non-zero so that the caller will know +** that (1) has happened. +*/ +static int sqlite3ReturningSubqueryCorrelated(Walker *pWalker, Select *pSelect){ + int i; + SrcList *pSrc; + assert( pSelect!=0 ); + pSrc = pSelect->pSrc; + assert( pSrc!=0 ); + for(i=0; inSrc; i++){ + if( pSrc->a[i].pTab==pWalker->u.pTab ){ + testcase( pSelect->selFlags & SF_Correlated ); + pSelect->selFlags |= SF_Correlated; + pWalker->eCode = 1; + break; + } + } + return WRC_Continue; +} + +/* +** Scan the expression list that is the argument to RETURNING looking +** for subqueries that depend on the table which is being modified in the +** statement that is hosting the RETURNING clause (pTab). Mark all such +** subqueries as SF_Correlated. If the subqueries are part of an +** expression, mark the expression as EP_VarSelect. +** +** https://sqlite.org/forum/forumpost/2c83569ce8945d39 +*/ +static void sqlite3ProcessReturningSubqueries( + ExprList *pEList, + Table *pTab +){ + Walker w; + memset(&w, 0, sizeof(w)); + w.xExprCallback = sqlite3ExprWalkNoop; + w.xSelectCallback = sqlite3ReturningSubqueryCorrelated; + w.u.pTab = pTab; + sqlite3WalkExprList(&w, pEList); + if( w.eCode ){ + w.xExprCallback = sqlite3ReturningSubqueryVarSelect; + w.xSelectCallback = sqlite3SelectWalkNoop; + sqlite3WalkExprList(&w, pEList); + } +} /* ** Generate code for the RETURNING trigger. Unlike other triggers ** that invoke a subprogram in the bytecode, the code for RETURNING ** is generated in-line. @@ -985,10 +1051,11 @@ memset(&sFrom, 0, sizeof(sFrom)); sSelect.pEList = sqlite3ExprListDup(db, pReturning->pReturnEL, 0); sSelect.pSrc = &sFrom; sFrom.nSrc = 1; sFrom.a[0].pTab = pTab; + sFrom.a[0].zName = pTab->zName; /* tag-20240424-1 */ sFrom.a[0].iCursor = -1; sqlite3SelectPrep(pParse, &sSelect, 0); if( pParse->nErr==0 ){ assert( db->mallocFailed==0 ); sqlite3GenerateColumnNames(pParse, &sSelect); @@ -1011,10 +1078,11 @@ && ALWAYS(!db->mallocFailed) ){ int i; int nCol = pNew->nExpr; int reg = pParse->nMem+1; + sqlite3ProcessReturningSubqueries(pNew, pTab); pParse->nMem += nCol+2; pReturning->iRetReg = reg; for(i=0; ia[i].pExpr; assert( pCol!=0 ); /* Due to !db->mallocFailed ~9 lines above */ Index: src/update.c ================================================================== --- src/update.c +++ src/update.c @@ -919,10 +919,13 @@ }else{ sqlite3VdbeAddOp2(v, OP_Null, 0, k); } } if( chngRowid==0 && pPk==0 ){ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + if( isView ) sqlite3VdbeAddOp2(v, OP_Null, 0, regOldRowid); +#endif sqlite3VdbeAddOp2(v, OP_Copy, regOldRowid, regNewRowid); } } /* Populate the array of registers beginning at regNew with the new Index: src/upsert.c ================================================================== --- src/upsert.c +++ src/upsert.c @@ -88,11 +88,12 @@ ** is wrong. */ int sqlite3UpsertAnalyzeTarget( Parse *pParse, /* The parsing context */ SrcList *pTabList, /* Table into which we are inserting */ - Upsert *pUpsert /* The ON CONFLICT clauses */ + Upsert *pUpsert, /* The ON CONFLICT clauses */ + Upsert *pAll /* Complete list of all ON CONFLICT clauses */ ){ Table *pTab; /* That table into which we are inserting */ int rc; /* Result code */ int iCursor; /* Cursor used by pTab */ Index *pIdx; /* One of the indexes of pTab */ @@ -191,10 +192,18 @@ /* Column ii of the index did not match any term of the conflict target. ** Continue the search with the next index. */ continue; } pUpsert->pUpsertIdx = pIdx; + if( sqlite3UpsertOfIndex(pAll,pIdx)!=pUpsert ){ + /* Really this should be an error. The isDup ON CONFLICT clause will + ** never fire. But this problem was not discovered until three years + ** after multi-CONFLICT upsert was added, and so we silently ignore + ** the problem to prevent breaking applications that might actually + ** have redundant ON CONFLICT clauses. */ + pUpsert->isDup = 1; + } break; } if( pUpsert->pUpsertIdx==0 ){ char zWhich[16]; if( nClause==0 && pUpsert->pNextUpsert==0 ){ @@ -217,13 +226,17 @@ */ int sqlite3UpsertNextIsIPK(Upsert *pUpsert){ Upsert *pNext; if( NEVER(pUpsert==0) ) return 0; pNext = pUpsert->pNextUpsert; - if( pNext==0 ) return 1; - if( pNext->pUpsertTarget==0 ) return 1; - if( pNext->pUpsertIdx==0 ) return 1; + while( 1 /*exit-by-return*/ ){ + if( pNext==0 ) return 1; + if( pNext->pUpsertTarget==0 ) return 1; + if( pNext->pUpsertIdx==0 ) return 1; + if( !pNext->isDup ) return 0; + pNext = pNext->pNextUpsert; + } return 0; } /* ** Given the list of ON CONFLICT clauses described by pUpsert, and Index: src/util.c ================================================================== --- src/util.c +++ src/util.c @@ -63,10 +63,23 @@ #else rc = isnan(x); #endif /* HAVE_ISNAN */ testcase( rc ); return rc; +} +#endif /* SQLITE_OMIT_FLOATING_POINT */ + +#ifndef SQLITE_OMIT_FLOATING_POINT +/* +** Return true if the floating point value is NaN or +Inf or -Inf. +*/ +int sqlite3IsOverflow(double x){ + int rc; /* The value return */ + u64 y; + memcpy(&y,&x,sizeof(y)); + rc = IsOvfl(y); + return rc; } #endif /* SQLITE_OMIT_FLOATING_POINT */ /* ** Compute a string length that is limited to what can be stored in Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -1127,11 +1127,11 @@ ** this opcode. So jump over the coroutine implementation to ** address P2. ** ** See also: EndCoroutine */ -case OP_InitCoroutine: { /* jump */ +case OP_InitCoroutine: { /* jump0 */ assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); assert( pOp->p2>=0 && pOp->p2nOp ); assert( pOp->p3>=0 && pOp->p3nOp ); pOut = &aMem[pOp->p1]; assert( !VdbeMemDynamic(pOut) ); @@ -1150,11 +1150,13 @@ /* Opcode: EndCoroutine P1 * * * * ** ** The instruction at the address in register P1 is a Yield. ** Jump to the P2 parameter of that Yield. -** After the jump, register P1 becomes undefined. +** After the jump, the value register P1 is left with a value +** such that subsequent OP_Yields go back to the this same +** OP_EndCoroutine instruction. ** ** See also: InitCoroutine */ case OP_EndCoroutine: { /* in1 */ VdbeOp *pCaller; @@ -1162,12 +1164,12 @@ assert( pIn1->flags==MEM_Int ); assert( pIn1->u.i>=0 && pIn1->u.inOp ); pCaller = &aOp[pIn1->u.i]; assert( pCaller->opcode==OP_Yield ); assert( pCaller->p2>=0 && pCaller->p2nOp ); + pIn1->u.i = (int)(pOp - p->aOp) - 1; pOp = &aOp[pCaller->p2 - 1]; - pIn1->flags = MEM_Undefined; break; } /* Opcode: Yield P1 P2 * * * ** @@ -1180,11 +1182,11 @@ ** EndCoroutine, then jump to P2 rather than continuing with the ** next instruction. ** ** See also: InitCoroutine */ -case OP_Yield: { /* in1, jump */ +case OP_Yield: { /* in1, jump0 */ int pcDest; pIn1 = &aMem[pOp->p1]; assert( VdbeMemDynamic(pIn1)==0 ); pIn1->flags = MEM_Int; pcDest = (int)pIn1->u.i; @@ -2039,11 +2041,11 @@ ** Force the value in register P1 to be an integer. If the value ** in P1 is not an integer and cannot be converted into an integer ** without data loss, then jump immediately to P2, or if P2==0 ** raise an SQLITE_MISMATCH exception. */ -case OP_MustBeInt: { /* jump, in1 */ +case OP_MustBeInt: { /* jump0, in1 */ pIn1 = &aMem[pOp->p1]; if( (pIn1->flags & MEM_Int)==0 ){ applyAffinity(pIn1, SQLITE_AFF_NUMERIC, encoding); if( (pIn1->flags & MEM_Int)==0 ){ VdbeBranchTaken(1, 2); @@ -4580,11 +4582,12 @@ ** row output from the sorter so that the row can be decomposed into ** individual columns using the OP_Column opcode. The OP_Column opcode ** is the only cursor opcode that works with a pseudo-table. ** ** P3 is the number of fields in the records that will be stored by -** the pseudo-table. +** the pseudo-table. If P2 is 0 or negative then the pseudo-cursor +** will return NULL for every column. */ case OP_OpenPseudo: { VdbeCursor *pCx; assert( pOp->p1>=0 ); @@ -4723,14 +4726,14 @@ ** OPFLAG_SEEKEQ flags is a hint to the btree layer to say that this ** is an equality search. ** ** See also: Found, NotFound, SeekGt, SeekGe, SeekLt */ -case OP_SeekLT: /* jump, in3, group, ncycle */ -case OP_SeekLE: /* jump, in3, group, ncycle */ -case OP_SeekGE: /* jump, in3, group, ncycle */ -case OP_SeekGT: { /* jump, in3, group, ncycle */ +case OP_SeekLT: /* jump0, in3, group, ncycle */ +case OP_SeekLE: /* jump0, in3, group, ncycle */ +case OP_SeekGE: /* jump0, in3, group, ncycle */ +case OP_SeekGT: { /* jump0, in3, group, ncycle */ int res; /* Comparison result */ int oc; /* Opcode */ VdbeCursor *pC; /* The cursor to seek */ UnpackedRecord r; /* The key to seek for */ int nField; /* Number of columns or fields in the key */ @@ -5393,11 +5396,11 @@ ** in either direction. In other words, the Next and Prev opcodes will ** not work following this opcode. ** ** See also: Found, NotFound, NoConflict, SeekRowid */ -case OP_SeekRowid: { /* jump, in3, ncycle */ +case OP_SeekRowid: { /* jump0, in3, ncycle */ VdbeCursor *pC; BtCursor *pCrsr; int res; u64 iKey; @@ -6152,11 +6155,11 @@ ** This opcode leaves the cursor configured to move in reverse order, ** from the end toward the beginning. In other words, the cursor is ** configured to use Prev, not Next. */ case OP_SeekEnd: /* ncycle */ -case OP_Last: { /* jump, ncycle */ +case OP_Last: { /* jump0, ncycle */ VdbeCursor *pC; BtCursor *pCrsr; int res; assert( pOp->p1>=0 && pOp->p1nCursor ); @@ -6190,11 +6193,12 @@ /* Opcode: IfSizeBetween P1 P2 P3 P4 * ** ** Let N be the approximate number of rows in the table or index ** with cursor P1 and let X be 10*log2(N) if N is positive or -1 -** if N is zero. Thus X will be within the range of -1 to 640, inclusive +** if N is zero. +** ** Jump to P2 if X is in between P3 and P4, inclusive. */ case OP_IfSizeBetween: { /* jump */ VdbeCursor *pC; BtCursor *pCrsr; @@ -6201,12 +6205,12 @@ int res; i64 sz; assert( pOp->p1>=0 && pOp->p1nCursor ); assert( pOp->p4type==P4_INT32 ); - assert( pOp->p3>=-1 && pOp->p3<=640 ); - assert( pOp->p4.i>=-1 && pOp->p4.i<=640 ); + assert( pOp->p3>=-1 && pOp->p3<=640*2 ); + assert( pOp->p4.i>=-1 && pOp->p4.i<=640*2 ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); pCrsr = pC->uc.pCursor; assert( pCrsr ); rc = sqlite3BtreeFirst(pCrsr, &res); @@ -6269,11 +6273,11 @@ ** ** This opcode leaves the cursor configured to move in forward order, ** from the beginning toward the end. In other words, the cursor is ** configured to use Next, not Prev. */ -case OP_Rewind: { /* jump, ncycle */ +case OP_Rewind: { /* jump0, ncycle */ VdbeCursor *pC; BtCursor *pCrsr; int res; assert( pOp->p1>=0 && pOp->p1nCursor ); @@ -7277,19 +7281,21 @@ ** Execute the trigger program passed as P4 (type P4_SUBPROGRAM). ** ** P1 contains the address of the memory cell that contains the first memory ** cell in an array of values used as arguments to the sub-program. P2 ** contains the address to jump to if the sub-program throws an IGNORE -** exception using the RAISE() function. Register P3 contains the address +** exception using the RAISE() function. P2 might be zero, if there is +** no possibility that an IGNORE exception will be raised. +** Register P3 contains the address ** of a memory cell in this (the parent) VM that is used to allocate the ** memory required by the sub-vdbe at runtime. ** ** P4 is a pointer to the VM containing the trigger program. ** ** If P5 is non-zero, then recursive program invocation is enabled. */ -case OP_Program: { /* jump */ +case OP_Program: { /* jump0 */ int nMem; /* Number of memory registers for sub-program */ int nByte; /* Bytes of runtime space required for sub-program */ Mem *pRt; /* Register to allocate runtime space */ Mem *pMem; /* Used to iterate through memory cells */ Mem *pEnd; /* Last memory cell in new array */ @@ -8834,11 +8840,11 @@ ** ** If P3 is not zero, then it is an address to jump to if an SQLITE_CORRUPT ** error is encountered. */ case OP_Trace: -case OP_Init: { /* jump */ +case OP_Init: { /* jump0 */ int i; #ifndef SQLITE_OMIT_TRACE char *zTrace; #endif Index: src/vdbeapi.c ================================================================== --- src/vdbeapi.c +++ src/vdbeapi.c @@ -2402,11 +2402,10 @@ } return 1; } if( flags & SQLITE_SCANSTAT_COMPLEX ){ idx = iScan; - pScan = &p->aScan[idx]; }else{ /* If the COMPLEX flag is clear, then this function must ignore any ** ScanStatus structures with ScanStatus.addrLoop set to 0. */ for(idx=0; idxnScan; idx++){ pScan = &p->aScan[idx]; @@ -2415,10 +2414,12 @@ if( iScan<0 ) break; } } } if( idx>=p->nScan ) return 1; + assert( pScan==0 || pScan==&p->aScan[idx] ); + pScan = &p->aScan[idx]; switch( iScanStatusOp ){ case SQLITE_SCANSTAT_NLOOP: { if( pScan->addrLoop>0 ){ *(sqlite3_int64*)pOut = aOp[pScan->addrLoop].nExec; Index: src/vdbeaux.c ================================================================== --- src/vdbeaux.c +++ src/vdbeaux.c @@ -937,10 +937,19 @@ assert( (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_JUMP)!=0 ); assert( ADDR(pOp->p2)<-pParse->nLabel ); assert( aLabel!=0 ); /* True because of tag-20230419-1 */ pOp->p2 = aLabel[ADDR(pOp->p2)]; } + + /* OPFLG_JUMP opcodes never have P2==0, though OPFLG_JUMP0 opcodes + ** might */ + assert( pOp->p2>0 + || (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_JUMP0)!=0 ); + + /* Jumps never go off the end of the bytecode array */ + assert( pOp->p2nOp + || (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_JUMP)==0 ); break; } } /* The mkopcodeh.tcl script has so arranged things that the only ** non-jump opcodes less than SQLITE_MX_JUMP_CODE are guaranteed to @@ -3344,13 +3353,13 @@ } } /* Check for immediate foreign key violations. */ if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){ - sqlite3VdbeCheckFk(p, 0); + (void)sqlite3VdbeCheckFk(p, 0); } - + /* If the auto-commit flag is set and this is the only active writer ** VM, then we do either a commit or rollback of the current transaction. ** ** Note: This block also runs if one of the special errors handled ** above has occurred. Index: src/vdbemem.c ================================================================== --- src/vdbemem.c +++ src/vdbemem.c @@ -1947,21 +1947,21 @@ int nRec, /* Size of buffer pRec in bytes */ int iCol, /* Column to extract */ sqlite3_value **ppVal /* OUT: Extracted value */ ){ u32 t = 0; /* a column type code */ - int nHdr; /* Size of the header in the record */ - int iHdr; /* Next unread header byte */ - int iField; /* Next unread data byte */ - int szField = 0; /* Size of the current data field */ + u32 nHdr; /* Size of the header in the record */ + u32 iHdr; /* Next unread header byte */ + i64 iField; /* Next unread data byte */ + u32 szField = 0; /* Size of the current data field */ int i; /* Column index */ u8 *a = (u8*)pRec; /* Typecast byte array */ Mem *pMem = *ppVal; /* Write result into this Mem object */ assert( iCol>0 ); iHdr = getVarint32(a, nHdr); - if( nHdr>nRec || iHdr>=nHdr ) return SQLITE_CORRUPT_BKPT; + if( nHdr>(u32)nRec || iHdr>=nHdr ) return SQLITE_CORRUPT_BKPT; iField = nHdr; for(i=0; i<=iCol; i++){ iHdr += getVarint32(&a[iHdr], t); testcase( iHdr==nHdr ); testcase( iHdr==nHdr+1 ); Index: src/vdbevtab.c ================================================================== --- src/vdbevtab.c +++ src/vdbevtab.c @@ -284,14 +284,14 @@ break; } #ifdef SQLITE_ENABLE_STMT_SCANSTATUS case 9: /* nexec */ - sqlite3_result_int(ctx, pOp->nExec); + sqlite3_result_int64(ctx, pOp->nExec); break; case 10: /* ncycle */ - sqlite3_result_int(ctx, pOp->nCycle); + sqlite3_result_int64(ctx, pOp->nCycle); break; #else case 9: /* nexec */ case 10: /* ncycle */ sqlite3_result_int(ctx, 0); Index: src/vtab.c ================================================================== --- src/vtab.c +++ src/vtab.c @@ -811,23 +811,42 @@ VtabCtx *pCtx; int rc = SQLITE_OK; Table *pTab; Parse sParse; int initBusy; + int i; + const unsigned char *z; + static const u8 aKeyword[] = { TK_CREATE, TK_TABLE, 0 }; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) || zCreateTable==0 ){ return SQLITE_MISUSE_BKPT; } #endif + + /* Verify that the first two keywords in the CREATE TABLE statement + ** really are "CREATE" and "TABLE". If this is not the case, then + ** sqlite3_declare_vtab() is being misused. + */ + z = (const unsigned char*)zCreateTable; + for(i=0; aKeyword[i]; i++){ + int tokenType = 0; + do{ z += sqlite3GetToken(z, &tokenType); }while( tokenType==TK_SPACE ); + if( tokenType!=aKeyword[i] ){ + sqlite3ErrorWithMsg(db, SQLITE_ERROR, "syntax error"); + return SQLITE_ERROR; + } + } + sqlite3_mutex_enter(db->mutex); pCtx = db->pVtabCtx; if( !pCtx || pCtx->bDeclared ){ sqlite3Error(db, SQLITE_MISUSE_BKPT); sqlite3_mutex_leave(db->mutex); return SQLITE_MISUSE_BKPT; } + pTab = pCtx->pTab; assert( IsVirtual(pTab) ); sqlite3ParseObjectInit(&sParse, db); sParse.eParseMode = PARSE_MODE_DECLARE_VTAB; @@ -837,15 +856,14 @@ ** in case a bug arises. */ assert( db->init.busy==0 ); initBusy = db->init.busy; db->init.busy = 0; sParse.nQueryLoop = 1; - if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable) - && ALWAYS(sParse.pNewTable!=0) - && ALWAYS(!db->mallocFailed) - && IsOrdinaryTable(sParse.pNewTable) - ){ + if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable) ){ + assert( sParse.pNewTable!=0 ); + assert( !db->mallocFailed ); + assert( IsOrdinaryTable(sParse.pNewTable) ); assert( sParse.zErrMsg==0 ); if( !pTab->aCol ){ Table *pNew = sParse.pNewTable; Index *pIdx; pTab->aCol = pNew->aCol; Index: src/where.c ================================================================== --- src/where.c +++ src/where.c @@ -299,10 +299,46 @@ if( ALWAYS(p!=0) && p->op==TK_COLUMN && !ExprHasProperty(p, EP_FixedCol) ){ return p; } return 0; } + +/* +** Term pTerm is guaranteed to be a WO_IN term. It may be a component term +** of a vector IN expression of the form "(x, y, ...) IN (SELECT ...)". +** This function checks to see if the term is compatible with an index +** column with affinity idxaff (one of the SQLITE_AFF_XYZ values). If so, +** it returns a pointer to the name of the collation sequence (e.g. "BINARY" +** or "NOCASE") used by the comparison in pTerm. If it is not compatible +** with affinity idxaff, NULL is returned. +*/ +static SQLITE_NOINLINE const char *indexInAffinityOk( + Parse *pParse, + WhereTerm *pTerm, + u8 idxaff +){ + Expr *pX = pTerm->pExpr; + Expr inexpr; + + assert( pTerm->eOperator & WO_IN ); + + if( sqlite3ExprIsVector(pX->pLeft) ){ + int iField = pTerm->u.x.iField - 1; + inexpr.flags = 0; + inexpr.op = TK_EQ; + inexpr.pLeft = pX->pLeft->x.pList->a[iField].pExpr; + assert( ExprUseXSelect(pX) ); + inexpr.pRight = pX->x.pSelect->pEList->a[iField].pExpr; + pX = &inexpr; + } + + if( sqlite3IndexAffinityOk(pX, idxaff) ){ + CollSeq *pRet = sqlite3ExprCompareCollSeq(pParse, pX); + return pRet ? pRet->zName : sqlite3StrBINARY; + } + return 0; +} /* ** Advance to the next WhereTerm that matches according to the criteria ** established when the pScan object was initialized by whereScanInit(). ** Return NULL if there are no more matching WhereTerms. @@ -350,20 +386,28 @@ } } if( (pTerm->eOperator & pScan->opMask)!=0 ){ /* Verify the affinity and collating sequence match */ if( pScan->zCollName && (pTerm->eOperator & WO_ISNULL)==0 ){ - CollSeq *pColl; + const char *zCollName; Parse *pParse = pWC->pWInfo->pParse; pX = pTerm->pExpr; - if( !sqlite3IndexAffinityOk(pX, pScan->idxaff) ){ - continue; + + if( (pTerm->eOperator & WO_IN) ){ + zCollName = indexInAffinityOk(pParse, pTerm, pScan->idxaff); + if( !zCollName ) continue; + }else{ + CollSeq *pColl; + if( !sqlite3IndexAffinityOk(pX, pScan->idxaff) ){ + continue; + } + assert(pX->pLeft); + pColl = sqlite3ExprCompareCollSeq(pParse, pX); + zCollName = pColl ? pColl->zName : sqlite3StrBINARY; } - assert(pX->pLeft); - pColl = sqlite3ExprCompareCollSeq(pParse, pX); - if( pColl==0 ) pColl = pParse->db->pDfltColl; - if( sqlite3StrICmp(pColl->zName, pScan->zCollName) ){ + + if( sqlite3StrICmp(zCollName, pScan->zCollName) ){ continue; } } if( (pTerm->eOperator & (WO_EQ|WO_IS))!=0 && (pX = pTerm->pExpr->pRight, ALWAYS(pX!=0)) @@ -711,13 +755,17 @@ ** structure. Used for testing and debugging only. If neither ** SQLITE_TEST or SQLITE_DEBUG are defined, then these routines ** are no-ops. */ #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(WHERETRACE_ENABLED) -static void whereTraceIndexInfoInputs(sqlite3_index_info *p){ +static void whereTraceIndexInfoInputs( + sqlite3_index_info *p, /* The IndexInfo object */ + Table *pTab /* The TABLE that is the virtual table */ +){ int i; if( (sqlite3WhereTrace & 0x10)==0 ) return; + sqlite3DebugPrintf("sqlite3_index_info inputs for %s:\n", pTab->zName); for(i=0; inConstraint; i++){ sqlite3DebugPrintf( " constraint[%d]: col=%d termid=%d op=%d usabled=%d collseq=%s\n", i, p->aConstraint[i].iColumn, @@ -731,13 +779,17 @@ i, p->aOrderBy[i].iColumn, p->aOrderBy[i].desc); } } -static void whereTraceIndexInfoOutputs(sqlite3_index_info *p){ +static void whereTraceIndexInfoOutputs( + sqlite3_index_info *p, /* The IndexInfo object */ + Table *pTab /* The TABLE that is the virtual table */ +){ int i; if( (sqlite3WhereTrace & 0x10)==0 ) return; + sqlite3DebugPrintf("sqlite3_index_info outputs for %s:\n", pTab->zName); for(i=0; inConstraint; i++){ sqlite3DebugPrintf(" usage[%d]: argvIdx=%d omit=%d\n", i, p->aConstraintUsage[i].argvIndex, p->aConstraintUsage[i].omit); @@ -747,12 +799,12 @@ sqlite3DebugPrintf(" orderByConsumed=%d\n", p->orderByConsumed); sqlite3DebugPrintf(" estimatedCost=%g\n", p->estimatedCost); sqlite3DebugPrintf(" estimatedRows=%lld\n", p->estimatedRows); } #else -#define whereTraceIndexInfoInputs(A) -#define whereTraceIndexInfoOutputs(A) +#define whereTraceIndexInfoInputs(A,B) +#define whereTraceIndexInfoOutputs(A,B) #endif /* ** We know that pSrc is an operand of an outer join. Return true if ** pTerm is a constraint that is compatible with that join. @@ -932,11 +984,11 @@ Expr *pExpr = pTerm->pExpr; /* Make the automatic index a partial index if there are terms in the ** WHERE clause (or the ON clause of a LEFT join) that constrain which ** rows of the target table (pSrc) that can be used. */ if( (pTerm->wtFlags & TERM_VIRTUAL)==0 - && sqlite3ExprIsSingleTableConstraint(pExpr, pTabList, pLevel->iFrom) + && sqlite3ExprIsSingleTableConstraint(pExpr, pTabList, pLevel->iFrom, 0) ){ pPartial = sqlite3ExprAnd(pParse, pPartial, sqlite3ExprDup(pParse->db, pExpr, 0)); } if( termCanDriveIndex(pTerm, pSrc, notReady) ){ @@ -974,11 +1026,11 @@ ** be a covering index because the index will not be updated if the ** original table changes and the index and table cannot both be used ** if they go out of sync. */ if( IsView(pTable) ){ - extraCols = ALLBITS; + extraCols = ALLBITS & ~idxCols; }else{ extraCols = pSrc->colUsed & (~idxCols | MASKBIT(BMS-1)); } mxBitCol = MIN(BMS-1,pTable->nCol); testcase( pTable->nCol==BMS-1 ); @@ -1201,11 +1253,11 @@ addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, iCur); VdbeCoverage(v); pWCEnd = &pWInfo->sWC.a[pWInfo->sWC.nTerm]; for(pTerm=pWInfo->sWC.a; pTermpExpr; if( (pTerm->wtFlags & TERM_VIRTUAL)==0 - && sqlite3ExprIsSingleTableConstraint(pExpr, pTabList, iSrc) + && sqlite3ExprIsSingleTableConstraint(pExpr, pTabList, iSrc, 0) ){ sqlite3ExprIfFalse(pParse, pTerm->pExpr, addrCont, SQLITE_JUMPIFNULL); } } if( pLoop->wsFlags & WHERE_IPK ){ @@ -1327,11 +1379,11 @@ for(i=0; ia[i].pExpr; Expr *pE2; /* Skip over constant terms in the ORDER BY clause */ - if( sqlite3ExprIsConstant(pExpr) ){ + if( sqlite3ExprIsConstant(0, pExpr) ){ continue; } /* Virtual tables are unable to deal with NULLS FIRST */ if( pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_BIGNULL ) break; @@ -1362,11 +1414,11 @@ /* No matches cause a break out of the loop */ break; } if( i==n ){ nOrderBy = n; - if( (pWInfo->wctrlFlags & WHERE_DISTINCTBY) ){ + if( (pWInfo->wctrlFlags & WHERE_DISTINCTBY) && !pSrc->fg.rowidUsed ){ eDistinct = 2 + ((pWInfo->wctrlFlags & WHERE_SORTBYGROUP)!=0); }else if( pWInfo->wctrlFlags & WHERE_GROUPBY ){ eDistinct = 1; } } @@ -1439,11 +1491,11 @@ } assert( j==nTerm ); pIdxInfo->nConstraint = j; for(i=j=0; ia[i].pExpr; - if( sqlite3ExprIsConstant(pExpr) ) continue; + if( sqlite3ExprIsConstant(0, pExpr) ) continue; assert( pExpr->op==TK_COLUMN || (pExpr->op==TK_COLLATE && pExpr->pLeft->op==TK_COLUMN && pExpr->iColumn==pExpr->pLeft->iColumn) ); pIdxOrderBy[j].iColumn = pExpr->iColumn; pIdxOrderBy[j].desc = pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_DESC; @@ -1491,15 +1543,15 @@ */ static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){ sqlite3_vtab *pVtab = sqlite3GetVTable(pParse->db, pTab)->pVtab; int rc; - whereTraceIndexInfoInputs(p); + whereTraceIndexInfoInputs(p, pTab); pParse->db->nSchemaLock++; rc = pVtab->pModule->xBestIndex(pVtab, p); pParse->db->nSchemaLock--; - whereTraceIndexInfoOutputs(p); + whereTraceIndexInfoOutputs(p, pTab); if( rc!=SQLITE_OK && rc!=SQLITE_CONSTRAINT ){ if( rc==SQLITE_NOMEM ){ sqlite3OomFault(pParse->db); }else if( !pVtab->zErrMsg ){ @@ -3242,14 +3294,17 @@ } } } } - /* Set rCostIdx to the cost of visiting selected rows in index. Add - ** it to pNew->rRun, which is currently set to the cost of the index - ** seek only. Then, if this is a non-covering index, add the cost of - ** visiting the rows in the main table. */ + /* Set rCostIdx to the estimated cost of visiting selected rows in the + ** index. The estimate is the sum of two values: + ** 1. The cost of doing one search-by-key to find the first matching + ** entry + ** 2. Stepping forward in the index pNew->nOut times to find all + ** additional matching entries. + */ assert( pSrc->pTab->szTabRow>0 ); if( pProbe->idxType==SQLITE_IDXTYPE_IPK ){ /* The pProbe->szIdxRow is low for an IPK table since the interior ** pages are small. Thus szIdxRow gives a good estimate of seek cost. ** But the leaf pages are full-size, so pProbe->szIdxRow would badly @@ -3256,11 +3311,19 @@ ** under-estimate the scanning cost. */ rCostIdx = pNew->nOut + 16; }else{ rCostIdx = pNew->nOut + 1 + (15*pProbe->szIdxRow)/pSrc->pTab->szTabRow; } - pNew->rRun = sqlite3LogEstAdd(rLogSize, rCostIdx); + rCostIdx = sqlite3LogEstAdd(rLogSize, rCostIdx); + + /* Estimate the cost of running the loop. If all data is coming + ** from the index, then this is just the cost of doing the index + ** lookup and scan. But if some data is coming out of the main table, + ** we also have to add in the cost of doing pNew->nOut searches to + ** locate the row in the main table that corresponds to the index entry. + */ + pNew->rRun = rCostIdx; if( (pNew->wsFlags & (WHERE_IDX_ONLY|WHERE_IPK|WHERE_EXPRIDX))==0 ){ pNew->rRun = sqlite3LogEstAdd(pNew->rRun, pNew->nOut + 16); } ApplyCostMultiplier(pNew->rRun, pProbe->pTable->costMult); @@ -3621,11 +3684,11 @@ Expr *pLeft = pPart->pLeft; Expr *pRight = pPart->pRight; u8 aff; if( pLeft->op!=TK_COLUMN ) return; - if( !sqlite3ExprIsConstant(pRight) ) return; + if( !sqlite3ExprIsConstant(0, pRight) ) return; if( !sqlite3IsBinary(sqlite3ExprCompareCollSeq(pParse, pPart)) ) return; if( pLeft->iColumn<0 ) return; aff = pIdx->pTable->aCol[pLeft->iColumn].affinity; if( aff>=SQLITE_AFF_TEXT ){ if( pItem ){ @@ -3991,10 +4054,25 @@ static int isLimitTerm(WhereTerm *pTerm){ assert( pTerm->eOperator==WO_AUX || pTerm->eMatchOp==0 ); return pTerm->eMatchOp>=SQLITE_INDEX_CONSTRAINT_LIMIT && pTerm->eMatchOp<=SQLITE_INDEX_CONSTRAINT_OFFSET; } + +/* +** Return true if the first nCons constraints in the pUsage array are +** marked as in-use (have argvIndex>0). False otherwise. +*/ +static int allConstraintsUsed( + struct sqlite3_index_constraint_usage *aUsage, + int nCons +){ + int ii; + for(ii=0; iipNew->iTab. This ** function marks a subset of those constraints usable, invokes the @@ -4132,17 +4210,24 @@ pIdxInfo->orderByConsumed = 0; pIdxInfo->idxFlags &= ~SQLITE_INDEX_SCAN_UNIQUE; *pbIn = 1; assert( (mExclude & WO_IN)==0 ); } + /* Unless pbRetryLimit is non-NULL, there should be no LIMIT/OFFSET + ** terms. And if there are any, they should follow all other terms. */ assert( pbRetryLimit || !isLimitTerm(pTerm) ); - if( isLimitTerm(pTerm) && *pbIn ){ + assert( !isLimitTerm(pTerm) || i>=nConstraint-2 ); + assert( !isLimitTerm(pTerm) || i==nConstraint-1 || isLimitTerm(pTerm+1) ); + + if( isLimitTerm(pTerm) && (*pbIn || !allConstraintsUsed(pUsage, i)) ){ /* If there is an IN(...) term handled as an == (separate call to ** xFilter for each value on the RHS of the IN) and a LIMIT or - ** OFFSET term handled as well, the plan is unusable. Set output - ** variable *pbRetryLimit to true to tell the caller to retry with - ** LIMIT and OFFSET disabled. */ + ** OFFSET term handled as well, the plan is unusable. Similarly, + ** if there is a LIMIT/OFFSET and there are other unused terms, + ** the plan cannot be used. In these cases set variable *pbRetryLimit + ** to true to tell the caller to retry with LIMIT and OFFSET + ** disabled. */ if( pIdxInfo->needToFreeIdxStr ){ sqlite3_free(pIdxInfo->idxStr); pIdxInfo->idxStr = 0; pIdxInfo->needToFreeIdxStr = 0; } @@ -4995,11 +5080,11 @@ Expr *p; Bitmask mTerm; if( MASKBIT(i) & obSat ) continue; p = pOrderBy->a[i].pExpr; mTerm = sqlite3WhereExprUsage(&pWInfo->sMaskSet,p); - if( mTerm==0 && !sqlite3ExprIsConstant(p) ) continue; + if( mTerm==0 && !sqlite3ExprIsConstant(0,p) ) continue; if( (mTerm&~orderDistinctMask)==0 ){ obSat |= MASKBIT(i); } } } @@ -5509,17 +5594,93 @@ pWInfo->revMask = revMask; } } } - pWInfo->nRowOut = pFrom->nRow; /* Free temporary memory and return success */ sqlite3StackFreeNN(pParse->db, pSpace); return SQLITE_OK; } + +/* +** This routine implements a heuristic designed to improve query planning. +** This routine is called in between the first and second call to +** wherePathSolver(). Hence the name "Interstage" "Heuristic". +** +** The first call to wherePathSolver() (hereafter just "solver()") computes +** the best path without regard to the order of the outputs. The second call +** to the solver() builds upon the first call to try to find an alternative +** path that satisfies the ORDER BY clause. +** +** This routine looks at the results of the first solver() run, and for +** every FROM clause term in the resulting query plan that uses an equality +** constraint against an index, disable other WhereLoops for that same +** FROM clause term that would try to do a full-table scan. This prevents +** an index search from being converted into a full-table scan in order to +** satisfy an ORDER BY clause, since even though we might get slightly better +** performance using the full-scan without sorting if the output size +** estimates are very precise, we might also get severe performance +** degradation using the full-scan if the output size estimate is too large. +** It is better to err on the side of caution. +** +** Except, if the first solver() call generated a full-table scan in an outer +** loop then stop this analysis at the first full-scan, since the second +** solver() run might try to swap that full-scan for another in order to +** get the output into the correct order. In other words, we allow a +** rewrite like this: +** +** First Solver() Second Solver() +** |-- SCAN t1 |-- SCAN t2 +** |-- SEARCH t2 `-- SEARCH t1 +** `-- SORT USING B-TREE +** +** The purpose of this routine is to disallow rewrites such as: +** +** First Solver() Second Solver() +** |-- SEARCH t1 |-- SCAN t2 <--- bad! +** |-- SEARCH t2 `-- SEARCH t1 +** `-- SORT USING B-TREE +** +** See test cases in test/whereN.test for the real-world query that +** originally provoked this heuristic. +*/ +static SQLITE_NOINLINE void whereInterstageHeuristic(WhereInfo *pWInfo){ + int i; +#ifdef WHERETRACE_ENABLED + int once = 0; +#endif + for(i=0; inLevel; i++){ + WhereLoop *p = pWInfo->a[i].pWLoop; + if( p==0 ) break; + if( (p->wsFlags & WHERE_VIRTUALTABLE)!=0 ) continue; + if( (p->wsFlags & (WHERE_COLUMN_EQ|WHERE_COLUMN_NULL|WHERE_COLUMN_IN))!=0 ){ + u8 iTab = p->iTab; + WhereLoop *pLoop; + for(pLoop=pWInfo->pLoops; pLoop; pLoop=pLoop->pNextLoop){ + if( pLoop->iTab!=iTab ) continue; + if( (pLoop->wsFlags & (WHERE_CONSTRAINT|WHERE_AUTO_INDEX))!=0 ){ + /* Auto-index and index-constrained loops allowed to remain */ + continue; + } +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace & 0x80 ){ + if( once==0 ){ + sqlite3DebugPrintf("Loops disabled by interstage heuristic:\n"); + once = 1; + } + sqlite3WhereLoopPrint(pLoop, &pWInfo->sWC); + } +#endif /* WHERETRACE_ENABLED */ + pLoop->prereq = ALLBITS; /* Prevent 2nd solver() from using this one */ + } + }else{ + break; + } + } +} /* ** Most queries use only a single table (they are not joins) and have ** simple == constraints against indexed fields. This routine attempts ** to plan those simple cases using much less ceremony than the @@ -5685,10 +5846,14 @@ ** might move from the right side to the left side of the RIGHT JOIN. ** Note: Due to (2), this condition can only arise if the table is ** the right-most table of a subquery that was flattened into the ** main query and that subquery was the right-hand operand of an ** inner join that held an ON or USING clause. +** 6) The ORDER BY clause has 63 or fewer terms +** 7) The omit-noop-join optimization is enabled. +** +** Items (1), (6), and (7) are checked by the caller. ** ** For example, given: ** ** CREATE TABLE t1(ipk INTEGER PRIMARY KEY, v1); ** CREATE TABLE t2(ipk INTEGER PRIMARY KEY, v2); @@ -5825,10 +5990,62 @@ } } nSearch += pLoop->nOut; } } + +/* +** Expression Node callback for sqlite3ExprCanReturnSubtype(). +** +** Only a function call is able to return a subtype. So if the node +** is not a function call, return WRC_Prune immediately. +** +** A function call is able to return a subtype if it has the +** SQLITE_RESULT_SUBTYPE property. +** +** Assume that every function is able to pass-through a subtype from +** one of its argument (using sqlite3_result_value()). Most functions +** are not this way, but we don't have a mechanism to distinguish those +** that are from those that are not, so assume they all work this way. +** That means that if one of its arguments is another function and that +** other function is able to return a subtype, then this function is +** able to return a subtype. +*/ +static int exprNodeCanReturnSubtype(Walker *pWalker, Expr *pExpr){ + int n; + FuncDef *pDef; + sqlite3 *db; + if( pExpr->op!=TK_FUNCTION ){ + return WRC_Prune; + } + assert( ExprUseXList(pExpr) ); + db = pWalker->pParse->db; + n = pExpr->x.pList ? pExpr->x.pList->nExpr : 0; + pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0); + if( pDef==0 || (pDef->funcFlags & SQLITE_RESULT_SUBTYPE)!=0 ){ + pWalker->eCode = 1; + return WRC_Prune; + } + return WRC_Continue; +} + +/* +** Return TRUE if expression pExpr is able to return a subtype. +** +** A TRUE return does not guarantee that a subtype will be returned. +** It only indicates that a subtype return is possible. False positives +** are acceptable as they only disable an optimization. False negatives, +** on the other hand, can lead to incorrect answers. +*/ +static int sqlite3ExprCanReturnSubtype(Parse *pParse, Expr *pExpr){ + Walker w; + memset(&w, 0, sizeof(w)); + w.pParse = pParse; + w.xExprCallback = exprNodeCanReturnSubtype; + sqlite3WalkExpr(&w, pExpr); + return w.eCode; +} /* ** The index pIdx is used by a query and contains one or more expressions. ** In other words pIdx is an index on an expression. iIdxCur is the cursor ** number for the index and iDataCur is the cursor number for the corresponding @@ -5851,37 +6068,23 @@ assert( pIdx->bHasExpr ); pTab = pIdx->pTable; for(i=0; inColumn; i++){ Expr *pExpr; int j = pIdx->aiColumn[i]; - int bMaybeNullRow; if( j==XN_EXPR ){ pExpr = pIdx->aColExpr->a[i].pExpr; - testcase( pTabItem->fg.jointype & JT_LEFT ); - testcase( pTabItem->fg.jointype & JT_RIGHT ); - testcase( pTabItem->fg.jointype & JT_LTORJ ); - bMaybeNullRow = (pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0; }else if( j>=0 && (pTab->aCol[j].colFlags & COLFLAG_VIRTUAL)!=0 ){ pExpr = sqlite3ColumnExpr(pTab, &pTab->aCol[j]); - bMaybeNullRow = 0; }else{ continue; } - if( sqlite3ExprIsConstant(pExpr) ) continue; - if( pExpr->op==TK_FUNCTION ){ + if( sqlite3ExprIsConstant(0,pExpr) ) continue; + if( pExpr->op==TK_FUNCTION && sqlite3ExprCanReturnSubtype(pParse,pExpr) ){ /* Functions that might set a subtype should not be replaced by the ** value taken from an expression index since the index omits the ** subtype. https://sqlite.org/forum/forumpost/68d284c86b082c3e */ - int n; - FuncDef *pDef; - sqlite3 *db = pParse->db; - assert( ExprUseXList(pExpr) ); - n = pExpr->x.pList ? pExpr->x.pList->nExpr : 0; - pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0); - if( pDef==0 || (pDef->funcFlags & SQLITE_RESULT_SUBTYPE)!=0 ){ - continue; - } + continue; } p = sqlite3DbMallocRaw(pParse->db, sizeof(IndexedExpr)); if( p==0 ) break; p->pIENext = pParse->pIdxEpr; #ifdef WHERETRACE_ENABLED @@ -5892,11 +6095,11 @@ #endif p->pExpr = sqlite3ExprDup(pParse->db, pExpr, 0); p->iDataCur = pTabItem->iCursor; p->iIdxCur = iIdxCur; p->iIdxCol = i; - p->bMaybeNullRow = bMaybeNullRow; + p->bMaybeNullRow = (pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0; if( sqlite3IndexAffinityStr(pParse->db, pIdx) ){ p->aff = pIdx->zColAff[i]; } #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS p->zIdxName = pIdx->zName; @@ -6060,10 +6263,11 @@ /* An ORDER/GROUP BY clause of more than 63 terms cannot be optimized */ testcase( pOrderBy && pOrderBy->nExpr==BMS-1 ); if( pOrderBy && pOrderBy->nExpr>=BMS ){ pOrderBy = 0; wctrlFlags &= ~WHERE_WANT_DISTINCT; + wctrlFlags |= WHERE_KEEP_ALL_JOINS; /* Disable omit-noop-join opt */ } /* The number of tables in the FROM clause is limited by the number of ** bits in a Bitmask */ @@ -6142,11 +6346,15 @@ if( (wctrlFlags & WHERE_WANT_DISTINCT)!=0 && OptimizationEnabled(db, SQLITE_DistinctOpt) ){ pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; } - ExplainQueryPlan((pParse, 0, "SCAN CONSTANT ROW")); + if( ALWAYS(pWInfo->pSelect) + && (pWInfo->pSelect->selFlags & SF_MultiValue)==0 + ){ + ExplainQueryPlan((pParse, 0, "SCAN CONSTANT ROW")); + } }else{ /* Assign a bit from the bitmask to every term in the FROM clause. ** ** The N-th term of the FROM clause is assigned a bitmask of 1<mallocFailed ) goto whereBeginError; if( pWInfo->pOrderBy ){ + whereInterstageHeuristic(pWInfo); wherePathSolver(pWInfo, pWInfo->nRowOut+1); if( db->mallocFailed ) goto whereBeginError; } /* TUNING: Assume that a DISTINCT clause on a subquery reduces @@ -6355,14 +6564,14 @@ ** some C-compiler optimizers from in-lining the ** sqlite3WhereCodeOneLoopStart() procedure, and it is important to ** in-line sqlite3WhereCodeOneLoopStart() for performance reasons. */ notReady = ~(Bitmask)0; - if( pWInfo->nLevel>=2 - && pResultSet!=0 /* these two combine to guarantee */ - && 0==(wctrlFlags & WHERE_AGG_DISTINCT) /* condition (1) above */ - && OptimizationEnabled(db, SQLITE_OmitNoopJoin) + if( pWInfo->nLevel>=2 /* Must be a join, or this opt8n is pointless */ + && pResultSet!=0 /* Condition (1) */ + && 0==(wctrlFlags & (WHERE_AGG_DISTINCT|WHERE_KEEP_ALL_JOINS)) /* (1),(6) */ + && OptimizationEnabled(db, SQLITE_OmitNoopJoin) /* (7) */ ){ notReady = whereOmitNoopJoin(pWInfo, notReady); nTabList = pWInfo->nLevel; assert( nTabList>0 ); } @@ -6844,11 +7053,19 @@ if( pLevel->iLeftJoin ){ int ws = pLoop->wsFlags; addr = sqlite3VdbeAddOp1(v, OP_IfPos, pLevel->iLeftJoin); VdbeCoverage(v); assert( (ws & WHERE_IDX_ONLY)==0 || (ws & WHERE_INDEXED)!=0 ); if( (ws & WHERE_IDX_ONLY)==0 ){ - assert( pLevel->iTabCur==pTabList->a[pLevel->iFrom].iCursor ); + SrcItem *pSrc = &pTabList->a[pLevel->iFrom]; + assert( pLevel->iTabCur==pSrc->iCursor ); + if( pSrc->fg.viaCoroutine ){ + int m, n; + n = pSrc->regResult; + assert( pSrc->pTab!=0 ); + m = pSrc->pTab->nCol; + sqlite3VdbeAddOp3(v, OP_Null, 0, n, n+m-1); + } sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iTabCur); } if( (ws & WHERE_INDEXED) || ((ws & WHERE_MULTI_OR) && pLevel->u.pCoveringIdx) ){ @@ -6894,10 +7111,11 @@ ** the co-routine into OP_Copy of result contained in a register. ** OP_Rowid becomes OP_Null. */ if( pTabItem->fg.viaCoroutine ){ testcase( pParse->db->mallocFailed ); + assert( pTabItem->regResult>=0 ); translateColumnToCopy(pParse, pLevel->addrBody, pLevel->iTabCur, pTabItem->regResult, 0); continue; } Index: src/wherecode.c ================================================================== --- src/wherecode.c +++ src/wherecode.c @@ -1334,10 +1334,31 @@ } pLevel->regFilter = 0; pLevel->addrBrk = 0; } } + +/* +** Loop pLoop is a WHERE_INDEXED level that uses at least one IN(...) +** operator. Return true if level pLoop is guaranteed to visit only one +** row for each key generated for the index. +*/ +static int whereLoopIsOneRow(WhereLoop *pLoop){ + if( pLoop->u.btree.pIndex->onError + && pLoop->nSkip==0 + && pLoop->u.btree.nEq==pLoop->u.btree.pIndex->nKeyCol + ){ + int ii; + for(ii=0; iiu.btree.nEq; ii++){ + if( pLoop->aLTerm[ii]->eOperator & (WO_IS|WO_ISNULL) ){ + return 0; + } + } + return 1; + } + return 0; +} /* ** Generate code for the start of the iLevel-th loop in the WHERE clause ** implementation described by pWInfo. */ @@ -1413,11 +1434,11 @@ || pLevel->iFrom>0 || (pTabItem[0].fg.jointype & JT_LEFT)==0 ); if( pLevel->iFrom>0 && (pTabItem[0].fg.jointype & JT_LEFT)!=0 ){ pLevel->iLeftJoin = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Integer, 0, pLevel->iLeftJoin); - VdbeComment((v, "init LEFT JOIN no-match flag")); + 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--){ @@ -2082,11 +2103,13 @@ ** a LEFT JOIN: */ assert( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN))==0 ); } /* Record the instruction used to terminate the loop. */ - if( pLoop->wsFlags & WHERE_ONEROW ){ + if( (pLoop->wsFlags & WHERE_ONEROW) + || (pLevel->u.in.nIn && regBignull==0 && whereLoopIsOneRow(pLoop)) + ){ pLevel->op = OP_Noop; }else if( bRev ){ pLevel->op = OP_Prev; }else{ pLevel->op = OP_Next; @@ -2472,10 +2495,16 @@ ** iLoop==2: Code remaining expressions that do not contain correlated ** sub-queries. ** iLoop==3: Code all remaining expressions. ** ** An effort is made to skip unnecessary iterations of the loop. + ** + ** This optimization of causing simple query restrictions to occur before + ** more complex one is call the "push-down" optimization in MySQL. Here + ** in SQLite, the name is "MySQL push-down", since there is also another + ** totally unrelated optimization called "WHERE-clause push-down". + ** Sometimes the qualifier is omitted, resulting in an ambiguity, so beware. */ iLoop = (pIdx ? 1 : 2); do{ int iNext = 0; /* Next value for iLoop */ for(pTerm=pWC->a, j=pWC->nTerm; j>0; j--, pTerm++){ @@ -2722,11 +2751,20 @@ ExplainQueryPlan((pParse, 1, "RIGHT-JOIN %s", pTabItem->pTab->zName)); sqlite3VdbeNoJumpsOutsideSubrtn(v, pRJ->addrSubrtn, pRJ->endSubrtn, pRJ->regReturn); for(k=0; ka[k].pWLoop->iTab == pWInfo->a[k].iFrom ); + pRight = &pWInfo->pTabList->a[pWInfo->a[k].iFrom]; mAll |= pWInfo->a[k].pWLoop->maskSelf; + if( pRight->fg.viaCoroutine ){ + sqlite3VdbeAddOp3( + v, OP_Null, 0, pRight->regResult, + pRight->regResult + pRight->pSelect->pEList->nExpr-1 + ); + } sqlite3VdbeAddOp1(v, OP_NullRow, pWInfo->a[k].iTabCur); iIdxCur = pWInfo->a[k].iIdxCur; if( iIdxCur ){ sqlite3VdbeAddOp1(v, OP_NullRow, iIdxCur); } Index: src/whereexpr.c ================================================================== --- src/whereexpr.c +++ src/whereexpr.c @@ -987,11 +987,11 @@ if( pIdx->aColExpr==0 ) continue; for(i=0; inKeyCol; i++){ if( pIdx->aiColumn[i]!=XN_EXPR ) continue; assert( pIdx->bHasExpr ); if( sqlite3ExprCompareSkip(pExpr,pIdx->aColExpr->a[i].pExpr,iCur)==0 - && !sqlite3ExprIsConstant(pIdx->aColExpr->a[i].pExpr) + && !sqlite3ExprIsConstant(0,pIdx->aColExpr->a[i].pExpr) ){ aiCurCol[0] = iCur; aiCurCol[1] = XN_EXPR; return 1; } @@ -1636,10 +1636,11 @@ ** will only be added if each of the child terms passes the ** (leftCursor==iCsr) test below. */ continue; } if( pWC->a[ii].leftCursor!=iCsr ) return; + if( pWC->a[ii].prereqRight!=0 ) return; } /* Check condition (5). Return early if it is not met. */ if( pOrderBy ){ for(ii=0; iinExpr; ii++){ @@ -1650,16 +1651,18 @@ } } /* All conditions are met. Add the terms to the where-clause object. */ assert( p->pLimit->op==TK_LIMIT ); - whereAddLimitExpr(pWC, p->iLimit, p->pLimit->pLeft, - iCsr, SQLITE_INDEX_CONSTRAINT_LIMIT); - if( p->iOffset>0 ){ + if( p->iOffset!=0 && (p->selFlags & SF_Compound)==0 ){ whereAddLimitExpr(pWC, p->iOffset, p->pLimit->pRight, iCsr, SQLITE_INDEX_CONSTRAINT_OFFSET); } + if( p->iOffset==0 || (p->selFlags & SF_Compound)==0 ){ + whereAddLimitExpr(pWC, p->iLimit, p->pLimit->pLeft, + iCsr, SQLITE_INDEX_CONSTRAINT_LIMIT); + } } } /* ** Initialize a preallocated WhereClause structure. Index: src/window.c ================================================================== --- src/window.c +++ src/window.c @@ -1162,11 +1162,11 @@ ** constant, change it to NULL. The fact that it is then a non-negative ** integer will be caught later. But it is important not to leave ** variable values in the expression tree. */ static Expr *sqlite3WindowOffsetExpr(Parse *pParse, Expr *pExpr){ - if( 0==sqlite3ExprIsConstant(pExpr) ){ + if( 0==sqlite3ExprIsConstant(0,pExpr) ){ if( IN_RENAME_OBJECT ) sqlite3RenameExprUnmap(pParse, pExpr); sqlite3ExprDelete(pParse->db, pExpr); pExpr = sqlite3ExprAlloc(pParse->db, TK_NULL, 0, 0); } return pExpr; Index: test/altertab2.test ================================================================== --- test/altertab2.test +++ test/altertab2.test @@ -358,6 +358,28 @@ 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}} + +reset_db + +do_execsql_test 9.0 { + CREATE TABLE t1(a,b,c,d); + CREATE TABLE t2(a,b,c,d,x); + + CREATE TRIGGER AFTER INSERT ON t2 BEGIN + + SELECT group_conct( + 123 ORDER BY ( + SELECT 1 FROM ( VALUES(a, 'b'), ('c') ) + )) + FROM t1; + + END; +} + +do_catchsql_test 9.1 { + ALTER TABLE t2 RENAME TO newname; +} {1 {error in trigger AFTER: all VALUES must have the same number of terms}} + finish_test Index: test/altertab3.test ================================================================== --- test/altertab3.test +++ test/altertab3.test @@ -733,7 +733,57 @@ SELECT z, y FROM ( SELECT "t3".* FROM "t3" ); END} } + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 30.0 { + CREATE TABLE t1(a, b); + CREATE VIEW v1 AS + SELECT ( VALUES(a), (b) ) FROM ( + SELECT a, b FROM t1 + ) + ; +} + +do_execsql_test 30.1 { + SELECT * FROM v1 +} + +do_execsql_test 30.1 { + ALTER TABLE t1 RENAME TO t2; +} +do_execsql_test 30.2 { + SELECT sql FROM sqlite_schema WHERE type='view' +} { + {CREATE VIEW v1 AS + SELECT ( VALUES(a), (b) ) FROM ( + SELECT a, b FROM "t2" + )} +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 31.0 { + CREATE TABLE t1(ii INTEGER PRIMARY KEY, tt INTEGER, rr REAL); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<50000 + ) + INSERT INTO t1 SELECT NULL, i, 5.0 FROM s; +} + +do_test 31.1 { + set pg [db one {PRAGMA page_count}] + execsql { + ALTER TABLE t1 DROP COLUMN tt; + } + set pg2 [db one {PRAGMA page_count}] + expr $pg==$pg2 +} {1} + +do_execsql_test 31.2 { + SELECT rr FROM t1 LIMIT 1 +} {5.0} finish_test Index: test/avfs.test ================================================================== --- test/avfs.test +++ test/avfs.test @@ -6,10 +6,11 @@ # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # This file implements tests for the appendvfs extension. # # Tests performed: # avfs-1.0. Test that an appendvfs DB can be added to an empty (ZLF) file. Index: test/bestindex8.test ================================================================== --- test/bestindex8.test +++ test/bestindex8.test @@ -156,11 +156,11 @@ do_test 2.2 { set ::lFilterArgs [list] execsql { SELECT * FROM vt1 LIMIT 5 OFFSET 50 } set ::lFilterArgs -} {{5 50}} +} {{50 5}} do_test 2.3 { set ::lFilterArgs [list] execsql { SELECT * FROM vt1 ORDER BY a, b LIMIT 1 OFFSET 1 } set ::lFilterArgs ADDED test/bestindexC.test Index: test/bestindexC.test ================================================================== --- /dev/null +++ test/bestindexC.test @@ -0,0 +1,213 @@ +# 2024-04-26 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix bestindexC + +ifcapable !vtab { + finish_test + return +} + +register_tcl_module db + +proc vtab_command {lVal method args} { + switch -- $method { + xConnect { + return "CREATE TABLE t1(a)" + } + + xBestIndex { + set hdl [lindex $args 0] + set clist [$hdl constraints] + set orderby [$hdl orderby] + + set idxstr [list] + set res [list] + + set idx 0 + foreach c $clist { + array set a $c + if {$a(usable)==0} continue + if {$a(op)=="limit" && ![info exists ::do_not_use_limit]} { + lappend idxstr limit + lappend res omit $idx + } + if {$a(op)=="offset" && ![info exists ::do_not_use_offset]} { + lappend idxstr offset + lappend res omit $idx + } + incr idx + } + + return "cost 1000000 rows 1000000 idxnum 0 idxstr {$idxstr} $res" + } + + xFilter { + set idxstr [lindex $args 1] + set LIMIT "" + foreach a $idxstr b [lindex $args 2] { + set x($a) $b + } + + if {![info exists x(limit)]} { set x(limit) -1 } + if {![info exists x(offset)]} { set x(offset) -1 } + set LIMIT " LIMIT $x(limit) OFFSET $x(offset)" + + set idx 1 + foreach v $lVal { + lappend lRow "($idx, '$v')" + incr idx + } + + return [list sql " + SELECT * FROM ( VALUES [join $lRow ,]) $LIMIT + "] + } + } + + return {} +} + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE x1 USING tcl(vtab_command "a b c d e f"); + CREATE VIRTUAL TABLE x2 USING tcl(vtab_command "A B C D E F a b"); +} {} + +do_execsql_test 1.1 { + CREATE TEMP TABLE t_unionall AS + SELECT * FROM x1 UNION ALL SELECT * FROM x2; + + CREATE TEMP TABLE t_intersect AS + SELECT * FROM x1 INTERSECT SELECT * FROM x2; + + CREATE TEMP TABLE t_union AS + SELECT * FROM x1 UNION SELECT * FROM x2; + + CREATE TEMP TABLE t_except AS + SELECT * FROM x1 EXCEPT SELECT * FROM x2; +} + +foreach {tn limit} { + 1 "LIMIT 8" + 2 "LIMIT 4" + 3 "LIMIT 4 OFFSET 2" + 4 "LIMIT 8 OFFSET 4" +} { + + foreach {op tbl} { + "UNION ALL" t_unionall + "UNION" t_union + "INTERSECT" t_intersect + "EXCEPT" t_except + } { + + set expect [execsql "SELECT * FROM $tbl $limit"] + do_execsql_test 1.2.$tbl.$tn "SELECT * FROM ( + SELECT * FROM x1 $op SELECT * FROM x2 + ) $limit" $expect + + } + +} + +#------------------------------------------------------------------------- +reset_db +register_tcl_module db + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE x1 USING tcl(vtab_command "a b c d e f"); + CREATE VIRTUAL TABLE x2 USING tcl(vtab_command "a b e f"); +} {} + +do_execsql_test 2.1 { + SELECT * FROM x1 + EXCEPT + SELECT * FROM x2 + LIMIT 3 +} {c d} + +#------------------------------------------------------------------------- +reset_db +register_tcl_module db +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE y1 USING tcl(vtab_command "1 2 3 4 5 6 7 8 9 10"); +} {} + +do_execsql_test 3.1 { + SELECT * FROM y1 WHERE a = COALESCE('8', a) LIMIT 3 +} {8} + +do_execsql_test 3.2 { + SELECT * FROM y1 WHERE a = '2' LIMIT 3 +} {2} + +load_static_extension db series +do_execsql_test 3.3 { + SELECT * FROM generate_series(1, 5) WHERE value = (value & 14) LIMIT 3 +} {2 4} + +do_execsql_test 3.4 { + SELECT value FROM generate_series(1,10) WHERE value>2 LIMIT 4 OFFSET 1; +} {4 5 6 7} + +set ::do_not_use_limit 1 +do_execsql_test 3.5 { + SELECT * FROM y1 LIMIT 5 OFFSET 3 +} {4 5 6 7 8} +unset ::do_not_use_limit +set ::do_not_use_offset 1 +do_execsql_test 3.6 { + SELECT * FROM y1 LIMIT 5 OFFSET 3 +} {4 5 6 7 8} +unset ::do_not_use_offset + +#------------------------------------------------------------------------- +reset_db +proc vtab_command {lVal method args} { + switch -- $method { + xConnect { error "not happy!" } + } + + return {} +} + +register_tcl_module db +do_catchsql_test 4.0 { + CREATE VIRTUAL TABLE y1 USING tcl(vtab_command 1); +} {1 {not happy!}} +do_test 4.1 { + sqlite3_errcode db +} SQLITE_ERROR + +proc vtab_command {lVal method args} { + switch -- $method { + xConnect { + return $lVal + } + } + return {} +} + +do_catchsql_test 4.2 { + CREATE VIRTUAL TABLE y1 USING tcl(vtab_command "PRAGMA page_size=1024"); +} {1 {declare_vtab: syntax error}} +do_catchsql_test 4.3 { + CREATE VIRTUAL TABLE y1 USING tcl(vtab_command "CREATE TABLE x1("); +} {1 {declare_vtab: incomplete input}} +do_catchsql_test 4.4 { + CREATE VIRTUAL TABLE y1 USING tcl(vtab_command "CREATE TABLE x1(insert)"); +} {1 {declare_vtab: near "insert": syntax error}} + +finish_test Index: test/capi3.test ================================================================== --- test/capi3.test +++ test/capi3.test @@ -179,11 +179,11 @@ sqlite3_errmsg $db2 } {unable to open database file} do_test capi3-3.5 { list [sqlite3_system_errno $db2] [sqlite3_close $db2] } [list $::capi3_errno SQLITE_OK] -if {[clang_sanitize_address]==0} { +if {[clang_sanitize_address]==0 && 0} { do_test capi3-3.6.1-misuse { sqlite3_close $db2 } {SQLITE_MISUSE} do_test capi3-3.6.2-misuse { sqlite3_errmsg $db2 ADDED test/cksumvfs.test Index: test/cksumvfs.test ================================================================== --- /dev/null +++ test/cksumvfs.test @@ -0,0 +1,33 @@ +# 2024 March 19 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix cksumvfs + +sqlite3_register_cksumvfs +db close +sqlite3 db test.db +file_control_reservebytes db 8 + +set text [db one "SELECT hex(randomblob(5000))"] + +do_execsql_test 1.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + INSERT INTO t1 VALUES(1, $text); +} + +do_execsql_test 1.1 { + SELECT * FROM t1; +} [list 1 $text] + +finish_test Index: test/corruptL.test ================================================================== --- test/corruptL.test +++ test/corruptL.test @@ -1502,7 +1502,92 @@ db close sqlite3 db test.db do_catchsql_test 19.4 { PRAGMA integrity_check; } {1 {database disk image is malformed}} + +#------------------------------------------------------------------------- +reset_db +do_test 18.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 20480 pagesize 4096 filename crash-a4150b729051e4.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 05 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 04 00 00 00 04 ................ +| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ +| 64: 00 00 00 00 00 00 00 00 00 00 ff f0 00 00 00 00 ................ +| 96: 00 00 00 00 0d 00 00 00 04 0e e5 00 0f c2 0f 75 ...............u +| 112: 0f 19 0e e5 00 00 00 00 00 00 00 01 00 00 00 00 ................ +| 3808: 00 00 00 00 00 32 04 06 17 17 11 01 4b 69 6e 64 .....2......Kind +| 3824: 65 78 74 31 61 62 63 74 31 05 43 52 45 41 54 45 ext1abct1.CREATE +| 3840: 20 49 4e 44 45 58 20 74 31 61 62 63 20 4f 4e 20 INDEX t1abc ON +| 3856: 74 31 28 61 2c 62 2c 63 29 5a 03 06 17 25 25 01 t1(a,b,c)Z...%%. +| 3872: 79 74 61 62 6c 65 73 71 6c 69 74 65 5f 73 74 61 ytablesqlite_sta +| 3888: 74 34 73 71 6c 69 74 65 5f 73 74 61 74 34 04 43 t4sqlite_stat4.C +| 3904: 52 45 41 54 45 20 54 41 42 4c 45 20 73 71 6c 69 REATE TABLE sqli +| 3920: 74 65 5f 73 74 61 74 34 28 74 62 6c 2c 69 64 78 te_stat4(tbl,idx +| 3936: 2c 6e 65 71 2c 6e 6c 74 2c 6e 64 6c 74 2c 73 61 ,neq,nlt,ndlt,sa +| 3952: 6d 70 6c 65 29 4b 02 06 17 25 25 01 5b 74 61 62 mple)K...%%.[tab +| 3968: 6c 65 73 71 6c 69 74 65 5f 73 74 61 74 31 73 71 lesqlite_stat1sq +| 3984: 6c 69 74 65 5f 73 74 61 74 31 03 43 52 45 41 54 lite_stat1.CREAT +| 4000: 45 20 54 41 42 4c 45 20 73 71 6c 69 74 65 5f 73 E TABLE sqlite_s +| 4016: 74 61 74 31 28 74 62 6c 2c 69 64 78 2c 73 74 61 tat1(tbl,idx,sta +| 4032: 74 29 3c 01 06 17 11 11 01 65 74 61 62 6c 65 74 t)<......etablet +| 4048: 31 74 31 02 43 52 45 41 54 45 20 54 41 42 4c 45 1t1.CREATE TABLE +| 4064: 20 74 31 28 61 20 54 45 58 54 2c 20 62 20 49 4e t1(a TEXT, b IN +| 4080: 54 2c 20 63 20 49 4e 54 2c 20 64 20 49 4e 54 29 T, c INT, d INT) +| page 2 offset 4096 +| 0: 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 4000: 0b 07 05 13 01 01 01 62 63 64 64 06 0b 0c 06 05 .......bcdd..... +| 4016: 13 02 01 01 64 65 66 01 59 09 0a 0c 05 05 13 03 ....def.Y....... +| 4032: 01 01 64 65 66 02 6f 08 09 0c 04 05 13 02 01 01 ..def.o......... +| 4048: 61 62 63 01 59 07 08 0c 03 05 13 02 01 01 87 62 abc.Y..........b +| 4064: 63 00 ea 06 07 0c 02 05 13 02 01 01 61 62 63 00 c...........abc. +| 4080: ea 06 06 0b 01 05 13 01 01 01 61 62 63 7b 04 04 ..........abc... +| page 3 offset 8192 +| 0: 0d 00 00 00 01 0f e0 00 0f e1 00 00 00 00 00 00 ................ +| 4064: 00 1d 01 04 11 17 31 74 31 74 31 61 62 63 31 30 ......1t1t1abc10 +| 4080: 30 30 30 20 35 30 30 30 20 32 30 30 30 20 31 30 000 5000 2000 10 +| page 4 offset 12288 +| 0: 0d 00 00 00 07 0e ac 00 0f d1 0f a0 0f 6f 0f 3e .............o.> +| 16: 0f 0e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +| 3744: 00 00 00 00 00 00 00 00 00 00 00 00 2f 07 07 11 ............/... +| 3760: 17 1b 1b 1b 24 74 31 74 31 61 62 63 32 20 31 20 ....$t1t1abc2 1 +| 3776: 31 20 31 35 20 36 20 36 20 36 32 20 35 20 36 20 1 15 6 6 62 5 6 +| 3792: 36 05 13 02 01 01 64 65 66 02 37 08 05 2f 06 07 6.....def.7../.. +| 3808: 11 17 1b 1b 1b 24 74 41 74 31 61 62 63 32 20 31 .....$tAt1abc2 1 +| 3824: 20 31 20 31 35 20 35 20 55 20 35 32 20 34 20 35 1 15 5 U 52 4 5 +| 3840: 20 35 05 13 02 01 01 64 65 66 01 59 09 06 2e 05 5.....def.Y.... +| 3856: 07 11 17 1b 1b 1b 22 74 31 74 31 61 62 63 31 20 .......t1t1abc1 +| 3872: 31 20 31 20 31 34 20 34 20 34 20 34 31 20 33 20 1 1 14 4 4 41 3 +| 3888: 34 20 34 08 b3 cd f0 f1 62 63 64 64 06 07 2f 05 4 4.....bcdd../. +| 3904: 07 11 17 1b 1b 1b 24 74 37 74 31 61 62 63 34 20 ......$t7t1abc4 +| 3920: 31 20 31 20 31 30 20 33 20 33 20 33 30 20 32 20 1 1 10 3 3 30 2 +| 3936: 33 20 33 05 13 02 01 01 61 62 63 01 59 07 04 2f 3 3.....abc.Y../ +| 3952: 03 07 11 17 1b 1b 1b 24 74 31 74 31 61 62 63 34 .......$t1t1abc4 +| 3968: 20 32 20 31 20 31 30 20 31 20 32 20 32 30 20 31 2 1 10 1 2 20 1 +| 3984: 20 32 20 32 05 13 02 01 01 61 62 63 00 ea 06 03 2 2.....abc.... +| 4000: 2f 02 07 11 17 1b 1b 1b 24 74 31 74 31 61 62 63 /.......$t1t1abc +| 4016: 34 20 32 20 31 20 31 30 20 31 20 31 20 31 30 20 4 2 1 10 1 1 10 +| 4032: 31 20 31 20 31 05 13 02 01 01 61 62 63 00 ea 05 1 1 1.....abc... +| 4048: 02 2d 01 07 11 17 1b 1b 1b 20 74 31 74 31 61 62 .-....... t1t1ab +| 4064: 63 34 20 31 20 31 20 31 30 20 30 20 30 1f 30 30 c4 1 1 10 0 0.00 +| 4080: 20 30 20 30 20 30 05 13 01 01 09 61 62 63 7b 04 0 0 0.....abc.. +| page 5 offset 16384 +| 0: 0a 00 00 00 07 0f a8 00 0f f5 00 00 00 00 00 00 ................ +| 4000: 00 00 00 00 00 00 00 00 0c 05 13 02 01 01 64 65 ..............de +| 4016: 66 02 37 08 05 0c 05 13 02 01 01 64 65 66 01 59 f.7........def.Y +| 4032: 09 06 0b 05 12 01 01 01 62 63 64 64 06 07 0c 05 ........bcdd.... +| 4048: 13 02 01 01 61 62 63 01 59 07 01 2c 05 13 02 01 ....abc.Y..,.... +| 4064: 01 61 62 63 00 ea 06 03 0c 05 13 02 01 01 61 62 .abc..........ab +| 4080: 63 00 ea 05 00 00 00 00 00 00 00 00 00 00 00 00 c............... +| end crash-a4150b729051e4.db +}]} {} + +do_catchsql_test 18.1 { + SELECT a FROM t1 WHERE b GLOB b AND b GLOB '0^x]␅6␚xz]'; +} {1 {database disk image is malformed}} finish_test Index: test/cost.test ================================================================== --- test/cost.test +++ test/cost.test @@ -101,11 +101,11 @@ do_eqp_test 5.2 { SELECT * FROM t2 ORDER BY x, y; } { QUERY PLAN |--SCAN t2 USING INDEX t2i1 - `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY + `--USE TEMP B-TREE FOR LAST TERM OF ORDER BY } do_eqp_test 5.3 { SELECT * FROM t2 WHERE x BETWEEN ? AND ? ORDER BY rowid; } { Index: test/default.test ================================================================== --- test/default.test +++ test/default.test @@ -133,8 +133,12 @@ # reset_db do_catchsql_test default-5.1 { CREATE TABLE t1 (a,b DEFAULT(random() NOTNULL IN (RAISE(IGNORE),2,3))); INSERT INTO t1(a) VALUES(1); -} {1 {RAISE() may only be used within a trigger-program}} +} {1 {default value of column [b] is not constant}} +do_catchsql_test default-5.2 { + CREATE TABLE Table0 (Col0 DEFAULT (RAISE(IGNORE) ) ) ; + INSERT INTO Table0 DEFAULT VALUES ; +} {1 {default value of column [Col0] is not constant}} finish_test Index: test/eqp.test ================================================================== --- test/eqp.test +++ test/eqp.test @@ -411,11 +411,11 @@ |--LEFT | |--SCAN t1 | `--USE TEMP B-TREE FOR ORDER BY `--RIGHT |--SCAN t2 USING INDEX t2i1 - `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY + `--USE TEMP B-TREE FOR LAST 2 TERMS OF ORDER BY } do_eqp_test 4.2.4 { SELECT * FROM t1 INTERSECT SELECT * FROM t2 ORDER BY 1 } { QUERY PLAN @@ -423,11 +423,11 @@ |--LEFT | |--SCAN t1 | `--USE TEMP B-TREE FOR ORDER BY `--RIGHT |--SCAN t2 USING INDEX t2i1 - `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY + `--USE TEMP B-TREE FOR LAST 2 TERMS OF ORDER BY } do_eqp_test 4.2.5 { SELECT * FROM t1 EXCEPT SELECT * FROM t2 ORDER BY 1 } { QUERY PLAN @@ -435,11 +435,11 @@ |--LEFT | |--SCAN t1 | `--USE TEMP B-TREE FOR ORDER BY `--RIGHT |--SCAN t2 USING INDEX t2i1 - `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY + `--USE TEMP B-TREE FOR LAST 2 TERMS OF ORDER BY } do_eqp_test 4.3.1 { SELECT x FROM t1 UNION SELECT x FROM t2 } { ADDED test/eqp2.test Index: test/eqp2.test ================================================================== --- /dev/null +++ test/eqp2.test @@ -0,0 +1,49 @@ +# 2024 March 20 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +set testprefix eqp2 + +do_execsql_test 1.0 { + CREATE TABLE t1(a, b, c, d); + CREATE INDEX i1 ON t1(a, b, c); +} + +do_eqp_test 1.1 { + SELECT * FROM t1 ORDER BY a, b, c +} { + QUERY PLAN + `--SCAN t1 USING INDEX i1 +} + + +do_eqp_test 1.2 { + SELECT * FROM t1 ORDER BY a, b, +c +} { + QUERY PLAN + |--SCAN t1 USING INDEX i1 + `--USE TEMP B-TREE FOR LAST TERM OF ORDER BY +} + +do_eqp_test 1.3 { + SELECT * FROM t1 ORDER BY a, +b, +c +} { + QUERY PLAN + |--SCAN t1 USING INDEX i1 + `--USE TEMP B-TREE FOR LAST 2 TERMS OF ORDER BY +} + +finish_test + + ADDED test/exprfault2.test Index: test/exprfault2.test ================================================================== --- /dev/null +++ test/exprfault2.test @@ -0,0 +1,35 @@ +# 2024-05-11 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix exprfault2 + +do_execsql_test 1.0 { + CREATE TABLE t1(a,b,c,d,f,PRIMARY KEY(b,b)); + CREATE TABLE t2(x INT PRIMARY KEY, y, z); + CREATE TABLE t3(a,b,c,d,e,PRIMARY KEY(a,b))WITHOUT ROWID; +} +faultsim_save_and_close + + +do_faultsim_test 1 -faults oom-t* -prep { + faultsim_restore_and_reopen +} -body { + execsql { + UPDATE t3 SET (d,d,d,d, a )=(SELECT EXISTS(SELECT 1 NOT IN(SELECT EXISTS(SELECT 1 IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT y FROM t2 UNION SELECT (SELECT max( 1 IN(SELECT x NOT IN(SELECT 1 NOT IN(SELECT EXISTS(SELECT 1 IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT y FROM t2 UNION SELECT (SELECT max( (SELECT x NOT IN(SELECT 1 NOT IN(SELECT EXISTS(SELECT 1 IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT y FROM t2 UNION SELECT (SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT y FROM t2 UNION SELECT x ORDER BY 1)))INTERSECT SELECT (SELECT 1 FROM t2 UNION SELECT d ORDER BY 1) ORDER BY 1) ORDERa)| (SELECT 1 x ORDER BY 1)))INTERSECT SELECT EXISTS(SELECT 1 FROM t2 UNION SELECT d ORDER BY 1) ORDER BY 1) a)| (SELECT 1 IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(ORDER BY sum((SELECT DISTINCT y FROM t2 UNION SELECT x ORDER BY 1)))INTERSECT SELECT EXISTS(SELECT 1 FROM t2 UNION SELECT x ORDER BY 1) ORDER BY 1) z)|9 AS blob) IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT DISTINCT y FROM t2 UNION SELECT x ORDER BY 1)))EXCEPT SELECT EXISTS(SELECT 1 FROM t2 UNION SELECT d ORDER BY 1) ORDER BY 1) z) ORDER BY 1) IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT DISTINCT y FROM t2 UNION SELECT x ORDER BY 1)))INTERSECT SELECT (SELECT 1 FROM t2 UNION SELECT d ORDER BY 1) ORDER BY 1) ORDERa)| (SELECT 1 x ORDER BY 1)))EXCEPT SELECT EXISTS(SELECT 1 FROM t2 UNION SELECT d ORDER BY 1) ORDER BY 1) a)| (SELECT 1 IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT DISTINCT y FROM t2 UNION SELECT x ORDER BY 1)))INTERSECT SELECT EXISTS(SELECT 1 FROM t2 UNION SELECT x ORDER BY 1) ORDER BY 1) z)|9 AS blob) IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT DISTINCT y FROM t2 UNION SELECT x ORDER BY 1)))EXCEPT SELECT EXISTS(SELECT 1 FROM t2 UNION SELECT d ORDER BY 1) ORDER BY 1) z) ORDER BY 1)) OVER(PARTITION BY sum((SELECT y FROM t2 UNION SELECT x ORDER BY 1)))INTERSECT SELECT EXISTS(SELECT 1 FROM t2 UNION SELECT d ORDER BY 1) ORDER BY 1) ORDERa)| (SELECT 1 x ORDER BY 1)))INTERSECT SELECT EXISTS(SELECT 5 FROM t2 UNION SELECT d ORDER BY 1) ORDER BY 1) ORDERa)| (SELECT 1 IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(ORDER BY sum((SELECT DISTINCT y FROM t2 UNION SELECT x ORDER BY 1)))INTERSECT SELECT EXISTS(SELECT 1 FROM t2 UNION SELECT x ORDER BY 1) ORDER BY 1) z)| 1 AS blob) IN(SELECT max( 1 IN(SELECT x ORDER BY 1)) OVER(PARTITION BY sum((SELECT DISTINCT y FROM t2 UNION SELECT x ORDER BY 1)))INTERSECT SELECT EXISTS(SELECT 1 FROM t2 UNION SELECT d ORDER BY 1) ORDER BY 1) z)| (SELECT 1 IN(SELECT max( 1 IN(SELECT c ORDER BY 1)) OVER(PARTITION BY sum((SELECT y FROM t2 UNION SELECT x ORDER BY 1)))INTERSECT SELECT (SELECT 1 FROM t2 UNION SELECT x ORDER BY 1) ORDER BY 1) e)|9 AS blob) FROM t2 WHERE aone} + +#------------------------------------------------------------------------- + +do_execsql_test 3.0 { +CREATE VIRTUAL TABLE f USING fts3(a,b); +INSERT INTO f VALUES (101,x'056522650565056505650d051e056505650565286505650565056505056505650565056505650565056505650565056505650565056505656505650565056505650d05650505656505650565ef65056505844c746e65650565056505650565056505650565056505650565058405800565056505650565056505651e650565056505650565056505650d056505056565056505650565056505840580056505650565056f05650565056505650565056505650565050565056505640565056505650565056505651e05650565056505650565056505650505656565056505650565056505651e0565056505650565056505650565052265056505650569056505650565056505650565056505650565056505650500406505650565056505650565056505000101e5c501014b010101c501c5c501010101f5010201010101014101017373737373737373737373737373737373737373737373737373737330737373737373737373737373737365056505650d051e05650565056528056505650d05650505656505650565650565056505650565056505e505650565056505656505650565056505650d05650505656505650565ef65056505844c746e65650565056505650565056505650565056505650565058405800565056505650565056505651e650565056505650565056505650d056505056565056505650565056505840580056505650565286505c705650565050565059494949494949494949494949494949494949494949494949494949494949494949494650565056505650565056505650565056505650565056505656505650565056505650d05650505656505650565ef650565058405056505650565056505650565056505650565056505650565058405800565056505650565056505651e650565056505650565056505650d056505056565056505650565056505840580056505650565056505650565056505650505650565056505650565056505650565056500000000000000000000000000000000000000000000000000000000000000000565056505656505650565056505650d056500000000000000000000000000000000000000000000000000000000000000000100000000000000ed0000000000ffffffffffffffffffffff0007ffffff0001c5c50001c5c50001c5c50001c5c50001c5c50001c5c50001e5c50001c5c50001c5c50001c5c50001c5c50001c5c5000100000014720000000000000016dac5c50001c5c50001c5c50001c5c50001c5c50001c5c50d0505656505650565ef650565058405056505650565056505650565056505650565056505650565058405800465056505650565056505651e650565056505650565056505650d05650505656505650565050565650584058005650565056505650565056505650565056522650565056505650d051e056505650561286505c70565056505056505650565056505650565056505650565056505650565056505656505650565056505650d05650505656505650565ef650565058405056505650565056505650565056505650565056505650565058405800565056505650565056505651e650565056505650565056505650d056505056565056505650565056505840580056505650565056505650565056505650565226505737373737373737373737373737373737373737373737373737373737373737373737373737373737373737373737373733a73737373737373737373737373737373737373737373737373737373737373737373737373737373737c7373737365056505650d051e05650565056528650565056505650505650565056505650565056505650565056505e505650565056505656505650565056505650d05650505656505650565ef65056505'); +} + +do_execsql_test 3.1 { + SELECT length(snippet(f)) FROM f WHERE b MATCH x'0565056505650565056505650565056505650565058405800565056505650565056505651e650565056505650565056505650d056505056565056505650565056505840580056505650565056505650565056505650565056505650565050565056505640565056505650565056505651e05650565056522650565056505650d051e056505650565286505650565056505056505650565056505650565056505650565056505650565056505656505650565056505650d05650505656505650565ef65056505844c746e65650565056505650565056505650565056505650565058405800565056505650565056505651e650565056505650565056505650d056505056565056505650565056505840580056505650565056f05650565056505650565056505650565050565056505640565056505650565056505651e05650565056505650565056505650505656565056505650565056505651e0565056505650565056505650565052265056505650569056505650565056505650565056505650565056505650500406505650565056505650565056505000101e5c501014b010101c501c5c501010101f50102010101010141010141010001017bf15905000000000017'; +} {192} set sqlite_fts3_enable_parentheses 0 finish_test Index: test/func.test ================================================================== --- test/func.test +++ test/func.test @@ -1558,7 +1558,28 @@ # do_execsql_test func-38.100 { WITH t1(x) AS (VALUES(9e+999)) SELECT sum(x), avg(x), total(x) FROM t1; WITH t1(x) AS (VALUES(-9e+999)) SELECT sum(x), avg(x), total(x) FROM t1; } {Inf Inf Inf -Inf -Inf -Inf} + +# 2024-03-21 https://sqlite.org/forum/forumpost/23b8688ef4 +# Another problem with Kahan-Babushka-Neumaier summation and +# infinities. +# +do_execsql_test func-39.101 { + WITH RECURSIVE c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<1) + SELECT sum(1.7976931348623157e308), + avg(1.7976931348623157e308), + total(1.7976931348623157e308) + FROM c; +} {1.79769313486232e+308 1.79769313486232e+308 1.79769313486232e+308} +for {set i 2} {$i<10} {incr i} { + do_execsql_test func-39.[expr {10*$i+100}] { + WITH RECURSIVE c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<$i) + SELECT sum(1.7976931348623157e308), + avg(1.7976931348623157e308), + total(1.7976931348623157e308) + FROM c; + } {Inf Inf Inf} +} finish_test Index: test/func2.test ================================================================== --- test/func2.test +++ test/func2.test @@ -505,7 +505,30 @@ } "12" do_test func2-3.9.2 { set blob [execsql "SELECT SUBSTR(x'1234', 2, -2)"] bin_to_hex [lindex $blob 0] } "12" + +#------------------------------------------------------------------------- +# At one point this was extremely slow to compile. +# +do_test func2-3.10 { + set tm [time { + execsql { + SELECT '' IN (zerobloB(zerobloB(zerobloB(zerobloB(zerobloB( + zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB( + zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB( + zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB( + zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB( + zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB( + zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB( + zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB( + zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(zerobloB(1) + ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) + } + }] + + set tm [lindex $tm 0] + expr $tm<2000000 +} {1} finish_test Index: test/func4.test ================================================================== --- test/func4.test +++ test/func4.test @@ -1,6 +1,6 @@ -# 2013 March 10 +# 2023-03-10 # # 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. @@ -7,11 +7,14 @@ # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** # This file implements regression tests for SQLite library. The focus of -# this file is testing the tointeger() and toreal() functions. +# this file is testing the tointeger() and toreal() functions that are +# part of the "totype.c" extension. This file does not test the core +# SQLite library. Failures of tests in this file are related to the +# ext/misc/totype.c extension. # # Several of the toreal() tests are disabled on platforms where floating # point precision is not high enough to represent their constant integer # expression arguments as double precision floating point values. # @@ -21,10 +24,24 @@ set tcl_precision 0 load_static_extension db totype set highPrecision(1) [expr \ {[db eval {SELECT tointeger(9223372036854775807 + 1);}] eq {{}}}] +set highPrecision(2) [expr \ + {[db eval {SELECT toreal(-9223372036854775808 + 1);}] eq {{}}}] + +# highPrecision(3) is only known to be false on i586 with gcc-13 and -O2. +# It is true on the exact same platform with -O0. Both results seem +# reasonable, so we'll just very the expectation accordingly. +# +set highPrecision(3) [expr \ + {[db eval {SELECT toreal(9007199254740992 + 1);}] eq {{}}}] + +if {!$highPrecision(1) || !$highPrecision(2) || !$highPrecision(3)} { + puts "NOTICE: use_long_double: [use_long_double] \ + highPrecision: $highPrecision(1) $highPrecision(2) $highPrecision(3)" +} do_execsql_test func4-1.1 { SELECT tointeger(NULL); } {{}} do_execsql_test func4-1.2 { @@ -193,12 +210,10 @@ do_execsql_test func4-1.55 { SELECT tointeger(18446744073709551616 + 1); } {{}} ifcapable floatingpoint { - set highPrecision(2) [expr \ - {[db eval {SELECT toreal(-9223372036854775808 + 1);}] eq {{}}}] do_execsql_test func4-2.1 { SELECT toreal(NULL); } {{}} do_execsql_test func4-2.2 { @@ -339,14 +354,18 @@ SELECT toreal(9007199254740992 - 1); } {9007199254740991.0} do_execsql_test func4-2.45 { SELECT toreal(9007199254740992); } {9007199254740992.0} - if {$highPrecision(2)} { + if {$highPrecision(3)} { + do_execsql_test func4-2.46 { + SELECT toreal(9007199254740992 + 1); + } {{}} + } else { do_execsql_test func4-2.46 { SELECT toreal(9007199254740992 + 1); - } {{}} + } {9007199254740992.0} } do_execsql_test func4-2.47 { SELECT toreal(9007199254740992 + 2); } {9007199254740994.0} do_execsql_test func4-2.48 { @@ -624,14 +643,18 @@ SELECT tointeger(toreal(9007199254740992 - 1)); } {9007199254740991} do_execsql_test func4-5.22 { SELECT tointeger(toreal(9007199254740992)); } {9007199254740992} - if {$highPrecision(2)} { + if {$highPrecision(3)} { do_execsql_test func4-5.23 { SELECT tointeger(toreal(9007199254740992 + 1)); } {{}} + } else { + do_execsql_test func4-5.23 { + SELECT tointeger(toreal(9007199254740992 + 1)); + } {9007199254740992} } do_execsql_test func4-5.24 { SELECT tointeger(toreal(9007199254740992 + 2)); } {9007199254740994} if {$highPrecision(1)} { Index: test/fuzzcheck.c ================================================================== --- test/fuzzcheck.c +++ test/fuzzcheck.c @@ -977,11 +977,12 @@ sqlite3_stmt *pStmt, /* Test statement stopped on an SQLITE_ROW */ int iCnt, /* Invariant sequence number, starting at 0 */ int iRow, /* The row number for pStmt */ int nRow, /* Total number of output rows */ int *pbCorrupt, /* IN/OUT: Flag indicating a corrupt database file */ - int eVerbosity /* How much debugging output */ + int eVerbosity, /* How much debugging output */ + unsigned int dbOpt /* Default optimization flags */ ); /* Implementation of sqlite_dbdata and sqlite_dbptr */ extern int sqlite3_dbdata_init(sqlite3*,const char**,void*); @@ -1029,11 +1030,16 @@ } /* ** Run the SQL text */ -static int runDbSql(sqlite3 *db, const char *zSql, unsigned int *pBtsFlags){ +static int runDbSql( + sqlite3 *db, /* Run SQL on this database connection */ + const char *zSql, /* The SQL to be run */ + unsigned int *pBtsFlags, + unsigned int dbOpt /* Default optimization flags */ +){ int rc; sqlite3_stmt *pStmt; int bCorrupt = 0; while( isspace(zSql[0]&0x7f) ) zSql++; if( zSql[0]==0 ) return SQLITE_OK; @@ -1105,11 +1111,11 @@ while( sqlite3_step(pStmt)==SQLITE_ROW ){ int iCnt = 0; iRow++; for(iCnt=0; iCnt<99999; iCnt++){ rc = fuzz_invariant(db, pStmt, iCnt, iRow, nRow, - &bCorrupt, eVerbosity); + &bCorrupt, eVerbosity, dbOpt); if( rc==SQLITE_DONE ) break; if( rc!=SQLITE_ERROR ) g.nInvariant++; if( eVerbosity>0 ){ if( rc==SQLITE_OK ){ printf("invariant-check: ok\n"); @@ -1328,21 +1334,21 @@ for(i=j=0; zSql[i]; i++){ if( zSql[i]==';' ){ char cSaved = zSql[i+1]; zSql[i+1] = 0; if( sqlite3_complete(zSql+j) ){ - rc = runDbSql(cx.db, zSql+j, &btsFlags); + rc = runDbSql(cx.db, zSql+j, &btsFlags, dbOpt); j = i+1; } zSql[i+1] = cSaved; if( rc==SQLITE_INTERRUPT || progress_handler(&cx) ){ goto testrun_finished; } } } if( j /* Forward references */ static char *fuzz_invariant_sql(sqlite3_stmt*, int); static int sameValue(sqlite3_stmt*,int,sqlite3_stmt*,int,sqlite3_stmt*); -static void reportInvariantFailed(sqlite3_stmt*,sqlite3_stmt*,int); +static void reportInvariantFailed( + sqlite3_stmt *pOrig, /* The original query */ + sqlite3_stmt *pTest, /* The alternative test query with a missing row */ + int iRow, /* Row number in pOrig */ + unsigned int dbOpt, /* Optimization flags on pOrig */ + int noOpt /* True if opt flags inverted for pTest */ +); /* ** Do an invariant check on pStmt. iCnt determines which invariant check to ** perform. The first check is iCnt==0. ** @@ -66,25 +72,33 @@ sqlite3_stmt *pStmt, /* Test statement stopped on an SQLITE_ROW */ int iCnt, /* Invariant sequence number, starting at 0 */ int iRow, /* Current row number */ int nRow, /* Number of output rows from pStmt */ int *pbCorrupt, /* IN/OUT: Flag indicating a corrupt database file */ - int eVerbosity /* How much debugging output */ + int eVerbosity, /* How much debugging output */ + unsigned int dbOpt /* Default optimization flags */ ){ char *zTest; sqlite3_stmt *pTestStmt = 0; int rc; int i; int nCol; int nParam; + int noOpt = (iCnt%3)==0; if( *pbCorrupt ) return SQLITE_DONE; nParam = sqlite3_bind_parameter_count(pStmt); if( nParam>100 ) return SQLITE_DONE; zTest = fuzz_invariant_sql(pStmt, iCnt); if( zTest==0 ) return SQLITE_DONE; + if( noOpt ){ + sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS, db, ~dbOpt); + } rc = sqlite3_prepare_v2(db, zTest, -1, &pTestStmt, 0); + if( noOpt ){ + sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS, db, dbOpt); + } if( rc ){ if( eVerbosity ){ printf("invariant compile failed: %s\n%s\n", sqlite3_errmsg(db), zTest); } @@ -210,21 +224,20 @@ sqlite3_bind_pointer(pCk, 1, pStmt, "stmt-pointer", 0); rc = sqlite3_step(pCk); } sqlite3_finalize(pCk); if( rc==SQLITE_DONE ){ - reportInvariantFailed(pStmt, pTestStmt, iRow); + reportInvariantFailed(pStmt, pTestStmt, iRow, dbOpt, noOpt); return SQLITE_INTERNAL; }else if( eVerbosity>0 ){ printf("invariant-error ignored due to the use of virtual tables\n"); } } not_a_fault: sqlite3_finalize(pTestStmt); return SQLITE_OK; } - /* ** Generate SQL used to test a statement invariant. ** ** Return 0 if the iCnt is out of range. @@ -487,17 +500,21 @@ ** does not appear in any row of the output from pTest. */ static void reportInvariantFailed( sqlite3_stmt *pOrig, /* The original query */ sqlite3_stmt *pTest, /* The alternative test query with a missing row */ - int iRow /* Row number in pOrig */ + int iRow, /* Row number in pOrig */ + unsigned int dbOpt, /* Optimization flags on pOrig */ + int noOpt /* True if opt flags inverted for pTest */ ){ int iTestRow = 0; printf("Invariant check failed on row %d.\n", iRow); - printf("Original query --------------------------------------------------\n"); + printf("Original query (opt-flags: 0x%08x) --------------------------\n", + dbOpt); printf("%s\n", sqlite3_expanded_sql(pOrig)); - printf("Alternative query -----------------------------------------------\n"); + printf("Alternative query (opt-flags: 0x%08x) -----------------------\n", + noOpt ? ~dbOpt : dbOpt); printf("%s\n", sqlite3_expanded_sql(pTest)); printf("Result row that is missing from the alternative -----------------\n"); printRow(pOrig, iRow); printf("Complete results from the alternative query ---------------------\n"); sqlite3_reset(pTest); Index: test/icu.test ================================================================== --- test/icu.test +++ test/icu.test @@ -147,11 +147,11 @@ } # 2020-03-19 # The ESCAPE clause on LIKE takes precedence over wildcards # -do_execsql_test idu-6.0 { +do_execsql_test icu-6.0 { DROP TABLE IF EXISTS t1; CREATE TABLE t1(id INTEGER PRIMARY KEY, x TEXT); INSERT INTO t1 VALUES (1,'abcde'), (2,'abc_'), @@ -161,7 +161,23 @@ SELECT id FROM t1 WHERE x LIKE 'abc%%' ESCAPE '%'; } {4} do_execsql_test icu-6.1 { SELECT id FROM t1 WHERE x LIKE 'abc__' ESCAPE '_'; } {2} + +# 2024-04-02 +# Optional 3rd argument to icu_load_collation() that specifies +# the "strength" of comparison. +# +reset_db +do_catchsql_test icu-7.1 { + SELECT icu_load_collation('en_US','error','xyzzy'); +} {1 {unknown collation strength "xyzzy" - should be one of: PRIMARY SECONDARY TERTIARY DEFAULT QUARTERNARY IDENTICAL}} +do_execsql_test icu-7.2 { + SELECT icu_load_collation('en_US','prim','PRIMARY'), + icu_load_collation('en_US','dflt','DEFAULT'); +} {{} {}} +do_execsql_test icu-7.3 { + SELECT char(0x100)=='a', char(0x100)=='a' COLLATE dflt, char(0x100)=='a' COLLATE prim; +} {0 0 1} finish_test Index: test/in4.test ================================================================== --- test/in4.test +++ test/in4.test @@ -456,18 +456,18 @@ ANALYZE sqlite_schema; } {} do_execsql_test 11.1 { SELECT * FROM t1 WHERE b IN (345, (SELECT 1 FROM t1 - WHERE b IN (345 NOT GLOB 510) + WHERE b IN (coalesce(1,random())) AND c GLOB 'abc*xyz')) AND c BETWEEN 'abc' AND 'xyz'; } {xyz 1 abcdefxyz 99} do_execsql_test 11.2 { EXPLAIN SELECT * FROM t1 WHERE b IN (345, (SELECT 1 FROM t1 - WHERE b IN (345 NOT GLOB 510) + WHERE b IN (coalesce(1,random())) AND c GLOB 'abc*xyz')) AND c BETWEEN 'abc' AND 'xyz'; } {/ SeekScan /} # 2021-06-25 ticket 6dcbfd11cf666e21 Index: test/in5.test ================================================================== --- test/in5.test +++ test/in5.test @@ -263,7 +263,29 @@ } {1e500} do_execsql_test 9.2 { SELECT lower('1e500') FROM t0 WHERE rowid != lower('1e500'); } {1e500} + +#------------------------------------------------------------------------- +# +reset_db + +do_execsql_test 10.0 { + CREATE TABLE t1(a, b TEXT COLLATE NOCASE); + INSERT INTO t1 VALUES('abc', 'def'); + INSERT INTO t1 VALUES('ghi', 'jkl'); +} + +do_execsql_test 10.1 { + SELECT rowid FROM t1 WHERE (a, b) IN ( VALUES('abc', 'def'), ('ghi', 'JKL') ); +} {1 2} + +do_execsql_test 10.2 { + CREATE INDEX i1 ON t1(a, b COLLATE BINARY); +} + +do_execsql_test 10.3 { + SELECT rowid FROM t1 WHERE (a, b) IN ( VALUES('abc', 'def'), ('ghi', 'JKL') ); +} {1 2} finish_test ADDED test/in7.test Index: test/in7.test ================================================================== --- /dev/null +++ test/in7.test @@ -0,0 +1,141 @@ +# 2024-05-01 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix in7 + +do_execsql_test 1.0 { + CREATE TABLE t1(a, b, c PRIMARY KEY); + CREATE TABLE t2(x, y, z); +} + +foreach {tn nNext idx sql} { + 1 1 { + CREATE INDEX i1 ON t1(a, b); + } { + SELECT * FROM t1 WHERE (a, b) IN (SELECT x, y FROM t2) + } + + 2 0 { + CREATE UNIQUE INDEX i1 ON t1(a, b); + } { + SELECT * FROM t1 WHERE (a, b) IN (SELECT x, y FROM t2) + } + + 3 0 { + CREATE UNIQUE INDEX i1 ON t1(a, b); + } { + SELECT * FROM t1 WHERE a = ? AND b = ? + } + + 3 1 { + CREATE UNIQUE INDEX i1 ON t1(a, b); + } { + SELECT * FROM t1 WHERE a = ? AND b IS ? + } + + 4 0 { + CREATE UNIQUE INDEX i1 ON t1(a, b); + } { + SELECT * FROM t1 WHERE a = ? AND b IN (?, ?, ?); + } + + 5 1 { + CREATE UNIQUE INDEX i1 ON t1(a, b, c); + } { + SELECT * FROM t1 WHERE a = ? AND b = ? + } + + 6 0 { + } { + SELECT * FROM t1 WHERE c IN (SELECT z FROM t2) + } + + 7 0 { + } { + SELECT * FROM t1 WHERE (a, c) IN (SELECT z, x FROM t2) + } + + 8 1 { + } { + SELECT * FROM t1 WHERE a IN (SELECT z FROM t2) + } + + 9 1 { + CREATE UNIQUE INDEX i1 ON t1(a, b); + } { + SELECT * FROM t1 WHERE a IN (SELECT z FROM t2) AND b IS ? + } + 10 0 { + CREATE UNIQUE INDEX i1 ON t1(a, b); + } { + SELECT * FROM t1 WHERE a IN (SELECT z FROM t2) AND b = ? + } + 11 1 { + CREATE UNIQUE INDEX i1 ON t1(a, b); + } { + SELECT * FROM t1 WHERE a IS NULL AND b IN (SELECT z FROM t2) + } + 12 0 { + CREATE UNIQUE INDEX i1 ON t1(a, b); + } { + SELECT * FROM t1 WHERE a = ? AND b IN (SELECT z FROM t2) + } +} { + do_test 1.1.$tn { + execsql BEGIN + execsql $idx + + catch { array unset root_to_tbl } + catch { array unset csr_to_root } + + db eval {SELECT rootpage, tbl_name FROM sqlite_schema} { + set root_to_tbl($rootpage) $tbl_name + } + + set nSeen 0 + db eval "explain $sql" { + if {$opcode=="OpenRead"} { + set csr_to_root($p1) $p2 + } + if {$opcode=="Next"} { + catch { + set root $csr_to_root($p1) + set tbl $root_to_tbl($root) + if {$tbl=="t1"} {incr nSeen} + } + } + } + + execsql ROLLBACK + + set nSeen + } $nNext +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 2.0 { + CREATE TABLE t1(a TEXT PRIMARY KEY, b TEXT) WITHOUT ROWID; + INSERT INTO t1 VALUES('1', 'one'); + INSERT INTO t1 VALUES('2', NULL); + INSERT INTO t1 VALUES('3', 'three'); +} + +do_execsql_test 2.1 { + SELECT b FROM t1 WHERE a IN (1,2,3) ORDER BY b ASC NULLS LAST; +} {one three {}} + + +finish_test Index: test/indexexpr1.test ================================================================== --- test/indexexpr1.test +++ test/indexexpr1.test @@ -613,10 +613,62 @@ FROM t1 JOIN t2 ON t2.t1_id = t1.id GROUP BY t2.type, t1.tag ) v ON v.type = 0 AND v.tag = u.tag; } {7 100 8 101} +do_execsql_test indexexpr1-2210 { + DROP TABLE t1; + CREATE TABLE t1(x INT, y TEXT); + INSERT INTO t1(x,y) VALUES(1,'{b:5}'); + SELECT json_insert('{}', '$.a', coalesce(null,json(y)))->>'$.a.b' FROM t1; +} {5} +db null NULL +do_execsql_test indexexpr1-2211 { + CREATE INDEX t1j ON t1(coalesce(null,json(y))); + SELECT json_insert('{}', '$.a', coalesce(null,json(y)))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2220 { + DROP INDEX t1j; + SELECT json_insert('{}', '$.a', iif(1,json(y),123))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2221 { + CREATE INDEX t1j ON t1(iif(1,json(y),123)); + SELECT json_insert('{}', '$.a', iif(1,json(y),123))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2230 { + DROP INDEX t1j; + SELECT json_insert('{}', '$.a', ifnull(NULL,json(y)))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2231 { + CREATE INDEX t1j ON t1(ifnull(NULL,json(y))); + SELECT json_insert('{}', '$.a', ifnull(NULL,json(y)))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2240 { + DROP INDEX t1j; + SELECT json_insert('{}', '$.a', nullif(json(y),8))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2241 { + CREATE INDEX t1j ON t1(nullif(json(y),8)); + SELECT json_insert('{}', '$.a', nullif(json(y),8))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2250 { + DROP INDEX t1j; + SELECT json_insert('{}', '$.a', min('~',json(y)))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2251 { + CREATE INDEX t1j ON t1(min('~',json(y))); + SELECT json_insert('{}', '$.a', min('~',json(y)))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2260 { + DROP INDEX t1j; + SELECT json_insert('{}', '$.a', max('...',json(y)))->>'$.a.b' FROM t1; +} {5} +do_execsql_test indexexpr1-2261 { + CREATE INDEX t1j ON t1(max('...',json(y))); + SELECT json_insert('{}', '$.a', max('...',json(y)))->>'$.a.b' FROM t1; +} {5} + # 2023-11-08 Forum post https://sqlite.org/forum/forumpost/68d284c86b082c3e # # Functions that return subtypes and that are indexed cannot be used to # cover function calls from the main table, since the indexed value does Index: test/join5.test ================================================================== --- test/join5.test +++ test/join5.test @@ -368,12 +368,16 @@ INSERT INTO t1 VALUES(1,1); CREATE INDEX t1x1 ON t1(a,b,a,a,a,a,a,a,a,a,a,b); ANALYZE sqlite_schema; INSERT INTO sqlite_stat1 VALUES('t1','t1x1','648 324 81 81 81 81 81 81 81081 81 81 81'); ANALYZE sqlite_schema; - SELECT a FROM (SELECT a FROM t1 NATURAL LEFT JOIN t1) NATURAL LEFT JOIN t1 WHERE (rowid,1)<=(5,0); -} {1} +} +do_catchsql_test 9.2 { + SELECT a FROM + (SELECT a FROM t1 NATURAL LEFT JOIN t1) NATURAL LEFT JOIN t1 + WHERE (rowid,1)<=(5,0); +} {0 1} # 2022-03-02 https://sqlite.org/forum/info/50a1bbe08ce4c29c # Bloom-filter pulldown is incompatible with skip-scan. # reset_db Index: test/joinH.test ================================================================== --- test/joinH.test +++ test/joinH.test @@ -199,17 +199,17 @@ CREATE TABLE wo3(a PRIMARY KEY, b) WITHOUT ROWID; } do_catchsql_test 9.1 { SELECT rowid FROM wo1, x1, x2; -} {1 {no such column: rowid}} +} {1 {ambiguous column name: rowid}} do_catchsql_test 9.2 { SELECT rowid FROM wo1, (x1, x2); -} {1 {no such column: rowid}} +} {1 {ambiguous column name: rowid}} do_catchsql_test 9.3 { SELECT rowid FROM wo1 JOIN (x1 JOIN x2); -} {1 {no such column: rowid}} +} {1 {ambiguous column name: rowid}} do_catchsql_test 9.4 { SELECT a FROM wo1, x1, x2; } {1 {ambiguous column name: a}} @@ -306,7 +306,39 @@ SELECT * FROM t1 LEFT JOIN t2 ON true RIGHT JOIN t3 ON d2=e3 WHERE c2 BETWEEN NULL AND a1; } do_execsql_test 12.3 { SELECT * FROM t1 LEFT JOIN t2 ON true RIGHT JOIN t3 ON d2=e3 WHERE c2 BETWEEN NULL AND a1; } + +#------------------------------------------------------------------------- +# 2024-04-05 dbsqlfuzz b9e65e2f110df998f1306571fae7af6c01e4d92b +reset_db +do_execsql_test 13.1 { + CREATE TABLE t1(a INT AS (b), b INT); + INSERT INTO t1(b) VALUES(123); + CREATE TABLE t2(a INT, c INT); + SELECT a FROM t2 NATURAL RIGHT JOIN t1; +} {123} +do_execsql_test 13.2 { + CREATE INDEX t1a ON t1(a); + SELECT a FROM t2 NATURAL RIGHT JOIN t1; +} {123} +# Further tests of the same logic (indexes on expressions +# used by RIGHT JOIN) from check-in ffe23af73fcb324d and +# forum post https://sqlite.org/forum/forumpost/9b491e1debf0b67a. +db null NULL +do_execsql_test 13.3 { + CREATE TABLE t3(a INT, b INT); + CREATE UNIQUE INDEX t3x ON t3(a, a+b); + INSERT INTO t3(a,b) VALUES(1,2),(4,8),(16,32),(4,80),(1,-300); + CREATE TABLE t4(x INT, y INT); + INSERT INTO t4(x,y) SELECT a, b FROM t3; + INSERT INTO t4(x,y) VALUES(99,99); + SELECT a1.a, sum( a1.a+a1.b ) FROM t3 AS a1 RIGHT JOIN t4 ON a=x + GROUP BY a1.a ORDER BY 1; +} {NULL NULL 1 -592 4 192 16 48} +do_execsql_test 13.4 { + SELECT sum( a1.a+a1.b ) FROM t3 AS a1 RIGHT JOIN t3 ON true + GROUP BY a1.a ORDER BY 1; +} {-1480 240 480} finish_test Index: test/json102.test ================================================================== --- test/json102.test +++ test/json102.test @@ -761,7 +761,38 @@ UPDATE t1 SET memo = JSON_SET(memo, '$.y', 6) WHERE a2 IN (876) AND JSON_TYPE(memo, '$.y') IS NULL; PRAGMA integrity_check; SELECT * FROM t1; } {ok 2023-08-03 876 5 {{"x":77,"y":6}}} + +# 2024-05-21 https://sqlite.org/forum/forumpost/9e52cdfe15c3926e +# What if the RHS of the -> or ->> operator is a string that looks +# like a number? PostgreSQL treats it as a string. +# +do_execsql_test json102-1800 { + SELECT '{"1":"one","2":"two","3":"three"}'->>'2'; +} two +db null NULL +do_execsql_test json102-1801 { + SELECT '{"1":"one","2":"two","3":"three"}'->>2; +} NULL +do_execsql_test json102-1810 { + SELECT '["zero","one","two"]'->>'1'; +} NULL +do_execsql_test json102-1811 { + SELECT '["zero","one","two"]'->>1; +} one +do_execsql_test json102-1820 { + SELECT '{"1":"one","2":"two","3":"three"}'->'2'; +} {{"two"}} +do_execsql_test json102-1821 { + SELECT '{"1":"one","2":"two","3":"three"}'->2; +} {NULL} +do_execsql_test json102-1830 { + SELECT '["zero","one","two"]'->'1'; +} {NULL} +do_execsql_test json102-1831 { + SELECT '["zero","one","two"]'->1; +} {{"one"}} + finish_test Index: test/lemon-test01.y ================================================================== --- test/lemon-test01.y +++ test/lemon-test01.y @@ -1,9 +1,14 @@ // A test case for the LEMON parser generator. Run as follows: // // lemon lemon-test01.y && gcc -g lemon-test01.c && ./a.out // +// This testcase was made obsolete by check-in 7cca80808cef192f on +// 2021-08-17 (associated with Forum Thread +// https://sqlite.org/forum/forumpost/bd91fd965c9803c4) and no longer +// works. It is retained for historical reference only. +// %token_prefix TK_ %token_type int %default_type int %include { static int nSyntaxError = 0; @@ -26,11 +31,11 @@ %code { #include #include "lemon-test01.h" static int nTest = 0; static int nErr = 0; - static int testCase(int testId, int shouldBe, int actual){ + static void testCase(int testId, int shouldBe, int actual){ nTest++; if( shouldBe==actual ){ printf("test %d: ok\n", testId); }else{ printf("test %d: got %d, expected %d\n", testId, actual, shouldBe); Index: test/misc2.test ================================================================== --- test/misc2.test +++ test/misc2.test @@ -52,23 +52,38 @@ CREATE TABLE t2(a,b,c); INSERT INTO t2 VALUES(7,8,9); } } {} ifcapable subquery { - do_catchsql_test misc2-2.2 { - SELECT rowid, * FROM (SELECT * FROM t1, t2); - } {1 {no such column: rowid}} + ifcapable allow_rowid_in_view { + do_catchsql_test misc2-2.2 { + SELECT rowid, * FROM (SELECT * FROM t1, t2); + } {0 {{} 1 2 3 7 8 9}} + } else { + do_catchsql_test misc2-2.2 { + SELECT rowid, * FROM (SELECT * FROM t1, t2); + } {1 {no such column: rowid}} + } do_catchsql_test misc2-2.2b { SELECT 'rowid', * FROM (SELECT * FROM t1, t2); } {0 {rowid 1 2 3 7 8 9}} } ifcapable view { - do_catchsql_test misc2-2.3 { - CREATE VIEW v1 AS SELECT * FROM t1, t2; - SELECT rowid, * FROM v1; - } {1 {no such column: rowid}} + ifcapable allow_rowid_in_view { + do_catchsql_test misc2-2.3 { + CREATE VIEW v1 AS SELECT * FROM t1, t2; + SELECT rowid, * FROM v1; + } {0 {{} 1 2 3 7 8 9}} + } else { + do_catchsql_test misc2-2.3 { + CREATE VIEW v1 AS SELECT * FROM t1, t2; + SELECT rowid, * FROM v1; + } {1 {no such column: rowid}} + } + + do_catchsql_test misc2-2.3b { SELECT 'rowid', * FROM v1; } {0 {rowid 1 2 3 7 8 9}} } ;# ifcapable view Index: test/misc8.test ================================================================== --- test/misc8.test +++ test/misc8.test @@ -98,10 +98,15 @@ 0 10 {} 10 {} {} } # 2016-02-26: An assertion fault found by the libFuzzer project # +ifcapable allow_rowid_in_view { + set nosuch "1 {ambiguous column name: rowid}" +} else { + set nosuch "1 {no such column: rowid}" +} do_catchsql_test misc8-3.0 { SELECT * FROM ( (SELECT 0 AS i) AS x1, @@ -108,11 +113,11 @@ (SELECT 1) AS x2 ) AS x3, (SELECT 6 AS j UNION ALL SELECT 7) AS x4 WHERE i0; } {- - 3 4 5} + +# 2024-04-05 +# Allow push-down of operators of the form "expr IN table". +# +reset_db +do_execsql_test 6.0 { + CREATE TABLE t01(w,x,y,z); + CREATE TABLE t02(w,x,y,z); + CREATE VIEW t0(w,x,y,z) AS + SELECT w,x,y,z FROM t01 UNION ALL SELECT w,x,y,z FROM t02; + CREATE INDEX t01x ON t01(w,x,y); + CREATE INDEX t02x ON t02(w,x,y); + CREATE VIEW v1(k) AS VALUES(77),(88),(99); + CREATE TABLE k1(k); + INSERT INTO k1 SELECT * FROM v1; +} +do_eqp_test 6.1 { + WITH k(n) AS (VALUES(77),(88),(99)) + SELECT max(z) FROM t0 WHERE w=123 AND x IN k AND y BETWEEN 44 AND 55; +} { + QUERY PLAN + |--CO-ROUTINE t0 + | `--COMPOUND QUERY + | |--LEFT-MOST SUBQUERY + | | |--SEARCH t01 USING INDEX t01x (w=? AND x=? AND y>? AND y? AND y? AND y? AND y? AND y? AND y3 + RETURNING a, + (SELECT min(a) FROM t1), + (SELECT max(a) FROM t1), + (SELECT round(avg(a),2) FROM t1); + ROLLBACK; +} { + 1 2 8 4.6 + 2 3 8 5.25 + 4 3 8 5.67 + 6 3 8 5.5 + 8 3 3 3.0 +} +do_execsql_test 20.2 { + BEGIN; + DELETE FROM t1 + RETURNING a, + (SELECT min(a) FROM t1), + (SELECT max(a) FROM t1), + (SELECT round(avg(a),2) FROM t1); + ROLLBACK; +} { + 1 2 8 4.6 + 2 3 8 5.25 + 3 4 8 6.0 + 4 6 8 7.0 + 6 8 8 8.0 + 8 N N N +} +do_execsql_test 20.3 { + BEGIN; + DELETE FROM t1 + RETURNING a, + (SELECT min(t2.a)+t1.a*100 FROM t1 AS t2), + (SELECT max(t2.a)+t1.a*100 FROM t1 AS t2), + (SELECT round(avg(t2.a),2)+t1.a*100 FROM t1 AS t2); + ROLLBACK; +} { + 1 102 108 104.6 + 2 203 208 205.25 + 3 304 308 306.0 + 4 406 408 407.0 + 6 608 608 608.0 + 8 N N N +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 21.0 { + PRAGMA writable_schema=ON; + INSERT INTO sqlite_schema DEFAULT VALUES RETURNING sqlite_schema.name; +} {{}} + +do_execsql_test 21.1 { + INSERT INTO sqlite_temp_schema DEFAULT VALUES RETURNING sqlite_temp_schema.name; +} {{}} finish_test Index: test/rowid.test ================================================================== --- test/rowid.test +++ test/rowid.test @@ -801,20 +801,33 @@ INSERT INTO t1(rowid, x) VALUES(1, 1); INSERT INTO t2(y) VALUES(2); INSERT INTO t3(rowid, z) VALUES(3, 3); } -do_execsql_test 16.1 { SELECT rowid FROM t1, t2; } {1} -do_execsql_test 16.2 { SELECT rowid FROM t1, v1; } {1} -do_execsql_test 16.3 { SELECT rowid FROM t3, v1; } {3} -do_execsql_test 16.4 { SELECT rowid FROM t3, (SELECT 123); } {3} - -do_execsql_test 16.5 { SELECT rowid FROM t2, t1; } {1} -do_execsql_test 16.6 { SELECT rowid FROM v1, t1; } {1} -do_execsql_test 16.7 { SELECT rowid FROM v1, t3; } {3} -do_execsql_test 16.8 { SELECT rowid FROM (SELECT 123), t3; } {3} - -do_catchsql_test 16.5 { SELECT rowid FROM t1, t3; } {1 {no such column: rowid}} - - +ifcapable allow_rowid_in_view { + set nosuch "1 {ambiguous column name: rowid}" + do_execsql_test 16.1 { SELECT rowid FROM t1, t2; } {1} + do_catchsql_test 16.2 { SELECT rowid FROM t1, v1; } $nosuch + do_catchsql_test 16.3 { SELECT rowid FROM t3, v1; } $nosuch + do_catchsql_test 16.4 { SELECT rowid FROM t3, (SELECT 123); } $nosuch + + do_execsql_test 16.5 { SELECT rowid FROM t2, t1; } {1} + do_catchsql_test 16.6 { SELECT rowid FROM v1, t1; } $nosuch + do_catchsql_test 16.7 { SELECT rowid FROM v1, t3; } $nosuch + do_execsql_test 16.8 { SELECT rowid FROM (SELECT 123), t3; } {3} +} else { + do_execsql_test 16.1 { SELECT rowid FROM t1, t2; } {1} + do_execsql_test 16.2 { SELECT rowid FROM t1, v1; } {1} + do_execsql_test 16.3 { SELECT rowid FROM t3, v1; } {3} + do_execsql_test 16.4 { SELECT rowid FROM t3, (SELECT 123); } {3} + + do_execsql_test 16.5 { SELECT rowid FROM t2, t1; } {1} + do_execsql_test 16.6 { SELECT rowid FROM v1, t1; } {1} + do_execsql_test 16.7 { SELECT rowid FROM v1, t3; } {3} + do_execsql_test 16.8 { SELECT rowid FROM (SELECT 123), t3; } {3} +} + +do_catchsql_test 16.9 { + SELECT rowid FROM t1, t3; +} {1 {ambiguous column name: rowid}} finish_test Index: test/scanstatus2.test ================================================================== --- test/scanstatus2.test +++ test/scanstatus2.test @@ -325,10 +325,21 @@ ----SCAN t1 (nCycle=nnn) ----USE TEMP B-TREE FOR GROUP BY (nCycle=nnn) --SCAN xy (nCycle=nnn) --SCAN xy2 (nCycle=nnn) } + +#------------------------------------------------------------------------- +reset_db + +# Check that an OOB parameter (45) does not cause asan or valgrind errors. +# +do_test 7.0 { + db eval {SELECT * FROM sqlite_schema} + set stmt [db version -last-stmt-ptr] + sqlite3_stmt_scanstatus -flags complex $stmt 1000000 +} {} #explain_i { SELECT (a % 2), group_concat(b) FROM t1 GROUP BY 1 } #puts_debug_info { SELECT (a % 2), group_concat(b) FROM t1 GROUP BY 1 } finish_test Index: test/selectA.test ================================================================== --- test/selectA.test +++ test/selectA.test @@ -1338,14 +1338,14 @@ } { QUERY PLAN `--MERGE (UNION ALL) |--LEFT | |--SCAN t5 USING INDEX i2 - | `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY + | `--USE TEMP B-TREE FOR LAST TERM OF ORDER BY `--RIGHT |--SCAN t4 USING INDEX i1 - `--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY + `--USE TEMP B-TREE FOR LAST TERM OF ORDER BY } do_execsql_test 4.1.3 { SELECT c, d FROM t5 UNION ALL Index: test/sessionfuzz.c ================================================================== --- test/sessionfuzz.c +++ test/sessionfuzz.c @@ -58,10 +58,11 @@ #undef SQLITE_THREADSAFE #endif #define SQLITE_DEBUG 1 #define SQLITE_THREADSAFE 0 +#undef SQLITE_OMIT_LOAD_EXTENSION #define SQLITE_OMIT_LOAD_EXTENSION 0 #define SQLITE_ENABLE_SESSION 1 #define SQLITE_ENABLE_PREUPDATE_HOOK 1 #define SQLITE_ENABLE_DESERIALIZE 1 #include "sqlite3.c" Index: test/shell1.test ================================================================== --- test/shell1.test +++ test/shell1.test @@ -9,10 +9,11 @@ # #*********************************************************************** # # The focus of this file is testing the CLI shell tool. # +# TESTRUNNER: shell # # Test plan: # # shell1-1.*: Basic command line option handling. Index: test/shell2.test ================================================================== --- test/shell2.test +++ test/shell2.test @@ -6,10 +6,11 @@ # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # The focus of this file is testing the CLI shell tool. # # $Id: shell2.test,v 1.7 2009/07/17 16:54:48 shaneh Exp $ # Index: test/shell3.test ================================================================== --- test/shell3.test +++ test/shell3.test @@ -6,10 +6,11 @@ # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # The focus of this file is testing the CLI shell tool. # # $Id: shell2.test,v 1.7 2009/07/17 16:54:48 shaneh Exp $ # Index: test/shell4.test ================================================================== --- test/shell4.test +++ test/shell4.test @@ -6,10 +6,11 @@ # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # The focus of this file is testing the CLI shell tool. # These tests are specific to the .stats command. # # 2015-03-19: Added tests for .trace Index: test/shell5.test ================================================================== --- test/shell5.test +++ test/shell5.test @@ -6,10 +6,11 @@ # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # The focus of this file is testing the CLI shell tool. # These tests are specific to the .import command. # # $Id: shell5.test,v 1.7 2009/07/17 16:54:48 shaneh Exp $ @@ -567,7 +568,33 @@ catchcmd test.db {.import -csv shell5.csv t1 .mode line SELECT * FROM t1;} } {0 { 1 = あい 2 = うえお}} + +# 2024-03-11 https://sqlite.org/forum/forumpost/ca014d7358 +# Import into a table that contains computed columns. +# +do_test shell5-7.1 { + set out [open shell5.csv w] + fconfigure $out -translation lf + puts $out {aaa|bbb} + close $out + forcedelete test.db + catchcmd :memory: {CREATE TABLE t1(a TEXT, b TEXT, c AS (a||b)); +.import shell5.csv t1 +SELECT * FROM t1;} +} {0 aaa|bbb|aaabbb} + +#------------------------------------------------------------------------- + +do_test shell5-8.1 { + + set out [open shell5.csv w] + fconfigure $out -translation lf + puts $out x + close $out + + catchcmd :memory: {.import --csv shell5.csv '""""""""""""""""""""""""""""""""""""""""""""""'} +} {0 {}} finish_test Index: test/shell6.test ================================================================== --- test/shell6.test +++ test/shell6.test @@ -6,10 +6,11 @@ # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # Test the shell tool ".lint fkey-indexes" command. # set testdir [file dirname $argv0] Index: test/shell7.test ================================================================== --- test/shell7.test +++ test/shell7.test @@ -6,10 +6,11 @@ # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # Test the readfile() function built into the shell tool. Specifically, # that it does not truncate the blob read at the first embedded 0x00 # byte. # Index: test/shell8.test ================================================================== --- test/shell8.test +++ test/shell8.test @@ -6,10 +6,11 @@ # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # Test the shell tool ".ar" command. # set testdir [file dirname $argv0] @@ -162,11 +163,11 @@ # pause between creating the archive and extracting its contents. # This is to test that timestamps are set correctly. # # Because it is slow, only do this for $tn==1. if {$tn==1} { - do_test 1.$tn.1 { + do_test 1.$tn.4 { catchcmd test_ar.db $c1 file delete -force ar1 after 2000 catchcmd test_ar.db $x1 dir_to_list ar1 @@ -190,7 +191,32 @@ catchcmd shell8.db {.ar -r ./file2 ./dir1} catchcmd shell8.db {.ar -g -r ./ju*2} catchcmd shell8.db {.ar -C ar4 -x .} regsub -all {ar4} [dir_content ar4] ar2 } {ar2/file1 ar2/file2 ar2/junk1} + +# Test symbolic links. +# +if {$tcl_platform(platform)=="unix"} { + populate_dir ar2 { + file1 "1234" + file2 "3456" + } + file link ar2/link1 file1 + + forcedelete shell8.db + forcedelete link1 + + do_test 3.1 { + catchcmd shell8.db {.ar -C ar2 -c file2 link1 } + } {0 {}} + + do_test 3.2 { + catchcmd shell8.db {.ar -x} + } {0 {}} + + do_test 3.3 { + catchcmd shell8.db {.ar -x} + } {0 {}} +} finish_test Index: test/shell9.test ================================================================== --- test/shell9.test +++ test/shell9.test @@ -6,10 +6,11 @@ # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** +# TESTRUNNER: shell # # The focus of this file is testing the CLI shell tool. Specifically, # testing that it is possible to run a ".dump" script that creates # virtual tables without explicitly disabling defensive mode. # Index: test/sqllimits1.test ================================================================== --- test/sqllimits1.test +++ test/sqllimits1.test @@ -920,11 +920,11 @@ } {0 {}} do_catchsql_test sqllimits1-18.2 { INSERT INTO b1 VALUES(1), (2), (3), (4), (5), (6), (7), (8), (9), (10) UNION VALUES(11); -} {1 {too many terms in compound SELECT}} +} {0 {}} #------------------------------------------------------------------------- # reset_db ifcapable utf16 { Index: test/subquery.test ================================================================== --- test/subquery.test +++ test/subquery.test @@ -649,7 +649,66 @@ # | `--USE TEMP B-TREE FOR GROUP BY # |--SCAN v2 # `--SEARCH t1 USING INDEX x12 (aa=?) # +#----------------------------------------------------------------------------- +# 2024-04-25 Column affinities for columns of compound subqueries +# +reset_db +sqlite3_test_control SQLITE_TESTCTRL_INTERNAL_FUNCTIONS db +do_execsql_test subquery-11.1 { + CREATE TABLE t1(ix INT, rx REAL, bx BLOB, tx TEXT, ax); + INSERT INTO t1 VALUES(1,1.0,x'31','x',NULL); + WITH c(a) AS (SELECT 'y' UNION SELECT tx FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT tx FROM t1 UNION SELECT 'y') SELECT affinity(a) FROM c; +} {text text text text} +do_execsql_test subquery-11.2 { + WITH c(a) AS (SELECT 2 UNION SELECT tx FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT tx FROM t1 UNION SELECT 2) SELECT affinity(a) FROM c; +} {blob blob blob blob} +do_execsql_test subquery-11.3 { + WITH c(a) AS (SELECT 2.0 UNION SELECT tx FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT tx FROM t1 UNION SELECT 2.0) SELECT affinity(a) FROM c; +} {blob blob blob blob} +do_execsql_test subquery-11.4 { + WITH c(a) AS (SELECT null UNION SELECT tx FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT tx FROM t1 UNION SELECT null) SELECT affinity(a) FROM c; +} {text text text text} +do_execsql_test subquery-11.5 { + WITH c(a) AS (SELECT x'32' UNION SELECT tx FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT tx FROM t1 UNION SELECT x'32') SELECT affinity(a) FROM c; +} {text text text text} +do_execsql_test subquery-11.6 { + WITH c(a) AS (SELECT 3 UNION SELECT ix FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT ix FROM t1 UNION SELECT 3) SELECT affinity(a) FROM c; +} {integer integer integer integer} +do_execsql_test subquery-11.7 { + WITH c(a) AS (SELECT 3.0 UNION SELECT ix FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT ix FROM t1 UNION SELECT 3.0) SELECT affinity(a) FROM c; +} {integer integer integer integer} +do_execsql_test subquery-11.8 { + WITH c(a) AS (SELECT '3' UNION SELECT ix FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT ix FROM t1 UNION SELECT '3') SELECT affinity(a) FROM c; +} {blob blob blob blob} +do_execsql_test subquery-11.10 { + WITH c(a) AS (SELECT x'32' UNION SELECT ix FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT ix FROM t1 UNION SELECT x'32') SELECT affinity(a) FROM c; +} {integer integer integer integer} +do_execsql_test subquery-11.11 { + WITH c(a) AS (SELECT 4 UNION SELECT rx FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT rx FROM t1 UNION SELECT 4) SELECT affinity(a) FROM c; +} {real real real real} +do_execsql_test subquery-11.12 { + WITH c(a) AS (SELECT '4' UNION SELECT rx FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT rx FROM t1 UNION SELECT '4') SELECT affinity(a) FROM c; +} {blob blob blob blob} +do_execsql_test subquery-11.13 { + WITH c(a) AS (SELECT null UNION SELECT rx FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT rx FROM t1 UNION SELECT null) SELECT affinity(a) FROM c; +} {real real real real} +do_execsql_test subquery-11.14 { + WITH c(a) AS (SELECT x'b4' UNION SELECT rx FROM t1) SELECT affinity(a) FROM c; + WITH c(a) AS (SELECT rx FROM t1 UNION SELECT x'b4') SELECT affinity(a) FROM c; +} {real real real real} finish_test Index: test/tabfunc01.test ================================================================== --- test/tabfunc01.test +++ test/tabfunc01.test @@ -319,10 +319,36 @@ catchsql { ALTER TABLE pragma_compile_options DROP COLUMN start; } } {1 {table pragma_compile_options may not be altered}} } + +#----------------------------------------------------------------------------- +# 2024-04-26 LIMIT and OFFSET passed into virtual tables +# https://sqlite.org/forum/forumpost/c243b8f856 +# +do_execsql_test tabfunc01-900 { + SELECT * FROM ( + SELECT * FROM generate_series(1,10) + UNION ALL + SELECT * FROM generate_series(101,104) + ) LIMIT 10 OFFSET 5; +} {6 7 8 9 10 101 102 103 104} +do_execsql_test tabfunc01-910 { + SELECT * FROM ( + SELECT * FROM generate_series(1,10) + UNION ALL + SELECT * FROM generate_series(101,104) + ) LIMIT -1 OFFSET 5; +} {6 7 8 9 10 101 102 103 104} +do_execsql_test tabfunc01-920 { + SELECT * FROM ( + SELECT * FROM generate_series(1,10) + UNION ALL + SELECT * FROM generate_series(101,104) + ) LIMIT -1 OFFSET 0; +} {1 2 3 4 5 6 7 8 9 10 101 102 103 104} # Free up memory allocations intarray_addr int64array_addr Index: test/testrunner.tcl ================================================================== --- test/testrunner.tcl +++ test/testrunner.tcl @@ -52,26 +52,39 @@ puts stderr [string trim [subst -nocommands { Usage: $a0 ?SWITCHES? ?PERMUTATION? ?PATTERNS? $a0 PERMUTATION FILE + $a0 help $a0 njob ?NJOB? + $a0 script ?-msvc? CONFIG $a0 status where SWITCHES are: - --buildonly - --dryrun - --jobs NUMBER-OF-JOBS - --zipvfs ZIPVFS-SOURCE-DIR - -Interesting values for PERMUTATION are: - - veryquick - a fast subset of the tcl test scripts. This is the default. - full - all tcl test scripts. + --buildonly Build test exes but do not run tests + --config CONFIGS Only use configs on comma-separate list CONFIGS + --dryrun Write what would have happened to testrunner.log + --explain Write summary to stdout + --jobs NUM Run tests using NUM separate processes + --omit CONFIGS Omit configs on comma-separated list CONFIGS + --stop-on-coredump Stop running if any test segfaults + --stop-on-error Stop running after any reported error + --zipvfs ZIPVFSDIR ZIPVFS source directory + +Special values for PERMUTATION that work with plain tclsh: + + list - show all allowed PERMUTATION arguments. + mdevtest - tests recommended prior to normal development check-ins. + release - full release test with various builds. + sdevtest - like mdevtest but using ASAN and UBSAN. + +Other PERMUTATION arguments must be run using testfixture, not tclsh: + all - all tcl test scripts, plus a subset of test scripts rerun with various permutations. - release - full release test with various builds. + full - all tcl test scripts. + veryquick - a fast subset of the tcl test scripts. This is the default. If no PATTERN arguments are present, all tests specified by the PERMUTATION are run. Otherwise, each pattern is interpreted as a glob pattern. Only those tcl tests for which the final component of the filename matches at least one specified pattern are run. @@ -87,10 +100,16 @@ The "status" and "njob" commands are designed to be run from the same directory as a running testrunner.tcl script that is running tests. The "status" command prints a report describing the current state and progress of the tests. The "njob" command may be used to query or modify the number of sub-processes the test script uses to run tests. + +The "script" command outputs the script used to build a configuration. +Add the "-msvc" option for a Windows-compatible script. For a list of +available configurations enter "$a0 script help". + +Full documentation here: https://sqlite.org/src/doc/trunk/doc/testrunner.md }]] exit 1 } #------------------------------------------------------------------------- @@ -124,10 +143,14 @@ } return $ret } proc default_njob {} { + global env + if {[info exists env(NJOB)] && $env(NJOB)>=1} { + return $env(NJOB) + } set nCore [guess_number_of_cores] if {$nCore<=2} { set nHelper 1 } else { set nHelper [expr int($nCore*0.5)] @@ -149,34 +172,42 @@ set TRG(cmdline) $argv set TRG(reporttime) 2000 set TRG(fuzztest) 0 ;# is the fuzztest option present. set TRG(zipvfs) "" ;# -zipvfs option, if any set TRG(buildonly) 0 ;# True if --buildonly option +set TRG(config) {} ;# Only build the named configurations +set TRG(omitconfig) {} ;# Do not build these configurations set TRG(dryrun) 0 ;# True if --dryrun option +set TRG(explain) 0 ;# True for the --explain option +set TRG(stopOnError) 0 ;# Stop running at first failure +set TRG(stopOnCore) 0 ;# Stop on a core-dump switch -nocase -glob -- $tcl_platform(os) { *darwin* { set TRG(platform) osx set TRG(make) make.sh set TRG(makecmd) "bash make.sh" set TRG(testfixture) testfixture + set TRG(shell) sqlite3 set TRG(run) run.sh set TRG(runcmd) "bash run.sh" } *linux* { set TRG(platform) linux set TRG(make) make.sh set TRG(makecmd) "bash make.sh" set TRG(testfixture) testfixture + set TRG(shell) sqlite3 set TRG(run) run.sh set TRG(runcmd) "bash run.sh" } *win* { set TRG(platform) win set TRG(make) make.bat - set TRG(makecmd) make.bat + set TRG(makecmd) "call make.bat" set TRG(testfixture) testfixture.exe + set TRG(shell) sqlite3.exe set TRG(run) run.bat set TRG(runcmd) "run.bat" } default { error "cannot determine platform!" @@ -237,11 +268,11 @@ priority INTEGER NOT NULL, -- higher priority jobs may run earlier /* Fields updated as jobs run */ starttime INTEGER, endtime INTEGER, - state TEXT CHECK( state IN ('', 'ready', 'running', 'done', 'failed') ), + state TEXT CHECK( state IN ('','ready','running','done','failed','omit') ), output TEXT ); CREATE TABLE config( name TEXT COLLATE nocase PRIMARY KEY, @@ -324,10 +355,18 @@ puts "$res" exit } #-------------------------------------------------------------------------- +#-------------------------------------------------------------------------- +# Check if this is the "help" command: +# +if {[string compare -nocase help [lindex $argv 0]]==0} { + usage +} +#-------------------------------------------------------------------------- + #-------------------------------------------------------------------------- # Check if this is the "script" command: # if {[string compare -nocase script [lindex $argv 0]]==0} { if {[llength $argv]!=2 && !([llength $argv]==3&&[lindex $argv 1]=="-msvc")} { @@ -406,10 +445,14 @@ mydb eval { SELECT * FROM jobs WHERE state='failed' ORDER BY starttime } job { display_job [array get job] } + set nOmit [db one {SELECT count(*) FROM jobs WHERE state='omit'}] + if {$nOmit} { + puts "$nOmit jobs omitted due to failures" + } } mydb close exit } @@ -431,22 +474,32 @@ incr ii set TRG(zipvfs) [file normalize [lindex $argv $ii]] if {$isLast} { usage } } elseif {($n>2 && [string match "$a*" --buildonly]) || $a=="-b"} { set TRG(buildonly) 1 + } elseif {($n>2 && [string match "$a*" --config]) || $a=="-c"} { + incr ii + set TRG(config) [lindex $argv $ii] } elseif {($n>2 && [string match "$a*" --dryrun]) || $a=="-d"} { set TRG(dryrun) 1 + } elseif {($n>2 && [string match "$a*" --explain]) || $a=="-e"} { + set TRG(explain) 1 + } elseif {($n>2 && [string match "$a*" --omit]) || $a=="-c"} { + incr ii + set TRG(omitconfig) [lindex $argv $ii] + } elseif {[string match "$a*" --stop-on-error]} { + set TRG(stopOnError) 1 + } elseif {[string match "$a*" --stop-on-coredump]} { + set TRG(stopOnCore) 1 } else { usage } } else { lappend TRG(patternlist) [string map {% *} $a] } } set argv [list] - - # This script runs individual tests - tcl scripts or [make xyz] commands - # in directories named "testdir$N", where $N is an integer. This variable # contains a list of integers indicating the directories in use. # @@ -615,11 +668,20 @@ } trdb last_insert_rowid } -proc add_tcl_jobs {build config patternlist} { +# Argument $build is either an empty string, or else a list of length 3 +# describing the job to build testfixture. In the usual form: +# +# {ID DIRNAME DISPLAYNAME} +# +# e.g +# +# {1 /home/user/sqlite/test/testrunner_bld_xyz All-Debug} +# +proc add_tcl_jobs {build config patternlist {shelldepid ""}} { global TRG set topdir [file dirname $::testdir] set testrunner_tcl [file normalize [info script]] @@ -664,37 +726,62 @@ set lProp [trd_test_script_properties $f] set priority 0 if {[lsearch $lProp slow]>=0} { set priority 2 } if {[lsearch $lProp superslow]>=0} { set priority 4 } + set depid [lindex $build 0] + if {$shelldepid!="" && [lsearch $lProp shell]>=0} { set depid $shelldepid } + add_job \ -displaytype tcl \ -displayname $displayname \ -cmd $cmd \ - -depid [lindex $build 0] \ + -depid $depid \ -priority $priority - } } -proc add_build_job {buildname target} { +proc add_build_job {buildname target {postcmd ""} {depid ""}} { global TRG set dirname "[string tolower [string map {- _} $buildname]]_$target" set dirname "testrunner_bld_$dirname" + + set cmd "$TRG(makecmd) $target" + if {$postcmd!=""} { + append cmd "\n" + append cmd $postcmd + } set id [add_job \ -displaytype bld \ -displayname "Build $buildname ($target)" \ -dirname $dirname \ -build $buildname \ - -cmd "$TRG(makecmd) $target" \ + -cmd $cmd \ + -depid $depid \ -priority 3 ] list $id [file normalize $dirname] $buildname } + +proc add_shell_build_job {buildname dirname depid} { + global TRG + + if {$TRG(platform)=="win"} { + set path [string map {/ \\} "$dirname/"] + set copycmd "xcopy $TRG(shell) $path" + } else { + set copycmd "cp $TRG(shell) $dirname/" + } + + return [ + add_build_job $buildname $TRG(shell) $copycmd $depid + ] +} + proc add_make_job {bld target} { global TRG if {$TRG(platform)=="win"} { @@ -765,14 +852,34 @@ proc add_devtest_jobs {lBld patternlist} { global TRG foreach b $lBld { set bld [add_build_job $b $TRG(testfixture)] - add_tcl_jobs $bld veryquick $patternlist + add_tcl_jobs $bld veryquick $patternlist SHELL if {$patternlist==""} { add_fuzztest_jobs $b } + + if {[trdb one "SELECT EXISTS (SELECT 1 FROM jobs WHERE depid='SHELL')"]} { + set sbld [add_shell_build_job $b [lindex $bld 1] [lindex $bld 0]] + set sbldid [lindex $sbld 0] + trdb eval { + UPDATE jobs SET depid=$sbldid WHERE depid='SHELL' + } + } + + } +} + +# Check to ensure that the interpreter is a full-blown "testfixture" +# build and not just a "tclsh". If this is not the case, issue an +# error message and exit. +# +proc must_be_testfixture {} { + if {[lsearch [info commands] sqlite3_soft_heap_limit]<0} { + puts "Use testfixture, not tclsh, for these arguments." + exit 1 } } proc add_jobs_from_cmdline {patternlist} { global TRG @@ -787,28 +894,39 @@ } set first [lindex $patternlist 0] switch -- $first { all { + must_be_testfixture set patternlist [lrange $patternlist 1 end] set clist [trd_all_configs] foreach c $clist { add_tcl_jobs "" $c $patternlist } } mdevtest { - add_devtest_jobs {All-O0 All-Debug} [lrange $patternlist 1 end] + set config_set { + All-O0 + All-Debug + } + add_devtest_jobs $config_set [lrange $patternlist 1 end] } sdevtest { - add_devtest_jobs {All-Sanitize All-Debug} [lrange $patternlist 1 end] + set config_set { + All-Sanitize + All-Debug + } + add_devtest_jobs $config_set [lrange $patternlist 1 end] } release { set patternlist [lrange $patternlist 1 end] foreach b [trd_builds $TRG(platform)] { + if {$TRG(config)!="" && ![regexp "\\y$b\\y" $TRG(config)]} continue + if {[regexp "\\y$b\\y" $TRG(omitconfig)]} continue set bld [add_build_job $b $TRG(testfixture)] foreach c [trd_configs $TRG(platform) $b] { add_tcl_jobs $bld $c $patternlist } @@ -821,12 +939,20 @@ } } } } } + + list { + set allperm [array names ::testspec] + lappend allperm all mdevtest sdevtest release list + puts "Allowed values for the PERMUTATION argument: [lsort $allperm]" + exit 0 + } default { + must_be_testfixture if {[info exists ::testspec($first)]} { add_tcl_jobs "" $first [lrange $patternlist 1 end] } else { add_tcl_jobs "" full $patternlist } @@ -851,15 +977,20 @@ } proc mark_job_as_finished {jobid output state endtm} { r_write_db { + if {$state=="failed"} { + set childstate omit + } else { + set childstate ready + } trdb eval { UPDATE jobs SET output=$output, state=$state, endtime=$endtm WHERE jobid=$jobid; - UPDATE jobs SET state='ready' WHERE depid=$jobid; + UPDATE jobs SET state=$childstate WHERE depid=$jobid; } } } proc script_input_ready {fd iJob jobid} { @@ -886,10 +1017,18 @@ if {[info exists TRG(reportlength)]} { puts -nonewline "[string repeat " " $TRG(reportlength)]\r" } puts "FAILED: $job(displayname) ($iJob)" set state "failed" + if {$TRG(stopOnError)} { + puts "OUTPUT: $O($iJob)" + exit 1 + } + if {$TRG(stopOnCore) && [string first {core dumped} $O($iJob)]>0} { + puts "OUTPUT: $O($iJob)" + exit 1 + } } set tm [clock_milliseconds] set jobtm [expr {$tm - $job(starttime)}] @@ -945,10 +1084,20 @@ } set fd [open [file join $dir $TRG(make)] w] puts $fd $script close $fd } + + # Add a batch/shell file command to set the directory used for temp + # files to the test's working directory. Otherwise, tests that use + # large numbers of temp files (e.g. zipvfs), might generate temp + # filename collisions. + if {$TRG(platform)=="win"} { + set set_tmp_dir "SET SQLITE_TMPDIR=[file normalize $dir]" + } else { + set set_tmp_dir "export SQLITE_TMPDIR=\"[file normalize $dir]\"" + } if { $TRG(dryrun) } { mark_job_as_finished $job(jobid) "" done 0 dirs_freeDir $iJob @@ -960,11 +1109,12 @@ } else { set pwd [pwd] cd $dir set fd [open $TRG(run) w] - puts $fd $job(cmd) + puts $fd $set_tmp_dir + puts $fd $job(cmd) close $fd set fd [open "|$TRG(runcmd) 2>@1" r] cd $pwd fconfigure $fd -blocking false @@ -1059,10 +1209,14 @@ SELECT displayname FROM jobs WHERE state='failed' } { puts "FAILED: $displayname" } } + set nOmit [trdb one {SELECT count(*) FROM jobs WHERE state='omit'}] + if {$nOmit>0} { + puts "$nOmit jobs skipped due to prior failures" + } } puts "\nTest database is $TRG(dbname)" puts "Test log is $TRG(logname)" } @@ -1075,18 +1229,45 @@ r_write_db { trdb eval { DELETE FROM jobs WHERE displaytype!='bld' } } } } + +# Handle the --explain option. Provide a human-readable +# explanation of all the tests that are in the trdb database jobs +# table. +# +proc explain_layer {indent depid} { + global TRG + if {$TRG(buildonly)} { + set showtests 0 + } else { + set showtests 1 + } + trdb eval {SELECT jobid, displayname, displaytype, dirname + FROM jobs WHERE depid=$depid ORDER BY displayname} { + if {$displaytype=="bld"} { + puts "${indent}$displayname in $dirname" + explain_layer "${indent} " $jobid + } elseif {$showtests} { + puts "${indent}[lindex $displayname end]" + } + } +} +proc explain_tests {} { + explain_layer "" "" +} sqlite3 trdb $TRG(dbname) trdb timeout $TRG(timeout) set tm [lindex [time { make_new_testset }] 0] -if {$TRG(nJob)>1} { - puts "splitting work across $TRG(nJob) jobs" -} -puts "built testset in [expr $tm/1000]ms.." - -handle_buildonly -run_testset -trdb close -#puts [pwd] +if {$TRG(explain)} { + explain_tests +} else { + if {$TRG(nJob)>1} { + puts "splitting work across $TRG(nJob) jobs" + } + puts "built testset in [expr $tm/1000]ms.." + handle_buildonly + run_testset +} +trdb close Index: test/testrunner_data.tcl ================================================================== --- test/testrunner_data.tcl +++ test/testrunner_data.tcl @@ -21,10 +21,11 @@ set tcltest(linux.Extra-Robustness) veryquick set tcltest(linux.Device-Two) veryquick set tcltest(linux.No-lookaside) veryquick set tcltest(linux.Devkit) veryquick set tcltest(linux.Apple) veryquick + set tcltest(linux.Android) veryquick set tcltest(linux.Sanitize) veryquick set tcltest(linux.Device-One) all set tcltest(linux.Default) all_plus_autovacuum_crash set tcltest(linux.Valgrind) valgrind @@ -51,10 +52,11 @@ set extra(linux.Update-Delete-Limit) {fuzztest sourcetest} set extra(linux.Extra-Robustness) {fuzztest sourcetest} set extra(linux.Device-Two) {fuzztest sourcetest threadtest} set extra(linux.No-lookaside) {fuzztest sourcetest} set extra(linux.Devkit) {fuzztest sourcetest} + set extra(linux.Android) {fuzztest sourcetest} set extra(linux.Apple) {fuzztest sourcetest} set extra(linux.Sanitize) {fuzztest sourcetest} set extra(linux.Default) {fuzztest sourcetest threadtest} set extra(osx.Apple) {fuzztest threadtest} @@ -106,10 +108,11 @@ set build(Sanitize) { CC=clang -fsanitize=address,undefined -fno-sanitize-recover=undefined -DSQLITE_ENABLE_STAT4 -DSQLITE_OMIT_LOOKASIDE=1 -DCONFIG_SLOWDOWN_FACTOR=5.0 + -DSQLITE_ENABLE_RBU --enable-debug --enable-all } set build(Stdcall) { -DUSE_STDCALL=1 @@ -243,10 +246,44 @@ } set build(Locking-Style) { -O2 -DSQLITE_ENABLE_LOCKING_STYLE=1 } + set build(Android) { + -Os + -DHAVE_USLEEP=1 + -DSQLITE_HAVE_ISNAN + -DSQLITE_POWERSAFE_OVERWRITE=1 + -DSQLITE_DEFAULT_FILE_FORMAT=4 + -DSQLITE_DEFAULT_AUTOVACUUM=1 + -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 + -DSQLITE_ENABLE_FTS3 + -DSQLITE_ENABLE_FTS3_BACKWARDS + -DSQLITE_ENABLE_FTS4 + -DSQLITE_SECURE_DELETE + -DSQLITE_ENABLE_BATCH_ATOMIC_WRITE + -DBIONIC_IOCTL_NO_SIGNEDNESS_OVERLOAD + -DSQLITE_ALLOW_ROWID_IN_VIEW + -DSQLITE_ENABLE_BYTECODE_VTAB + -Wno-unused-parameter + -Werror + -DUSE_PREAD64 + -Dfdatasync=fdatasync + -DHAVE_MALLOC_H=1 + -DHAVE_MALLOC_USABLE_SIZE + -DSQLITE_ENABLE_DBSTAT_VTAB + } + # Compile-options used by Android but omitted from these + # tests: + # -DNDEBUG=1 + # -DSQLITE_DEFAULT_LEGACY_ALTER_TABLE + # -DSQLITE_DEFAULT_JOURNAL_SIZE_LIMIT=1048576 + # -DSQLITE_DEFAULT_FILE_PERMISSIONS=0600 + # -DSQLITE_OMIT_BUILTIN_TEST + # -DSQLITE_OMIT_LOAD_EXTENSION + # -DSQLITE_OMIT_COMPILEOPTION_DIAGS + # set build(Apple) { -Os -DHAVE_GMTIME_R=1 -DHAVE_ISNAN=1 -DHAVE_LOCALTIME_R=1 @@ -596,11 +633,16 @@ proc trd_buildscript {config srcdir bMsvc} { trd_import # Ensure that the named configuration exists. if {![info exists build($config)]} { - error "No such build config: $config" + if {$config!="help"} { + puts "No such build config: $config" + } + puts "Available configurations: [lsort [array names build]]" + flush stdout + exit 1 } # Generate and return the script. return [make_script $build($config) $srcdir $bMsvc] } @@ -635,6 +677,5 @@ close $fd } set trd_test_script_properties_cache($path) } - Index: test/trigger9.test ================================================================== --- test/trigger9.test +++ test/trigger9.test @@ -240,14 +240,29 @@ CREATE TRIGGER tr3 INSTEAD OF INSERT ON v1 BEGIN INSERT INTO log VALUES('insert'); END; } -do_catchsql_test 4.2 { - DELETE FROM v1 WHERE rowid=1; -} {1 {no such column: rowid}} +ifcapable !allow_rowid_in_view { + do_catchsql_test 4.2 { + DELETE FROM v1 WHERE rowid=1; + } {1 {no such column: rowid}} -do_catchsql_test 4.3 { - UPDATE v1 SET a=b WHERE rowid=2; -} {1 {no such column: rowid}} + do_catchsql_test 4.3 { + UPDATE v1 SET a=b WHERE rowid=2; + } {1 {no such column: rowid}} +} else { + do_execsql_test 4.2a { + DELETE FROM log; + } + do_catchsql_test 4.2 { + DELETE FROM v1 WHERE rowid=1; + } {0 {}} + do_catchsql_test 4.3 { + UPDATE v1 SET a=b WHERE rowid=2; + } {0 {}} + do_execsql_test 4.3b { + SELECT * FROM log; + } +} finish_test Index: test/unionall.test ================================================================== --- test/unionall.test +++ test/unionall.test @@ -349,11 +349,11 @@ ifcapable vtab { do_catchsql_test 5.30 { SELECT * FROM (t1 NATURAL JOIN pragma_table_xinfo('t1_a') NATURAL JOIN t3) t1 NATURAL JOIN t2 NATURAL JOIN t3 WHERE rowid ISNULL>0 AND 0%y; -} {1 {no such column: rowid}} +} {1 {ambiguous column name: rowid}} } reset_db do_execsql_test 6.0 { CREATE TABLE t1(a,b); Index: test/upsert1.test ================================================================== --- test/upsert1.test +++ test/upsert1.test @@ -265,7 +265,31 @@ } sqlite3_db_config db ENABLE_QPSG 1 do_catchsql_test upsert1-1210 { INSERT INTO t1(a,b) VALUES(1,2) ON CONFLICT(b+?1) DO NOTHING; } {1 {ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint}} + +# 2024-04-11 https://sqlite.org/forum/forumpost/284955a3cd454a15 +# Incorrect value passed into a trigger that fires as the result of +# an upsert. +# +reset_db +do_execsql_test upsert1-1300 { + CREATE TABLE t1(x INT, y TEXT); + INSERT INTO t1 VALUES + (11, printf('%.9000c','a')), + (11, printf('%.9000c','a')), + (33, printf('%.9000c','b')), + (33, printf('%.9000c','b')); + CREATE TABLE t2(x INT UNIQUE, y TEXT); + CREATE TRIGGER r1 BEFORE UPDATE ON t2 BEGIN + SELECT raise(ABORT,'Incorrect old.y value passed to trigger!') + WHERE old.y != new.y; + /* ^^^ This trigger will fire and cause the ABORT if the problem has + ** not been fixed, or if there is a regression. */ + END; + INSERT INTO t2(x, y) SELECT x, y FROM t1 + WHERE true + ON CONFLICT (x) DO UPDATE SET y = excluded.y; +} {} finish_test Index: test/upsert5.test ================================================================== --- test/upsert5.test +++ test/upsert5.test @@ -405,7 +405,49 @@ INSERT INTO t2(a,b,c,e,d) VALUES(1,2,3,4,5) ON CONFLICT(c) DO UPDATE SET b='' ON CONFLICT((SELECT t2 FROM nosuchtable)) DO NOTHING; } {1 {no such table: nosuchtable}} + +# 2024-03-08 https://sqlite.org/forum/forumpost/919c6579c8 +# A redundant ON CONFLICT clause in an upsert can lead to +# index corruption. +# +reset_db +do_execsql_test 3.0 { + CREATE TABLE t1(aa INTEGER PRIMARY KEY, bb INT); + INSERT INTO t1 VALUES(11,22); + CREATE UNIQUE INDEX t1bb ON t1(bb); + REPLACE INTO t1 VALUES(11,33) + ON CONFLICT(bb) DO UPDATE SET aa = 44 + ON CONFLICT(bb) DO UPDATE SET aa = 44; + PRAGMA integrity_check; +} {ok} +do_execsql_test 3.1 { + SELECT * FROM t1 NOT INDEXED; +} {11 33} +do_execsql_test 3.2 { + SELECT * FROM t1 INDEXED BY t1bb; +} {11 33} +do_execsql_test 3.3 { + DROP TABLE t1; + CREATE TABLE t1(aa INTEGER PRIMARY KEY, bb INT, cc INT); + INSERT INTO t1 VALUES(10,21,32),(11,22,33),(12,23,34); + CREATE UNIQUE INDEX t1bb ON t1(bb); + CREATE UNIQUE INDEX t1cc ON t1(cc); + REPLACE INTO t1 VALUES(11,44,55) + ON CONFLICT(bb) DO UPDATE SET aa = 99 + ON CONFLICT(cc) DO UPDATE SET aa = 99 + ON CONFLICT(bb) DO UPDATE SET aa = 99; + PRAGMA integrity_check; +} {ok} +do_execsql_test 3.4 { + SELECT * FROM t1 NOT INDEXED ORDER BY +aa; +} {10 21 32 11 44 55 12 23 34} +do_execsql_test 3.5 { + SELECT * FROM t1 INDEXED BY t1bb ORDER BY +aa; +} {10 21 32 11 44 55 12 23 34} +do_execsql_test 3.6 { + SELECT * FROM t1 INDEXED BY t1cc ORDER BY +aa; +} {10 21 32 11 44 55 12 23 34} finish_test Index: test/vacuum-into.test ================================================================== --- test/vacuum-into.test +++ test/vacuum-into.test @@ -24,17 +24,40 @@ return } forcedelete out.db do_execsql_test vacuum-into-100 { - CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + CREATE TABLE t1( + a INTEGER PRIMARY KEY, + b ANY, + c INT AS (b+1), --- See "2024-04-09" block + CHECK( typeof(b)!='integer' OR b>a-5 ) --- comment below + ); WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<100) INSERT INTO t1(a,b) SELECT x, randomblob(600) FROM c; CREATE INDEX t1b ON t1(b); DELETE FROM t1 WHERE a%2; SELECT count(*), sum(a), sum(length(b)) FROM t1; } {50 2550 30000} + +# Update 2024-04-09 for forum post eec177d68fe7fa2c. +# +# VACUUM INTO is sensitive to tables holding both generated columns +# and CHECK constraints. +# +# CHECK constraints are ignored for read-only databases in order to save +# memory (see check-in 34ddf02d3d21151b on 2014-05-21). But the xfer +# optimization normally only works if CHECK constraints match between the +# source and destination tables. So the xfer optimization was not +# working for VACUUM INTO when the source was a read-only database and the +# table held CHECK constraints. But if the table has generated columns, +# then the xfer optimization is required or else VACUUM will raise an +# error. +# +# Fix this by ignoring CHECK constraints when determining whether or not +# the xfer optimization can run while doing VACUUM. + do_execsql_test vacuum-into-110 { VACUUM main INTO 'out.db'; } {} sqlite3 db2 out.db do_test vacuum-into-120 { @@ -86,15 +109,25 @@ VACUUM INTO target2() } {1 {no such function: target2}} # The ability to VACUUM INTO a read-only database db close +if {$tcl_platform(platform)=="windows"} { + file attributes test.db -readonly 1 +} else { + file attributes test.db -permissions 292 ;# 292 == 0444 +} sqlite3 db test.db -readonly 1 forcedelete test.db2 do_execsql_test vacuum-into-500 { VACUUM INTO 'test.db2'; } +if {$tcl_platform(platform)=="windows"} { + file attributes test.db -readonly 0 +} else { + file attributes test.db -permissions 420 ;# 420 = 0644 +} sqlite3 db2 test.db2 do_test vacuum-into-510 { db2 eval {SELECT name FROM sqlite_master ORDER BY 1} } {t1 t1b t2} db2 close ADDED test/values.test Index: test/values.test ================================================================== --- /dev/null +++ test/values.test @@ -0,0 +1,715 @@ +# 2024 March 3 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix values + + +do_execsql_test 1.0 { + CREATE TABLE x1(a, b, c); +} + + +explain_i { + INSERT INTO x1(a, b, c) VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4); +} +do_execsql_test 1.1.1 { + INSERT INTO x1 VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4); +} +do_execsql_test 1.1.2 { + SELECT * FROM x1; +} { + 1 1 1 + 2 2 2 + 3 3 3 + 4 4 4 +} + +do_execsql_test 1.2.0 { + DELETE FROM x1 +} +do_execsql_test 1.2.1 { + INSERT INTO x1 VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3) UNION ALL SELECT 4, 4, 4; + SELECT * FROM x1; +} {1 1 1 2 2 2 3 3 3 4 4 4} + +sqlite3_limit db SQLITE_LIMIT_COMPOUND_SELECT 4 + +do_execsql_test 1.2.2 { + DELETE FROM x1; + INSERT INTO x1 + VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5) + UNION ALL SELECT 6, 6, 6; + SELECT * FROM x1; +} {1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6} + +do_execsql_test 1.2.3 { + DELETE FROM x1; + INSERT INTO x1 + VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4) + UNION ALL SELECT 6, 6, 6; + SELECT * FROM x1; +} {1 1 1 2 2 2 3 3 3 4 4 4 6 6 6} + +do_execsql_test 1.2.4 { + DELETE FROM x1; + INSERT INTO x1 VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3) UNION ALL SELECT 6, 6, 6; + SELECT * FROM x1; +} { + 1 1 1 + 2 2 2 + 3 3 3 + 6 6 6 +} + +set a 4 +set b 5 +set c 6 +do_execsql_test 1.2.5 { + DELETE FROM x1; + INSERT INTO x1 + VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), + (4, 4, $a), (5, 5, $b), (6, 6, $c) +} + +do_execsql_test 1.2.6 { + SELECT * FROM x1; +} { + 1 1 1 + 2 2 2 + 3 3 3 + 4 4 4 + 5 5 5 + 6 6 6 +} + +#------------------------------------------------------------------------- +# SQLITE_LIMIT_COMPOUND_SELECT set to 0. +# +reset_db + +do_execsql_test 2.0 { + CREATE TABLE x1(a, b, c); +} + +sqlite3_limit db SQLITE_LIMIT_COMPOUND_SELECT 3 + +do_catchsql_test 2.1.1 { + INSERT INTO x1 VALUES + (1, 1, 1), + (2, 2, 2), + (3, 3, 3), + (4, 4, 4), + (5, 5, 5), + (6, 6, 6), + (7, 7, 7), + (8, 8, 8), + (9, 9, 9), + (10, 10, 10, 10) +} {1 {all VALUES must have the same number of terms}} + +do_catchsql_test 2.1.2 { + INSERT INTO x1 VALUES + (1, 1, 1), + (2, 2, 2, 2), + (3, 3, 3), + (4, 4, 4), + (5, 5, 5), + (6, 6, 6), + (7, 7, 7), + (8, 8, 8), + (9, 9, 9), + (10, 10, 10) +} {1 {all VALUES must have the same number of terms}} + +sqlite3_limit db SQLITE_LIMIT_COMPOUND_SELECT 0 + +do_execsql_test 2.2 { + INSERT INTO x1 VALUES + (1, 1, 1), + (2, 2, 2), + (3, 3, 3), + (4, 4, 4), + (5, 5, 5), + (6, 6, 6), + (7, 7, 7), + (8, 8, 8), + (9, 9, 9), + (10, 10, 10) +} {} +do_execsql_test 2.3 { + INSERT INTO x1 VALUES + (1, 1, 1), + (2, 2, 2), + (3, 3, 3), + (4, 4, 4), + (5, 5, 5), + (6, 6, 6), + (7, 7, 7), + (8, 8, 8), + (9, 9, 9), + (10, 10, 10) + UNION ALL + SELECT 5, 12, 12 + ORDER BY 1 +} {} + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 3.0 { + CREATE TABLE y1(x, y); +} + +do_execsql_test 3.1.1 { + DELETE FROM y1; + INSERT INTO y1 VALUES(1, 2), (3, 4), (row_number() OVER (), 5); +} +do_execsql_test 3.1.2 { + SELECT * FROM y1; +} {1 2 3 4 1 5} +do_execsql_test 3.2.1 { + DELETE FROM y1; + INSERT INTO y1 VALUES(1, 2), (3, 4), (row_number() OVER (), 6) + , (row_number() OVER (), 7) +} +do_execsql_test 3.1.2 { + SELECT * FROM y1; +} {1 2 3 4 1 6 1 7} + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 4.0 { + CREATE TABLE x1(a PRIMARY KEY, b) WITHOUT ROWID; +} + +foreach {tn iLimit} {1 0 2 3} { + sqlite3_limit db SQLITE_LIMIT_COMPOUND_SELECT $iLimit + + do_execsql_test 4.1.1 { + DELETE FROM x1; + INSERT INTO x1 VALUES + (1, 1), + (2, (SELECT * FROM (VALUES('a'), ('b'), ('c'), ('d')) )) + } + do_execsql_test 4.1.2 { + SELECT * FROM x1 + } {1 1 2 a} + + do_execsql_test 4.2.1 { + DELETE FROM x1; + INSERT INTO x1 VALUES + (1, 1), + (2, 2), + (3, 3), + (4, 4), + (5, (SELECT * FROM (VALUES('a'), ('b'), ('c'), ('d')) )) + } + do_execsql_test 4.2.2 { + SELECT * FROM x1 + } {1 1 2 2 3 3 4 4 5 a} + + do_execsql_test 4.3.1 { + DELETE FROM x1; + INSERT INTO x1 VALUES + (1, (SELECT * FROM (VALUES('a'), ('b'), ('c'), ('d'), ('e')) )) + } + do_execsql_test 4.3.2 { + SELECT * FROM x1 + } {1 a} +} + +#------------------------------------------------------------------------ +reset_db + +do_execsql_test 5.0 { + CREATE VIEW v1 AS VALUES(1, 2, 3), (4, 5, 6), (7, 8, 9); +} +do_execsql_test 5.1 { + SELECT * FROM v1 +} {1 2 3 4 5 6 7 8 9} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 6.0 { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES(1), (2); +} + +do_execsql_test 6.1 { + SELECT ( VALUES( x ), ( x ) ) FROM t1; +} {1 2} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 6.0 { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES('x'), ('y'); +} + +do_execsql_test 6.1 { + SELECT * FROM t1, (VALUES(1), (2)) +} {x 1 x 2 y 1 y 2} + +do_execsql_test 6.2 { + VALUES(CAST(44 AS REAL)),(55); +} {44.0 55} + +#------------------------------------------------------------------------ +do_execsql_test 7.1 { + WITH x1(a, b) AS ( + VALUES(1, 2), ('a', 'b') + ) + SELECT * FROM x1 one, x1 two +} { + 1 2 1 2 + 1 2 a b + a b 1 2 + a b a b +} + +#------------------------------------------------------------------------- +reset_db + +set VVV { + ( VALUES('a', 'b'), ('c', 'd'), (123, NULL) ) +} +set VVV2 { + ( + SELECT 'a' AS column1, 'b' AS column2 + UNION ALL SELECT 'c', 'd' UNION ALL SELECT 123, NULL + ) +} + +do_execsql_test 8.0 { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES('d'), (NULL), (123) +} +foreach {tn q res} { + 1 "SELECT * FROM t1 LEFT JOIN VVV" { + d a b d c d d 123 {} + {} a b {} c d {} 123 {} + 123 a b 123 c d 123 123 {} + } + + 2 "SELECT * FROM t1 LEFT JOIN VVV ON (column1=x)" { + d {} {} + {} {} {} + 123 123 {} + } + + 3 "SELECT * FROM t1 RIGHT JOIN VVV" { + d a b d c d d 123 {} + {} a b {} c d {} 123 {} + 123 a b 123 c d 123 123 {} + } + + 4 "SELECT * FROM t1 RIGHT JOIN VVV ON (column1=x)" { + 123 123 {} + {} a b + {} c d + } + + 5 "SELECT * FROM t1 FULL OUTER JOIN VVV ON (column1=x)" { + d {} {} + {} {} {} + 123 123 {} + {} a b + {} c d + } + + 6 "SELECT count(*) FROM VVV" { 3 } + + 7 "SELECT (SELECT column1 FROM VVV)" { a } + + 8 "SELECT * FROM VVV UNION ALL SELECT * FROM VVV" { + a b c d 123 {} + a b c d 123 {} + } + + 9 "SELECT * FROM VVV INTERSECT SELECT * FROM VVV" { + 123 {} a b c d + } + + 10 "SELECT * FROM VVV eXCEPT SELECT * FROM VVV" { } + + 11 "SELECT * FROM VVV eXCEPT SELECT 'a', 'b'" { 123 {} c d } + +} { + set q1 [string map [list VVV $VVV] $q] + set q2 [string map [list VVV $VVV2] $q] + set q3 "WITH VVV AS $VVV $q" + + do_execsql_test 8.1.$tn.1 $q1 $res + do_execsql_test 8.1.$tn.2 $q2 $res + do_execsql_test 8.1.$tn.3 $q3 $res +} + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 9.1 { + VALUES(456), (123), (NULL) UNION ALL SELECT 122 ORDER BY 1 +} { {} 122 123 456 } + +do_execsql_test 9.2 { + VALUES (1, 2), (3, 4), ( + ( SELECT column1 FROM ( VALUES (5, 6), (7, 8) ) ), + ( SELECT max(column2) FROM ( VALUES (5, 1), (7, 6) ) ) + ) +} { 1 2 3 4 5 6 } + +do_execsql_test 10.1 { + CREATE TABLE a2(a, b, c DEFAULT 'xyz'); +} +do_execsql_test 10.2 { + INSERT INTO a2(a) VALUES(3),(4); +} + +#------------------------------------------------------------------------- +reset_db +ifcapable fts5 { + do_execsql_test 11.0 { + CREATE VIRTUAL TABLE ft USING fts3(x); + } + do_execsql_test 11.1 { + INSERT INTO ft VALUES('one'), ('two'); + } +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 12.0 { + CREATE TABLE t1(a, b); +} +do_execsql_test 12.1 { + INSERT INTO t1 SELECT 1, 2 UNION ALL VALUES(3, 4), (5, 6); +} +do_execsql_test 12.2 { + SELECT * FROM t1 +} {1 2 3 4 5 6} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 13.0 { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES('xyz'); + + SELECT ( + VALUES( (max(substr('abc', 1, 1), x)) ), + (123), + (456) + ) + FROM t1; +} {xyz} + +do_catchsql_test 13.1 { + VALUES(300), (zeroblob(300) OVER win); +} {1 {zeroblob() may not be used as a window function}} + +#-------------------------------------------------------------------------- +reset_db +do_execsql_test 14.1 { + PRAGMA encoding = utf16; + CREATE TABLE t1(a, b); +} {} + +db close +sqlite3 db test.db + +do_execsql_test 14.2 { + INSERT INTO t1 VALUES + (17, 'craft'), + (16, 'urtlek' IN(1,2,3)); +} + +#-------------------------------------------------------------------------- +# +reset_db +do_eqp_test 15.1 { + VALUES(1),(2),(3),(4),(5); +} { + QUERY PLAN + `--SCAN 5-ROW VALUES CLAUSE +} +do_execsql_test 15.2 { + CREATE TABLE t1(a,b); +} +do_eqp_test 15.3 { + INSERT INTO t1 VALUES + (1,2),(3,4),(7,8); +} { + QUERY PLAN + `--SCAN 3-ROW VALUES CLAUSE +} +do_eqp_test 15.4 { + INSERT INTO t1 VALUES + (1,2),(3,4),(7,8), + (5,row_number()OVER()); +} { + QUERY PLAN + `--COMPOUND QUERY + |--LEFT-MOST SUBQUERY + | `--SCAN 3-ROW VALUES CLAUSE + `--UNION ALL + |--CO-ROUTINE (subquery-xxxxxx) + | `--SCAN CONSTANT ROW + `--SCAN (subquery-xxxxxx) +} +do_eqp_test 15.5 { + SELECT * FROM (VALUES(1),(2),(3),(4),(5),(6)), (VALUES('a'),('b'),('c')); +} { + QUERY PLAN + |--SCAN 6-ROW VALUES CLAUSE + `--SCAN 3-ROW VALUES CLAUSE +} +do_execsql_test 15.6 { + CREATE TABLE t2(x,y); +} +do_eqp_test 15.7 { + SELECT * FROM t2 UNION ALL VALUES(1,2),(3,4),(5,6),(7,8); +} { + QUERY PLAN + `--COMPOUND QUERY + |--LEFT-MOST SUBQUERY + | `--SCAN t2 + `--UNION ALL + `--SCAN 4-ROW VALUES CLAUSE +} + +#-------------------------------------------------------------------------- +# The VALUES-as-coroutine optimization can be applied to later rows of +# a VALUES clause even if earlier rows do not qualify. +# +reset_db +do_execsql_test 16.1 { + CREATE TABLE t1(a,b); +} +do_execsql_test 16.2 { + BEGIN; + INSERT INTO t1 VALUES(1,2),(3,4),(5,6), + (7,row_number()OVER()), + (9,10), (11,12), (13,14), (15,16); + SELECT * FROM t1 ORDER BY a, b; + ROLLBACK; +} {1 2 3 4 5 6 7 1 9 10 11 12 13 14 15 16} +do_eqp_test 16.3 { + INSERT INTO t1 VALUES(1,2),(3,4),(5,6), + (7,row_number()OVER()), + (9,10), (11,12), (13,14), (15,16); +} { + QUERY PLAN + `--COMPOUND QUERY + |--LEFT-MOST SUBQUERY + | `--SCAN 3-ROW VALUES CLAUSE + |--UNION ALL + | |--CO-ROUTINE (subquery-xxxxxx) + | | `--SCAN CONSTANT ROW + | `--SCAN (subquery-xxxxxx) + `--UNION ALL + `--SCAN 4-ROW VALUES CLAUSE +} +do_execsql_test 16.4 { + BEGIN; + INSERT INTO t1 VALUES + (1,row_number()OVER()), + (2,3), (4,5), (6,7); + SELECT * FROM t1 ORDER BY a, b; + ROLLBACK; +} {1 1 2 3 4 5 6 7} +do_eqp_test 16.5 { + INSERT INTO t1 VALUES + (1,row_number()OVER()), + (2,3), (4,5), (6,7); +} { + QUERY PLAN + `--COMPOUND QUERY + |--LEFT-MOST SUBQUERY + | |--CO-ROUTINE (subquery-xxxxxx) + | | `--SCAN CONSTANT ROW + | `--SCAN (subquery-xxxxxx) + `--UNION ALL + `--SCAN 3-ROW VALUES CLAUSE +} +do_execsql_test 16.6 { + BEGIN; + INSERT INTO t1 VALUES + (1,2),(3,4), + (5,row_number()OVER()), + (7,8),(9,10),(11,12), + (13,row_number()OVER()), + (15,16),(17,18),(19,20),(21,22); + SELECT * FROM t1 ORDER BY a, b; + ROLLBACK; +} { 1 2 3 4 5 1 7 8 9 10 11 12 13 1 15 16 17 18 19 20 21 22} +do_eqp_test 16.7 { + INSERT INTO t1 VALUES + (1,2),(3,4), + (5,row_number()OVER()), + (7,8),(9,10),(11,12), + (13,row_number()OVER()), + (15,16),(17,18),(19,20),(21,22); +} { + QUERY PLAN + `--COMPOUND QUERY + |--LEFT-MOST SUBQUERY + | `--SCAN 2-ROW VALUES CLAUSE + |--UNION ALL + | |--CO-ROUTINE (subquery-xxxxxx) + | | `--SCAN CONSTANT ROW + | `--SCAN (subquery-xxxxxx) + |--UNION ALL + | `--SCAN 3-ROW VALUES CLAUSE + |--UNION ALL + | |--CO-ROUTINE (subquery-xxxxxx) + | | `--SCAN CONSTANT ROW + | `--SCAN (subquery-xxxxxx) + `--UNION ALL + `--SCAN 4-ROW VALUES CLAUSE +} + +#-------------------------------------------------------------------------- +# 2024-03-23 dbsqlfuzz crash-c2c5e7e08b7e489d270a26d895077a03f678c33b +# +do_execsql_test 17.1 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1 AS SELECT * FROM (VALUES(1,2), (3,4 IN (1,2,3))); +} + +do_execsql_test 17.2 { + SELECT * FROM t1 +} {1 2 3 0} + +# 2024-03-25 dbsqlfuzz crash-74cf7c9904360322a6c917e4934b127543d1cd51 +# +do_catchsql_test 18.1 { + DROP TABLE t1; + CREATE TABLE t1(x INTEGER PRIMARY KEY); + INSERT INTO t1 VALUES(RAISE(IGNORE)),(0); +} {1 {RAISE() may only be used within a trigger-program}} +do_catchsql_test 18.2 { + DROP TABLE t1; + CREATE TABLE t1(x INTEGER PRIMARY KEY, y, z); + CREATE TRIGGER r2 AFTER INSERT ON t1 BEGIN + INSERT INTO t1(y) VALUES(RAISE(IGNORE)),(0); + END; + INSERT INTO t1 VALUES(1,2,3); + SELECT * FROM t1; +} {0 {1 2 3}} +do_catchsql_test 18.3.1 { + DROP TABLE t1; + CREATE TABLE t1(x INTEGER PRIMARY KEY, y, z); + CREATE TRIGGER r2 AFTER INSERT ON t1 BEGIN + INSERT INTO t1(y) VALUES(RAISE(ABORT,'error 18.3')),(0); + END; + INSERT INTO t1 VALUES(1,2,3); +} {1 {error 18.3}} +do_execsql_test 18.3.2 { + SELECT * FROM t1; +} {} +do_catchsql_test 18.4.1 { + DROP TABLE t1; + CREATE TABLE t1(x INTEGER PRIMARY KEY, y, z); + CREATE TRIGGER r2 AFTER INSERT ON t1 BEGIN + INSERT INTO t1(y) VALUES(1),(RAISE(ABORT,'error 18.4')),(0); + END; + INSERT INTO t1 VALUES(1,2,3); +} {1 {error 18.4}} +do_execsql_test 18.4.2 { + SELECT * FROM t1; +} {} +do_catchsql_test 18.5.1 { + DROP TABLE t1; + CREATE TABLE t1(x INTEGER PRIMARY KEY, y, z); + CREATE TRIGGER r2 AFTER INSERT ON t1 BEGIN + INSERT INTO t1(y) VALUES(1), + (CASE WHEN new.z>7 THEN RAISE(ABORT,'error 18.5') ELSE 2 END); + END; + INSERT INTO t1 VALUES(1,2,3); + SELECT * FROM t1; +} {0 {1 2 3 2 1 {} 3 2 {}}} +do_catchsql_test 18.5.2 { + DELETE FROM t1; + INSERT INTO t1 VALUES(1,2,13); +} {1 {error 18.5}} +do_catchsql_test 18.5.3 { + SELECT * FROM t1; +} {0 {}} + +# 2024-04-18 dbsqlfuzz crash-bde3bf80aedf25afa56e2997a0545a314765d3f8 +# Verify that the VALUES expressions used as an argument to an outer +# join work correctly. +# +reset_db +db null NULL +do_execsql_test 19.1 { + CREATE TABLE t1(a INT, b INT); + INSERT INTO t1 VALUES(11,22); + SELECT * FROM t1 LEFT JOIN (VALUES(33,44),(55,66)) AS t2 ON a=b; +} {11 22 NULL NULL} +do_execsql_test 19.2 { + SELECT * FROM (VALUES(33,44),(55,66)) AS t2 RIGHT JOIN t1 ON a=b; +} {NULL NULL 11 22} +do_execsql_test 19.3 { + SELECT *, '|' FROM t1 FULL JOIN (VALUES(33,44),(55,66)) AS t2 ON a=b + ORDER BY +column1 +} {11 22 NULL NULL | NULL NULL 33 44 | NULL NULL 55 66 |} +do_execsql_test 19.4 { + SELECT *, '|' FROM (VALUES(33,44),(55,66)) AS t2 FULL JOIN t1 ON a=b + ORDER BY +column1 +} {NULL NULL 11 22 | 33 44 NULL NULL | 55 66 NULL NULL |} + +# 2024-04-21 dbsqlfuzz 6fd1ff3a64bef4a6c092e8d757548e95698b0df5 +# A continuation of the 2024-04-18 problem above. We have to create +# Pseudo-cursor that is always NULL on the viaCoroutine loop in case +# there are OP_Columns generated against it by the sub-WHERE clause. +# +db null N +do_execsql_test 19.5 { + DROP TABLE IF EXISTS t1; + DROP TABLE IF EXISTS t2; + DROP TABLE IF EXISTS t3; + CREATE TABLE t1(a,b); INSERT INTO t1 VALUES(1,2); + CREATE TABLE t2(column1,column2); INSERT INTO t2 VALUES(11,22),(33,44); + CREATE TABLE t3(d,e); INSERT INTO t3 VALUES(3,4); +} +do_execsql_test 19.6 { + -- output verify using PG 14.2 + SELECT * + FROM t1 CROSS JOIN t2 FULL JOIN t3 ON a=d + ORDER BY +d, +column1; +} {1 2 11 22 N N + 1 2 33 44 N N + N N N N 3 4} +do_execsql_test 19.7 { + SELECT * + FROM t1 CROSS JOIN (VALUES(11,22),(33,44)) FULL JOIN t3 ON a=d + ORDER BY +d, +column1; +} {1 2 11 22 N N + 1 2 33 44 N N + N N N N 3 4} +do_execsql_test 19.8 { + -- output verified using PG 14.2 + SELECT * + FROM t1 CROSS JOIN t2 FULL JOIN t3 ON a=d + WHERE column1 IS NULL; +} {N N N N 3 4} +do_execsql_test 19.9 { + SELECT * + FROM t1 CROSS JOIN (VALUES(11,22),(33,44)) FULL JOIN t3 ON a=d + WHERE column1 IS NULL; +} {N N N N 3 4} + +finish_test ADDED test/valuesfault.test Index: test/valuesfault.test ================================================================== --- /dev/null +++ test/valuesfault.test @@ -0,0 +1,37 @@ +# 2024 March 3 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix valuesfault +source $testdir/malloc_common.tcl + + +do_execsql_test 1.0 { + CREATE TABLE x1(a, b, c); +} +faultsim_save_and_close + +do_faultsim_test 1 -prep { + faultsim_restore_and_reopen + sqlite3_limit db SQLITE_LIMIT_COMPOUND_SELECT 2 +} -body { + execsql { + INSERT INTO x1 VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4); + } +} -test { + faultsim_test_result {0 {}} +} + + +finish_test Index: test/view.test ================================================================== --- test/view.test +++ test/view.test @@ -798,7 +798,30 @@ } { 0 a INT 0 {} 0 1 b BLOB 0 {} 0 } do_execsql_test view-30.2 { PRAGMA table_info = t2; } { 0 a INT 0 {} 0 1 b BLOB 0 {} 0 } } + +#----------------------------------------------------------------------- +# 2024-04-25 Trying to make type information on compound subqueries +# more predictable and rational. +# +reset_db +do_execsql_test view-31.1 { + CREATE TABLE x2(b TEXT); + CREATE TABLE x1(a TEXT); + INSERT INTO x1 VALUES('123'); + -- Two queries get the same result even though the order of terms + -- in the CTE is reversed + WITH c(x) AS ( SELECT b FROM x2 UNION SELECT 123 ) + SELECT count(*) FROM x1 WHERE a IN c; + WITH c(x) AS ( SELECT 123 UNION SELECT b FROM x2 ) + SELECT count(*) FROM x1 WHERE a IN c; +} {0 0} +do_execsql_test view-31.2 { + CREATE TABLE t3(a INTEGER, b TEXT); + INSERT INTO t3 VALUES(123, 123); + WITH s AS ( VALUES(123), (456) ) SELECT * FROM t3 WHERE b IN s; +} {123 123} + finish_test ADDED test/vtabL.test Index: test/vtabL.test ================================================================== --- /dev/null +++ test/vtabL.test @@ -0,0 +1,75 @@ +# 2024-03-26 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix vtabL + +ifcapable !vtab { + finish_test + return +} + +register_tcl_module db + +proc vtab_command {method args} { + switch -- $method { + xConnect { + return $::create_table_sql + } + } + + return {} +} + +foreach {tn cts} { + 1 {SELECT 123} + 2 {SELECT 123, 456} + 3 {INSERT INTO t1 VALUES(5, 6)} + 4 {CREATE INDEX i1 ON t1(a)} + 5 {CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN SELECT 1; END;} + 6 {DROP TABLE nosuchtable} + 7 {DROP TABLE x1} + 8 {DROP TABLE t1} +} { + set ::create_table_sql $cts + do_catchsql_test 1.$tn { + CREATE VIRTUAL TABLE x1 USING tcl(vtab_command); + } {1 {declare_vtab: syntax error}} +} + +foreach {tn cts} { + 9 {CREATE TABLE xyz AS SELECT * FROM sqlite_schema} + 10 {CREATE TABLE xyz AS SELECT 1 AS 'col'} +} { + set ::create_table_sql $cts + do_catchsql_test 1.$tn { + CREATE VIRTUAL TABLE x1 USING tcl(vtab_command); + } {1 {declare_vtab: SQL logic error}} +} + +foreach {tn cts} { + 1 {CREATE TABLE IF NOT EXISTS t1(a, b)} + 2 {CREATE TABLE ""(a, b PRIMARY KEY) WITHOUT ROWID} +} { + set ::create_table_sql $cts + execsql { DROP TABLE IF EXISTS x1 } + do_execsql_test 2.$tn.1 { + CREATE VIRTUAL TABLE x1 USING tcl(vtab_command); + } + do_execsql_test 2.$tn.2 { + SELECT a, b FROM x1 + } +} + +finish_test + Index: test/whereL.test ================================================================== --- test/whereL.test +++ test/whereL.test @@ -47,10 +47,37 @@ QUERY PLAN |--SEARCH t1 USING INDEX sqlite_autoindex_t1_1 (a=?) |--SEARCH t2 USING INDEX sqlite_autoindex_t2_1 (a=?) `--SCAN t3 } +do_eqp_test 121 { + SELECT * FROM t1, t2, t3 + WHERE t1.a=t2.a AND t2.a=t3.j AND t3.j=abs(5) + ORDER BY t1.a; +} { + QUERY PLAN + |--SEARCH t1 USING INDEX sqlite_autoindex_t1_1 (a=?) + |--SEARCH t2 USING INDEX sqlite_autoindex_t2_1 (a=?) + `--SCAN t3 +} + +# The sqlite3ExprIsConstant() routine does not believe that +# the expression "coalesce(5,random())" is constant. So the +# optimization does not apply in this case. +# +sqlite3_create_function db +do_eqp_test 122 { + SELECT * FROM t1, t2, t3 + WHERE t1.a=t2.a AND t2.a=t3.j AND t3.j=coalesce(5,random()) + ORDER BY t1.a; +} { + QUERY PLAN + |--SCAN t3 + |--SEARCH t1 USING INDEX sqlite_autoindex_t1_1 (a=?) + |--SEARCH t2 USING INDEX sqlite_autoindex_t2_1 (a=?) + `--USE TEMP B-TREE FOR ORDER BY +} # Constant propagation in the face of collating sequences: # do_execsql_test 200 { CREATE TABLE c3(x COLLATE binary, y COLLATE nocase, z COLLATE binary); ADDED test/whereN.test Index: test/whereN.test ================================================================== --- /dev/null +++ test/whereN.test @@ -0,0 +1,103 @@ +# 2024-04-02 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# Tests for the whereInterstageHeuristic() routine in the query planner. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix whereN + +# The following is a simplified and "sanitized" version of the original +# real-world query that brought the problem to light. +# +# The issue is a slow query. The answer is correct, but it was taking too +# much time, because it was doing a full table scan rather than an indexed +# lookup. +# +# The problem was that the query planner was overestimating the number of +# output rows. The estimated number of output rows is accurate if the +# DSNAME parameter is "ds-one". In that case, a large fraction of the rows +# in "violation" end up being output. The query planner correctly deduces +# that it is faster to do a full table scan of the large "violation" table +# to avoid the after-query sort that implements the ORDER BY clause. However, +# if the DSNAME is "ds-two", then only a few rows (about 6) are generated, +# and it is much much faster to do an indexed lookup of "violation" followed +# by a sort operation to implement ORDER BY +# +# The problem, of course, is that the query planner has no way of knowing +# in advance how many rows will be generated. The query planner tries to +# estimate a worst case, which is a large number of output rows, and it picks +# the best plan for that case. However, the plan choosen is very inefficient +# when the number of output rows is small. +# +# The whereInterstageHeuristic() routine in the query planner attempts to +# correct this by adjusting the query plan such that it avoids the very bad +# query plan for a small number of rows, at the expense of a slightly less +# efficient plan for a large number of rows. The large number of rows case +# is perhaps 5% slower with the revised plan, but the small number of +# rows case is around 100 times faster. That seems like a good tradeoff. +# +do_execsql_test 1.0 { + CREATE TABLE datasource(dsid INT, name TEXT); + INSERT INTO datasource VALUES(1,'ds-one'),(2,'ds-two'),(3,'ds-three'); + CREATE INDEX ds1 ON datasource(name, dsid); + + CREATE TABLE rule(rid INT, team_id INT, dsid INT); + WITH RECURSIVE c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<9) + INSERT INTO rule(rid,team_id,dsid) SELECT n, 1, 1 FROM c; + WITH RECURSIVE c(n) AS (VALUES(10) UNION ALL SELECT n+1 FROM c WHERE n<24) + INSERT INTO rule(rid,team_id,dsid) SELECT n, 2, 2 FROM c; + CREATE INDEX rule2 ON rule(dsid, rid); + + CREATE TABLE violation(vid INT, rid INT, vx BLOB); + /*** Uncomment to insert actual data + WITH src(rid, cnt) AS (VALUES(1,3586),(2,1343),(3,6505),(5,76230), + (6,740),(7,287794),(8,457),(12,1), + (14,1),(16,1),(17,1),(18,1),(19,1)) + INSERT INTO violation(vid, rid, vx) + SELECT rid*1000000+value, rid, randomblob(15) + FROM src, generate_series(1,cnt); + ***/ + CREATE INDEX v1 ON violation(rid, vid); + CREATE INDEX v2 ON violation(vid); + ANALYZE; + DELETE FROM sqlite_stat1; + DROP TABLE IF EXISTS sqlite_stat4; + INSERT INTO sqlite_stat1 VALUES + ('violation','v2','376661 1'), + ('violation','v1','376661 28974 1'), + ('rule','rule2','24 12 1'), + ('datasource','ds1','3 1 1'); + ANALYZE sqlite_schema; +} +set DSNAME ds-two ;# Only a few rows. Change to "ds-one" for many rows. +do_eqp_test 1.1 { + SELECT count(*), length(group_concat(vx)) FROM ( + SELECT V.* + FROM datasource DS, rule R, violation V + WHERE V.rid=R.rid + AND R.dsid=DS.dsid + AND DS.name=$DSNAME + ORDER BY V.vid desc + ); +} { + QUERY PLAN + |--CO-ROUTINE (subquery-xxxxxx) + | |--SEARCH DS USING COVERING INDEX ds1 (name=?) + | |--SEARCH R USING COVERING INDEX rule2 (dsid=?) + | |--SEARCH V USING INDEX v1 (rid=?) + | `--USE TEMP B-TREE FOR ORDER BY + `--SCAN (subquery-xxxxxx) +} +# ^^^^---- We want to see three SEARCH terms. No SCAN terms. +# The ORDER BY is implemented by a separate sorter pass. + +finish_test Index: test/window1.test ================================================================== --- test/window1.test +++ test/window1.test @@ -2373,7 +2373,25 @@ } do_execsql_test 77.2 { SELECT max(~likely(x)) FILTER (WHERE true) FROM t1 INDEXED BY t1x GROUP BY x; } {-2 -3 -5 -9} +# 2024-05-23 https://sqlite.org/forum/forumpost/bf8f43aa522c2299 +# +# A bug in group_concat() when used as a window function, reported +# just hours after the 3.46.0 release, though first appearing +# in 3.28.0. +# +# When used as a window function, a group_concat() was not +# correctly distinguishing between NULL and empty-string for +# its return value. +# +do_execsql_test 78.1 { + SELECT quote(group_concat(x) OVER ()) FROM (SELECT '' AS x); +} '' +do_execsql_test 78.2 { + SELECT quote(group_concat(x) OVER ( + ORDER BY y RANGE BETWEEN 1 FOLLOWING AND 2 FOLLOWING + )) FROM (SELECT 'abc' AS x, 1 AS y); +} NULL finish_test Index: tool/lemon.c ================================================================== --- tool/lemon.c +++ tool/lemon.c @@ -56,10 +56,86 @@ ** Compilers are getting increasingly pedantic about type conversions ** as C evolves ever closer to Ada.... To work around the latest problems ** we have to define the following variant of strlen(). */ #define lemonStrlen(X) ((int)strlen(X)) + +/* +** Header on the linked list of memory allocations. +*/ +typedef struct MemChunk MemChunk; +struct MemChunk { + MemChunk *pNext; + size_t sz; + /* Actually memory follows */ +}; + +/* +** Global linked list of all memory allocations. +*/ +static MemChunk *memChunkList = 0; + +/* +** Wrappers around malloc(), calloc(), realloc() and free(). +** +** All memory allocations are kept on a doubly-linked list. The +** lemon_free_all() function can be called prior to exit to clean +** up any memory leaks. +** +** This is not necessary. But compilers and getting increasingly +** fussy about memory leaks, even in command-line programs like Lemon +** where they do not matter. So this code is provided to hush the +** warnings. +*/ +static void *lemon_malloc(size_t nByte){ + MemChunk *p; + if( nByte<0 ) return 0; + p = malloc( nByte + sizeof(MemChunk) ); + if( p==0 ){ + fprintf(stderr, "Out of memory. Failed to allocate %lld bytes.\n", + (long long int)nByte); + exit(1); + } + p->pNext = memChunkList; + p->sz = nByte; + memChunkList = p; + return (void*)&p[1]; +} +static void *lemon_calloc(size_t nElem, size_t sz){ + void *p = lemon_malloc(nElem*sz); + memset(p, 0, nElem*sz); + return p; +} +static void lemon_free(void *pOld){ + if( pOld ){ + MemChunk *p = (MemChunk*)pOld; + p--; + memset(pOld, 0, p->sz); + } +} +static void *lemon_realloc(void *pOld, size_t nNew){ + void *pNew; + MemChunk *p; + if( pOld==0 ) return lemon_malloc(nNew); + p = (MemChunk*)pOld; + p--; + if( p->sz>=nNew ) return pOld; + pNew = lemon_malloc( nNew ); + memcpy(pNew, pOld, p->sz); + return pNew; +} + +/* Free all outstanding memory allocations. +** Do this right before exiting. +*/ +static void lemon_free_all(void){ + while( memChunkList ){ + MemChunk *pNext = memChunkList->pNext; + free( memChunkList ); + memChunkList = pNext; + } +} /* ** Compilers are starting to complain about the use of sprintf() and strcpy(), ** saying they are unsafe. So we define our own versions of those routines too. ** @@ -495,11 +571,11 @@ struct action *newaction; if( actionfreelist==0 ){ int i; int amt = 100; - actionfreelist = (struct action *)calloc(amt, sizeof(struct action)); + actionfreelist = (struct action *)lemon_calloc(amt, sizeof(struct action)); if( actionfreelist==0 ){ fprintf(stderr,"Unable to allocate memory for a new parser action."); exit(1); } for(i=0; iaAction[N].lookahead) /* Free all memory associated with the given acttab */ void acttab_free(acttab *p){ - free( p->aAction ); - free( p->aLookahead ); - free( p ); + lemon_free( p->aAction ); + lemon_free( p->aLookahead ); + lemon_free( p ); } /* Allocate a new acttab structure */ acttab *acttab_alloc(int nsymbol, int nterminal){ - acttab *p = (acttab *) calloc( 1, sizeof(*p) ); + acttab *p = (acttab *) lemon_calloc( 1, sizeof(*p) ); if( p==0 ){ fprintf(stderr,"Unable to allocate memory for a new acttab."); exit(1); } memset(p, 0, sizeof(*p)); @@ -640,11 +716,11 @@ ** state. */ void acttab_action(acttab *p, int lookahead, int action){ if( p->nLookahead>=p->nLookaheadAlloc ){ p->nLookaheadAlloc += 25; - p->aLookahead = (struct lookahead_action *) realloc( p->aLookahead, + p->aLookahead = (struct lookahead_action *) lemon_realloc( p->aLookahead, sizeof(p->aLookahead[0])*p->nLookaheadAlloc ); if( p->aLookahead==0 ){ fprintf(stderr,"malloc failed\n"); exit(1); } @@ -690,11 +766,11 @@ */ n = p->nsymbol + 1; if( p->nAction + n >= p->nActionAlloc ){ int oldAlloc = p->nActionAlloc; p->nActionAlloc = p->nAction + n + p->nActionAlloc + 20; - p->aAction = (struct lookahead_action *) realloc( p->aAction, + p->aAction = (struct lookahead_action *) lemon_realloc( p->aAction, sizeof(p->aAction[0])*p->nActionAlloc); if( p->aAction==0 ){ fprintf(stderr,"malloc failed\n"); exit(1); } @@ -1312,11 +1388,11 @@ static struct config *basis = 0; /* Top of list of basis configs */ static struct config **basisend = 0; /* End of list of basis configs */ /* Return a pointer to a new configuration */ PRIVATE struct config *newconfig(void){ - return (struct config*)calloc(1, sizeof(struct config)); + return (struct config*)lemon_calloc(1, sizeof(struct config)); } /* The configuration "old" is no longer used */ PRIVATE void deleteconfig(struct config *old) { @@ -1528,23 +1604,23 @@ ** Add the macro defined to the azDefine array. */ static void handle_D_option(char *z){ char **paz; nDefine++; - azDefine = (char **) realloc(azDefine, sizeof(azDefine[0])*nDefine); + azDefine = (char **) lemon_realloc(azDefine, sizeof(azDefine[0])*nDefine); if( azDefine==0 ){ fprintf(stderr,"out of memory\n"); exit(1); } - bDefineUsed = (char*)realloc(bDefineUsed, nDefine); + bDefineUsed = (char*)lemon_realloc(bDefineUsed, nDefine); if( bDefineUsed==0 ){ fprintf(stderr,"out of memory\n"); exit(1); } bDefineUsed[nDefine-1] = 0; paz = &azDefine[nDefine-1]; - *paz = (char *) malloc( lemonStrlen(z)+1 ); + *paz = (char *) lemon_malloc( lemonStrlen(z)+1 ); if( *paz==0 ){ fprintf(stderr,"out of memory\n"); exit(1); } lemon_strcpy(*paz, z); @@ -1554,21 +1630,21 @@ /* Rember the name of the output directory */ static char *outputDir = NULL; static void handle_d_option(char *z){ - outputDir = (char *) malloc( lemonStrlen(z)+1 ); + outputDir = (char *) lemon_malloc( lemonStrlen(z)+1 ); if( outputDir==0 ){ fprintf(stderr,"out of memory\n"); exit(1); } lemon_strcpy(outputDir, z); } static char *user_templatename = NULL; static void handle_T_option(char *z){ - user_templatename = (char *) malloc( lemonStrlen(z)+1 ); + user_templatename = (char *) lemon_malloc( lemonStrlen(z)+1 ); if( user_templatename==0 ){ memory_error(); } lemon_strcpy(user_templatename, z); } @@ -1801,10 +1877,11 @@ fprintf(stderr,"%d parsing conflicts.\n",lem.nconflict); } /* return 0 on success, 1 on failure. */ exitcode = ((lem.errorcnt > 0) || (lem.nconflict > 0)) ? 1 : 0; + lemon_free_all(); exit(exitcode); return (exitcode); } /******************** From the file "msort.c" *******************************/ /* @@ -2389,11 +2466,11 @@ } break; case IN_RHS: if( x[0]=='.' ){ struct rule *rp; - rp = (struct rule *)calloc( sizeof(struct rule) + + rp = (struct rule *)lemon_calloc( sizeof(struct rule) + sizeof(struct symbol*)*psp->nrhs + sizeof(char*)*psp->nrhs, 1); if( rp==0 ){ ErrorMsg(psp->filename,psp->tokenlineno, "Can't allocate enough memory for this rule."); psp->errorcnt++; @@ -2441,21 +2518,21 @@ } }else if( (x[0]=='|' || x[0]=='/') && psp->nrhs>0 && ISUPPER(x[1]) ){ struct symbol *msp = psp->rhs[psp->nrhs-1]; if( msp->type!=MULTITERMINAL ){ struct symbol *origsp = msp; - msp = (struct symbol *) calloc(1,sizeof(*msp)); + msp = (struct symbol *) lemon_calloc(1,sizeof(*msp)); memset(msp, 0, sizeof(*msp)); msp->type = MULTITERMINAL; msp->nsubsym = 1; - msp->subsym = (struct symbol **) calloc(1,sizeof(struct symbol*)); + msp->subsym = (struct symbol**)lemon_calloc(1,sizeof(struct symbol*)); msp->subsym[0] = origsp; msp->name = origsp->name; psp->rhs[psp->nrhs-1] = msp; } msp->nsubsym++; - msp->subsym = (struct symbol **) realloc(msp->subsym, + msp->subsym = (struct symbol **) lemon_realloc(msp->subsym, sizeof(struct symbol*)*msp->nsubsym); msp->subsym[msp->nsubsym-1] = Symbol_new(&x[1]); if( ISLOWER(x[1]) || ISLOWER(msp->subsym[0]->name[0]) ){ ErrorMsg(psp->filename,psp->tokenlineno, "Cannot form a compound containing a non-terminal"); @@ -2667,11 +2744,11 @@ } lemon_sprintf(zLine, "#line %d ", psp->tokenlineno); nLine = lemonStrlen(zLine); n += nLine + lemonStrlen(psp->filename) + nBack; } - *psp->declargslot = (char *) realloc(*psp->declargslot, n); + *psp->declargslot = (char *) lemon_realloc(*psp->declargslot, n); zBuf = *psp->declargslot + nOld; if( addLineMacro ){ if( nOld && zBuf[-1]!='\n' ){ *(zBuf++) = '\n'; } @@ -2781,11 +2858,11 @@ if( x[0]=='.' ){ psp->state = WAITING_FOR_DECL_OR_RULE; }else if( ISUPPER(x[0]) || ((x[0]=='|' || x[0]=='/') && ISUPPER(x[1])) ){ struct symbol *msp = psp->tkclass; msp->nsubsym++; - msp->subsym = (struct symbol **) realloc(msp->subsym, + msp->subsym = (struct symbol **) lemon_realloc(msp->subsym, sizeof(struct symbol*)*msp->nsubsym); if( !ISUPPER(x[0]) ) x++; msp->subsym[msp->nsubsym-1] = Symbol_new(x); }else{ ErrorMsg(psp->filename, psp->tokenlineno, @@ -2996,22 +3073,22 @@ return; } fseek(fp,0,2); filesize = ftell(fp); rewind(fp); - filebuf = (char *)malloc( filesize+1 ); + filebuf = (char *)lemon_malloc( filesize+1 ); if( filesize>100000000 || filebuf==0 ){ ErrorMsg(ps.filename,0,"Input file too large."); - free(filebuf); + lemon_free(filebuf); gp->errorcnt++; fclose(fp); return; } if( fread(filebuf,1,filesize,fp)!=filesize ){ ErrorMsg(ps.filename,0,"Can't read in all %d bytes of this file.", filesize); - free(filebuf); + lemon_free(filebuf); gp->errorcnt++; fclose(fp); return; } fclose(fp); @@ -3119,11 +3196,11 @@ *cp = 0; /* Null terminate the token */ parseonetoken(&ps); /* Parse the token */ *cp = (char)c; /* Restore the buffer */ cp = nextcp; } - free(filebuf); /* Release the buffer after parsing */ + lemon_free(filebuf); /* Release the buffer after parsing */ gp->rule = ps.firstrule; gp->errorcnt = ps.errorcnt; } /*************************** From the file "plink.c" *********************/ /* @@ -3137,11 +3214,11 @@ struct plink *newlink; if( plink_freelist==0 ){ int i; int amt = 100; - plink_freelist = (struct plink *)calloc( amt, sizeof(struct plink) ); + plink_freelist = (struct plink *)lemon_calloc( amt, sizeof(struct plink) ); if( plink_freelist==0 ){ fprintf(stderr, "Unable to allocate memory for a new follow-set propagation link.\n"); exit(1); } @@ -3190,13 +3267,11 @@ /*********************** From the file "report.c" **************************/ /* ** Procedures for generating reports and tables in the LEMON parser generator. */ -/* Generate a filename with the given suffix. Space to hold the -** name comes from malloc() and must be freed by the calling -** function. +/* Generate a filename with the given suffix. */ PRIVATE char *file_makename(struct lemon *lemp, const char *suffix) { char *name; char *cp; @@ -3209,11 +3284,11 @@ } sz = lemonStrlen(filename); sz += lemonStrlen(suffix); if( outputDir ) sz += lemonStrlen(outputDir) + 1; sz += 5; - name = (char*)malloc( sz ); + name = (char*)lemon_malloc( sz ); if( name==0 ){ fprintf(stderr,"Can't allocate space for a filename.\n"); exit(1); } name[0] = 0; @@ -3236,11 +3311,11 @@ const char *suffix, const char *mode ){ FILE *fp; - if( lemp->outname ) free(lemp->outname); + if( lemp->outname ) lemon_free(lemp->outname); lemp->outname = file_makename(lemp, suffix); fp = fopen(lemp->outname,mode); if( fp==0 && *mode=='w' ){ fprintf(stderr,"Can't open file \"%s\".\n",lemp->outname); lemp->errorcnt++; @@ -3552,18 +3627,18 @@ cp = strrchr(argv0,'/'); #endif if( cp ){ c = *cp; *cp = 0; - path = (char *)malloc( lemonStrlen(argv0) + lemonStrlen(name) + 2 ); + path = (char *)lemon_malloc( lemonStrlen(argv0) + lemonStrlen(name) + 2 ); if( path ) lemon_sprintf(path,"%s/%s",argv0,name); *cp = c; }else{ pathlist = getenv("PATH"); if( pathlist==0 ) pathlist = ".:/bin:/usr/bin"; - pathbuf = (char *) malloc( lemonStrlen(pathlist) + 1 ); - path = (char *)malloc( lemonStrlen(pathlist)+lemonStrlen(name)+2 ); + pathbuf = (char *) lemon_malloc( lemonStrlen(pathlist) + 1 ); + path = (char *)lemon_malloc( lemonStrlen(pathlist)+lemonStrlen(name)+2 ); if( (pathbuf != 0) && (path!=0) ){ pathbufptr = pathbuf; lemon_strcpy(pathbuf, pathlist); while( *pathbuf ){ cp = strchr(pathbuf,':'); @@ -3575,11 +3650,11 @@ if( c==0 ) pathbuf[0] = 0; else pathbuf = &cp[1]; if( access(path,modemask)==0 ) break; } } - free(pathbufptr); + lemon_free(pathbufptr); } return path; } /* Given an action, compute the integer value for that action @@ -3706,11 +3781,11 @@ in = fopen(tpltname,"rb"); if( in==0 ){ fprintf(stderr,"Can't open the template file \"%s\".\n",tpltname); lemp->errorcnt++; } - free(toFree); + lemon_free(toFree); return in; } /* Print a #line directive line to the output file. */ PRIVATE void tplt_linedir(FILE *out, int lineno, char *filename) @@ -3835,11 +3910,11 @@ } n = lemonStrlen(zText); } if( (int) (n+sizeof(zInt)*2+used) >= alloced ){ alloced = n + sizeof(zInt)*2 + used + 200; - z = (char *) realloc(z, alloced); + z = (char *) lemon_realloc(z, alloced); } if( z==0 ) return empty; while( n-- > 0 ){ c = *(zText++); if( c=='%' && n>0 && zText[0]=='d' ){ @@ -4125,11 +4200,11 @@ unsigned hash; /* For hashing the name of a type */ const char *name; /* Name of the parser */ /* Allocate and initialize types[] and allocate stddt[] */ arraysize = lemp->nsymbol * 2; - types = (char**)calloc( arraysize, sizeof(char*) ); + types = (char**)lemon_calloc( arraysize, sizeof(char*) ); if( types==0 ){ fprintf(stderr,"Out of memory.\n"); exit(1); } for(i=0; isymbols[i]; if( sp->datatype==0 ) continue; len = lemonStrlen(sp->datatype); if( len>maxdtlength ) maxdtlength = len; } - stddt = (char*)malloc( maxdtlength*2 + 1 ); + stddt = (char*)lemon_malloc( maxdtlength*2 + 1 ); if( stddt==0 ){ fprintf(stderr,"Out of memory.\n"); exit(1); } @@ -4191,11 +4266,11 @@ hash++; if( hash>=(unsigned)arraysize ) hash = 0; } if( types[hash]==0 ){ sp->dtnum = hash + 1; - types[hash] = (char*)malloc( lemonStrlen(stddt)+1 ); + types[hash] = (char*)lemon_malloc( lemonStrlen(stddt)+1 ); if( types[hash]==0 ){ fprintf(stderr,"Out of memory.\n"); exit(1); } lemon_strcpy(types[hash],stddt); @@ -4213,17 +4288,17 @@ fprintf(out," int yyinit;\n"); lineno++; fprintf(out," %sTOKENTYPE yy0;\n",name); lineno++; for(i=0; ierrsym && lemp->errsym->useCnt ){ fprintf(out," int yy%d;\n",lemp->errsym->dtnum); lineno++; } - free(stddt); - free(types); + lemon_free(stddt); + lemon_free(types); fprintf(out,"} YYMINORTYPE;\n"); lineno++; *plineno = lineno; } /* @@ -4448,11 +4523,11 @@ /* Generate the include code, if any */ tplt_print(out,lemp,lemp->include,&lineno); if( mhflag ){ char *incName = file_makename(lemp, ".h"); fprintf(out,"#include \"%s\"\n", incName); lineno++; - free(incName); + lemon_free(incName); } tplt_xfer(lemp->name,in,out,&lineno); /* Generate #defines for all tokens */ if( lemp->tokenprefix ) prefix = lemp->tokenprefix; @@ -4555,11 +4630,11 @@ /* Compute the action table, but do not output it yet. The action ** table must be computed before generating the YYNSTATE macro because ** we need to know how many states can be eliminated. */ - ax = (struct axset *) calloc(lemp->nxstate*2, sizeof(ax[0])); + ax = (struct axset *) lemon_calloc(lemp->nxstate*2, sizeof(ax[0])); if( ax==0 ){ fprintf(stderr,"malloc failed\n"); exit(1); } for(i=0; inxstate; i++){ @@ -4613,11 +4688,11 @@ i, stp->statenum, ax[i].isTkn ? "Token" : "Var ", ax[i].nAction, pActtab->nAction, nn); } #endif } - free(ax); + lemon_free(ax); /* Mark rules that are actually used for reduce actions after all ** optimizations have been applied */ for(rp=lemp->rule; rp; rp=rp->next) rp->doesReduce = LEMON_FALSE; @@ -5239,21 +5314,21 @@ } /* Allocate a new set */ char *SetNew(void){ char *s; - s = (char*)calloc( size, 1); + s = (char*)lemon_calloc( size, 1); if( s==0 ){ memory_error(); } return s; } /* Deallocate a set */ void SetFree(char *s) { - free(s); + lemon_free(s); } /* Add a new element to the set. Return TRUE if the element was added ** and FALSE if it was already there. */ int SetAdd(char *s, int e) @@ -5308,11 +5383,11 @@ const char *z; char *cpy; if( y==0 ) return 0; z = Strsafe_find(y); - if( z==0 && (cpy=(char *)malloc( lemonStrlen(y)+1 ))!=0 ){ + if( z==0 && (cpy=(char *)lemon_malloc( lemonStrlen(y)+1 ))!=0 ){ lemon_strcpy(cpy,y); z = cpy; Strsafe_insert(z); } MemoryCheck(z); @@ -5344,17 +5419,17 @@ static struct s_x1 *x1a; /* Allocate a new associative array */ void Strsafe_init(void){ if( x1a ) return; - x1a = (struct s_x1*)malloc( sizeof(struct s_x1) ); + x1a = (struct s_x1*)lemon_malloc( sizeof(struct s_x1) ); if( x1a ){ x1a->size = 1024; x1a->count = 0; - x1a->tbl = (x1node*)calloc(1024, sizeof(x1node) + sizeof(x1node*)); + x1a->tbl = (x1node*)lemon_calloc(1024, sizeof(x1node) + sizeof(x1node*)); if( x1a->tbl==0 ){ - free(x1a); + lemon_free(x1a); x1a = 0; }else{ int i; x1a->ht = (x1node**)&(x1a->tbl[1024]); for(i=0; i<1024; i++) x1a->ht[i] = 0; @@ -5385,11 +5460,11 @@ /* Need to make the hash table bigger */ int i,arrSize; struct s_x1 array; array.size = arrSize = x1a->size*2; array.count = x1a->count; - array.tbl = (x1node*)calloc(arrSize, sizeof(x1node) + sizeof(x1node*)); + array.tbl = (x1node*)lemon_calloc(arrSize, sizeof(x1node)+sizeof(x1node*)); if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ array.ht = (x1node**)&(array.tbl[arrSize]); for(i=0; icount; i++){ x1node *oldnp, *newnp; @@ -5400,11 +5475,11 @@ newnp->next = array.ht[h]; newnp->data = oldnp->data; newnp->from = &(array.ht[h]); array.ht[h] = newnp; } - /* free(x1a->tbl); // This program was originally for 16-bit machines. + /* lemon_free(x1a->tbl); // This program was originally for 16-bit machines. ** Don't worry about freeing memory on modern platforms. */ *x1a = array; } /* Insert the new data */ h = ph & (x1a->size-1); @@ -5441,11 +5516,11 @@ { struct symbol *sp; sp = Symbol_find(x); if( sp==0 ){ - sp = (struct symbol *)calloc(1, sizeof(struct symbol) ); + sp = (struct symbol *)lemon_calloc(1, sizeof(struct symbol) ); MemoryCheck(sp); sp->name = Strsafe(x); sp->type = ISUPPER(*x) ? TERMINAL : NONTERMINAL; sp->rule = 0; sp->fallback = 0; @@ -5512,17 +5587,17 @@ static struct s_x2 *x2a; /* Allocate a new associative array */ void Symbol_init(void){ if( x2a ) return; - x2a = (struct s_x2*)malloc( sizeof(struct s_x2) ); + x2a = (struct s_x2*)lemon_malloc( sizeof(struct s_x2) ); if( x2a ){ x2a->size = 128; x2a->count = 0; - x2a->tbl = (x2node*)calloc(128, sizeof(x2node) + sizeof(x2node*)); + x2a->tbl = (x2node*)lemon_calloc(128, sizeof(x2node) + sizeof(x2node*)); if( x2a->tbl==0 ){ - free(x2a); + lemon_free(x2a); x2a = 0; }else{ int i; x2a->ht = (x2node**)&(x2a->tbl[128]); for(i=0; i<128; i++) x2a->ht[i] = 0; @@ -5553,11 +5628,11 @@ /* Need to make the hash table bigger */ int i,arrSize; struct s_x2 array; array.size = arrSize = x2a->size*2; array.count = x2a->count; - array.tbl = (x2node*)calloc(arrSize, sizeof(x2node) + sizeof(x2node*)); + array.tbl = (x2node*)lemon_calloc(arrSize, sizeof(x2node)+sizeof(x2node*)); if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ array.ht = (x2node**)&(array.tbl[arrSize]); for(i=0; icount; i++){ x2node *oldnp, *newnp; @@ -5569,11 +5644,11 @@ newnp->key = oldnp->key; newnp->data = oldnp->data; newnp->from = &(array.ht[h]); array.ht[h] = newnp; } - /* free(x2a->tbl); // This program was originally written for 16-bit + /* lemon_free(x2a->tbl); // This program was originally written for 16-bit ** machines. Don't worry about freeing this trivial amount of memory ** on modern platforms. Just leak it. */ *x2a = array; } /* Insert the new data */ @@ -5630,11 +5705,11 @@ { struct symbol **array; int i,arrSize; if( x2a==0 ) return 0; arrSize = x2a->count; - array = (struct symbol **)calloc(arrSize, sizeof(struct symbol *)); + array = (struct symbol **)lemon_calloc(arrSize, sizeof(struct symbol *)); if( array ){ for(i=0; itbl[i].data; } return array; } @@ -5678,11 +5753,11 @@ /* Allocate a new state structure */ struct state *State_new() { struct state *newstate; - newstate = (struct state *)calloc(1, sizeof(struct state) ); + newstate = (struct state *)lemon_calloc(1, sizeof(struct state) ); MemoryCheck(newstate); return newstate; } /* There is one instance of the following structure for each @@ -5711,17 +5786,17 @@ static struct s_x3 *x3a; /* Allocate a new associative array */ void State_init(void){ if( x3a ) return; - x3a = (struct s_x3*)malloc( sizeof(struct s_x3) ); + x3a = (struct s_x3*)lemon_malloc( sizeof(struct s_x3) ); if( x3a ){ x3a->size = 128; x3a->count = 0; - x3a->tbl = (x3node*)calloc(128, sizeof(x3node) + sizeof(x3node*)); + x3a->tbl = (x3node*)lemon_calloc(128, sizeof(x3node) + sizeof(x3node*)); if( x3a->tbl==0 ){ - free(x3a); + lemon_free(x3a); x3a = 0; }else{ int i; x3a->ht = (x3node**)&(x3a->tbl[128]); for(i=0; i<128; i++) x3a->ht[i] = 0; @@ -5752,11 +5827,11 @@ /* Need to make the hash table bigger */ int i,arrSize; struct s_x3 array; array.size = arrSize = x3a->size*2; array.count = x3a->count; - array.tbl = (x3node*)calloc(arrSize, sizeof(x3node) + sizeof(x3node*)); + array.tbl = (x3node*)lemon_calloc(arrSize, sizeof(x3node)+sizeof(x3node*)); if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ array.ht = (x3node**)&(array.tbl[arrSize]); for(i=0; icount; i++){ x3node *oldnp, *newnp; @@ -5768,11 +5843,11 @@ newnp->key = oldnp->key; newnp->data = oldnp->data; newnp->from = &(array.ht[h]); array.ht[h] = newnp; } - free(x3a->tbl); + lemon_free(x3a->tbl); *x3a = array; } /* Insert the new data */ h = ph & (x3a->size-1); np = &(x3a->tbl[x3a->count++]); @@ -5809,11 +5884,11 @@ { struct state **array; int i,arrSize; if( x3a==0 ) return 0; arrSize = x3a->count; - array = (struct state **)calloc(arrSize, sizeof(struct state *)); + array = (struct state **)lemon_calloc(arrSize, sizeof(struct state *)); if( array ){ for(i=0; itbl[i].data; } return array; } @@ -5851,17 +5926,17 @@ static struct s_x4 *x4a; /* Allocate a new associative array */ void Configtable_init(void){ if( x4a ) return; - x4a = (struct s_x4*)malloc( sizeof(struct s_x4) ); + x4a = (struct s_x4*)lemon_malloc( sizeof(struct s_x4) ); if( x4a ){ x4a->size = 64; x4a->count = 0; - x4a->tbl = (x4node*)calloc(64, sizeof(x4node) + sizeof(x4node*)); + x4a->tbl = (x4node*)lemon_calloc(64, sizeof(x4node) + sizeof(x4node*)); if( x4a->tbl==0 ){ - free(x4a); + lemon_free(x4a); x4a = 0; }else{ int i; x4a->ht = (x4node**)&(x4a->tbl[64]); for(i=0; i<64; i++) x4a->ht[i] = 0; @@ -5892,11 +5967,12 @@ /* Need to make the hash table bigger */ int i,arrSize; struct s_x4 array; array.size = arrSize = x4a->size*2; array.count = x4a->count; - array.tbl = (x4node*)calloc(arrSize, sizeof(x4node) + sizeof(x4node*)); + array.tbl = (x4node*)lemon_calloc(arrSize, + sizeof(x4node) + sizeof(x4node*)); if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ array.ht = (x4node**)&(array.tbl[arrSize]); for(i=0; icount; i++){ x4node *oldnp, *newnp; @@ -5907,13 +5983,10 @@ newnp->next = array.ht[h]; newnp->data = oldnp->data; newnp->from = &(array.ht[h]); array.ht[h] = newnp; } - /* free(x4a->tbl); // This code was originall written for 16-bit machines. - ** on modern machines, don't worry about freeing this trival amount of - ** memory. */ *x4a = array; } /* Insert the new data */ h = ph & (x4a->size-1); np = &(x4a->tbl[x4a->count++]); Index: tool/mkctimec.tcl ================================================================== --- tool/mkctimec.tcl +++ tool/mkctimec.tcl @@ -103,10 +103,11 @@ # All Boolean compile time options which default to 0 or empty. # set boolean_defnil_options { SQLITE_32BIT_ROWID SQLITE_4_BYTE_ALIGNED_MALLOC + SQLITE_ALLOW_ROWID_IN_VIEW SQLITE_ALLOW_URI_AUTHORITY SQLITE_BUG_COMPATIBLE_20160819 SQLITE_CASE_SENSITIVE_LIKE SQLITE_CHECK_PAGES SQLITE_COVERAGE_TEST Index: tool/mkopcodeh.tcl ================================================================== --- tool/mkopcodeh.tcl +++ tool/mkopcodeh.tcl @@ -79,10 +79,11 @@ set name [string trim [lindex $line 1] :] if {$name=="OP_Abortable"} continue; # put OP_Abortable last set op($name) -1 set group($name) 0 set jump($name) 0 + set jump0($name) 0 set in1($name) 0 set in2($name) 0 set in3($name) 0 set out2($name) 0 set out3($name) 0 @@ -107,10 +108,11 @@ in2 {set in2($name) 1} in3 {set in3($name) 1} out2 {set out2($name) 1} out3 {set out3($name) 1} ncycle {set ncycle($name) 1} + jump0 {set jump($name) 1; set jump0($name) 1;} } } if {$group($name)} { set newGroup 0 if {[info exists groups($nGroup)]} { @@ -135,10 +137,11 @@ # puts "/* Automatically generated. Do not edit */" puts "/* See the tool/mkopcodeh.tcl script for details */" foreach name {OP_Noop OP_Explain OP_Abortable} { set jump($name) 0 + set jump0($name) 0 set in1($name) 0 set in2($name) 0 set in3($name) 0 set out2($name) 0 set out3($name) 0 @@ -254,11 +257,13 @@ } if {$i>$max} {set max $i} set name $def($i) puts -nonewline [format {#define %-16s %3d} $name $i] set com {} - if {[info exists jump($name)] && $jump($name)} { + if {[info exists jump0($name)] && $jump0($name)} { + lappend com "jump0" + } elseif {[info exists jump($name)] && $jump($name)} { lappend com "jump" } if {[info exists sameas($i)]} { lappend com "same as $sameas($i)" } @@ -287,10 +292,11 @@ if {$in2($name)} {incr x 4} if {$in3($name)} {incr x 8} if {$out2($name)} {incr x 16} if {$out3($name)} {incr x 32} if {$ncycle($name)} {incr x 64} + if {$jump0($name)} {incr x 128} } set bv($i) $x } puts "" puts "/* Properties such as \"out2\" or \"jump\" that are specified in" @@ -302,10 +308,11 @@ puts "#define OPFLG_IN2 0x04 /* in2: P2 is an input */" puts "#define OPFLG_IN3 0x08 /* in3: P3 is an input */" puts "#define OPFLG_OUT2 0x10 /* out2: P2 is an output */" puts "#define OPFLG_OUT3 0x20 /* out3: P3 is an output */" puts "#define OPFLG_NCYCLE 0x40 /* ncycle:Cycles count against P1 */" +puts "#define OPFLG_JUMP0 0x80 /* jump0: P2 might be zero */" puts "#define OPFLG_INITIALIZER \173\\" for {set i 0} {$i<=$max} {incr i} { if {$i%8==0} { puts -nonewline [format "/* %3d */" $i] }