diff --git a/Makefile.in b/Makefile.in index 9b2fe86cf4..b961070c23 100644 --- a/Makefile.in +++ b/Makefile.in @@ -382,6 +382,7 @@ TESTSRC = \ $(TOP)/src/test_blob.c \ $(TOP)/src/test_btree.c \ $(TOP)/src/test_config.c \ + $(TOP)/src/test_delete.c \ $(TOP)/src/test_demovfs.c \ $(TOP)/src/test_devsym.c \ $(TOP)/src/test_fs.c \ @@ -429,6 +430,7 @@ TESTSRC += \ $(TOP)/ext/misc/nextchar.c \ $(TOP)/ext/misc/percentile.c \ $(TOP)/ext/misc/regexp.c \ + $(TOP)/ext/misc/remember.c \ $(TOP)/ext/misc/series.c \ $(TOP)/ext/misc/spellfix.c \ $(TOP)/ext/misc/totype.c \ @@ -549,7 +551,8 @@ FUZZDATA = \ $(TOP)/test/fuzzdata1.db \ $(TOP)/test/fuzzdata2.db \ $(TOP)/test/fuzzdata3.db \ - $(TOP)/test/fuzzdata4.db + $(TOP)/test/fuzzdata4.db \ + $(TOP)/test/fuzzdata5.db # Standard options to testfixture # @@ -562,7 +565,9 @@ SHELL_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS4 SHELL_OPT += -DSQLITE_ENABLE_EXPLAIN_COMMENTS SHELL_OPT += -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION FUZZERSHELL_OPT = -DSQLITE_ENABLE_JSON1 -FUZZCHECK_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5 +FUZZCHECK_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_OSS_FUZZ +FUZZCHECK_SRC = $(TOP)/test/fuzzcheck.c $(TOP)/test/ossfuzz.c +DBFUZZ_OPT = # This is the default Makefile target. The objects listed here # are what get build when you type just "make" with no arguments. @@ -611,8 +616,14 @@ fuzzershell$(TEXE): $(TOP)/tool/fuzzershell.c sqlite3.c sqlite3.h $(LTLINK) -o $@ $(FUZZERSHELL_OPT) \ $(TOP)/tool/fuzzershell.c sqlite3.c $(TLIBS) -fuzzcheck$(TEXE): $(TOP)/test/fuzzcheck.c sqlite3.c sqlite3.h - $(LTLINK) -o $@ $(FUZZCHECK_OPT) $(TOP)/test/fuzzcheck.c sqlite3.c $(TLIBS) +fuzzcheck$(TEXE): $(FUZZCHECK_SRC) sqlite3.c sqlite3.h + $(LTLINK) -o $@ $(FUZZCHECK_OPT) $(FUZZCHECK_SRC) sqlite3.c $(TLIBS) + +ossshell$(TEXE): $(TOP)/test/ossfuzz.c $(TOP)/test/ossshell.c sqlite3.c sqlite3.h + $(LTLINK) -o $@ $(FUZZCHECK_OPT) $(TOP)/test/ossshell.c sqlite3.c sqlite3.h $(TLIBS) + +dbfuzz$(TEXE): $(TOP)/test/dbfuzz.c sqlite3.c sqlite3.h + $(LTLINK) -o $@ $(DBFUZZ_OPT) $(TOP)/test/dbfuzz.c sqlite3.c $(TLIBS) mptester$(TEXE): sqlite3.lo $(TOP)/mptest/mptest.c $(LTLINK) -o $@ -I. $(TOP)/mptest/mptest.c sqlite3.lo \ @@ -1102,6 +1113,11 @@ fastfuzztest: fuzzcheck$(TEXE) $(FUZZDATA) valgrindfuzz: fuzzcheck$(TEXT) $(FUZZDATA) valgrind ./fuzzcheck$(TEXE) --cell-size-check --limit-mem 10M --timeout 600 $(FUZZDATA) +# The veryquick.test TCL tests. +# +tcltest: ./testfixture$(TEXE) + ./testfixture$(TEXE) $(TOP)/test/veryquick.test $(TESTOPTS) + # Minimal testing that runs in less than 3 minutes # quicktest: ./testfixture$(TEXE) @@ -1110,8 +1126,7 @@ quicktest: ./testfixture$(TEXE) # This is the common case. Run many tests that do not take too long, # including fuzzcheck, sqlite3_analyzer, and sqldiff tests. # -test: $(TESTPROGS) sourcetest fastfuzztest - ./testfixture$(TEXE) $(TOP)/test/veryquick.test $(TESTOPTS) +test: fastfuzztest sourcetest $(TESTPROGS) tcltest # Run a test using valgrind. This can take a really long time # because valgrind is so much slower than a native machine. diff --git a/Makefile.msc b/Makefile.msc index cd866ce4a4..d4aba301fb 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -24,6 +24,20 @@ USE_AMALGAMATION = 1 USE_FULLWARN = 0 !ENDIF +# Set this non-0 to enable full runtime error checks (-RTC1, etc). This +# has no effect if (any) optimizations are enabled. +# +!IFNDEF USE_RUNTIME_CHECKS +USE_RUNTIME_CHECKS = 0 +!ENDIF + +# Set this non-0 to create a SQLite amalgamation file that excludes the +# various built-in extensions. +# +!IFNDEF MINIMAL_AMALGAMATION +MINIMAL_AMALGAMATION = 0 +!ENDIF + # Set this non-0 to use "stdcall" calling convention for the core library # and shell executable. # @@ -263,12 +277,39 @@ SQLITE3EXEPDB = /pdb:sqlite3sh.pdb !ENDIF !ENDIF +# <> +# These are the names of the customized Tcl header files used by various parts +# of this makefile when the stdcall calling convention is in use. It is not +# used for any other purpose. +# +!IFNDEF SQLITETCLH +SQLITETCLH = sqlite_tcl.h +!ENDIF + +!IFNDEF SQLITETCLDECLSH +SQLITETCLDECLSH = sqlite_tclDecls.h +!ENDIF + +# These are the additional targets that the targets that integrate with the +# Tcl library should depend on when compiling, etc. +# +!IFNDEF SQLITE_TCL_DEP +!IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0 +SQLITE_TCL_DEP = $(SQLITETCLDECLSH) $(SQLITETCLH) +!ELSE +SQLITE_TCL_DEP = +!ENDIF +!ENDIF +# <> + # These are the "standard" SQLite compilation options used when compiling for # the Windows platform. # !IFNDEF OPT_FEATURE_FLAGS +!IF $(MINIMAL_AMALGAMATION)==0 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS3=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RTREE=1 +!ENDIF OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_COLUMN_METADATA=1 !ENDIF @@ -464,20 +505,32 @@ RCC = $(RC) -DSQLITE_OS_WIN=1 -I. -I$(TOP) -I$(TOP)\src $(RCOPTS) $(RCCOPTS) # !IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0 !IF "$(PLATFORM)"=="x86" -CORE_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_STDCALL=__stdcall -SHELL_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_STDCALL=__stdcall +CORE_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall +SHELL_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall +# <> +TEST_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall -DINCLUDE_SQLITE_TCL_H=1 -DSQLITE_TCLAPI=__cdecl +# <> !ELSE !IFNDEF PLATFORM -CORE_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_STDCALL=__stdcall -SHELL_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_STDCALL=__stdcall +CORE_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall +SHELL_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall +# <> +TEST_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall -DINCLUDE_SQLITE_TCL_H=1 -DSQLITE_TCLAPI=__cdecl +# <> !ELSE CORE_CCONV_OPTS = SHELL_CCONV_OPTS = +# <> +TEST_CCONV_OPTS = +# <> !ENDIF !ENDIF !ELSE CORE_CCONV_OPTS = SHELL_CCONV_OPTS = +# <> +TEST_CCONV_OPTS = +# <> !ENDIF # These are additional compiler options used for the core library. @@ -630,12 +683,35 @@ RCC = $(RCC) -I$(TOP)\ext\session # options are necessary in order to allow debugging symbols to # work correctly with Visual Studio when using the amalgamation. # +!IFNDEF MKSQLITE3C_TOOL +!IF $(MINIMAL_AMALGAMATION)!=0 +MKSQLITE3C_TOOL = $(TOP)\tool\mksqlite3c-noext.tcl +!ELSE +MKSQLITE3C_TOOL = $(TOP)\tool\mksqlite3c.tcl +!ENDIF +!ENDIF + !IFNDEF MKSQLITE3C_ARGS !IF $(DEBUG)>1 MKSQLITE3C_ARGS = --linemacros !ELSE MKSQLITE3C_ARGS = !ENDIF +!IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0 +MKSQLITE3C_ARGS = $(MKSQLITE3C_ARGS) --useapicall +!ENDIF +!ENDIF + +# The mksqlite3h.tcl script accepts some options on the command line. +# When compiling with stdcall support, some of these options are +# necessary. +# +!IFNDEF MKSQLITE3H_ARGS +!IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0 +MKSQLITE3H_ARGS = --useapicall +!ELSE +MKSQLITE3H_ARGS = +!ENDIF !ENDIF # <> @@ -718,11 +794,11 @@ TCLLIBDIR = c:\tcl\lib !ENDIF !IFNDEF LIBTCL -LIBTCL = tcl85.lib +LIBTCL = tcl86.lib !ENDIF !IFNDEF LIBTCLSTUB -LIBTCLSTUB = tclstub85.lib +LIBTCLSTUB = tclstub86.lib !ENDIF !IFNDEF LIBTCLPATH @@ -752,7 +828,7 @@ LIBICU = icuuc.lib icuin.lib # specific Tcl shell to use. # !IFNDEF TCLSH_CMD -TCLSH_CMD = tclsh85 +TCLSH_CMD = tclsh !ENDIF # <> @@ -835,6 +911,10 @@ RCC = $(RCC) -D_DEBUG !IF $(DEBUG)>1 || $(OPTIMIZATIONS)==0 TCC = $(TCC) -Od BCC = $(BCC) -Od +!IF $(USE_RUNTIME_CHECKS)!=0 +TCC = $(TCC) -RTC1 +BCC = $(BCC) -RTC1 +!ENDIF !ELSEIF $(OPTIMIZATIONS)>=3 TCC = $(TCC) -Ox BCC = $(BCC) -Ox @@ -1235,6 +1315,16 @@ SRC11 = \ parse.h \ $(SQLITE3H) +# Generated Tcl header files +# +!IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0 +SRC12 = \ + $(SQLITETCLH) \ + $(SQLITETCLDECLSH) +!ELSE +SRC12 = +!ENDIF + # All source code files. # SRC = $(SRC00) $(SRC01) $(SRC02) $(SRC03) $(SRC04) $(SRC05) $(SRC06) $(SRC07) $(SRC08) $(SRC09) $(SRC10) $(SRC11) @@ -1258,6 +1348,7 @@ TESTSRC = \ $(TOP)\src\test_blob.c \ $(TOP)\src\test_btree.c \ $(TOP)\src\test_config.c \ + $(TOP)\src\test_delete.c \ $(TOP)\src\test_demovfs.c \ $(TOP)\src\test_devsym.c \ $(TOP)\src\test_fs.c \ @@ -1305,6 +1396,7 @@ TESTEXT = \ $(TOP)\ext\misc\nextchar.c \ $(TOP)\ext\misc\percentile.c \ $(TOP)\ext\misc\regexp.c \ + $(TOP)\ext\misc\remember.c \ $(TOP)\ext\misc\series.c \ $(TOP)\ext\misc\spellfix.c \ $(TOP)\ext\misc\totype.c \ @@ -1341,7 +1433,7 @@ HDR = \ parse.h \ $(TOP)\src\pragma.h \ $(SQLITE3H) \ - $(TOP)\src\sqlite3ext.h \ + sqlite3ext.h \ $(TOP)\src\sqliteInt.h \ $(TOP)\src\sqliteLimit.h \ $(TOP)\src\vdbe.h \ @@ -1388,7 +1480,8 @@ FUZZDATA = \ $(TOP)\test\fuzzdata1.db \ $(TOP)\test\fuzzdata2.db \ $(TOP)\test\fuzzdata3.db \ - $(TOP)\test\fuzzdata4.db + $(TOP)\test\fuzzdata4.db \ + $(TOP)\test\fuzzdata5.db # <> # Additional compiler options for the shell. These are only effective @@ -1403,7 +1496,9 @@ SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_SHELL_JSON1 -DSQLITE_ENABLE_ # MPTESTER_COMPILE_OPTS = -DSQLITE_SHELL_JSON1 -DSQLITE_ENABLE_FTS5 FUZZERSHELL_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1 -FUZZCHECK_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5 +FUZZCHECK_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_OSS_FUZZ +FUZZCHECK_SRC = $(TOP)\test\fuzzcheck.c $(TOP)\test\ossfuzz.c +OSSSHELL_SRC = $(TOP)\test\ossshell.c $(TOP)\test\ossfuzz.c # Standard options to testfixture. # @@ -1473,8 +1568,11 @@ sourcetest: srcck1.exe sqlite3.c fuzzershell.exe: $(TOP)\tool\fuzzershell.c $(SQLITE3C) $(SQLITE3H) $(LTLINK) $(NO_WARN) $(FUZZERSHELL_COMPILE_OPTS) $(TOP)\tool\fuzzershell.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) -fuzzcheck.exe: $(TOP)\test\fuzzcheck.c $(SQLITE3C) $(SQLITE3H) - $(LTLINK) $(NO_WARN) $(FUZZCHECK_COMPILE_OPTS) $(TOP)\test\fuzzcheck.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) +fuzzcheck.exe: $(FUZZCHECK_SRC) $(SQLITE3C) $(SQLITE3H) + $(LTLINK) $(NO_WARN) $(FUZZCHECK_COMPILE_OPTS) $(FUZZCHECK_SRC) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) + +ossshell.exe: $(OSSSHELL_SRC) $(SQLITE3C) $(SQLITE3H) + $(LTLINK) $(NO_WARN) $(FUZZCHECK_COMPILE_OPTS) $(OSSSHELL_SRC) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) mptester.exe: $(TOP)\mptest\mptest.c $(SQLITE3C) $(SQLITE3H) $(LTLINK) $(NO_WARN) $(MPTESTER_COMPILE_OPTS) $(TOP)\mptest\mptest.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) @@ -1499,7 +1597,7 @@ mptest: mptester.exe # files are automatically generated. This target takes care of # all that automatic generation. # -.target_source: $(SRC) $(TOP)\tool\vdbe-compress.tcl fts5.c +.target_source: $(SRC) $(TOP)\tool\vdbe-compress.tcl fts5.c $(SQLITE_TCL_DEP) -rmdir /Q/S tsrc 2>NUL -mkdir tsrc for %i in ($(SRC00)) do copy /Y %i tsrc @@ -1514,6 +1612,7 @@ mptest: mptester.exe for %i in ($(SRC09)) do copy /Y %i tsrc for %i in ($(SRC10)) do copy /Y %i tsrc for %i in ($(SRC11)) do copy /Y %i tsrc + for %i in ($(SRC12)) do copy /Y %i tsrc copy /Y fts5.c tsrc copy /Y fts5.h tsrc del /Q tsrc\sqlite.h.in tsrc\parse.y 2>NUL @@ -1521,8 +1620,8 @@ mptest: mptester.exe move vdbe.new tsrc\vdbe.c echo > .target_source -sqlite3.c: .target_source sqlite3ext.h $(TOP)\tool\mksqlite3c.tcl - $(TCLSH_CMD) $(TOP)\tool\mksqlite3c.tcl $(MKSQLITE3C_ARGS) +sqlite3.c: .target_source sqlite3ext.h $(MKSQLITE3C_TOOL) + $(TCLSH_CMD) $(MKSQLITE3C_TOOL) $(MKSQLITE3C_ARGS) copy tsrc\shell.c . copy $(TOP)\ext\session\sqlite3session.h . @@ -1795,10 +1894,10 @@ wherecode.lo: $(TOP)\src\wherecode.c $(HDR) whereexpr.lo: $(TOP)\src\whereexpr.c $(HDR) $(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(TOP)\src\whereexpr.c -tclsqlite.lo: $(TOP)\src\tclsqlite.c $(HDR) +tclsqlite.lo: $(TOP)\src\tclsqlite.c $(HDR) $(SQLITE_TCL_DEP) $(LTCOMPILE) $(NO_WARN) -DUSE_TCL_STUBS=1 -DBUILD_sqlite -I$(TCLINCDIR) -c $(TOP)\src\tclsqlite.c -tclsqlite-shell.lo: $(TOP)\src\tclsqlite.c $(HDR) +tclsqlite-shell.lo: $(TOP)\src\tclsqlite.c $(HDR) $(SQLITE_TCL_DEP) $(LTCOMPILE) $(NO_WARN) -DTCLSH=1 -DBUILD_sqlite -I$(TCLINCDIR) -c $(TOP)\src\tclsqlite.c tclsqlite3.exe: tclsqlite-shell.lo $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS) @@ -1824,10 +1923,16 @@ parse.c: $(TOP)\src\parse.y lemon.exe $(TOP)\tool\addopcodes.tcl $(TCLSH_CMD) $(TOP)\tool\addopcodes.tcl parse.h.temp > parse.h $(SQLITE3H): $(TOP)\src\sqlite.h.in $(TOP)\manifest.uuid $(TOP)\VERSION - $(TCLSH_CMD) $(TOP)\tool\mksqlite3h.tcl $(TOP:\=/) > $(SQLITE3H) + $(TCLSH_CMD) $(TOP)\tool\mksqlite3h.tcl $(TOP:\=/) > $(SQLITE3H) $(MKSQLITE3H_ARGS) sqlite3ext.h: .target_source - copy tsrc\sqlite3ext.h . +!IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0 + type tsrc\sqlite3ext.h | $(TCLSH_CMD) $(TOP)\tool\replace.tcl regsub "\(\*\)" "(SQLITE_CALLBACK *)" \ + | $(TCLSH_CMD) $(TOP)\tool\replace.tcl regsub "\(\*" "(SQLITE_APICALL *" > sqlite3ext.h + copy /Y sqlite3ext.h tsrc\sqlite3ext.h +!ELSE + copy /Y tsrc\sqlite3ext.h sqlite3ext.h +!ENDIF mkkeywordhash.exe: $(TOP)\tool\mkkeywordhash.c $(BCC) $(NO_WARN) -Fe$@ $(REQ_FEATURE_FLAGS) $(OPT_FEATURE_FLAGS) $(EXT_FEATURE_FLAGS) $(OPTS) \ @@ -1960,6 +2065,7 @@ TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_SERVER=1 -DSQLITE_PRIVATE="" TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_CORE $(NO_WARN) TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_SERIES_CONSTRAINT_VERIFY=1 TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_DEFAULT_PAGE_SIZE=1024 +TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) $(TEST_CCONV_OPTS) TESTFIXTURE_SRC0 = $(TESTEXT) $(TESTSRC2) TESTFIXTURE_SRC1 = $(TESTEXT) $(SQLITE3C) @@ -1969,7 +2075,27 @@ TESTFIXTURE_SRC = $(TESTSRC) $(TOP)\src\tclsqlite.c $(TESTFIXTURE_SRC0) TESTFIXTURE_SRC = $(TESTSRC) $(TOP)\src\tclsqlite.c $(TESTFIXTURE_SRC1) !ENDIF -testfixture.exe: $(TESTFIXTURE_SRC) $(SQLITE3H) $(LIBRESOBJS) $(HDR) +!IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0 +sqlite_tclDecls.h: + echo #ifndef SQLITE_TCLAPI > $(SQLITETCLDECLSH) + echo # define SQLITE_TCLAPI >> $(SQLITETCLDECLSH) + echo #endif >> $(SQLITETCLDECLSH) + type "$(TCLINCDIR)\tclDecls.h" \ + | $(TCLSH_CMD) $(TOP)\tool\replace.tcl regsub "^(EXTERN(?: CONST\d+?)?\s+?[^\(]*?\s+?)Tcl_" "\1 SQLITE_TCLAPI Tcl_" \ + | $(TCLSH_CMD) $(TOP)\tool\replace.tcl regsub "^(EXTERN\s+?(?:void|VOID)\s+?)TclFreeObj" "\1 SQLITE_TCLAPI TclFreeObj" \ + | $(TCLSH_CMD) $(TOP)\tool\replace.tcl regsub "\(\*tcl_" "(SQLITE_TCLAPI *tcl_" \ + | $(TCLSH_CMD) $(TOP)\tool\replace.tcl regsub "\(\*tclFreeObj" "(SQLITE_TCLAPI *tclFreeObj" \ + | $(TCLSH_CMD) $(TOP)\tool\replace.tcl regsub "\(\*" "(SQLITE_TCLAPI *" >> $(SQLITETCLDECLSH) + +sqlite_tcl.h: + type "$(TCLINCDIR)\tcl.h" | $(TCLSH_CMD) $(TOP)\tool\replace.tcl exact tclDecls.h sqlite_tclDecls.h \ + | $(TCLSH_CMD) $(TOP)\tool\replace.tcl regsub "typedef (.*?)\(Tcl_" "typedef \1 (SQLITE_TCLAPI Tcl_" \ + | $(TCLSH_CMD) $(TOP)\tool\replace.tcl exact "void (*freeProc)" "void (SQLITE_TCLAPI *freeProc)" \ + | $(TCLSH_CMD) $(TOP)\tool\replace.tcl exact "Tcl_HashEntry *(*findProc)" "Tcl_HashEntry *(SQLITE_TCLAPI *findProc)" \ + | $(TCLSH_CMD) $(TOP)\tool\replace.tcl exact "Tcl_HashEntry *(*createProc)" "Tcl_HashEntry *(SQLITE_TCLAPI *createProc)" >> $(SQLITETCLH) +!ENDIF + +testfixture.exe: $(TESTFIXTURE_SRC) $(SQLITE3H) $(LIBRESOBJS) $(HDR) $(SQLITE_TCL_DEP) $(LTLINK) -DSQLITE_NO_SYNC=1 $(TESTFIXTURE_FLAGS) \ -DBUILD_sqlite -I$(TCLINCDIR) \ $(TESTFIXTURE_SRC) \ @@ -2018,7 +2144,7 @@ smoketest: $(TESTPROGS) @set PATH=$(LIBTCLPATH);$(PATH) .\testfixture.exe $(TOP)\test\main.test $(TESTOPTS) -sqlite3_analyzer.c: $(SQLITE3C) $(SQLITE3H) $(TOP)\src\tclsqlite.c $(TOP)\tool\spaceanal.tcl +sqlite3_analyzer.c: $(SQLITE3C) $(SQLITE3H) $(TOP)\src\tclsqlite.c $(TOP)\tool\spaceanal.tcl $(SQLITE_TCL_DEP) echo #define TCLSH 2 > $@ echo #define SQLITE_ENABLE_DBSTAT_VTAB 1 >> $@ copy $@ + $(SQLITE3C) + $(TOP)\src\tclsqlite.c $@ @@ -2098,7 +2224,7 @@ clean: -rmdir /Q/S .libs 2>NUL -rmdir /Q/S tsrc 2>NUL del /Q .target_source 2>NUL - del /Q tclsqlite3.exe 2>NUL + del /Q tclsqlite3.exe $(SQLITETCLH) $(SQLITETCLDECLSH) 2>NUL del /Q testloadext.dll 2>NUL del /Q testfixture.exe test.db 2>NUL del /Q LogEst.exe fts3view.exe rollback-test.exe showdb.exe 2>NUL diff --git a/VERSION b/VERSION index f02113fe87..3f67e25cea 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.15.0 +3.17.0 diff --git a/autoconf/Makefile.msc b/autoconf/Makefile.msc index d3bbc6b4d2..b53e2370eb 100644 --- a/autoconf/Makefile.msc +++ b/autoconf/Makefile.msc @@ -31,6 +31,13 @@ USE_FULLWARN = 0 USE_RUNTIME_CHECKS = 0 !ENDIF +# Set this non-0 to create a SQLite amalgamation file that excludes the +# various built-in extensions. +# +!IFNDEF MINIMAL_AMALGAMATION +MINIMAL_AMALGAMATION = 0 +!ENDIF + # Set this non-0 to use "stdcall" calling convention for the core library # and shell executable. # @@ -255,12 +262,15 @@ SQLITE3EXEPDB = /pdb:sqlite3sh.pdb !ENDIF !ENDIF + # These are the "standard" SQLite compilation options used when compiling for # the Windows platform. # !IFNDEF OPT_FEATURE_FLAGS +!IF $(MINIMAL_AMALGAMATION)==0 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS3=1 OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RTREE=1 +!ENDIF OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_COLUMN_METADATA=1 !ENDIF diff --git a/configure b/configure index 75c7a3720d..be7c89143b 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for sqlite 3.15.0. +# Generated by GNU Autoconf 2.69 for sqlite 3.17.0. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.15.0' -PACKAGE_STRING='sqlite 3.15.0' +PACKAGE_VERSION='3.17.0' +PACKAGE_STRING='sqlite 3.17.0' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1463,7 +1463,7 @@ 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.15.0 to adapt to many kinds of systems. +\`configure' configures sqlite 3.17.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1528,7 +1528,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.15.0:";; + short | recursive ) echo "Configuration of sqlite 3.17.0:";; esac cat <<\_ACEOF @@ -1652,7 +1652,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.15.0 +sqlite configure 3.17.0 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2071,7 +2071,7 @@ 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.15.0, which was +It was created by sqlite $as_me 3.17.0, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -11252,7 +11252,7 @@ else fi if test "${use_debug}" = "yes" ; then - TARGET_DEBUG="-DSQLITE_DEBUG=1" + TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE" else TARGET_DEBUG="-DNDEBUG" fi @@ -12151,7 +12151,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # 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.15.0, which was +This file was extended by sqlite $as_me 3.17.0, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12217,7 +12217,7 @@ _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.15.0 +sqlite config.status 3.17.0 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index c9e3af9220..095db6bf81 100644 --- a/configure.ac +++ b/configure.ac @@ -560,7 +560,7 @@ AC_SEARCH_LIBS(fdatasync, [rt]) AC_ARG_ENABLE(debug, AC_HELP_STRING([--enable-debug],[enable debugging & verbose explain]), [use_debug=$enableval],[use_debug=no]) if test "${use_debug}" = "yes" ; then - TARGET_DEBUG="-DSQLITE_DEBUG=1" + TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE" else TARGET_DEBUG="-DNDEBUG" fi diff --git a/doc/lemon.html b/doc/lemon.html index 773c68e6ad..114526f372 100644 --- a/doc/lemon.html +++ b/doc/lemon.html @@ -85,7 +85,7 @@ Define C preprocessor macro name. This macro is useable by Do not generate a parser. Instead write the input grammar to standard output with all comments, actions, and other extraneous text removed.
  • -l -Omit "#line" directives int the generated parser C code. +Omit "#line" directives in the generated parser C code.
  • -m Cause the output C source code to be compatible with the "makeheaders" program. @@ -929,7 +929,7 @@ token structure. Like this:

    If the data type of terminals is not specified, the default value -is "int".

    +is "void*".

    Non-terminal symbols can each have their own data types. Typically the data type of a non-terminal is a pointer to the root of a parse-tree diff --git a/ext/fts5/fts5_aux.c b/ext/fts5/fts5_aux.c index 836de8a0f0..219ea6fff8 100644 --- a/ext/fts5/fts5_aux.c +++ b/ext/fts5/fts5_aux.c @@ -189,7 +189,7 @@ static int fts5HighlightCb( if( p->iRangeEnd>0 && iPos==p->iRangeEnd ){ fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff); p->iOff = iEndOff; - if( iPositer.iEnd ){ + if( iPos>=p->iter.iStart && iPositer.iEnd ){ fts5HighlightAppend(&rc, p, p->zClose, -1); } } @@ -246,6 +246,118 @@ static void fts5HighlightFunction( ** End of highlight() implementation. **************************************************************************/ +/* +** Context object passed to the fts5SentenceFinderCb() function. +*/ +typedef struct Fts5SFinder Fts5SFinder; +struct Fts5SFinder { + int iPos; /* Current token position */ + int nFirstAlloc; /* Allocated size of aFirst[] */ + int nFirst; /* Number of entries in aFirst[] */ + int *aFirst; /* Array of first token in each sentence */ + const char *zDoc; /* Document being tokenized */ +}; + +/* +** Add an entry to the Fts5SFinder.aFirst[] array. Grow the array if +** necessary. Return SQLITE_OK if successful, or SQLITE_NOMEM if an +** error occurs. +*/ +static int fts5SentenceFinderAdd(Fts5SFinder *p, int iAdd){ + if( p->nFirstAlloc==p->nFirst ){ + int nNew = p->nFirstAlloc ? p->nFirstAlloc*2 : 64; + int *aNew; + + aNew = (int*)sqlite3_realloc(p->aFirst, nNew*sizeof(int)); + if( aNew==0 ) return SQLITE_NOMEM; + p->aFirst = aNew; + p->nFirstAlloc = nNew; + } + p->aFirst[p->nFirst++] = iAdd; + return SQLITE_OK; +} + +/* +** This function is an xTokenize() callback used by the auxiliary snippet() +** function. Its job is to identify tokens that are the first in a sentence. +** For each such token, an entry is added to the SFinder.aFirst[] array. +*/ +static int fts5SentenceFinderCb( + void *pContext, /* Pointer to HighlightContext object */ + int tflags, /* Mask of FTS5_TOKEN_* flags */ + const char *pToken, /* Buffer containing token */ + int nToken, /* Size of token in bytes */ + int iStartOff, /* Start offset of token */ + int iEndOff /* End offset of token */ +){ + int rc = SQLITE_OK; + + UNUSED_PARAM2(pToken, nToken); + UNUSED_PARAM(iEndOff); + + if( (tflags & FTS5_TOKEN_COLOCATED)==0 ){ + Fts5SFinder *p = (Fts5SFinder*)pContext; + if( p->iPos>0 ){ + int i; + char c = 0; + for(i=iStartOff-1; i>=0; i--){ + c = p->zDoc[i]; + if( c!=' ' && c!='\t' && c!='\n' && c!='\r' ) break; + } + if( i!=iStartOff-1 && (c=='.' || c==':') ){ + rc = fts5SentenceFinderAdd(p, p->iPos); + } + }else{ + rc = fts5SentenceFinderAdd(p, 0); + } + p->iPos++; + } + return rc; +} + +static int fts5SnippetScore( + const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ + Fts5Context *pFts, /* First arg to pass to pApi functions */ + int nDocsize, /* Size of column in tokens */ + unsigned char *aSeen, /* Array with one element per query phrase */ + int iCol, /* Column to score */ + int iPos, /* Starting offset to score */ + int nToken, /* Max tokens per snippet */ + int *pnScore, /* OUT: Score */ + int *piPos /* OUT: Adjusted offset */ +){ + int rc; + int i; + int ip = 0; + int ic = 0; + int iOff = 0; + int iFirst = -1; + int nInst; + int nScore = 0; + int iLast = 0; + + rc = pApi->xInstCount(pFts, &nInst); + for(i=0; ixInst(pFts, i, &ip, &ic, &iOff); + if( rc==SQLITE_OK && ic==iCol && iOff>=iPos && iOff<(iPos+nToken) ){ + nScore += (aSeen[ip] ? 1 : 1000); + aSeen[ip] = 1; + if( iFirst<0 ) iFirst = iOff; + iLast = iOff + pApi->xPhraseSize(pFts, ip); + } + } + + *pnScore = nScore; + if( piPos ){ + int iAdj = iFirst - (nToken - (iLast-iFirst)) / 2; + if( (iAdj+nToken)>nDocsize ) iAdj = nDocsize - nToken; + if( iAdj<0 ) iAdj = 0; + *piPos = iAdj; + } + + return rc; +} + /* ** Implementation of snippet() function. */ @@ -267,9 +379,10 @@ static void fts5SnippetFunction( unsigned char *aSeen; /* Array of "seen instance" flags */ int iBestCol; /* Column containing best snippet */ int iBestStart = 0; /* First token of best snippet */ - int iBestLast; /* Last token of best snippet */ int nBestScore = 0; /* Score of best snippet */ int nColSize = 0; /* Total size of iBestCol in tokens */ + Fts5SFinder sFinder; /* Used to find the beginnings of sentences */ + int nCol; if( nVal!=5 ){ const char *zErr = "wrong number of arguments to function snippet()"; @@ -277,13 +390,13 @@ static void fts5SnippetFunction( return; } + nCol = pApi->xColumnCount(pFts); memset(&ctx, 0, sizeof(HighlightContext)); iCol = sqlite3_value_int(apVal[0]); ctx.zOpen = (const char*)sqlite3_value_text(apVal[1]); ctx.zClose = (const char*)sqlite3_value_text(apVal[2]); zEllips = (const char*)sqlite3_value_text(apVal[3]); nToken = sqlite3_value_int(apVal[4]); - iBestLast = nToken-1; iBestCol = (iCol>=0 ? iCol : 0); nPhrase = pApi->xPhraseCount(pFts); @@ -291,65 +404,94 @@ static void fts5SnippetFunction( if( aSeen==0 ){ rc = SQLITE_NOMEM; } - if( rc==SQLITE_OK ){ rc = pApi->xInstCount(pFts, &nInst); } - for(i=0; rc==SQLITE_OK && ixInst(pFts, i, &ip, &iSnippetCol, &iStart); - if( rc==SQLITE_OK && (iCol<0 || iSnippetCol==iCol) ){ - int nScore = 1000; - int iLast = iStart - 1 + pApi->xPhraseSize(pFts, ip); - int j; - aSeen[ip] = 1; - for(j=i+1; rc==SQLITE_OK && jxInst(pFts, j, &ip, &ic, &io); - iFinal = io + pApi->xPhraseSize(pFts, ip) - 1; - if( rc==SQLITE_OK && ic==iSnippetCol && iLastiLast ) iLast = iFinal; + memset(&sFinder, 0, sizeof(Fts5SFinder)); + for(i=0; ixColumnText(pFts, i, &sFinder.zDoc, &nDoc); + if( rc!=SQLITE_OK ) break; + rc = pApi->xTokenize(pFts, + sFinder.zDoc, nDoc, (void*)&sFinder,fts5SentenceFinderCb + ); + if( rc!=SQLITE_OK ) break; + rc = pApi->xColumnSize(pFts, i, &nDocsize); + if( rc!=SQLITE_OK ) break; + + for(ii=0; rc==SQLITE_OK && iixInst(pFts, ii, &ip, &ic, &io); + if( ic!=i || rc!=SQLITE_OK ) continue; + memset(aSeen, 0, nPhrase); + rc = fts5SnippetScore(pApi, pFts, nDocsize, aSeen, i, + io, nToken, &nScore, &iAdj + ); + if( rc==SQLITE_OK && nScore>nBestScore ){ + nBestScore = nScore; + iBestCol = i; + iBestStart = iAdj; + nColSize = nDocsize; } - } - if( rc==SQLITE_OK && nScore>nBestScore ){ - iBestCol = iSnippetCol; - iBestStart = iStart; - iBestLast = iLast; - nBestScore = nScore; + if( rc==SQLITE_OK && sFinder.nFirst && nDocsize>nToken ){ + for(jj=0; jj<(sFinder.nFirst-1); jj++){ + if( sFinder.aFirst[jj+1]>io ) break; + } + + if( sFinder.aFirst[jj]nBestScore ){ + nBestScore = nScore; + iBestCol = i; + iBestStart = sFinder.aFirst[jj]; + nColSize = nDocsize; + } + } + } } } } - if( rc==SQLITE_OK ){ - rc = pApi->xColumnSize(pFts, iBestCol, &nColSize); - } if( rc==SQLITE_OK ){ rc = pApi->xColumnText(pFts, iBestCol, &ctx.zIn, &ctx.nIn); } + if( rc==SQLITE_OK && nColSize==0 ){ + rc = pApi->xColumnSize(pFts, iBestCol, &nColSize); + } if( ctx.zIn ){ if( rc==SQLITE_OK ){ rc = fts5CInstIterInit(pApi, pFts, iBestCol, &ctx.iter); } - if( (iBestStart+nToken-1)>iBestLast ){ - iBestStart -= (iBestStart+nToken-1-iBestLast) / 2; - } - if( iBestStart+nToken>nColSize ){ - iBestStart = nColSize - nToken; - } - if( iBestStart<0 ) iBestStart = 0; - ctx.iRangeStart = iBestStart; ctx.iRangeEnd = iBestStart + nToken - 1; if( iBestStart>0 ){ fts5HighlightAppend(&rc, &ctx, zEllips, -1); } + + /* Advance iterator ctx.iter so that it points to the first coalesced + ** phrase instance at or following position iBestStart. */ + while( ctx.iter.iStart>=0 && ctx.iter.iStartxTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb); } @@ -358,15 +500,15 @@ static void fts5SnippetFunction( }else{ fts5HighlightAppend(&rc, &ctx, zEllips, -1); } - - if( rc==SQLITE_OK ){ - sqlite3_result_text(pCtx, (const char*)ctx.zOut, -1, SQLITE_TRANSIENT); - }else{ - sqlite3_result_error_code(pCtx, rc); - } - sqlite3_free(ctx.zOut); } + if( rc==SQLITE_OK ){ + sqlite3_result_text(pCtx, (const char*)ctx.zOut, -1, SQLITE_TRANSIENT); + }else{ + sqlite3_result_error_code(pCtx, rc); + } + sqlite3_free(ctx.zOut); sqlite3_free(aSeen); + sqlite3_free(sFinder.aFirst); } /************************************************************************/ diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c index ca795a6b35..9dcd88a5bc 100644 --- a/ext/fts5/fts5_expr.c +++ b/ext/fts5/fts5_expr.c @@ -746,49 +746,61 @@ static int fts5ExprNearTest( ** Initialize all term iterators in the pNear object. If any term is found ** to match no documents at all, return immediately without initializing any ** further iterators. +** +** If an error occurs, return an SQLite error code. Otherwise, return +** SQLITE_OK. It is not considered an error if some term matches zero +** documents. */ static int fts5ExprNearInitAll( Fts5Expr *pExpr, Fts5ExprNode *pNode ){ Fts5ExprNearset *pNear = pNode->pNear; - int i, j; - int rc = SQLITE_OK; + int i; assert( pNode->bNomatch==0 ); - for(i=0; rc==SQLITE_OK && inPhrase; i++){ + for(i=0; inPhrase; i++){ Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; - for(j=0; jnTerm; j++){ - Fts5ExprTerm *pTerm = &pPhrase->aTerm[j]; - Fts5ExprTerm *p; - int bEof = 1; + if( pPhrase->nTerm==0 ){ + pNode->bEof = 1; + return SQLITE_OK; + }else{ + int j; + for(j=0; jnTerm; j++){ + Fts5ExprTerm *pTerm = &pPhrase->aTerm[j]; + Fts5ExprTerm *p; + int bHit = 0; - for(p=pTerm; p && rc==SQLITE_OK; p=p->pSynonym){ - if( p->pIter ){ - sqlite3Fts5IterClose(p->pIter); - p->pIter = 0; + for(p=pTerm; p; p=p->pSynonym){ + int rc; + if( p->pIter ){ + sqlite3Fts5IterClose(p->pIter); + p->pIter = 0; + } + rc = sqlite3Fts5IndexQuery( + pExpr->pIndex, p->zTerm, (int)strlen(p->zTerm), + (pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0) | + (pExpr->bDesc ? FTS5INDEX_QUERY_DESC : 0), + pNear->pColset, + &p->pIter + ); + assert( (rc==SQLITE_OK)==(p->pIter!=0) ); + if( rc!=SQLITE_OK ) return rc; + if( 0==sqlite3Fts5IterEof(p->pIter) ){ + bHit = 1; + } } - rc = sqlite3Fts5IndexQuery( - pExpr->pIndex, p->zTerm, (int)strlen(p->zTerm), - (pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0) | - (pExpr->bDesc ? FTS5INDEX_QUERY_DESC : 0), - pNear->pColset, - &p->pIter - ); - assert( rc==SQLITE_OK || p->pIter==0 ); - if( p->pIter && 0==sqlite3Fts5IterEof(p->pIter) ){ - bEof = 0; - } - } - if( bEof ){ - pNode->bEof = 1; - return rc; + if( bHit==0 ){ + pNode->bEof = 1; + return SQLITE_OK; + } } } } - return rc; + pNode->bEof = 0; + return SQLITE_OK; } /* @@ -921,7 +933,7 @@ static int fts5ExprNodeTest_STRING( } }else{ Fts5IndexIter *pIter = pPhrase->aTerm[j].pIter; - if( pIter->iRowid==iLast ) continue; + if( pIter->iRowid==iLast || pIter->bEof ) continue; bMatch = 0; if( fts5ExprAdvanceto(pIter, bDesc, &iLast, &rc, &pNode->bEof) ){ return rc; @@ -1332,7 +1344,10 @@ int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bDesc){ /* If not at EOF but the current rowid occurs earlier than iFirst in ** the iteration order, move to document iFirst or later. */ - if( pRoot->bEof==0 && fts5RowidCmp(p, pRoot->iRowid, iFirst)<0 ){ + if( rc==SQLITE_OK + && 0==pRoot->bEof + && fts5RowidCmp(p, pRoot->iRowid, iFirst)<0 + ){ rc = fts5ExprNodeNext(p, pRoot, 1, iFirst); } @@ -1638,7 +1653,6 @@ int sqlite3Fts5ExprClonePhrase( ){ int rc = SQLITE_OK; /* Return code */ Fts5ExprPhrase *pOrig; /* The phrase extracted from pExpr */ - int i; /* Used to iterate through phrase terms */ Fts5Expr *pNew = 0; /* Expression to return via *ppNew */ TokenCtx sCtx = {0,0}; /* Context object for fts5ParseTokenize */ @@ -1659,7 +1673,7 @@ int sqlite3Fts5ExprClonePhrase( if( rc==SQLITE_OK ){ Fts5Colset *pColsetOrig = pOrig->pNode->pNear->pColset; if( pColsetOrig ){ - int nByte = sizeof(Fts5Colset) + pColsetOrig->nCol * sizeof(int); + int nByte = sizeof(Fts5Colset) + (pColsetOrig->nCol-1) * sizeof(int); Fts5Colset *pColset = (Fts5Colset*)sqlite3Fts5MallocZero(&rc, nByte); if( pColset ){ memcpy(pColset, pColsetOrig, nByte); @@ -1668,18 +1682,25 @@ int sqlite3Fts5ExprClonePhrase( } } - for(i=0; rc==SQLITE_OK && inTerm; i++){ - int tflags = 0; - Fts5ExprTerm *p; - for(p=&pOrig->aTerm[i]; p && rc==SQLITE_OK; p=p->pSynonym){ - const char *zTerm = p->zTerm; - rc = fts5ParseTokenize((void*)&sCtx, tflags, zTerm, (int)strlen(zTerm), - 0, 0); - tflags = FTS5_TOKEN_COLOCATED; - } - if( rc==SQLITE_OK ){ - sCtx.pPhrase->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix; + if( pOrig->nTerm ){ + int i; /* Used to iterate through phrase terms */ + for(i=0; rc==SQLITE_OK && inTerm; i++){ + int tflags = 0; + Fts5ExprTerm *p; + for(p=&pOrig->aTerm[i]; p && rc==SQLITE_OK; p=p->pSynonym){ + const char *zTerm = p->zTerm; + rc = fts5ParseTokenize((void*)&sCtx, tflags, zTerm, (int)strlen(zTerm), + 0, 0); + tflags = FTS5_TOKEN_COLOCATED; + } + if( rc==SQLITE_OK ){ + sCtx.pPhrase->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix; + } } + }else{ + /* This happens when parsing a token or quoted phrase that contains + ** no token characters at all. (e.g ... MATCH '""'). */ + sCtx.pPhrase = sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprPhrase)); } if( rc==SQLITE_OK ){ diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index 65b92a2b99..750e0ca82d 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -702,7 +702,6 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){ return pRet; } - /* ** Release a reference to data record returned by an earlier call to ** fts5DataRead(). @@ -711,6 +710,18 @@ static void fts5DataRelease(Fts5Data *pData){ sqlite3_free(pData); } +static Fts5Data *fts5LeafRead(Fts5Index *p, i64 iRowid){ + Fts5Data *pRet = fts5DataRead(p, iRowid); + if( pRet ){ + if( pRet->szLeaf>pRet->nn ){ + p->rc = FTS5_CORRUPT; + fts5DataRelease(pRet); + pRet = 0; + } + } + return pRet; +} + static int fts5IndexPrepareStmt( Fts5Index *p, sqlite3_stmt **ppStmt, @@ -1519,7 +1530,7 @@ static void fts5SegIterNextPage( pIter->pLeaf = pIter->pNextLeaf; pIter->pNextLeaf = 0; }else if( pIter->iLeafPgno<=pSeg->pgnoLast ){ - pIter->pLeaf = fts5DataRead(p, + pIter->pLeaf = fts5LeafRead(p, FTS5_SEGMENT_ROWID(pSeg->iSegid, pIter->iLeafPgno) ); }else{ @@ -2022,14 +2033,13 @@ static void fts5SegIterNext( if( pLeaf->nn>pLeaf->szLeaf ){ pIter->iPgidxOff = pLeaf->szLeaf + fts5GetVarint32( &pLeaf->p[pLeaf->szLeaf], pIter->iEndofDoclist - ); + ); } - } else if( pLeaf->nn>pLeaf->szLeaf ){ pIter->iPgidxOff = pLeaf->szLeaf + fts5GetVarint32( &pLeaf->p[pLeaf->szLeaf], iOff - ); + ); pIter->iLeafOffset = iOff; pIter->iEndofDoclist = iOff; bNewTerm = 1; @@ -2063,6 +2073,7 @@ static void fts5SegIterNext( */ int nSz; assert( p->rc==SQLITE_OK ); + assert( pIter->iLeafOffset<=pIter->pLeaf->nn ); fts5FastGetVarint32(pIter->pLeaf->p, pIter->iLeafOffset, nSz); pIter->bDel = (nSz & 0x0001); pIter->nPos = nSz>>1; @@ -2269,6 +2280,11 @@ static void fts5LeafSeek( iTermOff += nKeep; iOff = iTermOff; + if( iOff>=n ){ + p->rc = FTS5_CORRUPT; + return; + } + /* Read the nKeep field of the next term. */ fts5FastGetVarint32(a, iOff, nKeep); } @@ -2825,6 +2841,7 @@ static void fts5MultiIterNext( i64 iFrom /* Advance at least as far as this */ ){ int bUseFrom = bFrom; + assert( pIter->base.bEof==0 ); while( p->rc==SQLITE_OK ){ int iFirst = pIter->aFirst[1].iFirst; int bNewTerm = 0; @@ -3051,7 +3068,7 @@ static void fts5ChunkIterate( break; }else{ pgno++; - pData = fts5DataRead(p, FTS5_SEGMENT_ROWID(pSeg->pSeg->iSegid, pgno)); + pData = fts5LeafRead(p, FTS5_SEGMENT_ROWID(pSeg->pSeg->iSegid, pgno)); if( pData==0 ) break; pChunk = &pData->p[4]; nChunk = MIN(nRem, pData->szLeaf - 4); @@ -5813,7 +5830,7 @@ static void fts5IndexIntegrityCheckSegment( ** ignore this b-tree entry. Otherwise, load it into memory. */ if( iIdxLeafpgnoFirst ) continue; iRow = FTS5_SEGMENT_ROWID(pSeg->iSegid, iIdxLeaf); - pLeaf = fts5DataRead(p, iRow); + pLeaf = fts5LeafRead(p, iRow); if( pLeaf==0 ) break; /* Check that the leaf contains at least one term, and that it is equal diff --git a/ext/fts5/fts5_vocab.c b/ext/fts5/fts5_vocab.c index f3a2381cc1..82c7dc9056 100644 --- a/ext/fts5/fts5_vocab.c +++ b/ext/fts5/fts5_vocab.c @@ -279,8 +279,19 @@ static int fts5VocabBestIndexMethod( } } - pInfo->idxNum = idxNum; + /* This virtual table always delivers results in ascending order of + ** the "term" column (column 0). So if the user has requested this + ** specifically - "ORDER BY term" or "ORDER BY term ASC" - set the + ** sqlite3_index_info.orderByConsumed flag to tell the core the results + ** are already in sorted order. */ + if( pInfo->nOrderBy==1 + && pInfo->aOrderBy[0].iColumn==0 + && pInfo->aOrderBy[0].desc==0 + ){ + pInfo->orderByConsumed = 1; + } + pInfo->idxNum = idxNum; return SQLITE_OK; } diff --git a/ext/fts5/test/fts5af.test b/ext/fts5/test/fts5af.test index d6b2241568..6aab55a557 100644 --- a/ext/fts5/test/fts5af.test +++ b/ext/fts5/test/fts5af.test @@ -72,42 +72,56 @@ foreach {tn doc res} { 2.2 {o X o o o o o o} {o [X] o o o o o...} 2.3 {o o X o o o o o} {o o [X] o o o o...} 2.4 {o o o X o o o o} {o o o [X] o o o...} - 2.5 {o o o o X o o o} {...o o o [X] o o o} - 2.6 {o o o o o X o o} {...o o o o [X] o o} - 2.7 {o o o o o o X o} {...o o o o o [X] o} + 2.5 {o o o o X o o o} {o o o o [X] o o...} + 2.6 {o o o o o X o o} {o o o o o [X] o...} + 2.7 {o o o o o o X o} {o o o o o o [X]...} 2.8 {o o o o o o o X} {...o o o o o o [X]} + 2.9 {o o o o o o o X o} {...o o o o o [X] o} + 2.10 {o o o o o o o X o o} {...o o o o [X] o o} + 2.11 {o o o o o o o X o o o} {...o o o [X] o o o} + 2.12 {o o o o o o o X o o o o} {...o o o [X] o o o...} + + 3.1 {X o o o o o o o o} {[X] o o o o o o...} 3.2 {o X o o o o o o o} {o [X] o o o o o...} 3.3 {o o X o o o o o o} {o o [X] o o o o...} 3.4 {o o o X o o o o o} {o o o [X] o o o...} - 3.5 {o o o o X o o o o} {...o o o [X] o o o...} - 3.6 {o o o o o X o o o} {...o o o [X] o o o} - 3.7 {o o o o o o X o o} {...o o o o [X] o o} - 3.8 {o o o o o o o X o} {...o o o o o [X] o} - 3.9 {o o o o o o o o X} {...o o o o o o [X]} + + 3.5 {o o o o o o o X o o o o} {...o o o [X] o o o...} + 3.6 {o o o o o o o o X o o o} {...o o o [X] o o o} + 3.7 {o o o o o o o o o X o o} {...o o o o [X] o o} + 3.8 {o o o o o o o o o o X o} {...o o o o o [X] o} + 3.9 {o o o o o o o o o o o X} {...o o o o o o [X]} 4.1 {X o o o o o X o o} {[X] o o o o o [X]...} - 4.2 {o X o o o o o X o} {...[X] o o o o o [X]...} - 4.3 {o o X o o o o o X} {...[X] o o o o o [X]} + 4.2 {o o o o o o o X o o o o o X o} {...[X] o o o o o [X]...} + 4.3 {o o o o o o o o X o o o o o X} {...[X] o o o o o [X]} 5.1 {X o o o o X o o o} {[X] o o o o [X] o...} - 5.2 {o X o o o o X o o} {...[X] o o o o [X] o...} - 5.3 {o o X o o o o X o} {...[X] o o o o [X] o} - 5.4 {o o o X o o o o X} {...o [X] o o o o [X]} + 5.2 {o o o o o o o X o o o o X o o} {...[X] o o o o [X] o...} + 5.3 {o o o o o o o o X o o o o X o} {...[X] o o o o [X] o} + 5.4 {o o o o o o o o o X o o o o X} {...o [X] o o o o [X]} 6.1 {X o o o X o o o} {[X] o o o [X] o o...} 6.2 {o X o o o X o o o} {o [X] o o o [X] o...} - 6.3 {o o X o o o X o o} {...o [X] o o o [X] o...} - 6.4 {o o o X o o o X o} {...o [X] o o o [X] o} - 6.5 {o o o o X o o o X} {...o o [X] o o o [X]} + 6.3 {o o o o o o o X o o o X o o} {...o [X] o o o [X] o...} + 6.4 {o o o o o o o o X o o o X o} {...o [X] o o o [X] o} + 6.5 {o o o o o o o o o X o o o X} {...o o [X] o o o [X]} 7.1 {X o o X o o o o o} {[X] o o [X] o o o...} 7.2 {o X o o X o o o o} {o [X] o o [X] o o...} - 7.3 {o o X o o X o o o} {...o [X] o o [X] o o...} - 7.4 {o o o X o o X o o} {...o [X] o o [X] o o} - 7.5 {o o o o X o o X o} {...o o [X] o o [X] o} - 7.6 {o o o o o X o o X} {...o o o [X] o o [X]} + 7.3 {o o o o o o o X o o X o o o} {...o [X] o o [X] o o...} + 7.4 {o o o o o o o o X o o X o o} {...o [X] o o [X] o o} + 7.5 {o o o o o o o o o X o o X o} {...o o [X] o o [X] o} + 7.6 {o o o o o o o o o o X o o X} {...o o o [X] o o [X]} + + 8.1 {o o o o o o o o o X o o o o o o o o o o o o o o o o X X X o o o} + {...o o [X] [X] [X] o o...} + 8.2 {o o o o o o o. o o X o o o o o o o o o o o o o o o o X X X o o o} + {...o o [X] o o o o...} + 8.3 {o o o o X o o o o o o o o o o o o o o o o o o o o o X X X o o o} + {o o o o [X] o o...} } { do_snippet_test 1.$tn $doc X $res } @@ -124,24 +138,43 @@ if {[detail_is_full]} { 2.1 {X Y o o o o o o} {[X Y] o o o o o...} 2.2 {o X Y o o o o o} {o [X Y] o o o o...} 2.3 {o o X Y o o o o} {o o [X Y] o o o...} - 2.4 {o o o X Y o o o} {...o o [X Y] o o o} - 2.5 {o o o o X Y o o} {...o o o [X Y] o o} - 2.6 {o o o o o X Y o} {...o o o o [X Y] o} - 2.7 {o o o o o o X Y} {...o o o o o [X Y]} + 2.4 {o o o o o o o X Y o o o} {...o o [X Y] o o o} + 2.5 {o o o o o o o o X Y o o} {...o o o [X Y] o o} + 2.6 {o o o o o o o o o X Y o} {...o o o o [X Y] o} + 2.7 {o o o o o o o o o o X Y} {...o o o o o [X Y]} 3.1 {X Y o o o o o o o} {[X Y] o o o o o...} 3.2 {o X Y o o o o o o} {o [X Y] o o o o...} 3.3 {o o X Y o o o o o} {o o [X Y] o o o...} - 3.4 {o o o X Y o o o o} {...o o [X Y] o o o...} - 3.5 {o o o o X Y o o o} {...o o [X Y] o o o} - 3.6 {o o o o o X Y o o} {...o o o [X Y] o o} - 3.7 {o o o o o o X Y o} {...o o o o [X Y] o} - 3.8 {o o o o o o o X Y} {...o o o o o [X Y]} + 3.4 {o o o o o o o X Y o o o o} {...o o [X Y] o o o...} + 3.5 {o o o o o o o o X Y o o o} {...o o [X Y] o o o} + 3.6 {o o o o o o o o o X Y o o} {...o o o [X Y] o o} + 3.7 {o o o o o o o o o o X Y o} {...o o o o [X Y] o} + 3.8 {o o o o o o o o o o o X Y} {...o o o o o [X Y]} } { do_snippet_test 2.$tn $doc "X + Y" $res } } +do_execsql_test 4.0 { + CREATE VIRTUAL TABLE x1 USING fts5(a, b); + INSERT INTO x1 VALUES('xyz', '1 2 3 4 5 6 7 8 9 10 11 12 13'); + SELECT snippet(x1, 1, '[', ']', '...', 5) FROM x1('xyz'); +} { + {1 2 3 4 5...} +} + +do_execsql_test 5.0 { + CREATE VIRTUAL TABLE p1 USING fts5(a, b); + INSERT INTO p1 VALUES( + 'x a a a a a a a a a a', + 'a a a a a a a a a a a a a a a a a a a x' + ); +} +do_execsql_test 5.1 { + SELECT snippet(p1, 0, '[', ']', '...', 6) FROM p1('x'); +} {{[x] a a a a a...}} + } ;# foreach_detail_mode finish_test diff --git a/ext/fts5/test/fts5corrupt2.test b/ext/fts5/test/fts5corrupt2.test index 3a4fcfaaed..c10017a266 100644 --- a/ext/fts5/test/fts5corrupt2.test +++ b/ext/fts5/test/fts5corrupt2.test @@ -37,7 +37,7 @@ do_execsql_test 1.0 { } set mask [expr 31 << 31] -if 1 { +if 0 { # Test 1: # @@ -84,6 +84,8 @@ foreach {tno stmt} { } } +} + # Using the same database as the 1.* tests. # # Run N-1 tests, where N is the number of bytes in the rightmost leaf page @@ -212,8 +214,6 @@ foreach {tn nCut} { # do_test 4.$tn.x { expr $nCorrupt>0 } 1 } -} - set doc [string repeat "A B C " 1000] do_execsql_test 5.0 { CREATE VIRTUAL TABLE x5 USING fts5(tt); diff --git a/ext/fts5/test/fts5eb.test b/ext/fts5/test/fts5eb.test index 69418aae63..510a0d790c 100644 --- a/ext/fts5/test/fts5eb.test +++ b/ext/fts5/test/fts5eb.test @@ -63,6 +63,23 @@ do_catchsql_test 2.1 { SELECT fts5_expr_tcl() } {1 {wrong number of arguments to function fts5_expr_tcl}} + +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE e1 USING fts5(text, tokenize = 'porter unicode61'); + INSERT INTO e1 VALUES ("just a few words with a / inside"); +} +do_execsql_test 3.1 { + SELECT rowid, bm25(e1) FROM e1 WHERE e1 MATCH '"just"' ORDER BY rank; +} {1 -1e-06} +do_execsql_test 3.2 { + SELECT rowid FROM e1 WHERE e1 MATCH '"/" OR "just"' +} 1 +do_execsql_test 3.3 { + SELECT rowid, bm25(e1) FROM e1 WHERE e1 MATCH '"/" OR "just"' ORDER BY rank; +} {1 -1e-06} + + + finish_test diff --git a/ext/fts5/test/fts5faultB.test b/ext/fts5/test/fts5faultB.test index 40df8b6043..2ce33f4bc4 100644 --- a/ext/fts5/test/fts5faultB.test +++ b/ext/fts5/test/fts5faultB.test @@ -78,6 +78,34 @@ do_faultsim_test 2.4 -faults oom* -body { faultsim_test_result {0 {{3 2} {2 3}}} } +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE x1 USING fts5(z); +} + +do_faultsim_test 3.1 -faults oom* -body { + execsql { + SELECT rowid FROM x1('c') WHERE rowid>1; + } +} -test { + faultsim_test_result {0 {}} +} + +do_execsql_test 3.2 { + INSERT INTO x1 VALUES('a b c'); + INSERT INTO x1 VALUES('b c d'); + INSERT INTO x1 VALUES('c d e'); + INSERT INTO x1 VALUES('d e f'); +} +do_faultsim_test 3.3 -faults oom* -body { + execsql { + SELECT rowid FROM x1('c') WHERE rowid>1; + } +} -test { + faultsim_test_result {0 {2 3}} +} finish_test diff --git a/ext/fts5/test/fts5prefix.test b/ext/fts5/test/fts5prefix.test index 8e0d5a2954..01d0fe5d14 100644 --- a/ext/fts5/test/fts5prefix.test +++ b/ext/fts5/test/fts5prefix.test @@ -9,7 +9,7 @@ # #*********************************************************************** # -# This file containst tests focused on prefix indexes. +# This file contains tests focused on prefix indexes. # source [file join [file dirname [info script]] fts5_common.tcl] diff --git a/ext/fts5/test/fts5simple2.test b/ext/fts5/test/fts5simple2.test index 186d771f76..a6c1789473 100644 --- a/ext/fts5/test/fts5simple2.test +++ b/ext/fts5/test/fts5simple2.test @@ -332,6 +332,41 @@ do_execsql_test 16.0 { DELETE FROM t2; } +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 17.0 { + CREATE VIRTUAL TABLE t2 USING fts5(x, y); + BEGIN; + INSERT INTO t2 VALUES('a aa aaa', 'b bb bbb'); + INSERT INTO t2 VALUES('a aa aaa', 'b bb bbb'); + INSERT INTO t2 VALUES('a aa aaa', 'b bb bbb'); + COMMIT; +} +do_execsql_test 17.1 { SELECT * FROM t2('y:a*') WHERE rowid BETWEEN 10 AND 20 } +do_execsql_test 17.2 { + BEGIN; + INSERT INTO t2 VALUES('a aa aaa', 'b bb bbb'); + SELECT * FROM t2('y:a*') WHERE rowid BETWEEN 10 AND 20 ; +} +do_execsql_test 17.3 { + COMMIT +} + +reset_db +do_execsql_test 17.4 { + CREATE VIRTUAL TABLE t2 USING fts5(x, y); + BEGIN; + INSERT INTO t2 VALUES('a aa aaa', 'b bb bbb'); + INSERT INTO t2 VALUES('a aa aaa', 'b bb bbb'); + SELECT * FROM t2('y:a*') WHERE rowid>66; +} +do_execsql_test 17.5 { SELECT * FROM t2('x:b* OR y:a*') } +do_execsql_test 17.5 { COMMIT ; SELECT * FROM t2('x:b* OR y:a*') } +do_execsql_test 17.6 { + SELECT * FROM t2('x:b* OR y:a*') WHERE rowid>55 +} + #db eval {SELECT rowid, fts5_decode_none(rowid, block) aS r FROM t2_data} {puts $r} finish_test diff --git a/ext/fts5/test/fts5simple3.test b/ext/fts5/test/fts5simple3.test index b6922f84dd..c755ea0567 100644 --- a/ext/fts5/test/fts5simple3.test +++ b/ext/fts5/test/fts5simple3.test @@ -80,6 +80,40 @@ do_execsql_test 3.0 { SELECT * FROM x3('x OR y OR z'); } +#------------------------------------------------------------------------- +# Test that a crash occuring when the second or subsequent tokens in a +# phrase matched zero rows has been fixed. +# +do_execsql_test 4.0 { + CREATE VIRTUAL TABLE t1 USING fts5(x); + INSERT INTO t1 VALUES('ab'); + INSERT INTO t1 VALUES('cd'); + INSERT INTO t1 VALUES('ab cd'); + INSERT INTO t1 VALUES('ab cdXXX'); + INSERT INTO t1 VALUES('abXXX cd'); +} +do_execsql_test 4.1 { + SELECT * FROM t1('"ab cd" OR "ab cd" *'); +} {{ab cd} {ab cdXXX}} +do_execsql_test 4.2 { + SELECT * FROM t1('"xy zz" OR "ab cd" *'); +} {{ab cd} {ab cdXXX}} +do_execsql_test 4.3 { + SELECT * FROM t1('"xy zz" OR "xy zz" *'); +} +do_execsql_test 4.4 { + SELECT * FROM t1('"ab cd" OR "xy zz" *'); +} {{ab cd}} +do_execsql_test 4.5 { + CREATE VIRTUAL TABLE t2 USING fts5(x); + INSERT INTO t2 VALUES('ab'); + INSERT INTO t2 VALUES('cd'); + INSERT INTO t2 VALUES('ef'); +} +do_execsql_test 4.6 { + SELECT * FROM t2('ab + xyz'); +} + finish_test diff --git a/ext/fts5/test/fts5unicode2.test b/ext/fts5/test/fts5unicode2.test index d3ff5128da..8e5bb8e22b 100644 --- a/ext/fts5/test/fts5unicode2.test +++ b/ext/fts5/test/fts5unicode2.test @@ -160,12 +160,12 @@ foreach {tn query snippet} { the maximum x value. } 4 "rollback" { - ...[ROLLBACK]. Instead, the pending statement - will return SQLITE_ABORT upon next access after the [ROLLBACK]. + Pending statements no longer block [ROLLBACK]. Instead, the pending + statement will return SQLITE_ABORT upon... } 5 "rOllback" { - ...[ROLLBACK]. Instead, the pending statement - will return SQLITE_ABORT upon next access after the [ROLLBACK]. + Pending statements no longer block [ROLLBACK]. Instead, the pending + statement will return SQLITE_ABORT upon... } 6 "lang*" { Added support for the FTS4 [languageid] option. diff --git a/ext/fts5/test/fts5vocab.test b/ext/fts5/test/fts5vocab.test index f7278dd5c1..5e0499fbf9 100644 --- a/ext/fts5/test/fts5vocab.test +++ b/ext/fts5/test/fts5vocab.test @@ -442,8 +442,44 @@ if {[detail_is_none]} { } sqlite3_fts5_may_be_corrupt 0 - } +#------------------------------------------------------------------------- +# Test that both "ORDER BY term" and "ORDER BY term DESC" work. +# +reset_db +do_execsql_test 9.1 { + CREATE VIRTUAL TABLE x1 USING fts5(x); + INSERT INTO x1 VALUES('def ABC ghi'); + INSERT INTO x1 VALUES('DEF abc GHI'); +} + +do_execsql_test 9.2 { + CREATE VIRTUAL TABLE rrr USING fts5vocab(x1, row); + SELECT * FROM rrr +} { + abc 2 2 def 2 2 ghi 2 2 +} +do_execsql_test 9.3 { + SELECT * FROM rrr ORDER BY term ASC +} { + abc 2 2 def 2 2 ghi 2 2 +} +do_execsql_test 9.4 { + SELECT * FROM rrr ORDER BY term DESC +} { + ghi 2 2 def 2 2 abc 2 2 +} +do_test 9.5 { + set e2 [db eval { EXPLAIN SELECT * FROM rrr ORDER BY term ASC }] + expr [lsearch $e2 SorterSort]<0 +} 1 +do_test 9.6 { + set e2 [db eval { EXPLAIN SELECT * FROM rrr ORDER BY term DESC }] + expr [lsearch $e2 SorterSort]<0 +} 0 + + + finish_test diff --git a/ext/icu/icu.c b/ext/icu/icu.c index 4d19dfbd62..d2beaa3353 100644 --- a/ext/icu/icu.c +++ b/ext/icu/icu.c @@ -349,7 +349,7 @@ static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){ ** of upper() or lower(). ** ** lower('I', 'en_us') -> 'i' -** lower('I', 'tr_tr') -> 'ı' (small dotless i) +** lower('I', 'tr_tr') -> '\u131' (small dotless i) ** ** http://www.icu-project.org/userguide/posix.html#case_mappings */ @@ -500,20 +500,20 @@ int sqlite3IcuInit(sqlite3 *db){ void *pContext; /* sqlite3_user_data() context */ void (*xFunc)(sqlite3_context*,int,sqlite3_value**); } scalars[] = { - {"regexp", 2, SQLITE_ANY, 0, icuRegexpFunc}, + {"regexp", 2, SQLITE_ANY|SQLITE_DETERMINISTIC, 0, icuRegexpFunc}, - {"lower", 1, SQLITE_UTF16, 0, icuCaseFunc16}, - {"lower", 2, SQLITE_UTF16, 0, icuCaseFunc16}, - {"upper", 1, SQLITE_UTF16, (void*)1, icuCaseFunc16}, - {"upper", 2, SQLITE_UTF16, (void*)1, icuCaseFunc16}, + {"lower", 1, SQLITE_UTF16|SQLITE_DETERMINISTIC, 0, icuCaseFunc16}, + {"lower", 2, SQLITE_UTF16|SQLITE_DETERMINISTIC, 0, icuCaseFunc16}, + {"upper", 1, SQLITE_UTF16|SQLITE_DETERMINISTIC, (void*)1, icuCaseFunc16}, + {"upper", 2, SQLITE_UTF16|SQLITE_DETERMINISTIC, (void*)1, icuCaseFunc16}, - {"lower", 1, SQLITE_UTF8, 0, icuCaseFunc16}, - {"lower", 2, SQLITE_UTF8, 0, icuCaseFunc16}, - {"upper", 1, SQLITE_UTF8, (void*)1, icuCaseFunc16}, - {"upper", 2, SQLITE_UTF8, (void*)1, icuCaseFunc16}, + {"lower", 1, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuCaseFunc16}, + {"lower", 2, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuCaseFunc16}, + {"upper", 1, SQLITE_UTF8|SQLITE_DETERMINISTIC, (void*)1, icuCaseFunc16}, + {"upper", 2, SQLITE_UTF8|SQLITE_DETERMINISTIC, (void*)1, icuCaseFunc16}, - {"like", 2, SQLITE_UTF8, 0, icuLikeFunc}, - {"like", 3, SQLITE_UTF8, 0, icuLikeFunc}, + {"like", 2, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuLikeFunc}, + {"like", 3, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuLikeFunc}, {"icu_load_collation", 2, SQLITE_UTF8, (void*)db, icuLoadCollation}, }; diff --git a/ext/misc/csv.c b/ext/misc/csv.c index 2a9463bd6e..f887784ab8 100644 --- a/ext/misc/csv.c +++ b/ext/misc/csv.c @@ -402,7 +402,7 @@ static int csv_boolean(const char *z){ if( sqlite3_stricmp("yes",z)==0 || sqlite3_stricmp("on",z)==0 || sqlite3_stricmp("true",z)==0 - || (z[0]=='1' && z[0]==0) + || (z[0]=='1' && z[1]==0) ){ return 1; } diff --git a/ext/misc/json1.c b/ext/misc/json1.c index 61c52ebe61..327ad57500 100644 --- a/ext/misc/json1.c +++ b/ext/misc/json1.c @@ -49,13 +49,15 @@ SQLITE_EXTENSION_INIT1 #ifdef sqlite3Isdigit /* Use the SQLite core versions if this routine is part of the ** SQLite amalgamation */ -# define safe_isdigit(x) sqlite3Isdigit(x) -# define safe_isalnum(x) sqlite3Isalnum(x) +# define safe_isdigit(x) sqlite3Isdigit(x) +# define safe_isalnum(x) sqlite3Isalnum(x) +# define safe_isxdigit(x) sqlite3Isxdigit(x) #else /* Use the standard library for separate compilation */ #include /* amalgamator: keep */ -# define safe_isdigit(x) isdigit((unsigned char)(x)) -# define safe_isalnum(x) isalnum((unsigned char)(x)) +# define safe_isdigit(x) isdigit((unsigned char)(x)) +# define safe_isalnum(x) isalnum((unsigned char)(x)) +# define safe_isxdigit(x) isxdigit((unsigned char)(x)) #endif /* @@ -593,12 +595,13 @@ static void jsonReturn( c = z[++i]; if( c=='u' ){ u32 v = 0, k; - for(k=0; k<4 && i='0' && c<='9' ) v = v*16 + c - '0'; - else if( c>='A' && c<='F' ) v = v*16 + c - 'A' + 10; - else if( c>='a' && c<='f' ) v = v*16 + c - 'a' + 10; - else break; + assert( safe_isxdigit(c) ); + if( c<='9' ) v = v*16 + c - '0'; + else if( c<='F' ) v = v*16 + c - 'A' + 10; + else v = v*16 + c - 'a' + 10; } if( v==0 ) break; if( v<=0x7f ){ @@ -702,6 +705,15 @@ static int jsonParseAddNode( return pParse->nNode++; } +/* +** Return true if z[] begins with 4 (or more) hexadecimal digits +*/ +static int jsonIs4Hex(const char *z){ + int i; + for(i=0; i<4; i++) if( !safe_isxdigit(z[i]) ) return 0; + return 1; +} + /* ** Parse a single JSON value which begins at pParse->zJson[i]. Return the ** index of the first character past the end of the value parsed. @@ -776,8 +788,13 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ if( c==0 ) return -1; if( c=='\\' ){ c = pParse->zJson[++j]; - if( c==0 ) return -1; - jnFlags = JNODE_ESCAPE; + if( c=='"' || c=='\\' || c=='/' || c=='b' || c=='f' + || c=='n' || c=='r' || c=='t' + || (c=='u' && jsonIs4Hex(pParse->zJson+j+1)) ){ + jnFlags = JNODE_ESCAPE; + }else{ + return -1; + } }else if( c=='"' ){ break; } @@ -1645,7 +1662,7 @@ static void jsonObjectFinal(sqlite3_context *ctx){ if( pStr ){ jsonAppendChar(pStr, '}'); if( pStr->bErr ){ - if( pStr->bErr==0 ) sqlite3_result_error_nomem(ctx); + if( pStr->bErr==1 ) sqlite3_result_error_nomem(ctx); assert( pStr->bStatic ); }else{ sqlite3_result_text(ctx, pStr->zBuf, pStr->nUsed, @@ -1923,9 +1940,9 @@ static int jsonEachColumn( /* For json_each() path and root are the same so fall through ** into the root case */ } - case JEACH_ROOT: { + default: { const char *zRoot = p->zRoot; - if( zRoot==0 ) zRoot = "$"; + if( zRoot==0 ) zRoot = "$"; sqlite3_result_text(ctx, zRoot, -1, SQLITE_STATIC); break; } diff --git a/ext/misc/memvfs.c b/ext/misc/memvfs.c new file mode 100644 index 0000000000..62a8a033d7 --- /dev/null +++ b/ext/misc/memvfs.c @@ -0,0 +1,491 @@ +/* +** 2016-09-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 is an in-memory read-only VFS implementation. The application +** supplies a block of memory which is the database file, and this VFS +** uses that block of memory. +** +** Because there is no place to store journals and no good way to lock +** the "file", this VFS is read-only. +** +** USAGE: +** +** sqlite3_open_v2("file:/whatever?ptr=0xf05538&sz=14336", &db, +** SQLITE_OPEN_READONLY | SQLITE_OPEN_URI, +** "memvfs"); +** +** The ptr= and sz= query parameters are required or the open will fail. +** The ptr= parameter gives the memory address of the buffer holding the +** read-only database and sz= gives the size of the database. The parameter +** values may be in hexadecimal or decimal. The filename is ignored. +*/ +#include +SQLITE_EXTENSION_INIT1 +#include +#include + + +/* +** Forward declaration of objects used by this utility +*/ +typedef struct sqlite3_vfs MemVfs; +typedef struct MemFile MemFile; + +/* Access to a lower-level VFS that (might) implement dynamic loading, +** access to randomness, etc. +*/ +#define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData)) + +/* An open file */ +struct MemFile { + sqlite3_file base; /* IO methods */ + sqlite3_int64 sz; /* Size of the file */ + unsigned char *aData; /* content of the file */ +}; + +/* +** Methods for MemFile +*/ +static int memClose(sqlite3_file*); +static int memRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); +static int memWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst); +static int memTruncate(sqlite3_file*, sqlite3_int64 size); +static int memSync(sqlite3_file*, int flags); +static int memFileSize(sqlite3_file*, sqlite3_int64 *pSize); +static int memLock(sqlite3_file*, int); +static int memUnlock(sqlite3_file*, int); +static int memCheckReservedLock(sqlite3_file*, int *pResOut); +static int memFileControl(sqlite3_file*, int op, void *pArg); +static int memSectorSize(sqlite3_file*); +static int memDeviceCharacteristics(sqlite3_file*); +static int memShmMap(sqlite3_file*, int iPg, int pgsz, int, void volatile**); +static int memShmLock(sqlite3_file*, int offset, int n, int flags); +static void memShmBarrier(sqlite3_file*); +static int memShmUnmap(sqlite3_file*, int deleteFlag); +static int memFetch(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp); +static int memUnfetch(sqlite3_file*, sqlite3_int64 iOfst, void *p); + +/* +** Methods for MemVfs +*/ +static int memOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *); +static int memDelete(sqlite3_vfs*, const char *zName, int syncDir); +static int memAccess(sqlite3_vfs*, const char *zName, int flags, int *); +static int memFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut); +static void *memDlOpen(sqlite3_vfs*, const char *zFilename); +static void memDlError(sqlite3_vfs*, int nByte, char *zErrMsg); +static void (*memDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void); +static void memDlClose(sqlite3_vfs*, void*); +static int memRandomness(sqlite3_vfs*, int nByte, char *zOut); +static int memSleep(sqlite3_vfs*, int microseconds); +static int memCurrentTime(sqlite3_vfs*, double*); +static int memGetLastError(sqlite3_vfs*, int, char *); +static int memCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*); + +static sqlite3_vfs mem_vfs = { + 2, /* iVersion */ + 0, /* szOsFile (set when registered) */ + 1024, /* mxPathname */ + 0, /* pNext */ + "memvfs", /* zName */ + 0, /* pAppData (set when registered) */ + memOpen, /* xOpen */ + memDelete, /* xDelete */ + memAccess, /* xAccess */ + memFullPathname, /* xFullPathname */ + memDlOpen, /* xDlOpen */ + memDlError, /* xDlError */ + memDlSym, /* xDlSym */ + memDlClose, /* xDlClose */ + memRandomness, /* xRandomness */ + memSleep, /* xSleep */ + memCurrentTime, /* xCurrentTime */ + memGetLastError, /* xGetLastError */ + memCurrentTimeInt64 /* xCurrentTimeInt64 */ +}; + +static const sqlite3_io_methods mem_io_methods = { + 3, /* iVersion */ + memClose, /* xClose */ + memRead, /* xRead */ + memWrite, /* xWrite */ + memTruncate, /* xTruncate */ + memSync, /* xSync */ + memFileSize, /* xFileSize */ + memLock, /* xLock */ + memUnlock, /* xUnlock */ + memCheckReservedLock, /* xCheckReservedLock */ + memFileControl, /* xFileControl */ + memSectorSize, /* xSectorSize */ + memDeviceCharacteristics, /* xDeviceCharacteristics */ + memShmMap, /* xShmMap */ + memShmLock, /* xShmLock */ + memShmBarrier, /* xShmBarrier */ + memShmUnmap, /* xShmUnmap */ + memFetch, /* xFetch */ + memUnfetch /* xUnfetch */ +}; + + + +/* +** Close an mem-file. +** +** The pData pointer is owned by the application, so there is nothing +** to free. +*/ +static int memClose(sqlite3_file *pFile){ + return SQLITE_OK; +} + +/* +** Read data from an mem-file. +*/ +static int memRead( + sqlite3_file *pFile, + void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + MemFile *p = (MemFile *)pFile; + memcpy(zBuf, p->aData+iOfst, iAmt); + return SQLITE_OK; +} + +/* +** Write data to an mem-file. +*/ +static int memWrite( + sqlite3_file *pFile, + const void *z, + int iAmt, + sqlite_int64 iOfst +){ + return SQLITE_READONLY; +} + +/* +** Truncate an mem-file. +*/ +static int memTruncate(sqlite3_file *pFile, sqlite_int64 size){ + return SQLITE_READONLY; +} + +/* +** Sync an mem-file. +*/ +static int memSync(sqlite3_file *pFile, int flags){ + return SQLITE_READONLY; +} + +/* +** Return the current file-size of an mem-file. +*/ +static int memFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ + MemFile *p = (MemFile *)pFile; + *pSize = p->sz; + return SQLITE_OK; +} + +/* +** Lock an mem-file. +*/ +static int memLock(sqlite3_file *pFile, int eLock){ + return SQLITE_READONLY; +} + +/* +** Unlock an mem-file. +*/ +static int memUnlock(sqlite3_file *pFile, int eLock){ + return SQLITE_OK; +} + +/* +** Check if another file-handle holds a RESERVED lock on an mem-file. +*/ +static int memCheckReservedLock(sqlite3_file *pFile, int *pResOut){ + *pResOut = 0; + return SQLITE_OK; +} + +/* +** File control method. For custom operations on an mem-file. +*/ +static int memFileControl(sqlite3_file *pFile, int op, void *pArg){ + MemFile *p = (MemFile *)pFile; + int rc = SQLITE_NOTFOUND; + if( op==SQLITE_FCNTL_VFSNAME ){ + *(char**)pArg = sqlite3_mprintf("mem(%p,%lld)", p->aData, p->sz); + rc = SQLITE_OK; + } + return rc; +} + +/* +** Return the sector-size in bytes for an mem-file. +*/ +static int memSectorSize(sqlite3_file *pFile){ + return 1024; +} + +/* +** Return the device characteristic flags supported by an mem-file. +*/ +static int memDeviceCharacteristics(sqlite3_file *pFile){ + return SQLITE_IOCAP_IMMUTABLE; +} + +/* Create a shared memory file mapping */ +static int memShmMap( + sqlite3_file *pFile, + int iPg, + int pgsz, + int bExtend, + void volatile **pp +){ + return SQLITE_READONLY; +} + +/* Perform locking on a shared-memory segment */ +static int memShmLock(sqlite3_file *pFile, int offset, int n, int flags){ + return SQLITE_READONLY; +} + +/* Memory barrier operation on shared memory */ +static void memShmBarrier(sqlite3_file *pFile){ + return; +} + +/* Unmap a shared memory segment */ +static int memShmUnmap(sqlite3_file *pFile, int deleteFlag){ + return SQLITE_OK; +} + +/* Fetch a page of a memory-mapped file */ +static int memFetch( + sqlite3_file *pFile, + sqlite3_int64 iOfst, + int iAmt, + void **pp +){ + MemFile *p = (MemFile *)pFile; + *pp = (void*)(p->aData + iOfst); + return SQLITE_OK; +} + +/* Release a memory-mapped page */ +static int memUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){ + return SQLITE_OK; +} + +/* +** Open an mem file handle. +*/ +static int memOpen( + sqlite3_vfs *pVfs, + const char *zName, + sqlite3_file *pFile, + int flags, + int *pOutFlags +){ + MemFile *p = (MemFile*)pFile; + memset(p, 0, sizeof(*p)); + if( (flags & SQLITE_OPEN_MAIN_DB)==0 ) return SQLITE_CANTOPEN; + p->aData = (unsigned char*)sqlite3_uri_int64(zName,"ptr",0); + if( p->aData==0 ) return SQLITE_CANTOPEN; + p->sz = sqlite3_uri_int64(zName,"sz",0); + if( p->sz<0 ) return SQLITE_CANTOPEN; + pFile->pMethods = &mem_io_methods; + return SQLITE_OK; +} + +/* +** Delete the file located at zPath. If the dirSync argument is true, +** ensure the file-system modifications are synced to disk before +** returning. +*/ +static int memDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ + return SQLITE_READONLY; +} + +/* +** Test for access permissions. Return true if the requested permission +** is available, or false otherwise. +*/ +static int memAccess( + sqlite3_vfs *pVfs, + const char *zPath, + int flags, + int *pResOut +){ + /* The spec says there are three possible values for flags. But only + ** two of them are actually used */ + assert( flags==SQLITE_ACCESS_EXISTS || flags==SQLITE_ACCESS_READWRITE ); + if( flags==SQLITE_ACCESS_READWRITE ){ + *pResOut = 0; + }else{ + *pResOut = 1; + } + return SQLITE_OK; +} + +/* +** Populate buffer zOut with the full canonical pathname corresponding +** to the pathname in zPath. zOut is guaranteed to point to a buffer +** of at least (INST_MAX_PATHNAME+1) bytes. +*/ +static int memFullPathname( + sqlite3_vfs *pVfs, + const char *zPath, + int nOut, + char *zOut +){ + sqlite3_snprintf(nOut, zOut, "%s", zPath); + return SQLITE_OK; +} + +/* +** Open the dynamic library located at zPath and return a handle. +*/ +static void *memDlOpen(sqlite3_vfs *pVfs, const char *zPath){ + return ORIGVFS(pVfs)->xDlOpen(ORIGVFS(pVfs), zPath); +} + +/* +** Populate the buffer zErrMsg (size nByte bytes) with a human readable +** utf-8 string describing the most recent error encountered associated +** with dynamic libraries. +*/ +static void memDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){ + ORIGVFS(pVfs)->xDlError(ORIGVFS(pVfs), nByte, zErrMsg); +} + +/* +** Return a pointer to the symbol zSymbol in the dynamic library pHandle. +*/ +static void (*memDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){ + return ORIGVFS(pVfs)->xDlSym(ORIGVFS(pVfs), p, zSym); +} + +/* +** Close the dynamic library handle pHandle. +*/ +static void memDlClose(sqlite3_vfs *pVfs, void *pHandle){ + ORIGVFS(pVfs)->xDlClose(ORIGVFS(pVfs), pHandle); +} + +/* +** Populate the buffer pointed to by zBufOut with nByte bytes of +** random data. +*/ +static int memRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ + return ORIGVFS(pVfs)->xRandomness(ORIGVFS(pVfs), nByte, zBufOut); +} + +/* +** Sleep for nMicro microseconds. Return the number of microseconds +** actually slept. +*/ +static int memSleep(sqlite3_vfs *pVfs, int nMicro){ + return ORIGVFS(pVfs)->xSleep(ORIGVFS(pVfs), nMicro); +} + +/* +** Return the current time as a Julian Day number in *pTimeOut. +*/ +static int memCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){ + return ORIGVFS(pVfs)->xCurrentTime(ORIGVFS(pVfs), pTimeOut); +} + +static int memGetLastError(sqlite3_vfs *pVfs, int a, char *b){ + return ORIGVFS(pVfs)->xGetLastError(ORIGVFS(pVfs), a, b); +} +static int memCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){ + return ORIGVFS(pVfs)->xCurrentTimeInt64(ORIGVFS(pVfs), p); +} + +#ifdef MEMVFS_TEST +/* +** memload(FILENAME) +** +** This an SQL function used to help in testing the memvfs VFS. The +** function reads the content of a file into memory and then returns +** a string that gives the locate and size of the in-memory buffer. +*/ +#include +static void memvfsMemloadFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + unsigned char *p; + sqlite3_int64 sz; + FILE *in; + const char *zFilename = (const char*)sqlite3_value_text(argv[0]); + char zReturn[100]; + + if( zFilename==0 ) return; + in = fopen(zFilename, "rb"); + if( in==0 ) return; + fseek(in, 0, SEEK_END); + sz = ftell(in); + rewind(in); + p = sqlite3_malloc( sz ); + if( p==0 ){ + fclose(in); + sqlite3_result_error_nomem(context); + return; + } + fread(p, sz, 1, in); + fclose(in); + sqlite3_snprintf(sizeof(zReturn),zReturn,"ptr=%lld&sz=%lld", + (sqlite3_int64)p, sz); + sqlite3_result_text(context, zReturn, -1, SQLITE_TRANSIENT); +} +/* Called for each new database connection */ +static int memvfsRegister( + sqlite3 *db, + const char **pzErrMsg, + const struct sqlite3_api_routines *pThunk +){ + return sqlite3_create_function(db, "memload", 1, SQLITE_UTF8, 0, + memvfsMemloadFunc, 0, 0); +} +#endif /* MEMVFS_TEST */ + + +#ifdef _WIN32 +__declspec(dllexport) +#endif +/* +** This routine is called when the extension is loaded. +** Register the new VFS. +*/ +int sqlite3_memvfs_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); + mem_vfs.pAppData = sqlite3_vfs_find(0); + mem_vfs.szOsFile = sizeof(MemFile); + rc = sqlite3_vfs_register(&mem_vfs, 1); +#ifdef MEMVFS_TEST + if( rc==SQLITE_OK ){ + rc = sqlite3_auto_extension((void(*)(void))memvfsRegister); + } +#endif + if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY; + return rc; +} diff --git a/ext/misc/remember.c b/ext/misc/remember.c new file mode 100644 index 0000000000..aa3eff8a3f --- /dev/null +++ b/ext/misc/remember.c @@ -0,0 +1,68 @@ +/* +** 2016-08-09 +** +** 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 demonstrates how to create an SQL function that is a pass-through +** for integer values (it returns a copy of its argument) but also saves the +** value that is passed through into a C-language variable. The address of +** the C-language variable is supplied as the second argument. +** +** This allows, for example, a counter to incremented and the original +** value retrieved, atomically, using a single statement: +** +** UPDATE counterTab SET cnt=remember(cnt,$PTR)+1 WHERE id=$ID +** +** Prepare the above statement once. Then to use it, bind the address +** of the output variable to $PTR and the id of the counter to $ID and +** run the prepared statement. +** +** One can imagine doing similar things with floating-point values and +** strings, but this demonstration extension will stick to using just +** integers. +*/ +#include "sqlite3ext.h" +SQLITE_EXTENSION_INIT1 +#include + +/* +** remember(V,PTR) +** +** Return the integer value V. Also save the value of V in a +** C-language variable whose address is PTR. +*/ +static void rememberFunc( + sqlite3_context *pCtx, + int argc, + sqlite3_value **argv +){ + sqlite3_int64 v; + sqlite3_int64 ptr; + assert( argc==2 ); + v = sqlite3_value_int64(argv[0]); + ptr = sqlite3_value_int64(argv[1]); + *((sqlite3_int64*)ptr) = v; + sqlite3_result_int64(pCtx, v); +} + +#ifdef _WIN32 +__declspec(dllexport) +#endif +int sqlite3_remember_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); + rc = sqlite3_create_function(db, "remember", 2, SQLITE_UTF8, 0, + rememberFunc, 0, 0); + return rc; +} diff --git a/ext/rbu/rbudiff.test b/ext/rbu/rbudiff.test index 041a242428..6f6af82804 100644 --- a/ext/rbu/rbudiff.test +++ b/ext/rbu/rbudiff.test @@ -140,6 +140,24 @@ foreach {tn init mod} { ); } + 4 { + CREATE TABLE x1(a, b, c, PRIMARY KEY(a, b, c)); + INSERT INTO x1 VALUES('u', 'v', NULL); + INSERT INTO x1 VALUES('x', 'y', 'z'); + INSERT INTO x1 VALUES('a', NULL, 'b'); + } { + INSERT INTO x1 VALUES('a', 'b', 'c'); + } + + 5 { + CREATE TABLE t1(a PRIMARY KEY, b); + INSERT INTO t1 VALUES(1, NULL); + INSERT INTO t1 VALUES(2, X''); + } { + UPDATE t1 SET b = X'' WHERE a=1; + UPDATE t1 SET b = NULL WHERE a=2; + } + } { catch { db close } @@ -280,5 +298,6 @@ tablE t1 USING FTs5(c); } } + finish_test diff --git a/ext/rbu/rbudor.test b/ext/rbu/rbudor.test new file mode 100644 index 0000000000..dcee7ac381 --- /dev/null +++ b/ext/rbu/rbudor.test @@ -0,0 +1,59 @@ +# 2016 October 21 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# This test file focuses on interactions between RBU and the feature +# enabled by SQLITE_DIRECT_OVERFLOW_READ - Direct Overflow Read. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source $testdir/tester.tcl +set ::testprefix rbudor + +set bigA [string repeat a 5000] +set bigB [string repeat b 5000] +do_execsql_test 1.0 { + PRAGMA page_size = 1024; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b BLOB); + INSERT INTO t1 VALUES(1, $bigA); +} {} + +do_test 1.1 { + forcedelete rbu.db + sqlite3 rbu rbu.db + rbu eval { + CREATE TABLE data_t1(a, b, rbu_control); + INSERT INTO data_t1 VALUES(2, $bigB, 0); + } + rbu close +} {} + +do_test 1.2 { + sqlite3rbu rbu test.db rbu.db + while {[rbu state]!="checkpoint"} { + rbu step + } + rbu step + db eval { SELECT * FROM t1 } +} [list 1 $bigA 2 $bigB] + +do_test 1.3 { + while {[rbu step]=="SQLITE_OK"} {} + rbu close +} {SQLITE_DONE} + +do_execsql_test 1.4 { + SELECT * FROM t1 +} [list 1 $bigA 2 $bigB] + +finish_test + diff --git a/ext/rbu/sqlite3rbu.h b/ext/rbu/sqlite3rbu.h index 3f5f29a8e6..12112c14e1 100644 --- a/ext/rbu/sqlite3rbu.h +++ b/ext/rbu/sqlite3rbu.h @@ -104,7 +104,7 @@ ** may also be named data_, where is any sequence ** of zero or more numeric characters (0-9). This can be significant because ** tables within the RBU database are always processed in order sorted by -** name. By judicious selection of the the portion of the names +** name. By judicious selection of the portion of the names ** of the RBU tables the user can therefore control the order in which they ** are processed. This can be useful, for example, to ensure that "external ** content" FTS4 tables are updated before their underlying content tables. diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c index fc69acd6fa..c7de6ac123 100644 --- a/ext/rtree/rtree.c +++ b/ext/rtree/rtree.c @@ -1542,7 +1542,7 @@ static int rtreeFilter( if( idxNum==1 ){ /* Special case - lookup by rowid. */ RtreeNode *pLeaf; /* Leaf on which the required cell resides */ - RtreeSearchPoint *p; /* Search point for the the leaf */ + RtreeSearchPoint *p; /* Search point for the leaf */ i64 iRowid = sqlite3_value_int64(argv[0]); i64 iNode = 0; rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode); @@ -3012,10 +3012,12 @@ static int rtreeQueryStat1(sqlite3 *db, Rtree *pRtree){ int rc; i64 nRow = 0; - if( sqlite3_table_column_metadata(db,pRtree->zDb,"sqlite_stat1", - 0,0,0,0,0,0)==SQLITE_ERROR ){ + rc = sqlite3_table_column_metadata( + db, pRtree->zDb, "sqlite_stat1",0,0,0,0,0,0 + ); + if( rc!=SQLITE_OK ){ pRtree->nRowEst = RTREE_DEFAULT_ROWEST; - return SQLITE_OK; + return rc==SQLITE_ERROR ? SQLITE_OK : rc; } zSql = sqlite3_mprintf(zFmt, pRtree->zDb, pRtree->zName); if( zSql==0 ){ diff --git a/ext/rtree/rtreeD.test b/ext/rtree/rtreeD.test index c4a7d22e2e..d846fc61ff 100644 --- a/ext/rtree/rtreeD.test +++ b/ext/rtree/rtreeD.test @@ -19,7 +19,7 @@ if {![info exists testdir]} { source [file join [file dirname [info script]] rtree_util.tcl] source $testdir/tester.tcl source $testdir/lock_common.tcl -ifcapable !rtree { +ifcapable !rtree||!builtin_test { finish_test return } diff --git a/ext/session/session1.test b/ext/session/session1.test index e97aa3c20d..09d35af7fc 100644 --- a/ext/session/session1.test +++ b/ext/session/session1.test @@ -569,5 +569,17 @@ do_changeset_test 10.1.2 S " " do_test 10.1.4 { S delete } {} +#------------------------------------------------------------------------- +# Test the effect of updating a column from 0.0 to 0.0. +# +reset_db +do_execsql_test 11.1 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b REAL); + INSERT INTO t1 VALUES(1, 0.0); +} +do_iterator_test 11.2 * { + UPDATE t1 SET b = 0.0; +} { +} finish_test diff --git a/ext/session/session4.test b/ext/session/session4.test index 8e179baf6d..120a230b57 100644 --- a/ext/session/session4.test +++ b/ext/session/session4.test @@ -30,7 +30,6 @@ do_test 1.0 { INSERT INTO x VALUES(19, 'domains', 'espouse', -94, 'throw'); } - sqlite3session S db main set changeset [changeset_from_sql { DELETE FROM x WHERE e = -66; UPDATE x SET a = 'parameterizable', b = 31.8 WHERE c = 35; diff --git a/ext/session/session_common.tcl b/ext/session/session_common.tcl index 38eebec0a0..06b05509b1 100644 --- a/ext/session/session_common.tcl +++ b/ext/session/session_common.tcl @@ -75,6 +75,9 @@ proc do_common_sql {sql} { } proc changeset_from_sql {sql {dbname main}} { + if {$dbname == "main"} { + return [sql_exec_changeset db $sql] + } set rc [catch { sqlite3session S db $dbname db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" { diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 70ca840dae..50793df0a0 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -374,9 +374,7 @@ static int sessionSerializeValue( if( aBuf ){ sessionVarintPut(&aBuf[1], n); - memcpy(&aBuf[nVarint + 1], eType==SQLITE_TEXT ? - sqlite3_value_text(pValue) : sqlite3_value_blob(pValue), n - ); + if( n ) memcpy(&aBuf[nVarint + 1], z, n); } nByte = 1 + nVarint + n; @@ -1792,7 +1790,7 @@ static void sessionAppendBlob( int nBlob, int *pRc ){ - if( 0==sessionBufferGrow(p, nBlob, pRc) ){ + if( nBlob>0 && 0==sessionBufferGrow(p, nBlob, pRc) ){ memcpy(&p->aBuf[p->nBuf], aBlob, nBlob); p->nBuf += nBlob; } @@ -1978,13 +1976,13 @@ static int sessionAppendUpdate( } default: { - int nByte; - int nHdr = 1 + sessionVarintGet(&pCsr[1], &nByte); + int n; + int nHdr = 1 + sessionVarintGet(&pCsr[1], &n); assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB ); - nAdvance = nHdr + nByte; + nAdvance = nHdr + n; if( eType==sqlite3_column_type(pStmt, i) - && nByte==sqlite3_column_bytes(pStmt, i) - && 0==memcmp(&pCsr[nHdr], sqlite3_column_blob(pStmt, i), nByte) + && n==sqlite3_column_bytes(pStmt, i) + && (n==0 || 0==memcmp(&pCsr[nHdr], sqlite3_column_blob(pStmt, i), n)) ){ break; } diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h index 23d9a33aef..c995d4ec28 100644 --- a/ext/session/sqlite3session.h +++ b/ext/session/sqlite3session.h @@ -157,7 +157,7 @@ int sqlite3session_attach( ** CAPI3REF: Set a table filter on a Session Object. ** ** The second argument (xFilter) is the "filter callback". For changes to rows -** in tables that are not attached to the Session oject, the filter is called +** in tables that are not attached to the Session object, the filter is called ** to determine whether changes to the table's rows should be tracked or not. ** If xFilter returns 0, changes is not tracked. Note that once a table is ** attached, xFilter will not be called again. @@ -423,7 +423,7 @@ int sqlite3session_isempty(sqlite3_session *pSession); ** [sqlite3changeset_invert()] functions, all changes within the changeset ** that apply to a single table are grouped together. This means that when ** an application iterates through a changeset using an iterator created by -** this function, all changes that relate to a single table are visted +** this function, all changes that relate to a single table are visited ** consecutively. There is no chance that the iterator will visit a change ** the applies to table X, then one for table Y, and then later on visit ** another change for table X. @@ -510,7 +510,7 @@ int sqlite3changeset_op( ** 0x01 if the corresponding column is part of the tables primary key, or ** 0x00 if it is not. ** -** If argumet pnCol is not NULL, then *pnCol is set to the number of columns +** If argument pnCol is not NULL, then *pnCol is set to the number of columns ** in the table. ** ** If this function is called when the iterator does not point to a valid @@ -727,12 +727,12 @@ int sqlite3changeset_concat( /* -** Changegroup handle. +** CAPI3REF: Changegroup Handle */ typedef struct sqlite3_changegroup sqlite3_changegroup; /* -** CAPI3REF: Combine two or more changesets into a single changeset. +** CAPI3REF: Create A New Changegroup Object ** ** An sqlite3_changegroup object is used to combine two or more changesets ** (or patchsets) into a single changeset (or patchset). A single changegroup @@ -769,6 +769,8 @@ typedef struct sqlite3_changegroup sqlite3_changegroup; int sqlite3changegroup_new(sqlite3_changegroup **pp); /* +** CAPI3REF: Add A Changeset To A Changegroup +** ** Add all changes within the changeset (or patchset) in buffer pData (size ** nData bytes) to the changegroup. ** @@ -783,7 +785,7 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp); ** apply to the same row as a change already present in the changegroup if ** the two rows have the same primary key. ** -** Changes to rows that that do not already appear in the changegroup are +** Changes to rows that do not already appear in the changegroup are ** simply copied into it. Or, if both the new changeset and the changegroup ** contain changes that apply to a single row, the final contents of the ** changegroup depends on the type of each change, as follows: @@ -844,6 +846,8 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp); int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); /* +** CAPI3REF: Obtain A Composite Changeset From A Changegroup +** ** Obtain a buffer containing a changeset (or patchset) representing the ** current contents of the changegroup. If the inputs to the changegroup ** were themselves changesets, the output is a changeset. Or, if the @@ -872,7 +876,7 @@ int sqlite3changegroup_output( ); /* -** Delete a changegroup object. +** CAPI3REF: Delete A Changegroup Object */ void sqlite3changegroup_delete(sqlite3_changegroup*); diff --git a/ext/session/test_session.c b/ext/session/test_session.c index 103a1c2d7a..411354cc02 100644 --- a/ext/session/test_session.c +++ b/ext/session/test_session.c @@ -29,6 +29,107 @@ struct TestStreamInput { int iData; /* Bytes of data already read by sessions */ }; +/* +** Extract an sqlite3* db handle from the object passed as the second +** argument. If successful, set *pDb to point to the db handle and return +** TCL_OK. Otherwise, return TCL_ERROR. +*/ +static int dbHandleFromObj(Tcl_Interp *interp, Tcl_Obj *pObj, sqlite3 **pDb){ + Tcl_CmdInfo info; + if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(pObj), &info) ){ + Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(pObj), 0); + return TCL_ERROR; + } + + *pDb = *(sqlite3 **)info.objClientData; + return TCL_OK; +} + +/************************************************************************* +** The following code is copied byte-for-byte from the sessions module +** documentation. It is used by some of the sessions modules tests to +** ensure that the example in the documentation does actually work. +*/ +/* +** Argument zSql points to a buffer containing an SQL script to execute +** against the database handle passed as the first argument. As well as +** executing the SQL script, this function collects a changeset recording +** all changes made to the "main" database file. Assuming no error occurs, +** output variables (*ppChangeset) and (*pnChangeset) are set to point +** to a buffer containing the changeset and the size of the changeset in +** bytes before returning SQLITE_OK. In this case it is the responsibility +** of the caller to eventually free the changeset blob by passing it to +** the sqlite3_free function. +** +** Or, if an error does occur, return an SQLite error code. The final +** value of (*pChangeset) and (*pnChangeset) are undefined in this case. +*/ +int sql_exec_changeset( + sqlite3 *db, /* Database handle */ + const char *zSql, /* SQL script to execute */ + int *pnChangeset, /* OUT: Size of changeset blob in bytes */ + void **ppChangeset /* OUT: Pointer to changeset blob */ +){ + sqlite3_session *pSession = 0; + int rc; + + /* Create a new session object */ + rc = sqlite3session_create(db, "main", &pSession); + + /* Configure the session object to record changes to all tables */ + if( rc==SQLITE_OK ) rc = sqlite3session_attach(pSession, NULL); + + /* Execute the SQL script */ + if( rc==SQLITE_OK ) rc = sqlite3_exec(db, zSql, 0, 0, 0); + + /* Collect the changeset */ + if( rc==SQLITE_OK ){ + rc = sqlite3session_changeset(pSession, pnChangeset, ppChangeset); + } + + /* Delete the session object */ + sqlite3session_delete(pSession); + + return rc; +} +/************************************************************************/ + +/* +** Tclcmd: sql_exec_changeset DB SQL +*/ +static int SQLITE_TCLAPI test_sql_exec_changeset( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + const char *zSql; + sqlite3 *db; + void *pChangeset; + int nChangeset; + int rc; + + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB SQL"); + return TCL_ERROR; + } + if( dbHandleFromObj(interp, objv[1], &db) ) return TCL_ERROR; + zSql = (const char*)Tcl_GetString(objv[2]); + + rc = sql_exec_changeset(db, zSql, &nChangeset, &pChangeset); + if( rc!=SQLITE_OK ){ + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "error in sql_exec_changeset()", 0); + return TCL_ERROR; + } + + Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pChangeset, nChangeset)); + sqlite3_free(pChangeset); + return TCL_OK; +} + + + #define SESSION_STREAM_TCL_VAR "sqlite3session_streams" /* @@ -919,23 +1020,26 @@ static int SQLITE_TCLAPI test_sqlite3session_foreach( } int TestSession_Init(Tcl_Interp *interp){ - Tcl_CreateObjCommand(interp, "sqlite3session", test_sqlite3session, 0, 0); - Tcl_CreateObjCommand( - interp, "sqlite3session_foreach", test_sqlite3session_foreach, 0, 0 - ); - Tcl_CreateObjCommand( - interp, "sqlite3changeset_invert", test_sqlite3changeset_invert, 0, 0 - ); - Tcl_CreateObjCommand( - interp, "sqlite3changeset_concat", test_sqlite3changeset_concat, 0, 0 - ); - Tcl_CreateObjCommand( - interp, "sqlite3changeset_apply", test_sqlite3changeset_apply, 0, 0 - ); - Tcl_CreateObjCommand( - interp, "sqlite3changeset_apply_replace_all", - test_sqlite3changeset_apply_replace_all, 0, 0 - ); + struct Cmd { + const char *zCmd; + Tcl_ObjCmdProc *xProc; + } aCmd[] = { + { "sqlite3session", test_sqlite3session }, + { "sqlite3session_foreach", test_sqlite3session_foreach }, + { "sqlite3changeset_invert", test_sqlite3changeset_invert }, + { "sqlite3changeset_concat", test_sqlite3changeset_concat }, + { "sqlite3changeset_apply", test_sqlite3changeset_apply }, + { "sqlite3changeset_apply_replace_all", + test_sqlite3changeset_apply_replace_all }, + { "sql_exec_changeset", test_sql_exec_changeset }, + }; + int i; + + for(i=0; izCmd, p->xProc, 0, 0); + } + return TCL_OK; } diff --git a/main.mk b/main.mk index 3322f209dc..acab56d6b0 100644 --- a/main.mk +++ b/main.mk @@ -293,6 +293,7 @@ TESTSRC = \ $(TOP)/src/test_blob.c \ $(TOP)/src/test_btree.c \ $(TOP)/src/test_config.c \ + $(TOP)/src/test_delete.c \ $(TOP)/src/test_demovfs.c \ $(TOP)/src/test_devsym.c \ $(TOP)/src/test_fs.c \ @@ -334,6 +335,7 @@ TESTSRC += \ $(TOP)/ext/misc/nextchar.c \ $(TOP)/ext/misc/percentile.c \ $(TOP)/ext/misc/regexp.c \ + $(TOP)/ext/misc/remember.c \ $(TOP)/ext/misc/series.c \ $(TOP)/ext/misc/spellfix.c \ $(TOP)/ext/misc/totype.c \ @@ -461,7 +463,8 @@ FUZZDATA = \ $(TOP)/test/fuzzdata1.db \ $(TOP)/test/fuzzdata2.db \ $(TOP)/test/fuzzdata3.db \ - $(TOP)/test/fuzzdata4.db + $(TOP)/test/fuzzdata4.db \ + $(TOP)/test/fuzzdata5.db # Standard options to testfixture # @@ -474,6 +477,7 @@ SHELL_OPT += -DSQLITE_ENABLE_EXPLAIN_COMMENTS SHELL_OPT += -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION FUZZERSHELL_OPT = -DSQLITE_ENABLE_JSON1 FUZZCHECK_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5 +DBFUZZ_OPT = # This is the default Makefile target. The objects listed here # are what get build when you type just "make" with no arguments. @@ -510,10 +514,20 @@ fuzzershell$(EXE): $(TOP)/tool/fuzzershell.c sqlite3.c sqlite3.h $(FUZZERSHELL_OPT) $(TOP)/tool/fuzzershell.c sqlite3.c \ $(TLIBS) $(THREADLIB) -fuzzcheck$(EXE): $(TOP)/test/fuzzcheck.c sqlite3.c sqlite3.h +dbfuzz$(EXE): $(TOP)/test/dbfuzz.c sqlite3.c sqlite3.h + $(TCCX) -o dbfuzz$(EXE) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \ + $(DBFUZZ_OPT) $(TOP)/test/dbfuzz.c sqlite3.c \ + $(TLIBS) $(THREADLIB) + +fuzzcheck$(EXE): $(TOP)/test/fuzzcheck.c sqlite3.c sqlite3.h $(TOP)/test/ossfuzz.c $(TCCX) -o fuzzcheck$(EXE) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \ + -DSQLITE_ENABLE_MEMSYS5 $(FUZZCHECK_OPT) -DSQLITE_OSS_FUZZ \ + $(TOP)/test/fuzzcheck.c $(TOP)/test/ossfuzz.c sqlite3.c $(TLIBS) $(THREADLIB) + +ossshell$(EXE): $(TOP)/test/ossfuzz.c $(TOP)/test/ossshell.c sqlite3.c sqlite3.h + $(TCCX) -o ossshell$(EXE) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \ -DSQLITE_ENABLE_MEMSYS5 $(FUZZCHECK_OPT) \ - $(TOP)/test/fuzzcheck.c sqlite3.c $(TLIBS) $(THREADLIB) + $(TOP)/test/ossfuzz.c $(TOP)/test/ossshell.c sqlite3.c $(TLIBS) $(THREADLIB) mptester$(EXE): sqlite3.c $(TOP)/mptest/mptest.c $(TCCX) -o $@ -I. $(TOP)/mptest/mptest.c sqlite3.c \ @@ -790,6 +804,11 @@ fastfuzztest: fuzzcheck$(EXE) $(FUZZDATA) valgrindfuzz: fuzzcheck$(EXE) $(FUZZDATA) valgrind ./fuzzcheck$(EXE) --cell-size-check --limit-mem 10M --timeout 600 $(FUZZDATA) +# The veryquick.test TCL tests. +# +tcltest: ./testfixture$(EXE) + ./testfixture$(EXE) $(TOP)/test/veryquick.test $(TESTOPTS) + # A very quick test using only testfixture and omitting all the slower # tests. Designed to run in under 3 minutes on a workstation. # @@ -798,9 +817,7 @@ quicktest: ./testfixture$(EXE) # The default test case. Runs most of the faster standard TCL tests, # and fuzz tests, and sqlite3_analyzer and sqldiff tests. -# -test: $(TESTPROGS) sourcetest fastfuzztest - ./testfixture$(EXE) $(TOP)/test/veryquick.test $(TESTOPTS) +test: fastfuzztest sourcetest $(TESTPROGS) tcltest # Run a test using valgrind. This can take a really long time # because valgrind is so much slower than a native machine. diff --git a/manifest b/manifest index b94ce9afa9..d78d211a93 100644 --- a/manifest +++ b/manifest @@ -1,17 +1,17 @@ -C Merge\sversion\s3.14\splus\ssome\ssubsequent\spatches\s(including\sthe\spage-cache\nperformance\spatch)\sfrom\strunk. -D 2016-08-11T19:12:25.266 -F Makefile.in cfd8fb987cd7a6af046daa87daa146d5aad0e088 +C Upgrade\sthis\sbranch\sto\s3.16\splus\sthe\svarious\sfixes\sthat\sappeared\safter\sits\nrelease. +D 2017-01-09T06:33:50.719 +F Makefile.in 41bd4cad981487345c4a84081074bcdb876e4b2e F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 -F Makefile.msc d66d0395c38571aab3804f8db0fa20707ae4609a +F Makefile.msc b8ca53350ae545e3562403d5da2a69cec79308da F README.md 8ecc12493ff9f820cdea6520a9016001cb2e59b7 -F VERSION 25e2e333adeff5965520bc8db999c658898c972d +F VERSION cddd8d88dc8202afa0ebc96da61fc4acbd1e96a5 F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 F art/sqlite370.jpg d512473dae7e378a67e28ff96a34da7cb331def2 F autoconf/INSTALL 83e4a25da9fd053c7b3665eaaaf7919707915903 F autoconf/Makefile.am 1a47d071e3d5435f8f7ebff7eb6703848bbd65d4 -F autoconf/Makefile.msc 7a909aa9b538138a21c0626e3ffc4b1f4519f50e +F autoconf/Makefile.msc b6d27f735911fd09a589d3c966936c47a5850c17 F autoconf/README.first 6c4f34fe115ff55d4e8dbfa3cecf04a0188292f7 F autoconf/README.txt 4f04b0819303aabaa35fff5f7b257fb0c1ef95f1 F autoconf/configure.ac cacf2616abf6e4a569bde2ef365c143caeec40bc @@ -30,10 +30,10 @@ F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 226d9a188c6196f3033ffc651cbc9dcee1a42977 F config.h.in 6376abec766e9a0785178b1823b5a587e9f1ccbc F config.sub 9ebe4c3b3dab6431ece34f16828b594fb420da55 -F configure 3f44893bc0e51c30653e02b0fd4dc2ab585e446a x -F configure.ac b5d3df43161374f8dffd2e5f4b88fbb51685b975 +F configure fcfa56d5d66668c9263dc007724f62acff06dc83 x +F configure.ac 605173e829ab64514ed89f9b53d0da1739d7b0a0 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad -F doc/lemon.html e2118945e5f07ed146b45c9cd2b2dd6eabb8ebf2 +F doc/lemon.html b5a3c07d33ecb8e019ce8f7660fe2dbbad9d7977 F doc/pager-invariants.txt 27fed9a70ddad2088750c4a2b493b63853da2710 F doc/vfs-shm.txt e101f27ea02a8387ce46a05be2b1a902a021d37a F ext/README.txt 913a7bd3f4837ab14d7e063304181787658b14e1 @@ -99,12 +99,12 @@ F ext/fts3/unicode/parseunicode.tcl da577d1384810fb4e2b209bf3313074353193e95 F ext/fts5/extract_api_docs.tcl a36e54ec777172ddd3f9a88daf593b00848368e0 F ext/fts5/fts5.h 62f3e33ceeb9a428db139f9c012186b371da1cc7 F ext/fts5/fts5Int.h b2eda36e0f224365c8e23dc8f559311834f1c13f -F ext/fts5/fts5_aux.c daa57fb45216491814520bbb587e97bf81ced458 +F ext/fts5/fts5_aux.c 67acf8d51723cf28ffc3828210ba662df4b8d267 F ext/fts5/fts5_buffer.c 4c1502d4c956cd092c89ce4480867f9d8bf325cd F ext/fts5/fts5_config.c 5af9c360e99669d29f06492c370892394aba0857 -F ext/fts5/fts5_expr.c df0004b5bffcbe34c329f2992669c6352443f415 +F ext/fts5/fts5_expr.c dc2cee9f56b1818b85df59304b8104a5dfb8ab60 F ext/fts5/fts5_hash.c 880998e596b60f078348d48732ca4ad9a90caad2 -F ext/fts5/fts5_index.c 05386732609221d066d204b22c4a5275a0225ed4 +F ext/fts5/fts5_index.c f67032a9a529ba52a545e6e3ab970764199c05d4 F ext/fts5/fts5_main.c f85281445dcf8be32d18841c93a6f90fe27dbfe2 F ext/fts5/fts5_storage.c de0ed8a06738bde433afe11e92295ceaffbc4e58 F ext/fts5/fts5_tcl.c 4a901f00c8553740dba63511603f5527d741c26a @@ -113,7 +113,7 @@ F ext/fts5/fts5_test_tok.c db08af63673c3a7d39f053b36fd6e065017706be F ext/fts5/fts5_tokenize.c 2ce7b44183538ec46b7907726262ee43ffdd39a8 F ext/fts5/fts5_unicode2.c b450b209b157d598f7b9df9f837afb75a14c24bf F ext/fts5/fts5_varint.c a5aceacda04dafcbae725413d7a16818ecd65738 -F ext/fts5/fts5_vocab.c dba72ca393d71c2588548b51380387f6b44c77a8 +F ext/fts5/fts5_vocab.c e44fefa7f0c1db252998af071daf06a7147e17e7 F ext/fts5/fts5parse.y e51b375403421b8b37428a89b095d00597129aae F ext/fts5/mkportersteps.tcl 5acf962d2e0074f701620bb5308155fa1e4a63ba F ext/fts5/test/fts5_common.tcl b01c584144b5064f30e6c648145a2dd6bc440841 @@ -122,7 +122,7 @@ F ext/fts5/test/fts5ab.test 30325a89453280160106be411bba3acf138e6d1b F ext/fts5/test/fts5ac.test 55cad4275a1f5acabfe14d8442a8046b47e49e5f F ext/fts5/test/fts5ad.test 36995f0586f30f5602074e012b9224c71ec5171c F ext/fts5/test/fts5ae.test 612dcb51f4069226791ff14c17dbfb3138c56f20 -F ext/fts5/test/fts5af.test be858a96b1f5de66ba6d64f0021bd8b2408e126c +F ext/fts5/test/fts5af.test c92825778ed2adc80014832762c056bbe968ef88 F ext/fts5/test/fts5ag.test 27180de76c03036be75ee80b93d8c5f540014071 F ext/fts5/test/fts5ah.test dfb7897711dbcda1dacb038aec310daca139fcf5 F ext/fts5/test/fts5ai.test 3909d0b949b2afcaae4d5795cd79153da75381df @@ -141,14 +141,14 @@ F ext/fts5/test/fts5config.test 7788b9c058074d640dfcdd81d97b6a9480000368 F ext/fts5/test/fts5conflict.test 26f4e46c4d31e16221794832a990dc4e30e18de5 F ext/fts5/test/fts5content.test 9a952c95518a14182dc3b59e3c8fa71cda82a4e1 F ext/fts5/test/fts5corrupt.test c2ad090192708150d50d961278df10ae7a4b8b62 -F ext/fts5/test/fts5corrupt2.test 26c0a39dd9ff73207e6229f83b50b21d37c7658c +F ext/fts5/test/fts5corrupt2.test 128eb6e2d26b09f4da339e581f424b3321e0fdaa F ext/fts5/test/fts5corrupt3.test f77f65e386231daf62902466b40ff998b2c8ce4f F ext/fts5/test/fts5detail.test ef5c690535a797413acaf5ad9b8ab5d49972df69 F ext/fts5/test/fts5determin.test 10648edb75ef1e196b10978fd21a9be0c31e09c3 F ext/fts5/test/fts5dlidx.test 007e9390c94638760797dbec2990c97c3fa08dfe F ext/fts5/test/fts5doclist.test 8edb5b57e5f144030ed74ec00ef6fa4294fed79b F ext/fts5/test/fts5ea.test b01e3a18cdfabbff8104a96a5242a06a68a998a0 -F ext/fts5/test/fts5eb.test c516ae0c934be6fd29ec95ea8b5f11f461311535 +F ext/fts5/test/fts5eb.test 14597b5ecc9b2b7039ac5e0b0fca0311a7d22005 F ext/fts5/test/fts5fault1.test e09040d3e17b8c0837101e8c79c8a874c4376fb7 F ext/fts5/test/fts5fault2.test d8c6c7f916ccbdfc10b2c69530e9dd3bc8313232 F ext/fts5/test/fts5fault3.test d6e9577d4312e331a913c72931bf131704efc8f3 @@ -159,7 +159,7 @@ F ext/fts5/test/fts5fault7.test cb14ea3c1f42394f06f2284abc58eecee6ff8080 F ext/fts5/test/fts5fault8.test 6785af34bd1760de74e2824ea9c161965af78f85 F ext/fts5/test/fts5fault9.test e10e395428a9ea0596ebe752ff7123d16ab78e08 F ext/fts5/test/fts5faultA.test fa5d59c0ff62b7125cd14eee38ded1c46e15a7ea -F ext/fts5/test/fts5faultB.test 92ae906284062bf081b6c854afa54dcb1aa9ef88 +F ext/fts5/test/fts5faultB.test 7f3bba790fa172073ac314f9b8ed197390b61eca F ext/fts5/test/fts5full.test 6f6143af0c6700501d9fd597189dfab1555bb741 F ext/fts5/test/fts5fuzz1.test bece4695fc169b61ab236ada7931c6e4942cbef9 F ext/fts5/test/fts5hash.test 06f9309ccb4d5050a131594e9e47d0b21456837d @@ -175,48 +175,50 @@ F ext/fts5/test/fts5phrase.test f6d1d464da5beb25dc56277aa4f1d6102f0d9a2f F ext/fts5/test/fts5plan.test 6a55ecbac9890765b0e16f8c421c7e0888cfe436 F ext/fts5/test/fts5porter.test 7cdc07bef301d70eebbfa75dcaf45c3680e1d0e1 F ext/fts5/test/fts5porter2.test 2e65633d58a1c525d5af0f6c01e5a59155bb3487 -F ext/fts5/test/fts5prefix.test efd42e00bb8e8a36383f25c838185508681c093f +F ext/fts5/test/fts5prefix.test 6ef7e875738412907b17687d25db39a25cbdaba4 F ext/fts5/test/fts5query.test f5ec25f5f2fbb70033424113cdffc101b1985a40 F ext/fts5/test/fts5rank.test 2bdc0c5f22ccc1f9dbe9f4d0b82a491dce6f8a32 F ext/fts5/test/fts5rebuild.test 03935f617ace91ed23a6099c7c74d905227ff29b F ext/fts5/test/fts5restart.test c17728fdea26e7d0f617d22ad5b4b2862b994c17 F ext/fts5/test/fts5rowid.test 16908a99d6efc9ba21081b4f2b86b3fc699839a6 F ext/fts5/test/fts5simple.test 5da9b15ed534eb0be9f279d8a2bb2e24d30e4e38 -F ext/fts5/test/fts5simple2.test 98377ae1ff7749a42c21fe1a139c1ed312522c46 -F ext/fts5/test/fts5simple3.test 8e71733b3d1b0e695011d02c68ebc5ca40b6124e +F ext/fts5/test/fts5simple2.test 00839031878f52391562594fdab0503e424ee071 +F ext/fts5/test/fts5simple3.test 25faa8cb8ad470c6f01f670bcc1317c19a89f091 F ext/fts5/test/fts5synonym.test 6475d189c2e20d60795808f83e36bf9318708d48 F ext/fts5/test/fts5synonym2.test aa4c43bd3b691ff80f658cb064f5ab40690e834e F ext/fts5/test/fts5tok1.test beb894c6f3468f10a574302f69ebe4436b0287c7 F ext/fts5/test/fts5tok2.test dcacb32d4a2a3f0dd3215d4a3987f78ae4be21a2 F ext/fts5/test/fts5tokenizer.test ea4df698b35cc427ebf2ba22829d0e28386d8c89 F ext/fts5/test/fts5unicode.test fbef8d8a3b4b88470536cc57604a82ca52e51841 -F ext/fts5/test/fts5unicode2.test c1dd890ba32b7609adba78e420faa847abe43b59 +F ext/fts5/test/fts5unicode2.test 529ac7e8648c943bc87bfed1e427128a2f3f9e33 F ext/fts5/test/fts5unicode3.test 35c3d02aa7acf7d43d8de3bfe32c15ba96e8928e F ext/fts5/test/fts5unindexed.test e9539d5b78c677315e7ed8ea911d4fd25437c680 F ext/fts5/test/fts5update.test 57c7012a7919889048947addae10e0613df45529 F ext/fts5/test/fts5version.test 978f59541d8cef7e8591f8be2115ec5ccb863e2e -F ext/fts5/test/fts5vocab.test 480d780aa6b699816c5066225fbd86f3a0239477 +F ext/fts5/test/fts5vocab.test 026799f8d24befc452cbddda79822f5ae8d5f4eb F ext/fts5/tool/fts5speed.tcl b0056f91a55b2d1a3684ec05729de92b042e2f85 F ext/fts5/tool/fts5txt2db.tcl 526a9979c963f1c54fd50976a05a502e533a4c59 F ext/fts5/tool/loadfts5.tcl 95b03429ee6b138645703c6ca192c3ac96eaf093 F ext/fts5/tool/mkfts5c.tcl d1c2a9ab8e0ec690a52316f33dd9b1d379942f45 F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt d9fbbad0c2f647c3fdf715fc9fd64af53aedfc43 -F ext/icu/icu.c 43df9d8ef2fae7a325100ebd713ab089dc829dd7 +F ext/icu/icu.c 03ff6f90f3004a7e5a86205b581b2b7035ebf6e1 F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37 F ext/misc/amatch.c 211108e201105e4bb0c076527b8cfd34330fc234 F ext/misc/carray.c 40c27641010a4dc67e3690bdb7c9d36ca58b3c2d F ext/misc/closure.c 0d2a038df8fbae7f19de42e7c7d71f2e4dc88704 F ext/misc/compress.c 122faa92d25033d6c3f07c39231de074ab3d2e83 -F ext/misc/csv.c 816a3715356e4210dae2d242057745e937050896 +F ext/misc/csv.c 531a46cbad789fca0aa9db69a0e6c8ac9e68767d F ext/misc/eval.c f971962e92ebb8b0a4e6b62949463ee454d88fa2 F ext/misc/fileio.c d4171c815d6543a9edef8308aab2951413cd8d0f F ext/misc/fuzzer.c 7c64b8197bb77b7d64eff7cac7848870235d4c25 F ext/misc/ieee754.c f190d0cc5182529acb15babd177781be1ac1718c -F ext/misc/json1.c 9799e4252b305edcbe659329eec3ca80ed85f968 +F ext/misc/json1.c e0797ef1bfa637429ceab9140ecba620809ee02a +F ext/misc/memvfs.c e5225bc22e79dde6b28380f3a068ddf600683a33 F ext/misc/nextchar.c 35c8b8baacb96d92abbb34a83a997b797075b342 F ext/misc/percentile.c 92699c8cd7d517ff610e6037e56506f8904dae2e F ext/misc/regexp.c a68d25c659bd2d893cd1215667bbf75ecb9dc7d4 +F ext/misc/remember.c 8440f8d0b452c5cdefb62b57135ccd1267aa729d F ext/misc/rot13.c 1ac6f95f99b575907b9b09c81a349114cf9be45a F ext/misc/scrub.c 1c5bfb8b0cd18b602fcb55755e84abf0023ac2fb F ext/misc/series.c e11e534ada797d5b816d7e7a93c022306563ca35 @@ -245,7 +247,8 @@ F ext/rbu/rbuB.test c25bc325b8072a766e56bb76c001866b405925c2 F ext/rbu/rbuC.test efe47db508a0269b683cb2a1913a425ffd39a831 F ext/rbu/rbu_common.tcl a38e8e2d4a50fd6aaf151633714c1b1d2fae3ead F ext/rbu/rbucrash.test 8d2ed5d4b05fef6c00c2a6b5f7ead71fa172a695 -F ext/rbu/rbudiff.test b3c7675810b81de98a930a87fcd40d9ae545619d +F ext/rbu/rbudiff.test 3e605cf624d00d04d0fb1316a3acec4fbe3b3ac5 +F ext/rbu/rbudor.test 99b05cc0df613e962c2c8085cfb05686a09cf315 F ext/rbu/rbufault.test cc0be8d5d392d98b0c2d6a51be377ea989250a89 F ext/rbu/rbufault2.test 9a7f19edd6ea35c4c9f807d8a3db0a03a5670c06 F ext/rbu/rbufault3.test 54a399888ac4af44c68f9f58afbed23149428bca @@ -255,10 +258,10 @@ F ext/rbu/rbusave.test 0f43b6686084f426ddd040b878426452fd2c2f48 F ext/rbu/rbuvacuum.test 4a977447c15c2581ab668781d9ef4294382530e0 F ext/rbu/rbuvacuum2.test 2569205b74ff40fbf3bda2fce33a58eb40eebdcc F ext/rbu/sqlite3rbu.c e074c38798b90591f7f0cf0032d62f152ce5a95e -F ext/rbu/sqlite3rbu.h 1d91c5b7d066645bd1ff8e4b85c2b9b5dd29fb05 +F ext/rbu/sqlite3rbu.h 6fb6294c34a9ca93b5894a33bca530c6f08decba F ext/rbu/test_rbu.c 5aa22616afac6f71ebd3d9bc9bf1006cfabcca88 F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 -F ext/rtree/rtree.c d26a815b0df1c412a6881dae8d7fd3c9c08cce68 +F ext/rtree/rtree.c 078fc27417b48aaebe988621bf96ef0e1645b201 F ext/rtree/rtree.h 834dbcb82dc85b2481cde6a07cdadfddc99e9b9e F ext/rtree/rtree1.test 42dadfc7b44a436cd74a1bebc0b9b689e4eaf7ec F ext/rtree/rtree2.test acbb3a4ce0f4fbc2c304d2b4b784cfa161856bba @@ -272,7 +275,7 @@ F ext/rtree/rtree9.test b5eb13849545dfd271a54ff16784cb00d8792aea F ext/rtree/rtreeA.test ace05e729a36e342d40cf94e9efc7b4723d9dcdf F ext/rtree/rtreeB.test c85f9ce78766c4e68b8b89fbf2979ee9cfa82b4e F ext/rtree/rtreeC.test c0a9c67f2efa98b6fae12acb8a28348d231a481d -F ext/rtree/rtreeD.test 636630357638f5983701550b37f0f5867130d2ca +F ext/rtree/rtreeD.test bdfaaf26df8b4eea7364039aca9150bc1e1f8825 F ext/rtree/rtreeE.test 45a147a64a76306172819562309681d8e90f94bb F ext/rtree/rtreeF.test 66deb9fd1611c7ca2e374adba63debdc2dbb12b4 F ext/rtree/rtreeG.test 3b185719630795f38594f64cd7d1de86a33f91f1 @@ -282,10 +285,10 @@ F ext/rtree/sqlite3rtree.h 9c5777af3d2921c7b4ae4954e8e5697502289d28 F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024 F ext/session/changeset.c 4ccbaa4531944c24584bf6a61ba3a39c62b6267a -F ext/session/session1.test 98f384736e2bc21ccf5ed81bdadcff4ad863393b +F ext/session/session1.test e5125b216d1e8c91e0984b361b0b68529e7c5dfb F ext/session/session2.test 284de45abae4cc1082bc52012ee81521d5ac58e0 F ext/session/session3.test a7a9ce59b8d1e49e2cc23d81421ac485be0eea01 -F ext/session/session4.test a6ed685da7a5293c5d6f99855bcf41dbc352ca84 +F ext/session/session4.test 457b02bdc349eb01151e54de014df77abd3c08c8 F ext/session/session5.test 716bc6fafd625ce60dfa62ae128971628c1a1169 F ext/session/session6.test 443789bc2fca12e4f7075cf692c60b8a2bea1a26 F ext/session/session8.test 8e194b3f655d861ca36de5d4de53f702751bab3b @@ -297,19 +300,19 @@ F ext/session/sessionD.test d4744c78334162851d2a2f285c7e603e31b49aa2 F ext/session/sessionE.test e60a238c47f0feb3bb707e7f35e22be09c7e8f26 F ext/session/sessionF.test c2f178d4dfd723a5fd94a730ea2ccb44c669e3ce F ext/session/sessionG.test 01ef705096a9d3984eebdcca79807a211dee1b60 -F ext/session/session_common.tcl a1293167d14774b5e728836720497f40fe4ea596 +F ext/session/session_common.tcl 9b696a341cf1d3744823715ed92bb19749b6c3d4 F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7 F ext/session/sessionfault2.test 04aa0bc9aa70ea43d8de82c4f648db4de1e990b0 -F ext/session/sqlite3session.c 37485891b4add26cf61495df193c419f36556a32 -F ext/session/sqlite3session.h 69bf73cfd71e58f2ae5d2aa935b2c1a541aee555 -F ext/session/test_session.c 2caed9a659586428c63ca46e4900347b374487d4 +F ext/session/sqlite3session.c c61a43396368ec00dc127f7bc647e9bd6a4ee5fb +F ext/session/sqlite3session.h 9345166bd8f80562145586cf817f707de5ecada2 +F ext/session/test_session.c eb0bd6c1ea791c1d66ee4ef94c16500dad936386 F ext/userauth/sqlite3userauth.h 19cb6f0e31316d0ee4afdfb7a85ef9da3333a220 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 F ext/userauth/userauth.c 5fa3bdb492f481bbc1709fc83c91ebd13460c69e F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60 -F main.mk 67e7995f96dcb48ec0ece72901ba2cd2a2b50437 +F main.mk a85772b3c9f3ba0a0f85e9f1c4e1151c5779c860 F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 @@ -321,36 +324,37 @@ F spec.template 86a4a43b99ebb3e75e6b9a735d5fd293a24e90ca F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786 F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a -F src/alter.c cc28ab933ae615b22add0d609794ffb6596b42ea -F src/analyze.c 37fedc80ac966ce1745811746e68e4d8fa64c7fe -F src/attach.c 771153bd1f4ab0b97a44a13dde2c7e5e1efeba22 -F src/auth.c 5c8e0f37f785f935f589496801edd19840485853 -F src/backup.c 17cd25a36d49330df2bacd2cadf2a61f3b525976 -F src/bitvec.c 3ee4c8b2c94ed3a7377256e18199e6ff5cf33f63 -F src/btmutex.c bc87dd3b062cc26edfe79918de2200ccb8d41e73 -F src/btree.c 8872dcc869d648ccc0b95834fc7e81cf49c456f7 -F src/btree.h c23154635a447f37308865ce09b5d1dd2e9deef0 -F src/btreeInt.h ca40607c2c9d4b4b7ded98e3d707b15c2f17f1bf -F src/build.c e3b41d597d4f6f24cee6bde1e64c648b1c3983ca +F src/alter.c 3b23977620ce9662ac54443f65b87ba996e36121 +F src/analyze.c 3c4a63ff7a55faefecf6eb1589932fdbc06b2415 +F src/attach.c 8c476f8bd5d2afe11d925f890d30e527e5b0ce43 +F src/auth.c 930b376a9c56998557367e6f7f8aaeac82a2a792 +F src/backup.c faf17e60b43233c214aae6a8179d24503a61e83b +F src/bitvec.c 17ea48eff8ba979f1f5b04cc484c7bb2be632f33 +F src/btmutex.c 0e9ce2d56159b89b9bc8e197e023ee11e39ff8ca +F src/btree.c 5e5bf5079aae45b91e71eb087f3eddd28deafafe +F src/btree.h 9306cfe42eed20cf2d0e407172704f5193af26cc +F src/btreeInt.h bade42398d4dcadd2263c6d17886e812e2471e87 +F src/build.c efef69bbc7d0729a9b1e6d71f1047d0c5e1cdb5b F src/callback.c 2e76147783386374bf01b227f752c81ec872d730 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e -F src/ctime.c e77f3dc297b4b65c96da78b4ae4272fdfae863d7 -F src/date.c 95c9a8d00767e7221a8e9a31f4e913fc8029bf6b -F src/dbstat.c 4f6f7f52b49beb9636ffbd517cfe44a402ba4ad0 -F src/delete.c 4aba4214a377ce8ddde2d2e609777bcc8235200f -F src/expr.c 9c5eca8602f6c496e8d4eefefe2aae3d831dd510 -F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb -F src/fkey.c bc4145347595b7770f9a598cff1c848302cf5413 -F src/func.c 29cc9acb170ec1387b9f63eb52cd85f8de96c771 -F src/global.c c45ea22aff29334f6a9ec549235ac3357c970015 -F src/hash.c 55b5fb474100cee0b901edaf203e26c970940f36 +F src/ctime.c 9f2296a4e5d26ebf0e0d95a0af4628f1ea694e7a +F src/date.c dc3f1391d9297f8c748132813aaffcb117090d6e +F src/dbstat.c 19ee7a4e89979d4df8e44cfac7a8f905ec89b77d +F src/delete.c c8bc10d145c9666a34ae906250326fdaa8d58fa5 +F src/expr.c f06f41e5e5daca10fb090e70a2502dcc0dbc992b +F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 +F src/fkey.c 2e9aabe1aee76273aff8a84ee92c464e095400ae +F src/func.c c67273e1ec08abbdcc14c189892a3ff6eeece86b +F src/global.c 4a34512d82fc5aa13c802db06bcfff5e1d3de955 +F src/hash.c 63d0ee752a3b92d4695b2b1f5259c4621b2cfebd F src/hash.h ab34c5c54a9e9de2e790b24349ba5aab3dbb4fd4 F src/hwtime.h 747c1bbe9df21a92e9c50f3bbec1de841dc5e5da -F src/insert.c 8f4e9fcbd8e95e85f15647ba8b413b18d556ec2b +F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 +F src/insert.c 7af46a3be2656f5e13791464625d93d6b07b8612 F src/legacy.c 75d3023be8f0d2b99d60f905090341a03358c58e -F src/loadext.c dd7a2b77902cc66c22555aef02e1a682554b7aec -F src/main.c 16c1b2114eae8804caf3a8de8cb47bf2c6d83ad3 -F src/malloc.c 1443d1ad95d67c21d77af7ae3f44678252f0efec +F src/loadext.c 5d6642d141c07d366e43d359e94ec9de47add41d +F src/main.c e207b81542d13b9f13d61e78ca441f9781f055b0 +F src/malloc.c f3fad34cd570022abca558c573f1761fb09a8212 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c 6919bcf12f221868ea066eec27e579fed95ce98b F src/mem2.c f1940d9e91948dd6a908fbb9ce3835c36b5d83c3 @@ -368,35 +372,35 @@ F src/os.c add02933b1dce7a39a005b00a2f5364b763e9a24 F src/os.h 8e976e59eb4ca1c0fca6d35ee803e38951cb0343 F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85 F src/os_setup.h 0dbaea40a7d36bf311613d31342e0b99e2536586 -F src/os_unix.c be9ca0f901a2b6c1bc93dc338f4863675180c189 -F src/os_win.c 520f23475f1de530c435d30b67b7b15fe90874b0 +F src/os_unix.c 30e2c43e4955db990e5b5a81e901f8aa74cc8820 +F src/os_win.c cf90abd4e50d9f56d2c20ce8e005aff55d7bd8e9 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a -F src/pager.c bd4867fbbe328bbb5f0669d60e3c16ac03dd776f -F src/pager.h 3ae6549ec75fe9f36b426a274788a31b1b2b2203 -F src/parse.y e87265f23548aed30b331fb86b4712e1c13c4673 -F src/pcache.c 5583c8ade4b05075a60ba953ef471d1c1a9c05df +F src/pager.c e5b8c9a975e0c208bc8886bf83b8780b6e578ac5 +F src/pager.h 0105583b49a9126b9d137f98ec57caefc64f51d4 +F src/parse.y bb387a078f2f7f78e5362479bfef4a05b9fd40b8 +F src/pcache.c 51070ec9b8251bbf9c6ea3d35fd96a458752929e F src/pcache.h 2cedcd8407eb23017d92790b112186886e179490 -F src/pcache1.c 4bb7a6a5300c67d0b033d25adb509c120c03e812 -F src/pragma.c c8b499756658cb8b82cfdbb5845c22cf11f297aa -F src/pragma.h 64c78a648751b9f4f297276c4eb7507b14b4628c -F src/prepare.c 22df6171aec1d86904ed2ad30c2348a5748aa04e -F src/printf.c a5f0ca08ddede803c241266abb46356ec748ded1 -F src/random.c ba2679f80ec82c4190062d756f22d0c358180696 -F src/resolve.c 0392c6686586b1d4dac9a4106959f03ddd70e9aa +F src/pcache1.c e3967219b2a92b9edcb9324a4ba75009090d3953 +F src/pragma.c 0e7a7c6f1c6fd8ff50c0fff65b8bb80174bc49c5 +F src/pragma.h 61aa5389118594bebb28120a6720401aee34ce1a +F src/prepare.c b1140c3d0cf59bc85ace00ce363153041b424b7a +F src/printf.c ff10a9b9902cd2afe5f655f3013c6307d969b1fd +F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384 +F src/resolve.c bb070cf5f23611c44ab7e4788803684e385fc3fb F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac -F src/select.c f3c6e9065fb34f6a23af27ec7f1f717ffbfc2ee4 -F src/shell.c 79dda477be6c96eba6e952a934957ad36f87acc7 -F src/sqlite.h.in 0f7580280d1b009b507d8beec1ff0f197ba0cc99 +F src/select.c 4437d9d5d56b6ffdedabf394c7fe3a07ff521ce9 +F src/shell.c 6095531aa900decdaa765e0f3993fba7153c92c1 +F src/sqlite.h.in 29bda4bee01248a5650567d7a22fac39bad1b542 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 8648034aa702469afb553231677306cc6492a1ae -F src/sqliteInt.h cf506e205f4b15618f5f0787e45d40fafc8ff42c +F src/sqliteInt.h 0081f4d95b49a1accc29e5c96cf35296d0f68098 F src/sqliteLimit.h c0373387c287c8d0932510b5547ecde31b5da247 F src/status.c a9e66593dfb28a9e746cba7153f84d49c1ddc4b1 F src/table.c 5226df15ab9179b9ed558d89575ea0ce37b03fc9 -F src/tclsqlite.c bdae822f21e229b6daced15938b6343ce44ef454 -F src/test1.c 0a0909cf7962d2359db329c08d15b90b4b6e724f -F src/test2.c b7174313e993754303a8b33c43df7c44b46857ab -F src/test3.c 1339a40be39650ae83894b6578f971dc7f96ea8a +F src/tclsqlite.c 205c66b9b81d97978a155caa3ef5be9c4de2b174 +F src/test1.c 8a98191a1da8e100f77cdb5cc716df67d405028d +F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5 +F src/test3.c d03f5b5da9a2410b7a91c64b0d3306ed28ab6fee F src/test4.c 18ec393bb4d0ad1de729f0b94da7267270f3d8e6 F src/test5.c 328aae2c010c57a9829d255dc099d6899311672d F src/test6.c 55aa2775c154415dcf4ed7cd1e19a193122b3a02 @@ -407,13 +411,14 @@ F src/test_async.c 195ab49da082053fdb0f949c114b806a49ca770a F src/test_autoext.c 915d245e736652a219a907909bb6710f0d587871 F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0 F src/test_bestindex.c d23f80d334c59662af69191854c76b8d3d0c8c96 -F src/test_blob.c a0f7ad49a0c9d4b72f693fe2a71c58d7e507174d +F src/test_blob.c 6a4c7920d1d9c6cc0f7aa50c89c4f80016aeda83 F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274 -F src/test_config.c 0e3fbf51a9e56a50b80d52bba032e295fae76848 +F src/test_config.c f3b4459a0c65933783030fb4a05073571f8fa897 +F src/test_delete.c 8499d4d323f2ec8e28301deb3d6ddd8eef8b8139 F src/test_demovfs.c a0c3bdd45ed044115c2c9f7779e56eafff18741e F src/test_devsym.c 4e58dec2602d8e139ca08659f62a62450587cb58 -F src/test_fs.c ac62ce7d5c0c23aa6932891cad5746945564c91c -F src/test_func.c a2b4993da973b0ea60dd5d51a7066238ede8c329 +F src/test_fs.c e16cbe68d3b107e00a907c20a9a02629870eb69b +F src/test_func.c a4fdab3363b436c1b12660e9362ce3f3782b7b5e F src/test_hexio.c 1d4469ca61ab202a1fcec6543f584d2407205e8d F src/test_init.c 4413c211a94b62157ca4c145b3f27c497f03c664 F src/test_intarray.c 988fc61cb0ff539f4172c0d95f15287c92516f64 @@ -421,7 +426,7 @@ F src/test_intarray.h f3b7672f5d1056eac563c0d6ea8480a660b1475c F src/test_journal.c d3b83f2bcb7792c709e57abddc456a2b1818643a F src/test_loadext.c 337056bae59f80b9eb00ba82088b39d0f4fe6dfd F src/test_malloc.c c05f6c40bd6c8bfe5f1718212f81fd5687f91766 -F src/test_multiplex.c 7c5b218d855cd0505a43185d55283b3fc257c8da +F src/test_multiplex.c e054459f7633f3ff8ce1245da724f9a8be189e4e F src/test_multiplex.h 5436d03f2d0501d04f3ed50a75819e190495b635 F src/test_mutex.c 7f4337ba23ee6b1d2ec81c189653608cb069926a F src/test_onefile.c 416f87a28d6d673352d33fc4b1c7d39db878e50f @@ -432,52 +437,52 @@ F src/test_quota.h 2a8ad1952d1d2ca9af0ce0465e56e6c023b5e15d F src/test_rtree.c 671f3fae50ff116ef2e32a3bf1fe21b5615b4b7b F src/test_schema.c f575932cb6274d12147a77e13ea4b49d52408513 F src/test_server.c a2615049954cbb9cfb4a62e18e2f0616e4dc38fe -F src/test_sqllog.c 0d138a8180a312bf996b37fa66da5c5799d4d57b +F src/test_sqllog.c 11e6ce7575f489155c604ac4b439f2ac1d3d5aef F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e F src/test_syscall.c 1073306ba2e9bfc886771871a13d3de281ed3939 F src/test_tclvar.c df9fe1213c2634687a9ca0b0bec0d2119d359ae3 F src/test_thread.c 911d15fb14e19c0c542bdc8aabf981c2f10a4858 F src/test_vfs.c f0186261a24de2671d080bcd8050732f0cb64f6e F src/test_vfstrace.c bab9594adc976cbe696ff3970728830b4c5ed698 -F src/test_windirent.c 8f5fada630348558d5745b334702f301da1ffc61 -F src/test_windirent.h b12055cab6227f7be10f5c19296f67c60cc5e2a5 +F src/test_windirent.c 600398db0198ca1c77ca183831bf456746b6f5c4 +F src/test_windirent.h 7edc57e2faa727026dbd5d010dd0e2e665d5aa01 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c -F src/tokenize.c 78c8085bc7af1922aa687f0f4bbd716821330de5 -F src/treeview.c c56d6ddbed564efda746236b35bcbb8238daac4b -F src/trigger.c e14840ee0c3e549e758ec9bf3e4146e166002280 -F src/update.c 4f05ea8cddfa367d045e03589756c02199e8f9bd +F src/tokenize.c 5c2f516876fc27fbd7753913f032f49eb89e83b5 +F src/treeview.c 4e44ade3bfe59d82005039f72e09333ce2b4162c +F src/trigger.c c9f0810043b265724fdb1bdd466894f984dfc182 +F src/update.c 1da7c462110bffed442a42884cb0d528c1db46d8 F src/utf.c 699001c79f28e48e9bcdf8a463da029ea660540c -F src/util.c 810ec3f22e2d1b62e66c30fe3621ebdedd23584d -F src/vacuum.c ca88e6d3f5270533dea68e21008493b10023c214 -F src/vdbe.c 75556e9cd0fefd200515133af759205e48e9fb33 -F src/vdbe.h 67bc551f7faf04c33493892e4b378aada823ed10 -F src/vdbeInt.h c59381049af5c7751a83456c39b80d1a6fde1f9d -F src/vdbeapi.c c3f6715a99995c11748ecad91d25e93fd9fc390b -F src/vdbeaux.c e46568111862599cf475e7cdcbc1322551064906 -F src/vdbeblob.c 83d2d266383157b02e2b809350bb197e89d7895b -F src/vdbemem.c 1ecaa5ee0caff07255f25d04e8dc88befb6f88d1 -F src/vdbesort.c 91fda3909326860382b0ca8aa251e609c6a9d62c +F src/util.c a88b0466fddf445ce752226d4698ca3faada620a +F src/vacuum.c ebcf45799c7a82729a496ef5b65c5d3b2715ef8e +F src/vdbe.c 0254dc5bfb4d00b5ccc359332eeef26eb66dc5ee +F src/vdbe.h b0866e4191f096f1c987a84b042c3599bdf5423b +F src/vdbeInt.h 281cb70332dc8b593b8c7afe776f3a2ba7d4255e +F src/vdbeapi.c d6ebaa465f070eb1af8ba4e7b34583ece87bdd24 +F src/vdbeaux.c d1e423b8a659705cbad5a502a219fba22f306048 +F src/vdbeblob.c f4f98ea672b242f807c08c92c7faaa79e5091b65 +F src/vdbemem.c 3b5a9a5b375458d3e12a50ae1aaa41eeec2175fd +F src/vdbesort.c eda25cb2d1727efca6f7862fea32b8aa33c0face F src/vdbetrace.c 41963d5376f0349842b5fc4aaaaacd7d9cdc0834 -F src/vtab.c 6b3cfaff7e4397739d6b48511e777ca58c6d06d4 +F src/vtab.c c4bbe0f870f52036553f8098aee0703997f0577a F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 -F src/wal.c 8605bf47b9d0c0202b14b36cadb4de22ab1d4f84 -F src/wal.h 520cb6942a265ac3d172e634266329a46e8c5b70 -F src/walker.c 2d2cc7fb0f320f7f415215d7247f3c584141ac09 -F src/where.c 5c9df42d50888be8274a5a0eb062eb0629869bd3 -F src/whereInt.h e5b939701a7ceffc5a3a8188a37f9746416ebcd0 -F src/wherecode.c 99707d11907c71d289ee9553d2d1a22f1fd8ba41 -F src/whereexpr.c 98ce9f3b8d92b6f741b9f3498f9619695739256e +F src/wal.c 2858e71b30d521c80949fa10ed6b116367865594 +F src/wal.h 8659519a248ef0f33e575b20ab04211cebe1f430 +F src/walker.c 91a6df7435827e41cff6bb7df50ea00934ee78b0 +F src/where.c 6bbf9284f4f15a6fa48663d033870cc0d7f5ee66 +F src/whereInt.h 2bcc3d176e6091cb8f50a30b65c006e88a73614d +F src/wherecode.c e04ac8f24c3ac8621df6c3be3ac8c7d4fa893745 +F src/whereexpr.c 87ecdf24beba4498e4380b31c4131febb0a6ceaa F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test a6d901b436328bd67a79b41bb0ac2663918fe3bd F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2 F test/aggnested.test b35b4cd69fc913f90d39a575e171e1116c3a4bb7 F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87 F test/all.test 6ff7b43c2b4b905c74dc4a813d201d0fa64c5783 -F test/alter.test 2facdddf08d0d48e75dc6cc312cd2b030f4835dd +F test/alter.test b820ab9dcf85f8e3a65bc8326accb2f0c7be64ef F test/alter2.test 7ea05c7d92ac99349a802ef7ada17294dd647060 F test/alter3.test 4d79934d812eaeacc6f22781a080f8cfe012fdc3 -F test/alter4.test 0c33c542247ba5aee4f4a0133ac44bcf8f97e5e0 +F test/alter4.test b6d7b86860111864f6cddb54af313f5862dda23b F test/altermalloc.test e81ac9657ed25c6c5bb09bebfa5a047cd8e4acfc F test/amatch1.test b5ae7065f042b7f4c1c922933f4700add50cdb9f F test/analyze.test 3eb35a4af972f98422e5dc0586501b17d103d321 @@ -493,7 +498,7 @@ F test/analyzeB.test a4c1c3048f6d9e090eb76e83eecb18bcf6d31a70 F test/analyzeC.test 555a6cc388b9818b6eda6df816f01ce0a75d3a93 F test/analyzeD.test 42af58de25a6436502e43006e9e59e2d71bcb0cf F test/analyzeE.test 8684e8ac5722fb97c251887ad97e5d496a98af1d -F test/analyzeF.test 5d1fe1024ba2dfea3c18bede8c1ccef8aba1ab34 +F test/analyzeF.test f423125b557f11ad71bb29765ef9c34b6dcf4ab7 F test/analyzer1.test 459fa02c445ddbf0101a3bad47b34290a35f2e49 F test/async.test 1d0e056ba1bb9729283a0f22718d3a25e82c277b F test/async2.test c0a9bd20816d7d6a2ceca7b8c03d3d69c28ffb8b @@ -501,15 +506,15 @@ F test/async3.test d73a062002376d7edc1fe3edff493edbec1fc2f7 F test/async4.test 1787e3952128aa10238bf39945126de7ca23685a F test/async5.test 383ab533fdb9f7ad228cc99ee66e1acb34cc0dc0 F test/atof1.test ff0b0156fd705b67c506e1f2bfe9e26102bea9bd -F test/attach.test 437107943f14d131cf5efc2ae5305a94d7cb1d58 +F test/attach.test 9976867b1a6585018f0966857aa82ed44e0bd6dc F test/attach2.test 0ec5defa340363de6cd50fd595046465e9aaba2d -F test/attach3.test 359eb65d00102cdfcef6fa4e81dc1648f8f80b27 +F test/attach3.test c59d92791070c59272e00183b7353eeb94915976 F test/attach4.test 53bf502f17647c6d6c5add46dda6bac8b6f4665c F test/attachmalloc.test 3a4bfca9545bfe906a8d2e622de10fbac5b711b0 -F test/auth.test 872a122b3977c1d1bb9fd637dc20016e5c01880f +F test/auth.test c6ede04bee65637ff354b43fc1235aa560c0863e F test/auth2.test 9eb7fce9f34bf1f50d3f366fb3e606be5a2000a1 -F test/auth3.test b810826b193831929951c0d50783a200e5ef6b72 -F test/autoinc.test c58912526998a39e11f66b533e23cfabea7f25b7 +F test/auth3.test 0d48b901cf111c14b4b1b5205c7d28f1a278190f +F test/autoinc.test 6ae8fb69c9f656962464ae4e6667045d0dfc3b46 F test/autoindex1.test 14b63a9f1e405fe6d5bfc8c8d00249c2ebaf13ea F test/autoindex2.test 12ef578928102baaa0dc23ad397601a2f4ecb0df F test/autoindex3.test a3be0d1a53a7d2edff208a5e442312957047e972 @@ -524,14 +529,15 @@ F test/backup2.test 34986ef926ea522911a51dfdb2f8e99b7b75ebcf F test/backup4.test 8f6fd48e0dfde77b9a3bb26dc471ede3e101df32 F test/backup5.test ee5da6d7fe5082f5b9b0bbfa31d016f52412a2e4 F test/backup_ioerr.test 4c3c7147cee85b024ecf6e150e090c32fdbb5135 -F test/backup_malloc.test 7162d604ec2b4683c4b3799a48657fb8b5e2d450 +F test/backup_malloc.test 0c9abdf74c51e7bedb66d504cd684f28d4bd4027 F test/badutf.test d5360fc31f643d37a973ab0d8b4fb85799c3169f F test/badutf2.test f5bc7f2d280670ecd79b9cf4f0f1760c607fe51f F test/bc_common.tcl b5e42d80305be95697e6370e015af571e5333a1c F test/bc_test1.c e0a092579552e066ed4ce7bcdaecfa69c4aacc8d F test/bestindex1.test 0cf1bd2d7b97d3a3a8c10736125274f64765c4ee F test/bestindex2.test 4a06b8922ab2fd09434870da8d1cdf525aaf7060 -F test/bestindex3.test dd5fa4f483cd0356549203c4522f8c9e21cb1fc0 +F test/bestindex3.test 578b6a52dab819e63f28e3640e04b32c85aed320 +F test/bestindex4.test 4cb5ff7dbaebadb87d366f51969271778423b455 F test/between.test 34d375fb5ce1ae283ffe82b6b233e9f38e84fc6c F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59 F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc @@ -599,7 +605,7 @@ F test/corrupt8.test 2399dfe40d2c0c63af86706e30f3e6302a8d0516 F test/corrupt9.test 730a3db08d4ab9aa43392ea30d9c2b4879cbff85 F test/corruptA.test 53e56dafd180addcdadb402244b8cb9771d2ba26 F test/corruptB.test 73a8d6c0b9833697ecf16b63e3c5c05c945b5dec -F test/corruptC.test 0c46574f8d4f27ecc799b1b5c4cbf9b1817bce9a +F test/corruptC.test 4ef10844eba5213bd262f4d96784d7fcda114afe F test/corruptD.test b3c205fac7952b1de645ce44bb02335cd9e3e040 F test/corruptE.test 82ccf4f8f543fdbedd4aa42c709cb077f7374c62 F test/corruptF.test be9fde98e4c93648f1ba52b74e5318edc8f59fe4 @@ -623,17 +629,19 @@ F test/crashtest1.c 09c1c7d728ccf4feb9e481671e29dda5669bbcc2 F test/createtab.test b5de160630b209c4b8925bdcbbaf48cc90b67fe8 F test/cse.test 277350a26264495e86b1785f34d2d0c8600e021c F test/csv01.test e0ba3caaa57e4c667a0b45977689fb8082f14348 -F test/ctime.test 0b995accd44a52914bd4744d5c1b6e1a56c7897c +F test/ctime.test ff6c38e822459d6ca743c34901caf57740b08b54 F test/cursorhint.test 7bc346788390475e77a345da2b92270d04d35856 -F test/cursorhint2.test 2b45341d32d1aae9991a00ef31ebca339b274255 -F test/date.test 984ac1e3e5e031386866f034006148d3972b4a65 +F test/cursorhint2.test fa41f0d997e67db921d08c31e73111b32811201a +F test/date.test a6a5a48b90907bca9fbcc79a30be5a715c1ab2fc +F test/dbfuzz.c 8cc2bdb818b4483a052f9f80f96be74cbd9a6e1d F test/dbstatus.test 73149851b3aff14fc6db478e58f9083a66422cf5 F test/dbstatus2.test e93ab03bfae6d62d4d935f20de928c19ca0ed0ab F test/default.test 0cb49b1c315a0d81c81d775e407f66906a2a604d -F test/delete.test e1bcdf8926234e27aac24b346ad83d3329ec8b6f +F test/delete.test acc38fca8ee4851467705b1c2cfea64cd26667e5 F test/delete2.test 3a03f2cca1f9a67ec469915cb8babd6485db43fa F test/delete3.test 555e84a00a99230b7d049d477a324a631126a6ab F test/delete4.test 21d2113217eeaacac2d99defe14fe6611615ae86 +F test/delete_db.test c70a43629dd4d3e1dd03fdaf7a22153af6a69d92 F test/descidx1.test 6d03b44c8538fe0eb4924e19fba10cdd8f3c9240 F test/descidx2.test 9f1a0c83fd57f8667c82310ca21b30a350888b5d F test/descidx3.test 09ddbe3f5295f482d2f8b687cf6db8bad7acd9a2 @@ -648,19 +656,19 @@ F test/e_changes.test fd66105385153dbf21fdb35eb8ef6c3e1eade579 F test/e_createtable.test d4c6059d44dcd4b636de9aae322766062b471844 F test/e_delete.test ab39084f26ae1f033c940b70ebdbbd523dc4962e F test/e_droptrigger.test 3cd080807622c13e5bbb61fc9a57bd7754da2412 -F test/e_dropview.test 0c9f7f60989164a70a67a9d9c26d1083bc808306 -F test/e_expr.test 03a84a6fa9bd3472112d6bd4599f5269f5f74803 -F test/e_fkey.test a1783fe1f759e1990e6a11adfcf0702dac4d0707 +F test/e_dropview.test 21ce09c361227ddbc9819a5608ee2700c276bdd5 +F test/e_expr.test d0dd488e0527cbc560ae2dea1939cc44cb9b9924 +F test/e_fkey.test 54cc0046d2d952d6c42b0dd94414e7a8f75f79f4 F test/e_fts3.test 5c02288842e4f941896fd44afdef564dd5fc1459 F test/e_insert.test 3de217e95094d3d165992a6de1164bbc4bd92dc7 F test/e_reindex.test 2bebf7b393e519198b7c654407221cf171a439b8 F test/e_resolve.test a61751c368b109db73df0f20fc75fb47e166b1d8 -F test/e_select.test 52692ff3849541e828ad4661fe3773a9b8711763 +F test/e_select.test 16651bb681e83a1a2875ff4a595ed2b4b4dee375 F test/e_select2.test aceb80ab927d46fba5ce7586ebabf23e2bb0604f F test/e_totalchanges.test b12ee5809d3e63aeb83238dd501a7bca7fd72c10 F test/e_update.test f46c2554d915c9197548681e8d8c33a267e84528 F test/e_uri.test 25385396082b67fd02ae0038b95a3b3575fe0519 -F test/e_vacuum.test 120f29ea56bdce4d43279527ece894ab5d1729d3 +F test/e_vacuum.test 1b8b4772d05374aa1b8958669138bbb4213ee26a F test/e_wal.test ae9a593207a77d711443ee69ffe081fda9243625 F test/e_walauto.test 248af31e73c98df23476a22bdb815524c9dc3ba8 F test/e_walckpt.test 28c371a6bb5e5fe7f31679c1df1763a19d19e8a0 @@ -676,20 +684,20 @@ F test/exclusive.test 9a57bd66e39144b888ca75c309914fcdefb4e3f9 F test/exclusive2.test 32798111aae78a5deec980eee383213f189df308 F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7 F test/exists.test 79a75323c78f02bbe9c251ea502a092f9ef63dac -F test/expr.test 79c3e7502d9e571553b85f0ecc8ff2ac7d0e4931 +F test/expr.test 66a2c9ac34f74f036faa4092f5402c7d3162fc93 F test/extension01.test 00d13cec817f331a687a243e0e5a2d87b0e358c9 F test/extraquick.test cb254400bd42bfb777ff675356aabf3287978f79 F test/fallocate.test 3e979af17dfa7e5e9dda5eba1a696c04fa9d47f7 -F test/filectrl.test 7c13f96457435238da99aff7343ad6a3a4885787 +F test/filectrl.test 6e871c2d35dead1d9a88e176e8d2ca094fec6bb3 F test/filefmt.test e4edbdc637ca9576ccf4337a3cce627d9df7a56c -F test/fkey1.test 13e3d48236a2b9f5c5ebd232eef9b3ab682a8a2c -F test/fkey2.test f3d27ecba480a348c328965d154214719bb158a9 +F test/fkey1.test ba64806ff9a04eecab2679caad377ae99a5e94e4 +F test/fkey2.test 155809016fad6b2a1491facf2ac53a551bc57c2c F test/fkey3.test 76d475c80b84ee7a5d062e56ccb6ea68882e2b49 F test/fkey4.test 86446017011273aad8f9a99c1a65019e7bd9ca9d F test/fkey5.test 5a373303f201ac03c22ba1ef17a733d3f56e611a F test/fkey6.test d078a1e323a740062bed38df32b8a736fd320dc0 F test/fkey7.test 72e915890ee4a005daaf3002cb208e8fe973ac13 -F test/fkey8.test 7bd1dd0174a0e29a90c62c517b9e2a410a0b345d +F test/fkey8.test e5372e32cdb4481f121ec3550703eeb7b4e0762c F test/fkey_malloc.test 594a7ea1fbab553c036c70813cd8bd9407d63749 F test/fordelete.test eb93a2f34137bb87bdab88fcab06c0bd92719aff F test/format4.test 1f0cac8ff3895e9359ed87e41aaabee982a812eb @@ -747,7 +755,7 @@ F test/fts3an.test a49ccadc07a2f7d646ec1b81bc09da2d85a85b18 F test/fts3ao.test 3e4e3d5e75c076520341d0bdf4eb17c00e8cbde2 F test/fts3atoken.test 4b4c16fdcfc972f2cdbba212375a060a86ccf5f1 F test/fts3auto.test b981fea19b132b4e6878f50d7c1f369b28f68eb9 -F test/fts3aux1.test f8f287a4a73f381f8fa15b6a70f36245f903d221 +F test/fts3aux1.test ca32c138aa777b476271df2da2c5538b7a43ada1 F test/fts3aux2.test 7ae2b2c13aefdf4169279a27a5f51780ce57f6ba F test/fts3b.test e93bbb653e52afde110ad53bbd793f14fe7a8984 F test/fts3c.test fc723a9cf10b397fdfc2b32e73c53c8b1ec02958 @@ -807,36 +815,38 @@ F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f F test/func3.test d202a7606d23f90988a664e88e268aed1087c11c F test/func4.test 6beacdfcb0e18c358e6c2dcacf1b65d1fa80955f F test/func5.test cdd224400bc3e48d891827cc913a57051a426fa4 -F test/fuzz-oss1.test 4912e528ec9cf2f42134456933659d371c9e0d74 +F test/fuzz-oss1.test e58330d01cbbd8215ee636b17a03fe220b37dbfa F test/fuzz.test 96083052bf5765e4518c1ba686ce2bab785670d1 F test/fuzz2.test 76dc35b32b6d6f965259508508abce75a6c4d7e1 F test/fuzz3.test b47377143f0c80f91ed29d722861077ff34415d5 F test/fuzz_common.tcl a87dfbb88c2a6b08a38e9a070dabd129e617b45b F test/fuzz_malloc.test 328f70aaca63adf29b4c6f06505ed0cf57ca7c26 -F test/fuzzcheck.c f2e8102f7829f7b946ebdb6f1a16c6f942d9de66 +F test/fuzzcheck.c a87e6067a8d19844bade916841cb76150ecf24a2 F test/fuzzdata1.db 7ee3227bad0e7ccdeb08a9e6822916777073c664 F test/fuzzdata2.db f03a420d3b822cc82e4f894ca957618fbe9c4973 F test/fuzzdata3.db c6586d3e3cef0fbc18108f9bb649aa77bfc38aba F test/fuzzdata4.db 1882f0055fb63214d8407ddc7aca9b0b1c59af21 +F test/fuzzdata5.db 9f0cdcc5c6e83b90cf9ae343bd07f684d2da2de7 F test/fuzzer1.test 3d4c4b7e547aba5e5511a2991e3e3d07166cfbb8 F test/fuzzer2.test a85ef814ce071293bce1ad8dffa217cbbaad4c14 F test/fuzzerfault.test 8792cd77fd5bce765b05d0c8e01b9edcf8af8536 +F test/gcfault.test dd28c228a38976d6336a3fc42d7e5f1ad060cb8c F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98 -F test/hexlit.test d7b0a5f41123df1e43985b91b8b2e70f95282d21 +F test/hexlit.test 4a6a5f46e3c65c4bf1fa06f5dd5a9507a5627751 F test/hidden.test 23c1393a79e846d68fd902d72c85d5e5dcf98711 -F test/hook.test 3b7b99d0eece6d279812c2aef6fa08bdfabc633e +F test/hook.test 3a01b876691f9151d3e44562354f7d663ff90fce F test/icu.test 73956798bace8982909c00476b216714a6d0559a F test/ieee754.test 806fc0ce7f305f57e3331eaceeddcfec9339e607 F test/imposter1.test c3f1db2d3db2c24611a6596a3fc0ffc14f1466c8 -F test/in.test 61d940ced6817bee66e4e0b09d5bc8608f57134b +F test/in.test 20c5529986998949908f889c8208b2cd894b2cc9 F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75 F test/in3.test 3cbf58c87f4052cee3a58b37b6389777505aa0c0 F test/in4.test d2b38cba404bc4320f4fe1b595b3d163f212c068 -F test/in5.test acc710c12af118df5f8645eaba9479f5619eed81 +F test/in5.test 6c006e0bcd7351b69350ef566e65f244023489e9 F test/incrblob.test c9b96afc292aeff43d6687bcb09b0280aa599822 F test/incrblob2.test a5ce5ed1d0b01e2ed347245a21170372528af0a5 F test/incrblob3.test d8d036fde015d4a159cd3cbae9d29003b37227a4 -F test/incrblob4.test f26502a5697893e5acea268c910f16478c2f0fab +F test/incrblob4.test 21a52a6843a56cdcce968c6a86b72a7066d0e6ba F test/incrblob_err.test 69f9247fed50278d48ea710d1a8f9cdb09e4c0b8 F test/incrblobfault.test 280474078f6da9e732cd2a215d3d854969014b6e F test/incrcorrupt.test 6c567fbf870aa9e91866fe52ce6f200cd548939a @@ -849,11 +859,11 @@ F test/index2.test f835d5e13ca163bd78c4459ca15fd2e4ed487407 F test/index3.test 81bc47890b8abfb181bc35f8d10b56c069803386 F test/index4.test ab92e736d5946840236cd61ac3191f91a7856bf6 F test/index5.test 8621491915800ec274609e42e02a97d67e9b13e7 -F test/index6.test 43b4e29258b978fcdab84fc61df4f5212119dd09 -F test/index7.test 9c6765a74fc3fcde7aebc5b3bd40d98df14a527c +F test/index6.test b4fc812290067a578b98bb2667b676db89e202a7 +F test/index7.test 7feababe16f2091b229c22aff2bcc1d4d6b9d2bb F test/index8.test bc2e3db70e8e62459aaa1bd7e4a9b39664f8f9d7 F test/indexedby.test 9c4cd331224e57f79fbf411ae245e6272d415985 -F test/indexexpr1.test cb71b6586177b840e28110dd952178bb2bdfedc2 +F test/indexexpr1.test 7d243fac508b4a99fb900ffe34eb488312cfce84 F test/indexfault.test 31d4ab9a7d2f6e9616933eb079722362a883eb1d F test/init.test 15c823093fdabbf7b531fe22cf037134d09587a7 F test/insert.test 38742b5e9601c8f8d76e9b7555f7270288c2d371 @@ -861,9 +871,11 @@ F test/insert2.test 4d14b8f1b810a41995f6286b64a6943215d52208 F test/insert3.test 1b7db95a03ad9c5013fdf7d6722b6cd66ee55e30 F test/insert4.test a20432f1c0fbbcff8f11d0e6ab4acb8c9db58023 F test/insert5.test 394f96728d1258f406fe5f5aeb0aaf29487c39a6 -F test/instr.test 737bbf80685232033f3abedc6ae92f75860b5dd2 -F test/intarray.test 066b7d7ac38d25bf96f87f1b017bfc687551cdd4 -F test/interrupt.test dfe9a67a94b0b2d8f70545ba1a6cca10780d71cc +F test/instr.test 9a8802f28437d8ade53fedfc47b2ca599b4e48ba +F test/instrfault.test 0f870b218ea17cd477bb19ed330eecdb460dd53a +F test/intarray.test 46d95b457916638c5d8b1af21fb174804b3acf8b +F test/interrupt.test 16ea879ec728cb76414c148c5f24afd5d1f91054 +F test/interrupt2.test e4408ca770a6feafbadb0801e54a0dcd1a8d108d F test/intpkey.test ac71107a49a06492b69b82aafaf225400598d3c8 F test/io.test f95bca1783b01ea7761671560d023360d2dfa4cc F test/ioerr.test 2a24bd6ed5a8b062e64bfe1f6cf94fb25e92210d @@ -876,7 +888,7 @@ F test/join.test f9d4a28dec81c6e9dc21b73518e024d73b5ebf57 F test/join2.test f2171c265e57ee298a27e57e7051d22962f9f324 F test/join3.test 6f0c774ff1ba0489e6c88a3e77b9d3528fb4fda0 F test/join4.test 1a352e4e267114444c29266ce79e941af5885916 -F test/join5.test 8a5c0be6f0c260a5c7177c3b8f07c7856141038a +F test/join5.test bc98ea4b4e5003f5b1453701ebb8cd7d1c01a550 F test/join6.test cfe6503791ceb0cbb509966740286ec423cbf10b F test/journal1.test 69abc726c51b4a0409189f9a85191205297c0577 F test/journal2.test 9dac6b4ba0ca79c3b21446bbae993a462c2397c4 @@ -884,17 +896,18 @@ F test/journal3.test ff8af941f9e06161d3db1b46bb9f965ff0e7f307 F test/jrnlmode.test 7864d59cf7f6e552b9b99ba0f38acd167edc10fa F test/jrnlmode2.test 81610545a4e6ed239ea8fa661891893385e23a1d F test/jrnlmode3.test 556b447a05be0e0963f4311e95ab1632b11c9eaa -F test/json101.test 865776ed8580703e1684fe4b8ee2e473333bb121 +F test/json101.test c0897616f32d95431f37fd291cb78742181980ac F test/json102.test bf3fe7a706d30936a76a0f7a0375e1e8e73aff5a F test/json103.test c5f6b85e69de05f6b3195f9f9d5ce9cd179099a0 F test/keyword1.test 37ef6bba5d2ed5b07ecdd6810571de2956599dff +F test/kvtest.c 2c66ddefcd03c2caa337f6dd79e6c82368af83df F test/lastinsert.test 42e948fd6442f07d60acbd15d33fb86473e0ef63 F test/laststmtchanges.test ae613f53819206b3222771828d024154d51db200 -F test/like.test 81632c437a947bf1f7130b19537da6a1a844806a +F test/like.test 0603f4fa0dad50987f70032c05800cbfa8985302 F test/like2.test 3b2ee13149ba4a8a60b59756f4e5d345573852da F test/like3.test 3608a2042b6f922f900fbfd5d3ce4e7eca57f7c4 F test/limit.test 0c99a27a87b14c646a9d583c7c89fd06c352663e -F test/limit2.test 40e79f3d95f2077742aa5b7676f58c9af1735d83 +F test/limit2.test e35f57bd3a62d7c5dcb5ac4306e675c75f974809 F test/loadext.test d077450695ddb5c1ea3ad7d48e5f5850fe732ad9 F test/loadext2.test 0408380b57adca04004247179837a18e866a74f7 F test/lock.test be4fe08118fb988fed741f429b7dd5d65e1c90db @@ -925,7 +938,7 @@ F test/mallocE.test db1ed69d7eded1b080952e2a7c37f364ad241b08 F test/mallocF.test 2d5c590ebc2fc7f0dcebdf5aa8498b9aed69107e F test/mallocG.test 0ff91b65c50bdaba680fb75d87fe4ad35bb7934f F test/mallocH.test 79b65aed612c9b3ed2dcdaa727c85895fd1bfbdb -F test/mallocI.test a88c2b9627c8506bf4703d8397420043a786cdb6 +F test/mallocI.test 6c23a71df077fa5d387be90e7e669c5b368ca38a F test/mallocJ.test b5d1839da331d96223e5f458856f8ffe1366f62e F test/mallocK.test 27cb5566a6e5f2d76f9d4aa2eca45524401fd61e F test/mallocL.test fb311ff80afddf3b1a75e52289081f4754d901dc @@ -947,20 +960,21 @@ F test/misc4.test 0d8be3466adf123a7791a66ba2bc8e8d229e87f3 F test/misc5.test fff0f75e834bc09a39f6079320dd8c37de956f4f F test/misc6.test 953cc693924d88e6117aeba16f46f0bf5abede91 F test/misc7.test edd0b63e2ee29a256900b0514f6fff27e19e9bb2 -F test/misc8.test 21ac9d35a5e110279ae9e1588b8914f54de1c60b +F test/misc8.test ba03aaa08f02d62fbb8d3b2f5595c1b33aa9bbc5 F test/misuse.test 3c34719944ba045cc6c188a4852ba04680728912 F test/mmap1.test d2cfc1635171c434dcff0ece2f1c8e0a658807ce F test/mmap2.test 9d6dd9ddb4ad2379f29cc78f38ce1e63ed418022 F test/mmap3.test b3c297e78e6a8520aafcc1a8f140535594c9086e F test/mmap4.test 2e2b4e32555b58da15176e6fe750f17c9dcf7f93 F test/mmapfault.test d4c9eff9cd8c2dc14bc43e71e042f175b0a26fe3 -F test/multiplex.test efd015ca0b5b4a57dc9535b8feb1273eebeadb60 +F test/multiplex.test dc0d67b66f84b484a83cb8bbdf3f0a7f49562ccd F test/multiplex2.test 580ca5817c7edbe4cc68fa150609c9473393003a F test/multiplex3.test d228f59eac91839a977eac19f21d053f03e4d101 F test/multiplex4.test e8ae4c4bd70606a5727743241f13b5701990abe4 F test/mutex1.test ea2cc74d97f077b9e74c84cbd024f14d79a8126f F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660 F test/nan.test dacc57f80859c06a433d30839336fe227d2038b3 +F test/nockpt.test 9a436a7213ba5ef7a32304998d386d3ea3f76c9d F test/nolock.test f196cf8b8fbea4e2ca345140a2b3f3b0da45c76e F test/notify1.test 669b2b743618efdc18ca4b02f45423d5d2304abf F test/notify2.test 2ecabaa1305083856b7c39cf32816b612740c161 @@ -981,6 +995,8 @@ F test/orderby7.test 3d1383d52ade5b9eb3a173b3147fdd296f0202da F test/orderby8.test 23ef1a5d72bd3adcc2f65561c654295d1b8047bd F test/orderby9.test 87fb9548debcc2cd141c5299002dd94672fa76a3 F test/oserror.test b32dc34f2363ef18532e3a0a7358e3e7e321974f +F test/ossfuzz.c e469138f4be3e92df6173b79b3b216ab6e17b407 +F test/ossshell.c d9f1a6f43e7bab45d6be857a5800f5d4a1861db3 F test/ovfl.test 199c482696defceacee8c8e0e0ef36da62726b2f F test/pager1.test 841868017e9dd3cb459b8d78862091a7d9cff21d F test/pager2.test 67b8f40ae98112bcdba1f2b2d03ea83266418c71 @@ -994,10 +1010,11 @@ F test/pagesize.test 5769fc62d8c890a83a503f67d47508dfdc543305 F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442 F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff -F test/permutations.test 544a2f317fc5045bc512ae432f89eae303c0d640 +F test/permutations.test cd0b7bc04bf5e50d3a993d24c834d591f7d4f5fe F test/pragma.test 1e94755164a3a3264cd39836de4bebcb7809e5f8 F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f F test/pragma3.test 14c12bc5352b1e100e0b6b44f371053a81ccf8ed +F test/pragma4.test 6e85b6eab8e61ffc9c7db59d842276674e8e3264 F test/printf.test b3ff34e73d59124140eaf89f7672e21bc2ca5fcc F test/printf2.test 0b61566dd1c0f0b802f59dffa228c5dc5aa6b054 F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb @@ -1013,9 +1030,9 @@ F test/randexpr1.test eda062a97e60f9c38ae8d806b03b0ddf23d796df F test/rbu.test 168573d353cd0fd10196b87b0caa322c144ef736 F test/rdonly.test 64e2696c322e3538df0b1ed624e21f9a23ed9ff8 F test/regexp1.test 497ea812f264d12b6198d6e50a76be4a1973a9d8 -F test/regexp2.test aa7ffcc21350007a78361b82bcf3b74d12227144 +F test/regexp2.test 40e894223b3d6672655481493f1be12012f2b33c F test/reindex.test 44edd3966b474468b823d481eafef0c305022254 -F test/releasetest.tcl 53560b838b79c468ec9973e0dfa3d8892628887f +F test/releasetest.tcl cb06c4df0de4872e65b178316f8a87ccf7624d59 x F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb F test/rollback.test 458fe73eb3ffdfdf9f6ba3e9b7350a6220414dea F test/rollback2.test 8435d6ff0f13f51d2a4181c232e706005fa90fc5 @@ -1023,6 +1040,16 @@ F test/rollbackfault.test 0e646aeab8840c399cfbfa43daab46fd609cf04a F test/rowallock.test 3f88ec6819489d0b2341c7a7528ae17c053ab7cc F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81 F test/rowid.test 5b7509f384f4f6fae1af3c8c104c8ca299fea18d +F test/rowvalue.test 44f3492f415cc9f374e8388a5eb61503eaca5230 +F test/rowvalue2.test 060d238b7e5639a7c5630cb5e63e311b44efef2b +F test/rowvalue3.test 3068f508753af69884b12125995f023da0dbb256 +F test/rowvalue4.test 4b556d7de161a0dd8cff095c336e913986398bea +F test/rowvalue5.test c81c7d8cf36711ab37675ad7376084ae2a359cb6 +F test/rowvalue6.test d19b54feb604d5601f8614b15e214e0774c01087 +F test/rowvalue7.test 5d06ff19d9e6969e574a2e662a531dd0c67801a8 +F test/rowvalue8.test 5900eddad9e2c3c2e26f1a95f74aafc1232ee5e0 +F test/rowvalue9.test d8dd2c6ecac432dadaa79e41dc2434f007be1b6b +F test/rowvaluefault.test 7cd9ccc6c2fbdd881672984087aad0491bb75504 F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798 F test/run-wordcount.sh 891e89c4c2d16e629cd45951d4ed899ad12afc09 F test/savepoint.test c671fdbd34cd3bfe1518a777526ada595180cf8d @@ -1036,7 +1063,7 @@ F test/scanstatus.test 5253c219e331318a437f436268e0e82345700285 F test/schema.test 8f7999be894260f151adf15c2c7540f1c6d6a481 F test/schema2.test 906408621ea881fdb496d878b1822572a34e32c5 F test/schema3.test 1bc1008e1f8cb5654b248c55f27249366eb7ed38 -F test/schema4.test e6a66e20cc69f0e306667c08be7fda3d11707dc5 +F test/schema4.test 3b26c9fa916abb6dadf894137adcf41b7796f7b9 F test/schema5.test 29699b4421f183c8f0e88bd28ce7d75d13ea653e F test/securedel.test 21749c32ccc30f1ea9e4b9f33295a6521ec20fa0 F test/securedel2.test 2d54c28e46eb1fd6902089958b20b1b056c6f1c5 @@ -1046,12 +1073,12 @@ F test/select3.test 2ce595f8fb8e2ac10071d3b4e424cadd4634a054 F test/select4.test 5389d9895968d1196c457d59b3ee6515d771d328 F test/select5.test e758b8ef94f69b111df4cb819008856655dcd535 F test/select6.test 39eac4a5c03650b2b473c532882273283ee8b7a0 -F test/select7.test 95e370c42d47c3c52377d05e9ffc01ccff7c1f61 +F test/select7.test f659f231489349e8c5734e610803d7654207318f F test/select8.test 8c8f5ae43894c891efc5755ed905467d1d67ad5d F test/select9.test aebc2bb0c3bc44606125033cbcaac2c8d1f33a95 F test/selectA.test 101e722370ac6e84978c2958b8931c78b10a1709 F test/selectB.test 954e4e49cf1f896d61794e440669e03a27ceea25 -F test/selectC.test 871fb55d884d3de5943c4057ebd22c2459e71977 +F test/selectC.test e25243f8ca503e06f252eb0218976d07cfeceac3 F test/selectD.test b0f02a04ef7737decb24e08be2c39b9664b43394 F test/selectE.test a8730ca330fcf40ace158f134f4fe0eb00c7edbf F test/selectF.test 21c94e6438f76537b72532fa9fd4710cdd455fc3 @@ -1070,22 +1097,25 @@ F test/sharedA.test 0cdf1a76dfa00e6beee66af5b534b1e8df2720f5 F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e F test/shared_err.test 2f2aee20db294b9924e81f6ccbe60f19e21e8506 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 -F test/shell1.test 65b10cd8a90cda9b5af9100a45689a57dcc01a31 +F test/shell1.test 52ac23a345772ab0d6d3241a21a633fdaa3ed581 F test/shell2.test e242a9912f44f4c23c3d1d802a83e934e84c853b -F test/shell3.test da513d522ef6f01cee8475dcf8332bff8982b3dd -F test/shell4.test 69995ee1cc278eb149aa8746ce1f935f4eaf98b9 +F test/shell3.test 9b95ba643eaa228376f06a898fb410ee9b6e57c1 +F test/shell4.test 89ad573879a745974ff2df20ff97c5d6ffffbd5d F test/shell5.test 50a732c1c2158b1cd62cf53975ce1ea7ce6b9dc9 +F test/shell6.test cff624fadf71bdb5e3a9b0f86eaf59a43c0622b1 +F test/shell7.test 07751911b294698e0c5df67bcbd29e7d2f0f2907 F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5 F test/shrink.test 1b4330b1fd9e818c04726d45cb28db73087535ce F test/sidedelete.test f0ad71abe6233e3b153100f3b8d679b19a488329 -F test/skipscan1.test f2d4482f195f018c3732374d3985fc34bcc3ae1a +F test/skipscan1.test 8ab5d2c7c5cd3fe7f172d366e6e74e887cb33cb4 F test/skipscan2.test d1d1450952b7275f0b0a3a981f0230532743951a F test/skipscan3.test ec5bab3f81c7038b43450e7b3062e04a198bdbb5 F test/skipscan5.test 67817a4b6857c47e0e33ba3e506da6f23ef68de2 F test/skipscan6.test 5866039d03a56f5bd0b3d172a012074a1d90a15b -F test/snapshot.test a19465046168b4420b5afeed37c3214e42a49f4a -F test/snapshot_fault.test 062ff0438a074978d45e9f9a92e7ad459b74ee73 +F test/snapshot.test 85735bd997a4f6d710140c28fd860519a299649f +F test/snapshot2.test 867652ed4a13282dce218723535fad1c7b44c3c4 +F test/snapshot_fault.test 52c5e97ebd218846a8ae2da4d147d3e77d71f963 F test/soak.test 0b5b6375c9f4110c828070b826b3b4b0bb65cd5f F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087 F test/sort.test c2adc635c2564241fefec0b3a68391ef6868fd3b @@ -1098,11 +1128,11 @@ F test/speed1.test f2974a91d79f58507ada01864c0e323093065452 F test/speed1p.explain d841e650a04728b39e6740296b852dccdca9b2cb F test/speed1p.test b180e98609c7677382cf618c0ec9b69f789033a8 F test/speed2.test 53177056baf6556dcbdcf032bbdfc41c1aa74ded -F test/speed3.test d32043614c08c53eafdc80f33191d5bd9b920523 +F test/speed3.test 694affeb9100526007436334cf7d08f3d74b85ef F test/speed4.test abc0ad3399dcf9703abed2fff8705e4f8e416715 F test/speed4p.explain 6b5f104ebeb34a038b2f714150f51d01143e59aa F test/speed4p.test 0e51908951677de5a969b723e03a27a1c45db38b -F test/speedtest1.c 28221f433d358dd3fcf2ca504ed51409aa66ffaf +F test/speedtest1.c 4e8ea6165046f02e1cfe0f4700256e91c981ec10 F test/spellfix.test f9c1f431e2c096c8775fec032952320c0e4700db F test/spellfix2.test dfc8f519a3fc204cb2dfa8b4f29821ae90f6f8c3 F test/spellfix3.test 0f9efaaa502a0e0a09848028518a6fb096c8ad33 @@ -1114,7 +1144,7 @@ F test/statfault.test f525a7bf633e50afd027700e9a486090684b1ac1 F test/stmt.test 64844332db69cf1a735fcb3e11548557fc95392f F test/subquery.test d7268d193dd33d5505df965399d3a594e76ae13f F test/subquery2.test 438f8a7da1457277b22e4176510f7659b286995f -F test/subselect.test d24fd8757daf97dafd2e889c73ea4c4272dcf4e4 +F test/subselect.test 0966aa8e720224dbd6a5e769a3ec2a723e332303 F test/substr.test 18f57c4ca8a598805c4d64e304c418734d843c1a F test/subtype1.test 7fe09496352f97053af1437150751be2d0a0cae8 F test/superlock.test ec94f0556b6488d97f71c79f9061ae08d9ab8f12 @@ -1122,19 +1152,19 @@ F test/symlink.test c9ebe7330d228249e447038276bfc8a7b22f4849 F test/sync.test 2f84bdbc2b2df1fcb0220575b4b9f8cea94b7529 F test/syscall.test f59ba4e25f7ba4a4c031026cc2ef8b6e4b4c639c F test/sysfault.test c9f2b0d8d677558f74de750c75e12a5454719d04 -F test/tabfunc01.test 50a9fb379f9747fd0d40ea6d8fa3a101361bb537 +F test/tabfunc01.test 8b2ef53caa37854864c89e1e57e8a10efd4f5e43 F test/table.test b708f3e5fa2542fa51dfab21fc07b36ea445cb2f F test/tableapi.test 2674633fa95d80da917571ebdd759a14d9819126 F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930 F test/tclsqlite.test 1d73b9203b1ca8798d7d7310742b8d3febc0d56e F test/tempdb.test bd92eba8f20e16a9136e434e20b280794de3cdb6 -F test/tempdb2.test 4fc92055f2a3f7626c0f2eabbb637dc021b311d5 +F test/tempdb2.test 27e41ed540b2f9b056c2e77e9bddc1b875358507 F test/tempfault.test 0c0d349c9a99bf5f374655742577f8712c647900 F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30 F test/temptable2.test cd396beb41117a5302fff61767c35fa4270a0d5e F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637 -F test/temptrigger.test 8ec228b0db5d7ebc4ee9b458fc28cb9e7873f5e1 -F test/tester.tcl 949b4a73bd0324b7c796818d0d6a6715712932b3 +F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc +F test/tester.tcl 4ce5afd5e192db4cae178e1a983b060e0f08c5d6 F test/thread001.test 9f22fd3525a307ff42a326b6bc7b0465be1745a5 F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -1174,7 +1204,7 @@ F test/tkt-78e04e52ea.test 813779f8888f3ca226df656c4eef078f9635f3c9 F test/tkt-7a31705a7e6.test e75a2bba4eec801b92c8040eb22096ac6d35e844 F test/tkt-7bbfb7d442.test 7b2cd79c7a17ae6750e75ec1a7846712a69c9d18 F test/tkt-80ba201079.test 105a721e6aad0ae3c5946d7615d1e4d03f6145b8 -F test/tkt-80e031a00f.test f50046f474ecf67ad5c50cd9200da04ff887d7cd +F test/tkt-80e031a00f.test 9ee36348b761bf7c14261e002b75a4c0d5a04d4c F test/tkt-8454a207b9.test c583a9f814a82a2b5ba95207f55001c9f0cd816c F test/tkt-868145d012.test a5f941107ece6a64410ca4755c6329b7eb57a356 F test/tkt-8c63ff0ec.test 258b7fc8d7e4e1cb5362c7d65c143528b9c4cbed @@ -1264,7 +1294,7 @@ F test/tkt3541.test 5dc257bde9bc833ab9cc6844bf170b998dbb950a F test/tkt3554.test f599967f279077bace39220cbe76085c7b423725 F test/tkt3581.test 1966b7193f1e3f14951cce8c66907ae69454e9a3 F test/tkt35xx.test f38c1b03713179d414969187c941466e44945b35 -F test/tkt3630.test 929f64852103054125200bc825c316d5f75d42f7 +F test/tkt3630.test 9a934c58c259f89a0ae6bb6bb846c56285a6fd0f F test/tkt3718.test 3b59dcb5c4e7754dacd91e7fd353a61492cc402a F test/tkt3731.test 0c5f4cbffe102d43c3b2188af91a9e36348f974b F test/tkt3757.test 10cd679a88675c880533083fc79ac04324525595 @@ -1273,7 +1303,7 @@ F test/tkt3762.test 4d439ff7abdc8d9323150269d182c37c2d514576 F test/tkt3773.test 7bca904d2a647a6a4a291bd86d7fd7c73855b789 F test/tkt3791.test a6624b9a80b216a26cf473607f42f3e51898c267 F test/tkt3793.test d90ffd75c52413908d15e1c44fc2ea9c80fcc449 -F test/tkt3810.test 90fa0635dfa7da9680c8cd3513350a49b3a8ae12 +F test/tkt3810.test 09608d81c63a6ff3aaf7bc70717909c51f5f4048 F test/tkt3824.test 150aa00bb6220672e5f0eb14dc8eaa36750425f0 F test/tkt3832.test 2300d10d57562b89875b72148338ac3e14f8847d F test/tkt3838.test 292e72489101cd1320d7278dc111c173ebf334d4 @@ -1292,7 +1322,7 @@ F test/tokenize.test ce430a7aed48fc98301611429595883fdfcab5d7 F test/tpch01.test 04adbf8d8300fa60a222f28d901abd76e7be6dd4 F test/trace.test 6f676313e3ebd2a50585036d2f212a3319dd5836 F test/trace2.test f5cb67ad3bc09e0c58e8cca78dfd0b5639259983 -F test/trace3.test 01e4111d582c7b20ab1c63156169157d256bc3d5 +F test/trace3.test 56ab944fddacf628b118cc298503fc45c2e50ab0 F test/trans.test 6e1b4c6a42dba31bd65f8fa5e61a2708e08ddde6 F test/trans2.test 62bd045bfc7a1c14c5ba83ba64d21ade31583f76 F test/trans3.test 91a100e5412b488e22a655fe423a14c26403ab94 @@ -1311,6 +1341,7 @@ F test/triggerB.test 56780c031b454abac2340dbb3b71ac5c56c3d7fe F test/triggerC.test 302d8995f5ffe63bbc15053abb3ef7a39cf5a092 F test/triggerD.test 8e7f3921a92a5797d472732108109e44575fa650 F test/triggerE.test 15fa63f1097db1f83dd62d121616006978063d1f +F test/triggerF.test 55b1eb13433997faac3a4948c1d8252f6c8c636b F test/tt3_checkpoint.c 9e75cf7c1c364f52e1c47fd0f14c4340a9db0fe1 F test/tt3_core.c 8cd89ead95410f70e7fb02c79f1e040f9c5ad5cf F test/tt3_index.c 39eec10a35f57672225be4d182862152896dee4a @@ -1318,21 +1349,23 @@ F test/tt3_lookaside1.c 0377e202c3c2a50d688cb65ba203afeda6fafeb9 F test/tt3_stress.c c57d804716165811d979d4a719e05baccd79277f F test/tt3_vacuum.c 1753f45917699c9c1f66b64c717a717c9379f776 F test/types.test bf816ce73c7dfcfe26b700c19f97ef4050d194ff -F test/types2.test 3555aacf8ed8dc883356e59efc314707e6247a84 +F test/types2.test 1aeb81976841a91eef292723649b5c4fe3bc3cac F test/types3.test 99e009491a54f4dc02c06bdbc0c5eea56ae3e25a F test/unique.test 93f8b2ef5ea51b9495f8d6493429b1fd0f465264 F test/unique2.test 3674e9f2a3f1fbbfd4772ac74b7a97090d0f77d2 F test/unixexcl.test d936ba2b06794018e136418addd59a2354eeae97 F test/unordered.test ca7adce0419e4ca0c50f039885e76ed2c531eda8 F test/update.test 6c68446b8a0a33d522a7c72b320934596a2d7d32 -F test/uri.test 6630ecbdea2aac10df3c89dbae2243f4c2c353e4 +F test/uri.test 3481026f00ade6dfe8adb7acb6e1e47b04369568 +F test/uri2.test 9d3ba7a53ee167572d53a298ee4a5d38ec4a8fb7 F test/userauth01.test e740a2697a7b40d7c5003a7d7edaee16acd349a9 F test/utf16align.test 54cd35a27c005a9b6e7815d887718780b6a462ae F test/vacuum.test ce91c39f7f91a4273bf620efad21086b5aa6ef1d F test/vacuum2.test aa048abee196c16c9ba308465494009057b79f9b F test/vacuum3.test 77ecdd54592b45a0bcb133339f99f1ae0ae94d0d F test/vacuum4.test d3f8ecff345f166911568f397d2432c16d2867d9 -F test/vacuummem.test e53a3fdca4612a99c515e1afe7934728a2383764 +F test/vacuum5.test c87234e8ca4107f349da4edbeda3e4ea5adc93f3 +F test/vacuummem.test 7b42abb3208bd82dd23a7536588396f295a314f2 F test/varint.test ab7b110089a08b9926ed7390e7e97bdefeb74102 F test/veryquick.test 57ab846bacf7b90cf4e9a672721ea5c5b669b661 F test/view.test 765802c7a66d37fabd5ac8e2f2dbe572b43eb9ab @@ -1350,8 +1383,8 @@ F test/vtabB.test 04df5dc531b9f44d9ca65b9c1b79f12b5922a796 F test/vtabC.test 4528f459a13136f982e75614d120aef165f17292 F test/vtabD.test 05b3f1d77117271671089e48719524b676842e96 F test/vtabE.test d5024aa42754962f6bb0afd261681686488e7afe -F test/vtabF.test fd5ad376f5a34fe0891df1f3cddb4fe7c3eb077e -F test/vtabH.test 5f5157a1501d9889ec35c1a1832f69612dd31444 +F test/vtabF.test 1918844c7c902f6a16c8dacf1ec8f84886d6e78b +F test/vtabH.test 97f61b0253260831af6232163f7852e2653baed6 F test/vtabI.test 751b07636700dbdea328e4265b6077ccd6811a3f F test/vtab_alter.test 9e374885248f69e251bdaacf480b04a197f125e5 F test/vtab_err.test 0d4d8eb4def1d053ac7c5050df3024fd47a3fbd8 @@ -1361,7 +1394,7 @@ F test/wal2.test 7effe771f5c194edc3c38922f71c2f4c789d998e F test/wal3.test 2a93004bc0fb2b5c29888964024695bade278ab2 F test/wal4.test 4744e155cd6299c6bd99d3eab1c82f77db9cdb3c F test/wal5.test 9c11da7aeccd83a46d79a556ad11a18d3cb15aa9 -F test/wal6.test a9d6aa635b9d63607dabdc11406f5f96ca986635 +F test/wal6.test b602704e4b066199bc89d91ca9000f335dcf4572 F test/wal64k.test 163655ecd2cb8afef4737cac2a40fdd2eeaf20b8 F test/wal7.test 2ae8f427d240099cc4b2dfef63cff44e2a68a1bd F test/wal8.test d9df3fba4caad5854ed69ed673c68482514203c8 @@ -1398,7 +1431,7 @@ F test/where9.test 729c3ba9b47e8f9f1aab96bae7dad2a524f1d1a2 F test/whereA.test 4d253178d135ec46d1671e440cd8f2b916aa6e6b F test/whereB.test 0def95db3bdec220a731c7e4bec5930327c1d8c5 F test/whereC.test cae295158703cb3fc23bf1a108a9ab730efff0f6 -F test/whereD.test 51366b07cb6f546cd30cc803f7e754f063b940de +F test/whereD.test 711d4df58d6d4fb9b3f5ce040b818564198be002 F test/whereE.test b3a055eef928c992b0a33198a7b8dc10eea5ad2f F test/whereF.test 5b2ba0dbe8074aa13e416b37c753991f0a2492d7 F test/whereG.test dde4c52a97385a55be6a7cd46be8373f0cf35501 @@ -1415,35 +1448,35 @@ F test/win32longpath.test 169c75a3b2e43481f4a62122510210c67b08f26d F test/win32nolock.test ac4f08811a562e45a5755e661f45ca85892bdbbc F test/with1.test cef099a491eac9874f2c28bd2dc86394fb3e47b3 F test/with2.test 2b40da883658eb74ad8ad06afabe11a408e7fb87 -F test/with3.test 511bacdbe41c49cf34f9fd1bd3245fe1575bca98 +F test/with3.test e71604a0e53cba82bc04c703987cb1d6751ec0b6 F test/withM.test 693b61765f2b387b5e3e24a4536e2e82de15ff64 F test/without_rowid1.test 1a7b9bd51b899928d327052df9741d2fe8dbe701 F test/without_rowid2.test af260339f79d13cb220288b67cd287fbcf81ad99 -F test/without_rowid3.test aad4f9d383e199349b6c7e508a778f7dff5dff79 +F test/without_rowid3.test 2724c787a51a5dce09d078453a758117b4b728f1 F test/without_rowid4.test 4e08bcbaee0399f35d58b5581881e7a6243d458a F test/without_rowid5.test 89b1c587bd92a0590e440da33e7666bf4891572a F test/without_rowid6.test 1f99644e6508447fb050f73697350c7ceca3392e -F test/wordcount.c 97856eec21fd00d77da720007b1888c383f63dcf +F test/wordcount.c 06efb84b7c48a4973c2c24ea06c93d00bce24389 F test/zeroblob.test 3857870fe681b8185654414a9bccfde80b62a0fa F test/zerodamage.test e59a56443d6298ecf7435f618f0b27654f0c849e F tool/GetFile.cs a15e08acb5dd7539b75ba23501581d7c2b462cb5 -F tool/GetTclKit.bat 629d87562e0487c386db630033931d12d62e6372 +F tool/GetTclKit.bat f94784e3bdc2f50c539266f5467cbf1f27612cb3 F tool/Replace.cs 02c67258801c2fb5f63231e0ac0f220b4b36ba91 -F tool/addopcodes.tcl a776c2060ccb3367c470779eb2b91268a0a56fde -F tool/build-all-msvc.bat 3e4e4043b53f1aede4308e0d2567bbd773614630 x +F tool/addopcodes.tcl f2e9aba2c2c718624fa14059198456d9e519b925 +F tool/build-all-msvc.bat 018c1b273458a90c8ba633c6f0c5654cfcb138bf x F tool/build-shell.sh 950f47c6174f1eea171319438b93ba67ff5bf367 -F tool/cg_anno.tcl 692ce4b8693d59e3a3de77ca97f4139ecfa641b0 x +F tool/cg_anno.tcl f95b0006c52cf7f0496b506343415b6ee3cdcdd3 x F tool/checkSpacing.c 810e51703529a204fc4e1eb060e9ab663e3c06d2 F tool/dbhash.c a06228aa21ebc4e6ea8daa486601d938499238a5 F tool/extract.c 054069d81b095fbdc189a6f5d4466e40380505e2 F tool/fast_vacuum.c 5ba0d6f5963a0a63bdc42840f678bad75b2ebce1 F tool/fragck.tcl 5265a95126abcf6ab357f7efa544787e5963f439 -F tool/fuzzershell.c 94019b185caceffc9f7c7b678a6489e42bc2aefa +F tool/fuzzershell.c dbf6c26eef936ec78cb0707570de3a4308b2507e F tool/genfkey.README cf68fddd4643bbe3ff8e31b8b6d8b0a1b85e20f4 F tool/genfkey.test 4196a8928b78f51d54ef58e99e99401ab2f0a7e5 F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce -F tool/lemon.c e3aa9ba3469804d7cae13b5e041aab192b7b381a -F tool/lempar.c 147e42a5cd83ce38275fde0d07a5df3330cb9b3b +F tool/lemon.c 5ccba178a8e8a4b21e1c9232944d23973da38ad7 +F tool/lempar.c 320d630b44da693407684c64d9fa91a163419dac F tool/libvers.c caafc3b689638a1d88d44bc5f526c2278760d9b9 F tool/loadfts.c c3c64e4d5e90e8ba41159232c2189dba4be7b862 F tool/logest.c 11346aa019e2e77a00902aa7d0cabd27bd2e8cca @@ -1453,13 +1486,13 @@ F tool/mkmsvcmin.tcl 2f12f7fa8858bbe61cf81820a2da96c79ed1ca8d F tool/mkopcodec.tcl d1b6362bd3aa80d5520d4d6f3765badf01f6c43c F tool/mkopcodeh.tcl a01d2c1d8a6205b03fc635adf3735b4c523befd3 F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e -F tool/mkpragmatab.tcl f0d5bb266d1d388cf86fce5ba01a891e95d72d41 +F tool/mkpragmatab.tcl ebb4bfcd2f8010e0a3934b6118db4b5f2f5edf5c F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 -F tool/mksqlite3c-noext.tcl aa58ea3be311c81821c2cd3209f55e46b07ab656 -F tool/mksqlite3c.tcl 63af8429841f08552e6da1d93b3dee4a93ff8071 -F tool/mksqlite3h.tcl e7b106fc4f29fbc258e8ba9b88d9108332ea2ade +F tool/mksqlite3c-noext.tcl fef88397668ae83166735c41af99d79f56afaabb +F tool/mksqlite3c.tcl 06b2e6a0f21cc0a5d70fbbd136b3e0a96470645e +F tool/mksqlite3h.tcl c006c4e5da57c649b24b689511dcd270dd7b0249 F tool/mksqlite3internalh.tcl eb994013e833359137eb53a55acdad0b5ae1049b -F tool/mkvsix.tcl 4abcaf3267171b2faadaf9b82a0dfbaa6e98f8b7 +F tool/mkvsix.tcl b9e0777a213c23156b6542842c238479e496ebf5 F tool/offsets.c fe4262fdfa378e8f5499a42136d17bf3b98f6091 F tool/omittest.tcl 34d7ac01fe4fd18e3637f64abe12c40eca0f6b97 F tool/opcodesum.tcl 740ed206ba8c5040018988129abbf3089a0ccf4a @@ -1471,18 +1504,18 @@ F tool/run-speed-test.sh f95d19fd669b68c4c38b6b475242841d47c66076 F tool/showdb.c c695a5d5c8110640e0d9fadf5e254da90c79c36e F tool/showjournal.c 5bad7ae8784a43d2b270d953060423b8bd480818 F tool/showlocks.c 9920bcc64f58378ff1118caead34147201f48c68 -F tool/showstat4.c bda40d6e395df7edb6e9ea630784d3d762c35b4b +F tool/showstat4.c b14159aa062f661b394ba37b6b7b94bfb8012ab9 F tool/showwal.c ec79959834f7b21f1e0a2aa52bb7c056d2203977 F tool/soak1.tcl 8d407956e1a45b485a8e072470a3e629a27037fe -F tool/spaceanal.tcl 85d90e6674d8298e3eaf82dbcef3abc2d5317f3e -F tool/speed-check.sh 45d3bf861b009993ff401f0d00e34a4cc937fce4 +F tool/spaceanal.tcl ab7d9bf68062907282a64b3e12ccbfad47193c5a +F tool/speed-check.sh e6ca0695b047af64201ebe0ef452e423f55d78b1 F tool/speedtest.tcl 06c76698485ccf597b9e7dbb1ac70706eb873355 F tool/speedtest16.c ecb6542862151c3e6509bbc00509b234562ae81e F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 7ce07da76b5e745783e703a834417d725b7d45fd F tool/split-sqlite3c.tcl d9be87f1c340285a3e081eb19b4a247981ed290c -F tool/sqldiff.c c965d49bf2677db06103854b47e105484b5b1b84 +F tool/sqldiff.c 3fb48a6c6669d2d2c59a7f072a410dd2583579b4 F tool/srcck1.c 371de5363b70154012955544f86fdee8f6e5326f F tool/stack_usage.tcl f8e71b92cdb099a147dad572375595eae55eca43 F tool/symbols-mingw.sh 4dbcea7e74768305384c9fd2ed2b41bbf9f0414d @@ -1492,7 +1525,7 @@ F tool/varint.c 5d94cb5003db9dbbcbcc5df08d66f16071aee003 F tool/vdbe-compress.tcl 5926c71f9c12d2ab73ef35c29376e756eb68361c F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 -F tool/warnings.sh a98af506df552f3b3c0d904f94e4cdc4e1a6d598 +F tool/warnings.sh d9ded5274b3cb947e98e70d587f71d6263f803b5 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F vsixtest/App.xaml b76d3b48860e7454775c47ea38ffea9c4abe3e85 F vsixtest/App.xaml.cpp c465147f50871165c60ca16955219f6c5812d6d8 @@ -1515,7 +1548,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 2c61b7ab1808d5576a21fea84c3f414db51abfa5 ed406d31ff54ee3de8db91690a966e5c561f8f94 -R 6144d43db42087097a2449fa8c85b94a -U drh -Z 204e3a86f7529a03a274de60935ccb45 +P d9f8918c5b7b6c8540b3f433142e1b4aa4433885 6696cd1878be4bd44a24841b04163e52d847711e +R 47f5758f699f0ce7270fcc21bb40e26e +U dan +Z 398299ab95e9ac138577a5fdad763428 diff --git a/manifest.uuid b/manifest.uuid index 51761e50c2..a10503843d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d9f8918c5b7b6c8540b3f433142e1b4aa4433885 \ No newline at end of file +d0e212d08f82dfb5e42a156b3e2bb03dd8e21258 \ No newline at end of file diff --git a/src/alter.c b/src/alter.c index 806ed49172..067cbb896d 100644 --- a/src/alter.c +++ b/src/alter.c @@ -413,7 +413,7 @@ void sqlite3AlterRenameTable( pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); if( !pTab ) goto exit_rename_table; iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); - zDb = db->aDb[iDb].zName; + zDb = db->aDb[iDb].zDbSName; db->flags |= SQLITE_PreferBuiltin; /* Get a NULL terminated version of the new table name. */ @@ -504,7 +504,7 @@ void sqlite3AlterRenameTable( sqlite3NestedParse(pParse, "UPDATE \"%w\".%s SET " "sql = sqlite_rename_parent(sql, %Q, %Q) " - "WHERE %s;", zDb, SCHEMA_TABLE(iDb), zTabName, zName, zWhere); + "WHERE %s;", zDb, MASTER_NAME, zTabName, zName, zWhere); sqlite3DbFree(db, zWhere); } } @@ -528,7 +528,7 @@ void sqlite3AlterRenameTable( "ELSE name END " "WHERE tbl_name=%Q COLLATE nocase AND " "(type='table' OR type='index' OR type='trigger');", - zDb, SCHEMA_TABLE(iDb), zName, zName, zName, + zDb, MASTER_NAME, zName, zName, zName, #ifndef SQLITE_OMIT_TRIGGER zName, #endif @@ -611,7 +611,7 @@ void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ assert( sqlite3BtreeHoldsAllMutexes(db) ); iDb = sqlite3SchemaToIndex(db, pNew->pSchema); - zDb = db->aDb[iDb].zName; + zDb = db->aDb[iDb].zDbSName; zTab = &pNew->zName[16]; /* Skip the "sqlite_altertab_" prefix on the name */ pCol = &pNew->aCol[pNew->nCol-1]; pDflt = pCol->pDflt; @@ -689,7 +689,7 @@ void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ "UPDATE \"%w\".%s SET " "sql = substr(sql,1,%d) || ', ' || %Q || substr(sql,%d) " "WHERE type = 'table' AND name = %Q", - zDb, SCHEMA_TABLE(iDb), pNew->addColOffset, zCol, pNew->addColOffset+1, + zDb, MASTER_NAME, pNew->addColOffset, zCol, pNew->addColOffset+1, zTab ); sqlite3DbFree(db, zCol); @@ -773,7 +773,7 @@ void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ pNew = (Table*)sqlite3DbMallocZero(db, sizeof(Table)); if( !pNew ) goto exit_begin_add_column; pParse->pNewTable = pNew; - pNew->nRef = 1; + pNew->nTabRef = 1; pNew->nCol = pTab->nCol; assert( pNew->nCol>0 ); nAlloc = (((pNew->nCol-1)/8)*8)+8; @@ -793,7 +793,7 @@ void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ } pNew->pSchema = db->aDb[iDb].pSchema; pNew->addColOffset = pTab->addColOffset; - pNew->nRef = 1; + pNew->nTabRef = 1; /* Begin a transaction and increment the schema cookie. */ sqlite3BeginWriteOperation(pParse, 0, iDb); diff --git a/src/analyze.c b/src/analyze.c index 3a81c93245..c480a0c507 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -210,14 +210,14 @@ static void openStatTable( for(i=0; izName))==0 ){ + if( (pStat = sqlite3FindTable(db, zTab, pDb->zDbSName))==0 ){ if( aTable[i].zCols ){ /* The sqlite_statN table does not exist. Create it. Note that a ** side-effect of the CREATE TABLE statement is to leave the rootpage ** of the new table in register pParse->regRoot. This is important ** because the OpenWrite opcode below will be needing it. */ sqlite3NestedParse(pParse, - "CREATE TABLE %Q.%s(%s)", pDb->zName, zTab, aTable[i].zCols + "CREATE TABLE %Q.%s(%s)", pDb->zDbSName, zTab, aTable[i].zCols ); aRoot[i] = pParse->regRoot; aCreateTbl[i] = OPFLAG_P2ISREG; @@ -232,7 +232,7 @@ static void openStatTable( if( zWhere ){ sqlite3NestedParse(pParse, "DELETE FROM %Q.%s WHERE %s=%Q", - pDb->zName, zTab, zWhereType, zWhere + pDb->zDbSName, zTab, zWhereType, zWhere ); }else{ /* The sqlite_stat[134] table already exists. Delete all rows. */ @@ -994,7 +994,7 @@ static void analyzeOneTable( assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); #ifndef SQLITE_OMIT_AUTHORIZATION if( sqlite3AuthCheck(pParse, SQLITE_ANALYZE, pTab->zName, 0, - db->aDb[iDb].zName ) ){ + db->aDb[iDb].zDbSName ) ){ return; } #endif @@ -1384,7 +1384,7 @@ void sqlite3Analyze(Parse *pParse, Token *pName1, Token *pName2){ /* Form 3: Analyze the fully qualified table name */ iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pTableName); if( iDb>=0 ){ - zDb = db->aDb[iDb].zName; + zDb = db->aDb[iDb].zDbSName; z = sqlite3NameFromToken(db, pTableName); if( z ){ if( (pIdx = sqlite3FindIndex(db, z, zDb))!=0 ){ @@ -1766,7 +1766,9 @@ static int loadStatTbl( sqlite3_finalize(pStmt); return SQLITE_NOMEM_BKPT; } - memcpy(pSample->p, sqlite3_column_blob(pStmt, 4), pSample->n); + if( pSample->n ){ + memcpy(pSample->p, sqlite3_column_blob(pStmt, 4), pSample->n); + } pIdx->nSample++; } rc = sqlite3_finalize(pStmt); @@ -1844,7 +1846,7 @@ int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ /* Load new statistics out of the sqlite_stat1 table */ sInfo.db = db; - sInfo.zDatabase = db->aDb[iDb].zName; + sInfo.zDatabase = db->aDb[iDb].zDbSName; if( sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase)!=0 ){ zSql = sqlite3MPrintf(db, "SELECT tbl,idx,stat FROM %Q.sqlite_stat1", sInfo.zDatabase); diff --git a/src/attach.c b/src/attach.c index ea378a40a2..393c46ccb6 100644 --- a/src/attach.c +++ b/src/attach.c @@ -97,7 +97,7 @@ static void attachFunc( goto attach_error; } for(i=0; inDb; i++){ - char *z = db->aDb[i].zName; + char *z = db->aDb[i].zDbSName; assert( z && zName ); if( sqlite3StrICmp(z, zName)==0 ){ zErrDyn = sqlite3MPrintf(db, "database %s is already in use", zName); @@ -137,6 +137,7 @@ static void attachFunc( rc = sqlite3BtreeOpen(pVfs, zPath, db, &aNew->pBt, 0, flags); sqlite3_free( zPath ); db->nDb++; + db->skipBtreeMutex = 0; if( rc==SQLITE_CONSTRAINT ){ rc = SQLITE_ERROR; zErrDyn = sqlite3MPrintf(db, "database is already attached"); @@ -162,8 +163,8 @@ static void attachFunc( sqlite3BtreeLeave(aNew->pBt); } aNew->safety_level = SQLITE_DEFAULT_SYNCHRONOUS+1; - aNew->zName = sqlite3DbStrDup(db, zName); - if( rc==SQLITE_OK && aNew->zName==0 ){ + aNew->zDbSName = sqlite3DbStrDup(db, zName); + if( rc==SQLITE_OK && aNew->zDbSName==0 ){ rc = SQLITE_NOMEM_BKPT; } @@ -192,7 +193,7 @@ static void attachFunc( case SQLITE_NULL: /* No key specified. Use the key from the main database */ sqlite3CodecGetKey(db, 0, (void**)&zKey, &nKey); - if( nKey>0 || sqlite3BtreeGetOptimalReserve(db->aDb[0].pBt)>0 ){ + if( nKey || sqlite3BtreeGetOptimalReserve(db->aDb[0].pBt)>0 ){ rc = sqlite3CodecAttach(db, db->nDb-1, zKey, nKey); } break; @@ -275,7 +276,7 @@ static void detachFunc( for(i=0; inDb; i++){ pDb = &db->aDb[i]; if( pDb->pBt==0 ) continue; - if( sqlite3StrICmp(pDb->zName, zName)==0 ) break; + if( sqlite3StrICmp(pDb->zDbSName, zName)==0 ) break; } if( i>=db->nDb ){ @@ -325,6 +326,7 @@ static void codeAttach( sqlite3* db = pParse->db; int regArgs; + if( pParse->nErr ) goto attach_end; memset(&sName, 0, sizeof(NameContext)); sName.pParse = pParse; @@ -433,7 +435,7 @@ void sqlite3FixInit( db = pParse->db; assert( db->nDb>iDb ); pFix->pParse = pParse; - pFix->zDb = db->aDb[iDb].zName; + pFix->zDb = db->aDb[iDb].zDbSName; pFix->pSchema = db->aDb[iDb].pSchema; pFix->zType = zType; pFix->pName = pName; @@ -530,7 +532,7 @@ int sqlite3FixExpr( return 1; } } - if( ExprHasProperty(pExpr, EP_TokenOnly) ) break; + if( ExprHasProperty(pExpr, EP_TokenOnly|EP_Leaf) ) break; if( ExprHasProperty(pExpr, EP_xIsSelect) ){ if( sqlite3FixSelect(pFix, pExpr->x.pSelect) ) return 1; }else{ diff --git a/src/auth.c b/src/auth.c index 8a3f8e7d08..77a95d4a8f 100644 --- a/src/auth.c +++ b/src/auth.c @@ -107,9 +107,9 @@ int sqlite3AuthReadCol( const char *zCol, /* Column name */ int iDb /* Index of containing database. */ ){ - sqlite3 *db = pParse->db; /* Database handle */ - char *zDb = db->aDb[iDb].zName; /* Name of attached database */ - int rc; /* Auth callback return code */ + sqlite3 *db = pParse->db; /* Database handle */ + char *zDb = db->aDb[iDb].zDbSName; /* Schema name of attached database */ + int rc; /* Auth callback return code */ if( db->init.busy ) return SQLITE_OK; rc = db->xAuth(db->pAuthArg, SQLITE_READ, zTab,zCol,zDb,pParse->zAuthContext diff --git a/src/backup.c b/src/backup.c index 19c3b2a647..165144d965 100644 --- a/src/backup.c +++ b/src/backup.c @@ -83,22 +83,16 @@ static Btree *findBtree(sqlite3 *pErrorDb, sqlite3 *pDb, const char *zDb){ int i = sqlite3FindDbName(pDb, zDb); if( i==1 ){ - Parse *pParse; + Parse sParse; int rc = 0; - pParse = sqlite3StackAllocZero(pErrorDb, sizeof(*pParse)); - if( pParse==0 ){ - sqlite3ErrorWithMsg(pErrorDb, SQLITE_NOMEM, "out of memory"); - rc = SQLITE_NOMEM_BKPT; - }else{ - pParse->db = pDb; - if( sqlite3OpenTempDatabase(pParse) ){ - sqlite3ErrorWithMsg(pErrorDb, pParse->rc, "%s", pParse->zErrMsg); - rc = SQLITE_ERROR; - } - sqlite3DbFree(pErrorDb, pParse->zErrMsg); - sqlite3ParserReset(pParse); - sqlite3StackFree(pErrorDb, pParse); + memset(&sParse, 0, sizeof(sParse)); + sParse.db = pDb; + if( sqlite3OpenTempDatabase(&sParse) ){ + sqlite3ErrorWithMsg(pErrorDb, sParse.rc, "%s", sParse.zErrMsg); + rc = SQLITE_ERROR; } + sqlite3DbFree(pErrorDb, sParse.zErrMsg); + sqlite3ParserReset(&sParse); if( rc ){ return 0; } @@ -196,7 +190,6 @@ sqlite3_backup *sqlite3_backup_init( p->isAttached = 0; if( 0==p->pSrc || 0==p->pDest - || setDestPgsz(p)==SQLITE_NOMEM || checkReadTransaction(pDestDb, p->pDest)!=SQLITE_OK ){ /* One (or both) of the named databases did not exist or an OOM @@ -384,14 +377,6 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){ rc = SQLITE_OK; } - /* Lock the destination database, if it is not locked already. */ - if( SQLITE_OK==rc && p->bDestLocked==0 - && SQLITE_OK==(rc = sqlite3BtreeBeginTrans(p->pDest, 2)) - ){ - p->bDestLocked = 1; - sqlite3BtreeGetMeta(p->pDest, BTREE_SCHEMA_VERSION, &p->iDestSchema); - } - /* If there is no open read-transaction on the source database, open ** one now. If a transaction is opened here, then it will be closed ** before this function exits. @@ -401,6 +386,24 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){ bCloseTrans = 1; } + /* If the destination database has not yet been locked (i.e. if this + ** is the first call to backup_step() for the current backup operation), + ** try to set its page size to the same as the source database. This + ** is especially important on ZipVFS systems, as in that case it is + ** not possible to create a database file that uses one page size by + ** writing to it with another. */ + if( p->bDestLocked==0 && rc==SQLITE_OK && setDestPgsz(p)==SQLITE_NOMEM ){ + rc = SQLITE_NOMEM; + } + + /* Lock the destination database, if it is not locked already. */ + if( SQLITE_OK==rc && p->bDestLocked==0 + && SQLITE_OK==(rc = sqlite3BtreeBeginTrans(p->pDest, 2)) + ){ + p->bDestLocked = 1; + sqlite3BtreeGetMeta(p->pDest, BTREE_SCHEMA_VERSION, &p->iDestSchema); + } + /* Do not allow backup if the destination database is in WAL mode ** and the page sizes are different between source and destination */ pgszSrc = sqlite3BtreeGetPageSize(p->pSrc); diff --git a/src/bitvec.c b/src/bitvec.c index 9d13ba9185..bd4a09429b 100644 --- a/src/bitvec.c +++ b/src/bitvec.c @@ -293,7 +293,7 @@ u32 sqlite3BitvecSize(Bitvec *p){ return p->iSize; } -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE /* ** Let V[] be an array of unsigned characters sufficient to hold ** up to N bits. Let I be an integer between 0 and N. 0<=Imutex) ); for(i=0; inDb; i++){ p = db->aDb[i].pBt; - if( p ) sqlite3BtreeEnter(p); + if( p && p->sharable ){ + sqlite3BtreeEnter(p); + skipOk = 0; + } } + db->skipBtreeMutex = skipOk; } -void sqlite3BtreeLeaveAll(sqlite3 *db){ +void sqlite3BtreeEnterAll(sqlite3 *db){ + if( db->skipBtreeMutex==0 ) btreeEnterAll(db); +} +static void SQLITE_NOINLINE btreeLeaveAll(sqlite3 *db){ int i; Btree *p; assert( sqlite3_mutex_held(db->mutex) ); @@ -201,6 +209,9 @@ void sqlite3BtreeLeaveAll(sqlite3 *db){ if( p ) sqlite3BtreeLeave(p); } } +void sqlite3BtreeLeaveAll(sqlite3 *db){ + if( db->skipBtreeMutex==0 ) btreeLeaveAll(db); +} #ifndef NDEBUG /* diff --git a/src/btree.c b/src/btree.c index a783d94fc9..b05d911865 100644 --- a/src/btree.c +++ b/src/btree.c @@ -828,7 +828,7 @@ static int saveCursorKey(BtCursor *pCur){ pCur->nKey = sqlite3BtreePayloadSize(pCur); pKey = sqlite3Malloc( pCur->nKey ); if( pKey ){ - rc = sqlite3BtreeKey(pCur, 0, (int)pCur->nKey, pKey); + rc = sqlite3BtreePayload(pCur, 0, (int)pCur->nKey, pKey); if( rc==SQLITE_OK ){ pCur->pKey = pKey; }else{ @@ -959,26 +959,23 @@ static int btreeMoveto( ){ int rc; /* Status code */ UnpackedRecord *pIdxKey; /* Unpacked index key */ - char aSpace[200]; /* Temp space for pIdxKey - to avoid a malloc */ - char *pFree = 0; if( pKey ){ assert( nKey==(i64)(int)nKey ); - pIdxKey = sqlite3VdbeAllocUnpackedRecord( - pCur->pKeyInfo, aSpace, sizeof(aSpace), &pFree - ); + pIdxKey = sqlite3VdbeAllocUnpackedRecord(pCur->pKeyInfo); if( pIdxKey==0 ) return SQLITE_NOMEM_BKPT; sqlite3VdbeRecordUnpack(pCur->pKeyInfo, (int)nKey, pKey, pIdxKey); if( pIdxKey->nField==0 ){ - sqlite3DbFree(pCur->pKeyInfo->db, pFree); - return SQLITE_CORRUPT_BKPT; + rc = SQLITE_CORRUPT_BKPT; + goto moveto_done; } }else{ pIdxKey = 0; } rc = sqlite3BtreeMovetoUnpacked(pCur, pIdxKey, nKey, bias, pRes); - if( pFree ){ - sqlite3DbFree(pCur->pKeyInfo->db, pFree); +moveto_done: + if( pIdxKey ){ + sqlite3DbFree(pCur->pKeyInfo->db, pIdxKey); } return rc; } @@ -1808,8 +1805,11 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ if( data[iPtr+1]==0 && data[iPtr]==0 ){ iFreeBlk = 0; /* Shortcut for the case when the freelist is empty */ }else{ - while( (iFreeBlk = get2byte(&data[iPtr]))>0 && iFreeBlkiLast ) return SQLITE_CORRUPT_BKPT; @@ -1943,7 +1943,7 @@ static int btreeInitPage(MemPage *pPage){ assert( pPage->aData == sqlite3PagerGetData(pPage->pDbPage) ); if( !pPage->isInit ){ - u16 pc; /* Address of a freeblock within pPage->aData[] */ + int pc; /* Address of a freeblock within pPage->aData[] */ u8 hdr; /* Offset to beginning of page header */ u8 *data; /* Equal to pPage->aData */ BtShared *pBt; /* The main btree structure */ @@ -2023,25 +2023,30 @@ static int btreeInitPage(MemPage *pPage){ ** freeblocks. */ pc = get2byte(&data[hdr+1]); nFree = data[hdr+7] + top; /* Init nFree to non-freeblock free space */ - while( pc>0 ){ - u16 next, size; - if( pciCellLast ){ + if( pc>0 ){ + u32 next, size; + if( pc0 && next<=pc+size+3) || pc+size>usableSize ){ - /* Free blocks must be in ascending order. And the last byte of - ** the free-block must lie on the database page. */ - return SQLITE_CORRUPT_BKPT; + while( 1 ){ + if( pc>iCellLast ){ + return SQLITE_CORRUPT_BKPT; /* Freeblock off the end of the page */ + } + next = get2byte(&data[pc]); + size = get2byte(&data[pc+2]); + nFree = nFree + size; + if( next<=pc+size+3 ) break; + pc = next; + } + if( next>0 ){ + return SQLITE_CORRUPT_BKPT; /* Freeblock not in ascending order */ + } + if( pc+size>(unsigned int)usableSize ){ + return SQLITE_CORRUPT_BKPT; /* Last freeblock extends past page end */ } - nFree = nFree + size; - pc = next; } /* At this point, nFree contains the sum of the offset to the start @@ -2482,7 +2487,7 @@ int sqlite3BtreeOpen( goto btree_open_out; } rc = sqlite3PagerOpen(pVfs, &pBt->pPager, zFilename, - EXTRA_SIZE, flags, vfsFlags, pageReinit); + sizeof(MemPage), flags, vfsFlags, pageReinit); if( rc==SQLITE_OK ){ sqlite3PagerSetMmapLimit(pBt->pPager, db->szMmap); rc = sqlite3PagerReadFileheader(pBt->pPager,sizeof(zDbHeader),zDbHeader); @@ -2595,12 +2600,14 @@ int sqlite3BtreeOpen( btree_open_out: if( rc!=SQLITE_OK ){ if( pBt && pBt->pPager ){ - sqlite3PagerClose(pBt->pPager); + sqlite3PagerClose(pBt->pPager, 0); } sqlite3_free(pBt); sqlite3_free(p); *ppBtree = 0; }else{ + sqlite3_file *pFile; + /* If the B-Tree was successfully opened, set the pager-cache size to the ** default value. Except, when opening on an existing shared pager-cache, ** do not change the pager-cache size. @@ -2608,6 +2615,11 @@ btree_open_out: if( sqlite3BtreeSchema(p, 0, 0)==0 ){ sqlite3PagerSetCachesize(p->pBt->pPager, SQLITE_DEFAULT_CACHE_SIZE); } + + pFile = sqlite3PagerFile(pBt->pPager); + if( pFile->pMethods ){ + sqlite3OsFileControlHint(pFile, SQLITE_FCNTL_PDB, (void*)&pBt->db); + } } if( mutexOpen ){ assert( sqlite3_mutex_held(mutexOpen) ); @@ -2737,7 +2749,7 @@ int sqlite3BtreeClose(Btree *p){ ** Clean out and delete the BtShared object. */ assert( !pBt->pCursor ); - sqlite3PagerClose(pBt->pPager); + sqlite3PagerClose(pBt->pPager, p->db); if( pBt->xFreeSchema && pBt->pSchema ){ pBt->xFreeSchema(pBt->pSchema); } @@ -3499,14 +3511,11 @@ static int setChildPtrmaps(MemPage *pPage){ int nCell; /* Number of cells in page pPage */ int rc; /* Return code */ BtShared *pBt = pPage->pBt; - u8 isInitOrig = pPage->isInit; Pgno pgno = pPage->pgno; assert( sqlite3_mutex_held(pPage->pBt->mutex) ); rc = btreeInitPage(pPage); - if( rc!=SQLITE_OK ){ - goto set_child_ptrmaps_out; - } + if( rc!=SQLITE_OK ) return rc; nCell = pPage->nCell; for(i=0; iisInit = isInitOrig; return rc; } @@ -3554,7 +3561,6 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){ } put4byte(pPage->aData, iTo); }else{ - u8 isInitOrig = pPage->isInit; int i; int nCell; int rc; @@ -3590,8 +3596,6 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){ } put4byte(&pPage->aData[pPage->hdrOffset+8], iTo); } - - pPage->isInit = isInitOrig; } return SQLITE_OK; } @@ -4664,6 +4668,10 @@ int sqlite3BtreeCursorIsValid(BtCursor *pCur){ return pCur && pCur->eState==CURSOR_VALID; } #endif /* NDEBUG */ +int sqlite3BtreeCursorIsValidNN(BtCursor *pCur){ + assert( pCur!=0 ); + return pCur->eState==CURSOR_VALID; +} /* ** Return the value of the integer key or "rowid" for a table btree. @@ -4994,7 +5002,7 @@ static int accessPayload( && (bEnd || a==ovflSize) /* (6) */ && pBt->inTransaction==TRANS_READ /* (4) */ && (fd = sqlite3PagerFile(pBt->pPager))->pMethods /* (3) */ - && pBt->pPage1->aData[19]==0x01 /* (5) */ + && 0==sqlite3PagerUseWal(pBt->pPager) /* (5) */ && &pBuf[-4]>=pBufStart /* (7) */ ){ u8 aSave[4]; @@ -5033,43 +5041,35 @@ static int accessPayload( } /* -** Read part of the key associated with cursor pCur. Exactly -** "amt" bytes will be transferred into pBuf[]. The transfer +** Read part of the payload for the row at which that cursor pCur is currently +** pointing. "amt" bytes will be transferred into pBuf[]. The transfer ** begins at "offset". ** -** The caller must ensure that pCur is pointing to a valid row -** in the table. +** pCur can be pointing to either a table or an index b-tree. +** If pointing to a table btree, then the content section is read. If +** pCur is pointing to an index b-tree then the key section is read. +** +** For sqlite3BtreePayload(), the caller must ensure that pCur is pointing +** to a valid row in the table. For sqlite3BtreePayloadChecked(), the +** cursor might be invalid or might need to be restored before being read. ** ** Return SQLITE_OK on success or an error code if anything goes ** wrong. An error is returned if "offset+amt" is larger than ** the available payload. */ -int sqlite3BtreeKey(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){ +int sqlite3BtreePayload(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){ assert( cursorHoldsMutex(pCur) ); assert( pCur->eState==CURSOR_VALID ); assert( pCur->iPage>=0 && pCur->apPage[pCur->iPage] ); assert( pCur->aiIdx[pCur->iPage]apPage[pCur->iPage]->nCell ); return accessPayload(pCur, offset, amt, (unsigned char*)pBuf, 0); } - -/* -** Read part of the data associated with cursor pCur. Exactly -** "amt" bytes will be transfered into pBuf[]. The transfer -** begins at "offset". -** -** Return SQLITE_OK on success or an error code if anything goes -** wrong. An error is returned if "offset+amt" is larger than -** the available payload. -*/ -int sqlite3BtreeData(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){ - int rc; - #ifndef SQLITE_OMIT_INCRBLOB +int sqlite3BtreePayloadChecked(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){ + int rc; if ( pCur->eState==CURSOR_INVALID ){ return SQLITE_ABORT; } -#endif - assert( cursorOwnsBtShared(pCur) ); rc = restoreCursorPosition(pCur); if( rc==SQLITE_OK ){ @@ -5080,6 +5080,7 @@ int sqlite3BtreeData(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){ } return rc; } +#endif /* SQLITE_OMIT_INCRBLOB */ /* ** Return a pointer to payload information from the entry that the @@ -5250,9 +5251,12 @@ static int moveToRoot(BtCursor *pCur){ } if( pCur->iPage>=0 ){ - while( pCur->iPage ){ - assert( pCur->apPage[pCur->iPage]!=0 ); - releasePageNotNull(pCur->apPage[pCur->iPage--]); + if( pCur->iPage ){ + do{ + assert( pCur->apPage[pCur->iPage]!=0 ); + releasePageNotNull(pCur->apPage[pCur->iPage--]); + }while( pCur->iPage); + goto skip_init; } }else if( pCur->pgnoRoot==0 ){ pCur->eState = CURSOR_INVALID; @@ -5263,7 +5267,7 @@ static int moveToRoot(BtCursor *pCur){ 0, pCur->curPagerFlags); if( rc!=SQLITE_OK ){ pCur->eState = CURSOR_INVALID; - return rc; + return rc; } pCur->iPage = 0; pCur->curIntKey = pCur->apPage[0]->intKey; @@ -5286,10 +5290,12 @@ static int moveToRoot(BtCursor *pCur){ return SQLITE_CORRUPT_BKPT; } +skip_init: pCur->aiIdx[0] = 0; pCur->info.nSize = 0; pCur->curFlags &= ~(BTCF_AtLast|BTCF_ValidNKey|BTCF_ValidOvfl); + pRoot = pCur->apPage[0]; if( pRoot->nCell>0 ){ pCur->eState = CURSOR_VALID; }else if( !pRoot->leaf ){ @@ -5546,16 +5552,16 @@ int sqlite3BtreeMovetoUnpacked( if( lwr>upr ){ c = +1; break; } }else{ assert( nCellKey==intKey ); - pCur->curFlags |= BTCF_ValidNKey; - pCur->info.nKey = nCellKey; pCur->aiIdx[pCur->iPage] = (u16)idx; if( !pPage->leaf ){ lwr = idx; goto moveto_next_layer; }else{ + pCur->curFlags |= BTCF_ValidNKey; + pCur->info.nKey = nCellKey; + pCur->info.nSize = 0; *pRes = 0; - rc = SQLITE_OK; - goto moveto_finish; + return SQLITE_OK; } } assert( lwr+upr>=0 ); @@ -5666,7 +5672,7 @@ moveto_next_layer: } moveto_finish: pCur->info.nSize = 0; - pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); + assert( (pCur->curFlags & BTCF_ValidOvfl)==0 ); return rc; } @@ -5864,7 +5870,7 @@ static SQLITE_NOINLINE int btreePrevious(BtCursor *pCur, int *pRes){ moveToParent(pCur); } assert( pCur->info.nSize==0 ); - assert( (pCur->curFlags & (BTCF_ValidNKey|BTCF_ValidOvfl))==0 ); + assert( (pCur->curFlags & (BTCF_ValidOvfl))==0 ); pCur->aiIdx[pCur->iPage]--; pPage = pCur->apPage[pCur->iPage]; @@ -6390,30 +6396,28 @@ static void freePage(MemPage *pPage, int *pRC){ static int clearCell( MemPage *pPage, /* The page that contains the Cell */ unsigned char *pCell, /* First byte of the Cell */ - u16 *pnSize /* Write the size of the Cell here */ + CellInfo *pInfo /* Size information about the cell */ ){ BtShared *pBt = pPage->pBt; - CellInfo info; Pgno ovflPgno; int rc; int nOvfl; u32 ovflPageSize; assert( sqlite3_mutex_held(pPage->pBt->mutex) ); - pPage->xParseCell(pPage, pCell, &info); - *pnSize = info.nSize; - if( info.nLocal==info.nPayload ){ + pPage->xParseCell(pPage, pCell, pInfo); + if( pInfo->nLocal==pInfo->nPayload ){ return SQLITE_OK; /* No overflow pages. Return without doing anything */ } - if( pCell+info.nSize-1 > pPage->aData+pPage->maskPage ){ + if( pCell+pInfo->nSize-1 > pPage->aData+pPage->maskPage ){ return SQLITE_CORRUPT_BKPT; /* Cell extends past end of page */ } - ovflPgno = get4byte(pCell + info.nSize - 4); + ovflPgno = get4byte(pCell + pInfo->nSize - 4); assert( pBt->usableSize > 4 ); ovflPageSize = pBt->usableSize - 4; - nOvfl = (info.nPayload - info.nLocal + ovflPageSize - 1)/ovflPageSize; + nOvfl = (pInfo->nPayload - pInfo->nLocal + ovflPageSize - 1)/ovflPageSize; assert( nOvfl>0 || - (CORRUPT_DB && (info.nPayload + ovflPageSize)nPayload + ovflPageSize)nKey); }else{ - assert( pX->nData==0 ); - assert( pX->nZero==0 ); assert( pX->nKey<=0x7fffffff && pX->pKey!=0 ); nSrc = nPayload = (int)pX->nKey; pSrc = pX->pKey; @@ -6654,7 +6656,6 @@ static void dropCell(MemPage *pPage, int idx, int sz, int *pRC){ int hdr; /* Beginning of the header. 0 most pages. 100 page 1 */ if( *pRC ) return; - assert( idx>=0 && idxnCell ); assert( CORRUPT_DB || sz==cellSize(pPage, idx) ); assert( sqlite3PagerIswriteable(pPage->pDbPage) ); @@ -6738,7 +6739,10 @@ static void insertCell( put4byte(pCell, iChild); } j = pPage->nOverflow++; - assert( j<(int)(sizeof(pPage->apOvfl)/sizeof(pPage->apOvfl[0])) ); + /* Comparison against ArraySize-1 since we hold back one extra slot + ** as a contingency. In other words, never need more than 3 overflow + ** slots but 4 are allocated, just to be safe. */ + assert( j < ArraySize(pPage->apOvfl)-1 ); pPage->apOvfl[j] = pCell; pPage->aiOvfl[j] = (u16)i; @@ -7477,7 +7481,7 @@ static int balance_nonroot( nMaxCells += 1+apOld[i]->nCell+apOld[i]->nOverflow; if( (i--)==0 ) break; - if( i+nxDiv==pParent->aiOvfl[0] && pParent->nOverflow ){ + if( pParent->nOverflow && i+nxDiv==pParent->aiOvfl[0] ){ apDiv[i] = pParent->apOvfl[0]; pgno = get4byte(apDiv[i]); szNew[i] = pParent->xCellSize(pParent, apDiv[i]); @@ -8331,17 +8335,19 @@ static int balance(BtCursor *pCur){ ** pX.pData,nData,nZero fields must be zero. ** ** If the seekResult parameter is non-zero, then a successful call to -** MovetoUnpacked() to seek cursor pCur to (pKey, nKey) has already -** been performed. seekResult is the search result returned (a negative -** number if pCur points at an entry that is smaller than (pKey, nKey), or -** a positive value if pCur points at an entry that is larger than -** (pKey, nKey)). +** MovetoUnpacked() to seek cursor pCur to (pKey,nKey) has already +** been performed. In other words, if seekResult!=0 then the cursor +** is currently pointing to a cell that will be adjacent to the cell +** to be inserted. If seekResult<0 then pCur points to a cell that is +** smaller then (pKey,nKey). If seekResult>0 then pCur points to a cell +** that is larger than (pKey,nKey). ** -** If the seekResult parameter is non-zero, then the caller guarantees that -** cursor pCur is pointing at the existing copy of a row that is to be -** overwritten. If the seekResult parameter is 0, then cursor pCur may -** point to any entry or to no entry at all and so this function has to seek -** the cursor before the new key can be inserted. +** If seekResult==0, that means pCur is pointing at some unknown location. +** In that case, this routine must seek the cursor to the correct insertion +** point for (pKey,nKey) before doing the insertion. For index btrees, +** if pX->nMem is non-zero, then pX->aMem contains pointers to the unpacked +** key values and pX->aMem can be used instead of pX->pKey to avoid having +** to decode the key. */ int sqlite3BtreeInsert( BtCursor *pCur, /* Insert data into the table of this cursor */ @@ -8402,15 +8408,30 @@ int sqlite3BtreeInsert( /* If the cursor is currently on the last row and we are appending a ** new row onto the end, set the "loc" to avoid an unnecessary ** btreeMoveto() call */ - if( (pCur->curFlags&BTCF_ValidNKey)!=0 && pX->nKey>0 - && pCur->info.nKey==pX->nKey-1 ){ - loc = -1; + if( (pCur->curFlags&BTCF_ValidNKey)!=0 && pX->nKey==pCur->info.nKey ){ + loc = 0; + }else if( (pCur->curFlags&BTCF_ValidNKey)!=0 && pX->nKey>0 + && pCur->info.nKey==pX->nKey-1 ){ + loc = -1; }else if( loc==0 ){ rc = sqlite3BtreeMovetoUnpacked(pCur, 0, pX->nKey, appendBias, &loc); if( rc ) return rc; } }else if( loc==0 ){ - rc = btreeMoveto(pCur, pX->pKey, pX->nKey, appendBias, &loc); + if( pX->nMem ){ + UnpackedRecord r; + r.pKeyInfo = pCur->pKeyInfo; + r.aMem = pX->aMem; + r.nField = pX->nMem; + r.default_rc = 0; + r.errCode = 0; + r.r1 = 0; + r.r2 = 0; + r.eqSeen = 0; + rc = sqlite3BtreeMovetoUnpacked(pCur, &r, 0, appendBias, &loc); + }else{ + rc = btreeMoveto(pCur, pX->pKey, pX->nKey, appendBias, &loc); + } if( rc ) return rc; } assert( pCur->eState==CURSOR_VALID || (pCur->eState==CURSOR_INVALID && loc) ); @@ -8431,7 +8452,7 @@ int sqlite3BtreeInsert( assert( szNew <= MX_CELL_SIZE(pBt) ); idx = pCur->aiIdx[pCur->iPage]; if( loc==0 ){ - u16 szOld; + CellInfo info; assert( idxnCell ); rc = sqlite3PagerWrite(pPage->pDbPage); if( rc ){ @@ -8441,8 +8462,19 @@ int sqlite3BtreeInsert( if( !pPage->leaf ){ memcpy(newCell, oldCell, 4); } - rc = clearCell(pPage, oldCell, &szOld); - dropCell(pPage, idx, szOld, &rc); + rc = clearCell(pPage, oldCell, &info); + if( info.nSize==szNew && info.nLocal==info.nPayload ){ + /* Overwrite the old cell with the new if they are the same size. + ** We could also try to do this if the old cell is smaller, then add + ** the leftover space to the free list. But experiments show that + ** doing that is no faster then skipping this optimization and just + ** calling dropCell() and insertCell(). */ + assert( rc==SQLITE_OK ); /* clearCell never fails when nLocal==nPayload */ + if( oldCell+szNew > pPage->aDataEnd ) return SQLITE_CORRUPT_BKPT; + memcpy(oldCell, newCell, szNew); + return SQLITE_OK; + } + dropCell(pPage, idx, info.nSize, &rc); if( rc ) goto end_insert; }else if( loc<0 && pPage->nCell>0 ){ assert( pPage->leaf ); @@ -8518,7 +8550,7 @@ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ unsigned char *pCell; /* Pointer to cell to delete */ int iCellIdx; /* Index of cell to delete */ int iCellDepth; /* Depth of node containing pCell */ - u16 szCell; /* Size of the cell being deleted */ + CellInfo info; /* Size of the cell being deleted */ int bSkipnext = 0; /* Leaf cursor in SKIPNEXT state */ u8 bPreserve = flags & BTREE_SAVEPOSITION; /* Keep cursor valid */ @@ -8590,8 +8622,8 @@ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ ** itself from within the page. */ rc = sqlite3PagerWrite(pPage->pDbPage); if( rc ) return rc; - rc = clearCell(pPage, pCell, &szCell); - dropCell(pPage, iCellIdx, szCell, &rc); + rc = clearCell(pPage, pCell, &info); + dropCell(pPage, iCellIdx, info.nSize, &rc); if( rc ) return rc; /* If the cell deleted was not located on a leaf page, then the cursor @@ -8841,7 +8873,7 @@ static int clearDatabasePage( unsigned char *pCell; int i; int hdr; - u16 szCell; + CellInfo info; assert( sqlite3_mutex_held(pBt->mutex) ); if( pgno>btreePagecount(pBt) ){ @@ -8861,7 +8893,7 @@ static int clearDatabasePage( rc = clearDatabasePage(pBt, get4byte(pCell), 1, pnChange); if( rc ) goto cleardatabasepage_out; } - rc = clearCell(pPage, pCell, &szCell); + rc = clearCell(pPage, pCell, &info); if( rc ) goto cleardatabasepage_out; } if( !pPage->leaf ){ @@ -8952,27 +8984,7 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){ assert( sqlite3BtreeHoldsMutex(p) ); assert( p->inTrans==TRANS_WRITE ); - - /* It is illegal to drop a table if any cursors are open on the - ** database. This is because in auto-vacuum mode the backend may - ** need to move another root-page to fill a gap left by the deleted - ** root page. If an open cursor was using this page a problem would - ** occur. - ** - ** This error is caught long before control reaches this point. - */ - if( NEVER(pBt->pCursor) ){ - sqlite3ConnectionBlocked(p->db, pBt->pCursor->pBtree->db); - return SQLITE_LOCKED_SHAREDCACHE; - } - - /* - ** It is illegal to drop the sqlite_master table on page 1. But again, - ** this error is caught long before reaching this point. - */ - if( NEVER(iTable<2) ){ - return SQLITE_CORRUPT_BKPT; - } + assert( iTable>=2 ); rc = btreeGetPage(pBt, (Pgno)iTable, &pPage, 0); if( rc ) return rc; @@ -9882,7 +9894,7 @@ int sqlite3BtreeCheckpoint(Btree *p, int eMode, int *pnLog, int *pnCkpt){ if( pBt->inTransaction!=TRANS_NONE ){ rc = SQLITE_LOCKED; }else{ - rc = sqlite3PagerCheckpoint(pBt->pPager, eMode, pnLog, pnCkpt); + rc = sqlite3PagerCheckpoint(pBt->pPager, p->db, eMode, pnLog, pnCkpt); } sqlite3BtreeLeave(p); } diff --git a/src/btree.h b/src/btree.h index bffaf6da43..a2bd57f1ac 100644 --- a/src/btree.h +++ b/src/btree.h @@ -90,7 +90,9 @@ int sqlite3BtreeIsInReadTrans(Btree*); int sqlite3BtreeIsInBackup(Btree*); void *sqlite3BtreeSchema(Btree *, int, void(*)(void *)); int sqlite3BtreeSchemaLocked(Btree *pBtree); +#ifndef SQLITE_OMIT_SHARED_CACHE int sqlite3BtreeLockTable(Btree *pBtree, int iTab, u8 isWriteLock); +#endif int sqlite3BtreeSavepoint(Btree *, int, int); const char *sqlite3BtreeGetFilename(Btree *); @@ -273,6 +275,8 @@ struct BtreePayload { const void *pKey; /* Key content for indexes. NULL for tables */ sqlite3_int64 nKey; /* Size of pKey for indexes. PRIMARY KEY for tabs */ const void *pData; /* Data for tables. NULL for indexes */ + struct Mem *aMem; /* First of nMem value in the unpacked pKey */ + u16 nMem; /* Number of aMem[] value. Might be zero */ int nData; /* Size of pData. 0 if none. */ int nZero; /* Extra zero data appended after pData,nData */ }; @@ -285,16 +289,18 @@ int sqlite3BtreeNext(BtCursor*, int *pRes); int sqlite3BtreeEof(BtCursor*); int sqlite3BtreePrevious(BtCursor*, int *pRes); i64 sqlite3BtreeIntegerKey(BtCursor*); -int sqlite3BtreeKey(BtCursor*, u32 offset, u32 amt, void*); +int sqlite3BtreePayload(BtCursor*, u32 offset, u32 amt, void*); const void *sqlite3BtreePayloadFetch(BtCursor*, u32 *pAmt); u32 sqlite3BtreePayloadSize(BtCursor*); -int sqlite3BtreeData(BtCursor*, u32 offset, u32 amt, void*); char *sqlite3BtreeIntegrityCheck(Btree*, int *aRoot, int nRoot, int, int*); struct Pager *sqlite3BtreePager(Btree*); +#ifndef SQLITE_OMIT_INCRBLOB +int sqlite3BtreePayloadChecked(BtCursor*, u32 offset, u32 amt, void*); int sqlite3BtreePutData(BtCursor*, u32 offset, u32 amt, void*); void sqlite3BtreeIncrblobCursor(BtCursor *); +#endif void sqlite3BtreeClearCursor(BtCursor *); int sqlite3BtreeSetVersion(Btree *pBt, int iVersion); int sqlite3BtreeCursorHasHint(BtCursor*, unsigned int mask); @@ -306,6 +312,7 @@ int sqlite3BtreeExclusiveLock(Btree *pBt); #ifndef NDEBUG int sqlite3BtreeCursorIsValid(BtCursor*); #endif +int sqlite3BtreeCursorIsValidNN(BtCursor*); #ifndef SQLITE_OMIT_BTREECOUNT int sqlite3BtreeCount(BtCursor *, i64 *); diff --git a/src/btreeInt.h b/src/btreeInt.h index 96bc232e25..6b2d96e9ca 100644 --- a/src/btreeInt.h +++ b/src/btreeInt.h @@ -260,37 +260,39 @@ typedef struct BtreePtrmap BtreePtrmap; #define PTF_LEAF 0x08 /* -** As each page of the file is loaded into memory, an instance of the following -** structure is appended and initialized to zero. This structure stores -** information about the page that is decoded from the raw file page. +** An instance of this object stores information about each a single database +** page that has been loaded into memory. The information in this object +** is derived from the raw on-disk page content. ** -** The pParent field points back to the parent page. This allows us to -** walk up the BTree from any leaf to the root. Care must be taken to -** unref() the parent page pointer when this page is no longer referenced. -** The pageDestructor() routine handles that chore. +** As each database page is loaded into memory, the pager allocats an +** instance of this object and zeros the first 8 bytes. (This is the +** "extra" information associated with each page of the pager.) ** ** Access to all fields of this structure is controlled by the mutex ** stored in MemPage.pBt->mutex. */ struct MemPage { u8 isInit; /* True if previously initialized. MUST BE FIRST! */ - u8 nOverflow; /* Number of overflow cell bodies in aCell[] */ + u8 bBusy; /* Prevent endless loops on corrupt database files */ u8 intKey; /* True if table b-trees. False for index b-trees */ u8 intKeyLeaf; /* True if the leaf of an intKey table */ + Pgno pgno; /* Page number for this page */ + /* Only the first 8 bytes (above) are zeroed by pager.c when a new page + ** is allocated. All fields that follow must be initialized before use */ u8 leaf; /* True if a leaf page */ u8 hdrOffset; /* 100 for page 1. 0 otherwise */ u8 childPtrSize; /* 0 if leaf==1. 4 if leaf==0 */ u8 max1bytePayload; /* min(maxLocal,127) */ - u8 bBusy; /* Prevent endless loops on corrupt database files */ + u8 nOverflow; /* Number of overflow cell bodies in aCell[] */ u16 maxLocal; /* Copy of BtShared.maxLocal or BtShared.maxLeaf */ u16 minLocal; /* Copy of BtShared.minLocal or BtShared.minLeaf */ u16 cellOffset; /* Index in aData of first cell pointer */ u16 nFree; /* Number of free bytes on the page */ u16 nCell; /* Number of cells on this page, local and ovfl */ u16 maskPage; /* Mask for page offset */ - u16 aiOvfl[5]; /* Insert the i-th overflow cell before the aiOvfl-th + u16 aiOvfl[4]; /* Insert the i-th overflow cell before the aiOvfl-th ** non-overflow cell */ - u8 *apOvfl[5]; /* Pointers to the body of overflow cells */ + u8 *apOvfl[4]; /* Pointers to the body of overflow cells */ BtShared *pBt; /* Pointer to BtShared that this page is part of */ u8 *aData; /* Pointer to disk image of the page data */ u8 *aDataEnd; /* One byte past the end of usable data */ @@ -299,16 +301,8 @@ struct MemPage { DbPage *pDbPage; /* Pager page handle */ u16 (*xCellSize)(MemPage*,u8*); /* cellSizePtr method */ void (*xParseCell)(MemPage*,u8*,CellInfo*); /* btreeParseCell method */ - Pgno pgno; /* Page number for this page */ }; -/* -** The in-memory image of a disk page has the auxiliary information appended -** to the end. EXTRA_SIZE is the number of bytes of space needed to hold -** that extra information. -*/ -#define EXTRA_SIZE sizeof(MemPage) - /* ** A linked list of the following structures is stored at BtShared.pLock. ** Locks are added (or upgraded from READ_LOCK to WRITE_LOCK) when a cursor diff --git a/src/build.c b/src/build.c index 744744a98b..e412a44bc1 100644 --- a/src/build.c +++ b/src/build.c @@ -30,10 +30,10 @@ ** codeTableLocks() functions. */ struct TableLock { - int iDb; /* The database containing the table to be locked */ - int iTab; /* The root page of the table to be locked */ - u8 isWriteLock; /* True for write lock. False for a read lock */ - const char *zName; /* Name of the table */ + int iDb; /* The database containing the table to be locked */ + int iTab; /* The root page of the table to be locked */ + u8 isWriteLock; /* True for write lock. False for a read lock */ + const char *zLockName; /* Name of the table */ }; /* @@ -59,6 +59,8 @@ void sqlite3TableLock( TableLock *p; assert( iDb>=0 ); + if( iDb==1 ) return; + if( !sqlite3BtreeSharable(pParse->db->aDb[iDb].pBt) ) return; for(i=0; inTableLock; i++){ p = &pToplevel->aTableLock[i]; if( p->iDb==iDb && p->iTab==iTab ){ @@ -75,7 +77,7 @@ void sqlite3TableLock( p->iDb = iDb; p->iTab = iTab; p->isWriteLock = isWriteLock; - p->zName = zName; + p->zLockName = zName; }else{ pToplevel->nTableLock = 0; sqlite3OomFault(pToplevel->db); @@ -97,7 +99,7 @@ static void codeTableLocks(Parse *pParse){ TableLock *p = &pParse->aTableLock[i]; int p1 = p->iDb; sqlite3VdbeAddOp4(pVdbe, OP_TableLock, p1, p->iTab, p->isWriteLock, - p->zName, P4_STATIC); + p->zLockName, P4_STATIC); } } #else @@ -146,15 +148,14 @@ void sqlite3FinishCoding(Parse *pParse){ assert( !pParse->isMultiWrite || sqlite3VdbeAssertMayAbort(v, pParse->mayAbort)); if( v ){ - while( sqlite3VdbeDeletePriorOpcode(v, OP_Close) ){} sqlite3VdbeAddOp0(v, OP_Halt); #if SQLITE_USER_AUTHENTICATION if( pParse->nTableLock>0 && db->init.busy==0 ){ sqlite3UserAuthInit(db); if( db->auth.authLevelrc = SQLITE_AUTH_USER; sqlite3ErrorMsg(pParse, "user not authenticated"); + pParse->rc = SQLITE_AUTH_USER; return; } } @@ -173,14 +174,16 @@ void sqlite3FinishCoding(Parse *pParse){ assert( sqlite3VdbeGetOp(v, 0)->opcode==OP_Init ); sqlite3VdbeJumpHere(v, 0); for(iDb=0; iDbnDb; iDb++){ + Schema *pSchema; if( DbMaskTest(pParse->cookieMask, iDb)==0 ) continue; sqlite3VdbeUsesBtree(v, iDb); + pSchema = db->aDb[iDb].pSchema; sqlite3VdbeAddOp4Int(v, OP_Transaction, /* Opcode */ iDb, /* P1 */ DbMaskTest(pParse->writeMask,iDb), /* P2 */ - pParse->cookieValue[iDb], /* P3 */ - db->aDb[iDb].pSchema->iGeneration /* P4 */ + pSchema->schema_cookie, /* P3 */ + pSchema->iGeneration /* P4 */ ); if( db->init.busy==0 ) sqlite3VdbeChangeP5(v, 1); VdbeComment((v, @@ -231,16 +234,6 @@ void sqlite3FinishCoding(Parse *pParse){ }else{ pParse->rc = SQLITE_ERROR; } - - /* We are done with this Parse object. There is no need to de-initialize it */ -#if 0 - pParse->colNamesSet = 0; - pParse->nTab = 0; - pParse->nMem = 0; - pParse->nSet = 0; - pParse->nVar = 0; - DbMaskZero(pParse->cookieMask); -#endif } /* @@ -260,8 +253,7 @@ void sqlite3NestedParse(Parse *pParse, const char *zFormat, ...){ char *zSql; char *zErrMsg = 0; sqlite3 *db = pParse->db; -# define SAVE_SZ (sizeof(Parse) - offsetof(Parse,nVar)) - char saveBuf[SAVE_SZ]; + char saveBuf[PARSE_TAIL_SZ]; if( pParse->nErr ) return; assert( pParse->nested<10 ); /* Nesting should only be of limited depth */ @@ -272,12 +264,12 @@ void sqlite3NestedParse(Parse *pParse, const char *zFormat, ...){ return; /* A malloc must have failed */ } pParse->nested++; - memcpy(saveBuf, &pParse->nVar, SAVE_SZ); - memset(&pParse->nVar, 0, SAVE_SZ); + memcpy(saveBuf, PARSE_TAIL(pParse), PARSE_TAIL_SZ); + memset(PARSE_TAIL(pParse), 0, PARSE_TAIL_SZ); sqlite3RunParser(pParse, zSql, &zErrMsg); sqlite3DbFree(db, zErrMsg); sqlite3DbFree(db, zSql); - memcpy(&pParse->nVar, saveBuf, SAVE_SZ); + memcpy(PARSE_TAIL(pParse), saveBuf, PARSE_TAIL_SZ); pParse->nested--; } @@ -316,14 +308,22 @@ Table *sqlite3FindTable(sqlite3 *db, const char *zName, const char *zDatabase){ return 0; } #endif - for(i=OMIT_TEMPDB; inDb; i++){ - int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ - if( zDatabase!=0 && sqlite3StrICmp(zDatabase, db->aDb[j].zName) ) continue; - assert( sqlite3SchemaMutexHeld(db, j, 0) ); - p = sqlite3HashFind(&db->aDb[j].pSchema->tblHash, zName); - if( p ) break; + while(1){ + for(i=OMIT_TEMPDB; inDb; i++){ + int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ + if( zDatabase==0 || sqlite3StrICmp(zDatabase, db->aDb[j].zDbSName)==0 ){ + assert( sqlite3SchemaMutexHeld(db, j, 0) ); + p = sqlite3HashFind(&db->aDb[j].pSchema->tblHash, zName); + if( p ) return p; + } + } + /* Not found. If the name we were looking for was temp.sqlite_master + ** then change the name to sqlite_temp_master and try again. */ + if( sqlite3StrICmp(zName, MASTER_NAME)!=0 ) break; + if( sqlite3_stricmp(zDatabase, db->aDb[1].zDbSName)!=0 ) break; + zName = TEMP_MASTER_NAME; } - return p; + return 0; } /* @@ -359,6 +359,9 @@ Table *sqlite3LocateTable( ** CREATE, then check to see if it is the name of an virtual table that ** can be an eponymous virtual table. */ Module *pMod = (Module*)sqlite3HashFind(&pParse->db->aModule, zName); + if( pMod==0 && sqlite3_strnicmp(zName, "pragma_", 7)==0 ){ + pMod = sqlite3PragmaVtabRegister(pParse->db, zName); + } if( pMod && sqlite3VtabEponymousTableInit(pParse, pMod) ){ return pMod->pEpoTab; } @@ -395,7 +398,7 @@ Table *sqlite3LocateTableItem( assert( p->pSchema==0 || p->zDatabase==0 ); if( p->pSchema ){ int iDb = sqlite3SchemaToIndex(pParse->db, p->pSchema); - zDb = pParse->db->aDb[iDb].zName; + zDb = pParse->db->aDb[iDb].zDbSName; }else{ zDb = p->zDatabase; } @@ -423,7 +426,7 @@ Index *sqlite3FindIndex(sqlite3 *db, const char *zName, const char *zDb){ int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ Schema *pSchema = db->aDb[j].pSchema; assert( pSchema ); - if( zDb && sqlite3StrICmp(zDb, db->aDb[j].zName) ) continue; + if( zDb && sqlite3StrICmp(zDb, db->aDb[j].zDbSName) ) continue; assert( sqlite3SchemaMutexHeld(db, j, 0) ); p = sqlite3HashFind(&pSchema->idxHash, zName); if( p ) break; @@ -492,8 +495,8 @@ void sqlite3CollapseDatabaseArray(sqlite3 *db){ for(i=j=2; inDb; i++){ struct Db *pDb = &db->aDb[i]; if( pDb->pBt==0 ){ - sqlite3DbFree(db, pDb->zName); - pDb->zName = 0; + sqlite3DbFree(db, pDb->zDbSName); + pDb->zDbSName = 0; continue; } if( jpnBytesFreed==0) && (--pTable->nRef)>0) ) return; + if( ((!db || db->pnBytesFreed==0) && (--pTable->nTabRef)>0) ) return; deleteTable(db, pTable); } @@ -695,7 +698,7 @@ char *sqlite3NameFromToken(sqlite3 *db, Token *pName){ */ void sqlite3OpenMasterTable(Parse *p, int iDb){ Vdbe *v = sqlite3GetVdbe(p); - sqlite3TableLock(p, iDb, MASTER_ROOT, 1, SCHEMA_TABLE(iDb)); + sqlite3TableLock(p, iDb, MASTER_ROOT, 1, MASTER_NAME); sqlite3VdbeAddOp4Int(v, OP_OpenWrite, 0, MASTER_ROOT, iDb, 5); if( p->nTab==0 ){ p->nTab = 1; @@ -713,7 +716,10 @@ int sqlite3FindDbName(sqlite3 *db, const char *zName){ if( zName ){ Db *pDb; for(i=(db->nDb-1), pDb=&db->aDb[i]; i>=0; i--, pDb--){ - if( 0==sqlite3StrICmp(pDb->zName, zName) ) break; + if( 0==sqlite3_stricmp(pDb->zDbSName, zName) ) break; + /* "main" is always an acceptable alias for the primary database + ** even if it has been renamed using SQLITE_DBCONFIG_MAINDBNAME. */ + if( i==0 && 0==sqlite3_stricmp("main", zName) ) break; } } return i; @@ -772,7 +778,7 @@ int sqlite3TwoPartName( return -1; } }else{ - assert( db->init.iDb==0 || db->init.busy ); + assert( db->init.iDb==0 || db->init.busy || (db->flags & SQLITE_Vacuum)!=0); iDb = db->init.iDb; *pUnqual = pName1; } @@ -883,7 +889,7 @@ void sqlite3StartTable( SQLITE_CREATE_VIEW, SQLITE_CREATE_TEMP_VIEW }; - char *zDb = db->aDb[iDb].zName; + char *zDb = db->aDb[iDb].zDbSName; if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(isTemp), 0, zDb) ){ goto begin_table_error; } @@ -902,7 +908,7 @@ void sqlite3StartTable( ** collisions. */ if( !IN_DECLARE_VTAB ){ - char *zDb = db->aDb[iDb].zName; + char *zDb = db->aDb[iDb].zDbSName; if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){ goto begin_table_error; } @@ -932,7 +938,7 @@ void sqlite3StartTable( pTable->zName = zName; pTable->iPKey = -1; pTable->pSchema = db->aDb[iDb].pSchema; - pTable->nRef = 1; + pTable->nTabRef = 1; pTable->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); assert( pParse->pNewTable==0 ); pParse->pNewTable = pTable; @@ -1457,6 +1463,9 @@ CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char *zName){ ** set back to prior value. But schema changes are infrequent ** and the probability of hitting the same cookie value is only ** 1 chance in 2^32. So we're safe enough. +** +** IMPLEMENTATION-OF: R-34230-56049 SQLite automatically increments +** the schema-version whenever the schema changes. */ void sqlite3ChangeCookie(Parse *pParse, int iDb){ sqlite3 *db = pParse->db; @@ -1995,7 +2004,7 @@ void sqlite3EndTable( "UPDATE %Q.%s " "SET type='%s', name=%Q, tbl_name=%Q, rootpage=#%d, sql=%Q " "WHERE rowid=#%d", - db->aDb[iDb].zName, SCHEMA_TABLE(iDb), + db->aDb[iDb].zDbSName, MASTER_NAME, zType, p->zName, p->zName, @@ -2010,13 +2019,13 @@ void sqlite3EndTable( /* Check to see if we need to create an sqlite_sequence table for ** keeping track of autoincrement keys. */ - if( p->tabFlags & TF_Autoincrement ){ + if( (p->tabFlags & TF_Autoincrement)!=0 ){ Db *pDb = &db->aDb[iDb]; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( pDb->pSchema->pSeqTab==0 ){ sqlite3NestedParse(pParse, "CREATE TABLE %Q.sqlite_sequence(name,seq)", - pDb->zName + pDb->zDbSName ); } } @@ -2140,7 +2149,9 @@ int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ int nErr = 0; /* Number of errors encountered */ int n; /* Temporarily holds the number of cursors assigned */ sqlite3 *db = pParse->db; /* Database connection for malloc errors */ +#ifndef SQLITE_OMIT_AUTHORIZATION sqlite3_xauth xAuth; /* Saved xAuth pointer */ +#endif assert( pTable ); @@ -2330,7 +2341,7 @@ static void destroyRootPage(Parse *pParse, int iTable, int iDb){ */ sqlite3NestedParse(pParse, "UPDATE %Q.%s SET rootpage=%d WHERE #%d AND rootpage=#%d", - pParse->db->aDb[iDb].zName, SCHEMA_TABLE(iDb), iTable, r1, r1); + pParse->db->aDb[iDb].zDbSName, MASTER_NAME, iTable, r1, r1); #endif sqlite3ReleaseTempReg(pParse, r1); } @@ -2406,7 +2417,7 @@ static void sqlite3ClearStatTables( const char *zName /* Name of index or table */ ){ int i; - const char *zDbName = pParse->db->aDb[iDb].zName; + const char *zDbName = pParse->db->aDb[iDb].zDbSName; for(i=1; i<=4; i++){ char zTab[24]; sqlite3_snprintf(sizeof(zTab),zTab,"sqlite_stat%d",i); @@ -2459,7 +2470,7 @@ void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, int isView){ if( pTab->tabFlags & TF_Autoincrement ){ sqlite3NestedParse(pParse, "DELETE FROM %Q.sqlite_sequence WHERE name=%Q", - pDb->zName, pTab->zName + pDb->zDbSName, pTab->zName ); } #endif @@ -2473,7 +2484,7 @@ void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, int isView){ */ sqlite3NestedParse(pParse, "DELETE FROM %Q.%s WHERE tbl_name=%Q and type!='trigger'", - pDb->zName, SCHEMA_TABLE(iDb), pTab->zName); + pDb->zDbSName, MASTER_NAME, pTab->zName); if( !isView && !IsVirtual(pTab) ){ destroyTable(pParse, pTab); } @@ -2527,7 +2538,7 @@ void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, int noErr){ { int code; const char *zTab = SCHEMA_TABLE(iDb); - const char *zDb = db->aDb[iDb].zName; + const char *zDb = db->aDb[iDb].zDbSName; const char *zArg2 = 0; if( sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb)){ goto exit_drop_table; @@ -2768,7 +2779,7 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ #ifndef SQLITE_OMIT_AUTHORIZATION if( sqlite3AuthCheck(pParse, SQLITE_REINDEX, pIndex->zName, 0, - db->aDb[iDb].zName ) ){ + db->aDb[iDb].zDbSName ) ){ return; } #endif @@ -2820,7 +2831,7 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ } sqlite3VdbeAddOp3(v, OP_SorterData, iSorter, regRecord, iIdx); sqlite3VdbeAddOp3(v, OP_Last, iIdx, 0, -1); - sqlite3VdbeAddOp3(v, OP_IdxInsert, iIdx, regRecord, 0); + sqlite3VdbeAddOp2(v, OP_IdxInsert, iIdx, regRecord); sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); sqlite3ReleaseTempReg(pParse, regRecord); sqlite3VdbeAddOp2(v, OP_SorterNext, iSorter, addr2); VdbeCoverage(v); @@ -3020,7 +3031,7 @@ void sqlite3CreateIndex( goto exit_create_index; } } - if( sqlite3FindIndex(db, zName, pDb->zName)!=0 ){ + if( sqlite3FindIndex(db, zName, pDb->zDbSName)!=0 ){ if( !ifNotExist ){ sqlite3ErrorMsg(pParse, "index %s already exists", zName); }else{ @@ -3050,7 +3061,7 @@ void sqlite3CreateIndex( */ #ifndef SQLITE_OMIT_AUTHORIZATION { - const char *zDb = pDb->zName; + const char *zDb = pDb->zDbSName; if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(iDb), 0, zDb) ){ goto exit_create_index; } @@ -3365,7 +3376,7 @@ void sqlite3CreateIndex( */ sqlite3NestedParse(pParse, "INSERT INTO %Q.%s VALUES('index',%Q,%Q,#%d,%Q);", - db->aDb[iDb].zName, SCHEMA_TABLE(iDb), + db->aDb[iDb].zDbSName, MASTER_NAME, pIndex->zName, pTab->zName, iMem, @@ -3499,7 +3510,7 @@ void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){ { int code = SQLITE_DROP_INDEX; Table *pTab = pIndex->pTable; - const char *zDb = db->aDb[iDb].zName; + const char *zDb = db->aDb[iDb].zDbSName; const char *zTab = SCHEMA_TABLE(iDb); if( sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){ goto exit_drop_index; @@ -3517,7 +3528,7 @@ void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){ sqlite3BeginWriteOperation(pParse, 1, iDb); sqlite3NestedParse(pParse, "DELETE FROM %Q.%s WHERE name=%Q AND type='index'", - db->aDb[iDb].zName, SCHEMA_TABLE(iDb), pIndex->zName + db->aDb[iDb].zDbSName, MASTER_NAME, pIndex->zName ); sqlite3ClearStatTables(pParse, iDb, "idx", pIndex->zName); sqlite3ChangeCookie(pParse, iDb); @@ -3660,7 +3671,7 @@ SrcList *sqlite3SrcListEnlarge( /* Allocate additional space if needed */ if( (u32)pSrc->nSrc+nExtra>pSrc->nAlloc ){ SrcList *pNew; - int nAlloc = pSrc->nSrc+nExtra; + int nAlloc = pSrc->nSrc*2+nExtra; int nGot; pNew = sqlite3DbRealloc(db, pSrc, sizeof(*pSrc) + (nAlloc-1)*sizeof(pSrc->a[0]) ); @@ -3738,9 +3749,12 @@ SrcList *sqlite3SrcListAppend( pList = sqlite3DbMallocRawNN(db, sizeof(SrcList) ); if( pList==0 ) return 0; pList->nAlloc = 1; - pList->nSrc = 0; + pList->nSrc = 1; + memset(&pList->a[0], 0, sizeof(pList->a[0])); + pList->a[0].iCursor = -1; + }else{ + pList = sqlite3SrcListEnlarge(db, pList, 1, pList->nSrc); } - pList = sqlite3SrcListEnlarge(db, pList, 1, pList->nSrc); if( db->mallocFailed ){ sqlite3SrcListDelete(db, pList); return 0; @@ -4038,15 +4052,13 @@ int sqlite3OpenTempDatabase(Parse *pParse){ */ void sqlite3CodeVerifySchema(Parse *pParse, int iDb){ Parse *pToplevel = sqlite3ParseToplevel(pParse); - sqlite3 *db = pToplevel->db; - assert( iDb>=0 && iDbnDb ); - assert( db->aDb[iDb].pBt!=0 || iDb==1 ); + assert( iDb>=0 && iDbdb->nDb ); + assert( pParse->db->aDb[iDb].pBt!=0 || iDb==1 ); assert( iDbdb, iDb, 0) ); if( DbMaskTest(pToplevel->cookieMask, iDb)==0 ){ DbMaskSet(pToplevel->cookieMask, iDb); - pToplevel->cookieValue[iDb] = db->aDb[iDb].pSchema->schema_cookie; if( !OMIT_TEMPDB && iDb==1 ){ sqlite3OpenTempDatabase(pToplevel); } @@ -4062,7 +4074,7 @@ void sqlite3CodeVerifyNamedSchema(Parse *pParse, const char *zDb){ int i; for(i=0; inDb; i++){ Db *pDb = &db->aDb[i]; - if( pDb->pBt && (!zDb || 0==sqlite3StrICmp(zDb, pDb->zName)) ){ + if( pDb->pBt && (!zDb || 0==sqlite3StrICmp(zDb, pDb->zDbSName)) ){ sqlite3CodeVerifySchema(pParse, i); } } @@ -4309,7 +4321,7 @@ void sqlite3Reindex(Parse *pParse, Token *pName1, Token *pName2){ if( iDb<0 ) return; z = sqlite3NameFromToken(db, pObjName); if( z==0 ) return; - zDb = db->aDb[iDb].zName; + zDb = db->aDb[iDb].zDbSName; pTab = sqlite3FindTable(db, z, zDb); if( pTab ){ reindexTable(pParse, pTab, 0); diff --git a/src/ctime.c b/src/ctime.c index 09587ea88e..969bbc73d2 100644 --- a/src/ctime.c +++ b/src/ctime.c @@ -66,6 +66,9 @@ static const char * const azCompileOpt[] = { #if defined(SQLITE_DEFAULT_MMAP_SIZE) && !defined(SQLITE_DEFAULT_MMAP_SIZE_xc) "DEFAULT_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_MMAP_SIZE), #endif +#if SQLITE_DIRECT_OVERFLOW_READ + "DIRECT_OVERFLOW_READ", +#endif #if SQLITE_DISABLE_DIRSYNC "DISABLE_DIRSYNC", #endif @@ -152,6 +155,9 @@ static const char * const azCompileOpt[] = { #if SQLITE_ENABLE_UPDATE_DELETE_LIMIT "ENABLE_UPDATE_DELETE_LIMIT", #endif +#if defined(SQLITE_ENABLE_URI_00_ERROR) + "ENABLE_URI_00_ERROR", +#endif #if SQLITE_HAS_CODEC "HAS_CODEC", #endif @@ -227,9 +233,6 @@ static const char * const azCompileOpt[] = { #if SQLITE_OMIT_BTREECOUNT "OMIT_BTREECOUNT", #endif -#if SQLITE_OMIT_BUILTIN_TEST - "OMIT_BUILTIN_TEST", -#endif #if SQLITE_OMIT_CAST "OMIT_CAST", #endif @@ -392,6 +395,9 @@ static const char * const azCompileOpt[] = { #if defined(SQLITE_THREADSAFE) "THREADSAFE=" CTIMEOPT_VAL(SQLITE_THREADSAFE), #endif +#if SQLITE_UNTESTABLE + "UNTESTABLE" +#endif #if SQLITE_USE_ALLOCA "USE_ALLOCA", #endif diff --git a/src/date.c b/src/date.c index 8b2f2fa717..a08248ce2f 100644 --- a/src/date.c +++ b/src/date.c @@ -65,16 +65,18 @@ struct tm *__cdecl localtime(const time_t *); */ typedef struct DateTime DateTime; struct DateTime { - sqlite3_int64 iJD; /* The julian day number times 86400000 */ - int Y, M, D; /* Year, month, and day */ - int h, m; /* Hour and minutes */ - int tz; /* Timezone offset in minutes */ - double s; /* Seconds */ - char validYMD; /* True (1) if Y,M,D are valid */ - char validHMS; /* True (1) if h,m,s are valid */ - char validJD; /* True (1) if iJD is valid */ - char validTZ; /* True (1) if tz is valid */ - char tzSet; /* Timezone was set explicitly */ + sqlite3_int64 iJD; /* The julian day number times 86400000 */ + int Y, M, D; /* Year, month, and day */ + int h, m; /* Hour and minutes */ + int tz; /* Timezone offset in minutes */ + double s; /* Seconds */ + char validJD; /* True (1) if iJD is valid */ + char rawS; /* Raw numeric value stored in s */ + char validYMD; /* True (1) if Y,M,D are valid */ + char validHMS; /* True (1) if h,m,s are valid */ + char validTZ; /* True (1) if tz is valid */ + char tzSet; /* Timezone was set explicitly */ + char isError; /* An overflow has occurred */ }; @@ -222,6 +224,7 @@ static int parseHhMmSs(const char *zDate, DateTime *p){ s = 0; } p->validJD = 0; + p->rawS = 0; p->validHMS = 1; p->h = h; p->m = m; @@ -231,6 +234,14 @@ static int parseHhMmSs(const char *zDate, DateTime *p){ return 0; } +/* +** Put the DateTime object into its error state. +*/ +static void datetimeError(DateTime *p){ + memset(p, 0, sizeof(*p)); + p->isError = 1; +} + /* ** Convert from YYYY-MM-DD HH:MM:SS to julian day. We always assume ** that the YYYY-MM-DD is according to the Gregorian calendar. @@ -250,6 +261,10 @@ static void computeJD(DateTime *p){ M = 1; D = 1; } + if( Y<-4713 || Y>9999 || p->rawS ){ + datetimeError(p); + return; + } if( M<=2 ){ Y--; M += 12; @@ -330,6 +345,21 @@ static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){ } } +/* +** Input "r" is a numeric quantity which might be a julian day number, +** or the number of seconds since 1970. If the value if r is within +** range of a julian day number, install it as such and set validJD. +** If the value is a valid unix timestamp, put it in p->s and set p->rawS. +*/ +static void setRawDateNumber(DateTime *p, double r){ + p->s = r; + p->rawS = 1; + if( r>=0.0 && r<5373484.5 ){ + p->iJD = (sqlite3_int64)(r*86400000.0 + 0.5); + p->validJD = 1; + } +} + /* ** Attempt to parse the given string into a julian day number. Return ** the number of errors. @@ -359,13 +389,30 @@ static int parseDateOrTime( }else if( sqlite3StrICmp(zDate,"now")==0){ return setDateTimeToCurrent(context, p); }else if( sqlite3AtoF(zDate, &r, sqlite3Strlen30(zDate), SQLITE_UTF8) ){ - p->iJD = (sqlite3_int64)(r*86400000.0 + 0.5); - p->validJD = 1; + setRawDateNumber(p, r); return 0; } return 1; } +/* The julian day number for 9999-12-31 23:59:59.999 is 5373484.4999999. +** Multiplying this by 86400000 gives 464269060799999 as the maximum value +** for DateTime.iJD. +** +** But some older compilers (ex: gcc 4.2.1 on older Macs) cannot deal with +** such a large integer literal, so we have to encode it. +*/ +#define INT_464269060799999 ((((i64)0x1a640)<<32)|0x1072fdff) + +/* +** Return TRUE if the given julian day number is within range. +** +** The input is the JulianDay times 86400000. +*/ +static int validJulianDay(sqlite3_int64 iJD){ + return iJD>=0 && iJD<=INT_464269060799999; +} + /* ** Compute the Year, Month, and Day from the julian day number. */ @@ -377,6 +424,7 @@ static void computeYMD(DateTime *p){ p->M = 1; p->D = 1; }else{ + assert( validJulianDay(p->iJD) ); Z = (int)((p->iJD + 43200000)/86400000); A = (int)((Z - 1867216.25)/36524.25); A = Z + 1 + A - (A/4); @@ -407,6 +455,7 @@ static void computeHMS(DateTime *p){ s -= p->h*3600; p->m = s/60; p->s += s - p->m*60; + p->rawS = 0; p->validHMS = 1; } @@ -468,14 +517,14 @@ static int osLocaltime(time_t *t, struct tm *pTm){ #endif sqlite3_mutex_enter(mutex); pX = localtime(t); -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE if( sqlite3GlobalConfig.bLocaltimeFault ) pX = 0; #endif if( pX ) *pTm = *pX; sqlite3_mutex_leave(mutex); rc = pX==0; #else -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE if( sqlite3GlobalConfig.bLocaltimeFault ) return 1; #endif #if HAVE_LOCALTIME_R @@ -546,13 +595,38 @@ static sqlite3_int64 localtimeOffset( y.validYMD = 1; y.validHMS = 1; y.validJD = 0; + y.rawS = 0; y.validTZ = 0; + y.isError = 0; computeJD(&y); *pRc = SQLITE_OK; return y.iJD - x.iJD; } #endif /* SQLITE_OMIT_LOCALTIME */ +/* +** The following table defines various date transformations of the form +** +** 'NNN days' +** +** Where NNN is an arbitrary floating-point number and "days" can be one +** of several units of time. +*/ +static const struct { + u8 eType; /* Transformation type code */ + u8 nName; /* Length of th name */ + char *zName; /* Name of the transformation */ + double rLimit; /* Maximum NNN value for this transform */ + double rXform; /* Constant used for this transform */ +} aXformType[] = { + { 0, 6, "second", 464269060800.0, 86400000.0/(24.0*60.0*60.0) }, + { 0, 6, "minute", 7737817680.0, 86400000.0/(24.0*60.0) }, + { 0, 4, "hour", 128963628.0, 86400000.0/24.0 }, + { 0, 3, "day", 5373485.0, 86400000.0 }, + { 1, 5, "month", 176546.0, 30.0*86400000.0 }, + { 2, 4, "year", 14713.0, 365.0*86400000.0 }, +}; + /* ** Process a modifier to a date-time stamp. The modifiers are ** as follows: @@ -577,17 +651,15 @@ static sqlite3_int64 localtimeOffset( ** to context pCtx. If the error is an unrecognized modifier, no error is ** written to pCtx. */ -static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){ +static int parseModifier( + sqlite3_context *pCtx, /* Function context */ + const char *z, /* The text of the modifier */ + int n, /* Length of zMod in bytes */ + DateTime *p /* The date/time value to be modified */ +){ int rc = 1; - int n; double r; - char *z, zBuf[30]; - z = zBuf; - for(n=0; niJD += localtimeOffset(p, pCtx, &rc); clearYMD_HMS_TZ(p); @@ -607,16 +679,21 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){ /* ** unixepoch ** - ** Treat the current value of p->iJD as the number of + ** Treat the current value of p->s as the number of ** seconds since 1970. Convert to a real julian day number. */ - if( strcmp(z, "unixepoch")==0 && p->validJD ){ - p->iJD = (p->iJD + 43200)/86400 + 21086676*(i64)10000000; - clearYMD_HMS_TZ(p); - rc = 0; + if( sqlite3_stricmp(z, "unixepoch")==0 && p->rawS ){ + r = p->s*1000.0 + 210866760000000.0; + if( r>=0.0 && r<464269060800000.0 ){ + clearYMD_HMS_TZ(p); + p->iJD = (sqlite3_int64)r; + p->validJD = 1; + p->rawS = 0; + rc = 0; + } } #ifndef SQLITE_OMIT_LOCALTIME - else if( strcmp(z, "utc")==0 ){ + else if( sqlite3_stricmp(z, "utc")==0 ){ if( p->tzSet==0 ){ sqlite3_int64 c1; computeJD(p); @@ -642,7 +719,7 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){ ** weekday N where 0==Sunday, 1==Monday, and so forth. If the ** date is already on the appropriate weekday, this is a no-op. */ - if( strncmp(z, "weekday ", 8)==0 + if( sqlite3_strnicmp(z, "weekday ", 8)==0 && sqlite3AtoF(&z[8], &r, sqlite3Strlen30(&z[8]), SQLITE_UTF8) && (n=(int)r)==r && n>=0 && r<7 ){ sqlite3_int64 Z; @@ -665,7 +742,7 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){ ** Move the date backwards to the beginning of the current day, ** or month or year. */ - if( strncmp(z, "start of ", 9)!=0 ) break; + if( sqlite3_strnicmp(z, "start of ", 9)!=0 ) break; z += 9; computeYMD(p); p->validHMS = 1; @@ -673,15 +750,15 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){ p->s = 0.0; p->validTZ = 0; p->validJD = 0; - if( strcmp(z,"month")==0 ){ + if( sqlite3_stricmp(z,"month")==0 ){ p->D = 1; rc = 0; - }else if( strcmp(z,"year")==0 ){ + }else if( sqlite3_stricmp(z,"year")==0 ){ computeYMD(p); p->M = 1; p->D = 1; rc = 0; - }else if( strcmp(z,"day")==0 ){ + }else if( sqlite3_stricmp(z,"day")==0 ){ rc = 0; } break; @@ -699,6 +776,7 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){ case '8': case '9': { double rRounder; + int i; for(n=1; z[n] && z[n]!=':' && !sqlite3Isspace(z[n]); n++){} if( !sqlite3AtoF(z, &r, n, SQLITE_UTF8) ){ rc = 1; @@ -727,46 +805,48 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){ rc = 0; break; } + + /* If control reaches this point, it means the transformation is + ** one of the forms like "+NNN days". */ z += n; while( sqlite3Isspace(*z) ) z++; n = sqlite3Strlen30(z); if( n>10 || n<3 ) break; - if( z[n-1]=='s' ){ z[n-1] = 0; n--; } + if( sqlite3UpperToLower[(u8)z[n-1]]=='s' ) n--; computeJD(p); - rc = 0; + rc = 1; rRounder = r<0 ? -0.5 : +0.5; - if( n==3 && strcmp(z,"day")==0 ){ - p->iJD += (sqlite3_int64)(r*86400000.0 + rRounder); - }else if( n==4 && strcmp(z,"hour")==0 ){ - p->iJD += (sqlite3_int64)(r*(86400000.0/24.0) + rRounder); - }else if( n==6 && strcmp(z,"minute")==0 ){ - p->iJD += (sqlite3_int64)(r*(86400000.0/(24.0*60.0)) + rRounder); - }else if( n==6 && strcmp(z,"second")==0 ){ - p->iJD += (sqlite3_int64)(r*(86400000.0/(24.0*60.0*60.0)) + rRounder); - }else if( n==5 && strcmp(z,"month")==0 ){ - int x, y; - computeYMD_HMS(p); - p->M += (int)r; - x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12; - p->Y += x; - p->M -= x*12; - p->validJD = 0; - computeJD(p); - y = (int)r; - if( y!=r ){ - p->iJD += (sqlite3_int64)((r - y)*30.0*86400000.0 + rRounder); + for(i=0; i-aXformType[i].rLimit && rM += (int)r; + x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12; + p->Y += x; + p->M -= x*12; + p->validJD = 0; + r -= (int)r; + break; + } + case 2: { /* Special processing to add years */ + int y = (int)r; + computeYMD_HMS(p); + p->Y += y; + p->validJD = 0; + r -= (int)r; + break; + } + } + computeJD(p); + p->iJD += (sqlite3_int64)(r*aXformType[i].rXform + rRounder); + rc = 0; + break; } - }else if( n==4 && strcmp(z,"year")==0 ){ - int y = (int)r; - computeYMD_HMS(p); - p->Y += y; - p->validJD = 0; - computeJD(p); - if( y!=r ){ - p->iJD += (sqlite3_int64)((r - y)*365.0*86400000.0 + rRounder); - } - }else{ - rc = 1; } clearYMD_HMS_TZ(p); break; @@ -793,7 +873,7 @@ static int isDate( sqlite3_value **argv, DateTime *p ){ - int i; + int i, n; const unsigned char *z; int eType; memset(p, 0, sizeof(*p)); @@ -802,8 +882,7 @@ static int isDate( } if( (eType = sqlite3_value_type(argv[0]))==SQLITE_FLOAT || eType==SQLITE_INTEGER ){ - p->iJD = (sqlite3_int64)(sqlite3_value_double(argv[0])*86400000.0 + 0.5); - p->validJD = 1; + setRawDateNumber(p, sqlite3_value_double(argv[0])); }else{ z = sqlite3_value_text(argv[0]); if( !z || parseDateOrTime(context, (char*)z, p) ){ @@ -812,8 +891,11 @@ static int isDate( } for(i=1; iisError || !validJulianDay(p->iJD) ) return 1; return 0; } diff --git a/src/dbstat.c b/src/dbstat.c index 92ce650df1..13974bb979 100644 --- a/src/dbstat.c +++ b/src/dbstat.c @@ -602,7 +602,7 @@ static int statFilter( " UNION ALL " "SELECT name, rootpage, type" " FROM \"%w\".%s WHERE rootpage!=0" - " ORDER BY name", pTab->db->aDb[pCsr->iDb].zName, zMaster); + " ORDER BY name", pTab->db->aDb[pCsr->iDb].zDbSName, zMaster); if( zSql==0 ){ return SQLITE_NOMEM_BKPT; }else{ @@ -656,7 +656,7 @@ static int statColumn( default: { /* schema */ sqlite3 *db = sqlite3_context_db_handle(ctx); int iDb = pCsr->iDb; - sqlite3_result_text(ctx, db->aDb[iDb].zName, -1, SQLITE_STATIC); + sqlite3_result_text(ctx, db->aDb[iDb].zDbSName, -1, SQLITE_STATIC); break; } } diff --git a/src/delete.c b/src/delete.c index e9e669e21c..9ec8ed59e3 100644 --- a/src/delete.c +++ b/src/delete.c @@ -36,7 +36,7 @@ Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){ sqlite3DeleteTable(pParse->db, pItem->pTab); pItem->pTab = pTab; if( pTab ){ - pTab->nRef++; + pTab->nTabRef++; } if( sqlite3IndexedByLookup(pParse, pItem) ){ pTab = 0; @@ -102,7 +102,7 @@ void sqlite3MaterializeView( if( pFrom ){ assert( pFrom->nSrc==1 ); pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName); - pFrom->a[0].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zName); + pFrom->a[0].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zDbSName); assert( pFrom->a[0].pOn==0 ); assert( pFrom->a[0].pUsing==0 ); } @@ -164,7 +164,7 @@ Expr *sqlite3LimitWhere( ** ); */ - pSelectRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0, 0); + pSelectRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0); if( pSelectRowid == 0 ) goto limit_where_cleanup; pEList = sqlite3ExprListAppend(pParse, 0, pSelectRowid); if( pEList == 0 ) goto limit_where_cleanup; @@ -183,8 +183,8 @@ Expr *sqlite3LimitWhere( if( pSelect == 0 ) return 0; /* now generate the new WHERE rowid IN clause for the DELETE/UDPATE */ - pWhereRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0, 0); - pInClause = pWhereRowid ? sqlite3PExpr(pParse, TK_IN, pWhereRowid, 0, 0) : 0; + pWhereRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0); + pInClause = pWhereRowid ? sqlite3PExpr(pParse, TK_IN, pWhereRowid, 0) : 0; sqlite3PExprAddSelect(pParse, pInClause, pSelect); return pInClause; @@ -212,7 +212,6 @@ void sqlite3DeleteFrom( ){ Vdbe *v; /* The virtual database engine */ Table *pTab; /* The table from which records will be deleted */ - const char *zDb; /* Name of database holding pTab */ int i; /* Loop counter */ WhereInfo *pWInfo; /* Information about the WHERE clause */ Index *pIdx; /* For looping over indices of the table */ @@ -289,8 +288,8 @@ void sqlite3DeleteFrom( } iDb = sqlite3SchemaToIndex(db, pTab->pSchema); assert( iDbnDb ); - zDb = db->aDb[iDb].zName; - rcauth = sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, zDb); + rcauth = sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, + db->aDb[iDb].zDbSName); assert( rcauth==SQLITE_OK || rcauth==SQLITE_DENY || rcauth==SQLITE_IGNORE ); if( rcauth==SQLITE_DENY ){ goto delete_from_cleanup; @@ -450,7 +449,7 @@ void sqlite3DeleteFrom( nKey = 0; /* Zero tells OP_Found to use a composite key */ sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, iKey, sqlite3IndexAffinityStr(pParse->db, pPk), nPk); - sqlite3VdbeAddOp2(v, OP_IdxInsert, iEphCur, iKey); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iEphCur, iKey, iPk, nPk); }else{ /* Add the rowid of the row to be deleted to the RowSet */ nKey = 1; /* OP_Seek always uses a single rowid */ @@ -474,7 +473,7 @@ void sqlite3DeleteFrom( if( !isView ){ int iAddrOnce = 0; if( eOnePass==ONEPASS_MULTI ){ - iAddrOnce = sqlite3CodeOnce(pParse); VdbeCoverage(v); + iAddrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } testcase( IsVirtual(pTab) ); sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, OPFLAG_FORDELETE, @@ -496,7 +495,7 @@ void sqlite3DeleteFrom( } }else if( pPk ){ addrLoop = sqlite3VdbeAddOp1(v, OP_Rewind, iEphCur); VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_RowKey, iEphCur, iKey); + sqlite3VdbeAddOp2(v, OP_RowData, iEphCur, iKey); assert( nKey==0 ); /* OP_Found will use a composite key */ }else{ addrLoop = sqlite3VdbeAddOp3(v, OP_RowSetRead, iRowSet, 0, iKey); @@ -539,14 +538,6 @@ void sqlite3DeleteFrom( sqlite3VdbeGoto(v, addrLoop); sqlite3VdbeJumpHere(v, addrLoop); } - - /* Close the cursors open on the table and its indexes. */ - if( !isView && !IsVirtual(pTab) ){ - if( !pPk ) sqlite3VdbeAddOp1(v, OP_Close, iDataCur); - for(i=0, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ - sqlite3VdbeAddOp1(v, OP_Close, iIdxCur + i); - } - } } /* End non-truncate path */ /* Update the sqlite_sequence table by storing the content of the @@ -721,7 +712,7 @@ void sqlite3GenerateRowDelete( u8 p5 = 0; sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek); sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, (count?OPFLAG_NCHANGE:0)); - sqlite3VdbeChangeP4(v, -1, (char*)pTab, P4_TABLE); + sqlite3VdbeAppendP4(v, (char*)pTab, P4_TABLE); if( eMode!=ONEPASS_OFF ){ sqlite3VdbeChangeP5(v, OPFLAG_AUXDELETE); } diff --git a/src/expr.c b/src/expr.c index 74f09bbee9..154d078ff7 100644 --- a/src/expr.c +++ b/src/expr.c @@ -14,6 +14,18 @@ */ #include "sqliteInt.h" +/* Forward declarations */ +static void exprCodeBetween(Parse*,Expr*,int,void(*)(Parse*,Expr*,int,int),int); +static int exprCodeVector(Parse *pParse, Expr *p, int *piToFree); + +/* +** Return the affinity character for a single column of a table. +*/ +char sqlite3TableColumnAffinity(Table *pTab, int iCol){ + assert( iColnCol ); + return iCol>=0 ? pTab->aCol[iCol].affinity : SQLITE_AFF_INTEGER; +} + /* ** Return the 'affinity' of the expression pExpr if any. ** @@ -39,21 +51,21 @@ char sqlite3ExprAffinity(Expr *pExpr){ assert( pExpr->flags&EP_xIsSelect ); return sqlite3ExprAffinity(pExpr->x.pSelect->pEList->a[0].pExpr); } + if( op==TK_REGISTER ) op = pExpr->op2; #ifndef SQLITE_OMIT_CAST if( op==TK_CAST ){ assert( !ExprHasProperty(pExpr, EP_IntValue) ); return sqlite3AffinityType(pExpr->u.zToken, 0); } #endif - if( (op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_REGISTER) - && pExpr->pTab!=0 - ){ - /* op==TK_REGISTER && pExpr->pTab!=0 happens when pExpr was originally - ** a TK_COLUMN but was previously evaluated and cached in a register */ - int j = pExpr->iColumn; - if( j<0 ) return SQLITE_AFF_INTEGER; - assert( pExpr->pTab && jpTab->nCol ); - return pExpr->pTab->aCol[j].affinity; + if( op==TK_AGG_COLUMN || op==TK_COLUMN ){ + return sqlite3TableColumnAffinity(pExpr->pTab, pExpr->iColumn); + } + if( op==TK_SELECT_COLUMN ){ + assert( pExpr->pLeft->flags&EP_xIsSelect ); + return sqlite3ExprAffinity( + pExpr->pLeft->x.pSelect->pEList->a[pExpr->iColumn].pExpr + ); } return pExpr->affinity; } @@ -219,7 +231,7 @@ static char comparisonAffinity(Expr *pExpr){ aff = sqlite3CompareAffinity(pExpr->pRight, aff); }else if( ExprHasProperty(pExpr, EP_xIsSelect) ){ aff = sqlite3CompareAffinity(pExpr->x.pSelect->pEList->a[0].pExpr, aff); - }else if( !aff ){ + }else if( NEVER(aff==0) ){ aff = SQLITE_AFF_BLOB; } return aff; @@ -309,6 +321,274 @@ static int codeCompare( return addr; } +/* +** Return true if expression pExpr is a vector, or false otherwise. +** +** A vector is defined as any expression that results in two or more +** columns of result. Every TK_VECTOR node is an vector because the +** parser will not generate a TK_VECTOR with fewer than two entries. +** But a TK_SELECT might be either a vector or a scalar. It is only +** considered a vector if it has two or more result columns. +*/ +int sqlite3ExprIsVector(Expr *pExpr){ + return sqlite3ExprVectorSize(pExpr)>1; +} + +/* +** If the expression passed as the only argument is of type TK_VECTOR +** return the number of expressions in the vector. Or, if the expression +** is a sub-select, return the number of columns in the sub-select. For +** any other type of expression, return 1. +*/ +int sqlite3ExprVectorSize(Expr *pExpr){ + u8 op = pExpr->op; + if( op==TK_REGISTER ) op = pExpr->op2; + if( op==TK_VECTOR ){ + return pExpr->x.pList->nExpr; + }else if( op==TK_SELECT ){ + return pExpr->x.pSelect->pEList->nExpr; + }else{ + return 1; + } +} + +#ifndef SQLITE_OMIT_SUBQUERY +/* +** Return a pointer to a subexpression of pVector that is the i-th +** column of the vector (numbered starting with 0). The caller must +** ensure that i is within range. +** +** If pVector is really a scalar (and "scalar" here includes subqueries +** that return a single column!) then return pVector unmodified. +** +** pVector retains ownership of the returned subexpression. +** +** If the vector is a (SELECT ...) then the expression returned is +** just the expression for the i-th term of the result set, and may +** not be ready for evaluation because the table cursor has not yet +** been positioned. +*/ +Expr *sqlite3VectorFieldSubexpr(Expr *pVector, int i){ + assert( iop2==0 || pVector->op==TK_REGISTER ); + if( pVector->op==TK_SELECT || pVector->op2==TK_SELECT ){ + return pVector->x.pSelect->pEList->a[i].pExpr; + }else{ + return pVector->x.pList->a[i].pExpr; + } + } + return pVector; +} +#endif /* !defined(SQLITE_OMIT_SUBQUERY) */ + +#ifndef SQLITE_OMIT_SUBQUERY +/* +** Compute and return a new Expr object which when passed to +** sqlite3ExprCode() will generate all necessary code to compute +** the iField-th column of the vector expression pVector. +** +** It is ok for pVector to be a scalar (as long as iField==0). +** In that case, this routine works like sqlite3ExprDup(). +** +** The caller owns the returned Expr object and is responsible for +** ensuring that the returned value eventually gets freed. +** +** The caller retains ownership of pVector. If pVector is a TK_SELECT, +** then the returned object will reference pVector and so pVector must remain +** valid for the life of the returned object. If pVector is a TK_VECTOR +** or a scalar expression, then it can be deleted as soon as this routine +** returns. +** +** A trick to cause a TK_SELECT pVector to be deleted together with +** the returned Expr object is to attach the pVector to the pRight field +** of the returned TK_SELECT_COLUMN Expr object. +*/ +Expr *sqlite3ExprForVectorField( + Parse *pParse, /* Parsing context */ + Expr *pVector, /* The vector. List of expressions or a sub-SELECT */ + int iField /* Which column of the vector to return */ +){ + Expr *pRet; + if( pVector->op==TK_SELECT ){ + assert( pVector->flags & EP_xIsSelect ); + /* The TK_SELECT_COLUMN Expr node: + ** + ** pLeft: pVector containing TK_SELECT. Not deleted. + ** pRight: not used. But recursively deleted. + ** iColumn: Index of a column in pVector + ** iTable: 0 or the number of columns on the LHS of an assignment + ** pLeft->iTable: First in an array of register holding result, or 0 + ** if the result is not yet computed. + ** + ** sqlite3ExprDelete() specifically skips the recursive delete of + ** pLeft on TK_SELECT_COLUMN nodes. But pRight is followed, so pVector + ** can be attached to pRight to cause this node to take ownership of + ** pVector. Typically there will be multiple TK_SELECT_COLUMN nodes + ** with the same pLeft pointer to the pVector, but only one of them + ** will own the pVector. + */ + pRet = sqlite3PExpr(pParse, TK_SELECT_COLUMN, 0, 0); + if( pRet ){ + pRet->iColumn = iField; + pRet->pLeft = pVector; + } + assert( pRet==0 || pRet->iTable==0 ); + }else{ + if( pVector->op==TK_VECTOR ) pVector = pVector->x.pList->a[iField].pExpr; + pRet = sqlite3ExprDup(pParse->db, pVector, 0); + } + return pRet; +} +#endif /* !define(SQLITE_OMIT_SUBQUERY) */ + +/* +** If expression pExpr is of type TK_SELECT, generate code to evaluate +** it. Return the register in which the result is stored (or, if the +** sub-select returns more than one column, the first in an array +** of registers in which the result is stored). +** +** If pExpr is not a TK_SELECT expression, return 0. +*/ +static int exprCodeSubselect(Parse *pParse, Expr *pExpr){ + int reg = 0; +#ifndef SQLITE_OMIT_SUBQUERY + if( pExpr->op==TK_SELECT ){ + reg = sqlite3CodeSubselect(pParse, pExpr, 0, 0); + } +#endif + return reg; +} + +/* +** Argument pVector points to a vector expression - either a TK_VECTOR +** or TK_SELECT that returns more than one column. This function returns +** the register number of a register that contains the value of +** element iField of the vector. +** +** If pVector is a TK_SELECT expression, then code for it must have +** already been generated using the exprCodeSubselect() routine. In this +** case parameter regSelect should be the first in an array of registers +** containing the results of the sub-select. +** +** If pVector is of type TK_VECTOR, then code for the requested field +** is generated. In this case (*pRegFree) may be set to the number of +** a temporary register to be freed by the caller before returning. +** +** Before returning, output parameter (*ppExpr) is set to point to the +** Expr object corresponding to element iElem of the vector. +*/ +static int exprVectorRegister( + Parse *pParse, /* Parse context */ + Expr *pVector, /* Vector to extract element from */ + int iField, /* Field to extract from pVector */ + int regSelect, /* First in array of registers */ + Expr **ppExpr, /* OUT: Expression element */ + int *pRegFree /* OUT: Temp register to free */ +){ + u8 op = pVector->op; + assert( op==TK_VECTOR || op==TK_REGISTER || op==TK_SELECT ); + if( op==TK_REGISTER ){ + *ppExpr = sqlite3VectorFieldSubexpr(pVector, iField); + return pVector->iTable+iField; + } + if( op==TK_SELECT ){ + *ppExpr = pVector->x.pSelect->pEList->a[iField].pExpr; + return regSelect+iField; + } + *ppExpr = pVector->x.pList->a[iField].pExpr; + return sqlite3ExprCodeTemp(pParse, *ppExpr, pRegFree); +} + +/* +** Expression pExpr is a comparison between two vector values. Compute +** the result of the comparison (1, 0, or NULL) and write that +** result into register dest. +** +** The caller must satisfy the following preconditions: +** +** if pExpr->op==TK_IS: op==TK_EQ and p5==SQLITE_NULLEQ +** if pExpr->op==TK_ISNOT: op==TK_NE and p5==SQLITE_NULLEQ +** otherwise: op==pExpr->op and p5==0 +*/ +static void codeVectorCompare( + Parse *pParse, /* Code generator context */ + Expr *pExpr, /* The comparison operation */ + int dest, /* Write results into this register */ + u8 op, /* Comparison operator */ + u8 p5 /* SQLITE_NULLEQ or zero */ +){ + Vdbe *v = pParse->pVdbe; + Expr *pLeft = pExpr->pLeft; + Expr *pRight = pExpr->pRight; + int nLeft = sqlite3ExprVectorSize(pLeft); + int i; + int regLeft = 0; + int regRight = 0; + u8 opx = op; + int addrDone = sqlite3VdbeMakeLabel(v); + + if( nLeft!=sqlite3ExprVectorSize(pRight) ){ + sqlite3ErrorMsg(pParse, "row value misused"); + return; + } + assert( pExpr->op==TK_EQ || pExpr->op==TK_NE + || pExpr->op==TK_IS || pExpr->op==TK_ISNOT + || pExpr->op==TK_LT || pExpr->op==TK_GT + || pExpr->op==TK_LE || pExpr->op==TK_GE + ); + assert( pExpr->op==op || (pExpr->op==TK_IS && op==TK_EQ) + || (pExpr->op==TK_ISNOT && op==TK_NE) ); + assert( p5==0 || pExpr->op!=op ); + assert( p5==SQLITE_NULLEQ || pExpr->op==op ); + + p5 |= SQLITE_STOREP2; + if( opx==TK_LE ) opx = TK_LT; + if( opx==TK_GE ) opx = TK_GT; + + regLeft = exprCodeSubselect(pParse, pLeft); + regRight = exprCodeSubselect(pParse, pRight); + + for(i=0; 1 /*Loop exits by "break"*/; i++){ + int regFree1 = 0, regFree2 = 0; + Expr *pL, *pR; + int r1, r2; + assert( i>=0 && i0 ) sqlite3ExprCachePush(pParse); + r1 = exprVectorRegister(pParse, pLeft, i, regLeft, &pL, ®Free1); + r2 = exprVectorRegister(pParse, pRight, i, regRight, &pR, ®Free2); + codeCompare(pParse, pL, pR, opx, r1, r2, dest, p5); + testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); + testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); + testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt); + testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); + testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq); + testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne); + sqlite3ReleaseTempReg(pParse, regFree1); + sqlite3ReleaseTempReg(pParse, regFree2); + if( i>0 ) sqlite3ExprCachePop(pParse); + if( i==nLeft-1 ){ + break; + } + if( opx==TK_EQ ){ + sqlite3VdbeAddOp2(v, OP_IfNot, dest, addrDone); VdbeCoverage(v); + p5 |= SQLITE_KEEPNULL; + }else if( opx==TK_NE ){ + sqlite3VdbeAddOp2(v, OP_If, dest, addrDone); VdbeCoverage(v); + p5 |= SQLITE_KEEPNULL; + }else{ + assert( op==TK_LT || op==TK_GT || op==TK_LE || op==TK_GE ); + sqlite3VdbeAddOp2(v, OP_ElseNotEq, 0, addrDone); + VdbeCoverageIf(v, op==TK_LT); + VdbeCoverageIf(v, op==TK_GT); + VdbeCoverageIf(v, op==TK_LE); + VdbeCoverageIf(v, op==TK_GE); + if( i==nLeft-2 ) opx = op; + } + } + sqlite3VdbeResolveLabel(v, addrDone); +} + #if SQLITE_MAX_EXPR_DEPTH>0 /* ** Check that argument nHeight is less than or equal to the maximum @@ -444,7 +724,7 @@ void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){ ** is allocated to hold the integer text and the dequote flag is ignored. */ Expr *sqlite3ExprAlloc( - sqlite3 *db, /* Handle for sqlite3DbMallocZero() (may be null) */ + sqlite3 *db, /* Handle for sqlite3DbMallocRawNN() */ int op, /* Expression opcode */ const Token *pToken, /* Token argument. Might be NULL */ int dequote /* True to dequote */ @@ -543,15 +823,19 @@ Expr *sqlite3PExpr( Parse *pParse, /* Parsing context */ int op, /* Expression opcode */ Expr *pLeft, /* Left operand */ - Expr *pRight, /* Right operand */ - const Token *pToken /* Argument token */ + Expr *pRight /* Right operand */ ){ Expr *p; if( op==TK_AND && pParse->nErr==0 ){ /* Take advantage of short-circuit false optimization for AND */ p = sqlite3ExprAnd(pParse->db, pLeft, pRight); }else{ - p = sqlite3ExprAlloc(pParse->db, op & TKFLG_MASK, pToken, 1); + p = sqlite3DbMallocRawNN(pParse->db, sizeof(Expr)); + if( p ){ + memset(p, 0, sizeof(Expr)); + p->op = op & TKFLG_MASK; + p->iAgg = -1; + } sqlite3ExprAttachSubtrees(pParse->db, p, pLeft, pRight); } if( p ) { @@ -654,7 +938,7 @@ Expr *sqlite3ExprFunction(Parse *pParse, ExprList *pList, Token *pToken){ ** variable number. ** ** Wildcards of the form "?nnn" are assigned the number "nnn". We make -** sure "nnn" is not too be to avoid a denial of service attack when +** sure "nnn" is not too big to avoid a denial of service attack when ** the SQL statement comes from an external source. ** ** Wildcards of the form ":aaa", "@aaa", or "$aaa" are assigned the same number @@ -662,28 +946,29 @@ Expr *sqlite3ExprFunction(Parse *pParse, ExprList *pList, Token *pToken){ ** instance of the wildcard, the next sequential variable number is ** assigned. */ -void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr){ +void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr, u32 n){ sqlite3 *db = pParse->db; const char *z; + ynVar x; if( pExpr==0 ) return; assert( !ExprHasProperty(pExpr, EP_IntValue|EP_Reduced|EP_TokenOnly) ); z = pExpr->u.zToken; assert( z!=0 ); assert( z[0]!=0 ); + assert( n==sqlite3Strlen30(z) ); if( z[1]==0 ){ /* Wildcard of the form "?". Assign the next variable number */ assert( z[0]=='?' ); - pExpr->iColumn = (ynVar)(++pParse->nVar); + x = (ynVar)(++pParse->nVar); }else{ - ynVar x = 0; - u32 n = sqlite3Strlen30(z); + int doAdd = 0; if( z[0]=='?' ){ /* Wildcard of the form "?nnn". Convert "nnn" to an integer and ** use it as the variable number */ i64 i; int bOk = 0==sqlite3Atoi64(&z[1], &i, n-1, SQLITE_UTF8); - pExpr->iColumn = x = (ynVar)i; + x = (ynVar)i; testcase( i==0 ); testcase( i==1 ); testcase( i==db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER]-1 ); @@ -691,44 +976,31 @@ void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr){ if( bOk==0 || i<1 || i>db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ){ sqlite3ErrorMsg(pParse, "variable number must be between ?1 and ?%d", db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER]); - x = 0; + return; } - if( i>pParse->nVar ){ - pParse->nVar = (int)i; + if( x>pParse->nVar ){ + pParse->nVar = (int)x; + doAdd = 1; + }else if( sqlite3VListNumToName(pParse->pVList, x)==0 ){ + doAdd = 1; } }else{ /* Wildcards like ":aaa", "$aaa" or "@aaa". Reuse the same variable ** number as the prior appearance of the same name, or if the name ** has never appeared before, reuse the same variable number */ - ynVar i; - for(i=0; inzVar; i++){ - if( pParse->azVar[i] && strcmp(pParse->azVar[i],z)==0 ){ - pExpr->iColumn = x = (ynVar)i+1; - break; - } - } - if( x==0 ) x = pExpr->iColumn = (ynVar)(++pParse->nVar); - } - if( x>0 ){ - if( x>pParse->nzVar ){ - char **a; - a = sqlite3DbRealloc(db, pParse->azVar, x*sizeof(a[0])); - if( a==0 ){ - assert( db->mallocFailed ); /* Error reported through mallocFailed */ - return; - } - pParse->azVar = a; - memset(&a[pParse->nzVar], 0, (x-pParse->nzVar)*sizeof(a[0])); - pParse->nzVar = x; - } - if( z[0]!='?' || pParse->azVar[x-1]==0 ){ - sqlite3DbFree(db, pParse->azVar[x-1]); - pParse->azVar[x-1] = sqlite3DbStrNDup(db, z, n); + x = (ynVar)sqlite3VListNameToNum(pParse->pVList, z, n); + if( x==0 ){ + x = (ynVar)(++pParse->nVar); + doAdd = 1; } } - } - if( !pParse->nErr && pParse->nVar>db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ){ + if( doAdd ){ + pParse->pVList = sqlite3VListAdd(db, pParse->pVList, z, n, x); + } + } + pExpr->iColumn = x; + if( x>db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ){ sqlite3ErrorMsg(pParse, "too many SQL variables"); } } @@ -740,18 +1012,25 @@ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){ assert( p!=0 ); /* Sanity check: Assert that the IntValue is non-negative if it exists */ assert( !ExprHasProperty(p, EP_IntValue) || p->u.iValue>=0 ); - if( !ExprHasProperty(p, EP_TokenOnly) ){ +#ifdef SQLITE_DEBUG + if( ExprHasProperty(p, EP_Leaf) && !ExprHasProperty(p, EP_TokenOnly) ){ + assert( p->pLeft==0 ); + assert( p->pRight==0 ); + assert( p->x.pSelect==0 ); + } +#endif + if( !ExprHasProperty(p, (EP_TokenOnly|EP_Leaf)) ){ /* The Expr.x union is never used at the same time as Expr.pRight */ assert( p->x.pList==0 || p->pRight==0 ); - sqlite3ExprDelete(db, p->pLeft); + if( p->pLeft && p->op!=TK_SELECT_COLUMN ) sqlite3ExprDeleteNN(db, p->pLeft); sqlite3ExprDelete(db, p->pRight); - if( ExprHasProperty(p, EP_MemToken) ) sqlite3DbFree(db, p->u.zToken); if( ExprHasProperty(p, EP_xIsSelect) ){ sqlite3SelectDelete(db, p->x.pSelect); }else{ sqlite3ExprListDelete(db, p->x.pList); } } + if( ExprHasProperty(p, EP_MemToken) ) sqlite3DbFree(db, p->u.zToken); if( !ExprHasProperty(p, EP_Static) ){ sqlite3DbFree(db, p); } @@ -810,7 +1089,7 @@ static int dupedExprStructSize(Expr *p, int flags){ assert( flags==EXPRDUP_REDUCE || flags==0 ); /* Only one flag value allowed */ assert( EXPR_FULLSIZE<=0xfff ); assert( (0xfff & (EP_Reduced|EP_TokenOnly))==0 ); - if( 0==flags ){ + if( 0==flags || p->op==TK_SELECT_COLUMN ){ nSize = EXPR_FULLSIZE; }else{ assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); @@ -928,7 +1207,7 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ memcpy(zToken, p->u.zToken, nToken); } - if( 0==((p->flags|pNew->flags) & EP_TokenOnly) ){ + if( 0==((p->flags|pNew->flags) & (EP_TokenOnly|EP_Leaf)) ){ /* Fill in the pNew->x.pSelect or pNew->x.pList member. */ if( ExprHasProperty(p, EP_xIsSelect) ){ pNew->x.pSelect = sqlite3SelectDup(db, p->x.pSelect, dupFlags); @@ -940,7 +1219,7 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ /* Fill in pNew->pLeft and pNew->pRight. */ if( ExprHasProperty(pNew, EP_Reduced|EP_TokenOnly) ){ zAlloc += dupedExprNodeSize(p, dupFlags); - if( ExprHasProperty(pNew, EP_Reduced) ){ + if( !ExprHasProperty(pNew, EP_TokenOnly|EP_Leaf) ){ pNew->pLeft = p->pLeft ? exprDup(db, p->pLeft, EXPRDUP_REDUCE, &zAlloc) : 0; pNew->pRight = p->pRight ? @@ -950,8 +1229,14 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ *pzBuffer = zAlloc; } }else{ - if( !ExprHasProperty(p, EP_TokenOnly) ){ - pNew->pLeft = sqlite3ExprDup(db, p->pLeft, 0); + if( !ExprHasProperty(p, EP_TokenOnly|EP_Leaf) ){ + if( pNew->op==TK_SELECT_COLUMN ){ + pNew->pLeft = p->pLeft; + assert( p->iColumn==0 || p->pRight==0 ); + assert( p->pRight==0 || p->pRight==p->pLeft ); + }else{ + pNew->pLeft = sqlite3ExprDup(db, p->pLeft, 0); + } pNew->pRight = sqlite3ExprDup(db, p->pRight, 0); } } @@ -1011,6 +1296,7 @@ ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags){ ExprList *pNew; struct ExprList_item *pItem, *pOldItem; int i; + Expr *pPriorSelectCol = 0; assert( db!=0 ); if( p==0 ) return 0; pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew) ); @@ -1025,7 +1311,24 @@ ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags){ pOldItem = p->a; for(i=0; inExpr; i++, pItem++, pOldItem++){ Expr *pOldExpr = pOldItem->pExpr; + Expr *pNewExpr; pItem->pExpr = sqlite3ExprDup(db, pOldExpr, flags); + if( pOldExpr + && pOldExpr->op==TK_SELECT_COLUMN + && (pNewExpr = pItem->pExpr)!=0 + ){ + assert( pNewExpr->iColumn==0 || i>0 ); + if( pNewExpr->iColumn==0 ){ + assert( pOldExpr->pLeft==pOldExpr->pRight ); + pPriorSelectCol = pNewExpr->pLeft = pNewExpr->pRight; + }else{ + assert( i>0 ); + assert( pItem[-1].pExpr!=0 ); + assert( pNewExpr->iColumn==pItem[-1].pExpr->iColumn+1 ); + assert( pPriorSelectCol==pItem[-1].pExpr->pLeft ); + pNewExpr->pLeft = pPriorSelectCol; + } + } pItem->zName = sqlite3DbStrDup(db, pOldItem->zName); pItem->zSpan = sqlite3DbStrDup(db, pOldItem->zSpan); pItem->sortOrder = pOldItem->sortOrder; @@ -1076,7 +1379,7 @@ SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){ } pTab = pNewItem->pTab = pOldItem->pTab; if( pTab ){ - pTab->nRef++; + pTab->nTabRef++; } pNewItem->pSelect = sqlite3SelectDup(db, pOldItem->pSelect, flags); pNewItem->pOn = sqlite3ExprDup(db, pOldItem->pOn, flags); @@ -1192,6 +1495,75 @@ no_mem: return 0; } +/* +** pColumns and pExpr form a vector assignment which is part of the SET +** clause of an UPDATE statement. Like this: +** +** (a,b,c) = (expr1,expr2,expr3) +** Or: (a,b,c) = (SELECT x,y,z FROM ....) +** +** For each term of the vector assignment, append new entries to the +** expression list pList. In the case of a subquery on the RHS, append +** TK_SELECT_COLUMN expressions. +*/ +ExprList *sqlite3ExprListAppendVector( + Parse *pParse, /* Parsing context */ + ExprList *pList, /* List to which to append. Might be NULL */ + IdList *pColumns, /* List of names of LHS of the assignment */ + Expr *pExpr /* Vector expression to be appended. Might be NULL */ +){ + sqlite3 *db = pParse->db; + int n; + int i; + int iFirst = pList ? pList->nExpr : 0; + /* pColumns can only be NULL due to an OOM but an OOM will cause an + ** exit prior to this routine being invoked */ + if( NEVER(pColumns==0) ) goto vector_append_error; + if( pExpr==0 ) goto vector_append_error; + + /* If the RHS is a vector, then we can immediately check to see that + ** the size of the RHS and LHS match. But if the RHS is a SELECT, + ** wildcards ("*") in the result set of the SELECT must be expanded before + ** we can do the size check, so defer the size check until code generation. + */ + if( pExpr->op!=TK_SELECT && pColumns->nId!=(n=sqlite3ExprVectorSize(pExpr)) ){ + sqlite3ErrorMsg(pParse, "%d columns assigned %d values", + pColumns->nId, n); + goto vector_append_error; + } + + for(i=0; inId; i++){ + Expr *pSubExpr = sqlite3ExprForVectorField(pParse, pExpr, i); + pList = sqlite3ExprListAppend(pParse, pList, pSubExpr); + if( pList ){ + assert( pList->nExpr==iFirst+i+1 ); + pList->a[pList->nExpr-1].zName = pColumns->a[i].zName; + pColumns->a[i].zName = 0; + } + } + + if( pExpr->op==TK_SELECT ){ + if( pList && pList->a[iFirst].pExpr ){ + Expr *pFirst = pList->a[iFirst].pExpr; + assert( pFirst->op==TK_SELECT_COLUMN ); + + /* Store the SELECT statement in pRight so it will be deleted when + ** sqlite3ExprListDelete() is called */ + pFirst->pRight = pExpr; + pExpr = 0; + + /* Remember the size of the LHS in iTable so that we can check that + ** the RHS and LHS sizes match during code generation. */ + pFirst->iTable = pColumns->nId; + } + } + +vector_append_error: + sqlite3ExprDelete(db, pExpr); + sqlite3IdListDelete(db, pColumns); + return pList; +} + /* ** Set the sort order for the last element on the given ExprList. */ @@ -1599,8 +1971,8 @@ static Select *isCandidateForInOpt(Expr *pX){ Select *p; SrcList *pSrc; ExprList *pEList; - Expr *pRes; Table *pTab; + int i; if( !ExprHasProperty(pX, EP_xIsSelect) ) return 0; /* Not a subquery */ if( ExprHasProperty(pX, EP_VarSelect) ) return 0; /* Correlated subq */ p = pX->x.pSelect; @@ -1623,23 +1995,18 @@ static Select *isCandidateForInOpt(Expr *pX){ assert( pTab->pSelect==0 ); /* FROM clause is not a view */ if( IsVirtual(pTab) ) return 0; /* FROM clause not a virtual table */ pEList = p->pEList; - if( pEList->nExpr!=1 ) return 0; /* One column in the result set */ - pRes = pEList->a[0].pExpr; - if( pRes->op!=TK_COLUMN ) return 0; /* Result is a column */ - assert( pRes->iTable==pSrc->a[0].iCursor ); /* Not a correlated subquery */ + assert( pEList!=0 ); + /* All SELECT results must be columns. */ + for(i=0; inExpr; i++){ + Expr *pRes = pEList->a[i].pExpr; + if( pRes->op!=TK_COLUMN ) return 0; + assert( pRes->iTable==pSrc->a[0].iCursor ); /* Not a correlated subquery */ + } return p; } #endif /* SQLITE_OMIT_SUBQUERY */ -/* -** Code an OP_Once instruction and allocate space for its flag. Return the -** address of the new instruction. -*/ -int sqlite3CodeOnce(Parse *pParse){ - Vdbe *v = sqlite3GetVdbe(pParse); /* Virtual machine being coded */ - return sqlite3VdbeAddOp1(v, OP_Once, pParse->nOnce++); -} - +#ifndef SQLITE_OMIT_SUBQUERY /* ** Generate code that checks the left-most column of index table iCur to see if ** it contains any NULL entries. Cause the register at regHasNull to be set @@ -1655,6 +2022,7 @@ static void sqlite3SetHasNullFlag(Vdbe *v, int iCur, int regHasNull){ VdbeComment((v, "first_entry_in(%d)", iCur)); sqlite3VdbeJumpHere(v, addr1); } +#endif #ifndef SQLITE_OMIT_SUBQUERY @@ -1699,7 +2067,7 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** An existing b-tree might be used if the RHS expression pX is a simple ** subquery such as: ** -** SELECT FROM +** SELECT , ... FROM
    ** ** If the RHS of the IN operator is a list or a more complex subquery, then ** an ephemeral table might need to be generated from the RHS and then @@ -1715,14 +2083,14 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** ** When IN_INDEX_LOOP is used (and the b-tree will be used to iterate ** through the set members) then the b-tree must not contain duplicates. -** An epheremal table must be used unless the selected is guaranteed -** to be unique - either because it is an INTEGER PRIMARY KEY or it -** has a UNIQUE constraint or UNIQUE index. +** An epheremal table must be used unless the selected columns are guaranteed +** to be unique - either because it is an INTEGER PRIMARY KEY or due to +** a UNIQUE constraint or index. ** ** When IN_INDEX_MEMBERSHIP is used (and the b-tree will be used ** for fast set membership tests) then an epheremal table must -** be used unless is an INTEGER PRIMARY KEY or an index can -** be found with as its left-most column. +** be used unless is a single INTEGER PRIMARY KEY column or an +** index can be found with the specified as its left-most. ** ** If the IN_INDEX_NOOP_OK and IN_INDEX_MEMBERSHIP are both set and ** if the RHS of the IN operator is a list (not a subquery) then this @@ -1743,9 +2111,26 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** the value in that register will be NULL if the b-tree contains one or more ** NULL values, and it will be some non-NULL value if the b-tree contains no ** NULL values. +** +** If the aiMap parameter is not NULL, it must point to an array containing +** one element for each column returned by the SELECT statement on the RHS +** of the IN(...) operator. The i'th entry of the array is populated with the +** offset of the index column that matches the i'th column returned by the +** SELECT. For example, if the expression and selected index are: +** +** (?,?,?) IN (SELECT a, b, c FROM t1) +** CREATE INDEX i1 ON t1(b, c, a); +** +** then aiMap[] is populated with {2, 0, 1}. */ #ifndef SQLITE_OMIT_SUBQUERY -int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int *prRhsHasNull){ +int sqlite3FindInIndex( + Parse *pParse, /* Parsing context */ + Expr *pX, /* The right-hand side (RHS) of the IN operator */ + u32 inFlags, /* IN_INDEX_LOOP, _MEMBERSHIP, and/or _NOOP_OK */ + int *prRhsHasNull, /* Register holding NULL status. See notes */ + int *aiMap /* Mapping from Index fields to RHS fields */ +){ Select *p; /* SELECT to the right of IN operator */ int eType = 0; /* Type of RHS table. IN_INDEX_* */ int iTab = pParse->nTab++; /* Cursor of the RHS table */ @@ -1755,36 +2140,46 @@ int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int *prRhsHasNull){ assert( pX->op==TK_IN ); mustBeUnique = (inFlags & IN_INDEX_LOOP)!=0; + /* If the RHS of this IN(...) operator is a SELECT, and if it matters + ** whether or not the SELECT result contains NULL values, check whether + ** or not NULL is actually possible (it may not be, for example, due + ** to NOT NULL constraints in the schema). If no NULL values are possible, + ** set prRhsHasNull to 0 before continuing. */ + if( prRhsHasNull && (pX->flags & EP_xIsSelect) ){ + int i; + ExprList *pEList = pX->x.pSelect->pEList; + for(i=0; inExpr; i++){ + if( sqlite3ExprCanBeNull(pEList->a[i].pExpr) ) break; + } + if( i==pEList->nExpr ){ + prRhsHasNull = 0; + } + } + /* Check to see if an existing table or index can be used to ** satisfy the query. This is preferable to generating a new - ** ephemeral table. - */ + ** ephemeral table. */ if( pParse->nErr==0 && (p = isCandidateForInOpt(pX))!=0 ){ sqlite3 *db = pParse->db; /* Database connection */ Table *pTab; /* Table
    . */ - Expr *pExpr; /* Expression */ - i16 iCol; /* Index of column */ i16 iDb; /* Database idx for pTab */ + ExprList *pEList = p->pEList; + int nExpr = pEList->nExpr; assert( p->pEList!=0 ); /* Because of isCandidateForInOpt(p) */ assert( p->pEList->a[0].pExpr!=0 ); /* Because of isCandidateForInOpt(p) */ assert( p->pSrc!=0 ); /* Because of isCandidateForInOpt(p) */ pTab = p->pSrc->a[0].pTab; - pExpr = p->pEList->a[0].pExpr; - iCol = (i16)pExpr->iColumn; - + /* Code an OP_Transaction and OP_TableLock for
    . */ iDb = sqlite3SchemaToIndex(db, pTab->pSchema); sqlite3CodeVerifySchema(pParse, iDb); sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); - /* This function is only called from two places. In both cases the vdbe - ** has already been allocated. So assume sqlite3GetVdbe() is always - ** successful here. - */ - assert(v); - if( iCol<0 ){ - int iAddr = sqlite3CodeOnce(pParse); + assert(v); /* sqlite3GetVdbe() has always been previously called */ + if( nExpr==1 && pEList->a[0].pExpr->iColumn<0 ){ + /* The "x IN (SELECT rowid FROM table)" case */ + int iAddr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); sqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead); @@ -1793,44 +2188,109 @@ int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int *prRhsHasNull){ sqlite3VdbeJumpHere(v, iAddr); }else{ Index *pIdx; /* Iterator variable */ + int affinity_ok = 1; + int i; - /* The collation sequence used by the comparison. If an index is to - ** be used in place of a temp-table, it must be ordered according - ** to this collation sequence. */ - CollSeq *pReq = sqlite3BinaryCompareCollSeq(pParse, pX->pLeft, pExpr); - - /* Check that the affinity that will be used to perform the - ** comparison is the same as the affinity of the column. If - ** it is not, it is not possible to use any index. - */ - int affinity_ok = sqlite3IndexAffinityOk(pX, pTab->aCol[iCol].affinity); - - for(pIdx=pTab->pIndex; pIdx && eType==0 && affinity_ok; pIdx=pIdx->pNext){ - if( (pIdx->aiColumn[0]==iCol) - && sqlite3FindCollSeq(db, ENC(db), pIdx->azColl[0], 0)==pReq - && (!mustBeUnique || (pIdx->nKeyCol==1 && IsUniqueIndex(pIdx))) - ){ - int iAddr = sqlite3CodeOnce(pParse); VdbeCoverage(v); - sqlite3VdbeAddOp3(v, OP_OpenRead, iTab, pIdx->tnum, iDb); - sqlite3VdbeSetP4KeyInfo(pParse, pIdx); - VdbeComment((v, "%s", pIdx->zName)); - assert( IN_INDEX_INDEX_DESC == IN_INDEX_INDEX_ASC+1 ); - eType = IN_INDEX_INDEX_ASC + pIdx->aSortOrder[0]; - - if( prRhsHasNull && !pTab->aCol[iCol].notNull ){ -#ifdef SQLITE_ENABLE_COLUMN_USED_MASK - const i64 sOne = 1; - sqlite3VdbeAddOp4Dup8(v, OP_ColumnsUsed, - iTab, 0, 0, (u8*)&sOne, P4_INT64); -#endif - *prRhsHasNull = ++pParse->nMem; - sqlite3SetHasNullFlag(v, iTab, *prRhsHasNull); - } - sqlite3VdbeJumpHere(v, iAddr); + /* Check that the affinity that will be used to perform each + ** comparison is the same as the affinity of each column in table + ** on the RHS of the IN operator. If it not, it is not possible to + ** use any index of the RHS table. */ + for(i=0; ipLeft, i); + int iCol = pEList->a[i].pExpr->iColumn; + char idxaff = sqlite3TableColumnAffinity(pTab,iCol); /* RHS table */ + char cmpaff = sqlite3CompareAffinity(pLhs, idxaff); + testcase( cmpaff==SQLITE_AFF_BLOB ); + testcase( cmpaff==SQLITE_AFF_TEXT ); + switch( cmpaff ){ + case SQLITE_AFF_BLOB: + break; + case SQLITE_AFF_TEXT: + /* sqlite3CompareAffinity() only returns TEXT if one side or the + ** other has no affinity and the other side is TEXT. Hence, + ** the only way for cmpaff to be TEXT is for idxaff to be TEXT + ** and for the term on the LHS of the IN to have no affinity. */ + assert( idxaff==SQLITE_AFF_TEXT ); + break; + default: + affinity_ok = sqlite3IsNumericAffinity(idxaff); } } - } - } + + if( affinity_ok ){ + /* Search for an existing index that will work for this IN operator */ + for(pIdx=pTab->pIndex; pIdx && eType==0; pIdx=pIdx->pNext){ + Bitmask colUsed; /* Columns of the index used */ + Bitmask mCol; /* Mask for the current column */ + if( pIdx->nColumnnColumn==BMS-2 ); + testcase( pIdx->nColumn==BMS-1 ); + if( pIdx->nColumn>=BMS-1 ) continue; + if( mustBeUnique ){ + if( pIdx->nKeyCol>nExpr + ||(pIdx->nColumn>nExpr && !IsUniqueIndex(pIdx)) + ){ + continue; /* This index is not unique over the IN RHS columns */ + } + } + + colUsed = 0; /* Columns of index used so far */ + for(i=0; ipLeft, i); + Expr *pRhs = pEList->a[i].pExpr; + CollSeq *pReq = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs); + int j; + + assert( pReq!=0 || pRhs->iColumn==XN_ROWID || pParse->nErr ); + for(j=0; jaiColumn[j]!=pRhs->iColumn ) continue; + assert( pIdx->azColl[j] ); + if( pReq!=0 && sqlite3StrICmp(pReq->zName, pIdx->azColl[j])!=0 ){ + continue; + } + break; + } + if( j==nExpr ) break; + mCol = MASKBIT(j); + if( mCol & colUsed ) break; /* Each column used only once */ + colUsed |= mCol; + if( aiMap ) aiMap[i] = j; + } + + assert( i==nExpr || colUsed!=(MASKBIT(nExpr)-1) ); + if( colUsed==(MASKBIT(nExpr)-1) ){ + /* If we reach this point, that means the index pIdx is usable */ + int iAddr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); +#ifndef SQLITE_OMIT_EXPLAIN + sqlite3VdbeAddOp4(v, OP_Explain, 0, 0, 0, + sqlite3MPrintf(db, "USING INDEX %s FOR IN-OPERATOR",pIdx->zName), + P4_DYNAMIC); +#endif + sqlite3VdbeAddOp3(v, OP_OpenRead, iTab, pIdx->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); + VdbeComment((v, "%s", pIdx->zName)); + assert( IN_INDEX_INDEX_DESC == IN_INDEX_INDEX_ASC+1 ); + eType = IN_INDEX_INDEX_ASC + pIdx->aSortOrder[0]; + + if( prRhsHasNull ){ +#ifdef SQLITE_ENABLE_COLUMN_USED_MASK + i64 mask = (1<nMem; + if( nExpr==1 ){ + sqlite3SetHasNullFlag(v, iTab, *prRhsHasNull); + } + } + sqlite3VdbeJumpHere(v, iAddr); + } + } /* End loop over indexes */ + } /* End if( affinity_ok ) */ + } /* End if not an rowid index */ + } /* End attempt to optimize using an index */ /* If no preexisting index is available for the IN clause ** and IN_INDEX_NOOP is an allowed reply @@ -1846,7 +2306,6 @@ int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int *prRhsHasNull){ ){ eType = IN_INDEX_NOOP; } - if( eType==0 ){ /* Could not find an existing table or index to use as the RHS b-tree. @@ -1868,10 +2327,85 @@ int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int *prRhsHasNull){ }else{ pX->iTable = iTab; } + + if( aiMap && eType!=IN_INDEX_INDEX_ASC && eType!=IN_INDEX_INDEX_DESC ){ + int i, n; + n = sqlite3ExprVectorSize(pX->pLeft); + for(i=0; ipLeft; + int nVal = sqlite3ExprVectorSize(pLeft); + Select *pSelect = (pExpr->flags & EP_xIsSelect) ? pExpr->x.pSelect : 0; + char *zRet; + + assert( pExpr->op==TK_IN ); + zRet = sqlite3DbMallocZero(pParse->db, nVal+1); + if( zRet ){ + int i; + for(i=0; ipEList->a[i].pExpr, a); + }else{ + zRet[i] = a; + } + } + zRet[nVal] = '\0'; + } + return zRet; +} +#endif + +#ifndef SQLITE_OMIT_SUBQUERY +/* +** Load the Parse object passed as the first argument with an error +** message of the form: +** +** "sub-select returns N columns - expected M" +*/ +void sqlite3SubselectError(Parse *pParse, int nActual, int nExpect){ + const char *zFmt = "sub-select returns %d columns - expected %d"; + sqlite3ErrorMsg(pParse, zFmt, nActual, nExpect); +} +#endif + +/* +** Expression pExpr is a vector that has been used in a context where +** it is not permitted. If pExpr is a sub-select vector, this routine +** loads the Parse object with a message of the form: +** +** "sub-select returns N columns - expected 1" +** +** Or, if it is a regular scalar vector: +** +** "row value misused" +*/ +void sqlite3VectorErrorMsg(Parse *pParse, Expr *pExpr){ +#ifndef SQLITE_OMIT_SUBQUERY + if( pExpr->flags & EP_xIsSelect ){ + sqlite3SubselectError(pParse, pExpr->x.pSelect->pEList->nExpr, 1); + }else +#endif + { + sqlite3ErrorMsg(pParse, "row value misused"); + } +} + /* ** Generate code for scalar subqueries used as a subquery expression, EXISTS, ** or IN operators. Examples: @@ -1897,7 +2431,9 @@ int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int *prRhsHasNull){ ** value to non-NULL if the RHS is NULL-free. ** ** For a SELECT or EXISTS operator, return the register that holds the -** result. For IN operators or if an error occurs, the return value is 0. +** result. For a multi-column SELECT, the result is stored in a contiguous +** array of registers and the return value is the register of the left-most +** result column. Return 0 for IN operators or if an error occurs. */ #ifndef SQLITE_OMIT_SUBQUERY int sqlite3CodeSubselect( @@ -1912,8 +2448,8 @@ int sqlite3CodeSubselect( if( NEVER(v==0) ) return 0; sqlite3ExprCachePush(pParse); - /* This code must be run in its entirety every time it is encountered - ** if any of the following is true: + /* The evaluation of the IN/EXISTS/SELECT must be repeated every time it + ** is encountered if any of the following is true: ** ** * The right-hand side is a correlated subquery ** * The right-hand side is an expression list containing variables @@ -1923,7 +2459,7 @@ int sqlite3CodeSubselect( ** save the results, and reuse the same result on subsequent invocations. */ if( !ExprHasProperty(pExpr, EP_VarSelect) ){ - jmpIfDynamic = sqlite3CodeOnce(pParse); VdbeCoverage(v); + jmpIfDynamic = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } #ifndef SQLITE_OMIT_EXPLAIN @@ -1939,17 +2475,18 @@ int sqlite3CodeSubselect( switch( pExpr->op ){ case TK_IN: { - char affinity; /* Affinity of the LHS of the IN */ int addr; /* Address of OP_OpenEphemeral instruction */ Expr *pLeft = pExpr->pLeft; /* the LHS of the IN operator */ KeyInfo *pKeyInfo = 0; /* Key information */ - - affinity = sqlite3ExprAffinity(pLeft); + int nVal; /* Size of vector pLeft */ + + nVal = sqlite3ExprVectorSize(pLeft); + assert( !isRowid || nVal==1 ); /* Whether this is an 'x IN(SELECT...)' or an 'x IN()' ** expression it is handled the same way. An ephemeral table is - ** filled with single-field index keys representing the results - ** from the SELECT or the . + ** filled with index keys representing the results from the + ** SELECT or the . ** ** If the 'x' expression is a column value, or the SELECT... ** statement returns a column value, then the affinity of that @@ -1960,8 +2497,9 @@ int sqlite3CodeSubselect( ** is used. */ pExpr->iTable = pParse->nTab++; - addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pExpr->iTable, !isRowid); - pKeyInfo = isRowid ? 0 : sqlite3KeyInfoAlloc(pParse->db, 1, 1); + addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, + pExpr->iTable, (isRowid?0:nVal)); + pKeyInfo = isRowid ? 0 : sqlite3KeyInfoAlloc(pParse->db, nVal, 1); if( ExprHasProperty(pExpr, EP_xIsSelect) ){ /* Case 1: expr IN (SELECT ...) @@ -1970,27 +2508,37 @@ int sqlite3CodeSubselect( ** table allocated and opened above. */ Select *pSelect = pExpr->x.pSelect; - SelectDest dest; - ExprList *pEList; + ExprList *pEList = pSelect->pEList; assert( !isRowid ); - sqlite3SelectDestInit(&dest, SRT_Set, pExpr->iTable); - dest.affSdst = (u8)affinity; - assert( (pExpr->iTable&0x0000FFFF)==pExpr->iTable ); - pSelect->iLimit = 0; - testcase( pSelect->selFlags & SF_Distinct ); - testcase( pKeyInfo==0 ); /* Caused by OOM in sqlite3KeyInfoAlloc() */ - if( sqlite3Select(pParse, pSelect, &dest) ){ - sqlite3KeyInfoUnref(pKeyInfo); - return 0; + /* If the LHS and RHS of the IN operator do not match, that + ** error will have been caught long before we reach this point. */ + if( ALWAYS(pEList->nExpr==nVal) ){ + SelectDest dest; + int i; + sqlite3SelectDestInit(&dest, SRT_Set, pExpr->iTable); + dest.zAffSdst = exprINAffinity(pParse, pExpr); + assert( (pExpr->iTable&0x0000FFFF)==pExpr->iTable ); + pSelect->iLimit = 0; + testcase( pSelect->selFlags & SF_Distinct ); + testcase( pKeyInfo==0 ); /* Caused by OOM in sqlite3KeyInfoAlloc() */ + if( sqlite3Select(pParse, pSelect, &dest) ){ + sqlite3DbFree(pParse->db, dest.zAffSdst); + sqlite3KeyInfoUnref(pKeyInfo); + return 0; + } + sqlite3DbFree(pParse->db, dest.zAffSdst); + assert( pKeyInfo!=0 ); /* OOM will cause exit after sqlite3Select() */ + assert( pEList!=0 ); + assert( pEList->nExpr>0 ); + assert( sqlite3KeyInfoIsWriteable(pKeyInfo) ); + for(i=0; iaColl[i] = sqlite3BinaryCompareCollSeq( + pParse, p, pEList->a[i].pExpr + ); + } } - pEList = pSelect->pEList; - assert( pKeyInfo!=0 ); /* OOM will cause exit after sqlite3Select() */ - assert( pEList!=0 ); - assert( pEList->nExpr>0 ); - assert( sqlite3KeyInfoIsWriteable(pKeyInfo) ); - pKeyInfo->aColl[0] = sqlite3BinaryCompareCollSeq(pParse, pExpr->pLeft, - pEList->a[0].pExpr); }else if( ALWAYS(pExpr->x.pList!=0) ){ /* Case 2: expr IN (exprlist) ** @@ -1999,11 +2547,13 @@ int sqlite3CodeSubselect( ** that columns affinity when building index keys. If is not ** a column, use numeric affinity. */ + char affinity; /* Affinity of the LHS of the IN */ int i; ExprList *pList = pExpr->x.pList; struct ExprList_item *pItem; int r1, r2, r3; + affinity = sqlite3ExprAffinity(pLeft); if( !affinity ){ affinity = SQLITE_AFF_BLOB; } @@ -2043,7 +2593,7 @@ int sqlite3CodeSubselect( }else{ sqlite3VdbeAddOp4(v, OP_MakeRecord, r3, 1, r2, &affinity, 1); sqlite3ExprCacheAffinityChange(pParse, r3, 1); - sqlite3VdbeAddOp2(v, OP_IdxInsert, pExpr->iTable, r2); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, pExpr->iTable, r2, r3, 1); } } } @@ -2059,26 +2609,37 @@ int sqlite3CodeSubselect( case TK_EXISTS: case TK_SELECT: default: { - /* If this has to be a scalar SELECT. Generate code to put the - ** value of this select in a memory cell and record the number - ** of the memory cell in iColumn. If this is an EXISTS, write - ** an integer 0 (not exists) or 1 (exists) into a memory cell - ** and record that memory cell in iColumn. + /* Case 3: (SELECT ... FROM ...) + ** or: EXISTS(SELECT ... FROM ...) + ** + ** For a SELECT, generate code to put the values for all columns of + ** the first row into an array of registers and return the index of + ** the first register. + ** + ** If this is an EXISTS, write an integer 0 (not exists) or 1 (exists) + ** into a register and return that register number. + ** + ** In both cases, the query is augmented with "LIMIT 1". Any + ** preexisting limit is discarded in place of the new LIMIT 1. */ Select *pSel; /* SELECT statement to encode */ - SelectDest dest; /* How to deal with SELECt result */ + SelectDest dest; /* How to deal with SELECT result */ + int nReg; /* Registers to allocate */ testcase( pExpr->op==TK_EXISTS ); testcase( pExpr->op==TK_SELECT ); assert( pExpr->op==TK_EXISTS || pExpr->op==TK_SELECT ); - assert( ExprHasProperty(pExpr, EP_xIsSelect) ); + pSel = pExpr->x.pSelect; - sqlite3SelectDestInit(&dest, 0, ++pParse->nMem); + nReg = pExpr->op==TK_SELECT ? pSel->pEList->nExpr : 1; + sqlite3SelectDestInit(&dest, 0, pParse->nMem+1); + pParse->nMem += nReg; if( pExpr->op==TK_SELECT ){ dest.eDest = SRT_Mem; dest.iSdst = dest.iSDParm; - sqlite3VdbeAddOp2(v, OP_Null, 0, dest.iSDParm); + dest.nSdst = nReg; + sqlite3VdbeAddOp3(v, OP_Null, 0, dest.iSDParm, dest.iSDParm+nReg-1); VdbeComment((v, "Init subquery result")); }else{ dest.eDest = SRT_Exists; @@ -2086,8 +2647,8 @@ int sqlite3CodeSubselect( VdbeComment((v, "Init EXISTS result")); } sqlite3ExprDelete(pParse->db, pSel->pLimit); - pSel->pLimit = sqlite3PExpr(pParse, TK_INTEGER, 0, 0, - &sqlite3IntTokens[1]); + pSel->pLimit = sqlite3ExprAlloc(pParse->db, TK_INTEGER, + &sqlite3IntTokens[1], 0); pSel->iLimit = 0; pSel->selFlags &= ~SF_MultiValue; if( sqlite3Select(pParse, pSel, &dest) ){ @@ -2112,6 +2673,28 @@ int sqlite3CodeSubselect( } #endif /* SQLITE_OMIT_SUBQUERY */ +#ifndef SQLITE_OMIT_SUBQUERY +/* +** Expr pIn is an IN(...) expression. This function checks that the +** sub-select on the RHS of the IN() operator has the same number of +** columns as the vector on the LHS. Or, if the RHS of the IN() is not +** a sub-query, that the LHS is a vector of size 1. +*/ +int sqlite3ExprCheckIN(Parse *pParse, Expr *pIn){ + int nVector = sqlite3ExprVectorSize(pIn->pLeft); + if( (pIn->flags & EP_xIsSelect) ){ + if( nVector!=pIn->x.pSelect->pEList->nExpr ){ + sqlite3SubselectError(pParse, pIn->x.pSelect->pEList->nExpr, nVector); + return 1; + } + }else if( nVector!=1 ){ + sqlite3VectorErrorMsg(pParse, pIn->pLeft); + return 1; + } + return 0; +} +#endif + #ifndef SQLITE_OMIT_SUBQUERY /* ** Generate code for an IN expression. @@ -2119,16 +2702,24 @@ int sqlite3CodeSubselect( ** x IN (SELECT ...) ** x IN (value, value, ...) ** -** The left-hand side (LHS) is a scalar expression. The right-hand side (RHS) -** is an array of zero or more values. The expression is true if the LHS is -** contained within the RHS. The value of the expression is unknown (NULL) -** if the LHS is NULL or if the LHS is not contained within the RHS and the -** RHS contains one or more NULL values. +** The left-hand side (LHS) is a scalar or vector expression. The +** right-hand side (RHS) is an array of zero or more scalar values, or a +** subquery. If the RHS is a subquery, the number of result columns must +** match the number of columns in the vector on the LHS. If the RHS is +** a list of values, the LHS must be a scalar. +** +** The IN operator is true if the LHS value is contained within the RHS. +** The result is false if the LHS is definitely not in the RHS. The +** result is NULL if the presence of the LHS in the RHS cannot be +** determined due to NULLs. ** ** This routine generates code that jumps to destIfFalse if the LHS is not ** contained within the RHS. If due to NULLs we cannot determine if the LHS ** is contained in the RHS then jump to destIfNull. If the LHS is contained ** within the RHS then fall through. +** +** See the separate in-operator.md documentation file in the canonical +** SQLite source tree for additional information. */ static void sqlite3ExprCodeIN( Parse *pParse, /* Parsing and code generating context */ @@ -2137,36 +2728,83 @@ static void sqlite3ExprCodeIN( int destIfNull /* Jump here if the results are unknown due to NULLs */ ){ int rRhsHasNull = 0; /* Register that is true if RHS contains NULL values */ - char affinity; /* Comparison affinity to use */ int eType; /* Type of the RHS */ - int r1; /* Temporary use register */ + int rLhs; /* Register(s) holding the LHS values */ + int rLhsOrig; /* LHS values prior to reordering by aiMap[] */ Vdbe *v; /* Statement under construction */ + int *aiMap = 0; /* Map from vector field to index column */ + char *zAff = 0; /* Affinity string for comparisons */ + int nVector; /* Size of vectors for this IN operator */ + int iDummy; /* Dummy parameter to exprCodeVector() */ + Expr *pLeft; /* The LHS of the IN operator */ + int i; /* loop counter */ + int destStep2; /* Where to jump when NULLs seen in step 2 */ + int destStep6 = 0; /* Start of code for Step 6 */ + int addrTruthOp; /* Address of opcode that determines the IN is true */ + int destNotNull; /* Jump here if a comparison is not true in step 6 */ + int addrTop; /* Top of the step-6 loop */ - /* Compute the RHS. After this step, the table with cursor - ** pExpr->iTable will contains the values that make up the RHS. - */ + pLeft = pExpr->pLeft; + if( sqlite3ExprCheckIN(pParse, pExpr) ) return; + zAff = exprINAffinity(pParse, pExpr); + nVector = sqlite3ExprVectorSize(pExpr->pLeft); + aiMap = (int*)sqlite3DbMallocZero( + pParse->db, nVector*(sizeof(int) + sizeof(char)) + 1 + ); + if( pParse->db->mallocFailed ) goto sqlite3ExprCodeIN_oom_error; + + /* Attempt to compute the RHS. After this step, if anything other than + ** IN_INDEX_NOOP is returned, the table opened ith cursor pExpr->iTable + ** contains the values that make up the RHS. If IN_INDEX_NOOP is returned, + ** the RHS has not yet been coded. */ v = pParse->pVdbe; assert( v!=0 ); /* OOM detected prior to this routine */ VdbeNoopComment((v, "begin IN expr")); eType = sqlite3FindInIndex(pParse, pExpr, IN_INDEX_MEMBERSHIP | IN_INDEX_NOOP_OK, - destIfFalse==destIfNull ? 0 : &rRhsHasNull); + destIfFalse==destIfNull ? 0 : &rRhsHasNull, aiMap); - /* Figure out the affinity to use to create a key from the results - ** of the expression. affinityStr stores a static string suitable for - ** P4 of OP_MakeRecord. - */ - affinity = comparisonAffinity(pExpr); + assert( pParse->nErr || nVector==1 || eType==IN_INDEX_EPH + || eType==IN_INDEX_INDEX_ASC || eType==IN_INDEX_INDEX_DESC + ); +#ifdef SQLITE_DEBUG + /* Confirm that aiMap[] contains nVector integer values between 0 and + ** nVector-1. */ + for(i=0; i from " IN (...)". + /* Code the LHS, the from " IN (...)". If the LHS is a + ** vector, then it is stored in an array of nVector registers starting + ** at r1. + ** + ** sqlite3FindInIndex() might have reordered the fields of the LHS vector + ** so that the fields are in the same order as an existing index. The + ** aiMap[] array contains a mapping from the original LHS field order to + ** the field order that matches the RHS index. */ sqlite3ExprCachePush(pParse); - r1 = sqlite3GetTempReg(pParse); - sqlite3ExprCode(pParse, pExpr->pLeft, r1); + rLhsOrig = exprCodeVector(pParse, pLeft, &iDummy); + for(i=0; ix.pList; @@ -2178,7 +2816,7 @@ static void sqlite3ExprCodeIN( assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); if( destIfNull!=destIfFalse ){ regCkNull = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp3(v, OP_BitAnd, r1, r1, regCkNull); + sqlite3VdbeAddOp3(v, OP_BitAnd, rLhs, rLhs, regCkNull); } for(ii=0; iinExpr; ii++){ r2 = sqlite3ExprCodeTemp(pParse, pList->a[ii].pExpr, ®ToFree); @@ -2186,16 +2824,16 @@ static void sqlite3ExprCodeIN( sqlite3VdbeAddOp3(v, OP_BitAnd, regCkNull, r2, regCkNull); } if( iinExpr-1 || destIfNull!=destIfFalse ){ - sqlite3VdbeAddOp4(v, OP_Eq, r1, labelOk, r2, + sqlite3VdbeAddOp4(v, OP_Eq, rLhs, labelOk, r2, (void*)pColl, P4_COLLSEQ); VdbeCoverageIf(v, iinExpr-1); VdbeCoverageIf(v, ii==pList->nExpr-1); - sqlite3VdbeChangeP5(v, affinity); + sqlite3VdbeChangeP5(v, zAff[0]); }else{ assert( destIfNull==destIfFalse ); - sqlite3VdbeAddOp4(v, OP_Ne, r1, destIfFalse, r2, + sqlite3VdbeAddOp4(v, OP_Ne, rLhs, destIfFalse, r2, (void*)pColl, P4_COLLSEQ); VdbeCoverage(v); - sqlite3VdbeChangeP5(v, affinity | SQLITE_JUMPIFNULL); + sqlite3VdbeChangeP5(v, zAff[0] | SQLITE_JUMPIFNULL); } sqlite3ReleaseTempReg(pParse, regToFree); } @@ -2205,77 +2843,113 @@ static void sqlite3ExprCodeIN( } sqlite3VdbeResolveLabel(v, labelOk); sqlite3ReleaseTempReg(pParse, regCkNull); + goto sqlite3ExprCodeIN_finished; + } + + /* Step 2: Check to see if the LHS contains any NULL columns. If the + ** LHS does contain NULLs then the result must be either FALSE or NULL. + ** We will then skip the binary search of the RHS. + */ + if( destIfNull==destIfFalse ){ + destStep2 = destIfFalse; }else{ - - /* If the LHS is NULL, then the result is either false or NULL depending - ** on whether the RHS is empty or not, respectively. - */ - if( sqlite3ExprCanBeNull(pExpr->pLeft) ){ - if( destIfNull==destIfFalse ){ - /* Shortcut for the common case where the false and NULL outcomes are - ** the same. */ - sqlite3VdbeAddOp2(v, OP_IsNull, r1, destIfNull); VdbeCoverage(v); - }else{ - int addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, r1); VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_Rewind, pExpr->iTable, destIfFalse); - VdbeCoverage(v); - sqlite3VdbeGoto(v, destIfNull); - sqlite3VdbeJumpHere(v, addr1); - } - } - - if( eType==IN_INDEX_ROWID ){ - /* In this case, the RHS is the ROWID of table b-tree - */ - sqlite3VdbeAddOp3(v, OP_SeekRowid, pExpr->iTable, destIfFalse, r1); + destStep2 = destStep6 = sqlite3VdbeMakeLabel(v); + } + for(i=0; ipLeft, i); + if( sqlite3ExprCanBeNull(p) ){ + sqlite3VdbeAddOp2(v, OP_IsNull, rLhs+i, destStep2); VdbeCoverage(v); - }else{ - /* In this case, the RHS is an index b-tree. - */ - sqlite3VdbeAddOp4(v, OP_Affinity, r1, 1, 0, &affinity, 1); - - /* If the set membership test fails, then the result of the - ** "x IN (...)" expression must be either 0 or NULL. If the set - ** contains no NULL values, then the result is 0. If the set - ** contains one or more NULL values, then the result of the - ** expression is also NULL. - */ - assert( destIfFalse!=destIfNull || rRhsHasNull==0 ); - if( rRhsHasNull==0 ){ - /* This branch runs if it is known at compile time that the RHS - ** cannot contain NULL values. This happens as the result - ** of a "NOT NULL" constraint in the database schema. - ** - ** Also run this branch if NULL is equivalent to FALSE - ** for this particular IN operator. - */ - sqlite3VdbeAddOp4Int(v, OP_NotFound, pExpr->iTable, destIfFalse, r1, 1); - VdbeCoverage(v); - }else{ - /* In this branch, the RHS of the IN might contain a NULL and - ** the presence of a NULL on the RHS makes a difference in the - ** outcome. - */ - int addr1; - - /* First check to see if the LHS is contained in the RHS. If so, - ** then the answer is TRUE the presence of NULLs in the RHS does - ** not matter. If the LHS is not contained in the RHS, then the - ** answer is NULL if the RHS contains NULLs and the answer is - ** FALSE if the RHS is NULL-free. - */ - addr1 = sqlite3VdbeAddOp4Int(v, OP_Found, pExpr->iTable, 0, r1, 1); - VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_IsNull, rRhsHasNull, destIfNull); - VdbeCoverage(v); - sqlite3VdbeGoto(v, destIfFalse); - sqlite3VdbeJumpHere(v, addr1); - } } } - sqlite3ReleaseTempReg(pParse, r1); + + /* Step 3. The LHS is now known to be non-NULL. Do the binary search + ** of the RHS using the LHS as a probe. If found, the result is + ** true. + */ + if( eType==IN_INDEX_ROWID ){ + /* In this case, the RHS is the ROWID of table b-tree and so we also + ** know that the RHS is non-NULL. Hence, we combine steps 3 and 4 + ** into a single opcode. */ + sqlite3VdbeAddOp3(v, OP_SeekRowid, pExpr->iTable, destIfFalse, rLhs); + VdbeCoverage(v); + addrTruthOp = sqlite3VdbeAddOp0(v, OP_Goto); /* Return True */ + }else{ + sqlite3VdbeAddOp4(v, OP_Affinity, rLhs, nVector, 0, zAff, nVector); + if( destIfFalse==destIfNull ){ + /* Combine Step 3 and Step 5 into a single opcode */ + sqlite3VdbeAddOp4Int(v, OP_NotFound, pExpr->iTable, destIfFalse, + rLhs, nVector); VdbeCoverage(v); + goto sqlite3ExprCodeIN_finished; + } + /* Ordinary Step 3, for the case where FALSE and NULL are distinct */ + addrTruthOp = sqlite3VdbeAddOp4Int(v, OP_Found, pExpr->iTable, 0, + rLhs, nVector); VdbeCoverage(v); + } + + /* Step 4. If the RHS is known to be non-NULL and we did not find + ** an match on the search above, then the result must be FALSE. + */ + if( rRhsHasNull && nVector==1 ){ + sqlite3VdbeAddOp2(v, OP_NotNull, rRhsHasNull, destIfFalse); + VdbeCoverage(v); + } + + /* Step 5. If we do not care about the difference between NULL and + ** FALSE, then just return false. + */ + if( destIfFalse==destIfNull ) sqlite3VdbeGoto(v, destIfFalse); + + /* Step 6: Loop through rows of the RHS. Compare each row to the LHS. + ** If any comparison is NULL, then the result is NULL. If all + ** comparisons are FALSE then the final result is FALSE. + ** + ** For a scalar LHS, it is sufficient to check just the first row + ** of the RHS. + */ + if( destStep6 ) sqlite3VdbeResolveLabel(v, destStep6); + addrTop = sqlite3VdbeAddOp2(v, OP_Rewind, pExpr->iTable, destIfFalse); + VdbeCoverage(v); + if( nVector>1 ){ + destNotNull = sqlite3VdbeMakeLabel(v); + }else{ + /* For nVector==1, combine steps 6 and 7 by immediately returning + ** FALSE if the first comparison is not NULL */ + destNotNull = destIfFalse; + } + for(i=0; iiTable, i, r3); + sqlite3VdbeAddOp4(v, OP_Ne, rLhs+i, destNotNull, r3, + (void*)pColl, P4_COLLSEQ); + VdbeCoverage(v); + sqlite3ReleaseTempReg(pParse, r3); + } + sqlite3VdbeAddOp2(v, OP_Goto, 0, destIfNull); + if( nVector>1 ){ + sqlite3VdbeResolveLabel(v, destNotNull); + sqlite3VdbeAddOp2(v, OP_Next, pExpr->iTable, addrTop+1); + VdbeCoverage(v); + + /* Step 7: If we reach this point, we know that the result must + ** be false. */ + sqlite3VdbeAddOp2(v, OP_Goto, 0, destIfFalse); + } + + /* Jumps here in order to return true. */ + sqlite3VdbeJumpHere(v, addrTruthOp); + +sqlite3ExprCodeIN_finished: + if( rLhs!=rLhsOrig ) sqlite3ReleaseTempReg(pParse, rLhs); sqlite3ExprCachePop(pParse); VdbeComment((v, "end IN expr")); +sqlite3ExprCodeIN_oom_error: + sqlite3DbFree(pParse->db, aiMap); + sqlite3DbFree(pParse->db, zAff); } #endif /* SQLITE_OMIT_SUBQUERY */ @@ -2319,52 +2993,39 @@ static void codeInteger(Parse *pParse, Expr *pExpr, int negFlag, int iMem){ const char *z = pExpr->u.zToken; assert( z!=0 ); c = sqlite3DecOrHexToI64(z, &value); - if( c==0 || (c==2 && negFlag) ){ - if( negFlag ){ value = c==2 ? SMALLEST_INT64 : -value; } - sqlite3VdbeAddOp4Dup8(v, OP_Int64, 0, iMem, 0, (u8*)&value, P4_INT64); - }else{ + if( c==1 || (c==2 && !negFlag) || (negFlag && value==SMALLEST_INT64)){ #ifdef SQLITE_OMIT_FLOATING_POINT sqlite3ErrorMsg(pParse, "oversized integer: %s%s", negFlag ? "-" : "", z); #else #ifndef SQLITE_OMIT_HEX_INTEGER if( sqlite3_strnicmp(z,"0x",2)==0 ){ - sqlite3ErrorMsg(pParse, "hex literal too big: %s", z); + sqlite3ErrorMsg(pParse, "hex literal too big: %s%s", negFlag?"-":"",z); }else #endif { codeReal(v, z, negFlag, iMem); } #endif + }else{ + if( negFlag ){ value = c==2 ? SMALLEST_INT64 : -value; } + sqlite3VdbeAddOp4Dup8(v, OP_Int64, 0, iMem, 0, (u8*)&value, P4_INT64); } } } -#if defined(SQLITE_DEBUG) /* -** Verify the consistency of the column cache +** Erase column-cache entry number i */ -static int cacheIsValid(Parse *pParse){ - int i, n; - for(i=n=0; iaColCache[i].iReg>0 ) n++; - } - return n==pParse->nColCache; -} -#endif - -/* -** Clear a cache entry. -*/ -static void cacheEntryClear(Parse *pParse, struct yColCache *p){ - if( p->tempReg ){ +static void cacheEntryClear(Parse *pParse, int i){ + if( pParse->aColCache[i].tempReg ){ if( pParse->nTempRegaTempReg) ){ - pParse->aTempReg[pParse->nTempReg++] = p->iReg; + pParse->aTempReg[pParse->nTempReg++] = pParse->aColCache[i].iReg; } - p->tempReg = 0; } - p->iReg = 0; pParse->nColCache--; - assert( pParse->db->mallocFailed || cacheIsValid(pParse) ); + if( inColCache ){ + pParse->aColCache[i] = pParse->aColCache[pParse->nColCache]; + } } @@ -2394,46 +3055,33 @@ void sqlite3ExprCacheStore(Parse *pParse, int iTab, int iCol, int iReg){ ** that the object will never already be in cache. Verify this guarantee. */ #ifndef NDEBUG - for(i=0, p=pParse->aColCache; iiReg==0 || p->iTable!=iTab || p->iColumn!=iCol ); + for(i=0, p=pParse->aColCache; inColCache; i++, p++){ + assert( p->iTable!=iTab || p->iColumn!=iCol ); } #endif - /* Find an empty slot and replace it */ - for(i=0, p=pParse->aColCache; iiReg==0 ){ - p->iLevel = pParse->iCacheLevel; - p->iTable = iTab; - p->iColumn = iCol; - p->iReg = iReg; - p->tempReg = 0; - p->lru = pParse->iCacheCnt++; - pParse->nColCache++; - assert( pParse->db->mallocFailed || cacheIsValid(pParse) ); - return; + /* If the cache is already full, delete the least recently used entry */ + if( pParse->nColCache>=SQLITE_N_COLCACHE ){ + minLru = 0x7fffffff; + idxLru = -1; + for(i=0, p=pParse->aColCache; ilrulru; + } } + p = &pParse->aColCache[idxLru]; + }else{ + p = &pParse->aColCache[pParse->nColCache++]; } - /* Replace the last recently used */ - minLru = 0x7fffffff; - idxLru = -1; - for(i=0, p=pParse->aColCache; ilrulru; - } - } - if( ALWAYS(idxLru>=0) ){ - p = &pParse->aColCache[idxLru]; - p->iLevel = pParse->iCacheLevel; - p->iTable = iTab; - p->iColumn = iCol; - p->iReg = iReg; - p->tempReg = 0; - p->lru = pParse->iCacheCnt++; - assert( cacheIsValid(pParse) ); - return; - } + /* Add the new entry to the end of the cache */ + p->iLevel = pParse->iCacheLevel; + p->iTable = iTab; + p->iColumn = iCol; + p->iReg = iReg; + p->tempReg = 0; + p->lru = pParse->iCacheCnt++; } /* @@ -2441,13 +3089,14 @@ void sqlite3ExprCacheStore(Parse *pParse, int iTab, int iCol, int iReg){ ** Purge the range of registers from the column cache. */ void sqlite3ExprCacheRemove(Parse *pParse, int iReg, int nReg){ - struct yColCache *p; - if( iReg<=0 || pParse->nColCache==0 ) return; - p = &pParse->aColCache[SQLITE_N_COLCACHE-1]; - while(1){ - if( p->iReg >= iReg && p->iReg < iReg+nReg ) cacheEntryClear(pParse, p); - if( p==pParse->aColCache ) break; - p--; + int i = 0; + while( inColCache ){ + struct yColCache *p = &pParse->aColCache[i]; + if( p->iReg >= iReg && p->iReg < iReg+nReg ){ + cacheEntryClear(pParse, i); + }else{ + i++; + } } } @@ -2471,8 +3120,7 @@ void sqlite3ExprCachePush(Parse *pParse){ ** the cache to the state it was in prior the most recent Push. */ void sqlite3ExprCachePop(Parse *pParse){ - int i; - struct yColCache *p; + int i = 0; assert( pParse->iCacheLevel>=1 ); pParse->iCacheLevel--; #ifdef SQLITE_DEBUG @@ -2480,9 +3128,11 @@ void sqlite3ExprCachePop(Parse *pParse){ printf("POP to %d\n", pParse->iCacheLevel); } #endif - for(i=0, p=pParse->aColCache; iiReg && p->iLevel>pParse->iCacheLevel ){ - cacheEntryClear(pParse, p); + while( inColCache ){ + if( pParse->aColCache[i].iLevel>pParse->iCacheLevel ){ + cacheEntryClear(pParse, i); + }else{ + i++; } } } @@ -2496,7 +3146,7 @@ void sqlite3ExprCachePop(Parse *pParse){ static void sqlite3ExprCachePinRegister(Parse *pParse, int iReg){ int i; struct yColCache *p; - for(i=0, p=pParse->aColCache; iaColCache; inColCache; i++, p++){ if( p->iReg==iReg ){ p->tempReg = 0; } @@ -2574,8 +3224,8 @@ int sqlite3ExprCodeGetColumn( int i; struct yColCache *p; - for(i=0, p=pParse->aColCache; iiReg>0 && p->iTable==iTable && p->iColumn==iColumn ){ + for(i=0, p=pParse->aColCache; inColCache; i++, p++){ + if( p->iTable==iTable && p->iColumn==iColumn ){ p->lru = pParse->iCacheCnt++; sqlite3ExprCachePinRegister(pParse, p->iReg); return p->iReg; @@ -2607,18 +3257,20 @@ void sqlite3ExprCodeGetColumnToReg( */ void sqlite3ExprCacheClear(Parse *pParse){ int i; - struct yColCache *p; #if SQLITE_DEBUG if( pParse->db->flags & SQLITE_VdbeAddopTrace ){ printf("CLEAR\n"); } #endif - for(i=0, p=pParse->aColCache; iiReg ){ - cacheEntryClear(pParse, p); + for(i=0; inColCache; i++){ + if( pParse->aColCache[i].tempReg + && pParse->nTempRegaTempReg) + ){ + pParse->aTempReg[pParse->nTempReg++] = pParse->aColCache[i].iReg; } } + pParse->nColCache = 0; } /* @@ -2650,7 +3302,7 @@ void sqlite3ExprCodeMove(Parse *pParse, int iFrom, int iTo, int nReg){ static int usedAsColumnCache(Parse *pParse, int iFrom, int iTo){ int i; struct yColCache *p; - for(i=0, p=pParse->aColCache; iaColCache; inColCache; i++, p++){ int r = p->iReg; if( r>=iFrom && r<=iTo ) return 1; /*NO_TEST*/ } @@ -2660,7 +3312,9 @@ static int usedAsColumnCache(Parse *pParse, int iFrom, int iTo){ /* -** Convert an expression node to a TK_REGISTER +** Convert a scalar expression node to a TK_REGISTER referencing +** register iReg. The caller must ensure that iReg already contains +** the correct value for the expression. */ static void exprToRegister(Expr *p, int iReg){ p->op2 = p->op; @@ -2669,6 +3323,38 @@ static void exprToRegister(Expr *p, int iReg){ ExprClearProperty(p, EP_Skip); } +/* +** Evaluate an expression (either a vector or a scalar expression) and store +** the result in continguous temporary registers. Return the index of +** the first register used to store the result. +** +** If the returned result register is a temporary scalar, then also write +** that register number into *piFreeable. If the returned result register +** is not a temporary or if the expression is a vector set *piFreeable +** to 0. +*/ +static int exprCodeVector(Parse *pParse, Expr *p, int *piFreeable){ + int iResult; + int nResult = sqlite3ExprVectorSize(p); + if( nResult==1 ){ + iResult = sqlite3ExprCodeTemp(pParse, p, piFreeable); + }else{ + *piFreeable = 0; + if( p->op==TK_SELECT ){ + iResult = sqlite3CodeSubselect(pParse, p, 0, 0); + }else{ + int i; + iResult = pParse->nMem+1; + pParse->nMem += nResult; + for(i=0; ix.pList->a[i].pExpr, i+iResult); + } + } + } + return iResult; +} + + /* ** Generate code into the current Vdbe to evaluate the given ** expression. Attempt to store the results in register "target". @@ -2686,9 +3372,9 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ int inReg = target; /* Results stored in register inReg */ int regFree1 = 0; /* If non-zero free this temporary register */ int regFree2 = 0; /* If non-zero free this temporary register */ - int r1, r2, r3, r4; /* Various register numbers */ - sqlite3 *db = pParse->db; /* The database connection */ + int r1, r2; /* Various register numbers */ Expr tempX; /* Temporary expression node */ + int p5 = 0; assert( target>0 && target<=pParse->nMem ); if( v==0 ){ @@ -2707,12 +3393,11 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ struct AggInfo_col *pCol = &pAggInfo->aCol[pExpr->iAgg]; if( !pAggInfo->directMode ){ assert( pCol->iMem>0 ); - inReg = pCol->iMem; - break; + return pCol->iMem; }else if( pAggInfo->useSortingIdx ){ sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab, pCol->iSorterColumn, target); - break; + return target; } /* Otherwise, fall thru into the TK_COLUMN case */ } @@ -2721,38 +3406,36 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ if( iTab<0 ){ if( pParse->ckBase>0 ){ /* Generating CHECK constraints or inserting into partial index */ - inReg = pExpr->iColumn + pParse->ckBase; - break; + return pExpr->iColumn + pParse->ckBase; }else{ /* Coding an expression that is part of an index where column names ** in the index refer to the table to which the index belongs */ iTab = pParse->iSelfTab; } } - inReg = sqlite3ExprCodeGetColumn(pParse, pExpr->pTab, + return sqlite3ExprCodeGetColumn(pParse, pExpr->pTab, pExpr->iColumn, iTab, target, pExpr->op2); - break; } case TK_INTEGER: { codeInteger(pParse, pExpr, 0, target); - break; + return target; } #ifndef SQLITE_OMIT_FLOATING_POINT case TK_FLOAT: { assert( !ExprHasProperty(pExpr, EP_IntValue) ); codeReal(v, pExpr->u.zToken, 0, target); - break; + return target; } #endif case TK_STRING: { assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3VdbeLoadString(v, target, pExpr->u.zToken); - break; + return target; } case TK_NULL: { sqlite3VdbeAddOp2(v, OP_Null, 0, target); - break; + return target; } #ifndef SQLITE_OMIT_BLOB_LITERAL case TK_BLOB: { @@ -2767,7 +3450,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ assert( z[n]=='\'' ); zBlob = sqlite3HexToBlob(sqlite3VdbeDb(v), z, n); sqlite3VdbeAddOp4(v, OP_Blob, n/2, target, 0, zBlob, P4_DYNAMIC); - break; + return target; } #endif case TK_VARIABLE: { @@ -2776,15 +3459,15 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ assert( pExpr->u.zToken[0]!=0 ); sqlite3VdbeAddOp2(v, OP_Variable, pExpr->iColumn, target); if( pExpr->u.zToken[1]!=0 ){ - assert( pExpr->u.zToken[0]=='?' - || strcmp(pExpr->u.zToken, pParse->azVar[pExpr->iColumn-1])==0 ); - sqlite3VdbeChangeP4(v, -1, pParse->azVar[pExpr->iColumn-1], P4_STATIC); + const char *z = sqlite3VListNumToName(pParse->pVList, pExpr->iColumn); + assert( pExpr->u.zToken[0]=='?' || strcmp(pExpr->u.zToken, z)==0 ); + pParse->pVList[0] = 0; /* Indicate VList may no longer be enlarged */ + sqlite3VdbeAppendP4(v, (char*)z, P4_STATIC); } - break; + return target; } case TK_REGISTER: { - inReg = pExpr->iTable; - break; + return pExpr->iTable; } #ifndef SQLITE_OMIT_CAST case TK_CAST: { @@ -2798,42 +3481,37 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ sqlite3AffinityType(pExpr->u.zToken, 0)); testcase( usedAsColumnCache(pParse, inReg, inReg) ); sqlite3ExprCacheAffinityChange(pParse, inReg, 1); - break; + return inReg; } #endif /* SQLITE_OMIT_CAST */ + case TK_IS: + case TK_ISNOT: + op = (op==TK_IS) ? TK_EQ : TK_NE; + p5 = SQLITE_NULLEQ; + /* fall-through */ case TK_LT: case TK_LE: case TK_GT: case TK_GE: case TK_NE: case TK_EQ: { - r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); - r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); - codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, - r1, r2, inReg, SQLITE_STOREP2); - assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); - assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); - assert(TK_GT==OP_Gt); testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt); - assert(TK_GE==OP_Ge); testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); - assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq); - assert(TK_NE==OP_Ne); testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne); - testcase( regFree1==0 ); - testcase( regFree2==0 ); - break; - } - case TK_IS: - case TK_ISNOT: { - testcase( op==TK_IS ); - testcase( op==TK_ISNOT ); - r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); - r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); - op = (op==TK_IS) ? TK_EQ : TK_NE; - codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, - r1, r2, inReg, SQLITE_STOREP2 | SQLITE_NULLEQ); - VdbeCoverageIf(v, op==TK_EQ); - VdbeCoverageIf(v, op==TK_NE); - testcase( regFree1==0 ); - testcase( regFree2==0 ); + Expr *pLeft = pExpr->pLeft; + if( sqlite3ExprIsVector(pLeft) ){ + codeVectorCompare(pParse, pExpr, target, op, p5); + }else{ + r1 = sqlite3ExprCodeTemp(pParse, pLeft, ®Free1); + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); + codeCompare(pParse, pLeft, pExpr->pRight, op, + r1, r2, inReg, SQLITE_STOREP2 | p5); + assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); + assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); + assert(TK_GT==OP_Gt); testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt); + assert(TK_GE==OP_Ge); testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); + assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq); + assert(TK_NE==OP_Ne); testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne); + testcase( regFree1==0 ); + testcase( regFree2==0 ); + } break; } case TK_AND: @@ -2871,10 +3549,12 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ assert( pLeft ); if( pLeft->op==TK_INTEGER ){ codeInteger(pParse, pLeft, 1, target); + return target; #ifndef SQLITE_OMIT_FLOATING_POINT }else if( pLeft->op==TK_FLOAT ){ assert( !ExprHasProperty(pExpr, EP_IntValue) ); codeReal(v, pLeft->u.zToken, 1, target); + return target; #endif }else{ tempX.op = TK_INTEGER; @@ -2885,7 +3565,6 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ sqlite3VdbeAddOp3(v, OP_Subtract, r2, r1, target); testcase( regFree2==0 ); } - inReg = target; break; } case TK_BITNOT: @@ -2894,7 +3573,6 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ assert( TK_NOT==OP_Not ); testcase( op==TK_NOT ); r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); testcase( regFree1==0 ); - inReg = target; sqlite3VdbeAddOp2(v, op, r1, inReg); break; } @@ -2919,7 +3597,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3ErrorMsg(pParse, "misuse of aggregate: %s()", pExpr->u.zToken); }else{ - inReg = pInfo->aFunc[pExpr->iAgg].iMem; + return pInfo->aFunc[pExpr->iAgg].iMem; } break; } @@ -2930,9 +3608,15 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ const char *zId; /* The function name */ u32 constMask = 0; /* Mask of function arguments that are constant */ int i; /* Loop counter */ + sqlite3 *db = pParse->db; /* The database connection */ u8 enc = ENC(db); /* The text encoding used by this database */ CollSeq *pColl = 0; /* A collating sequence */ + if( ConstFactorOk(pParse) && sqlite3ExprIsConstantNotJoin(pExpr) ){ + /* SQL functions can be expensive. So try to move constant functions + ** out of the inner loop, even if that means an extra OP_Copy. */ + return sqlite3ExprCodeAtInit(pParse, pExpr, -1); + } assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); if( ExprHasProperty(pExpr, EP_TokenOnly) ){ pFarg = 0; @@ -2978,10 +3662,25 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ */ if( pDef->funcFlags & SQLITE_FUNC_UNLIKELY ){ assert( nFarg>=1 ); - inReg = sqlite3ExprCodeTarget(pParse, pFarg->a[0].pExpr, target); - break; + return sqlite3ExprCodeTarget(pParse, pFarg->a[0].pExpr, target); } +#ifdef SQLITE_DEBUG + /* The AFFINITY() function evaluates to a string that describes + ** the type affinity of the argument. This is used for testing of + ** the SQLite type logic. + */ + if( pDef->funcFlags & SQLITE_FUNC_AFFINITY ){ + const char *azAff[] = { "blob", "text", "numeric", "integer", "real" }; + char aff; + assert( nFarg==1 ); + aff = sqlite3ExprAffinity(pFarg->a[0].pExpr); + sqlite3VdbeLoadString(v, target, + aff ? azAff[aff-SQLITE_AFF_BLOB] : "none"); + return target; + } +#endif + for(i=0; ia[i].pExpr) ){ testcase( i==31 ); @@ -3054,16 +3753,35 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ if( nFarg && constMask==0 ){ sqlite3ReleaseTempRange(pParse, r1, nFarg); } - break; + return target; } #ifndef SQLITE_OMIT_SUBQUERY case TK_EXISTS: case TK_SELECT: { + int nCol; testcase( op==TK_EXISTS ); testcase( op==TK_SELECT ); - inReg = sqlite3CodeSubselect(pParse, pExpr, 0, 0); + if( op==TK_SELECT && (nCol = pExpr->x.pSelect->pEList->nExpr)!=1 ){ + sqlite3SubselectError(pParse, nCol, 1); + }else{ + return sqlite3CodeSubselect(pParse, pExpr, 0, 0); + } break; } + case TK_SELECT_COLUMN: { + int n; + if( pExpr->pLeft->iTable==0 ){ + pExpr->pLeft->iTable = sqlite3CodeSubselect(pParse, pExpr->pLeft, 0, 0); + } + assert( pExpr->iTable==0 || pExpr->pLeft->op==TK_SELECT ); + if( pExpr->iTable + && pExpr->iTable!=(n = sqlite3ExprVectorSize(pExpr->pLeft)) + ){ + sqlite3ErrorMsg(pParse, "%d columns assigned %d values", + pExpr->iTable, n); + } + return pExpr->pLeft->iTable + pExpr->iColumn; + } case TK_IN: { int destIfFalse = sqlite3VdbeMakeLabel(v); int destIfNull = sqlite3VdbeMakeLabel(v); @@ -3073,7 +3791,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ sqlite3VdbeResolveLabel(v, destIfFalse); sqlite3VdbeAddOp2(v, OP_AddImm, target, 0); sqlite3VdbeResolveLabel(v, destIfNull); - break; + return target; } #endif /* SQLITE_OMIT_SUBQUERY */ @@ -3090,35 +3808,13 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ ** Z is stored in pExpr->pList->a[1].pExpr. */ case TK_BETWEEN: { - Expr *pLeft = pExpr->pLeft; - struct ExprList_item *pLItem = pExpr->x.pList->a; - Expr *pRight = pLItem->pExpr; - - r1 = sqlite3ExprCodeTemp(pParse, pLeft, ®Free1); - r2 = sqlite3ExprCodeTemp(pParse, pRight, ®Free2); - testcase( regFree1==0 ); - testcase( regFree2==0 ); - r3 = sqlite3GetTempReg(pParse); - r4 = sqlite3GetTempReg(pParse); - codeCompare(pParse, pLeft, pRight, OP_Ge, - r1, r2, r3, SQLITE_STOREP2); VdbeCoverage(v); - pLItem++; - pRight = pLItem->pExpr; - sqlite3ReleaseTempReg(pParse, regFree2); - r2 = sqlite3ExprCodeTemp(pParse, pRight, ®Free2); - testcase( regFree2==0 ); - codeCompare(pParse, pLeft, pRight, OP_Le, r1, r2, r4, SQLITE_STOREP2); - VdbeCoverage(v); - sqlite3VdbeAddOp3(v, OP_And, r3, r4, target); - sqlite3ReleaseTempReg(pParse, r3); - sqlite3ReleaseTempReg(pParse, r4); - break; + exprCodeBetween(pParse, pExpr, target, 0, 0); + return target; } case TK_SPAN: case TK_COLLATE: case TK_UPLUS: { - inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); - break; + return sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); } case TK_TRIGGER: { @@ -3177,6 +3873,10 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ break; } + case TK_VECTOR: { + sqlite3ErrorMsg(pParse, "row value misused"); + break; + } /* ** Form A: @@ -3220,8 +3920,9 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ if( (pX = pExpr->pLeft)!=0 ){ tempX = *pX; testcase( pX->op==TK_COLUMN ); - exprToRegister(&tempX, sqlite3ExprCodeTemp(pParse, pX, ®Free1)); + exprToRegister(&tempX, exprCodeVector(pParse, &tempX, ®Free1)); testcase( regFree1==0 ); + memset(&opCompare, 0, sizeof(opCompare)); opCompare.op = TK_EQ; opCompare.pLeft = &tempX; pTest = &opCompare; @@ -3255,7 +3956,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ }else{ sqlite3VdbeAddOp2(v, OP_Null, 0, target); } - assert( db->mallocFailed || pParse->nErr>0 + assert( pParse->db->mallocFailed || pParse->nErr>0 || pParse->iCacheLevel==iCacheLevel ); sqlite3VdbeResolveLabel(v, endLabel); break; @@ -3296,24 +3997,40 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ /* ** Factor out the code of the given expression to initialization time. +** +** If regDest>=0 then the result is always stored in that register and the +** result is not reusable. If regDest<0 then this routine is free to +** store the value whereever it wants. The register where the expression +** is stored is returned. When regDest<0, two identical expressions will +** code to the same register. */ -void sqlite3ExprCodeAtInit( +int sqlite3ExprCodeAtInit( Parse *pParse, /* Parsing context */ Expr *pExpr, /* The expression to code when the VDBE initializes */ - int regDest, /* Store the value in this register */ - u8 reusable /* True if this expression is reusable */ + int regDest /* Store the value in this register */ ){ ExprList *p; assert( ConstFactorOk(pParse) ); p = pParse->pConstExpr; + if( regDest<0 && p ){ + struct ExprList_item *pItem; + int i; + for(pItem=p->a, i=p->nExpr; i>0; pItem++, i--){ + if( pItem->reusable && sqlite3ExprCompare(pItem->pExpr,pExpr,-1)==0 ){ + return pItem->u.iConstExprReg; + } + } + } pExpr = sqlite3ExprDup(pParse->db, pExpr, 0); p = sqlite3ExprListAppend(pParse, p, pExpr); if( p ){ struct ExprList_item *pItem = &p->a[p->nExpr-1]; + pItem->reusable = regDest<0; + if( regDest<0 ) regDest = ++pParse->nMem; pItem->u.iConstExprReg = regDest; - pItem->reusable = reusable; } pParse->pConstExpr = p; + return regDest; } /* @@ -3336,19 +4053,8 @@ int sqlite3ExprCodeTemp(Parse *pParse, Expr *pExpr, int *pReg){ && pExpr->op!=TK_REGISTER && sqlite3ExprIsConstantNotJoin(pExpr) ){ - ExprList *p = pParse->pConstExpr; - int i; *pReg = 0; - if( p ){ - struct ExprList_item *pItem; - for(pItem=p->a, i=p->nExpr; i>0; pItem++, i--){ - if( pItem->reusable && sqlite3ExprCompare(pItem->pExpr,pExpr,-1)==0 ){ - return pItem->u.iConstExprReg; - } - } - } - r2 = ++pParse->nMem; - sqlite3ExprCodeAtInit(pParse, pExpr, r2, 1); + r2 = sqlite3ExprCodeAtInit(pParse, pExpr, -1); }else{ int r1 = sqlite3GetTempReg(pParse); r2 = sqlite3ExprCodeTarget(pParse, pExpr, r1); @@ -3402,7 +4108,7 @@ void sqlite3ExprCodeCopy(Parse *pParse, Expr *pExpr, int target){ */ void sqlite3ExprCodeFactorable(Parse *pParse, Expr *pExpr, int target){ if( pParse->okConstFactor && sqlite3ExprIsConstant(pExpr) ){ - sqlite3ExprCodeAtInit(pParse, pExpr, target, 0); + sqlite3ExprCodeAtInit(pParse, pExpr, target); }else{ sqlite3ExprCode(pParse, pExpr, target); } @@ -3466,10 +4172,15 @@ int sqlite3ExprCodeExprList( if( !ConstFactorOk(pParse) ) flags &= ~SQLITE_ECEL_FACTOR; for(pItem=pList->a, i=0; ipExpr; - if( (flags & SQLITE_ECEL_REF)!=0 && (j = pList->a[i].u.x.iOrderByCol)>0 ){ - sqlite3VdbeAddOp2(v, copyOp, j+srcReg-1, target+i); + if( (flags & SQLITE_ECEL_REF)!=0 && (j = pItem->u.x.iOrderByCol)>0 ){ + if( flags & SQLITE_ECEL_OMITREF ){ + i--; + n--; + }else{ + sqlite3VdbeAddOp2(v, copyOp, j+srcReg-1, target+i); + } }else if( (flags & SQLITE_ECEL_FACTOR)!=0 && sqlite3ExprIsConstant(pExpr) ){ - sqlite3ExprCodeAtInit(pParse, pExpr, target+i, 0); + sqlite3ExprCodeAtInit(pParse, pExpr, target+i); }else{ int inReg = sqlite3ExprCodeTarget(pParse, pExpr, target+i); if( inReg!=target+i ){ @@ -3500,20 +4211,33 @@ int sqlite3ExprCodeExprList( ** ** Code it as such, taking care to do the common subexpression ** elimination of x. +** +** The xJumpIf parameter determines details: +** +** NULL: Store the boolean result in reg[dest] +** sqlite3ExprIfTrue: Jump to dest if true +** sqlite3ExprIfFalse: Jump to dest if false +** +** The jumpIfNull parameter is ignored if xJumpIf is NULL. */ static void exprCodeBetween( Parse *pParse, /* Parsing and code generating context */ Expr *pExpr, /* The BETWEEN expression */ - int dest, /* Jump here if the jump is taken */ - int jumpIfTrue, /* Take the jump if the BETWEEN is true */ + int dest, /* Jump destination or storage location */ + void (*xJump)(Parse*,Expr*,int,int), /* Action to take */ int jumpIfNull /* Take the jump if the BETWEEN is NULL */ ){ - Expr exprAnd; /* The AND operator in x>=y AND x<=z */ + Expr exprAnd; /* The AND operator in x>=y AND x<=z */ Expr compLeft; /* The x>=y term */ Expr compRight; /* The x<=z term */ Expr exprX; /* The x subexpression */ int regFree1 = 0; /* Temporary use register */ + + memset(&compLeft, 0, sizeof(Expr)); + memset(&compRight, 0, sizeof(Expr)); + memset(&exprAnd, 0, sizeof(Expr)); + assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); exprX = *pExpr->pLeft; exprAnd.op = TK_AND; @@ -3525,23 +4249,30 @@ static void exprCodeBetween( compRight.op = TK_LE; compRight.pLeft = &exprX; compRight.pRight = pExpr->x.pList->a[1].pExpr; - exprToRegister(&exprX, sqlite3ExprCodeTemp(pParse, &exprX, ®Free1)); - if( jumpIfTrue ){ - sqlite3ExprIfTrue(pParse, &exprAnd, dest, jumpIfNull); + exprToRegister(&exprX, exprCodeVector(pParse, &exprX, ®Free1)); + if( xJump ){ + xJump(pParse, &exprAnd, dest, jumpIfNull); }else{ - sqlite3ExprIfFalse(pParse, &exprAnd, dest, jumpIfNull); + /* Mark the expression is being from the ON or USING clause of a join + ** so that the sqlite3ExprCodeTarget() routine will not attempt to move + ** it into the Parse.pConstExpr list. We should use a new bit for this, + ** for clarity, but we are out of bits in the Expr.flags field so we + ** have to reuse the EP_FromJoin bit. Bummer. */ + exprX.flags |= EP_FromJoin; + sqlite3ExprCodeTarget(pParse, &exprAnd, dest); } sqlite3ReleaseTempReg(pParse, regFree1); /* Ensure adequate test coverage */ - testcase( jumpIfTrue==0 && jumpIfNull==0 && regFree1==0 ); - testcase( jumpIfTrue==0 && jumpIfNull==0 && regFree1!=0 ); - testcase( jumpIfTrue==0 && jumpIfNull!=0 && regFree1==0 ); - testcase( jumpIfTrue==0 && jumpIfNull!=0 && regFree1!=0 ); - testcase( jumpIfTrue!=0 && jumpIfNull==0 && regFree1==0 ); - testcase( jumpIfTrue!=0 && jumpIfNull==0 && regFree1!=0 ); - testcase( jumpIfTrue!=0 && jumpIfNull!=0 && regFree1==0 ); - testcase( jumpIfTrue!=0 && jumpIfNull!=0 && regFree1!=0 ); + testcase( xJump==sqlite3ExprIfTrue && jumpIfNull==0 && regFree1==0 ); + testcase( xJump==sqlite3ExprIfTrue && jumpIfNull==0 && regFree1!=0 ); + testcase( xJump==sqlite3ExprIfTrue && jumpIfNull!=0 && regFree1==0 ); + testcase( xJump==sqlite3ExprIfTrue && jumpIfNull!=0 && regFree1!=0 ); + testcase( xJump==sqlite3ExprIfFalse && jumpIfNull==0 && regFree1==0 ); + testcase( xJump==sqlite3ExprIfFalse && jumpIfNull==0 && regFree1!=0 ); + testcase( xJump==sqlite3ExprIfFalse && jumpIfNull!=0 && regFree1==0 ); + testcase( xJump==sqlite3ExprIfFalse && jumpIfNull!=0 && regFree1!=0 ); + testcase( xJump==0 ); } /* @@ -3606,6 +4337,7 @@ void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ case TK_GE: case TK_NE: case TK_EQ: { + if( sqlite3ExprIsVector(pExpr->pLeft) ) goto default_expr; testcase( jumpIfNull==0 ); r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); @@ -3638,7 +4370,7 @@ void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ } case TK_BETWEEN: { testcase( jumpIfNull==0 ); - exprCodeBetween(pParse, pExpr, dest, 1, jumpIfNull); + exprCodeBetween(pParse, pExpr, dest, sqlite3ExprIfTrue, jumpIfNull); break; } #ifndef SQLITE_OMIT_SUBQUERY @@ -3652,6 +4384,7 @@ void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ } #endif default: { + default_expr: if( exprAlwaysTrue(pExpr) ){ sqlite3VdbeGoto(v, dest); }else if( exprAlwaysFalse(pExpr) ){ @@ -3758,6 +4491,7 @@ void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ case TK_GE: case TK_NE: case TK_EQ: { + if( sqlite3ExprIsVector(pExpr->pLeft) ) goto default_expr; testcase( jumpIfNull==0 ); r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); @@ -3788,7 +4522,7 @@ void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ } case TK_BETWEEN: { testcase( jumpIfNull==0 ); - exprCodeBetween(pParse, pExpr, dest, 0, jumpIfNull); + exprCodeBetween(pParse, pExpr, dest, sqlite3ExprIfFalse, jumpIfNull); break; } #ifndef SQLITE_OMIT_SUBQUERY @@ -3804,6 +4538,7 @@ void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ } #endif default: { + default_expr: if( exprAlwaysFalse(pExpr) ){ sqlite3VdbeGoto(v, dest); }else if( exprAlwaysTrue(pExpr) ){ @@ -3961,11 +4696,10 @@ int sqlite3ExprImpliesExpr(Expr *pE1, Expr *pE2, int iTab){ ){ return 1; } - if( pE2->op==TK_NOTNULL - && sqlite3ExprCompare(pE1->pLeft, pE2->pLeft, iTab)==0 - && (pE1->op!=TK_ISNULL && pE1->op!=TK_IS) - ){ - return 1; + if( pE2->op==TK_NOTNULL && pE1->op!=TK_ISNULL && pE1->op!=TK_IS ){ + Expr *pX = sqlite3ExprSkipCollate(pE1->pLeft); + testcase( pX!=pE1->pLeft ); + if( sqlite3ExprCompare(pX, pE2->pLeft, iTab)==0 ) return 1; } return 0; } @@ -4308,7 +5042,7 @@ void sqlite3ReleaseTempReg(Parse *pParse, int iReg){ if( iReg && pParse->nTempRegaTempReg) ){ int i; struct yColCache *p; - for(i=0, p=pParse->aColCache; iaColCache; inColCache; i++, p++){ if( p->iReg==iReg ){ p->tempReg = 1; return; @@ -4319,10 +5053,11 @@ void sqlite3ReleaseTempReg(Parse *pParse, int iReg){ } /* -** Allocate or deallocate a block of nReg consecutive registers +** Allocate or deallocate a block of nReg consecutive registers. */ int sqlite3GetTempRange(Parse *pParse, int nReg){ int i, n; + if( nReg==1 ) return sqlite3GetTempReg(pParse); i = pParse->iRangeReg; n = pParse->nRangeReg; if( nReg<=n ){ @@ -4336,6 +5071,10 @@ int sqlite3GetTempRange(Parse *pParse, int nReg){ return i; } void sqlite3ReleaseTempRange(Parse *pParse, int iReg, int nReg){ + if( nReg==1 ){ + sqlite3ReleaseTempReg(pParse, iReg); + return; + } sqlite3ExprCacheRemove(pParse, iReg, nReg); if( nReg>pParse->nRangeReg ){ pParse->nRangeReg = nReg; diff --git a/src/fault.c b/src/fault.c index c3028c4f93..5b41b60363 100644 --- a/src/fault.c +++ b/src/fault.c @@ -26,7 +26,7 @@ #include "sqliteInt.h" -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE /* ** Global variables. @@ -84,4 +84,4 @@ void sqlite3EndBenignMalloc(void){ } } -#endif /* #ifndef SQLITE_OMIT_BUILTIN_TEST */ +#endif /* #ifndef SQLITE_UNTESTABLE */ diff --git a/src/fkey.c b/src/fkey.c index 25d9f84917..cb78764f51 100644 --- a/src/fkey.c +++ b/src/fkey.c @@ -225,7 +225,7 @@ int sqlite3FkLocateIndex( } for(pIdx=pParent->pIndex; pIdx; pIdx=pIdx->pNext){ - if( pIdx->nKeyCol==nCol && IsUniqueIndex(pIdx) ){ + if( pIdx->nKeyCol==nCol && IsUniqueIndex(pIdx) && pIdx->pPartIdxWhere==0 ){ /* pIdx is a UNIQUE index (or a PRIMARY KEY) and has the right number ** of columns. If each indexed column corresponds to a foreign key ** column of pFKey, then this index is a winner. */ @@ -584,7 +584,7 @@ static void fkScanChildren( assert( iCol>=0 ); zCol = pFKey->pFrom->aCol[iCol].zName; pRight = sqlite3Expr(db, TK_ID, zCol); - pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight, 0); + pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight); pWhere = sqlite3ExprAnd(db, pWhere, pEq); } @@ -606,7 +606,7 @@ static void fkScanChildren( if( HasRowid(pTab) ){ pLeft = exprTableRegister(pParse, pTab, regData, -1); pRight = exprTableColumn(db, pTab, pSrc->a[0].iCursor, -1); - pNe = sqlite3PExpr(pParse, TK_NE, pLeft, pRight, 0); + pNe = sqlite3PExpr(pParse, TK_NE, pLeft, pRight); }else{ Expr *pEq, *pAll = 0; Index *pPk = sqlite3PrimaryKeyIndex(pTab); @@ -616,10 +616,10 @@ static void fkScanChildren( assert( iCol>=0 ); pLeft = exprTableRegister(pParse, pTab, regData, iCol); pRight = exprTableColumn(db, pTab, pSrc->a[0].iCursor, iCol); - pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight, 0); + pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight); pAll = sqlite3ExprAnd(db, pAll, pEq); } - pNe = sqlite3PExpr(pParse, TK_NOT, pAll, 0, 0); + pNe = sqlite3PExpr(pParse, TK_NOT, pAll, 0); } pWhere = sqlite3ExprAnd(db, pWhere, pNe); } @@ -871,7 +871,7 @@ void sqlite3FkCheck( if( (db->flags&SQLITE_ForeignKeys)==0 ) return; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); - zDb = db->aDb[iDb].zName; + zDb = db->aDb[iDb].zDbSName; /* Loop through all the foreign key constraints for which pTab is the ** child table (the table that the foreign key definition is part of). */ @@ -1007,7 +1007,7 @@ void sqlite3FkCheck( struct SrcList_item *pItem = pSrc->a; pItem->pTab = pFKey->pFrom; pItem->zName = pFKey->pFrom->zName; - pItem->pTab->nRef++; + pItem->pTab->nTabRef++; pItem->iCursor = pParse->nTab++; if( regNew!=0 ){ @@ -1205,10 +1205,9 @@ static Trigger *fkActionTrigger( pEq = sqlite3PExpr(pParse, TK_EQ, sqlite3PExpr(pParse, TK_DOT, sqlite3ExprAlloc(db, TK_ID, &tOld, 0), - sqlite3ExprAlloc(db, TK_ID, &tToCol, 0) - , 0), + sqlite3ExprAlloc(db, TK_ID, &tToCol, 0)), sqlite3ExprAlloc(db, TK_ID, &tFromCol, 0) - , 0); + ); pWhere = sqlite3ExprAnd(db, pWhere, pEq); /* For ON UPDATE, construct the next term of the WHEN clause. @@ -1220,13 +1219,11 @@ static Trigger *fkActionTrigger( pEq = sqlite3PExpr(pParse, TK_IS, sqlite3PExpr(pParse, TK_DOT, sqlite3ExprAlloc(db, TK_ID, &tOld, 0), - sqlite3ExprAlloc(db, TK_ID, &tToCol, 0), - 0), + sqlite3ExprAlloc(db, TK_ID, &tToCol, 0)), sqlite3PExpr(pParse, TK_DOT, sqlite3ExprAlloc(db, TK_ID, &tNew, 0), - sqlite3ExprAlloc(db, TK_ID, &tToCol, 0), - 0), - 0); + sqlite3ExprAlloc(db, TK_ID, &tToCol, 0)) + ); pWhen = sqlite3ExprAnd(db, pWhen, pEq); } @@ -1235,17 +1232,16 @@ static Trigger *fkActionTrigger( if( action==OE_Cascade ){ pNew = sqlite3PExpr(pParse, TK_DOT, sqlite3ExprAlloc(db, TK_ID, &tNew, 0), - sqlite3ExprAlloc(db, TK_ID, &tToCol, 0) - , 0); + sqlite3ExprAlloc(db, TK_ID, &tToCol, 0)); }else if( action==OE_SetDflt ){ Expr *pDflt = pFKey->pFrom->aCol[iFromCol].pDflt; if( pDflt ){ pNew = sqlite3ExprDup(db, pDflt, 0); }else{ - pNew = sqlite3PExpr(pParse, TK_NULL, 0, 0, 0); + pNew = sqlite3ExprAlloc(db, TK_NULL, 0, 0); } }else{ - pNew = sqlite3PExpr(pParse, TK_NULL, 0, 0, 0); + pNew = sqlite3ExprAlloc(db, TK_NULL, 0, 0); } pList = sqlite3ExprListAppend(pParse, pList, pNew); sqlite3ExprListSetName(pParse, pList, &tFromCol, 0); @@ -1292,7 +1288,7 @@ static Trigger *fkActionTrigger( pStep->pExprList = sqlite3ExprListDup(db, pList, EXPRDUP_REDUCE); pStep->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); if( pWhen ){ - pWhen = sqlite3PExpr(pParse, TK_NOT, pWhen, 0, 0); + pWhen = sqlite3PExpr(pParse, TK_NOT, pWhen, 0); pTrigger->pWhen = sqlite3ExprDup(db, pWhen, EXPRDUP_REDUCE); } } diff --git a/src/func.c b/src/func.c index 79bb1e3dd2..885725bc6b 100644 --- a/src/func.c +++ b/src/func.c @@ -200,23 +200,28 @@ static void instrFunc( if( typeHaystack==SQLITE_NULL || typeNeedle==SQLITE_NULL ) return; nHaystack = sqlite3_value_bytes(argv[0]); nNeedle = sqlite3_value_bytes(argv[1]); - if( typeHaystack==SQLITE_BLOB && typeNeedle==SQLITE_BLOB ){ - zHaystack = sqlite3_value_blob(argv[0]); - zNeedle = sqlite3_value_blob(argv[1]); - isText = 0; - }else{ - zHaystack = sqlite3_value_text(argv[0]); - zNeedle = sqlite3_value_text(argv[1]); - isText = 1; + if( nNeedle>0 ){ + if( typeHaystack==SQLITE_BLOB && typeNeedle==SQLITE_BLOB ){ + zHaystack = sqlite3_value_blob(argv[0]); + zNeedle = sqlite3_value_blob(argv[1]); + assert( zNeedle!=0 ); + assert( zHaystack!=0 || nHaystack==0 ); + isText = 0; + }else{ + zHaystack = sqlite3_value_text(argv[0]); + zNeedle = sqlite3_value_text(argv[1]); + isText = 1; + if( zHaystack==0 || zNeedle==0 ) return; + } + while( nNeedle<=nHaystack && memcmp(zHaystack, zNeedle, nNeedle)!=0 ){ + N++; + do{ + nHaystack--; + zHaystack++; + }while( isText && (zHaystack[0]&0xc0)==0x80 ); + } + if( nNeedle>nHaystack ) N = 0; } - while( nNeedle<=nHaystack && memcmp(zHaystack, zNeedle, nNeedle)!=0 ){ - N++; - do{ - nHaystack--; - zHaystack++; - }while( isText && (zHaystack[0]&0xc0)==0x80 ); - } - if( nNeedle>nHaystack ) N = 0; sqlite3_result_int(context, N); } @@ -596,9 +601,19 @@ static const struct compareInfo likeInfoNorm = { '%', '_', 0, 1 }; static const struct compareInfo likeInfoAlt = { '%', '_', 0, 0 }; /* -** Compare two UTF-8 strings for equality where the first string can -** potentially be a "glob" or "like" expression. Return true (1) if they -** are the same and false (0) if they are different. +** Possible error returns from patternMatch() +*/ +#define SQLITE_MATCH 0 +#define SQLITE_NOMATCH 1 +#define SQLITE_NOWILDCARDMATCH 2 + +/* +** Compare two UTF-8 strings for equality where the first string is +** a GLOB or LIKE expression. Return values: +** +** SQLITE_MATCH: Match +** SQLITE_NOMATCH: No match +** SQLITE_NOWILDCARDMATCH: No match in spite of having * or % wildcards. ** ** Globbing rules: ** @@ -649,30 +664,31 @@ static int patternCompare( ** single character of the input string for each "?" skipped */ while( (c=Utf8Read(zPattern)) == matchAll || c == matchOne ){ if( c==matchOne && sqlite3Utf8Read(&zString)==0 ){ - return 0; + return SQLITE_NOWILDCARDMATCH; } } if( c==0 ){ - return 1; /* "*" at the end of the pattern matches */ + return SQLITE_MATCH; /* "*" at the end of the pattern matches */ }else if( c==matchOther ){ if( pInfo->matchSet==0 ){ c = sqlite3Utf8Read(&zPattern); - if( c==0 ) return 0; + if( c==0 ) return SQLITE_NOWILDCARDMATCH; }else{ /* "[...]" immediately follows the "*". We have to do a slow ** recursive search in this case, but it is an unusual case. */ assert( matchOther<0x80 ); /* '[' is a single-byte character */ - while( *zString - && patternCompare(&zPattern[-1],zString,pInfo,matchOther)==0 ){ + while( *zString ){ + int bMatch = patternCompare(&zPattern[-1],zString,pInfo,matchOther); + if( bMatch!=SQLITE_NOMATCH ) return bMatch; SQLITE_SKIP_UTF8(zString); } - return *zString!=0; + return SQLITE_NOWILDCARDMATCH; } } /* At this point variable c contains the first character of the ** pattern string past the "*". Search in the input string for the - ** first matching character and recursively contine the match from + ** first matching character and recursively continue the match from ** that point. ** ** For a case-insensitive search, set variable cx to be the same as @@ -681,6 +697,7 @@ static int patternCompare( */ if( c<=0x80 ){ u32 cx; + int bMatch; if( noCase ){ cx = sqlite3Toupper(c); c = sqlite3Tolower(c); @@ -689,27 +706,30 @@ static int patternCompare( } while( (c2 = *(zString++))!=0 ){ if( c2!=c && c2!=cx ) continue; - if( patternCompare(zPattern,zString,pInfo,matchOther) ) return 1; + bMatch = patternCompare(zPattern,zString,pInfo,matchOther); + if( bMatch!=SQLITE_NOMATCH ) return bMatch; } }else{ + int bMatch; while( (c2 = Utf8Read(zString))!=0 ){ if( c2!=c ) continue; - if( patternCompare(zPattern,zString,pInfo,matchOther) ) return 1; + bMatch = patternCompare(zPattern,zString,pInfo,matchOther); + if( bMatch!=SQLITE_NOMATCH ) return bMatch; } } - return 0; + return SQLITE_NOWILDCARDMATCH; } if( c==matchOther ){ if( pInfo->matchSet==0 ){ c = sqlite3Utf8Read(&zPattern); - if( c==0 ) return 0; + if( c==0 ) return SQLITE_NOMATCH; zEscaped = zPattern; }else{ u32 prior_c = 0; int seen = 0; int invert = 0; c = sqlite3Utf8Read(&zString); - if( c==0 ) return 0; + if( c==0 ) return SQLITE_NOMATCH; c2 = sqlite3Utf8Read(&zPattern); if( c2=='^' ){ invert = 1; @@ -733,7 +753,7 @@ static int patternCompare( c2 = sqlite3Utf8Read(&zPattern); } if( c2==0 || (seen ^ invert)==0 ){ - return 0; + return SQLITE_NOMATCH; } continue; } @@ -744,23 +764,25 @@ static int patternCompare( continue; } if( c==matchOne && zPattern!=zEscaped && c2!=0 ) continue; - return 0; + return SQLITE_NOMATCH; } - return *zString==0; + return *zString==0 ? SQLITE_MATCH : SQLITE_NOMATCH; } /* -** The sqlite3_strglob() interface. +** The sqlite3_strglob() interface. Return 0 on a match (like strcmp()) and +** non-zero if there is no match. */ int sqlite3_strglob(const char *zGlobPattern, const char *zString){ - return patternCompare((u8*)zGlobPattern, (u8*)zString, &globInfo, '[')==0; + return patternCompare((u8*)zGlobPattern, (u8*)zString, &globInfo, '['); } /* -** The sqlite3_strlike() interface. +** The sqlite3_strlike() interface. Return 0 on a match and non-zero for +** a miss - like strcmp(). */ int sqlite3_strlike(const char *zPattern, const char *zStr, unsigned int esc){ - return patternCompare((u8*)zPattern, (u8*)zStr, &likeInfoNorm, esc)==0; + return patternCompare((u8*)zPattern, (u8*)zStr, &likeInfoNorm, esc); } /* @@ -841,7 +863,7 @@ static void likeFunc( #ifdef SQLITE_TEST sqlite3_like_count++; #endif - sqlite3_result_int(context, patternCompare(zB, zA, pInfo, escape)); + sqlite3_result_int(context, patternCompare(zB, zA, pInfo, escape)==SQLITE_MATCH); } } @@ -1612,7 +1634,7 @@ static void groupConcatStep( zSep = ","; nSep = 1; } - if( nSep ) sqlite3StrAccumAppend(pAccum, zSep, nSep); + if( zSep ) sqlite3StrAccumAppend(pAccum, zSep, nSep); } zVal = (char*)sqlite3_value_text(argv[0]); nVal = sqlite3_value_bytes(argv[0]); @@ -1753,6 +1775,9 @@ void sqlite3RegisterBuiltinFunctions(void){ FUNCTION2(unlikely, 1, 0, 0, noopFunc, SQLITE_FUNC_UNLIKELY), FUNCTION2(likelihood, 2, 0, 0, noopFunc, SQLITE_FUNC_UNLIKELY), FUNCTION2(likely, 1, 0, 0, noopFunc, SQLITE_FUNC_UNLIKELY), +#ifdef SQLITE_DEBUG + FUNCTION2(affinity, 1, 0, 0, noopFunc, SQLITE_FUNC_AFFINITY), +#endif FUNCTION(ltrim, 1, 1, 0, trimFunc ), FUNCTION(ltrim, 2, 1, 0, trimFunc ), FUNCTION(rtrim, 1, 2, 0, trimFunc ), diff --git a/src/global.c b/src/global.c index 76901a89b1..a105135561 100644 --- a/src/global.c +++ b/src/global.c @@ -79,16 +79,13 @@ const unsigned char sqlite3UpperToLower[] = { ** ** (x & ~(map[x]&0x20)) ** -** Standard function tolower() is implemented using the sqlite3UpperToLower[] +** The equivalent of tolower() is implemented using the sqlite3UpperToLower[] ** array. tolower() is used more often than toupper() by SQLite. ** -** Bit 0x40 is set if the character non-alphanumeric and can be used in an +** Bit 0x40 is set if the character is non-alphanumeric and can be used in an ** SQLite identifier. Identifiers are alphanumerics, "_", "$", and any ** non-ASCII UTF character. Hence the test for whether or not a character is ** part of an identifier is 0x46. -** -** SQLite's versions are identical to the standard versions assuming a -** locale of "C". They are implemented as macros in sqliteInt.h. */ #ifdef SQLITE_ASCII const unsigned char sqlite3CtypeMap[256] = { @@ -161,7 +158,7 @@ const unsigned char sqlite3CtypeMap[256] = { #endif /* Statement journals spill to disk when their size exceeds the following -** threashold (in bytes). 0 means that statement journals are created and +** threshold (in bytes). 0 means that statement journals are created and ** written to disk immediately (the default behavior for SQLite versions ** before 3.12.0). -1 means always keep the entire statement journal in ** memory. (The statement journal is also always held entirely in memory @@ -172,6 +169,19 @@ const unsigned char sqlite3CtypeMap[256] = { # define SQLITE_STMTJRNL_SPILL (64*1024) #endif +/* +** The default lookaside-configuration, the format "SZ,N". SZ is the +** number of bytes in each lookaside slot (should be a multiple of 8) +** and N is the number of slots. The lookaside-configuration can be +** changed as start-time using sqlite3_config(SQLITE_CONFIG_LOOKASIDE) +** or at run-time for an individual database connection using +** sqlite3_db_config(db, SQLITE_DBCONFIG_LOOKASIDE); +*/ +#ifndef SQLITE_DEFAULT_LOOKASIDE +# define SQLITE_DEFAULT_LOOKASIDE 1200,100 +#endif + + /* ** The following singleton contains the global configuration for ** the SQLite library. @@ -184,8 +194,7 @@ SQLITE_WSD struct Sqlite3Config sqlite3Config = { SQLITE_ALLOW_COVERING_INDEX_SCAN, /* bUseCis */ 0x7ffffffe, /* mxStrlen */ 0, /* neverCorrupt */ - 128, /* szLookaside */ - 500, /* nLookaside */ + SQLITE_DEFAULT_LOOKASIDE, /* szLookaside, nLookaside */ SQLITE_STMTJRNL_SPILL, /* nStmtSpill */ {0,0,0,0,0,0,0,0}, /* m */ {0,0,0,0,0,0,0,0,0}, /* mutex */ @@ -222,10 +231,11 @@ SQLITE_WSD struct Sqlite3Config sqlite3Config = { 0, /* xVdbeBranch */ 0, /* pVbeBranchArg */ #endif -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE 0, /* xTestCallback */ #endif - 0 /* bLocaltimeFault */ + 0, /* bLocaltimeFault */ + 0x7ffffffe /* iOnceResetThreshold */ }; /* @@ -248,7 +258,7 @@ const Token sqlite3IntTokens[] = { ** The value of the "pending" byte must be 0x40000000 (1 byte past the ** 1-gibabyte boundary) in a compatible database. SQLite never uses ** the database page that contains the pending byte. It never attempts -** to read or write that page. The pending byte page is set assign +** to read or write that page. The pending byte page is set aside ** for use by the VFS layers as space for managing file locks. ** ** During testing, it is often desirable to move the pending byte to diff --git a/src/hash.c b/src/hash.c index eea2dd1ac2..62b695a1bb 100644 --- a/src/hash.c +++ b/src/hash.c @@ -56,7 +56,11 @@ static unsigned int strHash(const char *z){ unsigned int h = 0; unsigned char c; while( (c = (unsigned char)*z++)!=0 ){ /*OPTIMIZATION-IF-TRUE*/ - h = (h<<3) ^ h ^ sqlite3UpperToLower[c]; + /* Knuth multiplicative hashing. (Sorting & Searching, p. 510). + ** 0x9e3779b1 is 2654435761 which is the closest prime number to + ** (2**32)*golden_ratio, where golden_ratio = (sqrt(5) - 1)/2. */ + h += sqlite3UpperToLower[c]; + h *= 0x9e3779b1; } return h; } diff --git a/src/in-operator.md b/src/in-operator.md new file mode 100644 index 0000000000..e9ad2101aa --- /dev/null +++ b/src/in-operator.md @@ -0,0 +1,107 @@ +IN-Operator Implementation Notes +================================ + +## Definitions: + +An IN operator has one of the following formats: + +> + x IN (y1,y2,y3,...,yN) + x IN (subquery) + +The "x" is referred to as the LHS (left-hand side). The list or subquery +on the right is called the RHS (right-hand side). If the RHS is a list +it must be a non-empty list. But if the RHS is a subquery, it can be an +empty set. + +The LHS can be a scalar (a single quantity) or a vector (a list of +two or or more values) or a subquery that returns one or more columns. +We use the term "vector" to mean an actually list of values or a +subquery that returns two or more columns. An isolated value or +a subquery that returns a single columns is called a scalar. + +The RHS can be a subquery that returns a single column, a subquery +that returns two or more columns, or a list of scalars. It is not +currently support for the RHS to be a list of vectors. + +The number of columns for LHS must match the number of columns for +the RHS. If the RHS is a list of values, then the LHS must be a +scalar. If the RHS is a subquery returning N columns, then the LHS +must be a vector of size N. + +NULL values can occur in either or both of the LHS and RHS. +If the LHS contains only +NULL values then we say that it is a "total-NULL". If the LHS contains +some NULL values and some non-NULL values, then it is a "partial-NULL". +For a scalar, there is no difference between a partial-NULL and a total-NULL. +The RHS is a partial-NULL if any row contains a NULL value. The RHS is +a total-NULL if it contains one or more rows that contain only NULL values. +The LHS is called "non-NULL" if it contains no NULL values. The RHS is +called "non-NULL" if it contains no NULL values in any row. + +The result of an IN operator is one of TRUE, FALSE, or NULL. A NULL result +means that it cannot be determined if the LHS is contained in the RHS due +to the presence of NULL values. In some contexts (for example, when the IN +operator occurs in a WHERE clause) +the system only needs a binary result: TRUE or NOT-TRUE. One can also +to define a binary result of FALSE and NOT-FALSE, but +it turns out that no extra optimizations are possible in that case, so if +the FALSE/NOT-FALSE binary is needed, we have to compute the three-state +TRUE/FALSE/NULL result and then combine the TRUE and NULL values into +NOT-FALSE. + +A "NOT IN" operator is computed by first computing the equivalent IN +operator, then interchanging the TRUE and FALSE results. + +## Simple Full-Scan Algorithm + +The following algorithm always compute the correct answer. However, this +algorithm is suboptimal, especially if there are many rows on the RHS. + + 1. Set the null-flag to false + 2. For each row in the RHS: +
      +
    1. Compare the LHS against the RHS +
    2. If the LHS exactly matches the RHS, immediately return TRUE +
    3. If the comparison result is NULL, set the null-flag to true +
    + 3. If the null-flag is true, return NULL. + 4. Return FALSE + +## Optimized Algorithm + +The following procedure computes the same answer as the simple full-scan +algorithm, though it does so with less work in the common case. This +is the algorithm that is implemented in SQLite. + + 1. If the RHS is a constant list of length 1 or 2, then rewrite the + IN operator as a simple expression. Implement + + x IN (y1,y2) + + as if it were + + x=y1 OR x=y2 + + This is the INDEX_NOOP optimization and is only undertaken if the + IN operator is used for membership testing. If the IN operator is + driving a loop, then skip this step entirely. + + 2. Check the LHS to see if it is a partial-NULL and if it is, jump + ahead to step 5. + + 3. Do a binary search of the RHS using the LHS as a probe. If + an exact match is found, return TRUE. + + 4. If the RHS is non-NULL then return FALSE. + + 5. If we do not need to distinguish between FALSE and NULL, + then return FALSE. + + 6. For each row in the RHS, compare that row against the LHS and + if the result is NULL, immediately return NULL. In the case + of a scalar IN operator, we only need to look at the very first + row the RHS because for a scalar RHS, all NULLs will always come + first. If the RHS is empty, this step is a no-op. + + 7. Return FALSE. diff --git a/src/insert.c b/src/insert.c index 5dc045ab69..e261c1994e 100644 --- a/src/insert.c +++ b/src/insert.c @@ -200,7 +200,9 @@ static int readsTable(Parse *p, int iDb, Table *pTab){ /* ** Locate or create an AutoincInfo structure associated with table pTab ** which is in database iDb. Return the register number for the register -** that holds the maximum rowid. +** that holds the maximum rowid. Return zero if pTab is not an AUTOINCREMENT +** table. (Also return zero when doing a VACUUM since we do not want to +** update the AUTOINCREMENT counters during a VACUUM.) ** ** There is at most one AutoincInfo structure per table even if the ** same table is autoincremented multiple times due to inserts within @@ -223,7 +225,9 @@ static int autoIncBegin( Table *pTab /* The table we are writing to */ ){ int memId = 0; /* Register holding maximum rowid */ - if( pTab->tabFlags & TF_Autoincrement ){ + if( (pTab->tabFlags & TF_Autoincrement)!=0 + && (pParse->db->flags & SQLITE_Vacuum)==0 + ){ Parse *pToplevel = sqlite3ParseToplevel(pParse); AutoincInfo *pInfo; @@ -481,8 +485,7 @@ void sqlite3Insert( sqlite3 *db; /* The main database structure */ Table *pTab; /* The table to insert into. aka TABLE */ char *zTab; /* Name of the table into which we are inserting */ - const char *zDb; /* Name of the database holding this table */ - int i, j, idx; /* Loop counters */ + int i, j; /* Loop counters */ Vdbe *v; /* Generate code into this virtual machine */ Index *pIdx; /* For looping over indices of the table */ int nColumn; /* Number of columns in the data */ @@ -496,7 +499,6 @@ void sqlite3Insert( int addrCont = 0; /* Top of insert loop. Label "C" in templates 3 and 4 */ SelectDest dest; /* Destination for SELECT on rhs of INSERT */ int iDb; /* Index of database holding TABLE */ - Db *pDb; /* The database containing table being inserted into */ u8 useTempTable = 0; /* Store SELECT results in intermediate table */ u8 appendFlag = 0; /* True if the insert is likely to be an append */ u8 withoutRowid; /* 0 for normal table. 1 for WITHOUT ROWID table */ @@ -546,9 +548,8 @@ void sqlite3Insert( } iDb = sqlite3SchemaToIndex(db, pTab->pSchema); assert( iDbnDb ); - pDb = &db->aDb[iDb]; - zDb = pDb->zName; - if( sqlite3AuthCheck(pParse, SQLITE_INSERT, pTab->zName, 0, zDb) ){ + if( sqlite3AuthCheck(pParse, SQLITE_INSERT, pTab->zName, 0, + db->aDb[iDb].zDbSName) ){ goto insert_cleanup; } withoutRowid = !HasRowid(pTab); @@ -791,8 +792,10 @@ void sqlite3Insert( if( aRegIdx==0 ){ goto insert_cleanup; } - for(i=0; ipIndex; ipNext, i++){ + assert( pIdx ); aRegIdx[i] = ++pParse->nMem; + pParse->nMem += pIdx->nColumn; } } @@ -994,12 +997,26 @@ void sqlite3Insert( #endif { int isReplace; /* Set to true if constraints may cause a replace */ + int bUseSeek; /* True to use OPFLAG_SEEKRESULT */ sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur, regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, 0 ); sqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0); + + /* Set the OPFLAG_USESEEKRESULT flag if either (a) there are no REPLACE + ** constraints or (b) there are no triggers and this table is not a + ** parent table in a foreign key constraint. It is safe to set the + ** flag in the second case as if any REPLACE constraint is hit, an + ** OP_Delete or OP_IdxDelete instruction will be executed on each + ** cursor that is disturbed. And these instructions both clear the + ** VdbeCursor.seekResult variable, disabling the OPFLAG_USESEEKRESULT + ** functionality. */ + bUseSeek = (isReplace==0 || (pTrigger==0 && + ((db->flags & SQLITE_ForeignKeys)==0 || sqlite3FkReferences(pTab)==0) + )); sqlite3CompleteInsertion(pParse, pTab, iDataCur, iIdxCur, - regIns, aRegIdx, 0, appendFlag, isReplace==0); + regIns, aRegIdx, 0, appendFlag, bUseSeek + ); } } @@ -1028,14 +1045,6 @@ void sqlite3Insert( sqlite3VdbeJumpHere(v, addrInsTop); } - if( !IsVirtual(pTab) && !isView ){ - /* Close all tables opened */ - if( iDataCurpIndex; pIdx; pIdx=pIdx->pNext, idx++){ - sqlite3VdbeAddOp1(v, OP_Close, idx+iIdxCur); - } - } - insert_end: /* Update the sqlite_sequence table by storing the content of the ** maximum rowid counter values recorded while inserting into @@ -1242,7 +1251,6 @@ void sqlite3GenerateConstraintChecks( int ipkBottom = 0; /* Bottom of the rowid change constraint check */ u8 isUpdate; /* True if this is an UPDATE operation */ u8 bAffinityDone = 0; /* True if the OP_Affinity operation has been run */ - int regRowid = -1; /* Register holding ROWID value */ isUpdate = regOldData!=0; db = pParse->db; @@ -1297,8 +1305,9 @@ void sqlite3GenerateConstraintChecks( case OE_Fail: { char *zMsg = sqlite3MPrintf(db, "%s.%s", pTab->zName, pTab->aCol[i].zName); - sqlite3VdbeAddOp4(v, OP_HaltIfNull, SQLITE_CONSTRAINT_NOTNULL, onError, - regNewData+1+i, zMsg, P4_DYNAMIC); + sqlite3VdbeAddOp3(v, OP_HaltIfNull, SQLITE_CONSTRAINT_NOTNULL, onError, + regNewData+1+i); + sqlite3VdbeAppendP4(v, zMsg, P4_DYNAMIC); sqlite3VdbeChangeP5(v, P5_ConstraintNotNull); VdbeCoverage(v); break; @@ -1362,7 +1371,7 @@ void sqlite3GenerateConstraintChecks( } if( isUpdate ){ - /* pkChng!=0 does not mean that the rowid has change, only that + /* pkChng!=0 does not mean that the rowid has changed, only that ** it might have changed. Skip the conflict logic below if the rowid ** is unchanged. */ sqlite3VdbeAddOp3(v, OP_Eq, regNewData, addrRowidOk, regOldData); @@ -1440,7 +1449,7 @@ void sqlite3GenerateConstraintChecks( ** OP_Insert replace the existing entry than it is to delete the ** existing entry and then insert a new one. */ sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, OPFLAG_ISNOOP); - sqlite3VdbeChangeP4(v, -1, (char *)pTab, P4_TABLE); + sqlite3VdbeAppendP4(v, pTab, P4_TABLE); } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ if( pTab->pIndex ){ @@ -1497,7 +1506,7 @@ void sqlite3GenerateConstraintChecks( /* Create a record for this index entry as it should appear after ** the insert or update. Store that record in the aRegIdx[ix] register */ - regIdx = sqlite3GetTempRange(pParse, pIdx->nColumn); + regIdx = aRegIdx[ix]+1; for(i=0; inColumn; i++){ int iField = pIdx->aiColumn[i]; int x; @@ -1508,9 +1517,7 @@ void sqlite3GenerateConstraintChecks( VdbeComment((v, "%s column %d", pIdx->zName, i)); }else{ if( iField==XN_ROWID || iField==pTab->iPKey ){ - if( regRowid==regIdx+i ) continue; /* ROWID already in regIdx+i */ x = regNewData; - regRowid = pIdx->pPartIdxWhere ? -1 : regIdx+i; }else{ x = iField + regNewData + 1; } @@ -1520,7 +1527,6 @@ void sqlite3GenerateConstraintChecks( } sqlite3VdbeAddOp3(v, OP_MakeRecord, regIdx, pIdx->nColumn, aRegIdx[ix]); VdbeComment((v, "for %s", pIdx->zName)); - sqlite3ExprCacheAffinityChange(pParse, regIdx, pIdx->nColumn); /* In an UPDATE operation, if this index is the PRIMARY KEY index ** of a WITHOUT ROWID table and there has been no change the @@ -1534,7 +1540,6 @@ void sqlite3GenerateConstraintChecks( /* Find out what action to take in case there is a uniqueness conflict */ onError = pIdx->onError; if( onError==OE_None ){ - sqlite3ReleaseTempRange(pParse, regIdx, pIdx->nColumn); sqlite3VdbeResolveLabel(v, addrUniqueOk); continue; /* pIdx is not a UNIQUE index */ } @@ -1543,7 +1548,26 @@ void sqlite3GenerateConstraintChecks( }else if( onError==OE_Default ){ onError = OE_Abort; } - + + /* Collision detection may be omitted if all of the following are true: + ** (1) The conflict resolution algorithm is REPLACE + ** (2) The table is a WITHOUT ROWID table + ** (3) There are no secondary indexes on the table + ** (4) No delete triggers need to be fired if there is a conflict + ** (5) No FK constraint counters need to be updated if a conflict occurs. + */ + if( (ix==0 && pIdx->pNext==0) /* Condition 3 */ + && pPk==pIdx /* Condition 2 */ + && onError==OE_Replace /* Condition 1 */ + && ( 0==(db->flags&SQLITE_RecTriggers) || /* Condition 4 */ + 0==sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0)) + && ( 0==(db->flags&SQLITE_ForeignKeys) || /* Condition 5 */ + (0==pTab->pFKey && 0==sqlite3FkReferences(pTab))) + ){ + sqlite3VdbeResolveLabel(v, addrUniqueOk); + continue; + } + /* Check to see if the new index entry will be unique */ sqlite3VdbeAddOp4Int(v, OP_NoConflict, iThisCur, addrUniqueOk, regIdx, pIdx->nKeyCol); VdbeCoverage(v); @@ -1633,7 +1657,6 @@ void sqlite3GenerateConstraintChecks( } } sqlite3VdbeResolveLabel(v, addrUniqueOk); - sqlite3ReleaseTempRange(pParse, regIdx, pIdx->nColumn); if( regR!=regIdx ) sqlite3ReleaseTempRange(pParse, regR, nPkField); } if( ipkTop ){ @@ -1683,7 +1706,9 @@ void sqlite3CompleteInsertion( sqlite3VdbeAddOp2(v, OP_IsNull, aRegIdx[i], sqlite3VdbeCurrentAddr(v)+2); VdbeCoverage(v); } - sqlite3VdbeAddOp2(v, OP_IdxInsert, iIdxCur+i, aRegIdx[i]); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iIdxCur+i, aRegIdx[i], + aRegIdx[i]+1, + pIdx->uniqNotNull ? pIdx->nKeyCol: pIdx->nColumn); pik_flags = 0; if( useSeekResult ) pik_flags = OPFLAG_USESEEKRESULT; if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ @@ -1696,8 +1721,10 @@ void sqlite3CompleteInsertion( regData = regNewData + 1; regRec = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_MakeRecord, regData, pTab->nCol, regRec); - if( !bAffinityDone ) sqlite3TableAffinity(v, pTab, 0); - sqlite3ExprCacheAffinityChange(pParse, regData, pTab->nCol); + if( !bAffinityDone ){ + sqlite3TableAffinity(v, pTab, 0); + sqlite3ExprCacheAffinityChange(pParse, regData, pTab->nCol); + } if( pParse->nested ){ pik_flags = 0; }else{ @@ -1712,7 +1739,7 @@ void sqlite3CompleteInsertion( } sqlite3VdbeAddOp3(v, OP_Insert, iDataCur, regRec, regNewData); if( !pParse->nested ){ - sqlite3VdbeChangeP4(v, -1, (char *)pTab, P4_TABLE); + sqlite3VdbeAppendP4(v, pTab, P4_TABLE); } sqlite3VdbeChangeP5(v, pik_flags); } @@ -1777,15 +1804,15 @@ int sqlite3OpenTableAndIndices( for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ int iIdxCur = iBase++; assert( pIdx->pSchema==pTab->pSchema ); + if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ + if( piDataCur ) *piDataCur = iIdxCur; + p5 = 0; + } if( aToOpen==0 || aToOpen[i+1] ){ sqlite3VdbeAddOp3(v, op, iIdxCur, pIdx->tnum, iDb); sqlite3VdbeSetP4KeyInfo(pParse, pIdx); - VdbeComment((v, "%s", pIdx->zName)); - } - if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ - if( piDataCur ) *piDataCur = iIdxCur; - }else{ sqlite3VdbeChangeP5(v, p5); + VdbeComment((v, "%s", pIdx->zName)); } } if( iBase>pParse->nTab ) pParse->nTab = iBase; @@ -2095,6 +2122,7 @@ static int xferOptimization( sqlite3VdbeJumpHere(v, addr1); } if( HasRowid(pSrc) ){ + u8 insFlags; sqlite3OpenTable(pParse, iSrc, iDbSrc, pSrc, OP_OpenRead); emptySrcTest = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); VdbeCoverage(v); if( pDest->iPKey>=0 ){ @@ -2111,9 +2139,16 @@ static int xferOptimization( assert( (pDest->tabFlags & TF_Autoincrement)==0 ); } sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData); + if( db->flags & SQLITE_Vacuum ){ + sqlite3VdbeAddOp3(v, OP_Last, iDest, 0, -1); + insFlags = OPFLAG_NCHANGE|OPFLAG_LASTROWID| + OPFLAG_APPEND|OPFLAG_USESEEKRESULT; + }else{ + insFlags = OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND; + } sqlite3VdbeAddOp4(v, OP_Insert, iDest, regData, regRowid, (char*)pDest, P4_TABLE); - sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND); + sqlite3VdbeChangeP5(v, insFlags); sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0); sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); @@ -2135,7 +2170,7 @@ static int xferOptimization( sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR); VdbeComment((v, "%s", pDestIdx->zName)); addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_RowKey, iSrc, regData); + sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData); if( db->flags & SQLITE_Vacuum ){ /* This INSERT command is part of a VACUUM operation, which guarantees ** that the destination table is empty. If all indexed columns use @@ -2165,8 +2200,8 @@ static int xferOptimization( if( !HasRowid(pSrc) && pDestIdx->idxType==2 ){ idxInsFlags |= OPFLAG_NCHANGE; } - sqlite3VdbeAddOp3(v, OP_IdxInsert, iDest, regData, 1); - sqlite3VdbeChangeP5(v, idxInsFlags); + sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData); + sqlite3VdbeChangeP5(v, idxInsFlags|OPFLAG_APPEND); sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1+1); VdbeCoverage(v); sqlite3VdbeJumpHere(v, addr1); sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0); @@ -2176,6 +2211,7 @@ static int xferOptimization( sqlite3ReleaseTempReg(pParse, regRowid); sqlite3ReleaseTempReg(pParse, regData); if( emptyDestTest ){ + sqlite3AutoincrementEnd(pParse); sqlite3VdbeAddOp2(v, OP_Halt, SQLITE_OK, 0); sqlite3VdbeJumpHere(v, emptyDestTest); sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); diff --git a/src/loadext.c b/src/loadext.c index 9feabbd045..6011fd2bf8 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -620,18 +620,7 @@ int sqlite3_enable_load_extension(sqlite3 *db, int onoff){ return SQLITE_OK; } -#endif /* SQLITE_OMIT_LOAD_EXTENSION */ - -/* -** The auto-extension code added regardless of whether or not extension -** loading is supported. We need a dummy sqlite3Apis pointer for that -** code if regular extension loading is not available. This is that -** dummy pointer. -*/ -#ifdef SQLITE_OMIT_LOAD_EXTENSION -static const sqlite3_api_routines sqlite3Apis = { 0 }; -#endif - +#endif /* !defined(SQLITE_OMIT_LOAD_EXTENSION) */ /* ** The following object holds the list of automatically loaded @@ -775,6 +764,11 @@ void sqlite3AutoLoadExtensions(sqlite3 *db){ char *zErrmsg; #if SQLITE_THREADSAFE sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); +#endif +#ifdef SQLITE_OMIT_LOAD_EXTENSION + const sqlite3_api_routines *pThunk = 0; +#else + const sqlite3_api_routines *pThunk = &sqlite3Apis; #endif sqlite3_mutex_enter(mutex); if( i>=wsdAutoext.nExt ){ @@ -785,7 +779,7 @@ void sqlite3AutoLoadExtensions(sqlite3 *db){ } sqlite3_mutex_leave(mutex); zErrmsg = 0; - if( xInit && (rc = xInit(db, &zErrmsg, &sqlite3Apis))!=0 ){ + if( xInit && (rc = xInit(db, &zErrmsg, pThunk))!=0 ){ sqlite3ErrorWithMsg(db, rc, "automatic extension loading failed: %s", zErrmsg); go = 0; diff --git a/src/main.c b/src/main.c index f31f865818..9aad8fdd4c 100644 --- a/src/main.c +++ b/src/main.c @@ -789,6 +789,11 @@ int sqlite3_db_config(sqlite3 *db, int op, ...){ int rc; va_start(ap, op); switch( op ){ + case SQLITE_DBCONFIG_MAINDBNAME: { + db->aDb[0].zDbSName = va_arg(ap,char*); + rc = SQLITE_OK; + break; + } case SQLITE_DBCONFIG_LOOKASIDE: { void *pBuf = va_arg(ap, void*); /* IMP: R-26835-10964 */ int sz = va_arg(ap, int); /* IMP: R-47871-25994 */ @@ -805,6 +810,7 @@ int sqlite3_db_config(sqlite3 *db, int op, ...){ { SQLITE_DBCONFIG_ENABLE_TRIGGER, SQLITE_EnableTrigger }, { SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, SQLITE_Fts3Tokenizer }, { SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, SQLITE_LoadExtension }, + { SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, SQLITE_NoCkptOnClose }, }; unsigned int i; rc = SQLITE_ERROR; /* IMP: R-42790-23372 */ @@ -1562,7 +1568,7 @@ int sqlite3_busy_timeout(sqlite3 *db, int ms){ */ void sqlite3_interrupt(sqlite3 *db){ #ifdef SQLITE_ENABLE_API_ARMOR - if( !sqlite3SafetyCheckOk(db) ){ + if( !sqlite3SafetyCheckOk(db) && (db==0 || db->magic!=SQLITE_MAGIC_ZOMBIE) ){ (void)SQLITE_MISUSE_BKPT; return; } @@ -1838,6 +1844,8 @@ int sqlite3_trace_v2( } #endif sqlite3_mutex_enter(db->mutex); + if( mTrace==0 ) xTrace = 0; + if( xTrace==0 ) mTrace = 0; db->mTrace = mTrace; db->xTrace = xTrace; db->pTraceArg = pArg; @@ -2099,6 +2107,13 @@ int sqlite3_wal_checkpoint_v2( sqlite3Error(db, rc); } rc = sqlite3ApiExit(db, rc); + + /* If there are no active statements, clear the interrupt flag at this + ** point. */ + if( db->nVdbeActive==0 ){ + db->u1.isInterrupted = 0; + } + sqlite3_mutex_leave(db->mutex); return rc; #endif @@ -2601,6 +2616,7 @@ int sqlite3ParseUri( assert( octet>=0 && octet<256 ); if( octet==0 ){ +#ifndef SQLITE_ENABLE_URI_00_ERROR /* This branch is taken when "%00" appears within the URI. In this ** case we ignore all text in the remainder of the path, name or ** value currently being parsed. So ignore the current character @@ -2613,6 +2629,12 @@ int sqlite3ParseUri( iIn++; } continue; +#else + /* If ENABLE_URI_00_ERROR is defined, "%00" in a URI is an error. */ + *pzErrMsg = sqlite3_mprintf("unexpected %%00 in uri"); + rc = SQLITE_ERROR; + goto parse_uri_out; +#endif } c = octet; }else if( eState==1 && (c=='&' || c=='=') ){ @@ -2717,7 +2739,9 @@ int sqlite3ParseUri( }else{ zFile = sqlite3_malloc64(nUri+2); if( !zFile ) return SQLITE_NOMEM_BKPT; - memcpy(zFile, zUri, nUri); + if( nUri ){ + memcpy(zFile, zUri, nUri); + } zFile[nUri] = '\0'; zFile[nUri+1] = '\0'; flags &= ~SQLITE_OPEN_URI; @@ -2932,9 +2956,9 @@ static int openDatabase( /* The default safety_level for the main database is FULL; for the temp ** database it is OFF. This matches the pager layer defaults. */ - db->aDb[0].zName = "main"; + db->aDb[0].zDbSName = "main"; db->aDb[0].safety_level = SQLITE_DEFAULT_SYNCHRONOUS+1; - db->aDb[1].zName = "temp"; + db->aDb[1].zDbSName = "temp"; db->aDb[1].safety_level = PAGER_SYNCHRONOUS_OFF; db->magic = SQLITE_MAGIC_OPEN; @@ -2948,11 +2972,20 @@ static int openDatabase( */ sqlite3Error(db, SQLITE_OK); sqlite3RegisterPerConnectionBuiltinFunctions(db); + rc = sqlite3_errcode(db); + +#ifdef SQLITE_ENABLE_FTS5 + /* Register any built-in FTS5 module before loading the automatic + ** extensions. This allows automatic extensions to register FTS5 + ** tokenizers and auxiliary functions. */ + if( !db->mallocFailed && rc==SQLITE_OK ){ + rc = sqlite3Fts5Init(db); + } +#endif /* Load automatic extensions - extensions that have been registered ** using the sqlite3_automatic_extension() API. */ - rc = sqlite3_errcode(db); if( rc==SQLITE_OK ){ sqlite3AutoLoadExtensions(db); rc = sqlite3_errcode(db); @@ -2981,12 +3014,6 @@ static int openDatabase( } #endif -#ifdef SQLITE_ENABLE_FTS5 - if( !db->mallocFailed && rc==SQLITE_OK ){ - rc = sqlite3Fts5Init(db); - } -#endif - #ifdef SQLITE_ENABLE_ICU if( !db->mallocFailed && rc==SQLITE_OK ){ rc = sqlite3IcuInit(db); @@ -3508,7 +3535,7 @@ int sqlite3_file_control(sqlite3 *db, const char *zDbName, int op, void *pArg){ */ int sqlite3_test_control(int op, ...){ int rc = 0; -#ifdef SQLITE_OMIT_BUILTIN_TEST +#ifdef SQLITE_UNTESTABLE UNUSED_PARAMETER(op); #else va_list ap; @@ -3774,6 +3801,15 @@ int sqlite3_test_control(int op, ...){ break; } + /* Set the threshold at which OP_Once counters reset back to zero. + ** By default this is 0x7ffffffe (over 2 billion), but that value is + ** too big to test in a reasonable amount of time, so this control is + ** provided to set a small and easily reachable reset value. + */ + case SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD: { + sqlite3GlobalConfig.iOnceResetThreshold = va_arg(ap, int); + break; + } /* sqlite3_test_control(SQLITE_TESTCTRL_VDBE_COVERAGE, xCallback, ptr); ** @@ -3836,7 +3872,7 @@ int sqlite3_test_control(int op, ...){ } } va_end(ap); -#endif /* SQLITE_OMIT_BUILTIN_TEST */ +#endif /* SQLITE_UNTESTABLE */ return rc; } @@ -3892,15 +3928,8 @@ sqlite3_int64 sqlite3_uri_int64( ** Return the Btree pointer identified by zDbName. Return NULL if not found. */ Btree *sqlite3DbNameToBtree(sqlite3 *db, const char *zDbName){ - int i; - for(i=0; inDb; i++){ - if( db->aDb[i].pBt - && (zDbName==0 || sqlite3StrICmp(zDbName, db->aDb[i].zName)==0) - ){ - return db->aDb[i].pBt; - } - } - return 0; + int iDb = zDbName ? sqlite3FindDbName(db, zDbName) : 0; + return iDb<0 ? 0 : db->aDb[iDb].pBt; } /* @@ -3947,7 +3976,6 @@ int sqlite3_snapshot_get( ){ int rc = SQLITE_ERROR; #ifndef SQLITE_OMIT_WAL - int iDb; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ){ @@ -3956,13 +3984,15 @@ int sqlite3_snapshot_get( #endif sqlite3_mutex_enter(db->mutex); - iDb = sqlite3FindDbName(db, zDb); - if( iDb==0 || iDb>1 ){ - Btree *pBt = db->aDb[iDb].pBt; - if( 0==sqlite3BtreeIsInTrans(pBt) ){ - rc = sqlite3BtreeBeginTrans(pBt, 0); - if( rc==SQLITE_OK ){ - rc = sqlite3PagerSnapshotGet(sqlite3BtreePager(pBt), ppSnapshot); + if( db->autoCommit==0 ){ + int iDb = sqlite3FindDbName(db, zDb); + if( iDb==0 || iDb>1 ){ + Btree *pBt = db->aDb[iDb].pBt; + if( 0==sqlite3BtreeIsInTrans(pBt) ){ + rc = sqlite3BtreeBeginTrans(pBt, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3PagerSnapshotGet(sqlite3BtreePager(pBt), ppSnapshot); + } } } } @@ -4009,6 +4039,38 @@ int sqlite3_snapshot_open( return rc; } +/* +** Recover as many snapshots as possible from the wal file associated with +** schema zDb of database db. +*/ +int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb){ + int rc = SQLITE_ERROR; + int iDb; +#ifndef SQLITE_OMIT_WAL + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + return SQLITE_MISUSE_BKPT; + } +#endif + + sqlite3_mutex_enter(db->mutex); + iDb = sqlite3FindDbName(db, zDb); + if( iDb==0 || iDb>1 ){ + Btree *pBt = db->aDb[iDb].pBt; + if( 0==sqlite3BtreeIsInReadTrans(pBt) ){ + rc = sqlite3BtreeBeginTrans(pBt, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3PagerSnapshotRecover(sqlite3BtreePager(pBt)); + sqlite3BtreeCommit(pBt); + } + } + } + sqlite3_mutex_leave(db->mutex); +#endif /* SQLITE_OMIT_WAL */ + return rc; +} + /* ** Free a snapshot handle obtained from sqlite3_snapshot_get(). */ diff --git a/src/malloc.c b/src/malloc.c index 462d78e68c..84191c78a1 100644 --- a/src/malloc.c +++ b/src/malloc.c @@ -519,7 +519,7 @@ void *sqlite3Realloc(void *pOld, u64 nBytes){ sqlite3_mutex_enter(mem0.mutex); sqlite3StatusHighwater(SQLITE_STATUS_MALLOC_SIZE, (int)nBytes); nDiff = nNew - nOld; - if( sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED) >= + if( nDiff>0 && sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED) >= mem0.alarmThreshold-nDiff ){ sqlite3MallocAlarm(nDiff); } @@ -726,9 +726,8 @@ char *sqlite3DbStrDup(sqlite3 *db, const char *z){ if( z==0 ){ return 0; } - n = sqlite3Strlen30(z) + 1; - assert( (n&0x7fffffff)==n ); - zNew = sqlite3DbMallocRaw(db, (int)n); + n = strlen(z) + 1; + zNew = sqlite3DbMallocRaw(db, n); if( zNew ){ memcpy(zNew, z, n); } diff --git a/src/os_unix.c b/src/os_unix.c index 08d4cb5d11..7f0ebdba6f 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -1061,7 +1061,14 @@ struct unixFileId { #if OS_VXWORKS struct vxworksFileId *pId; /* Unique file ID for vxworks. */ #else - ino_t ino; /* Inode number */ + /* We are told that some versions of Android contain a bug that + ** sizes ino_t at only 32-bits instead of 64-bits. (See + ** https://android-review.googlesource.com/#/c/115351/3/dist/sqlite3.c) + ** To work around this, always allocate 64-bits for the inode number. + ** On small machines that only have 32-bit inodes, this wastes 4 bytes, + ** but that should not be a big deal. */ + /* WAS: ino_t ino; */ + u64 ino; /* Inode number */ #endif }; @@ -1306,7 +1313,7 @@ static int findInodeInfo( #if OS_VXWORKS fileId.pId = pFile->pId; #else - fileId.ino = statbuf.st_ino; + fileId.ino = (u64)statbuf.st_ino; #endif pInode = inodeList; while( pInode && memcmp(&fileId, &pInode->fileId, sizeof(fileId)) ){ @@ -1340,7 +1347,8 @@ static int fileHasMoved(unixFile *pFile){ #else struct stat buf; return pFile->pInode!=0 && - (osStat(pFile->zPath, &buf)!=0 || buf.st_ino!=pFile->pInode->fileId.ino); + (osStat(pFile->zPath, &buf)!=0 + || (u64)buf.st_ino!=pFile->pInode->fileId.ino); #endif } @@ -5512,7 +5520,7 @@ static UnixUnusedFd *findReusableFd(const char *zPath, int flags){ unixEnterMutex(); pInode = inodeList; while( pInode && (pInode->fileId.dev!=sStat.st_dev - || pInode->fileId.ino!=sStat.st_ino) ){ + || pInode->fileId.ino!=(u64)sStat.st_ino) ){ pInode = pInode->pNext; } if( pInode ){ diff --git a/src/os_win.c b/src/os_win.c index 6224088583..2cb5f7b0c8 100644 --- a/src/os_win.c +++ b/src/os_win.c @@ -3488,6 +3488,12 @@ static int winFileControl(sqlite3_file *id, int op, void *pArg){ OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h)); return SQLITE_OK; } + case SQLITE_FCNTL_WIN32_GET_HANDLE: { + LPHANDLE phFile = (LPHANDLE)pArg; + *phFile = pFile->h; + OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h)); + return SQLITE_OK; + } #ifdef SQLITE_TEST case SQLITE_FCNTL_WIN32_SET_HANDLE: { LPHANDLE phFile = (LPHANDLE)pArg; diff --git a/src/pager.c b/src/pager.c index 240bbb5f63..a7e83242dd 100644 --- a/src/pager.c +++ b/src/pager.c @@ -696,6 +696,7 @@ struct Pager { int nRead; /* Database pages read */ #endif void (*xReiniter)(DbPage*); /* Call this routine when reloading pages */ + int (*xGet)(Pager*,Pgno,DbPage**,int); /* Routine to fetch a patch */ #ifdef SQLITE_HAS_CODEC void *(*xCodec)(void*,void*,Pgno,int); /* Routine for en/decoding data */ void (*xCodecSizeChng)(void*,int,int); /* Notify of page size changes */ @@ -820,9 +821,10 @@ static const unsigned char aJournalMagic[] = { ** rollback journal. Otherwise false. */ #ifndef SQLITE_OMIT_WAL -static int pagerUseWal(Pager *pPager){ +int sqlite3PagerUseWal(Pager *pPager){ return (pPager->pWal!=0); } +# define pagerUseWal(x) sqlite3PagerUseWal(x) #else # define pagerUseWal(x) 0 # define pagerRollbackWal(x) 0 @@ -1023,6 +1025,33 @@ static char *print_pager_state(Pager *p){ } #endif +/* Forward references to the various page getters */ +static int getPageNormal(Pager*,Pgno,DbPage**,int); +static int getPageError(Pager*,Pgno,DbPage**,int); +#if SQLITE_MAX_MMAP_SIZE>0 +static int getPageMMap(Pager*,Pgno,DbPage**,int); +#endif + +/* +** Set the Pager.xGet method for the appropriate routine used to fetch +** content from the pager. +*/ +static void setGetterMethod(Pager *pPager){ + if( pPager->errCode ){ + pPager->xGet = getPageError; +#if SQLITE_MAX_MMAP_SIZE>0 + }else if( USEFETCH(pPager) +#ifdef SQLITE_HAS_CODEC + && pPager->xCodec==0 +#endif + ){ + pPager->xGet = getPageMMap; +#endif /* SQLITE_MAX_MMAP_SIZE>0 */ + }else{ + pPager->xGet = getPageNormal; + } +} + /* ** Return true if it is necessary to write page *pPg into the sub-journal. ** A page needs to be written into the sub-journal if there exists one @@ -1882,6 +1911,7 @@ static void pager_unlock(Pager *pPager){ } if( USEFETCH(pPager) ) sqlite3OsUnfetch(pPager->fd, 0, 0); pPager->errCode = SQLITE_OK; + setGetterMethod(pPager); } pPager->journalOff = 0; @@ -1919,6 +1949,7 @@ static int pager_error(Pager *pPager, int rc){ if( rc2==SQLITE_FULL || rc2==SQLITE_IOERR ){ pPager->errCode = rc; pPager->eState = PAGER_ERROR; + setGetterMethod(pPager); } return rc; } @@ -2086,7 +2117,7 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){ pagerFreeBitvecs(pPager); pPager->nRec = 0; if( rc==SQLITE_OK ){ - if( pagerFlushOnCommit(pPager, bCommit) ){ + if( MEMDB || pagerFlushOnCommit(pPager, bCommit) ){ sqlite3PcacheCleanAll(pPager->pPCache); }else{ sqlite3PcacheClearWritable(pPager->pPCache); @@ -3495,6 +3526,7 @@ static void pagerFixMaplimit(Pager *pPager){ sqlite3_int64 sz; sz = pPager->szMmap; pPager->bUseFetch = (sz>0); + setGetterMethod(pPager); sqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_MMAP_SIZE, &sz); } #endif @@ -3991,6 +4023,7 @@ static int pagerSyncHotJournal(Pager *pPager){ return rc; } +#if SQLITE_MAX_MMAP_SIZE>0 /* ** Obtain a reference to a memory mapped page object for page number pgno. ** The new object will use the pointer pData, obtained from xFetch(). @@ -4013,7 +4046,8 @@ static int pagerAcquireMapPage( *ppPage = p = pPager->pMmapFreelist; pPager->pMmapFreelist = p->pDirty; p->pDirty = 0; - memset(p->pExtra, 0, pPager->nExtra); + assert( pPager->nExtra>=8 ); + memset(p->pExtra, 0, 8); }else{ *ppPage = p = (PgHdr *)sqlite3MallocZero(sizeof(PgHdr) + pPager->nExtra); if( p==0 ){ @@ -4038,6 +4072,7 @@ static int pagerAcquireMapPage( return SQLITE_OK; } +#endif /* ** Release a reference to page pPg. pPg must have been returned by an @@ -4080,9 +4115,10 @@ static void pagerFreeMapHdrs(Pager *pPager){ ** a hot journal may be left in the filesystem but no error is returned ** to the caller. */ -int sqlite3PagerClose(Pager *pPager){ +int sqlite3PagerClose(Pager *pPager, sqlite3 *db){ u8 *pTmp = (u8 *)pPager->pTmpSpace; + assert( db || pagerUseWal(pPager)==0 ); assert( assert_pager_state(pPager) ); disable_simulated_io_errors(); sqlite3BeginBenignMalloc(); @@ -4090,7 +4126,10 @@ int sqlite3PagerClose(Pager *pPager){ /* pPager->errCode = 0; */ pPager->exclusiveMode = 0; #ifndef SQLITE_OMIT_WAL - sqlite3WalClose(pPager->pWal, pPager->ckptSyncFlags, pPager->pageSize, pTmp); + assert( db || pPager->pWal==0 ); + sqlite3WalClose(pPager->pWal, db, pPager->ckptSyncFlags, pPager->pageSize, + (db && (db->flags & SQLITE_NoCkptOnClose) ? 0 : pTmp) + ); pPager->pWal = 0; #endif pager_reset(pPager); @@ -4615,7 +4654,9 @@ int sqlite3PagerFlush(Pager *pPager){ ** ** The nExtra parameter specifies the number of bytes of space allocated ** along with each page reference. This space is available to the user -** via the sqlite3PagerGetExtra() API. +** via the sqlite3PagerGetExtra() API. When a new page is allocated, the +** first 8 bytes of this space are zeroed but the remainder is uninitialized. +** (The extra space is used by btree as the MemPage object.) ** ** The flags argument is used to specify properties that affect the ** operation of the pager. It should be passed some bitwise combination @@ -4845,8 +4886,8 @@ act_like_temp_file: /* Initialize the PCache object. */ if( rc==SQLITE_OK ){ - assert( nExtra<1000 ); nExtra = ROUND8(nExtra); + assert( nExtra>=8 && nExtra<1000 ); rc = sqlite3PcacheOpen(szPageDflt, nExtra, !memDb, !memDb?pagerStress:0, (void *)pPager, pPager->pPCache); } @@ -4911,6 +4952,7 @@ act_like_temp_file: /* pPager->xBusyHandler = 0; */ /* pPager->pBusyHandlerArg = 0; */ pPager->xReiniter = xReinit; + setGetterMethod(pPager); /* memset(pPager->aHash, 0, sizeof(pPager->aHash)); */ /* pPager->szMmap = SQLITE_DEFAULT_MMAP_SIZE // will be set by btree.c */ @@ -5324,10 +5366,17 @@ static void pagerUnlockIfUnused(Pager *pPager){ } /* -** Acquire a reference to page number pgno in pager pPager (a page -** reference has type DbPage*). If the requested reference is +** The page getter methods each try to acquire a reference to a +** page with page number pgno. If the requested reference is ** successfully obtained, it is copied to *ppPage and SQLITE_OK returned. ** +** There are different implementations of the getter method depending +** on the current state of the pager. +** +** getPageNormal() -- The normal getter +** getPageError() -- Used if the pager is in an error state +** getPageMmap() -- Used if memory-mapped I/O is enabled +** ** If the requested page is already in the cache, it is returned. ** Otherwise, a new page object is allocated and populated with data ** read from the database file. In some cases, the pcache module may @@ -5339,14 +5388,14 @@ static void pagerUnlockIfUnused(Pager *pPager){ ** already in the cache when this function is called, then the extra ** data is left as it was when the page object was last used. ** -** If the database image is smaller than the requested page or if a -** non-zero value is passed as the noContent parameter and the +** If the database image is smaller than the requested page or if +** the flags parameter contains the PAGER_GET_NOCONTENT bit and the ** requested page is not already stored in the cache, then no ** actual disk read occurs. In this case the memory image of the ** page is initialized to all zeros. ** -** If noContent is true, it means that we do not care about the contents -** of the page. This occurs in two scenarios: +** If PAGER_GET_NOCONTENT is true, it means that we do not care about +** the contents of the page. This occurs in two scenarios: ** ** a) When reading a free-list leaf page from the database, and ** @@ -5354,8 +5403,8 @@ static void pagerUnlockIfUnused(Pager *pPager){ ** a new page into the cache to be filled with the data read ** from the savepoint journal. ** -** If noContent is true, then the data returned is zeroed instead of -** being read from the database. Additionally, the bits corresponding +** If PAGER_GET_NOCONTENT is true, then the data returned is zeroed instead +** of being read from the database. Additionally, the bits corresponding ** to pgno in Pager.pInJournal (bitvec of pages already written to the ** journal file) and the PagerSavepoint.pInSavepoint bitvecs of any open ** savepoints are set. This means if the page is made writable at any @@ -5373,39 +5422,20 @@ static void pagerUnlockIfUnused(Pager *pPager){ ** Since Lookup() never goes to disk, it never has to deal with locks ** or journal files. */ -int sqlite3PagerGet( +static int getPageNormal( Pager *pPager, /* The pager open on the database file */ Pgno pgno, /* Page number to fetch */ DbPage **ppPage, /* Write a pointer to the page here */ int flags /* PAGER_GET_XXX flags */ ){ int rc = SQLITE_OK; - PgHdr *pPg = 0; - u32 iFrame = 0; /* Frame to read from WAL file */ - const int noContent = (flags & PAGER_GET_NOCONTENT); + PgHdr *pPg; + u8 noContent; /* True if PAGER_GET_NOCONTENT is set */ + sqlite3_pcache_page *pBase; - /* It is acceptable to use a read-only (mmap) page for any page except - ** page 1 if there is no write-transaction open or the ACQUIRE_READONLY - ** flag was specified by the caller. And so long as the db is not a - ** temporary or in-memory database. */ - const int bMmapOk = (pgno>1 && USEFETCH(pPager) - && (pPager->eState==PAGER_READER || (flags & PAGER_GET_READONLY)) -#ifdef SQLITE_HAS_CODEC - && pPager->xCodec==0 -#endif - ); - - /* Optimization note: Adding the "pgno<=1" term before "pgno==0" here - ** allows the compiler optimizer to reuse the results of the "pgno>1" - ** test in the previous statement, and avoid testing pgno==0 in the - ** common case where pgno is large. */ - if( pgno<=1 && pgno==0 ){ - return SQLITE_CORRUPT_BKPT; - } + assert( pPager->errCode==SQLITE_OK ); assert( pPager->eState>=PAGER_READER ); assert( assert_pager_state(pPager) ); - assert( noContent==0 || bMmapOk==0 ); - assert( pPager->hasHeldSharedLock==1 ); #ifndef SQLITE_OMIT_CONCURRENT @@ -5418,71 +5448,23 @@ int sqlite3PagerGet( } #endif - /* If the pager is in the error state, return an error immediately. - ** Otherwise, request the page from the PCache layer. */ - if( pPager->errCode!=SQLITE_OK ){ - rc = pPager->errCode; - }else{ - if( bMmapOk && pagerUseWal(pPager) ){ - rc = sqlite3WalFindFrame(pPager->pWal, pgno, &iFrame); - if( rc!=SQLITE_OK ) goto pager_acquire_err; - } - - if( bMmapOk && iFrame==0 ){ - void *pData = 0; - - rc = sqlite3OsFetch(pPager->fd, - (i64)(pgno-1) * pPager->pageSize, pPager->pageSize, &pData - ); - - if( rc==SQLITE_OK && pData ){ - if( pPager->eState>PAGER_READER || pPager->tempFile ){ - pPg = sqlite3PagerLookup(pPager, pgno); - } - if( pPg==0 ){ - rc = pagerAcquireMapPage(pPager, pgno, pData, &pPg); - }else{ - sqlite3OsUnfetch(pPager->fd, (i64)(pgno-1)*pPager->pageSize, pData); - } - if( pPg ){ - assert( rc==SQLITE_OK ); - *ppPage = pPg; - return SQLITE_OK; - } - } - if( rc!=SQLITE_OK ){ - goto pager_acquire_err; - } - } - - { - sqlite3_pcache_page *pBase; - pBase = sqlite3PcacheFetch(pPager->pPCache, pgno, 3); - if( pBase==0 ){ - rc = sqlite3PcacheFetchStress(pPager->pPCache, pgno, &pBase); - if( rc!=SQLITE_OK ) goto pager_acquire_err; - if( pBase==0 ){ - pPg = *ppPage = 0; - rc = SQLITE_NOMEM_BKPT; - goto pager_acquire_err; - } - } - pPg = *ppPage = sqlite3PcacheFetchFinish(pPager->pPCache, pgno, pBase); - assert( pPg!=0 ); - } - } - - if( rc!=SQLITE_OK ){ - /* Either the call to sqlite3PcacheFetch() returned an error or the - ** pager was already in the error-state when this function was called. - ** Set pPg to 0 and jump to the exception handler. */ + if( pgno==0 ) return SQLITE_CORRUPT_BKPT; + pBase = sqlite3PcacheFetch(pPager->pPCache, pgno, 3); + if( pBase==0 ){ pPg = 0; - goto pager_acquire_err; + rc = sqlite3PcacheFetchStress(pPager->pPCache, pgno, &pBase); + if( rc!=SQLITE_OK ) goto pager_acquire_err; + if( pBase==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto pager_acquire_err; + } } + pPg = *ppPage = sqlite3PcacheFetchFinish(pPager->pPCache, pgno, pBase); assert( pPg==(*ppPage) ); assert( pPg->pgno==pgno ); assert( pPg->pPager==pPager || pPg->pPager==0 ); + noContent = (flags & PAGER_GET_NOCONTENT)!=0; if( pPg->pPager && !noContent ){ /* In this case the pcache already contains an initialized copy of ** the page. Return without further ado. */ @@ -5492,17 +5474,18 @@ int sqlite3PagerGet( }else{ /* The pager cache has created a new page. Its content needs to - ** be initialized. */ - - pPg->pPager = pPager; - - /* The maximum page number is 2^31. Return SQLITE_CORRUPT if a page - ** number greater than this, or the unused locking-page, is requested. */ + ** be initialized. But first some error checks: + ** + ** (1) The maximum page number is 2^31 + ** (2) Never try to fetch the locking page + */ if( pgno>PAGER_MAX_PGNO || pgno==PAGER_MJ_PGNO(pPager) ){ rc = SQLITE_CORRUPT_BKPT; goto pager_acquire_err; } + pPg->pPager = pPager; + assert( !isOpen(pPager->fd) || !MEMDB ); if( !isOpen(pPager->fd) || pPager->dbSizepPager->mxPgno ){ @@ -5528,7 +5511,8 @@ int sqlite3PagerGet( memset(pPg->pData, 0, pPager->pageSize); IOTRACE(("ZERO %p %d\n", pPager, pgno)); }else{ - if( pagerUseWal(pPager) && bMmapOk==0 ){ + u32 iFrame = 0; /* Frame to read from WAL file */ + if( pagerUseWal(pPager) ){ rc = sqlite3WalFindFrame(pPager->pWal, pgno, &iFrame); if( rc!=SQLITE_OK ) goto pager_acquire_err; } @@ -5541,7 +5525,6 @@ int sqlite3PagerGet( } pager_set_pagehash(pPg); } - return SQLITE_OK; pager_acquire_err: @@ -5550,11 +5533,109 @@ pager_acquire_err: sqlite3PcacheDrop(pPg); } pagerUnlockIfUnused(pPager); - *ppPage = 0; return rc; } +#if SQLITE_MAX_MMAP_SIZE>0 +/* The page getter for when memory-mapped I/O is enabled */ +static int getPageMMap( + Pager *pPager, /* The pager open on the database file */ + Pgno pgno, /* Page number to fetch */ + DbPage **ppPage, /* Write a pointer to the page here */ + int flags /* PAGER_GET_XXX flags */ +){ + int rc = SQLITE_OK; + PgHdr *pPg = 0; + u32 iFrame = 0; /* Frame to read from WAL file */ + + /* It is acceptable to use a read-only (mmap) page for any page except + ** page 1 if there is no write-transaction open or the ACQUIRE_READONLY + ** flag was specified by the caller. And so long as the db is not a + ** temporary or in-memory database. */ + const int bMmapOk = (pgno>1 + && (pPager->eState==PAGER_READER || (flags & PAGER_GET_READONLY)) + ); + + assert( USEFETCH(pPager) ); +#ifdef SQLITE_HAS_CODEC + assert( pPager->xCodec==0 ); +#endif + + /* Optimization note: Adding the "pgno<=1" term before "pgno==0" here + ** allows the compiler optimizer to reuse the results of the "pgno>1" + ** test in the previous statement, and avoid testing pgno==0 in the + ** common case where pgno is large. */ + if( pgno<=1 && pgno==0 ){ + return SQLITE_CORRUPT_BKPT; + } + assert( pPager->eState>=PAGER_READER ); + assert( assert_pager_state(pPager) ); + assert( pPager->hasHeldSharedLock==1 ); + assert( pPager->errCode==SQLITE_OK ); + + if( bMmapOk && pagerUseWal(pPager) ){ + rc = sqlite3WalFindFrame(pPager->pWal, pgno, &iFrame); + if( rc!=SQLITE_OK ){ + *ppPage = 0; + return rc; + } + } + if( bMmapOk && iFrame==0 ){ + void *pData = 0; + rc = sqlite3OsFetch(pPager->fd, + (i64)(pgno-1) * pPager->pageSize, pPager->pageSize, &pData + ); + if( rc==SQLITE_OK && pData ){ + if( pPager->eState>PAGER_READER || pPager->tempFile ){ + pPg = sqlite3PagerLookup(pPager, pgno); + } + if( pPg==0 ){ + rc = pagerAcquireMapPage(pPager, pgno, pData, &pPg); + }else{ + sqlite3OsUnfetch(pPager->fd, (i64)(pgno-1)*pPager->pageSize, pData); + } + if( pPg ){ + assert( rc==SQLITE_OK ); + *ppPage = pPg; + return SQLITE_OK; + } + } + if( rc!=SQLITE_OK ){ + *ppPage = 0; + return rc; + } + } + return getPageNormal(pPager, pgno, ppPage, flags); +} +#endif /* SQLITE_MAX_MMAP_SIZE>0 */ + +/* The page getter method for when the pager is an error state */ +static int getPageError( + Pager *pPager, /* The pager open on the database file */ + Pgno pgno, /* Page number to fetch */ + DbPage **ppPage, /* Write a pointer to the page here */ + int flags /* PAGER_GET_XXX flags */ +){ + UNUSED_PARAMETER(pgno); + UNUSED_PARAMETER(flags); + assert( pPager->errCode!=SQLITE_OK ); + *ppPage = 0; + return pPager->errCode; +} + + +/* Dispatch all page fetch requests to the appropriate getter method. +*/ +int sqlite3PagerGet( + Pager *pPager, /* The pager open on the database file */ + Pgno pgno, /* Page number to fetch */ + DbPage **ppPage, /* Write a pointer to the page here */ + int flags /* PAGER_GET_XXX flags */ +){ + return pPager->xGet(pPager, pgno, ppPage, flags); +} + /* ** Acquire a page if it is already in the in-memory cache. Do ** not read the page from disk. Return a pointer to the page, @@ -6031,11 +6112,11 @@ int sqlite3PagerWrite(PgHdr *pPg){ assert( (pPg->flags & PGHDR_MMAP)==0 ); assert( pPager->eState>=PAGER_WRITER_LOCKED ); assert( assert_pager_state(pPager) ); - if( pPager->errCode ){ - return pPager->errCode; - }else if( (pPg->flags & PGHDR_WRITEABLE)!=0 && pPager->dbSize>=pPg->pgno ){ + if( (pPg->flags & PGHDR_WRITEABLE)!=0 && pPager->dbSize>=pPg->pgno ){ if( pPager->nSavepoint ) return subjournalPageIfRequired(pPg); return SQLITE_OK; + }else if( pPager->errCode ){ + return pPager->errCode; }else if( pPager->sectorSize > (u32)pPager->pageSize ){ assert( pPager->tempFile==0 ); return pagerWriteLargeSector(pPg); @@ -6604,6 +6685,7 @@ int sqlite3PagerRollback(Pager *pPager){ */ pPager->errCode = SQLITE_ABORT; pPager->eState = PAGER_ERROR; + setGetterMethod(pPager); return rc; } }else{ @@ -6808,7 +6890,11 @@ int sqlite3PagerOpenSavepoint(Pager *pPager, int nSavepoint){ ** savepoint. If no errors occur, SQLITE_OK is returned. */ int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ - int rc = pPager->errCode; /* Return code */ + int rc = pPager->errCode; + +#ifdef SQLITE_ENABLE_ZIPVFS + if( op==SAVEPOINT_RELEASE ) rc = SQLITE_OK; +#endif assert( op==SAVEPOINT_RELEASE || op==SAVEPOINT_ROLLBACK ); assert( iSavepoint>=0 || op==SAVEPOINT_ROLLBACK ); @@ -6849,6 +6935,21 @@ int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ rc = pagerPlaybackSavepoint(pPager, pSavepoint); assert(rc!=SQLITE_DONE); } + +#ifdef SQLITE_ENABLE_ZIPVFS + /* If the cache has been modified but the savepoint cannot be rolled + ** back journal_mode=off, put the pager in the error state. This way, + ** if the VFS used by this pager includes ZipVFS, the entire transaction + ** can be rolled back at the ZipVFS level. */ + else if( + pPager->journalMode==PAGER_JOURNALMODE_OFF + && pPager->eState>=PAGER_WRITER_CACHEMOD + ){ + pPager->errCode = SQLITE_ABORT; + pPager->eState = PAGER_ERROR; + setGetterMethod(pPager); + } +#endif } return rc; @@ -6919,6 +7020,7 @@ void sqlite3PagerSetCodec( pPager->xCodecSizeChng = xCodecSizeChng; pPager->xCodecFree = xCodecFree; pPager->pCodec = pCodec; + setGetterMethod(pPager); pagerReportSize(pPager); } void *sqlite3PagerGetCodec(Pager *pPager){ @@ -7328,10 +7430,16 @@ void sqlite3PagerClearCache(Pager *pPager){ ** ** Parameter eMode is one of SQLITE_CHECKPOINT_PASSIVE, FULL or RESTART. */ -int sqlite3PagerCheckpoint(Pager *pPager, int eMode, int *pnLog, int *pnCkpt){ +int sqlite3PagerCheckpoint( + Pager *pPager, /* Checkpoint on this pager */ + sqlite3 *db, /* Db handle used to check for interrupts */ + int eMode, /* Type of checkpoint */ + int *pnLog, /* OUT: Final number of frames in log */ + int *pnCkpt /* OUT: Final number of checkpointed frames */ +){ int rc = SQLITE_OK; if( pPager->pWal ){ - rc = sqlite3WalCheckpoint(pPager->pWal, eMode, + rc = sqlite3WalCheckpoint(pPager->pWal, db, eMode, (eMode==SQLITE_CHECKPOINT_PASSIVE ? 0 : pPager->xBusyHandler), pPager->pBusyHandlerArg, pPager->ckptSyncFlags, pPager->pageSize, (u8 *)pPager->pTmpSpace, @@ -7463,7 +7571,7 @@ int sqlite3PagerOpenWal( ** error (SQLITE_BUSY) is returned and the log connection is not closed. ** If successful, the EXCLUSIVE lock is not released before returning. */ -int sqlite3PagerCloseWal(Pager *pPager){ +int sqlite3PagerCloseWal(Pager *pPager, sqlite3 *db){ int rc = SQLITE_OK; assert( pPager->journalMode==PAGER_JOURNALMODE_WAL ); @@ -7491,7 +7599,7 @@ int sqlite3PagerCloseWal(Pager *pPager){ if( rc==SQLITE_OK && pPager->pWal ){ rc = pagerExclusiveLock(pPager); if( rc==SQLITE_OK ){ - rc = sqlite3WalClose(pPager->pWal, pPager->ckptSyncFlags, + rc = sqlite3WalClose(pPager->pWal, db, pPager->ckptSyncFlags, pPager->pageSize, (u8*)pPager->pTmpSpace); pPager->pWal = 0; pagerFixMaplimit(pPager); @@ -7528,6 +7636,20 @@ int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot){ } return rc; } + +/* +** If this is a WAL database, call sqlite3WalSnapshotRecover(). If this +** is not a WAL database, return an error. +*/ +int sqlite3PagerSnapshotRecover(Pager *pPager){ + int rc; + if( pPager->pWal ){ + rc = sqlite3WalSnapshotRecover(pPager->pWal); + }else{ + rc = SQLITE_ERROR; + } + return rc; +} #endif /* SQLITE_ENABLE_SNAPSHOT */ #endif /* !SQLITE_OMIT_WAL */ diff --git a/src/pager.h b/src/pager.h index 03f1bd5c84..60a844c713 100644 --- a/src/pager.h +++ b/src/pager.h @@ -122,7 +122,7 @@ int sqlite3PagerOpen( int, void(*)(DbPage*) ); -int sqlite3PagerClose(Pager *pPager); +int sqlite3PagerClose(Pager *pPager, sqlite3*); int sqlite3PagerReadFileheader(Pager*, int, unsigned char*); /* Functions used to configure a Pager object. */ @@ -174,15 +174,19 @@ int sqlite3PagerSharedLock(Pager *pPager); #ifndef SQLITE_OMIT_WAL - int sqlite3PagerCheckpoint(Pager *pPager, int, int*, int*); + int sqlite3PagerCheckpoint(Pager *pPager, sqlite3*, int, int*, int*); int sqlite3PagerWalSupported(Pager *pPager); int sqlite3PagerWalCallback(Pager *pPager); int sqlite3PagerOpenWal(Pager *pPager, int *pisOpen); - int sqlite3PagerCloseWal(Pager *pPager); + int sqlite3PagerCloseWal(Pager *pPager, sqlite3*); + int sqlite3PagerUseWal(Pager *pPager); # ifdef SQLITE_ENABLE_SNAPSHOT int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot); int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot); + int sqlite3PagerSnapshotRecover(Pager *pPager); # endif +#else +# define sqlite3PagerUseWal(x) 0 #endif #ifdef SQLITE_ENABLE_ZIPVFS diff --git a/src/parse.y b/src/parse.y index afa37c84a8..fa2aef17a7 100644 --- a/src/parse.y +++ b/src/parse.y @@ -80,15 +80,6 @@ struct LimitVal { Expr *pOffset; /* The OFFSET expression. NULL if there is none */ }; -/* -** An instance of this structure is used to store the LIKE, -** GLOB, NOT LIKE, and NOT GLOB operators. -*/ -struct LikeOp { - Token eOperator; /* "like" or "glob" or "regexp" */ - int bNot; /* True if the NOT keyword is present */ -}; - /* ** An instance of the following structure describes the event of a ** TRIGGER. "a" is the event type, one of TK_UPDATE, TK_INSERT, @@ -100,11 +91,6 @@ struct LikeOp { */ struct TrigEvent { int a; IdList * b; }; -/* -** An instance of this structure holds the ATTACH key and the key type. -*/ -struct AttachKey { int type; Token key; }; - /* ** Generate a syntax error */ @@ -298,7 +284,7 @@ ccons ::= DEFAULT LP expr(X) RP. {sqlite3AddDefaultValue(pParse,&X);} ccons ::= DEFAULT PLUS term(X). {sqlite3AddDefaultValue(pParse,&X);} ccons ::= DEFAULT MINUS(A) term(X). { ExprSpan v; - v.pExpr = sqlite3PExpr(pParse, TK_UMINUS, X.pExpr, 0, 0); + v.pExpr = sqlite3PExpr(pParse, TK_UMINUS, X.pExpr, 0); v.zStart = A.z; v.zEnd = X.zEnd; sqlite3AddDefaultValue(pParse,&v); @@ -572,10 +558,10 @@ selcollist(A) ::= sclp(A) STAR. { Expr *p = sqlite3Expr(pParse->db, TK_ASTERISK, 0); A = sqlite3ExprListAppend(pParse, A, p); } -selcollist(A) ::= sclp(A) nm(X) DOT STAR(Y). { - Expr *pRight = sqlite3PExpr(pParse, TK_ASTERISK, 0, 0, &Y); - Expr *pLeft = sqlite3PExpr(pParse, TK_ID, 0, 0, &X); - Expr *pDot = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight, 0); +selcollist(A) ::= sclp(A) nm(X) DOT STAR. { + Expr *pRight = sqlite3PExpr(pParse, TK_ASTERISK, 0, 0); + Expr *pLeft = sqlite3ExprAlloc(pParse->db, TK_ID, &X, 1); + Expr *pDot = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight); A = sqlite3ExprListAppend(pParse,A, pDot); } @@ -805,10 +791,16 @@ setlist(A) ::= setlist(A) COMMA nm(X) EQ expr(Y). { A = sqlite3ExprListAppend(pParse, A, Y.pExpr); sqlite3ExprListSetName(pParse, A, &X, 1); } +setlist(A) ::= setlist(A) COMMA LP idlist(X) RP EQ expr(Y). { + A = sqlite3ExprListAppendVector(pParse, A, X, Y.pExpr); +} setlist(A) ::= nm(X) EQ expr(Y). { A = sqlite3ExprListAppend(pParse, 0, Y.pExpr); sqlite3ExprListSetName(pParse, A, &X, 1); } +setlist(A) ::= LP idlist(X) RP EQ expr(Y). { + A = sqlite3ExprListAppendVector(pParse, 0, X, Y.pExpr); +} ////////////////////////// The INSERT command ///////////////////////////////// // @@ -861,7 +853,24 @@ idlist(A) ::= nm(Y). ** that created the expression. */ static void spanExpr(ExprSpan *pOut, Parse *pParse, int op, Token t){ - pOut->pExpr = sqlite3PExpr(pParse, op, 0, 0, &t); + Expr *p = sqlite3DbMallocRawNN(pParse->db, sizeof(Expr)+t.n+1); + if( p ){ + memset(p, 0, sizeof(Expr)); + p->op = (u8)op; + p->flags = EP_Leaf; + p->iAgg = -1; + p->u.zToken = (char*)&p[1]; + memcpy(p->u.zToken, t.z, t.n); + p->u.zToken[t.n] = 0; + if( sqlite3Isquote(p->u.zToken[0]) ){ + if( p->u.zToken[0]=='"' ) p->flags |= EP_DblQuoted; + sqlite3Dequote(p->u.zToken); + } +#if SQLITE_MAX_EXPR_DEPTH>0 + p->nHeight = 1; +#endif + } + pOut->pExpr = p; pOut->zStart = t.z; pOut->zEnd = &t.z[t.n]; } @@ -874,25 +883,32 @@ term(A) ::= NULL(X). {spanExpr(&A,pParse,@X,X);/*A-overwrites-X*/} expr(A) ::= id(X). {spanExpr(&A,pParse,TK_ID,X); /*A-overwrites-X*/} expr(A) ::= JOIN_KW(X). {spanExpr(&A,pParse,TK_ID,X); /*A-overwrites-X*/} expr(A) ::= nm(X) DOT nm(Y). { - Expr *temp1 = sqlite3PExpr(pParse, TK_ID, 0, 0, &X); - Expr *temp2 = sqlite3PExpr(pParse, TK_ID, 0, 0, &Y); + Expr *temp1 = sqlite3ExprAlloc(pParse->db, TK_ID, &X, 1); + Expr *temp2 = sqlite3ExprAlloc(pParse->db, TK_ID, &Y, 1); spanSet(&A,&X,&Y); /*A-overwrites-X*/ - A.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp2, 0); + A.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp2); } expr(A) ::= nm(X) DOT nm(Y) DOT nm(Z). { - Expr *temp1 = sqlite3PExpr(pParse, TK_ID, 0, 0, &X); - Expr *temp2 = sqlite3PExpr(pParse, TK_ID, 0, 0, &Y); - Expr *temp3 = sqlite3PExpr(pParse, TK_ID, 0, 0, &Z); - Expr *temp4 = sqlite3PExpr(pParse, TK_DOT, temp2, temp3, 0); + Expr *temp1 = sqlite3ExprAlloc(pParse->db, TK_ID, &X, 1); + Expr *temp2 = sqlite3ExprAlloc(pParse->db, TK_ID, &Y, 1); + Expr *temp3 = sqlite3ExprAlloc(pParse->db, TK_ID, &Z, 1); + Expr *temp4 = sqlite3PExpr(pParse, TK_DOT, temp2, temp3); spanSet(&A,&X,&Z); /*A-overwrites-X*/ - A.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp4, 0); + A.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp4); +} +term(A) ::= FLOAT|BLOB(X). {spanExpr(&A,pParse,@X,X);/*A-overwrites-X*/} +term(A) ::= STRING(X). {spanExpr(&A,pParse,@X,X);/*A-overwrites-X*/} +term(A) ::= INTEGER(X). { + A.pExpr = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &X, 1); + A.zStart = X.z; + A.zEnd = X.z + X.n; + if( A.pExpr ) A.pExpr->flags |= EP_Leaf; } -term(A) ::= INTEGER|FLOAT|BLOB(X). {spanExpr(&A,pParse,@X,X);/*A-overwrites-X*/} -term(A) ::= STRING(X). {spanExpr(&A,pParse,@X,X);/*A-overwrites-X*/} expr(A) ::= VARIABLE(X). { if( !(X.z[0]=='#' && sqlite3Isdigit(X.z[1])) ){ + u32 n = X.n; spanExpr(&A, pParse, TK_VARIABLE, X); - sqlite3ExprAssignVarNumber(pParse, A.pExpr); + sqlite3ExprAssignVarNumber(pParse, A.pExpr, n); }else{ /* When doing a nested parse, one can include terms in an expression ** that look like this: #1 #2 ... These terms refer to registers @@ -904,7 +920,7 @@ expr(A) ::= VARIABLE(X). { parserSyntaxError(pParse, &t); A.pExpr = 0; }else{ - A.pExpr = sqlite3PExpr(pParse, TK_REGISTER, 0, 0, &t); + A.pExpr = sqlite3PExpr(pParse, TK_REGISTER, 0, 0); if( A.pExpr ) sqlite3GetInt32(&t.z[1], &A.pExpr->iTable); } } @@ -916,7 +932,8 @@ expr(A) ::= expr(A) COLLATE ids(C). { %ifndef SQLITE_OMIT_CAST expr(A) ::= CAST(X) LP expr(E) AS typetoken(T) RP(Y). { spanSet(&A,&X,&Y); /*A-overwrites-X*/ - A.pExpr = sqlite3PExpr(pParse, TK_CAST, E.pExpr, 0, &T); + A.pExpr = sqlite3ExprAlloc(pParse->db, TK_CAST, &T, 1); + sqlite3ExprAttachSubtrees(pParse->db, A.pExpr, E.pExpr, 0); } %endif SQLITE_OMIT_CAST expr(A) ::= id(X) LP distinct(D) exprlist(Y) RP(E). { @@ -948,7 +965,7 @@ term(A) ::= CTIME_KW(OP). { ExprSpan *pLeft, /* The left operand, and output */ ExprSpan *pRight /* The right operand */ ){ - pLeft->pExpr = sqlite3PExpr(pParse, op, pLeft->pExpr, pRight->pExpr, 0); + pLeft->pExpr = sqlite3PExpr(pParse, op, pLeft->pExpr, pRight->pExpr); pLeft->zEnd = pRight->zEnd; } @@ -957,11 +974,22 @@ term(A) ::= CTIME_KW(OP). { */ static void exprNot(Parse *pParse, int doNot, ExprSpan *pSpan){ if( doNot ){ - pSpan->pExpr = sqlite3PExpr(pParse, TK_NOT, pSpan->pExpr, 0, 0); + pSpan->pExpr = sqlite3PExpr(pParse, TK_NOT, pSpan->pExpr, 0); } } } +expr(A) ::= LP(L) nexprlist(X) COMMA expr(Y) RP(R). { + ExprList *pList = sqlite3ExprListAppend(pParse, X, Y.pExpr); + A.pExpr = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); + if( A.pExpr ){ + A.pExpr->x.pList = pList; + spanSet(&A, &L, &R); + }else{ + sqlite3ExprListDelete(pParse->db, pList); + } +} + expr(A) ::= expr(A) AND(OP) expr(Y). {spanBinaryExpr(pParse,@OP,&A,&Y);} expr(A) ::= expr(A) OR(OP) expr(Y). {spanBinaryExpr(pParse,@OP,&A,&Y);} expr(A) ::= expr(A) LT|GT|GE|LE(OP) expr(Y). @@ -974,25 +1002,29 @@ expr(A) ::= expr(A) PLUS|MINUS(OP) expr(Y). expr(A) ::= expr(A) STAR|SLASH|REM(OP) expr(Y). {spanBinaryExpr(pParse,@OP,&A,&Y);} expr(A) ::= expr(A) CONCAT(OP) expr(Y). {spanBinaryExpr(pParse,@OP,&A,&Y);} -%type likeop {struct LikeOp} -likeop(A) ::= LIKE_KW|MATCH(X). {A.eOperator = X; A.bNot = 0;/*A-overwrites-X*/} -likeop(A) ::= NOT LIKE_KW|MATCH(X). {A.eOperator = X; A.bNot = 1;} +%type likeop {Token} +likeop(A) ::= LIKE_KW|MATCH(X). {A=X;/*A-overwrites-X*/} +likeop(A) ::= NOT LIKE_KW|MATCH(X). {A=X; A.n|=0x80000000; /*A-overwrite-X*/} expr(A) ::= expr(A) likeop(OP) expr(Y). [LIKE_KW] { ExprList *pList; + int bNot = OP.n & 0x80000000; + OP.n &= 0x7fffffff; pList = sqlite3ExprListAppend(pParse,0, Y.pExpr); pList = sqlite3ExprListAppend(pParse,pList, A.pExpr); - A.pExpr = sqlite3ExprFunction(pParse, pList, &OP.eOperator); - exprNot(pParse, OP.bNot, &A); + A.pExpr = sqlite3ExprFunction(pParse, pList, &OP); + exprNot(pParse, bNot, &A); A.zEnd = Y.zEnd; if( A.pExpr ) A.pExpr->flags |= EP_InfixFunc; } expr(A) ::= expr(A) likeop(OP) expr(Y) ESCAPE expr(E). [LIKE_KW] { ExprList *pList; + int bNot = OP.n & 0x80000000; + OP.n &= 0x7fffffff; pList = sqlite3ExprListAppend(pParse,0, Y.pExpr); pList = sqlite3ExprListAppend(pParse,pList, A.pExpr); pList = sqlite3ExprListAppend(pParse,pList, E.pExpr); - A.pExpr = sqlite3ExprFunction(pParse, pList, &OP.eOperator); - exprNot(pParse, OP.bNot, &A); + A.pExpr = sqlite3ExprFunction(pParse, pList, &OP); + exprNot(pParse, bNot, &A); A.zEnd = E.zEnd; if( A.pExpr ) A.pExpr->flags |= EP_InfixFunc; } @@ -1006,7 +1038,7 @@ expr(A) ::= expr(A) likeop(OP) expr(Y) ESCAPE expr(E). [LIKE_KW] { ExprSpan *pOperand, /* The operand, and output */ Token *pPostOp /* The operand token for setting the span */ ){ - pOperand->pExpr = sqlite3PExpr(pParse, op, pOperand->pExpr, 0, 0); + pOperand->pExpr = sqlite3PExpr(pParse, op, pOperand->pExpr, 0); pOperand->zEnd = &pPostOp->z[pPostOp->n]; } } @@ -1053,7 +1085,7 @@ expr(A) ::= expr(A) IS NOT expr(Y). { Token *pPreOp /* The operand token for setting the span */ ){ pOut->zStart = pPreOp->z; - pOut->pExpr = sqlite3PExpr(pParse, op, pOperand->pExpr, 0, 0); + pOut->pExpr = sqlite3PExpr(pParse, op, pOperand->pExpr, 0); pOut->zEnd = pOperand->zEnd; } } @@ -1075,7 +1107,7 @@ between_op(A) ::= NOT BETWEEN. {A = 1;} expr(A) ::= expr(A) between_op(N) expr(X) AND expr(Y). [BETWEEN] { ExprList *pList = sqlite3ExprListAppend(pParse,0, X.pExpr); pList = sqlite3ExprListAppend(pParse,pList, Y.pExpr); - A.pExpr = sqlite3PExpr(pParse, TK_BETWEEN, A.pExpr, 0, 0); + A.pExpr = sqlite3PExpr(pParse, TK_BETWEEN, A.pExpr, 0); if( A.pExpr ){ A.pExpr->x.pList = pList; }else{ @@ -1099,7 +1131,7 @@ expr(A) ::= expr(A) between_op(N) expr(X) AND expr(Y). [BETWEEN] { ** regardless of the value of expr1. */ sqlite3ExprDelete(pParse->db, A.pExpr); - A.pExpr = sqlite3PExpr(pParse, TK_INTEGER, 0, 0, &sqlite3IntTokens[N]); + A.pExpr = sqlite3ExprAlloc(pParse->db, TK_INTEGER,&sqlite3IntTokens[N],1); }else if( Y->nExpr==1 ){ /* Expressions of the form: ** @@ -1126,9 +1158,9 @@ expr(A) ::= expr(A) between_op(N) expr(X) AND expr(Y). [BETWEEN] { pRHS->flags &= ~EP_Collate; pRHS->flags |= EP_Generic; } - A.pExpr = sqlite3PExpr(pParse, N ? TK_NE : TK_EQ, A.pExpr, pRHS, 0); + A.pExpr = sqlite3PExpr(pParse, N ? TK_NE : TK_EQ, A.pExpr, pRHS); }else{ - A.pExpr = sqlite3PExpr(pParse, TK_IN, A.pExpr, 0, 0); + A.pExpr = sqlite3PExpr(pParse, TK_IN, A.pExpr, 0); if( A.pExpr ){ A.pExpr->x.pList = Y; sqlite3ExprSetHeightAndFlags(pParse, A.pExpr); @@ -1141,11 +1173,11 @@ expr(A) ::= expr(A) between_op(N) expr(X) AND expr(Y). [BETWEEN] { } expr(A) ::= LP(B) select(X) RP(E). { spanSet(&A,&B,&E); /*A-overwrites-B*/ - A.pExpr = sqlite3PExpr(pParse, TK_SELECT, 0, 0, 0); + A.pExpr = sqlite3PExpr(pParse, TK_SELECT, 0, 0); sqlite3PExprAddSelect(pParse, A.pExpr, X); } expr(A) ::= expr(A) in_op(N) LP select(Y) RP(E). [IN] { - A.pExpr = sqlite3PExpr(pParse, TK_IN, A.pExpr, 0, 0); + A.pExpr = sqlite3PExpr(pParse, TK_IN, A.pExpr, 0); sqlite3PExprAddSelect(pParse, A.pExpr, Y); exprNot(pParse, N, &A); A.zEnd = &E.z[E.n]; @@ -1154,7 +1186,7 @@ expr(A) ::= expr(A) between_op(N) expr(X) AND expr(Y). [BETWEEN] { SrcList *pSrc = sqlite3SrcListAppend(pParse->db, 0,&Y,&Z); Select *pSelect = sqlite3SelectNew(pParse, 0,pSrc,0,0,0,0,0,0,0); if( E ) sqlite3SrcListFuncArgs(pParse, pSelect ? pSrc : 0, E); - A.pExpr = sqlite3PExpr(pParse, TK_IN, A.pExpr, 0, 0); + A.pExpr = sqlite3PExpr(pParse, TK_IN, A.pExpr, 0); sqlite3PExprAddSelect(pParse, A.pExpr, pSelect); exprNot(pParse, N, &A); A.zEnd = Z.z ? &Z.z[Z.n] : &Y.z[Y.n]; @@ -1162,7 +1194,7 @@ expr(A) ::= expr(A) between_op(N) expr(X) AND expr(Y). [BETWEEN] { expr(A) ::= EXISTS(B) LP select(Y) RP(E). { Expr *p; spanSet(&A,&B,&E); /*A-overwrites-B*/ - p = A.pExpr = sqlite3PExpr(pParse, TK_EXISTS, 0, 0, 0); + p = A.pExpr = sqlite3PExpr(pParse, TK_EXISTS, 0, 0); sqlite3PExprAddSelect(pParse, p, Y); } %endif SQLITE_OMIT_SUBQUERY @@ -1170,7 +1202,7 @@ expr(A) ::= expr(A) between_op(N) expr(X) AND expr(Y). [BETWEEN] { /* CASE expressions */ expr(A) ::= CASE(C) case_operand(X) case_exprlist(Y) case_else(Z) END(E). { spanSet(&A,&C,&E); /*A-overwrites-C*/ - A.pExpr = sqlite3PExpr(pParse, TK_CASE, X, 0, 0); + A.pExpr = sqlite3PExpr(pParse, TK_CASE, X, 0); if( A.pExpr ){ A.pExpr->x.pList = Z ? sqlite3ExprListAppend(pParse,Y,Z) : Y; sqlite3ExprSetHeightAndFlags(pParse, A.pExpr); @@ -1301,8 +1333,8 @@ cmd ::= DROP INDEX ifexists(E) fullname(X). {sqlite3DropIndex(pParse, X, E);} // %ifndef SQLITE_OMIT_VACUUM %ifndef SQLITE_OMIT_ATTACH -cmd ::= VACUUM. {sqlite3Vacuum(pParse);} -cmd ::= VACUUM nm. {sqlite3Vacuum(pParse);} +cmd ::= VACUUM. {sqlite3Vacuum(pParse,0);} +cmd ::= VACUUM nm(X). {sqlite3Vacuum(pParse,&X);} %endif SQLITE_OMIT_ATTACH %endif SQLITE_OMIT_VACUUM @@ -1430,14 +1462,14 @@ trigger_cmd(A) ::= select(X). // The special RAISE expression that may occur in trigger programs expr(A) ::= RAISE(X) LP IGNORE RP(Y). { spanSet(&A,&X,&Y); /*A-overwrites-X*/ - A.pExpr = sqlite3PExpr(pParse, TK_RAISE, 0, 0, 0); + A.pExpr = sqlite3PExpr(pParse, TK_RAISE, 0, 0); if( A.pExpr ){ A.pExpr->affinity = OE_Ignore; } } expr(A) ::= RAISE(X) LP raisetype(T) COMMA nm(Z) RP(Y). { spanSet(&A,&X,&Y); /*A-overwrites-X*/ - A.pExpr = sqlite3PExpr(pParse, TK_RAISE, 0, 0, &Z); + A.pExpr = sqlite3ExprAlloc(pParse->db, TK_RAISE, &Z, 1); if( A.pExpr ) { A.pExpr->affinity = (char)T; } diff --git a/src/pcache.c b/src/pcache.c index ccbcc96cd0..0fc44c5499 100644 --- a/src/pcache.c +++ b/src/pcache.c @@ -108,7 +108,7 @@ struct PCache { int sqlite3PcachePageSanity(PgHdr *pPg){ PCache *pCache; assert( pPg!=0 ); - assert( pPg->pgno>0 ); /* Page number is 1 or more */ + assert( pPg->pgno>0 || pPg->pPager==0 ); /* Page number is 1 or more */ pCache = pPg->pCache; assert( pCache!=0 ); /* Every page has an associated PCache */ if( pPg->flags & PGHDR_CLEAN ){ @@ -284,6 +284,12 @@ int sqlite3PcacheSize(void){ return sizeof(PCache); } ** has already been allocated and is passed in as the p pointer. ** The caller discovers how much space needs to be allocated by ** calling sqlite3PcacheSize(). +** +** szExtra is some extra space allocated for each page. The first +** 8 bytes of the extra space will be zeroed as the page is allocated, +** but remaining content will be uninitialized. Though it is opaque +** to this module, the extra space really ends up being the MemPage +** structure in the pager. */ int sqlite3PcacheOpen( int szPage, /* Size of every page */ @@ -296,6 +302,7 @@ int sqlite3PcacheOpen( memset(p, 0, sizeof(PCache)); p->szPage = 1; p->szExtra = szExtra; + assert( szExtra>=8 ); /* First 8 bytes will be zeroed */ p->bPurgeable = bPurgeable; p->eCreate = 2; p->xStress = xStress; @@ -365,7 +372,6 @@ sqlite3_pcache_page *sqlite3PcacheFetch( assert( pCache!=0 ); assert( pCache->pCache!=0 ); assert( createFlag==3 || createFlag==0 ); - assert( pgno>0 ); assert( pCache->eCreate==((pCache->bPurgeable && pCache->pDirty) ? 1 : 2) ); /* eCreate defines what to do if the page does not exist. @@ -461,11 +467,11 @@ static SQLITE_NOINLINE PgHdr *pcacheFetchFinishWithInit( assert( pPage!=0 ); pPgHdr = (PgHdr*)pPage->pExtra; assert( pPgHdr->pPage==0 ); - memset(pPgHdr, 0, sizeof(PgHdr)); + memset(&pPgHdr->pDirty, 0, sizeof(PgHdr) - offsetof(PgHdr,pDirty)); pPgHdr->pPage = pPage; pPgHdr->pData = pPage->pBuf; pPgHdr->pExtra = (void *)&pPgHdr[1]; - memset(pPgHdr->pExtra, 0, pCache->szExtra); + memset(pPgHdr->pExtra, 0, 8); pPgHdr->pCache = pCache; pPgHdr->pgno = pgno; pPgHdr->flags = PGHDR_CLEAN; diff --git a/src/pcache1.c b/src/pcache1.c index 1b1971a399..110d7ec656 100644 --- a/src/pcache1.c +++ b/src/pcache1.c @@ -279,7 +279,7 @@ static int pcache1InitBulk(PCache1 *pCache){ szBulk = -1024 * (i64)pcache1.nInitPage; } if( szBulk > pCache->szAlloc*(i64)pCache->nMax ){ - szBulk = pCache->szAlloc*pCache->nMax; + szBulk = pCache->szAlloc*(i64)pCache->nMax; } zBulk = pCache->pBulk = sqlite3Malloc( szBulk ); sqlite3EndBenignMalloc(); diff --git a/src/pragma.c b/src/pragma.c index 1ee08fbec0..5251d0cd26 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -163,29 +163,29 @@ static int changeTempStorage(Parse *pParse, const char *zStorageType){ #endif /* SQLITE_PAGER_PRAGMAS */ /* -** Set the names of the first N columns to the values in azCol[] +** Set result column names for a pragma. */ -static void setAllColumnNames( - Vdbe *v, /* The query under construction */ - int N, /* Number of columns */ - const char **azCol /* Names of columns */ +static void setPragmaResultColumnNames( + Vdbe *v, /* The query under construction */ + const PragmaName *pPragma /* The pragma */ ){ - int i; - sqlite3VdbeSetNumCols(v, N); - for(i=0; inPragCName; + sqlite3VdbeSetNumCols(v, n==0 ? 1 : n); + if( n==0 ){ + sqlite3VdbeSetColName(v, 0, COLNAME_NAME, pPragma->zName, SQLITE_STATIC); + }else{ + int i, j; + for(i=0, j=pPragma->iPragCName; iupr ? 0 : &aPragmaName[mid]; +} + /* ** Process a pragma statement. ** @@ -305,12 +323,11 @@ void sqlite3Pragma( Token *pId; /* Pointer to token */ char *aFcntl[4]; /* Argument to SQLITE_FCNTL_PRAGMA */ int iDb; /* Database index for */ - int lwr, upr, mid = 0; /* Binary search bounds */ int rc; /* return value form SQLITE_FCNTL_PRAGMA */ sqlite3 *db = pParse->db; /* The database connection */ Db *pDb; /* The specific database being pragmaed */ Vdbe *v = sqlite3GetVdbe(pParse); /* Prepared statement */ - const struct sPragmaNames *pPragma; + const PragmaName *pPragma; /* The pragma */ if( v==0 ) return; sqlite3VdbeRunOnlyOnce(v); @@ -338,7 +355,7 @@ void sqlite3Pragma( } assert( pId2 ); - zDb = pId2->n>0 ? pDb->zName : 0; + zDb = pId2->n>0 ? pDb->zDbSName : 0; if( sqlite3AuthCheck(pParse, SQLITE_PRAGMA, zLeft, zRight, zDb) ){ goto pragma_out; } @@ -365,7 +382,9 @@ void sqlite3Pragma( db->busyHandler.nBusy = 0; rc = sqlite3_file_control(db, zDb, SQLITE_FCNTL_PRAGMA, (void*)aFcntl); if( rc==SQLITE_OK ){ - returnSingleText(v, "result", aFcntl[0]); + sqlite3VdbeSetNumCols(v, 1); + sqlite3VdbeSetColName(v, 0, COLNAME_NAME, aFcntl[0], SQLITE_TRANSIENT); + returnSingleText(v, aFcntl[0]); sqlite3_free(aFcntl[0]); goto pragma_out; } @@ -380,26 +399,21 @@ void sqlite3Pragma( } /* Locate the pragma in the lookup table */ - lwr = 0; - upr = ArraySize(aPragmaNames)-1; - while( lwr<=upr ){ - mid = (lwr+upr)/2; - rc = sqlite3_stricmp(zLeft, aPragmaNames[mid].zName); - if( rc==0 ) break; - if( rc<0 ){ - upr = mid - 1; - }else{ - lwr = mid + 1; - } - } - if( lwr>upr ) goto pragma_out; - pPragma = &aPragmaNames[mid]; + pPragma = pragmaLocate(zLeft); + if( pPragma==0 ) goto pragma_out; /* Make sure the database schema is loaded if the pragma requires that */ - if( (pPragma->mPragFlag & PragFlag_NeedSchema)!=0 ){ + if( (pPragma->mPragFlg & PragFlg_NeedSchema)!=0 ){ if( sqlite3ReadSchema(pParse) ) goto pragma_out; } + /* Register the result column names for pragmas that return results */ + if( (pPragma->mPragFlg & PragFlg_NoColumns)==0 + && ((pPragma->mPragFlg & PragFlg_NoColumns1)==0 || zRight==0) + ){ + setPragmaResultColumnNames(v, pPragma); + } + /* Jump to the appropriate pragma handler */ switch( pPragma->ePragTyp ){ @@ -436,7 +450,6 @@ void sqlite3Pragma( VdbeOp *aOp; sqlite3VdbeUsesBtree(v, iDb); if( !zRight ){ - setOneColumnName(v, "cache_size"); pParse->nMem += 2; sqlite3VdbeVerifyNoMallocRequired(v, ArraySize(getCacheSize)); aOp = sqlite3VdbeAddOpList(v, ArraySize(getCacheSize), getCacheSize, iLn); @@ -471,7 +484,7 @@ void sqlite3Pragma( assert( pBt!=0 ); if( !zRight ){ int size = ALWAYS(pBt) ? sqlite3BtreeGetPageSize(pBt) : 0; - returnSingleInt(v, "page_size", size); + returnSingleInt(v, size); }else{ /* Malloc may fail when setting the page-size, as there is an internal ** buffer that the pager module resizes using sqlite3_realloc(). @@ -506,7 +519,7 @@ void sqlite3Pragma( } } b = sqlite3BtreeSecureDelete(pBt, b); - returnSingleInt(v, "secure_delete", b); + returnSingleInt(v, b); break; } @@ -538,8 +551,6 @@ void sqlite3Pragma( sqlite3AbsInt32(sqlite3Atoi(zRight))); } sqlite3VdbeAddOp2(v, OP_ResultRow, iReg, 1); - sqlite3VdbeSetNumCols(v, 1); - sqlite3VdbeSetColName(v, 0, COLNAME_NAME, zLeft, SQLITE_TRANSIENT); break; } @@ -585,7 +596,7 @@ void sqlite3Pragma( if( eMode==PAGER_LOCKINGMODE_EXCLUSIVE ){ zRet = "exclusive"; } - returnSingleText(v, "locking_mode", zRet); + returnSingleText(v, zRet); break; } @@ -598,7 +609,6 @@ void sqlite3Pragma( int eMode; /* One of the PAGER_JOURNALMODE_XXX symbols */ int ii; /* Loop counter */ - setOneColumnName(v, "journal_mode"); if( zRight==0 ){ /* If there is no "=MODE" part of the pragma, do a query for the ** current mode */ @@ -644,7 +654,7 @@ void sqlite3Pragma( if( iLimit<-1 ) iLimit = -1; } iLimit = sqlite3PagerJournalSizeLimit(pPager, iLimit); - returnSingleInt(v, "journal_size_limit", iLimit); + returnSingleInt(v, iLimit); break; } @@ -662,7 +672,7 @@ void sqlite3Pragma( Btree *pBt = pDb->pBt; assert( pBt!=0 ); if( !zRight ){ - returnSingleInt(v, "auto_vacuum", sqlite3BtreeGetAutoVacuum(pBt)); + returnSingleInt(v, sqlite3BtreeGetAutoVacuum(pBt)); }else{ int eAuto = getAutoVacuum(zRight); assert( eAuto>=0 && eAuto<=2 ); @@ -741,7 +751,7 @@ void sqlite3Pragma( case PragTyp_CACHE_SIZE: { assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( !zRight ){ - returnSingleInt(v, "cache_size", pDb->pSchema->cache_size); + returnSingleInt(v, pDb->pSchema->cache_size); }else{ int size = sqlite3Atoi(zRight); pDb->pSchema->cache_size = size; @@ -775,7 +785,7 @@ void sqlite3Pragma( case PragTyp_CACHE_SPILL: { assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( !zRight ){ - returnSingleInt(v, "cache_spill", + returnSingleInt(v, (db->flags & SQLITE_CacheSpill)==0 ? 0 : sqlite3BtreeSetSpillSize(pDb->pBt,0)); }else{ @@ -829,7 +839,7 @@ void sqlite3Pragma( rc = SQLITE_OK; #endif if( rc==SQLITE_OK ){ - returnSingleInt(v, "mmap_size", sz); + returnSingleInt(v, sz); }else if( rc!=SQLITE_NOTFOUND ){ pParse->nErr++; pParse->rc = rc; @@ -850,7 +860,7 @@ void sqlite3Pragma( */ case PragTyp_TEMP_STORE: { if( !zRight ){ - returnSingleInt(v, "temp_store", db->temp_store); + returnSingleInt(v, db->temp_store); }else{ changeTempStorage(pParse, zRight); } @@ -869,7 +879,7 @@ void sqlite3Pragma( */ case PragTyp_TEMP_STORE_DIRECTORY: { if( !zRight ){ - returnSingleText(v, "temp_store_directory", sqlite3_temp_directory); + returnSingleText(v, sqlite3_temp_directory); }else{ #ifndef SQLITE_OMIT_WSD if( zRight[0] ){ @@ -913,7 +923,7 @@ void sqlite3Pragma( */ case PragTyp_DATA_STORE_DIRECTORY: { if( !zRight ){ - returnSingleText(v, "data_store_directory", sqlite3_data_directory); + returnSingleText(v, sqlite3_data_directory); }else{ #ifndef SQLITE_OMIT_WSD if( zRight[0] ){ @@ -952,7 +962,7 @@ void sqlite3Pragma( sqlite3_file *pFile = sqlite3PagerFile(pPager); sqlite3OsFileControlHint(pFile, SQLITE_GET_LOCKPROXYFILE, &proxy_file_path); - returnSingleText(v, "lock_proxy_file", proxy_file_path); + returnSingleText(v, proxy_file_path); }else{ Pager *pPager = sqlite3BtreePager(pDb->pBt); sqlite3_file *pFile = sqlite3PagerFile(pPager); @@ -984,7 +994,7 @@ void sqlite3Pragma( */ case PragTyp_SYNCHRONOUS: { if( !zRight ){ - returnSingleInt(v, "synchronous", pDb->safety_level-1); + returnSingleInt(v, pDb->safety_level-1); }else{ if( !db->autoCommit ){ sqlite3ErrorMsg(pParse, @@ -1004,7 +1014,8 @@ void sqlite3Pragma( #ifndef SQLITE_OMIT_FLAG_PRAGMAS case PragTyp_FLAG: { if( zRight==0 ){ - returnSingleInt(v, pPragma->zName, (db->flags & pPragma->iArg)!=0 ); + setPragmaResultColumnNames(v, pPragma); + returnSingleInt(v, (db->flags & pPragma->iArg)!=0 ); }else{ int mask = pPragma->iArg; /* Mask of bits to set or clear. */ if( db->autoCommit==0 ){ @@ -1054,16 +1065,12 @@ void sqlite3Pragma( Table *pTab; pTab = sqlite3LocateTable(pParse, LOCATE_NOERR, zRight, zDb); if( pTab ){ - static const char *azCol[] = { - "cid", "name", "type", "notnull", "dflt_value", "pk" - }; int i, k; int nHidden = 0; Column *pCol; Index *pPk = sqlite3PrimaryKeyIndex(pTab); pParse->nMem = 6; sqlite3CodeVerifySchema(pParse, iDb); - setAllColumnNames(v, 6, azCol); assert( 6==ArraySize(azCol) ); sqlite3ViewGetColumnNames(pParse, pTab); for(i=0, pCol=pTab->aCol; inCol; i++, pCol++){ if( IsHiddenColumn(pCol) ){ @@ -1092,13 +1099,10 @@ void sqlite3Pragma( break; case PragTyp_STATS: { - static const char *azCol[] = { "table", "index", "width", "height" }; Index *pIdx; HashElem *i; - v = sqlite3GetVdbe(pParse); pParse->nMem = 4; sqlite3CodeVerifySchema(pParse, iDb); - setAllColumnNames(v, 4, azCol); assert( 4==ArraySize(azCol) ); for(i=sqliteHashFirst(&pDb->pSchema->tblHash); i; i=sqliteHashNext(i)){ Table *pTab = sqliteHashData(i); sqlite3VdbeMultiLoad(v, 1, "ssii", @@ -1123,9 +1127,6 @@ void sqlite3Pragma( Table *pTab; pIdx = sqlite3FindIndex(db, zRight, zDb); if( pIdx ){ - static const char *azCol[] = { - "seqno", "cid", "name", "desc", "coll", "key" - }; int i; int mx; if( pPragma->iArg ){ @@ -1139,8 +1140,7 @@ void sqlite3Pragma( } pTab = pIdx->pTable; sqlite3CodeVerifySchema(pParse, iDb); - assert( pParse->nMem<=ArraySize(azCol) ); - setAllColumnNames(v, pParse->nMem, azCol); + assert( pParse->nMem<=pPragma->nPragCName ); for(i=0; iaiColumn[i]; sqlite3VdbeMultiLoad(v, 1, "iis", i, cnum, @@ -1163,13 +1163,8 @@ void sqlite3Pragma( int i; pTab = sqlite3FindTable(db, zRight, zDb); if( pTab ){ - static const char *azCol[] = { - "seq", "name", "unique", "origin", "partial" - }; - v = sqlite3GetVdbe(pParse); pParse->nMem = 5; sqlite3CodeVerifySchema(pParse, iDb); - setAllColumnNames(v, 5, azCol); assert( 5==ArraySize(azCol) ); for(pIdx=pTab->pIndex, i=0; pIdx; pIdx=pIdx->pNext, i++){ const char *azOrigin[] = { "c", "u", "pk" }; sqlite3VdbeMultiLoad(v, 1, "isisi", @@ -1185,16 +1180,14 @@ void sqlite3Pragma( break; case PragTyp_DATABASE_LIST: { - static const char *azCol[] = { "seq", "name", "file" }; int i; pParse->nMem = 3; - setAllColumnNames(v, 3, azCol); assert( 3==ArraySize(azCol) ); for(i=0; inDb; i++){ if( db->aDb[i].pBt==0 ) continue; - assert( db->aDb[i].zName!=0 ); + assert( db->aDb[i].zDbSName!=0 ); sqlite3VdbeMultiLoad(v, 1, "iss", i, - db->aDb[i].zName, + db->aDb[i].zDbSName, sqlite3BtreeGetFilename(db->aDb[i].pBt)); sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 3); } @@ -1202,11 +1195,9 @@ void sqlite3Pragma( break; case PragTyp_COLLATION_LIST: { - static const char *azCol[] = { "seq", "name" }; int i = 0; HashElem *p; pParse->nMem = 2; - setAllColumnNames(v, 2, azCol); assert( 2==ArraySize(azCol) ); for(p=sqliteHashFirst(&db->aCollSeq); p; p=sqliteHashNext(p)){ CollSeq *pColl = (CollSeq *)sqliteHashData(p); sqlite3VdbeMultiLoad(v, 1, "is", i++, pColl->zName); @@ -1222,17 +1213,11 @@ void sqlite3Pragma( Table *pTab; pTab = sqlite3FindTable(db, zRight, zDb); if( pTab ){ - v = sqlite3GetVdbe(pParse); pFK = pTab->pFKey; if( pFK ){ - static const char *azCol[] = { - "id", "seq", "table", "from", "to", "on_update", "on_delete", - "match" - }; int i = 0; pParse->nMem = 8; sqlite3CodeVerifySchema(pParse, iDb); - setAllColumnNames(v, 8, azCol); assert( 8==ArraySize(azCol) ); while(pFK){ int j; for(j=0; jnCol; j++){ @@ -1273,14 +1258,11 @@ void sqlite3Pragma( int addrTop; /* Top of a loop checking foreign keys */ int addrOk; /* Jump here if the key is OK */ int *aiCols; /* child to parent column mapping */ - static const char *azCol[] = { "table", "rowid", "parent", "fkid" }; regResult = pParse->nMem+1; pParse->nMem += 4; regKey = ++pParse->nMem; regRow = ++pParse->nMem; - v = sqlite3GetVdbe(pParse); - setAllColumnNames(v, 4, azCol); assert( 4==ArraySize(azCol) ); sqlite3CodeVerifySchema(pParse, iDb); k = sqliteHashFirst(&db->aDb[iDb].pSchema->tblHash); while( k ){ @@ -1419,7 +1401,6 @@ void sqlite3Pragma( /* Initialize the VDBE program */ pParse->nMem = 6; - setOneColumnName(v, "integrity_check"); /* Set the maximum error count */ mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX; @@ -1483,7 +1464,7 @@ void sqlite3Pragma( sqlite3VdbeChangeP5(v, (u8)i); addr = sqlite3VdbeAddOp1(v, OP_IsNull, 2); VdbeCoverage(v); sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, - sqlite3MPrintf(db, "*** in database %s ***\n", db->aDb[i].zName), + sqlite3MPrintf(db, "*** in database %s ***\n", db->aDb[i].zDbSName), P4_DYNAMIC); sqlite3VdbeAddOp3(v, OP_Move, 2, 4, 1); sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 2); @@ -1671,7 +1652,7 @@ void sqlite3Pragma( assert( encnames[SQLITE_UTF8].enc==SQLITE_UTF8 ); assert( encnames[SQLITE_UTF16LE].enc==SQLITE_UTF16LE ); assert( encnames[SQLITE_UTF16BE].enc==SQLITE_UTF16BE ); - returnSingleText(v, "encoding", encnames[ENC(pParse->db)].zName); + returnSingleText(v, encnames[ENC(pParse->db)].zName); }else{ /* "PRAGMA encoding = XXX" */ /* Only change the value of sqlite.enc if the database handle is not ** initialized. If the main database exists, the new sqlite.enc value @@ -1734,7 +1715,7 @@ void sqlite3Pragma( case PragTyp_HEADER_VALUE: { int iCookie = pPragma->iArg; /* Which cookie to read or write */ sqlite3VdbeUsesBtree(v, iDb); - if( zRight && (pPragma->mPragFlag & PragFlag_ReadOnly)==0 ){ + if( zRight && (pPragma->mPragFlg & PragFlg_ReadOnly)==0 ){ /* Write the specified cookie value */ static const VdbeOpList setCookie[] = { { OP_Transaction, 0, 1, 0}, /* 0 */ @@ -1762,8 +1743,6 @@ void sqlite3Pragma( aOp[0].p1 = iDb; aOp[1].p1 = iDb; aOp[1].p3 = iCookie; - sqlite3VdbeSetNumCols(v, 1); - sqlite3VdbeSetColName(v, 0, COLNAME_NAME, zLeft, SQLITE_TRANSIENT); sqlite3VdbeReusable(v); } } @@ -1781,7 +1760,6 @@ void sqlite3Pragma( int i = 0; const char *zOpt; pParse->nMem = 1; - setOneColumnName(v, "compile_option"); while( (zOpt = sqlite3_compileoption_get(i++))!=0 ){ sqlite3VdbeLoadString(v, 1, zOpt); sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1); @@ -1798,7 +1776,6 @@ void sqlite3Pragma( ** Checkpoint the database. */ case PragTyp_WAL_CHECKPOINT: { - static const char *azCol[] = { "busy", "log", "checkpointed" }; int iBt = (pId2->z?iDb:SQLITE_MAX_ATTACHED); int eMode = SQLITE_CHECKPOINT_PASSIVE; if( zRight ){ @@ -1810,7 +1787,6 @@ void sqlite3Pragma( eMode = SQLITE_CHECKPOINT_TRUNCATE; } } - setAllColumnNames(v, 3, azCol); assert( 3==ArraySize(azCol) ); pParse->nMem = 3; sqlite3VdbeAddOp3(v, OP_Checkpoint, iBt, eMode, 1); sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 3); @@ -1829,7 +1805,7 @@ void sqlite3Pragma( if( zRight ){ sqlite3_wal_autocheckpoint(db, sqlite3Atoi(zRight)); } - returnSingleInt(v, "wal_autocheckpoint", + returnSingleInt(v, db->xWalCallback==sqlite3WalDefaultHook ? SQLITE_PTR_TO_INT(db->pWalArg) : 0); } @@ -1862,7 +1838,7 @@ void sqlite3Pragma( if( zRight ){ sqlite3_busy_timeout(db, sqlite3Atoi(zRight)); } - returnSingleInt(v, "timeout", db->busyTimeout); + returnSingleInt(v, db->busyTimeout); break; } @@ -1882,7 +1858,7 @@ void sqlite3Pragma( if( zRight && sqlite3DecOrHexToI64(zRight, &N)==SQLITE_OK ){ sqlite3_soft_heap_limit64(N); } - returnSingleInt(v, "soft_heap_limit", sqlite3_soft_heap_limit64(-1)); + returnSingleInt(v, sqlite3_soft_heap_limit64(-1)); break; } @@ -1901,8 +1877,7 @@ void sqlite3Pragma( ){ sqlite3_limit(db, SQLITE_LIMIT_WORKER_THREADS, (int)(N&0x7fffffff)); } - returnSingleInt(v, "threads", - sqlite3_limit(db, SQLITE_LIMIT_WORKER_THREADS, -1)); + returnSingleInt(v, sqlite3_limit(db, SQLITE_LIMIT_WORKER_THREADS, -1)); break; } @@ -1914,23 +1889,21 @@ void sqlite3Pragma( static const char *const azLockName[] = { "unlocked", "shared", "reserved", "pending", "exclusive" }; - static const char *azCol[] = { "database", "status" }; int i; - setAllColumnNames(v, 2, azCol); assert( 2==ArraySize(azCol) ); pParse->nMem = 2; for(i=0; inDb; i++){ Btree *pBt; const char *zState = "unknown"; int j; - if( db->aDb[i].zName==0 ) continue; + if( db->aDb[i].zDbSName==0 ) continue; pBt = db->aDb[i].pBt; if( pBt==0 || sqlite3BtreePager(pBt)==0 ){ zState = "closed"; - }else if( sqlite3_file_control(db, i ? db->aDb[i].zName : 0, + }else if( sqlite3_file_control(db, i ? db->aDb[i].zDbSName : 0, SQLITE_FCNTL_LOCKSTATE, &j)==SQLITE_OK ){ zState = azLockName[j]; } - sqlite3VdbeMultiLoad(v, 1, "ss", db->aDb[i].zName, zState); + sqlite3VdbeMultiLoad(v, 1, "ss", db->aDb[i].zDbSName, zState); sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 2); } break; @@ -1982,9 +1955,324 @@ void sqlite3Pragma( } /* End of the PRAGMA switch */ + /* The following block is a no-op unless SQLITE_DEBUG is defined. Its only + ** purpose is to execute assert() statements to verify that if the + ** PragFlg_NoColumns1 flag is set and the caller specified an argument + ** to the PRAGMA, the implementation has not added any OP_ResultRow + ** instructions to the VM. */ + if( (pPragma->mPragFlg & PragFlg_NoColumns1) && zRight ){ + sqlite3VdbeVerifyNoResultRow(v); + } + pragma_out: sqlite3DbFree(db, zLeft); sqlite3DbFree(db, zRight); } +#ifndef SQLITE_OMIT_VIRTUALTABLE +/***************************************************************************** +** Implementation of an eponymous virtual table that runs a pragma. +** +*/ +typedef struct PragmaVtab PragmaVtab; +typedef struct PragmaVtabCursor PragmaVtabCursor; +struct PragmaVtab { + sqlite3_vtab base; /* Base class. Must be first */ + sqlite3 *db; /* The database connection to which it belongs */ + const PragmaName *pName; /* Name of the pragma */ + u8 nHidden; /* Number of hidden columns */ + u8 iHidden; /* Index of the first hidden column */ +}; +struct PragmaVtabCursor { + sqlite3_vtab_cursor base; /* Base class. Must be first */ + sqlite3_stmt *pPragma; /* The pragma statement to run */ + sqlite_int64 iRowid; /* Current rowid */ + char *azArg[2]; /* Value of the argument and schema */ +}; + +/* +** Pragma virtual table module xConnect method. +*/ +static int pragmaVtabConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + const PragmaName *pPragma = (const PragmaName*)pAux; + PragmaVtab *pTab = 0; + int rc; + int i, j; + char cSep = '('; + StrAccum acc; + char zBuf[200]; + + UNUSED_PARAMETER(argc); + UNUSED_PARAMETER(argv); + sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0); + sqlite3StrAccumAppendAll(&acc, "CREATE TABLE x"); + for(i=0, j=pPragma->iPragCName; inPragCName; i++, j++){ + sqlite3XPrintf(&acc, "%c\"%s\"", cSep, pragCName[j]); + cSep = ','; + } + if( i==0 ){ + sqlite3XPrintf(&acc, "(\"%s\"", pPragma->zName); + cSep = ','; + i++; + } + j = 0; + if( pPragma->mPragFlg & PragFlg_Result1 ){ + sqlite3StrAccumAppendAll(&acc, ",arg HIDDEN"); + j++; + } + if( pPragma->mPragFlg & (PragFlg_SchemaOpt|PragFlg_SchemaReq) ){ + sqlite3StrAccumAppendAll(&acc, ",schema HIDDEN"); + j++; + } + sqlite3StrAccumAppend(&acc, ")", 1); + sqlite3StrAccumFinish(&acc); + assert( strlen(zBuf) < sizeof(zBuf)-1 ); + rc = sqlite3_declare_vtab(db, zBuf); + if( rc==SQLITE_OK ){ + pTab = (PragmaVtab*)sqlite3_malloc(sizeof(PragmaVtab)); + if( pTab==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(pTab, 0, sizeof(PragmaVtab)); + pTab->pName = pPragma; + pTab->db = db; + pTab->iHidden = i; + pTab->nHidden = j; + } + }else{ + *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db)); + } + + *ppVtab = (sqlite3_vtab*)pTab; + return rc; +} + +/* +** Pragma virtual table module xDisconnect method. +*/ +static int pragmaVtabDisconnect(sqlite3_vtab *pVtab){ + PragmaVtab *pTab = (PragmaVtab*)pVtab; + sqlite3_free(pTab); + return SQLITE_OK; +} + +/* Figure out the best index to use to search a pragma virtual table. +** +** There are not really any index choices. But we want to encourage the +** query planner to give == constraints on as many hidden parameters as +** possible, and especially on the first hidden parameter. So return a +** high cost if hidden parameters are unconstrained. +*/ +static int pragmaVtabBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ + PragmaVtab *pTab = (PragmaVtab*)tab; + const struct sqlite3_index_constraint *pConstraint; + int i, j; + int seen[2]; + + pIdxInfo->estimatedCost = (double)1; + 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->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + if( pConstraint->iColumn < pTab->iHidden ) continue; + j = pConstraint->iColumn - pTab->iHidden; + assert( j < 2 ); + seen[j] = i+1; + } + if( seen[0]==0 ){ + pIdxInfo->estimatedCost = (double)2147483647; + pIdxInfo->estimatedRows = 2147483647; + 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; + return SQLITE_OK; +} + +/* Create a new cursor for the pragma virtual table */ +static int pragmaVtabOpen(sqlite3_vtab *pVtab, sqlite3_vtab_cursor **ppCursor){ + PragmaVtabCursor *pCsr; + pCsr = (PragmaVtabCursor*)sqlite3_malloc(sizeof(*pCsr)); + if( pCsr==0 ) return SQLITE_NOMEM; + memset(pCsr, 0, sizeof(PragmaVtabCursor)); + pCsr->base.pVtab = pVtab; + *ppCursor = &pCsr->base; + return SQLITE_OK; +} + +/* Clear all content from pragma virtual table cursor. */ +static void pragmaVtabCursorClear(PragmaVtabCursor *pCsr){ + int i; + sqlite3_finalize(pCsr->pPragma); + pCsr->pPragma = 0; + for(i=0; iazArg); i++){ + sqlite3_free(pCsr->azArg[i]); + pCsr->azArg[i] = 0; + } +} + +/* Close a pragma virtual table cursor */ +static int pragmaVtabClose(sqlite3_vtab_cursor *cur){ + PragmaVtabCursor *pCsr = (PragmaVtabCursor*)cur; + pragmaVtabCursorClear(pCsr); + sqlite3_free(pCsr); + return SQLITE_OK; +} + +/* Advance the pragma virtual table cursor to the next row */ +static int pragmaVtabNext(sqlite3_vtab_cursor *pVtabCursor){ + PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor; + int rc = SQLITE_OK; + + /* Increment the xRowid value */ + pCsr->iRowid++; + assert( pCsr->pPragma ); + if( SQLITE_ROW!=sqlite3_step(pCsr->pPragma) ){ + rc = sqlite3_finalize(pCsr->pPragma); + pCsr->pPragma = 0; + pragmaVtabCursorClear(pCsr); + } + return rc; +} + +/* +** Pragma virtual table module xFilter method. +*/ +static int pragmaVtabFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor; + PragmaVtab *pTab = (PragmaVtab*)(pVtabCursor->pVtab); + int rc; + int i, j; + StrAccum acc; + char *zSql; + + UNUSED_PARAMETER(idxNum); + UNUSED_PARAMETER(idxStr); + pragmaVtabCursorClear(pCsr); + j = (pTab->pName->mPragFlg & PragFlg_Result1)!=0 ? 0 : 1; + for(i=0; iazArg) ); + pCsr->azArg[j] = sqlite3_mprintf("%s", sqlite3_value_text(argv[i])); + if( pCsr->azArg[j]==0 ){ + return SQLITE_NOMEM; + } + } + sqlite3StrAccumInit(&acc, 0, 0, 0, pTab->db->aLimit[SQLITE_LIMIT_SQL_LENGTH]); + sqlite3StrAccumAppendAll(&acc, "PRAGMA "); + if( pCsr->azArg[1] ){ + sqlite3XPrintf(&acc, "%Q.", pCsr->azArg[1]); + } + sqlite3StrAccumAppendAll(&acc, pTab->pName->zName); + if( pCsr->azArg[0] ){ + sqlite3XPrintf(&acc, "=%Q", pCsr->azArg[0]); + } + zSql = sqlite3StrAccumFinish(&acc); + if( zSql==0 ) return SQLITE_NOMEM; + rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pPragma, 0); + sqlite3_free(zSql); + if( rc!=SQLITE_OK ){ + pTab->base.zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pTab->db)); + return rc; + } + return pragmaVtabNext(pVtabCursor); +} + +/* +** Pragma virtual table module xEof method. +*/ +static int pragmaVtabEof(sqlite3_vtab_cursor *pVtabCursor){ + PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor; + return (pCsr->pPragma==0); +} + +/* The xColumn method simply returns the corresponding column from +** the PRAGMA. +*/ +static int pragmaVtabColumn( + sqlite3_vtab_cursor *pVtabCursor, + sqlite3_context *ctx, + int i +){ + PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor; + PragmaVtab *pTab = (PragmaVtab*)(pVtabCursor->pVtab); + if( iiHidden ){ + sqlite3_result_value(ctx, sqlite3_column_value(pCsr->pPragma, i)); + }else{ + sqlite3_result_text(ctx, pCsr->azArg[i-pTab->iHidden],-1,SQLITE_TRANSIENT); + } + return SQLITE_OK; +} + +/* +** Pragma virtual table module xRowid method. +*/ +static int pragmaVtabRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *p){ + PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor; + *p = pCsr->iRowid; + return SQLITE_OK; +} + +/* The pragma virtual table object */ +static const sqlite3_module pragmaVtabModule = { + 0, /* iVersion */ + 0, /* xCreate - create a table */ + pragmaVtabConnect, /* xConnect - connect to an existing table */ + pragmaVtabBestIndex, /* xBestIndex - Determine search strategy */ + pragmaVtabDisconnect, /* xDisconnect - Disconnect from a table */ + 0, /* xDestroy - Drop a table */ + pragmaVtabOpen, /* xOpen - open a cursor */ + pragmaVtabClose, /* xClose - close a cursor */ + pragmaVtabFilter, /* xFilter - configure scan constraints */ + pragmaVtabNext, /* xNext - advance a cursor */ + pragmaVtabEof, /* xEof */ + pragmaVtabColumn, /* xColumn - read data */ + pragmaVtabRowid, /* xRowid - read data */ + 0, /* xUpdate - write data */ + 0, /* xBegin - begin transaction */ + 0, /* xSync - sync transaction */ + 0, /* xCommit - commit transaction */ + 0, /* xRollback - rollback transaction */ + 0, /* xFindFunction - function overloading */ + 0, /* xRename - rename the table */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0 /* xRollbackTo */ +}; + +/* +** Check to see if zTabName is really the name of a pragma. If it is, +** then register an eponymous virtual table for that pragma and return +** a pointer to the Module object for the new virtual table. +*/ +Module *sqlite3PragmaVtabRegister(sqlite3 *db, const char *zName){ + const PragmaName *pName; + assert( sqlite3_strnicmp(zName, "pragma_", 7)==0 ); + pName = pragmaLocate(zName+7); + if( pName==0 ) return 0; + if( (pName->mPragFlg & (PragFlg_Result0|PragFlg_Result1))==0 ) return 0; + assert( sqlite3HashFind(&db->aModule, zName)==0 ); + return sqlite3VtabCreateModule(db, zName, &pragmaVtabModule, (void*)pName, 0); +} + +#endif /* SQLITE_OMIT_VIRTUALTABLE */ #endif /* SQLITE_OMIT_PRAGMA */ diff --git a/src/pragma.h b/src/pragma.h index 81779e9d4a..5d8d0aa35b 100644 --- a/src/pragma.h +++ b/src/pragma.h @@ -3,6 +3,8 @@ ** ../tool/mkpragmatab.tcl. To update the set of pragmas, edit ** that script and rerun it. */ + +/* The various pragma types */ #define PragTyp_HEADER_VALUE 0 #define PragTyp_AUTO_VACUUM 1 #define PragTyp_FLAG 2 @@ -46,419 +48,560 @@ #define PragTyp_REKEY 40 #define PragTyp_LOCK_STATUS 41 #define PragTyp_PARSER_TRACE 42 -#define PragFlag_NeedSchema 0x01 -#define PragFlag_ReadOnly 0x02 -static const struct sPragmaNames { - const char *const zName; /* Name of pragma */ - u8 ePragTyp; /* PragTyp_XXX value */ - u8 mPragFlag; /* Zero or more PragFlag_XXX values */ - u32 iArg; /* Extra argument */ -} aPragmaNames[] = { + +/* Property flags associated with various pragma. */ +#define PragFlg_NeedSchema 0x01 /* Force schema load before running */ +#define PragFlg_NoColumns 0x02 /* OP_ResultRow called with zero columns */ +#define PragFlg_NoColumns1 0x04 /* zero columns if RHS argument is present */ +#define PragFlg_ReadOnly 0x08 /* Read-only HEADER_VALUE */ +#define PragFlg_Result0 0x10 /* Acts as query when no argument */ +#define PragFlg_Result1 0x20 /* Acts as query when has one argument */ +#define PragFlg_SchemaOpt 0x40 /* Schema restricts name search if present */ +#define PragFlg_SchemaReq 0x80 /* Schema required - "main" is default */ + +/* Names of columns for pragmas that return multi-column result +** or that return single-column results where the name of the +** result column is different from the name of the pragma +*/ +static const char *const pragCName[] = { + /* 0 */ "cache_size", /* Used by: default_cache_size */ + /* 1 */ "cid", /* Used by: table_info */ + /* 2 */ "name", + /* 3 */ "type", + /* 4 */ "notnull", + /* 5 */ "dflt_value", + /* 6 */ "pk", + /* 7 */ "table", /* Used by: stats */ + /* 8 */ "index", + /* 9 */ "width", + /* 10 */ "height", + /* 11 */ "seqno", /* Used by: index_info */ + /* 12 */ "cid", + /* 13 */ "name", + /* 14 */ "seqno", /* Used by: index_xinfo */ + /* 15 */ "cid", + /* 16 */ "name", + /* 17 */ "desc", + /* 18 */ "coll", + /* 19 */ "key", + /* 20 */ "seq", /* Used by: index_list */ + /* 21 */ "name", + /* 22 */ "unique", + /* 23 */ "origin", + /* 24 */ "partial", + /* 25 */ "seq", /* Used by: database_list */ + /* 26 */ "name", + /* 27 */ "file", + /* 28 */ "seq", /* Used by: collation_list */ + /* 29 */ "name", + /* 30 */ "id", /* Used by: foreign_key_list */ + /* 31 */ "seq", + /* 32 */ "table", + /* 33 */ "from", + /* 34 */ "to", + /* 35 */ "on_update", + /* 36 */ "on_delete", + /* 37 */ "match", + /* 38 */ "table", /* Used by: foreign_key_check */ + /* 39 */ "rowid", + /* 40 */ "parent", + /* 41 */ "fkid", + /* 42 */ "busy", /* Used by: wal_checkpoint */ + /* 43 */ "log", + /* 44 */ "checkpointed", + /* 45 */ "timeout", /* Used by: busy_timeout */ + /* 46 */ "database", /* Used by: lock_status */ + /* 47 */ "status", +}; + +/* Definitions of all built-in pragmas */ +typedef struct PragmaName { + const char *const zName; /* Name of pragma */ + u8 ePragTyp; /* PragTyp_XXX value */ + u8 mPragFlg; /* Zero or more PragFlg_XXX values */ + u8 iPragCName; /* Start of column names in pragCName[] */ + u8 nPragCName; /* Num of col names. 0 means use pragma name */ + u32 iArg; /* Extra argument */ +} PragmaName; +static const PragmaName aPragmaName[] = { #if defined(SQLITE_HAS_CODEC) || defined(SQLITE_ENABLE_CEROD) - { /* zName: */ "activate_extensions", - /* ePragTyp: */ PragTyp_ACTIVATE_EXTENSIONS, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "activate_extensions", + /* ePragTyp: */ PragTyp_ACTIVATE_EXTENSIONS, + /* ePragFlg: */ 0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) - { /* zName: */ "application_id", - /* ePragTyp: */ PragTyp_HEADER_VALUE, - /* ePragFlag: */ 0, - /* iArg: */ BTREE_APPLICATION_ID }, + {/* zName: */ "application_id", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlg: */ PragFlg_NoColumns1|PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ BTREE_APPLICATION_ID }, #endif #if !defined(SQLITE_OMIT_AUTOVACUUM) - { /* zName: */ "auto_vacuum", - /* ePragTyp: */ PragTyp_AUTO_VACUUM, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "auto_vacuum", + /* ePragTyp: */ PragTyp_AUTO_VACUUM, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if !defined(SQLITE_OMIT_AUTOMATIC_INDEX) - { /* zName: */ "automatic_index", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_AutoIndex }, + {/* zName: */ "automatic_index", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_AutoIndex }, #endif #endif - { /* zName: */ "busy_timeout", - /* ePragTyp: */ PragTyp_BUSY_TIMEOUT, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "busy_timeout", + /* ePragTyp: */ PragTyp_BUSY_TIMEOUT, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 45, 1, + /* iArg: */ 0 }, #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) - { /* zName: */ "cache_size", - /* ePragTyp: */ PragTyp_CACHE_SIZE, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "cache_size", + /* ePragTyp: */ PragTyp_CACHE_SIZE, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "cache_spill", - /* ePragTyp: */ PragTyp_CACHE_SPILL, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "cache_spill", + /* ePragTyp: */ PragTyp_CACHE_SPILL, + /* ePragFlg: */ PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif - { /* zName: */ "case_sensitive_like", - /* ePragTyp: */ PragTyp_CASE_SENSITIVE_LIKE, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, - { /* zName: */ "cell_size_check", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_CellSizeCk }, + {/* zName: */ "case_sensitive_like", + /* ePragTyp: */ PragTyp_CASE_SENSITIVE_LIKE, + /* ePragFlg: */ PragFlg_NoColumns, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "cell_size_check", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_CellSizeCk }, #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "checkpoint_fullfsync", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_CkptFullFSync }, + {/* zName: */ "checkpoint_fullfsync", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_CkptFullFSync }, #endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) - { /* zName: */ "collation_list", - /* ePragTyp: */ PragTyp_COLLATION_LIST, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "collation_list", + /* ePragTyp: */ PragTyp_COLLATION_LIST, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 28, 2, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_COMPILEOPTION_DIAGS) - { /* zName: */ "compile_options", - /* ePragTyp: */ PragTyp_COMPILE_OPTIONS, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "compile_options", + /* ePragTyp: */ PragTyp_COMPILE_OPTIONS, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "count_changes", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_CountRows }, + {/* zName: */ "count_changes", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_CountRows }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && SQLITE_OS_WIN - { /* zName: */ "data_store_directory", - /* ePragTyp: */ PragTyp_DATA_STORE_DIRECTORY, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "data_store_directory", + /* ePragTyp: */ PragTyp_DATA_STORE_DIRECTORY, + /* ePragFlg: */ PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) - { /* zName: */ "data_version", - /* ePragTyp: */ PragTyp_HEADER_VALUE, - /* ePragFlag: */ PragFlag_ReadOnly, - /* iArg: */ BTREE_DATA_VERSION }, + {/* zName: */ "data_version", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlg: */ PragFlg_ReadOnly|PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ BTREE_DATA_VERSION }, #endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) - { /* zName: */ "database_list", - /* ePragTyp: */ PragTyp_DATABASE_LIST, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "database_list", + /* ePragTyp: */ PragTyp_DATABASE_LIST, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0, + /* ColNames: */ 25, 3, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED) - { /* zName: */ "default_cache_size", - /* ePragTyp: */ PragTyp_DEFAULT_CACHE_SIZE, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "default_cache_size", + /* ePragTyp: */ PragTyp_DEFAULT_CACHE_SIZE, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ColNames: */ 0, 1, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) - { /* zName: */ "defer_foreign_keys", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_DeferFKs }, + {/* zName: */ "defer_foreign_keys", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_DeferFKs }, #endif #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "empty_result_callbacks", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_NullCallback }, + {/* zName: */ "empty_result_callbacks", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_NullCallback }, #endif #if !defined(SQLITE_OMIT_UTF16) - { /* zName: */ "encoding", - /* ePragTyp: */ PragTyp_ENCODING, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "encoding", + /* ePragTyp: */ PragTyp_ENCODING, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) - { /* zName: */ "foreign_key_check", - /* ePragTyp: */ PragTyp_FOREIGN_KEY_CHECK, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "foreign_key_check", + /* ePragTyp: */ PragTyp_FOREIGN_KEY_CHECK, + /* ePragFlg: */ PragFlg_NeedSchema, + /* ColNames: */ 38, 4, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FOREIGN_KEY) - { /* zName: */ "foreign_key_list", - /* ePragTyp: */ PragTyp_FOREIGN_KEY_LIST, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "foreign_key_list", + /* ePragTyp: */ PragTyp_FOREIGN_KEY_LIST, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 30, 8, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) - { /* zName: */ "foreign_keys", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_ForeignKeys }, + {/* zName: */ "foreign_keys", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_ForeignKeys }, #endif #endif #if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) - { /* zName: */ "freelist_count", - /* ePragTyp: */ PragTyp_HEADER_VALUE, - /* ePragFlag: */ PragFlag_ReadOnly, - /* iArg: */ BTREE_FREE_PAGE_COUNT }, + {/* zName: */ "freelist_count", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlg: */ PragFlg_ReadOnly|PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ BTREE_FREE_PAGE_COUNT }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "full_column_names", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_FullColNames }, - { /* zName: */ "fullfsync", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_FullFSync }, + {/* zName: */ "full_column_names", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_FullColNames }, + {/* zName: */ "fullfsync", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_FullFSync }, #endif #if defined(SQLITE_HAS_CODEC) - { /* zName: */ "hexkey", - /* ePragTyp: */ PragTyp_HEXKEY, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, - { /* zName: */ "hexrekey", - /* ePragTyp: */ PragTyp_HEXKEY, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "hexkey", + /* ePragTyp: */ PragTyp_HEXKEY, + /* ePragFlg: */ 0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "hexrekey", + /* ePragTyp: */ PragTyp_HEXKEY, + /* ePragFlg: */ 0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if !defined(SQLITE_OMIT_CHECK) - { /* zName: */ "ignore_check_constraints", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_IgnoreChecks }, + {/* zName: */ "ignore_check_constraints", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_IgnoreChecks }, #endif #endif #if !defined(SQLITE_OMIT_AUTOVACUUM) - { /* zName: */ "incremental_vacuum", - /* ePragTyp: */ PragTyp_INCREMENTAL_VACUUM, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "incremental_vacuum", + /* ePragTyp: */ PragTyp_INCREMENTAL_VACUUM, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_NoColumns, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) - { /* zName: */ "index_info", - /* ePragTyp: */ PragTyp_INDEX_INFO, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, - { /* zName: */ "index_list", - /* ePragTyp: */ PragTyp_INDEX_LIST, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, - { /* zName: */ "index_xinfo", - /* ePragTyp: */ PragTyp_INDEX_INFO, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 1 }, + {/* zName: */ "index_info", + /* ePragTyp: */ PragTyp_INDEX_INFO, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 11, 3, + /* iArg: */ 0 }, + {/* zName: */ "index_list", + /* ePragTyp: */ PragTyp_INDEX_LIST, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 20, 5, + /* iArg: */ 0 }, + {/* zName: */ "index_xinfo", + /* ePragTyp: */ PragTyp_INDEX_INFO, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 14, 6, + /* iArg: */ 1 }, #endif #if !defined(SQLITE_OMIT_INTEGRITY_CHECK) - { /* zName: */ "integrity_check", - /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "integrity_check", + /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, + /* ePragFlg: */ PragFlg_NeedSchema, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) - { /* zName: */ "journal_mode", - /* ePragTyp: */ PragTyp_JOURNAL_MODE, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, - { /* zName: */ "journal_size_limit", - /* ePragTyp: */ PragTyp_JOURNAL_SIZE_LIMIT, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "journal_mode", + /* ePragTyp: */ PragTyp_JOURNAL_MODE, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "journal_size_limit", + /* ePragTyp: */ PragTyp_JOURNAL_SIZE_LIMIT, + /* ePragFlg: */ PragFlg_Result0|PragFlg_SchemaReq, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if defined(SQLITE_HAS_CODEC) - { /* zName: */ "key", - /* ePragTyp: */ PragTyp_KEY, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "key", + /* ePragTyp: */ PragTyp_KEY, + /* ePragFlg: */ 0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "legacy_file_format", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_LegacyFileFmt }, + {/* zName: */ "legacy_file_format", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_LegacyFileFmt }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && SQLITE_ENABLE_LOCKING_STYLE - { /* zName: */ "lock_proxy_file", - /* ePragTyp: */ PragTyp_LOCK_PROXY_FILE, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "lock_proxy_file", + /* ePragTyp: */ PragTyp_LOCK_PROXY_FILE, + /* ePragFlg: */ PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) - { /* zName: */ "lock_status", - /* ePragTyp: */ PragTyp_LOCK_STATUS, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "lock_status", + /* ePragTyp: */ PragTyp_LOCK_STATUS, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 46, 2, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) - { /* zName: */ "locking_mode", - /* ePragTyp: */ PragTyp_LOCKING_MODE, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, - { /* zName: */ "max_page_count", - /* ePragTyp: */ PragTyp_PAGE_COUNT, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, - { /* zName: */ "mmap_size", - /* ePragTyp: */ PragTyp_MMAP_SIZE, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, - { /* zName: */ "page_count", - /* ePragTyp: */ PragTyp_PAGE_COUNT, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, - { /* zName: */ "page_size", - /* ePragTyp: */ PragTyp_PAGE_SIZE, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "locking_mode", + /* ePragTyp: */ PragTyp_LOCKING_MODE, + /* ePragFlg: */ PragFlg_Result0|PragFlg_SchemaReq, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "max_page_count", + /* ePragTyp: */ PragTyp_PAGE_COUNT, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "mmap_size", + /* ePragTyp: */ PragTyp_MMAP_SIZE, + /* ePragFlg: */ 0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "page_count", + /* ePragTyp: */ PragTyp_PAGE_COUNT, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "page_size", + /* ePragTyp: */ PragTyp_PAGE_SIZE, + /* ePragFlg: */ PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_PARSER_TRACE) - { /* zName: */ "parser_trace", - /* ePragTyp: */ PragTyp_PARSER_TRACE, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "parser_trace", + /* ePragTyp: */ PragTyp_PARSER_TRACE, + /* ePragFlg: */ 0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "query_only", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_QueryOnly }, + {/* zName: */ "query_only", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_QueryOnly }, #endif #if !defined(SQLITE_OMIT_INTEGRITY_CHECK) - { /* zName: */ "quick_check", - /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "quick_check", + /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, + /* ePragFlg: */ PragFlg_NeedSchema, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "read_uncommitted", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_ReadUncommitted }, - { /* zName: */ "recursive_triggers", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_RecTriggers }, + {/* zName: */ "read_uncommitted", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_ReadUncommitted }, + {/* zName: */ "recursive_triggers", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_RecTriggers }, #endif #if defined(SQLITE_HAS_CODEC) - { /* zName: */ "rekey", - /* ePragTyp: */ PragTyp_REKEY, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "rekey", + /* ePragTyp: */ PragTyp_REKEY, + /* ePragFlg: */ 0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "reverse_unordered_selects", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_ReverseOrder }, + {/* zName: */ "reverse_unordered_selects", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_ReverseOrder }, #endif #if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) - { /* zName: */ "schema_version", - /* ePragTyp: */ PragTyp_HEADER_VALUE, - /* ePragFlag: */ 0, - /* iArg: */ BTREE_SCHEMA_VERSION }, + {/* zName: */ "schema_version", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlg: */ PragFlg_NoColumns1|PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ BTREE_SCHEMA_VERSION }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) - { /* zName: */ "secure_delete", - /* ePragTyp: */ PragTyp_SECURE_DELETE, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "secure_delete", + /* ePragTyp: */ PragTyp_SECURE_DELETE, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "short_column_names", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_ShortColNames }, + {/* zName: */ "short_column_names", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_ShortColNames }, #endif - { /* zName: */ "shrink_memory", - /* ePragTyp: */ PragTyp_SHRINK_MEMORY, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, - { /* zName: */ "soft_heap_limit", - /* ePragTyp: */ PragTyp_SOFT_HEAP_LIMIT, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "shrink_memory", + /* ePragTyp: */ PragTyp_SHRINK_MEMORY, + /* ePragFlg: */ PragFlg_NoColumns, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "soft_heap_limit", + /* ePragTyp: */ PragTyp_SOFT_HEAP_LIMIT, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if defined(SQLITE_DEBUG) - { /* zName: */ "sql_trace", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_SqlTrace }, + {/* zName: */ "sql_trace", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_SqlTrace }, #endif #endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) - { /* zName: */ "stats", - /* ePragTyp: */ PragTyp_STATS, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "stats", + /* ePragTyp: */ PragTyp_STATS, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq, + /* ColNames: */ 7, 4, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) - { /* zName: */ "synchronous", - /* ePragTyp: */ PragTyp_SYNCHRONOUS, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "synchronous", + /* ePragTyp: */ PragTyp_SYNCHRONOUS, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) - { /* zName: */ "table_info", - /* ePragTyp: */ PragTyp_TABLE_INFO, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "table_info", + /* ePragTyp: */ PragTyp_TABLE_INFO, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 1, 6, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) - { /* zName: */ "temp_store", - /* ePragTyp: */ PragTyp_TEMP_STORE, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, - { /* zName: */ "temp_store_directory", - /* ePragTyp: */ PragTyp_TEMP_STORE_DIRECTORY, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "temp_store", + /* ePragTyp: */ PragTyp_TEMP_STORE, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "temp_store_directory", + /* ePragTyp: */ PragTyp_TEMP_STORE_DIRECTORY, + /* ePragFlg: */ PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif - { /* zName: */ "threads", - /* ePragTyp: */ PragTyp_THREADS, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "threads", + /* ePragTyp: */ PragTyp_THREADS, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) - { /* zName: */ "user_version", - /* ePragTyp: */ PragTyp_HEADER_VALUE, - /* ePragFlag: */ 0, - /* iArg: */ BTREE_USER_VERSION }, + {/* zName: */ "user_version", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlg: */ PragFlg_NoColumns1|PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ BTREE_USER_VERSION }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if defined(SQLITE_DEBUG) - { /* zName: */ "vdbe_addoptrace", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_VdbeAddopTrace }, - { /* zName: */ "vdbe_debug", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_SqlTrace|SQLITE_VdbeListing|SQLITE_VdbeTrace }, - { /* zName: */ "vdbe_eqp", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_VdbeEQP }, - { /* zName: */ "vdbe_listing", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_VdbeListing }, - { /* zName: */ "vdbe_trace", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_VdbeTrace }, + {/* zName: */ "vdbe_addoptrace", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_VdbeAddopTrace }, + {/* zName: */ "vdbe_debug", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_SqlTrace|SQLITE_VdbeListing|SQLITE_VdbeTrace }, + {/* zName: */ "vdbe_eqp", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_VdbeEQP }, + {/* zName: */ "vdbe_listing", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_VdbeListing }, + {/* zName: */ "vdbe_trace", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_VdbeTrace }, #endif #endif #if !defined(SQLITE_OMIT_WAL) - { /* zName: */ "wal_autocheckpoint", - /* ePragTyp: */ PragTyp_WAL_AUTOCHECKPOINT, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, - { /* zName: */ "wal_checkpoint", - /* ePragTyp: */ PragTyp_WAL_CHECKPOINT, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "wal_autocheckpoint", + /* ePragTyp: */ PragTyp_WAL_AUTOCHECKPOINT, + /* ePragFlg: */ 0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "wal_checkpoint", + /* ePragTyp: */ PragTyp_WAL_CHECKPOINT, + /* ePragFlg: */ PragFlg_NeedSchema, + /* ColNames: */ 42, 3, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "writable_schema", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_WriteSchema|SQLITE_RecoveryMode }, + {/* zName: */ "writable_schema", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_WriteSchema|SQLITE_RecoveryMode }, #endif }; /* Number of pragmas: 60 on by default, 73 total. */ diff --git a/src/prepare.c b/src/prepare.c index 6685dfeaf7..74127bc76b 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -73,6 +73,7 @@ int sqlite3InitCallback(void *pInit, int argc, char **argv, char **NotUsed){ ** structures that describe the table, index, or view. */ int rc; + u8 saved_iDb = db->init.iDb; sqlite3_stmt *pStmt; TESTONLY(int rcp); /* Return code from sqlite3_prepare() */ @@ -83,7 +84,8 @@ int sqlite3InitCallback(void *pInit, int argc, char **argv, char **NotUsed){ TESTONLY(rcp = ) sqlite3_prepare(db, argv[2], -1, &pStmt, 0); rc = db->errCode; assert( (rc&0xFF)==(rcp&0xFF) ); - db->init.iDb = 0; + db->init.iDb = saved_iDb; + assert( saved_iDb==0 || (db->flags & SQLITE_Vacuum)!=0 ); if( SQLITE_OK!=rc ){ if( db->init.orphanTrigger ){ assert( iDb==1 ); @@ -107,7 +109,7 @@ int sqlite3InitCallback(void *pInit, int argc, char **argv, char **NotUsed){ ** to do here is record the root page number for that index. */ Index *pIndex; - pIndex = sqlite3FindIndex(db, argv[0], db->aDb[iDb].zName); + pIndex = sqlite3FindIndex(db, argv[0], db->aDb[iDb].zDbSName); if( pIndex==0 ){ /* This can occur if there exists an index on a TEMP table which ** has the same name as another index on a permanent index. Since @@ -286,7 +288,7 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){ char *zSql; zSql = sqlite3MPrintf(db, "SELECT name, rootpage, sql FROM \"%w\".%s ORDER BY rowid", - db->aDb[iDb].zName, zMasterName); + db->aDb[iDb].zDbSName, zMasterName); #ifndef SQLITE_OMIT_AUTHORIZATION { sqlite3_xauth xAuth; @@ -516,18 +518,14 @@ static int sqlite3Prepare( sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ const char **pzTail /* OUT: End of parsed string */ ){ - Parse *pParse; /* Parsing context */ char *zErrMsg = 0; /* Error message */ int rc = SQLITE_OK; /* Result code */ int i; /* Loop counter */ + Parse sParse; /* Parsing context */ - /* Allocate the parsing context */ - pParse = sqlite3StackAllocZero(db, sizeof(*pParse)); - if( pParse==0 ){ - rc = SQLITE_NOMEM_BKPT; - goto end_prepare; - } - pParse->pReprepare = pReprepare; + memset(&sParse, 0, PARSE_HDR_SZ); + memset(PARSE_TAIL(&sParse), 0, PARSE_TAIL_SZ); + sParse.pReprepare = pReprepare; assert( ppStmt && *ppStmt==0 ); /* assert( !db->mallocFailed ); // not true with SQLITE_USE_ALLOCA */ assert( sqlite3_mutex_held(db->mutex) ); @@ -561,7 +559,7 @@ static int sqlite3Prepare( assert( sqlite3BtreeHoldsMutex(pBt) ); rc = sqlite3BtreeSchemaLocked(pBt); if( rc ){ - const char *zDb = db->aDb[i].zName; + const char *zDb = db->aDb[i].zDbSName; sqlite3ErrorWithMsg(db, rc, "database schema is locked: %s", zDb); testcase( db->flags & SQLITE_ReadUncommitted ); goto end_prepare; @@ -571,8 +569,7 @@ static int sqlite3Prepare( sqlite3VtabUnlockList(db); - pParse->db = db; - pParse->nQueryLoop = 0; /* Logarithmic, so 0 really means 1 */ + sParse.db = db; if( nBytes>=0 && (nBytes==0 || zSql[nBytes-1]!=0) ){ char *zSqlCopy; int mxLen = db->aLimit[SQLITE_LIMIT_SQL_LENGTH]; @@ -585,61 +582,61 @@ static int sqlite3Prepare( } zSqlCopy = sqlite3DbStrNDup(db, zSql, nBytes); if( zSqlCopy ){ - sqlite3RunParser(pParse, zSqlCopy, &zErrMsg); - pParse->zTail = &zSql[pParse->zTail-zSqlCopy]; + sqlite3RunParser(&sParse, zSqlCopy, &zErrMsg); + sParse.zTail = &zSql[sParse.zTail-zSqlCopy]; sqlite3DbFree(db, zSqlCopy); }else{ - pParse->zTail = &zSql[nBytes]; + sParse.zTail = &zSql[nBytes]; } }else{ - sqlite3RunParser(pParse, zSql, &zErrMsg); + sqlite3RunParser(&sParse, zSql, &zErrMsg); } - assert( 0==pParse->nQueryLoop ); + assert( 0==sParse.nQueryLoop ); - if( pParse->rc==SQLITE_DONE ) pParse->rc = SQLITE_OK; - if( pParse->checkSchema ){ - schemaIsValid(pParse); + if( sParse.rc==SQLITE_DONE ) sParse.rc = SQLITE_OK; + if( sParse.checkSchema ){ + schemaIsValid(&sParse); } if( db->mallocFailed ){ - pParse->rc = SQLITE_NOMEM_BKPT; + sParse.rc = SQLITE_NOMEM_BKPT; } if( pzTail ){ - *pzTail = pParse->zTail; + *pzTail = sParse.zTail; } - rc = pParse->rc; + rc = sParse.rc; #ifndef SQLITE_OMIT_EXPLAIN - if( rc==SQLITE_OK && pParse->pVdbe && pParse->explain ){ + if( rc==SQLITE_OK && sParse.pVdbe && sParse.explain ){ static const char * const azColName[] = { "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment", "selectid", "order", "from", "detail" }; int iFirst, mx; - if( pParse->explain==2 ){ - sqlite3VdbeSetNumCols(pParse->pVdbe, 4); + if( sParse.explain==2 ){ + sqlite3VdbeSetNumCols(sParse.pVdbe, 4); iFirst = 8; mx = 12; }else{ - sqlite3VdbeSetNumCols(pParse->pVdbe, 8); + sqlite3VdbeSetNumCols(sParse.pVdbe, 8); iFirst = 0; mx = 8; } for(i=iFirst; ipVdbe, i-iFirst, COLNAME_NAME, + sqlite3VdbeSetColName(sParse.pVdbe, i-iFirst, COLNAME_NAME, azColName[i], SQLITE_STATIC); } } #endif if( db->init.busy==0 ){ - Vdbe *pVdbe = pParse->pVdbe; - sqlite3VdbeSetSql(pVdbe, zSql, (int)(pParse->zTail-zSql), saveSqlFlag); + Vdbe *pVdbe = sParse.pVdbe; + sqlite3VdbeSetSql(pVdbe, zSql, (int)(sParse.zTail-zSql), saveSqlFlag); } - if( pParse->pVdbe && (rc!=SQLITE_OK || db->mallocFailed) ){ - sqlite3VdbeFinalize(pParse->pVdbe); + if( sParse.pVdbe && (rc!=SQLITE_OK || db->mallocFailed) ){ + sqlite3VdbeFinalize(sParse.pVdbe); assert(!(*ppStmt)); }else{ - *ppStmt = (sqlite3_stmt*)pParse->pVdbe; + *ppStmt = (sqlite3_stmt*)sParse.pVdbe; } if( zErrMsg ){ @@ -650,16 +647,15 @@ static int sqlite3Prepare( } /* Delete any TriggerPrg structures allocated while parsing this statement. */ - while( pParse->pTriggerPrg ){ - TriggerPrg *pT = pParse->pTriggerPrg; - pParse->pTriggerPrg = pT->pNext; + while( sParse.pTriggerPrg ){ + TriggerPrg *pT = sParse.pTriggerPrg; + sParse.pTriggerPrg = pT->pNext; sqlite3DbFree(db, pT); } end_prepare: - sqlite3ParserReset(pParse); - sqlite3StackFree(db, pParse); + sqlite3ParserReset(&sParse); rc = sqlite3ApiExit(db, rc); assert( (rc&db->errMask)==rc ); return rc; diff --git a/src/printf.c b/src/printf.c index a888d50b94..ede86f1208 100644 --- a/src/printf.c +++ b/src/printf.c @@ -59,7 +59,6 @@ typedef struct et_info { /* Information about each format field */ ** Allowed values for et_info.flags */ #define FLAG_SIGNED 1 /* True if the value to convert is signed */ -#define FLAG_INTERN 2 /* True if for internal use only */ #define FLAG_STRING 4 /* Allow infinity precision */ @@ -93,11 +92,10 @@ static const et_info fmtinfo[] = { { '%', 0, 0, etPERCENT, 0, 0 }, { 'p', 16, 0, etPOINTER, 0, 1 }, -/* All the rest have the FLAG_INTERN bit set and are thus for internal -** use only */ - { 'T', 0, 2, etTOKEN, 0, 0 }, - { 'S', 0, 2, etSRCLIST, 0, 0 }, - { 'r', 10, 3, etORDINAL, 0, 0 }, + /* All the rest are undocumented and are for internal use only */ + { 'T', 0, 0, etTOKEN, 0, 0 }, + { 'S', 0, 0, etSRCLIST, 0, 0 }, + { 'r', 10, 1, etORDINAL, 0, 0 }, }; /* @@ -191,7 +189,6 @@ void sqlite3VXPrintf( etByte done; /* Loop termination flag */ etByte xtype = etINVALID; /* Conversion paradigm */ u8 bArgList; /* True for SQLITE_PRINTF_SQLFUNC */ - u8 useIntern; /* Ok to use internal conversions (ex: %T) */ char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ sqlite_uint64 longvalue; /* Value for integer types */ LONGDOUBLE_TYPE realvalue; /* Value for real types */ @@ -210,13 +207,11 @@ void sqlite3VXPrintf( char buf[etBUFSIZE]; /* Conversion buffer */ bufpt = 0; - if( pAccum->printfFlags ){ - if( (bArgList = (pAccum->printfFlags & SQLITE_PRINTF_SQLFUNC))!=0 ){ - pArgList = va_arg(ap, PrintfArguments*); - } - useIntern = pAccum->printfFlags & SQLITE_PRINTF_INTERNAL; + if( (pAccum->printfFlags & SQLITE_PRINTF_SQLFUNC)!=0 ){ + pArgList = va_arg(ap, PrintfArguments*); + bArgList = 1; }else{ - bArgList = useIntern = 0; + bArgList = 0; } for(; (c=(*fmt))!=0; ++fmt){ if( c!='%' ){ @@ -328,11 +323,7 @@ void sqlite3VXPrintf( for(idx=0; idxflags & FLAG_INTERN)==0 ){ - xtype = infop->type; - }else{ - return; - } + xtype = infop->type; break; } } @@ -701,7 +692,9 @@ void sqlite3VXPrintf( break; } case etTOKEN: { - Token *pToken = va_arg(ap, Token*); + Token *pToken; + if( (pAccum->printfFlags & SQLITE_PRINTF_INTERNAL)==0 ) return; + pToken = va_arg(ap, Token*); assert( bArgList==0 ); if( pToken && pToken->n ){ sqlite3StrAccumAppend(pAccum, (const char*)pToken->z, pToken->n); @@ -710,9 +703,13 @@ void sqlite3VXPrintf( break; } case etSRCLIST: { - SrcList *pSrc = va_arg(ap, SrcList*); - int k = va_arg(ap, int); - struct SrcList_item *pItem = &pSrc->a[k]; + SrcList *pSrc; + int k; + struct SrcList_item *pItem; + if( (pAccum->printfFlags & SQLITE_PRINTF_INTERNAL)==0 ) return; + pSrc = va_arg(ap, SrcList*); + k = va_arg(ap, int); + pItem = &pSrc->a[k]; assert( bArgList==0 ); assert( k>=0 && knSrc ); if( pItem->zDatabase ){ @@ -734,9 +731,13 @@ void sqlite3VXPrintf( ** the output. */ width -= length; - if( width>0 && !flag_leftjustify ) sqlite3AppendChar(pAccum, width, ' '); - sqlite3StrAccumAppend(pAccum, bufpt, length); - if( width>0 && flag_leftjustify ) sqlite3AppendChar(pAccum, width, ' '); + if( width>0 ){ + if( !flag_leftjustify ) sqlite3AppendChar(pAccum, width, ' '); + sqlite3StrAccumAppend(pAccum, bufpt, length); + if( flag_leftjustify ) sqlite3AppendChar(pAccum, width, ' '); + }else{ + sqlite3StrAccumAppend(pAccum, bufpt, length); + } if( zExtra ){ sqlite3DbFree(pAccum->db, zExtra); @@ -841,7 +842,7 @@ void sqlite3StrAccumAppend(StrAccum *p, const char *z, int N){ assert( p->accError==0 || p->nAlloc==0 ); if( p->nChar+N >= p->nAlloc ){ enlargeAndAppend(p,z,N); - }else{ + }else if( N ){ assert( p->zText ); p->nChar += N; memcpy(&p->zText[p->nChar-N], z, N); @@ -861,18 +862,23 @@ void sqlite3StrAccumAppendAll(StrAccum *p, const char *z){ ** Return a pointer to the resulting string. Return a NULL ** pointer if any kind of error was encountered. */ +static SQLITE_NOINLINE char *strAccumFinishRealloc(StrAccum *p){ + assert( p->mxAlloc>0 && !isMalloced(p) ); + p->zText = sqlite3DbMallocRaw(p->db, p->nChar+1 ); + if( p->zText ){ + memcpy(p->zText, p->zBase, p->nChar+1); + p->printfFlags |= SQLITE_PRINTF_MALLOCED; + }else{ + setStrAccumError(p, STRACCUM_NOMEM); + } + return p->zText; +} char *sqlite3StrAccumFinish(StrAccum *p){ if( p->zText ){ assert( (p->zText==p->zBase)==!isMalloced(p) ); p->zText[p->nChar] = 0; if( p->mxAlloc>0 && !isMalloced(p) ){ - p->zText = sqlite3DbMallocRaw(p->db, p->nChar+1 ); - if( p->zText ){ - memcpy(p->zText, p->zBase, p->nChar+1); - p->printfFlags |= SQLITE_PRINTF_MALLOCED; - }else{ - setStrAccumError(p, STRACCUM_NOMEM); - } + return strAccumFinishRealloc(p); } } return p->zText; @@ -1012,7 +1018,8 @@ char *sqlite3_vsnprintf(int n, char *zBuf, const char *zFormat, va_list ap){ #endif sqlite3StrAccumInit(&acc, 0, zBuf, n, 0); sqlite3VXPrintf(&acc, zFormat, ap); - return sqlite3StrAccumFinish(&acc); + zBuf[acc.nChar] = 0; + return zBuf; } char *sqlite3_snprintf(int n, char *zBuf, const char *zFormat, ...){ char *z; diff --git a/src/random.c b/src/random.c index 179d01bef2..d4ae77c435 100644 --- a/src/random.c +++ b/src/random.c @@ -106,7 +106,7 @@ void sqlite3_randomness(int N, void *pBuf){ sqlite3_mutex_leave(mutex); } -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE /* ** For testing purposes, we sometimes want to preserve the state of ** PRNG and restore the PRNG to its saved state at a later time, or @@ -131,4 +131,4 @@ void sqlite3PrngRestoreState(void){ sizeof(sqlite3Prng) ); } -#endif /* SQLITE_OMIT_BUILTIN_TEST */ +#endif /* SQLITE_UNTESTABLE */ diff --git a/src/resolve.c b/src/resolve.c index 8ae7f0919c..dac73e5fa9 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -221,8 +221,8 @@ static int lookupName( zDb = 0; }else{ for(i=0; inDb; i++){ - assert( db->aDb[i].zName ); - if( sqlite3StrICmp(db->aDb[i].zName,zDb)==0 ){ + assert( db->aDb[i].zDbSName ); + if( sqlite3StrICmp(db->aDb[i].zDbSName,zDb)==0 ){ pSchema = db->aDb[i].pSchema; break; } @@ -400,6 +400,10 @@ static int lookupName( sqlite3ErrorMsg(pParse, "misuse of aliased aggregate %s", zAs); return WRC_Abort; } + if( sqlite3ExprVectorSize(pOrig)!=1 ){ + sqlite3ErrorMsg(pParse, "row value misused"); + return WRC_Abort; + } resolveAlias(pParse, pEList, j, pExpr, "", nSubquery); cnt = 1; pMatch = 0; @@ -623,7 +627,6 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ /* if( pSrcList==0 ) break; */ notValid(pParse, pNC, "the \".\" operator", NC_IdxExpr); - /*notValid(pParse, pNC, "the \".\" operator", NC_PartIdx|NC_IsCheck, 1);*/ pRight = pExpr->pRight; if( pRight->op==TK_ID ){ zDb = 0; @@ -646,14 +649,12 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ int no_such_func = 0; /* True if no such function exists */ int wrong_num_args = 0; /* True if wrong number of arguments */ int is_agg = 0; /* True if is an aggregate function */ - int auth; /* Authorization to use the function */ int nId; /* Number of characters in function name */ const char *zId; /* The function name. */ FuncDef *pDef; /* Information about the function */ u8 enc = ENC(pParse->db); /* The database encoding */ assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); - notValid(pParse, pNC, "functions", NC_PartIdx); zId = pExpr->u.zToken; nId = sqlite3Strlen30(zId); pDef = sqlite3FindFunction(pParse->db, zId, n, enc, 0); @@ -690,15 +691,17 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ } } #ifndef SQLITE_OMIT_AUTHORIZATION - auth = sqlite3AuthCheck(pParse, SQLITE_FUNCTION, 0, pDef->zName, 0); - if( auth!=SQLITE_OK ){ - if( auth==SQLITE_DENY ){ - sqlite3ErrorMsg(pParse, "not authorized to use function: %s", - pDef->zName); - pNC->nErr++; + { + int auth = sqlite3AuthCheck(pParse, SQLITE_FUNCTION, 0,pDef->zName,0); + if( auth!=SQLITE_OK ){ + if( auth==SQLITE_DENY ){ + sqlite3ErrorMsg(pParse, "not authorized to use function: %s", + pDef->zName); + pNC->nErr++; + } + pExpr->op = TK_NULL; + return WRC_Prune; } - pExpr->op = TK_NULL; - return WRC_Prune; } #endif if( pDef->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG) ){ @@ -711,7 +714,8 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ /* Date/time functions that use 'now', and other functions like ** sqlite_version() that might change over time cannot be used ** in an index. */ - notValid(pParse, pNC, "non-deterministic functions", NC_IdxExpr); + notValid(pParse, pNC, "non-deterministic functions", + NC_IdxExpr|NC_PartIdx); } } if( is_agg && (pNC->ncFlags & NC_AllowAgg)==0 ){ @@ -776,6 +780,42 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ notValid(pParse, pNC, "parameters", NC_IsCheck|NC_PartIdx|NC_IdxExpr); break; } + case TK_BETWEEN: + case TK_EQ: + case TK_NE: + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: + case TK_IS: + case TK_ISNOT: { + int nLeft, nRight; + if( pParse->db->mallocFailed ) break; + assert( pExpr->pLeft!=0 ); + nLeft = sqlite3ExprVectorSize(pExpr->pLeft); + if( pExpr->op==TK_BETWEEN ){ + nRight = sqlite3ExprVectorSize(pExpr->x.pList->a[0].pExpr); + if( nRight==nLeft ){ + nRight = sqlite3ExprVectorSize(pExpr->x.pList->a[1].pExpr); + } + }else{ + assert( pExpr->pRight!=0 ); + nRight = sqlite3ExprVectorSize(pExpr->pRight); + } + if( nLeft!=nRight ){ + testcase( pExpr->op==TK_EQ ); + testcase( pExpr->op==TK_NE ); + testcase( pExpr->op==TK_LT ); + testcase( pExpr->op==TK_LE ); + testcase( pExpr->op==TK_GT ); + testcase( pExpr->op==TK_GE ); + testcase( pExpr->op==TK_IS ); + testcase( pExpr->op==TK_ISNOT ); + testcase( pExpr->op==TK_BETWEEN ); + sqlite3ErrorMsg(pParse, "row value misused"); + } + break; + } } return (pParse->nErr || pParse->db->mallocFailed) ? WRC_Abort : WRC_Continue; } diff --git a/src/select.c b/src/select.c index e21ac57dc5..75ae082e96 100644 --- a/src/select.c +++ b/src/select.c @@ -88,7 +88,7 @@ static void clearSelect(sqlite3 *db, Select *p, int bFree){ void sqlite3SelectDestInit(SelectDest *pDest, int eDest, int iParm){ pDest->eDest = (u8)eDest; pDest->iSDParm = iParm; - pDest->affSdst = 0; + pDest->zAffSdst = 0; pDest->iSdst = 0; pDest->nSdst = 0; } @@ -334,7 +334,7 @@ static void addWhereTerm( pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iColLeft); pE2 = sqlite3CreateColumnExpr(db, pSrc, iRight, iColRight); - pEq = sqlite3PExpr(pParse, TK_EQ, pE1, pE2, 0); + pEq = sqlite3PExpr(pParse, TK_EQ, pE1, pE2); if( pEq && isOuterJoin ){ ExprSetProperty(pEq, EP_FromJoin); assert( !ExprHasProperty(pEq, EP_TokenOnly|EP_Reduced) ); @@ -521,7 +521,7 @@ static void pushOntoSorter( int iLimit; /* LIMIT counter */ assert( bSeq==0 || bSeq==1 ); - assert( nData==1 || regData==regOrigData ); + assert( nData==1 || regData==regOrigData || regOrigData==0 ); if( nPrefixReg ){ assert( nPrefixReg==nExpr+bSeq ); regBase = regData - nExpr - bSeq; @@ -533,11 +533,11 @@ static void pushOntoSorter( iLimit = pSelect->iOffset ? pSelect->iOffset+1 : pSelect->iLimit; pSort->labelDone = sqlite3VdbeMakeLabel(v); sqlite3ExprCodeExprList(pParse, pSort->pOrderBy, regBase, regOrigData, - SQLITE_ECEL_DUP|SQLITE_ECEL_REF); + SQLITE_ECEL_DUP | (regOrigData? SQLITE_ECEL_REF : 0)); if( bSeq ){ sqlite3VdbeAddOp2(v, OP_Sequence, pSort->iECursor, regBase+nExpr); } - if( nPrefixReg==0 ){ + if( nPrefixReg==0 && nData>0 ){ sqlite3ExprCodeMove(pParse, regData, regBase+nExpr+bSeq, nData); } sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase+nOBSat, nBase-nOBSat, regRecord); @@ -587,7 +587,8 @@ static void pushOntoSorter( }else{ op = OP_IdxInsert; } - sqlite3VdbeAddOp2(v, op, pSort->iECursor, regRecord); + sqlite3VdbeAddOp4Int(v, op, pSort->iECursor, regRecord, + regBase+nOBSat, nBase-nOBSat); if( iLimit ){ int addr; int r1 = 0; @@ -595,7 +596,7 @@ static void pushOntoSorter( ** register is initialized with value of LIMIT+OFFSET.) After the sorter ** fills up, delete the least entry in the sorter after each insert. ** Thus we never hold more than the LIMIT+OFFSET rows in memory at once */ - addr = sqlite3VdbeAddOp3(v, OP_IfNotZero, iLimit, 0, 1); VdbeCoverage(v); + addr = sqlite3VdbeAddOp1(v, OP_IfNotZero, iLimit); VdbeCoverage(v); sqlite3VdbeAddOp1(v, OP_Last, pSort->iECursor); if( pSort->bOrderedInnerLoop ){ r1 = ++pParse->nMem; @@ -655,34 +656,10 @@ static void codeDistinct( r1 = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp4Int(v, OP_Found, iTab, addrRepeat, iMem, N); VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_MakeRecord, iMem, N, r1); - sqlite3VdbeAddOp2(v, OP_IdxInsert, iTab, r1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iTab, r1, iMem, N); sqlite3ReleaseTempReg(pParse, r1); } -#ifndef SQLITE_OMIT_SUBQUERY -/* -** Generate an error message when a SELECT is used within a subexpression -** (example: "a IN (SELECT * FROM table)") but it has more than 1 result -** column. We do this in a subroutine because the error used to occur -** in multiple places. (The error only occurs in one place now, but we -** retain the subroutine to minimize code disruption.) -*/ -static int checkForMultiColumnSelectError( - Parse *pParse, /* Parse context. */ - SelectDest *pDest, /* Destination of SELECT results */ - int nExpr /* Number of result columns returned by SELECT */ -){ - int eDest = pDest->eDest; - if( nExpr>1 && (eDest==SRT_Mem || eDest==SRT_Set) ){ - sqlite3ErrorMsg(pParse, "only a single result allowed for " - "a SELECT that is part of an expression"); - return 1; - }else{ - return 0; - } -} -#endif - /* ** This routine generates the code for the inside of the inner loop ** of a SELECT. @@ -690,7 +667,7 @@ static int checkForMultiColumnSelectError( ** If srcTab is negative, then the pEList expressions ** are evaluated in order to get the data for this row. If srcTab is ** zero or more, then data is pulled from srcTab and pEList is used only -** to get number columns and the datatype for each column. +** to get the number of columns and the collation sequence for each column. */ static void selectInnerLoop( Parse *pParse, /* The parser context */ @@ -705,13 +682,20 @@ static void selectInnerLoop( ){ Vdbe *v = pParse->pVdbe; int i; - int hasDistinct; /* True if the DISTINCT keyword is present */ - int regResult; /* Start of memory holding result set */ + int hasDistinct; /* True if the DISTINCT keyword is present */ int eDest = pDest->eDest; /* How to dispose of results */ int iParm = pDest->iSDParm; /* First argument to disposal method */ int nResultCol; /* Number of result columns */ int nPrefixReg = 0; /* Number of extra registers before regResult */ + /* Usually, regResult is the first cell in an array of memory cells + ** containing the current result row. In this case regOrig is set to the + ** same value. However, if the results are being sent to the sorter, the + ** values for any expressions that are also part of the sort-key are omitted + ** from this array. In this case regOrig is set to zero. */ + int regResult; /* Start of memory holding current results */ + int regOrig; /* Start of memory holding full result (or 0) */ + assert( v ); assert( pEList!=0 ); hasDistinct = pDistinct ? pDistinct->eTnctType : WHERE_DISTINCT_NOOP; @@ -742,7 +726,7 @@ static void selectInnerLoop( pParse->nMem += nResultCol; } pDest->nSdst = nResultCol; - regResult = pDest->iSdst; + regOrig = regResult = pDest->iSdst; if( srcTab>=0 ){ for(i=0; ipOrderBy), set the associated + ** iOrderByCol value to one more than the index of the ORDER BY + ** expression within the sort-key that pushOntoSorter() will generate. + ** This allows the pEList field to be omitted from the sorted record, + ** saving space and CPU cycles. */ + ecelFlags |= (SQLITE_ECEL_OMITREF|SQLITE_ECEL_REF); + for(i=pSort->nOBSat; ipOrderBy->nExpr; i++){ + int j; + if( (j = pSort->pOrderBy->a[i].u.x.iOrderByCol)>0 ){ + pEList->a[j-1].u.x.iOrderByCol = i+1-pSort->nOBSat; + } + } + regOrig = 0; + assert( eDest==SRT_Set || eDest==SRT_Mem + || eDest==SRT_Coroutine || eDest==SRT_Output ); + } + nResultCol = sqlite3ExprCodeExprList(pParse,pEList,regResult,0,ecelFlags); } /* If the DISTINCT keyword was present on the SELECT statement @@ -832,7 +834,7 @@ static void selectInnerLoop( int r1; r1 = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1); - sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regResult, nResultCol); sqlite3ReleaseTempReg(pParse, r1); break; } @@ -869,7 +871,7 @@ static void selectInnerLoop( int addr = sqlite3VdbeCurrentAddr(v) + 4; sqlite3VdbeAddOp4Int(v, OP_Found, iParm+1, addr, r1, 0); VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm+1, r1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm+1, r1,regResult,nResultCol); assert( pSort==0 ); } #endif @@ -892,20 +894,20 @@ static void selectInnerLoop( ** item into the set table with bogus data. */ case SRT_Set: { - assert( nResultCol==1 ); - pDest->affSdst = - sqlite3CompareAffinity(pEList->a[0].pExpr, pDest->affSdst); if( pSort ){ /* At first glance you would think we could optimize out the ** ORDER BY in this case since the order of entries in the set ** does not matter. But there might be a LIMIT clause, in which ** case the order does matter */ - pushOntoSorter(pParse, pSort, p, regResult, regResult, 1, nPrefixReg); + pushOntoSorter( + pParse, pSort, p, regResult, regOrig, nResultCol, nPrefixReg); }else{ int r1 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult,1,r1, &pDest->affSdst, 1); - sqlite3ExprCacheAffinityChange(pParse, regResult, 1); - sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1); + assert( sqlite3Strlen30(pDest->zAffSdst)==nResultCol ); + sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult, nResultCol, + r1, pDest->zAffSdst, nResultCol); + sqlite3ExprCacheAffinityChange(pParse, regResult, nResultCol); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regResult, nResultCol); sqlite3ReleaseTempReg(pParse, r1); } break; @@ -920,14 +922,16 @@ static void selectInnerLoop( } /* If this is a scalar select that is part of an expression, then - ** store the results in the appropriate memory cell and break out - ** of the scan loop. + ** store the results in the appropriate memory cell or array of + ** memory cells and break out of the scan loop. */ case SRT_Mem: { - assert( nResultCol==1 ); if( pSort ){ - pushOntoSorter(pParse, pSort, p, regResult, regResult, 1, nPrefixReg); + assert( nResultCol<=pDest->nSdst ); + pushOntoSorter( + pParse, pSort, p, regResult, regOrig, nResultCol, nPrefixReg); }else{ + assert( nResultCol==pDest->nSdst ); assert( regResult==iParm ); /* The LIMIT clause will jump out of the loop for us */ } @@ -940,7 +944,7 @@ static void selectInnerLoop( testcase( eDest==SRT_Coroutine ); testcase( eDest==SRT_Output ); if( pSort ){ - pushOntoSorter(pParse, pSort, p, regResult, regResult, nResultCol, + pushOntoSorter(pParse, pSort, p, regResult, regOrig, nResultCol, nPrefixReg); }else if( eDest==SRT_Coroutine ){ sqlite3VdbeAddOp1(v, OP_Yield, pDest->iSDParm); @@ -990,7 +994,7 @@ static void selectInnerLoop( } sqlite3VdbeAddOp2(v, OP_Sequence, iParm, r2+nKey); sqlite3VdbeAddOp3(v, OP_MakeRecord, r2, nKey+2, r1); - sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, r2, nKey+2); if( addrTest ) sqlite3VdbeJumpHere(v, addrTest); sqlite3ReleaseTempReg(pParse, r1); sqlite3ReleaseTempRange(pParse, r2, nKey+2); @@ -1028,7 +1032,7 @@ static void selectInnerLoop( */ KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){ int nExtra = (N+X)*(sizeof(CollSeq*)+1); - KeyInfo *p = sqlite3DbMallocRaw(db, sizeof(KeyInfo) + nExtra); + KeyInfo *p = sqlite3DbMallocRawNN(db, sizeof(KeyInfo) + nExtra); if( p ){ p->aSortOrder = (u8*)&p->aColl[N+X]; p->nField = (u16)N; @@ -1225,14 +1229,13 @@ static void generateSortTail( int iParm = pDest->iSDParm; int regRow; int regRowid; + int iCol; int nKey; int iSortTab; /* Sorter cursor to read from */ int nSortData; /* Trailing values to read from sorter */ int i; int bSeq; /* True if sorter record includes seq. no. */ -#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS struct ExprList_item *aOutEx = p->pEList->a; -#endif assert( addrBreak<0 ); if( pSort->labelBkOut ){ @@ -1241,21 +1244,21 @@ static void generateSortTail( sqlite3VdbeResolveLabel(v, pSort->labelBkOut); } iTab = pSort->iECursor; - if( eDest==SRT_Output || eDest==SRT_Coroutine ){ + if( eDest==SRT_Output || eDest==SRT_Coroutine || eDest==SRT_Mem ){ regRowid = 0; regRow = pDest->iSdst; nSortData = nColumn; }else{ regRowid = sqlite3GetTempReg(pParse); - regRow = sqlite3GetTempReg(pParse); - nSortData = 1; + regRow = sqlite3GetTempRange(pParse, nColumn); + nSortData = nColumn; } nKey = pOrderBy->nExpr - pSort->nOBSat; if( pSort->sortFlags & SORTFLAG_UseSorter ){ int regSortOut = ++pParse->nMem; iSortTab = pParse->nTab++; if( pSort->labelBkOut ){ - addrOnce = sqlite3CodeOnce(pParse); VdbeCoverage(v); + addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } sqlite3VdbeAddOp3(v, OP_OpenPseudo, iSortTab, regSortOut, nKey+1+nSortData); if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce); @@ -1270,11 +1273,18 @@ static void generateSortTail( iSortTab = iTab; bSeq = 1; } - for(i=0; iaffSdst, 1); - sqlite3ExprCacheAffinityChange(pParse, regRow, 1); - sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, regRowid); + assert( nColumn==sqlite3Strlen30(pDest->zAffSdst) ); + sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, nColumn, regRowid, + pDest->zAffSdst, nColumn); + sqlite3ExprCacheAffinityChange(pParse, regRow, nColumn); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, regRowid, regRow, nColumn); break; } case SRT_Mem: { - assert( nColumn==1 ); - sqlite3ExprCodeMove(pParse, regRow, iParm, 1); /* The LIMIT clause will terminate the loop for us */ break; } @@ -1311,7 +1319,11 @@ static void generateSortTail( } } if( regRowid ){ - sqlite3ReleaseTempReg(pParse, regRow); + if( eDest==SRT_Set ){ + sqlite3ReleaseTempRange(pParse, regRow, nColumn); + }else{ + sqlite3ReleaseTempReg(pParse, regRow); + } sqlite3ReleaseTempReg(pParse, regRowid); } /* The bottom of the loop @@ -1458,7 +1470,7 @@ static const char *columnTypeImpl( zOrigTab = pTab->zName; if( pNC->pParse ){ int iDb = sqlite3SchemaToIndex(pNC->pParse->db, pTab->pSchema); - zOrigDb = pNC->pParse->db->aDb[iDb].zName; + zOrigDb = pNC->pParse->db->aDb[iDb].zDbSName; } #else if( iCol<0 ){ @@ -1794,7 +1806,7 @@ Table *sqlite3ResultSetOfSelect(Parse *pParse, Select *pSelect){ /* The sqlite3ResultSetOfSelect() is only used n contexts where lookaside ** is disabled */ assert( db->lookaside.bDisable ); - pTab->nRef = 1; + pTab->nTabRef = 1; pTab->zName = 0; pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); sqlite3ColumnsFromExprList(pParse, pSelect->pEList, &pTab->nCol, &pTab->aCol); @@ -1813,7 +1825,7 @@ Table *sqlite3ResultSetOfSelect(Parse *pParse, Select *pSelect){ */ static SQLITE_NOINLINE Vdbe *allocVdbe(Parse *pParse){ Vdbe *v = pParse->pVdbe = sqlite3VdbeCreate(pParse); - if( v ) sqlite3VdbeAddOp0(v, OP_Init); + if( v ) sqlite3VdbeAddOp2(v, OP_Init, 0, 1); if( pParse->pToplevel==0 && OptimizationEnabled(pParse->db,SQLITE_FactorOutConst) ){ @@ -2025,6 +2037,7 @@ static void generateWithRecursiveQuery( /* Process the LIMIT and OFFSET clauses, if they exist */ addrBreak = sqlite3VdbeMakeLabel(v); + p->nSelectRow = 320; /* 4 billion rows */ computeLimitRegisters(pParse, p, addrBreak); pLimit = p->pLimit; pOffset = p->pOffset; @@ -2494,7 +2507,7 @@ static int multiSelect( computeLimitRegisters(pParse, p, iBreak); sqlite3VdbeAddOp2(v, OP_Rewind, tab1, iBreak); VdbeCoverage(v); r1 = sqlite3GetTempReg(pParse); - iStart = sqlite3VdbeAddOp2(v, OP_RowKey, tab1, r1); + iStart = sqlite3VdbeAddOp2(v, OP_RowData, tab1, r1); sqlite3VdbeAddOp4Int(v, OP_NotFound, tab2, iCont, r1, 0); VdbeCoverage(v); sqlite3ReleaseTempReg(pParse, r1); selectInnerLoop(pParse, p, p->pEList, tab1, @@ -2652,19 +2665,17 @@ static int generateOutputSubroutine( } #ifndef SQLITE_OMIT_SUBQUERY - /* If we are creating a set for an "expr IN (SELECT ...)" construct, - ** then there should be a single item on the stack. Write this - ** item into the set table with bogus data. + /* If we are creating a set for an "expr IN (SELECT ...)". */ case SRT_Set: { int r1; - assert( pIn->nSdst==1 || pParse->nErr>0 ); - pDest->affSdst = - sqlite3CompareAffinity(p->pEList->a[0].pExpr, pDest->affSdst); + testcase( pIn->nSdst>1 ); r1 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp4(v, OP_MakeRecord, pIn->iSdst, 1, r1, &pDest->affSdst,1); - sqlite3ExprCacheAffinityChange(pParse, pIn->iSdst, 1); - sqlite3VdbeAddOp2(v, OP_IdxInsert, pDest->iSDParm, r1); + sqlite3VdbeAddOp4(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst, + r1, pDest->zAffSdst, pIn->nSdst); + sqlite3ExprCacheAffinityChange(pParse, pIn->iSdst, pIn->nSdst); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, pDest->iSDParm, r1, + pIn->iSdst, pIn->nSdst); sqlite3ReleaseTempReg(pParse, r1); break; } @@ -3123,8 +3134,8 @@ static int multiSelectOrderBy( #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) /* Forward Declarations */ -static void substExprList(sqlite3*, ExprList*, int, ExprList*); -static void substSelect(sqlite3*, Select *, int, ExprList*, int); +static void substExprList(Parse*, ExprList*, int, ExprList*); +static void substSelect(Parse*, Select *, int, ExprList*, int); /* ** Scan through the expression pExpr. Replace every reference to @@ -3140,36 +3151,46 @@ static void substSelect(sqlite3*, Select *, int, ExprList*, int); ** of the subquery rather the result set of the subquery. */ static Expr *substExpr( - sqlite3 *db, /* Report malloc errors to this connection */ + Parse *pParse, /* Report errors here */ Expr *pExpr, /* Expr in which substitution occurs */ int iTable, /* Table to be substituted */ ExprList *pEList /* Substitute expressions */ ){ + sqlite3 *db = pParse->db; if( pExpr==0 ) return 0; if( pExpr->op==TK_COLUMN && pExpr->iTable==iTable ){ if( pExpr->iColumn<0 ){ pExpr->op = TK_NULL; }else{ Expr *pNew; + Expr *pCopy = pEList->a[pExpr->iColumn].pExpr; assert( pEList!=0 && pExpr->iColumnnExpr ); assert( pExpr->pLeft==0 && pExpr->pRight==0 ); - pNew = sqlite3ExprDup(db, pEList->a[pExpr->iColumn].pExpr, 0); - sqlite3ExprDelete(db, pExpr); - pExpr = pNew; + if( sqlite3ExprIsVector(pCopy) ){ + sqlite3VectorErrorMsg(pParse, pCopy); + }else{ + pNew = sqlite3ExprDup(db, pCopy, 0); + if( pNew && (pExpr->flags & EP_FromJoin) ){ + pNew->iRightJoinTable = pExpr->iRightJoinTable; + pNew->flags |= EP_FromJoin; + } + sqlite3ExprDelete(db, pExpr); + pExpr = pNew; + } } }else{ - pExpr->pLeft = substExpr(db, pExpr->pLeft, iTable, pEList); - pExpr->pRight = substExpr(db, pExpr->pRight, iTable, pEList); + pExpr->pLeft = substExpr(pParse, pExpr->pLeft, iTable, pEList); + pExpr->pRight = substExpr(pParse, pExpr->pRight, iTable, pEList); if( ExprHasProperty(pExpr, EP_xIsSelect) ){ - substSelect(db, pExpr->x.pSelect, iTable, pEList, 1); + substSelect(pParse, pExpr->x.pSelect, iTable, pEList, 1); }else{ - substExprList(db, pExpr->x.pList, iTable, pEList); + substExprList(pParse, pExpr->x.pList, iTable, pEList); } } return pExpr; } static void substExprList( - sqlite3 *db, /* Report malloc errors here */ + Parse *pParse, /* Report errors here */ ExprList *pList, /* List to scan and in which to make substitutes */ int iTable, /* Table to be substituted */ ExprList *pEList /* Substitute values */ @@ -3177,11 +3198,11 @@ static void substExprList( int i; if( pList==0 ) return; for(i=0; inExpr; i++){ - pList->a[i].pExpr = substExpr(db, pList->a[i].pExpr, iTable, pEList); + pList->a[i].pExpr = substExpr(pParse, pList->a[i].pExpr, iTable, pEList); } } static void substSelect( - sqlite3 *db, /* Report malloc errors here */ + Parse *pParse, /* Report errors here */ Select *p, /* SELECT statement in which to make substitutions */ int iTable, /* Table to be replaced */ ExprList *pEList, /* Substitute values */ @@ -3192,17 +3213,17 @@ static void substSelect( int i; if( !p ) return; do{ - substExprList(db, p->pEList, iTable, pEList); - substExprList(db, p->pGroupBy, iTable, pEList); - substExprList(db, p->pOrderBy, iTable, pEList); - p->pHaving = substExpr(db, p->pHaving, iTable, pEList); - p->pWhere = substExpr(db, p->pWhere, iTable, pEList); + substExprList(pParse, p->pEList, iTable, pEList); + substExprList(pParse, p->pGroupBy, iTable, pEList); + substExprList(pParse, p->pOrderBy, iTable, pEList); + p->pHaving = substExpr(pParse, p->pHaving, iTable, pEList); + p->pWhere = substExpr(pParse, p->pWhere, iTable, pEList); pSrc = p->pSrc; assert( pSrc!=0 ); for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){ - substSelect(db, pItem->pSelect, iTable, pEList, 1); + substSelect(pParse, pItem->pSelect, iTable, pEList, 1); if( pItem->fg.isTabFunc ){ - substExprList(db, pItem->u1.pFuncArg, iTable, pEList); + substExprList(pParse, pItem->u1.pFuncArg, iTable, pEList); } } }while( doPrior && (p = p->pPrior)!=0 ); @@ -3599,12 +3620,12 @@ static int flattenSubquery( */ if( ALWAYS(pSubitem->pTab!=0) ){ Table *pTabToDel = pSubitem->pTab; - if( pTabToDel->nRef==1 ){ + if( pTabToDel->nTabRef==1 ){ Parse *pToplevel = sqlite3ParseToplevel(pParse); pTabToDel->pNextZombie = pToplevel->pZombieTab; pToplevel->pZombieTab = pTabToDel; }else{ - pTabToDel->nRef--; + pTabToDel->nTabRef--; } pSubitem->pTab = 0; } @@ -3719,14 +3740,15 @@ static int flattenSubquery( assert( pParent->pHaving==0 ); pParent->pHaving = pParent->pWhere; pParent->pWhere = pWhere; - pParent->pHaving = sqlite3ExprAnd(db, pParent->pHaving, - sqlite3ExprDup(db, pSub->pHaving, 0)); + pParent->pHaving = sqlite3ExprAnd(db, + sqlite3ExprDup(db, pSub->pHaving, 0), pParent->pHaving + ); assert( pParent->pGroupBy==0 ); pParent->pGroupBy = sqlite3ExprListDup(db, pSub->pGroupBy, 0); }else{ - pParent->pWhere = sqlite3ExprAnd(db, pParent->pWhere, pWhere); + pParent->pWhere = sqlite3ExprAnd(db, pWhere, pParent->pWhere); } - substSelect(db, pParent, iParent, pSub->pEList, 0); + substSelect(pParse, pParent, iParent, pSub->pEList, 0); /* The flattened query is distinct if either the inner or the ** outer query is distinct. @@ -3800,7 +3822,7 @@ static int flattenSubquery( ** terms are duplicated into the subquery. */ static int pushDownWhereTerms( - sqlite3 *db, /* The database connection (for malloc()) */ + Parse *pParse, /* Parse context (for malloc() and error reporting) */ Select *pSubq, /* The subquery whose WHERE clause is to be augmented */ Expr *pWhere, /* The WHERE clause of the outer query */ int iCursor /* Cursor number of the subquery */ @@ -3821,16 +3843,16 @@ static int pushDownWhereTerms( return 0; /* restriction (3) */ } while( pWhere->op==TK_AND ){ - nChng += pushDownWhereTerms(db, pSubq, pWhere->pRight, iCursor); + nChng += pushDownWhereTerms(pParse, pSubq, pWhere->pRight, iCursor); pWhere = pWhere->pLeft; } if( ExprHasProperty(pWhere,EP_FromJoin) ) return 0; /* restriction 5 */ if( sqlite3ExprIsTableConstant(pWhere, iCursor) ){ nChng++; while( pSubq ){ - pNew = sqlite3ExprDup(db, pWhere, 0); - pNew = substExpr(db, pNew, iCursor, pSubq->pEList); - pSubq->pWhere = sqlite3ExprAnd(db, pSubq->pWhere, pNew); + pNew = sqlite3ExprDup(pParse->db, pWhere, 0); + pNew = substExpr(pParse, pNew, iCursor, pSubq->pEList); + pSubq->pWhere = sqlite3ExprAnd(pParse->db, pSubq->pWhere, pNew); pSubq = pSubq->pPrior; } } @@ -4122,7 +4144,7 @@ static int withExpand( assert( pFrom->pTab==0 ); pFrom->pTab = pTab = sqlite3DbMallocZero(db, sizeof(Table)); if( pTab==0 ) return WRC_Abort; - pTab->nRef = 1; + pTab->nTabRef = 1; pTab->zName = sqlite3DbStrDup(db, pCte->zName); pTab->iPKey = -1; pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); @@ -4145,20 +4167,20 @@ static int withExpand( ){ pItem->pTab = pTab; pItem->fg.isRecursive = 1; - pTab->nRef++; + pTab->nTabRef++; pSel->selFlags |= SF_Recursive; } } } /* Only one recursive reference is permitted. */ - if( pTab->nRef>2 ){ + if( pTab->nTabRef>2 ){ sqlite3ErrorMsg( pParse, "multiple references to recursive table: %s", pCte->zName ); return SQLITE_ERROR; } - assert( pTab->nRef==1 || ((pSel->selFlags&SF_Recursive) && pTab->nRef==2 )); + assert( pTab->nTabRef==1 || ((pSel->selFlags&SF_Recursive) && pTab->nTabRef==2 )); pCte->zCteErr = "circular reference: %s"; pSavedWith = pParse->pWith; @@ -4291,7 +4313,7 @@ static int selectExpander(Walker *pWalker, Select *p){ if( sqlite3WalkSelect(pWalker, pSel) ) return WRC_Abort; pFrom->pTab = pTab = sqlite3DbMallocZero(db, sizeof(Table)); if( pTab==0 ) return WRC_Abort; - pTab->nRef = 1; + pTab->nTabRef = 1; pTab->zName = sqlite3MPrintf(db, "sqlite_sq_%p", (void*)pTab); while( pSel->pPrior ){ pSel = pSel->pPrior; } sqlite3ColumnsFromExprList(pParse, pSel->pEList,&pTab->nCol,&pTab->aCol); @@ -4304,13 +4326,13 @@ static int selectExpander(Walker *pWalker, Select *p){ assert( pFrom->pTab==0 ); pFrom->pTab = pTab = sqlite3LocateTableItem(pParse, 0, pFrom); if( pTab==0 ) return WRC_Abort; - if( pTab->nRef==0xffff ){ + if( pTab->nTabRef>=0xffff ){ sqlite3ErrorMsg(pParse, "too many references to \"%s\": max 65535", pTab->zName); pFrom->pTab = 0; return WRC_Abort; } - pTab->nRef++; + pTab->nTabRef++; if( !IsVirtual(pTab) && cannotBeFunction(pParse, pFrom) ){ return WRC_Abort; } @@ -4414,7 +4436,7 @@ static int selectExpander(Walker *pWalker, Select *p){ continue; } iDb = sqlite3SchemaToIndex(db, pTab->pSchema); - zSchemaName = iDb>=0 ? db->aDb[iDb].zName : "*"; + zSchemaName = iDb>=0 ? db->aDb[iDb].zDbSName : "*"; } for(j=0; jnCol; j++){ char *zName = pTab->aCol[j].zName; @@ -4460,10 +4482,10 @@ static int selectExpander(Walker *pWalker, Select *p){ if( longNames || pTabList->nSrc>1 ){ Expr *pLeft; pLeft = sqlite3Expr(db, TK_ID, zTabName); - pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight, 0); + pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight); if( zSchemaName ){ pLeft = sqlite3Expr(db, TK_ID, zSchemaName); - pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pExpr, 0); + pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pExpr); } if( longNames ){ zColname = sqlite3MPrintf(db, "%s.%s", zTabName, zName); @@ -4700,8 +4722,8 @@ static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){ for(i=0, pF=pAggInfo->aFunc; inFunc; i++, pF++){ ExprList *pList = pF->pExpr->x.pList; assert( !ExprHasProperty(pF->pExpr, EP_xIsSelect) ); - sqlite3VdbeAddOp4(v, OP_AggFinal, pF->iMem, pList ? pList->nExpr : 0, 0, - (void*)pF->pFunc, P4_FUNCDEF); + sqlite3VdbeAddOp2(v, OP_AggFinal, pF->iMem, pList ? pList->nExpr : 0); + sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); } } @@ -4752,8 +4774,8 @@ static void updateAccumulator(Parse *pParse, AggInfo *pAggInfo){ if( regHit==0 && pAggInfo->nAccumulator ) regHit = ++pParse->nMem; sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0, (char *)pColl, P4_COLLSEQ); } - sqlite3VdbeAddOp4(v, OP_AggStep0, 0, regAgg, pF->iMem, - (void*)pF->pFunc, P4_FUNCDEF); + sqlite3VdbeAddOp3(v, OP_AggStep0, 0, regAgg, pF->iMem); + sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); sqlite3VdbeChangeP5(v, (u8)nArg); sqlite3ExprCacheAffinityChange(pParse, regAgg, nArg); sqlite3ReleaseTempRange(pParse, regAgg, nArg); @@ -4897,16 +4919,6 @@ int sqlite3Select( } #endif - - /* If writing to memory or generating a set - ** only a single column may be output. - */ -#ifndef SQLITE_OMIT_SUBQUERY - if( checkForMultiColumnSelectError(pParse, pDest, p->pEList->nExpr) ){ - goto select_end; - } -#endif - /* Try to flatten subqueries in the FROM clause up into the main query */ #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) @@ -4997,7 +5009,7 @@ int sqlite3Select( ** inside the subquery. This can help the subquery to run more efficiently. */ if( (pItem->fg.jointype & JT_OUTER)==0 - && pushDownWhereTerms(db, pSub, p->pWhere, pItem->iCursor) + && pushDownWhereTerms(pParse, pSub, p->pWhere, pItem->iCursor) ){ #if SELECTTRACE_ENABLED if( sqlite3SelectTrace & 0x100 ){ @@ -5061,7 +5073,7 @@ int sqlite3Select( /* If the subquery is not correlated and if we are not inside of ** a trigger, then we only need to compute the value of the subquery ** once. */ - onceAddr = sqlite3CodeOnce(pParse); VdbeCoverage(v); + onceAddr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); VdbeComment((v, "materialize \"%s\"", pItem->pTab->zName)); }else{ VdbeNoopComment((v, "materialize \"%s\"", pItem->pTab->zName)); @@ -5159,7 +5171,9 @@ int sqlite3Select( /* Set the limiter. */ iEnd = sqlite3VdbeMakeLabel(v); - p->nSelectRow = 320; /* 4 billion rows */ + if( (p->selFlags & SF_FixedLimit)==0 ){ + p->nSelectRow = 320; /* 4 billion rows */ + } computeLimitRegisters(pParse, p, iEnd); if( p->iLimit==0 && sSort.addrSortIndex>=0 ){ sqlite3VdbeChangeOpcode(v, sSort.addrSortIndex, OP_SorterOpen); diff --git a/src/shell.c b/src/shell.c index 18c6ef7197..b0928ce792 100644 --- a/src/shell.c +++ b/src/shell.c @@ -143,6 +143,7 @@ extern char *sqlite3_win32_unicode_to_utf8(LPCWSTR); extern char *sqlite3_win32_mbcs_to_utf8_v2(const char *, int); extern char *sqlite3_win32_utf8_to_mbcs_v2(const char *, int); +extern LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText); #endif /* On Windows, we normally run with output mode of TEXT so that \n characters @@ -524,7 +525,7 @@ static char *local_getline(char *zLine, FILE *in){ #if defined(_WIN32) || defined(WIN32) /* For interactive input on Windows systems, translate the ** multi-byte characterset characters into UTF-8. */ - if( stdin_is_interactive ){ + if( stdin_is_interactive && in==stdin ){ char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0); if( zTrans ){ int nTrans = strlen30(zTrans)+1; @@ -626,8 +627,10 @@ struct ShellState { int normalMode; /* Output mode before ".explain on" */ int writableSchema; /* True if PRAGMA writable_schema=ON */ int showHeader; /* True to show column names in List or Column mode */ + int nCheck; /* Number of ".check" commands run */ unsigned shellFlgs; /* Various flags */ char *zDestTable; /* Name of destination table when MODE_Insert */ + char zTestcase[30]; /* Name of current test case */ char colSeparator[20]; /* Column separator character for several modes */ char rowSeparator[20]; /* Row separator character for MODE_Ascii */ int colWidth[100]; /* Requested width of each column when in column mode*/ @@ -665,11 +668,12 @@ struct ShellState { #define MODE_Semi 3 /* Same as MODE_List but append ";" to each line */ #define MODE_Html 4 /* Generate an XHTML table */ #define MODE_Insert 5 /* Generate SQL "insert" statements */ -#define MODE_Tcl 6 /* Generate ANSI-C or TCL quoted elements */ -#define MODE_Csv 7 /* Quote strings, numbers are plain */ -#define MODE_Explain 8 /* Like MODE_Column, but do not truncate data */ -#define MODE_Ascii 9 /* Use ASCII unit and record separators (0x1F/0x1E) */ -#define MODE_Pretty 10 /* Pretty-print schemas */ +#define MODE_Quote 6 /* Quote values as for SQL */ +#define MODE_Tcl 7 /* Generate ANSI-C or TCL quoted elements */ +#define MODE_Csv 8 /* Quote strings, numbers are plain */ +#define MODE_Explain 9 /* Like MODE_Column, but do not truncate data */ +#define MODE_Ascii 10 /* Use ASCII unit and record separators (0x1F/0x1E) */ +#define MODE_Pretty 11 /* Pretty-print schemas */ static const char *modeDescr[] = { "line", @@ -678,6 +682,7 @@ static const char *modeDescr[] = { "semi", "html", "insert", + "quote", "tcl", "csv", "explain", @@ -894,6 +899,7 @@ static void interrupt_handler(int NotUsed){ } #endif +#ifndef SQLITE_OMIT_AUTHORIZATION /* ** When the ".auth ON" is set, the following authorizer callback is ** invoked. It always returns SQLITE_OK. @@ -926,7 +932,7 @@ static int shellAuth( az[1] = zA2; az[2] = zA3; az[3] = zA4; - raw_printf(p->out, "authorizer: %s", azAction[op]); + utf8_printf(p->out, "authorizer: %s", azAction[op]); for(i=0; i<4; i++){ raw_printf(p->out, " "); if( az[i] ){ @@ -938,7 +944,27 @@ static int shellAuth( raw_printf(p->out, "\n"); return SQLITE_OK; } - +#endif + +/* +** Print a schema statement. Part of MODE_Semi and MODE_Pretty output. +** +** This routine converts some CREATE TABLE statements for shadow tables +** in FTS3/4/5 into CREATE TABLE IF NOT EXISTS statements. +*/ +static void printSchemaLine(FILE *out, const char *z, const char *zTail){ + if( sqlite3_strglob("CREATE TABLE ['\"]*", z)==0 ){ + utf8_printf(out, "CREATE TABLE IF NOT EXISTS %s%s", z+13, zTail); + }else{ + utf8_printf(out, "%s%s", z, zTail); + } +} +static void printSchemaLineN(FILE *out, char *z, int n, const char *zTail){ + char c = z[n]; + z[n] = 0; + printSchemaLine(out, z, zTail); + z[n] = c; +} /* ** This is the callback routine that the shell @@ -1057,7 +1083,7 @@ static int shell_callback( break; } case MODE_Semi: { /* .schema and .fullschema output */ - utf8_printf(p->out, "%s;\n", azArg[0]); + printSchemaLine(p->out, azArg[0], ";\n"); break; } case MODE_Pretty: { /* .schema and .fullschema with --indent */ @@ -1101,14 +1127,14 @@ static int shell_callback( }else if( c==')' ){ nParen--; if( nLine>0 && nParen==0 && j>0 ){ - utf8_printf(p->out, "%.*s\n", j, z); + printSchemaLineN(p->out, z, j, "\n"); j = 0; } } z[j++] = c; if( nParen==1 && (c=='(' || c==',' || c=='\n') ){ if( c=='\n' ) j--; - utf8_printf(p->out, "%.*s\n ", j, z); + printSchemaLineN(p->out, z, j, "\n "); j = 0; nLine++; while( IsSpace(z[i+1]) ){ i++; } @@ -1116,7 +1142,7 @@ static int shell_callback( } z[j] = 0; } - utf8_printf(p->out, "%s;\n", z); + printSchemaLine(p->out, z, ";\n"); sqlite3_free(z); break; } @@ -1193,19 +1219,28 @@ static int shell_callback( setTextMode(p->out, 1); break; } + case MODE_Quote: case MODE_Insert: { - p->cnt++; if( azArg==0 ) break; - utf8_printf(p->out,"INSERT INTO %s",p->zDestTable); - if( p->showHeader ){ - raw_printf(p->out,"("); - for(i=0; i0 ? ",": ""; - utf8_printf(p->out, "%s%s", zSep, azCol[i]); + if( p->cMode==MODE_Insert ){ + utf8_printf(p->out,"INSERT INTO %s",p->zDestTable); + if( p->showHeader ){ + raw_printf(p->out,"("); + for(i=0; i0 ? ",": ""; + utf8_printf(p->out, "%s%s", zSep, azCol[i]); + } + raw_printf(p->out,")"); } - raw_printf(p->out,")"); + raw_printf(p->out," VALUES("); + }else if( p->cnt==0 && p->showHeader ){ + for(i=0; i0 ) raw_printf(p->out, ","); + output_quoted_string(p->out, azCol[i]); + } + raw_printf(p->out,"\n"); } - raw_printf(p->out," VALUES("); + p->cnt++; for(i=0; i0 ? ",": ""; if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ @@ -1228,7 +1263,7 @@ static int shell_callback( output_quoted_string(p->out, azArg[i]); } } - raw_printf(p->out,");\n"); + raw_printf(p->out,p->cMode==MODE_Quote?"\n":");\n"); break; } case MODE_Ascii: { @@ -1442,7 +1477,7 @@ static void displayLinuxIoStats(FILE *out){ for(i=0; iout, "%s;\n", zSql); + printSchemaLine(p->out, zSql, ";\n"); } if( strcmp(zType, "table")==0 ){ @@ -2131,11 +2167,14 @@ static int run_schema_dump_query( ** Text of a help message */ static char zHelp[] = +#ifndef SQLITE_OMIT_AUTHORIZATION ".auth ON|OFF Show authorizer callbacks\n" +#endif ".backup ?DB? FILE Backup DB (default \"main\") to FILE\n" ".bail on|off Stop after hitting an error. Default OFF\n" ".binary on|off Turn binary output on or off. Default OFF\n" ".changes on|off Show number of rows changed by SQL\n" + ".check GLOB Fail if output since .testcase does not match\n" ".clone NEWDB Clone data into NEWDB from the existing database\n" ".databases List names and files of attached databases\n" ".dbinfo ?DB? Show status information about the database\n" @@ -2150,6 +2189,9 @@ static char zHelp[] = ".headers on|off Turn display of headers on or off\n" ".help Show this message\n" ".import FILE TABLE Import data from FILE into TABLE\n" +#ifndef SQLITE_OMIT_TEST_CONTROL + ".imposter INDEX TABLE Create imposter table TABLE on index INDEX\n" +#endif ".indexes ?TABLE? Show names of all indexes\n" " If TABLE specified, only show indexes for tables\n" " matching LIKE pattern TABLE.\n" @@ -2157,6 +2199,8 @@ static char zHelp[] = ".iotrace FILE Enable I/O diagnostic logging to FILE\n" #endif ".limit ?LIMIT? ?VAL? Display or change the value of an SQLITE_LIMIT\n" + ".lint OPTIONS Report potential schema issues. Options:\n" + " fkey-indexes Find missing foreign key indexes\n" #ifndef SQLITE_OMIT_LOAD_EXTENSION ".load FILE ?ENTRY? Load an extension library\n" #endif @@ -2169,11 +2213,13 @@ static char zHelp[] = " insert SQL insert statements for TABLE\n" " line One value per line\n" " list Values delimited by .separator strings\n" + " quote Escape answers as for SQL\n" " tabs Tab-separated values\n" " tcl TCL list elements\n" ".nullvalue STRING Use STRING in place of NULL values\n" ".once FILENAME Output for the next SQL command only to FILENAME\n" - ".open ?FILENAME? Close existing database and reopen FILENAME\n" + ".open ?--new? ?FILE? Close existing database and reopen FILE\n" + " The --new starts with an empty file\n" ".output ?FILENAME? Send output to FILENAME or stdout\n" ".print STRING... Print literal STRING\n" ".prompt MAIN CONTINUE Replace the standard prompts\n" @@ -2196,6 +2242,7 @@ static char zHelp[] = ".tables ?TABLE? List names of tables\n" " If TABLE specified, only list tables matching\n" " LIKE pattern TABLE.\n" + ".testcase NAME Begin redirecting output to 'testcase-out.txt'\n" ".timeout MS Try opening locked tables for MS milliseconds\n" ".timer on|off Turn SQL timer on or off\n" ".trace FILE|off Output each SQL statement as it is run\n" @@ -2232,6 +2279,44 @@ void session_help(ShellState *p){ /* Forward reference */ static int process_input(ShellState *p, FILE *in); + +/* +** Read the content of file zName into memory obtained from sqlite3_malloc64() +** and return a pointer to the buffer. The caller is responsible for freeing +** the memory. +** +** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes +** read. +** +** For convenience, a nul-terminator byte is always appended to the data read +** from the file before the buffer is returned. This byte is not included in +** the final value of (*pnByte), if applicable. +** +** NULL is returned if any error is encountered. The final value of *pnByte +** is undefined in this case. +*/ +static char *readFile(const char *zName, int *pnByte){ + FILE *in = fopen(zName, "rb"); + long nIn; + size_t nRead; + char *pBuf; + if( in==0 ) return 0; + fseek(in, 0, SEEK_END); + nIn = ftell(in); + rewind(in); + pBuf = sqlite3_malloc64( nIn+1 ); + if( pBuf==0 ) return 0; + nRead = fread(pBuf, nIn, 1, in); + fclose(in); + if( nRead!=1 ){ + sqlite3_free(pBuf); + return 0; + } + pBuf[nIn] = 0; + if( pnByte ) *pnByte = nIn; + return pBuf; +} + /* ** Implementation of the "readfile(X)" SQL function. The entire content ** of the file named X is read and returned as a BLOB. NULL is returned @@ -2243,25 +2328,14 @@ static void readfileFunc( sqlite3_value **argv ){ const char *zName; - FILE *in; - long nIn; void *pBuf; + int nBuf; UNUSED_PARAMETER(argc); zName = (const char*)sqlite3_value_text(argv[0]); if( zName==0 ) return; - in = fopen(zName, "rb"); - if( in==0 ) return; - fseek(in, 0, SEEK_END); - nIn = ftell(in); - rewind(in); - pBuf = sqlite3_malloc64( nIn ); - if( pBuf && 1==fread(pBuf, nIn, 1, in) ){ - sqlite3_result_blob(context, pBuf, nIn, sqlite3_free); - }else{ - sqlite3_free(pBuf); - } - fclose(in); + pBuf = readFile(zName, &nBuf); + if( pBuf ) sqlite3_result_blob(context, pBuf, nBuf, sqlite3_free); } /* @@ -2540,6 +2614,8 @@ static FILE *output_file_open(const char *zFile){ return f; } +#if !defined(SQLITE_UNTESTABLE) +#if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT) /* ** A routine for handling output from sqlite3_trace(). */ @@ -2560,6 +2636,8 @@ static int sql_trace_callback( } return 0; } +#endif +#endif /* ** A no-op routine that runs with the ".breakpoint" doc-command. This is @@ -3063,6 +3141,104 @@ static int shellNomemError(void){ return 1; } +/* +** Compare the pattern in zGlob[] against the text in z[]. Return TRUE +** if they match and FALSE (0) if they do not match. +** +** Globbing rules: +** +** '*' Matches any sequence of zero or more characters. +** +** '?' Matches exactly one character. +** +** [...] Matches one character from the enclosed list of +** characters. +** +** [^...] Matches one character not in the enclosed list. +** +** '#' Matches any sequence of one or more digits with an +** optional + or - sign in front +** +** ' ' Any span of whitespace matches any other span of +** whitespace. +** +** Extra whitespace at the end of z[] is ignored. +*/ +static int testcase_glob(const char *zGlob, const char *z){ + int c, c2; + int invert; + int seen; + + while( (c = (*(zGlob++)))!=0 ){ + if( IsSpace(c) ){ + if( !IsSpace(*z) ) return 0; + while( IsSpace(*zGlob) ) zGlob++; + while( IsSpace(*z) ) z++; + }else if( c=='*' ){ + while( (c=(*(zGlob++))) == '*' || c=='?' ){ + if( c=='?' && (*(z++))==0 ) return 0; + } + if( c==0 ){ + return 1; + }else if( c=='[' ){ + while( *z && testcase_glob(zGlob-1,z)==0 ){ + z++; + } + return (*z)!=0; + } + while( (c2 = (*(z++)))!=0 ){ + while( c2!=c ){ + c2 = *(z++); + if( c2==0 ) return 0; + } + if( testcase_glob(zGlob,z) ) return 1; + } + return 0; + }else if( c=='?' ){ + if( (*(z++))==0 ) return 0; + }else if( c=='[' ){ + int prior_c = 0; + seen = 0; + invert = 0; + c = *(z++); + if( c==0 ) return 0; + c2 = *(zGlob++); + if( c2=='^' ){ + invert = 1; + c2 = *(zGlob++); + } + if( c2==']' ){ + if( c==']' ) seen = 1; + c2 = *(zGlob++); + } + while( c2 && c2!=']' ){ + if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){ + c2 = *(zGlob++); + if( c>=prior_c && c<=c2 ) seen = 1; + prior_c = 0; + }else{ + if( c==c2 ){ + seen = 1; + } + prior_c = c2; + } + c2 = *(zGlob++); + } + if( c2==0 || (seen ^ invert)==0 ) return 0; + }else if( c=='#' ){ + if( (z[0]=='-' || z[0]=='+') && IsDigit(z[1]) ) z++; + if( !IsDigit(z[0]) ) return 0; + z++; + while( IsDigit(z[0]) ){ z++; } + }else{ + if( c!=(*(z++)) ) return 0; + } + } + while( IsSpace(*z) ){ z++; } + return *z==0; +} + + /* ** Compare the string as a command-line option with either one or two ** initial "-" characters. @@ -3074,6 +3250,268 @@ static int optionMatch(const char *zStr, const char *zOpt){ return strcmp(zStr, zOpt)==0; } +/* +** Delete a file. +*/ +int shellDeleteFile(const char *zFilename){ + int rc; +#ifdef _WIN32 + wchar_t *z = sqlite3_win32_utf8_to_unicode(zFilename); + rc = _wunlink(z); + sqlite3_free(z); +#else + rc = unlink(zFilename); +#endif + return rc; +} + + +/* +** The implementation of SQL scalar function fkey_collate_clause(), used +** by the ".lint fkey-indexes" command. This scalar function is always +** called with four arguments - the parent table name, the parent column name, +** the child table name and the child column name. +** +** fkey_collate_clause('parent-tab', 'parent-col', 'child-tab', 'child-col') +** +** If either of the named tables or columns do not exist, this function +** returns an empty string. An empty string is also returned if both tables +** and columns exist but have the same default collation sequence. Or, +** if both exist but the default collation sequences are different, this +** function returns the string " COLLATE ", where +** is the default collation sequence of the parent column. +*/ +static void shellFkeyCollateClause( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + sqlite3 *db = sqlite3_context_db_handle(pCtx); + const char *zParent; + const char *zParentCol; + const char *zParentSeq; + const char *zChild; + const char *zChildCol; + const char *zChildSeq = 0; /* Initialize to avoid false-positive warning */ + int rc; + + assert( nVal==4 ); + zParent = (const char*)sqlite3_value_text(apVal[0]); + zParentCol = (const char*)sqlite3_value_text(apVal[1]); + zChild = (const char*)sqlite3_value_text(apVal[2]); + zChildCol = (const char*)sqlite3_value_text(apVal[3]); + + sqlite3_result_text(pCtx, "", -1, SQLITE_STATIC); + rc = sqlite3_table_column_metadata( + db, "main", zParent, zParentCol, 0, &zParentSeq, 0, 0, 0 + ); + if( rc==SQLITE_OK ){ + rc = sqlite3_table_column_metadata( + db, "main", zChild, zChildCol, 0, &zChildSeq, 0, 0, 0 + ); + } + + if( rc==SQLITE_OK && sqlite3_stricmp(zParentSeq, zChildSeq) ){ + char *z = sqlite3_mprintf(" COLLATE %s", zParentSeq); + sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT); + sqlite3_free(z); + } +} + + +/* +** The implementation of dot-command ".lint fkey-indexes". +*/ +static int lintFkeyIndexes( + ShellState *pState, /* Current shell tool state */ + char **azArg, /* Array of arguments passed to dot command */ + int nArg /* Number of entries in azArg[] */ +){ + sqlite3 *db = pState->db; /* Database handle to query "main" db of */ + FILE *out = pState->out; /* Stream to write non-error output to */ + int bVerbose = 0; /* If -verbose is present */ + int bGroupByParent = 0; /* If -groupbyparent is present */ + int i; /* To iterate through azArg[] */ + const char *zIndent = ""; /* How much to indent CREATE INDEX by */ + int rc; /* Return code */ + sqlite3_stmt *pSql = 0; /* Compiled version of SQL statement below */ + + /* + ** This SELECT statement returns one row for each foreign key constraint + ** in the schema of the main database. The column values are: + ** + ** 0. The text of an SQL statement similar to: + ** + ** "EXPLAIN QUERY PLAN SELECT rowid FROM child_table WHERE child_key=?" + ** + ** This is the same SELECT that the foreign keys implementation needs + ** to run internally on child tables. If there is an index that can + ** be used to optimize this query, then it can also be used by the FK + ** implementation to optimize DELETE or UPDATE statements on the parent + ** table. + ** + ** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by + ** the EXPLAIN QUERY PLAN command matches this pattern, then the schema + ** contains an index that can be used to optimize the query. + ** + ** 2. Human readable text that describes the child table and columns. e.g. + ** + ** "child_table(child_key1, child_key2)" + ** + ** 3. Human readable text that describes the parent table and columns. e.g. + ** + ** "parent_table(parent_key1, parent_key2)" + ** + ** 4. A full CREATE INDEX statement for an index that could be used to + ** optimize DELETE or UPDATE statements on the parent table. e.g. + ** + ** "CREATE INDEX child_table_child_key ON child_table(child_key)" + ** + ** 5. The name of the parent table. + ** + ** These six values are used by the C logic below to generate the report. + */ + const char *zSql = + "SELECT " + " 'EXPLAIN QUERY PLAN SELECT rowid FROM ' || quote(s.name) || ' WHERE '" + " || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' " + " || fkey_collate_clause(f.[table], f.[to], s.name, f.[from]),' AND ')" + ", " + " 'SEARCH TABLE ' || s.name || ' USING COVERING INDEX*('" + " || group_concat('*=?', ' AND ') || ')'" + ", " + " s.name || '(' || group_concat(f.[from], ', ') || ')'" + ", " + " f.[table] || '(' || group_concat(COALESCE(f.[to], " + " (SELECT name FROM pragma_table_info(f.[table]) WHERE pk=seq+1)" + " )) || ')'" + ", " + " 'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))" + " || ' ON ' || quote(s.name) || '('" + " || group_concat(quote(f.[from]) ||" + " fkey_collate_clause(f.[table], f.[to], s.name, f.[from]), ', ')" + " || ');'" + ", " + " f.[table] " + + "FROM sqlite_master AS s, pragma_foreign_key_list(s.name) AS f " + "GROUP BY s.name, f.id " + "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)" + ; + + for(i=2; i1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){ + bVerbose = 1; + } + else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){ + bGroupByParent = 1; + zIndent = " "; + } + else{ + raw_printf(stderr, "Usage: %s %s ?-verbose? ?-groupbyparent?\n", + azArg[0], azArg[1] + ); + return SQLITE_ERROR; + } + } + + /* Register the fkey_collate_clause() SQL function */ + rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8, + 0, shellFkeyCollateClause, 0, 0 + ); + + + if( rc==SQLITE_OK ){ + rc = sqlite3_prepare_v2(db, zSql, -1, &pSql, 0); + } + if( rc==SQLITE_OK ){ + sqlite3_bind_int(pSql, 1, bGroupByParent); + } + + if( rc==SQLITE_OK ){ + int rc2; + char *zPrev = 0; + while( SQLITE_ROW==sqlite3_step(pSql) ){ + int res = -1; + sqlite3_stmt *pExplain = 0; + const char *zEQP = (const char*)sqlite3_column_text(pSql, 0); + const char *zGlob = (const char*)sqlite3_column_text(pSql, 1); + const char *zFrom = (const char*)sqlite3_column_text(pSql, 2); + const char *zTarget = (const char*)sqlite3_column_text(pSql, 3); + const char *zCI = (const char*)sqlite3_column_text(pSql, 4); + const char *zParent = (const char*)sqlite3_column_text(pSql, 5); + + rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0); + if( rc!=SQLITE_OK ) break; + if( SQLITE_ROW==sqlite3_step(pExplain) ){ + const char *zPlan = (const char*)sqlite3_column_text(pExplain, 3); + res = (0==sqlite3_strglob(zGlob, zPlan)); + } + rc = sqlite3_finalize(pExplain); + if( rc!=SQLITE_OK ) break; + + if( res<0 ){ + raw_printf(stderr, "Error: internal error"); + break; + }else{ + if( bGroupByParent + && (bVerbose || res==0) + && (zPrev==0 || sqlite3_stricmp(zParent, zPrev)) + ){ + raw_printf(out, "-- Parent table %s\n", zParent); + sqlite3_free(zPrev); + zPrev = sqlite3_mprintf("%s", zParent); + } + + if( res==0 ){ + raw_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget); + }else if( bVerbose ){ + raw_printf(out, "%s/* no extra indexes required for %s -> %s */\n", + zIndent, zFrom, zTarget + ); + } + } + } + sqlite3_free(zPrev); + + if( rc!=SQLITE_OK ){ + raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); + } + + rc2 = sqlite3_finalize(pSql); + if( rc==SQLITE_OK && rc2!=SQLITE_OK ){ + rc = rc2; + raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); + } + }else{ + raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); + } + + return rc; +} + +/* +** Implementation of ".lint" dot command. +*/ +static int lintDotCommand( + ShellState *pState, /* Current shell tool state */ + char **azArg, /* Array of arguments passed to dot command */ + int nArg /* Number of entries in azArg[] */ +){ + int n; + n = (nArg>=2 ? (int)strlen(azArg[1]) : 0); + if( n<1 || sqlite3_strnicmp(azArg[1], "fkey-indexes", n) ) goto usage; + return lintFkeyIndexes(pState, azArg, nArg); + + usage: + raw_printf(stderr, "Usage %s sub-command ?switches...?\n", azArg[0]); + raw_printf(stderr, "Where sub-commands are:\n"); + raw_printf(stderr, " fkey-indexes\n"); + return SQLITE_ERROR; +} + + /* ** If an input line begins with "." then invoke this routine to ** process that line. @@ -3117,6 +3555,7 @@ static int do_meta_command(char *zLine, ShellState *p){ n = strlen30(azArg[0]); c = azArg[0][0]; +#ifndef SQLITE_OMIT_AUTHORIZATION if( c=='a' && strncmp(azArg[0], "auth", n)==0 ){ if( nArg!=2 ){ raw_printf(stderr, "Usage: .auth ON|OFF\n"); @@ -3130,6 +3569,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_set_authorizer(p->db, 0, 0); } }else +#endif if( (c=='b' && n>=3 && strncmp(azArg[0], "backup", n)==0) || (c=='s' && n>=3 && strncmp(azArg[0], "save", n)==0) @@ -3225,6 +3665,31 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else + /* Cancel output redirection, if it is currently set (by .testcase) + ** Then read the content of the testcase-out.txt file and compare against + ** azArg[1]. If there are differences, report an error and exit. + */ + if( c=='c' && n>=3 && strncmp(azArg[0], "check", n)==0 ){ + char *zRes = 0; + output_reset(p); + if( nArg!=2 ){ + raw_printf(stderr, "Usage: .check GLOB-PATTERN\n"); + rc = 2; + }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){ + raw_printf(stderr, "Error: cannot read 'testcase-out.txt'\n"); + rc = 2; + }else if( testcase_glob(azArg[1],zRes)==0 ){ + utf8_printf(stderr, + "testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n", + p->zTestcase, azArg[1], zRes); + rc = 2; + }else{ + utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase); + p->nCheck++; + } + sqlite3_free(zRes); + }else + if( c=='c' && strncmp(azArg[0], "clone", n)==0 ){ if( nArg==2 ){ tryToClone(p, azArg[1]); @@ -3239,13 +3704,12 @@ static int do_meta_command(char *zLine, ShellState *p){ char *zErrMsg = 0; open_db(p, 0); memcpy(&data, p, sizeof(data)); - data.showHeader = 1; - data.cMode = data.mode = MODE_Column; - data.colWidth[0] = 3; - data.colWidth[1] = 15; - data.colWidth[2] = 58; + data.showHeader = 0; + data.cMode = data.mode = MODE_List; + sqlite3_snprintf(sizeof(data.colSeparator),data.colSeparator,": "); data.cnt = 0; - sqlite3_exec(p->db, "PRAGMA database_list; ", callback, &data, &zErrMsg); + sqlite3_exec(p->db, "SELECT name, file FROM pragma_database_list", + callback, &data, &zErrMsg); if( zErrMsg ){ utf8_printf(stderr,"Error: %s\n", zErrMsg); sqlite3_free(zErrMsg); @@ -3626,51 +4090,78 @@ static int do_meta_command(char *zLine, ShellState *p){ if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0); }else - if( c=='i' && (strncmp(azArg[0], "indices", n)==0 - || strncmp(azArg[0], "indexes", n)==0) ){ - ShellState data; - char *zErrMsg = 0; - open_db(p, 0); - memcpy(&data, p, sizeof(data)); - data.showHeader = 0; - data.cMode = data.mode = MODE_List; - if( nArg==1 ){ - rc = sqlite3_exec(p->db, - "SELECT name FROM sqlite_master " - "WHERE type='index' AND name NOT LIKE 'sqlite_%' " - "UNION ALL " - "SELECT name FROM sqlite_temp_master " - "WHERE type='index' " - "ORDER BY 1", - callback, &data, &zErrMsg - ); - }else if( nArg==2 ){ - zShellStatic = azArg[1]; - rc = sqlite3_exec(p->db, - "SELECT name FROM sqlite_master " - "WHERE type='index' AND tbl_name LIKE shellstatic() " - "UNION ALL " - "SELECT name FROM sqlite_temp_master " - "WHERE type='index' AND tbl_name LIKE shellstatic() " - "ORDER BY 1", - callback, &data, &zErrMsg - ); - zShellStatic = 0; - }else{ - raw_printf(stderr, "Usage: .indexes ?LIKE-PATTERN?\n"); +#ifndef SQLITE_UNTESTABLE + if( c=='i' && strncmp(azArg[0], "imposter", n)==0 ){ + char *zSql; + char *zCollist = 0; + sqlite3_stmt *pStmt; + int tnum = 0; + int i; + if( nArg!=3 ){ + utf8_printf(stderr, "Usage: .imposter INDEX IMPOSTER\n"); rc = 1; goto meta_command_exit; } - if( zErrMsg ){ - utf8_printf(stderr,"Error: %s\n", zErrMsg); - sqlite3_free(zErrMsg); + open_db(p, 0); + zSql = sqlite3_mprintf("SELECT rootpage FROM sqlite_master" + " WHERE name='%q' AND type='index'", azArg[1]); + sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + tnum = sqlite3_column_int(pStmt, 0); + } + sqlite3_finalize(pStmt); + if( tnum==0 ){ + utf8_printf(stderr, "no such index: \"%s\"\n", azArg[1]); rc = 1; - }else if( rc != SQLITE_OK ){ - raw_printf(stderr, - "Error: querying sqlite_master and sqlite_temp_master\n"); + goto meta_command_exit; + } + zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]); + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + i = 0; + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + char zLabel[20]; + const char *zCol = (const char*)sqlite3_column_text(pStmt,2); + i++; + if( zCol==0 ){ + if( sqlite3_column_int(pStmt,1)==-1 ){ + zCol = "_ROWID_"; + }else{ + sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i); + zCol = zLabel; + } + } + if( zCollist==0 ){ + zCollist = sqlite3_mprintf("\"%w\"", zCol); + }else{ + zCollist = sqlite3_mprintf("%z,\"%w\"", zCollist, zCol); + } + } + sqlite3_finalize(pStmt); + zSql = sqlite3_mprintf( + "CREATE TABLE \"%w\"(%s,PRIMARY KEY(%s))WITHOUT ROWID", + azArg[2], zCollist, zCollist); + sqlite3_free(zCollist); + rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 1, tnum); + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(p->db, zSql, 0, 0, 0); + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0); + if( rc ){ + utf8_printf(stderr, "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db)); + }else{ + utf8_printf(stdout, "%s;\n", zSql); + raw_printf(stdout, + "WARNING: writing to an imposter table will corrupt the index!\n" + ); + } + }else{ + raw_printf(stderr, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); rc = 1; } + sqlite3_free(zSql); }else +#endif /* !defined(SQLITE_OMIT_TEST_CONTROL) */ #ifdef SQLITE_ENABLE_IOTRACE if( c=='i' && strncmp(azArg[0], "iotrace", n)==0 ){ @@ -3694,6 +4185,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else #endif + if( c=='l' && n>=5 && strncmp(azArg[0], "limits", n)==0 ){ static const struct { const char *zLimitName; /* Name of a limit */ @@ -3753,6 +4245,11 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else + if( c=='l' && n>2 && strncmp(azArg[0], "lint", n)==0 ){ + open_db(p, 0); + lintDotCommand(p, azArg, nArg); + }else + #ifndef SQLITE_OMIT_LOAD_EXTENSION if( c=='l' && strncmp(azArg[0], "load", n)==0 ){ const char *zFile, *zProc; @@ -3810,13 +4307,15 @@ static int do_meta_command(char *zLine, ShellState *p){ }else if( c2=='i' && strncmp(azArg[1],"insert",n2)==0 ){ p->mode = MODE_Insert; set_table_name(p, nArg>=3 ? azArg[2] : "table"); + }else if( c2=='q' && strncmp(azArg[1],"quote",n2)==0 ){ + p->mode = MODE_Quote; }else if( c2=='a' && strncmp(azArg[1],"ascii",n2)==0 ){ p->mode = MODE_Ascii; sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Unit); sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Record); }else { raw_printf(stderr, "Error: mode should be one of: " - "ascii column csv html insert line list tabs tcl\n"); + "ascii column csv html insert line list quote tabs tcl\n"); rc = 1; } p->cMode = p->mode; @@ -3833,22 +4332,43 @@ static int do_meta_command(char *zLine, ShellState *p){ }else if( c=='o' && strncmp(azArg[0], "open", n)==0 && n>=2 ){ - sqlite3 *savedDb = p->db; - const char *zSavedFilename = p->zDbFilename; - char *zNewFilename = 0; + char *zNewFilename; /* Name of the database file to open */ + int iName = 1; /* Index in azArg[] of the filename */ + int newFlag = 0; /* True to delete file before opening */ + /* Close the existing database */ + session_close_all(p); + sqlite3_close(p->db); p->db = 0; - if( nArg>=2 ) zNewFilename = sqlite3_mprintf("%s", azArg[1]); - p->zDbFilename = zNewFilename; - open_db(p, 1); - if( p->db!=0 ){ - session_close_all(p); - sqlite3_close(savedDb); - sqlite3_free(p->zFreeOnClose); - p->zFreeOnClose = zNewFilename; - }else{ - sqlite3_free(zNewFilename); - p->db = savedDb; - p->zDbFilename = zSavedFilename; + sqlite3_free(p->zFreeOnClose); + p->zFreeOnClose = 0; + /* Check for command-line arguments */ + for(iName=1; iNameiName ? sqlite3_mprintf("%s", azArg[iName]) : 0; + if( zNewFilename ){ + if( newFlag ) shellDeleteFile(zNewFilename); + p->zDbFilename = zNewFilename; + open_db(p, 1); + if( p->db==0 ){ + utf8_printf(stderr, "Error: cannot open '%s'\n", zNewFilename); + sqlite3_free(zNewFilename); + }else{ + p->zFreeOnClose = zNewFilename; + } + } + if( p->db==0 ){ + /* As a fall-back open a TEMP database */ + p->zDbFilename = 0; + open_db(p, 0); } }else @@ -4378,6 +4898,8 @@ static int do_meta_command(char *zLine, ShellState *p){ raw_printf(p->out, "%d ", p->colWidth[i]); } raw_printf(p->out, "\n"); + utf8_printf(p->out, "%12.12s: %s\n", "filename", + p->zDbFilename ? p->zDbFilename : ""); }else if( c=='s' && strncmp(azArg[0], "stats", n)==0 ){ @@ -4391,7 +4913,10 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else - if( c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0 ){ + if( (c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0) + || (c=='i' && (strncmp(azArg[0], "indices", n)==0 + || strncmp(azArg[0], "indexes", n)==0) ) + ){ sqlite3_stmt *pStmt; char **azResult; int nRow, nAlloc; @@ -4404,28 +4929,41 @@ static int do_meta_command(char *zLine, ShellState *p){ /* Create an SQL statement to query for the list of tables in the ** main and all attached databases where the table name matches the ** LIKE pattern bound to variable "?1". */ - zSql = sqlite3_mprintf( - "SELECT name FROM sqlite_master" - " WHERE type IN ('table','view')" - " AND name NOT LIKE 'sqlite_%%'" - " AND name LIKE ?1"); - while( zSql && sqlite3_step(pStmt)==SQLITE_ROW ){ + if( c=='t' ){ + zSql = sqlite3_mprintf( + "SELECT name FROM sqlite_master" + " WHERE type IN ('table','view')" + " AND name NOT LIKE 'sqlite_%%'" + " AND name LIKE ?1"); + }else if( nArg>2 ){ + /* It is an historical accident that the .indexes command shows an error + ** when called with the wrong number of arguments whereas the .tables + ** command does not. */ + raw_printf(stderr, "Usage: .indexes ?LIKE-PATTERN?\n"); + rc = 1; + goto meta_command_exit; + }else{ + zSql = sqlite3_mprintf( + "SELECT name FROM sqlite_master" + " WHERE type='index'" + " AND tbl_name LIKE ?1"); + } + for(ii=0; zSql && sqlite3_step(pStmt)==SQLITE_ROW; ii++){ const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1); - if( zDbName==0 || strcmp(zDbName,"main")==0 ) continue; - if( strcmp(zDbName,"temp")==0 ){ - zSql = sqlite3_mprintf( - "%z UNION ALL " - "SELECT 'temp.' || name FROM sqlite_temp_master" - " WHERE type IN ('table','view')" - " AND name NOT LIKE 'sqlite_%%'" - " AND name LIKE ?1", zSql); - }else{ + if( zDbName==0 || ii==0 ) continue; + if( c=='t' ){ zSql = sqlite3_mprintf( "%z UNION ALL " "SELECT '%q.' || name FROM \"%w\".sqlite_master" " WHERE type IN ('table','view')" " AND name NOT LIKE 'sqlite_%%'" " AND name LIKE ?1", zSql, zDbName, zDbName); + }else{ + zSql = sqlite3_mprintf( + "%z UNION ALL " + "SELECT '%q.' || name FROM \"%w\".sqlite_master" + " WHERE type='index'" + " AND tbl_name LIKE ?1", zSql, zDbName, zDbName); } } rc = sqlite3_finalize(pStmt); @@ -4495,6 +5033,21 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_free(azResult); }else + /* Begin redirecting output to the file "testcase-out.txt" */ + if( c=='t' && strcmp(azArg[0],"testcase")==0 ){ + output_reset(p); + p->out = output_file_open("testcase-out.txt"); + if( p->out==0 ){ + raw_printf(stderr, "Error: cannot open 'testcase-out.txt'\n"); + } + if( nArg>=2 ){ + sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", azArg[1]); + }else{ + sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?"); + } + }else + +#ifndef SQLITE_UNTESTABLE if( c=='t' && n>=8 && strncmp(azArg[0], "testctrl", n)==0 && nArg>=2 ){ static const struct { const char *zCtrlName; /* Name of a test-control option */ @@ -4670,6 +5223,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } #endif }else +#endif /* !defined(SQLITE_UNTESTABLE) */ #if SQLITE_USER_AUTHENTICATION if( c=='u' && strncmp(azArg[0], "user", n)==0 ){ @@ -4878,6 +5432,42 @@ static int line_is_complete(char *zSql, int nSql){ return rc; } +/* +** Run a single line of SQL +*/ +static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ + int rc; + char *zErrMsg = 0; + + open_db(p, 0); + if( p->backslashOn ) resolve_backslashes(zSql); + BEGIN_TIMER; + rc = shell_exec(p->db, zSql, shell_callback, p, &zErrMsg); + END_TIMER; + if( rc || zErrMsg ){ + char zPrefix[100]; + if( in!=0 || !stdin_is_interactive ){ + sqlite3_snprintf(sizeof(zPrefix), zPrefix, + "Error: near line %d:", startline); + }else{ + sqlite3_snprintf(sizeof(zPrefix), zPrefix, "Error:"); + } + if( zErrMsg!=0 ){ + utf8_printf(stderr, "%s %s\n", zPrefix, zErrMsg); + sqlite3_free(zErrMsg); + zErrMsg = 0; + }else{ + utf8_printf(stderr, "%s %s\n", zPrefix, sqlite3_errmsg(p->db)); + } + return 1; + }else if( p->countChanges ){ + raw_printf(p->out, "changes: %3d total_changes: %d\n", + sqlite3_changes(p->db), sqlite3_total_changes(p->db)); + } + return 0; +} + + /* ** Read input from *in and process it. If *in==0 then input ** is interactive - the user is typing it it. Otherwise, input @@ -4894,7 +5484,6 @@ static int process_input(ShellState *p, FILE *in){ int nSql = 0; /* Bytes of zSql[] used */ int nAlloc = 0; /* Allocated zSql[] space */ int nSqlPrior = 0; /* Bytes of zSql[] used by prior line */ - char *zErrMsg; /* Error message returned */ int rc; /* Error code */ int errCnt = 0; /* Number of errors seen */ int lineno = 0; /* Current line number */ @@ -4905,7 +5494,7 @@ static int process_input(ShellState *p, FILE *in){ zLine = one_input_line(in, zLine, nSql>0); if( zLine==0 ){ /* End of input */ - if( stdin_is_interactive ) printf("\n"); + if( in==0 && stdin_is_interactive ) printf("\n"); break; } if( seenInterrupt ){ @@ -4954,32 +5543,7 @@ static int process_input(ShellState *p, FILE *in){ } if( nSql && line_contains_semicolon(&zSql[nSqlPrior], nSql-nSqlPrior) && sqlite3_complete(zSql) ){ - p->cnt = 0; - open_db(p, 0); - if( p->backslashOn ) resolve_backslashes(zSql); - BEGIN_TIMER; - rc = shell_exec(p->db, zSql, shell_callback, p, &zErrMsg); - END_TIMER; - if( rc || zErrMsg ){ - char zPrefix[100]; - if( in!=0 || !stdin_is_interactive ){ - sqlite3_snprintf(sizeof(zPrefix), zPrefix, - "Error: near line %d:", startline); - }else{ - sqlite3_snprintf(sizeof(zPrefix), zPrefix, "Error:"); - } - if( zErrMsg!=0 ){ - utf8_printf(stderr, "%s %s\n", zPrefix, zErrMsg); - sqlite3_free(zErrMsg); - zErrMsg = 0; - }else{ - utf8_printf(stderr, "%s %s\n", zPrefix, sqlite3_errmsg(p->db)); - } - errCnt++; - }else if( p->countChanges ){ - raw_printf(p->out, "changes: %3d total_changes: %d\n", - sqlite3_changes(p->db), sqlite3_total_changes(p->db)); - } + errCnt += runOneSqlLine(p, zSql, in, startline); nSql = 0; if( p->outCount ){ output_reset(p); @@ -4990,11 +5554,8 @@ static int process_input(ShellState *p, FILE *in){ nSql = 0; } } - if( nSql ){ - if( !_all_whitespace(zSql) ){ - utf8_printf(stderr, "Error: incomplete SQL: %s\n", zSql); - errCnt++; - } + if( nSql && !_all_whitespace(zSql) ){ + runOneSqlLine(p, zSql, in, startline); } free(zSql); free(zLine); @@ -5005,8 +5566,13 @@ static int process_input(ShellState *p, FILE *in){ ** Return a pathname which is the user's home directory. A ** 0 return indicates an error of some kind. */ -static char *find_home_dir(void){ +static char *find_home_dir(int clearFlag){ static char *home_dir = NULL; + if( clearFlag ){ + free(home_dir); + home_dir = 0; + return 0; + } if( home_dir ) return home_dir; #if !defined(_WIN32) && !defined(WIN32) && !defined(_WIN32_WCE) \ @@ -5081,7 +5647,7 @@ static void process_sqliterc( FILE *in = NULL; if (sqliterc == NULL) { - home_dir = find_home_dir(); + home_dir = find_home_dir(0); if( home_dir==0 ){ raw_printf(stderr, "-- warning: cannot find home directory;" " cannot read ~/.sqliterc\n"); @@ -5569,7 +6135,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ printf(".\nUse \".open FILENAME\" to reopen on a " "persistent database.\n"); } - zHome = find_home_dir(); + zHome = find_home_dir(0); if( zHome ){ nHistory = strlen30(zHome) + 20; if( (zHistory = malloc(nHistory))!=0 ){ @@ -5593,6 +6159,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ sqlite3_close(data.db); } sqlite3_free(data.zFreeOnClose); + find_home_dir(1); #if !SQLITE_SHELL_IS_UTF8 for(i=0; iFossil configuration management ** system. ^The SQLITE_SOURCE_ID macro evaluates to ** a string which identifies a particular check-in of SQLite @@ -126,7 +127,7 @@ extern "C" { /* ** CAPI3REF: Run-Time Library Version Numbers -** KEYWORDS: sqlite3_version, sqlite3_sourceid +** KEYWORDS: sqlite3_version sqlite3_sourceid ** ** These interfaces provide the same information as the [SQLITE_VERSION], ** [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros @@ -452,7 +453,8 @@ int sqlite3_exec( ** [result codes]. However, experience has shown that many of ** these result codes are too coarse-grained. They do not provide as ** much information about problems as programmers might like. In an effort to -** address this, newer versions of SQLite (version 3.3.8 and later) include +** address this, newer versions of SQLite (version 3.3.8 [dateof:3.3.8] +** and later) include ** support for additional result codes that provide more detailed information ** about errors. These [extended result codes] are enabled or disabled ** on a per database connection basis using the @@ -976,6 +978,12 @@ struct sqlite3_io_methods { ** on whether or not the file has been renamed, moved, or deleted since it ** was first opened. ** +**
  • [[SQLITE_FCNTL_WIN32_GET_HANDLE]] +** The [SQLITE_FCNTL_WIN32_GET_HANDLE] opcode can be used to obtain the +** underlying native file handle associated with a file handle. This file +** control interprets its argument as a pointer to a native file handle and +** writes the resulting value there. +** **
  • [[SQLITE_FCNTL_WIN32_SET_HANDLE]] ** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging. This ** opcode causes the xFileControl method to swap the file handle with the one @@ -1026,6 +1034,8 @@ struct sqlite3_io_methods { #define SQLITE_FCNTL_RBU 26 #define SQLITE_FCNTL_VFS_POINTER 27 #define SQLITE_FCNTL_JOURNAL_POINTER 28 +#define SQLITE_FCNTL_WIN32_GET_HANDLE 29 +#define SQLITE_FCNTL_PDB 30 /* deprecated names */ #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE @@ -1969,13 +1979,36 @@ struct sqlite3_mem_methods { ** be a NULL pointer, in which case the new setting is not reported back. ** ** +**
    SQLITE_DBCONFIG_MAINDBNAME
    +**
    ^This option is used to change the name of the "main" database +** schema. ^The sole argument is a pointer to a constant UTF8 string +** which will become the new schema name in place of "main". ^SQLite +** does not make a copy of the new main schema name string, so the application +** must ensure that the argument passed into this DBCONFIG option is unchanged +** until after the database connection closes. +**
    +** +**
    SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE
    +**
    Usually, when a database in wal mode is closed or detached from a +** database handle, SQLite checks if this will mean that there are now no +** connections at all to the database. If so, it performs a checkpoint +** operation before closing the connection. This option may be used to +** override this behaviour. The first parameter passed to this operation +** is an integer - non-zero to disable checkpoints-on-close, or zero (the +** default) to enable them. The second parameter is a pointer to an integer +** into which is written 0 or 1 to indicate whether checkpoints-on-close +** have been disabled - 0 if they are not disabled, 1 if they are. +**
    +** ** */ +#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */ #define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ #define SQLITE_DBCONFIG_ENABLE_FKEY 1002 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_TRIGGER 1003 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005 /* int int* */ +#define SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006 /* int int* */ /* @@ -3577,6 +3610,10 @@ char *sqlite3_expanded_sql(sqlite3_stmt *pStmt); ** sqlite3_stmt_readonly() to return true since, while those statements ** change the configuration of a database connection, they do not make ** changes to the content of the database files on disk. +** ^The sqlite3_stmt_readonly() interface returns true for [BEGIN] since +** [BEGIN] merely sets internal flags, but the [BEGIN|BEGIN IMMEDIATE] and +** [BEGIN|BEGIN EXCLUSIVE] commands do touch the database and so +** sqlite3_stmt_readonly() returns false for those commands. */ int sqlite3_stmt_readonly(sqlite3_stmt *pStmt); @@ -3859,8 +3896,12 @@ int sqlite3_clear_bindings(sqlite3_stmt*); ** METHOD: sqlite3_stmt ** ** ^Return the number of columns in the result set returned by the -** [prepared statement]. ^This routine returns 0 if pStmt is an SQL -** statement that does not return data (for example an [UPDATE]). +** [prepared statement]. ^If this routine returns 0, that means the +** [prepared statement] returns no data (for example an [UPDATE]). +** ^However, just because this routine returns a positive number does not +** mean that one or more rows of data will be returned. ^A SELECT statement +** will always have a positive sqlite3_column_count() but depending on the +** WHERE clause constraints and the table content, it might return no rows. ** ** See also: [sqlite3_data_count()] */ @@ -4041,7 +4082,8 @@ const void *sqlite3_column_decltype16(sqlite3_stmt*,int); ** other than [SQLITE_ROW] before any subsequent invocation of ** sqlite3_step(). Failure to reset the prepared statement using ** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from -** sqlite3_step(). But after version 3.6.23.1, sqlite3_step() began +** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1], +** sqlite3_step() began ** calling [sqlite3_reset()] automatically in this circumstance rather ** than returning [SQLITE_MISUSE]. This is not considered a compatibility ** break because any application that ever receives an SQLITE_MISUSE error @@ -5404,7 +5446,8 @@ void *sqlite3_update_hook( ** and disabled if the argument is false.)^ ** ** ^Cache sharing is enabled and disabled for an entire process. -** This is a change as of SQLite version 3.5.0. In prior versions of SQLite, +** This is a change as of SQLite [version 3.5.0] ([dateof:3.5.0]). +** In prior versions of SQLite, ** sharing was enabled or disabled for each thread separately. ** ** ^(The cache sharing mode set by this interface effects all subsequent @@ -5498,7 +5541,8 @@ int sqlite3_db_release_memory(sqlite3*); ** from the heap. ** )^ ** -** Beginning with SQLite version 3.7.3, the soft heap limit is enforced +** Beginning with SQLite [version 3.7.3] ([dateof:3.7.3]), +** the soft heap limit is enforced ** regardless of whether or not the [SQLITE_ENABLE_MEMORY_MANAGEMENT] ** compile-time option is invoked. With [SQLITE_ENABLE_MEMORY_MANAGEMENT], ** the soft heap limit is enforced on every memory allocation. Without @@ -5892,13 +5936,15 @@ struct sqlite3_module { ** the xUpdate method are automatically rolled back by SQLite. ** ** IMPORTANT: The estimatedRows field was added to the sqlite3_index_info -** structure for SQLite version 3.8.2. If a virtual table extension is +** structure for SQLite [version 3.8.2] ([dateof:3.8.2]). +** If a virtual table extension is ** used with an SQLite version earlier than 3.8.2, the results of attempting ** to read or write the estimatedRows field are undefined (but are likely ** to included crashing the application). The estimatedRows field should ** therefore only be used if [sqlite3_libversion_number()] returns a ** value greater than or equal to 3008002. Similarly, the idxFlags field -** was added for version 3.9.0. It may therefore only be used if +** was added for [version 3.9.0] ([dateof:3.9.0]). +** It may therefore only be used if ** sqlite3_libversion_number() returns a value greater than or equal to ** 3009000. */ @@ -6596,7 +6642,7 @@ int sqlite3_mutex_notheld(sqlite3_mutex*); #define SQLITE_MUTEX_STATIC_MEM 3 /* sqlite3_malloc() */ #define SQLITE_MUTEX_STATIC_MEM2 4 /* NOT USED */ #define SQLITE_MUTEX_STATIC_OPEN 4 /* sqlite3BtreeOpen() */ -#define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_random() */ +#define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_randomness() */ #define SQLITE_MUTEX_STATIC_LRU 6 /* lru page list */ #define SQLITE_MUTEX_STATIC_LRU2 7 /* NOT USED */ #define SQLITE_MUTEX_STATIC_PMEM 7 /* sqlite3PageMalloc() */ @@ -6700,6 +6746,7 @@ int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_SCRATCHMALLOC 17 #define SQLITE_TESTCTRL_LOCALTIME_FAULT 18 #define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */ +#define SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD 19 #define SQLITE_TESTCTRL_NEVER_CORRUPT 20 #define SQLITE_TESTCTRL_VDBE_COVERAGE 21 #define SQLITE_TESTCTRL_BYTEORDER 22 @@ -8186,7 +8233,8 @@ int sqlite3_db_cacheflush(sqlite3*); ** ** See also: [sqlite3_update_hook()] */ -SQLITE_EXPERIMENTAL void *sqlite3_preupdate_hook( +#if defined(SQLITE_ENABLE_PREUPDATE_HOOK) +void *sqlite3_preupdate_hook( sqlite3 *db, void(*xPreUpdate)( void *pCtx, /* Copy of third arg to preupdate_hook() */ @@ -8199,10 +8247,11 @@ SQLITE_EXPERIMENTAL void *sqlite3_preupdate_hook( ), void* ); -SQLITE_EXPERIMENTAL int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **); -SQLITE_EXPERIMENTAL int sqlite3_preupdate_count(sqlite3 *); -SQLITE_EXPERIMENTAL int sqlite3_preupdate_depth(sqlite3 *); -SQLITE_EXPERIMENTAL int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **); +int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **); +int sqlite3_preupdate_count(sqlite3 *); +int sqlite3_preupdate_depth(sqlite3 *); +int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **); +#endif /* ** CAPI3REF: Low-level system error code @@ -8218,7 +8267,7 @@ int sqlite3_system_errno(sqlite3*); /* ** CAPI3REF: Database Snapshot -** KEYWORDS: {snapshot} +** KEYWORDS: {snapshot} {sqlite3_snapshot} ** EXPERIMENTAL ** ** An instance of the snapshot object records the state of a [WAL mode] @@ -8242,7 +8291,9 @@ int sqlite3_system_errno(sqlite3*); ** to an historical snapshot (if possible). The destructor for ** sqlite3_snapshot objects is [sqlite3_snapshot_free()]. */ -typedef struct sqlite3_snapshot sqlite3_snapshot; +typedef struct sqlite3_snapshot { + unsigned char hidden[48]; +} sqlite3_snapshot; /* ** CAPI3REF: Record A Database Snapshot @@ -8253,9 +8304,32 @@ typedef struct sqlite3_snapshot sqlite3_snapshot; ** schema S in database connection D. ^On success, the ** [sqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly ** created [sqlite3_snapshot] object into *P and returns SQLITE_OK. -** ^If schema S of [database connection] D is not a [WAL mode] database -** that is in a read transaction, then [sqlite3_snapshot_get(D,S,P)] -** leaves the *P value unchanged and returns an appropriate [error code]. +** If there is not already a read-transaction open on schema S when +** this function is called, one is opened automatically. +** +** The following must be true for this function to succeed. If any of +** the following statements are false when sqlite3_snapshot_get() is +** called, SQLITE_ERROR is returned. The final value of *P is undefined +** in this case. +** +**
      +**
    • The database handle must be in [autocommit mode]. +** +**
    • Schema S of [database connection] D must be a [WAL mode] database. +** +**
    • There must not be a write transaction open on schema S of database +** connection D. +** +**
    • One or more transactions must have been written to the current wal +** file since it was created on disk (by any connection). This means +** that a snapshot cannot be taken on a wal mode database with no wal +** file immediately after it is first opened. At least one transaction +** must be written to it first. +**
    +** +** This function may also return SQLITE_NOMEM. If it is called with the +** database handle in autocommit mode but fails for some other reason, +** whether or not a read transaction is opened on schema S is undefined. ** ** The [sqlite3_snapshot] object returned from a successful call to ** [sqlite3_snapshot_get()] must be freed using [sqlite3_snapshot_free()] @@ -8348,6 +8422,28 @@ SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp( sqlite3_snapshot *p2 ); +/* +** CAPI3REF: Recover snapshots from a wal file +** EXPERIMENTAL +** +** If all connections disconnect from a database file but do not perform +** a checkpoint, the existing wal file is opened along with the database +** file the next time the database is opened. At this point it is only +** possible to successfully call sqlite3_snapshot_open() to open the most +** recent snapshot of the database (the one at the head of the wal file), +** even though the wal file may contain other valid snapshots for which +** clients have sqlite3_snapshot handles. +** +** This function attempts to scan the wal file associated with database zDb +** of database handle db and make all valid snapshots available to +** sqlite3_snapshot_open(). It is an error if there is already a read +** transaction open on the database, or if the database is not a wal mode +** database. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +*/ +SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); + /* ** Undo the hack that converts floating point types to integer for ** builds on processors without floating point support. diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 3cbdec748b..308498afc5 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -110,6 +110,14 @@ # define GCC_VERSION 0 #endif +/* What version of CLANG is being used. 0 means CLANG is not being used */ +#ifdef __clang__ +# define CLANG_VERSION \ + (__clang_major__*1000000+__clang_minor__*1000+__clang_patchlevel__) +#else +# define CLANG_VERSION 0 +#endif + /* Needed for various definitions... */ #if defined(__GNUC__) && !defined(_GNU_SOURCE) # define _GNU_SOURCE @@ -587,9 +595,12 @@ ** pagecaches for each database connection. A positive number is the ** number of pages. A negative number N translations means that a buffer ** of -1024*N bytes is allocated and used for as many pages as it will hold. +** +** The default value of "20" was choosen to minimize the run-time of the +** speedtest1 test program with options: --shrink-memory --reprepare */ #ifndef SQLITE_DEFAULT_PCACHE_INITSZ -# define SQLITE_DEFAULT_PCACHE_INITSZ 100 +# define SQLITE_DEFAULT_PCACHE_INITSZ 20 #endif /* @@ -1046,6 +1057,14 @@ typedef struct Walker Walker; typedef struct WhereInfo WhereInfo; typedef struct With With; +/* A VList object records a mapping between parameters/variables/wildcards +** in the SQL statement (such as $abc, @pqr, or :xyz) and the integer +** variable number associated with that parameter. See the format description +** on the sqlite3VListAdd() routine for more information. A VList is really +** just an array of integers. +*/ +typedef int VList; + /* ** Defer sourcing vdbe.h and btree.h until after the "u8" and ** "BusyHandler" typedefs. vdbe.h also requires a few of the opaque @@ -1097,7 +1116,7 @@ typedef struct With With; ** databases may be attached. */ struct Db { - char *zName; /* Name of this database */ + char *zDbSName; /* Name of this database. (schema name, not filename) */ Btree *pBt; /* The B*Tree structure for this database file */ u8 safety_level; /* How aggressive at syncing data to disk */ u8 bSyncSet; /* True if "PRAGMA synchronous=N" has been run */ @@ -1289,6 +1308,7 @@ struct sqlite3 { u8 vtabOnConflict; /* Value to return for s3_vtab_on_conflict() */ u8 isTransactionSavepoint; /* True if the outermost savepoint is a TS */ u8 mTrace; /* zero or more SQLITE_TRACE flags */ + u8 skipBtreeMutex; /* True if no shared-cache backends */ int nextPagesize; /* Pagesize after VACUUM if >0 */ u32 magic; /* Magic number for detect library misuse */ int nChange; /* Value returned by sqlite3_changes() */ @@ -1436,6 +1456,7 @@ struct sqlite3 { #define SQLITE_Vacuum 0x10000000 /* Currently in a VACUUM */ #define SQLITE_CellSizeCk 0x20000000 /* Check btree cell sizes on load */ #define SQLITE_Fts3Tokenizer 0x40000000 /* Enable fts3_tokenizer(2) */ +#define SQLITE_NoCkptOnClose 0x80000000 /* No checkpoint on close()/DETACH */ /* @@ -1461,13 +1482,8 @@ struct sqlite3 { /* ** Macros for testing whether or not optimizations are enabled or disabled. */ -#ifndef SQLITE_OMIT_BUILTIN_TEST #define OptimizationDisabled(db, mask) (((db)->dbOptFlags&(mask))!=0) #define OptimizationEnabled(db, mask) (((db)->dbOptFlags&(mask))==0) -#else -#define OptimizationDisabled(db, mask) 0 -#define OptimizationEnabled(db, mask) 1 -#endif /* ** Return true if it OK to factor constant expressions into the initialization @@ -1558,6 +1574,7 @@ struct FuncDestructor { #define SQLITE_FUNC_MINMAX 0x1000 /* True for min() and max() aggregates */ #define SQLITE_FUNC_SLOCHNG 0x2000 /* "Slow Change". Value constant during a ** single query - might change over time */ +#define SQLITE_FUNC_AFFINITY 0x4000 /* Built-in affinity() function */ /* ** The following three macros, FUNCTION(), LIKEFUNC() and AGGREGATE() are @@ -1734,6 +1751,7 @@ struct CollSeq { ** operator is NULL. It is added to certain comparison operators to ** prove that the operands are always NOT NULL. */ +#define SQLITE_KEEPNULL 0x08 /* Used by vector == or <> */ #define SQLITE_JUMPIFNULL 0x10 /* jumps if either operand is NULL */ #define SQLITE_STOREP2 0x20 /* Store result in reg[P2] rather than jump */ #define SQLITE_NULLEQ 0x80 /* NULL=NULL */ @@ -1805,9 +1823,9 @@ struct Table { ExprList *pCheck; /* All CHECK constraints */ /* ... also used as column name list in a VIEW */ int tnum; /* Root BTree page for this table */ + u32 nTabRef; /* Number of pointers to this Table */ i16 iPKey; /* If not negative, use aCol[iPKey] as the rowid */ i16 nCol; /* Number of columns in this table */ - u16 nRef; /* Number of pointers to this Table */ LogEst nRowLogEst; /* Estimated rows in table - from sqlite_stat1 table */ LogEst szTabRow; /* Estimated size of each table row in bytes */ #ifdef SQLITE_ENABLE_COSTMULT @@ -2298,9 +2316,11 @@ struct Expr { int iTable; /* TK_COLUMN: cursor number of table holding column ** TK_REGISTER: register number ** TK_TRIGGER: 1 -> new, 0 -> old - ** EP_Unlikely: 134217728 times likelihood */ + ** EP_Unlikely: 134217728 times likelihood + ** TK_SELECT: 1st register of result vector */ ynVar iColumn; /* TK_COLUMN: column index. -1 for rowid. - ** TK_VARIABLE: variable number (always >= 1). */ + ** TK_VARIABLE: variable number (always >= 1). + ** TK_SELECT_COLUMN: column of the result vector */ i16 iAgg; /* Which entry in pAggInfo->aCol[] or ->aFunc[] */ i16 iRightJoinTable; /* If EP_FromJoin, the right table of the join */ u8 op2; /* TK_REGISTER: original value of Expr.op @@ -2336,6 +2356,7 @@ struct Expr { #define EP_CanBeNull 0x100000 /* Can be null despite NOT NULL constraint */ #define EP_Subquery 0x200000 /* Tree contains a TK_SELECT operator */ #define EP_Alias 0x400000 /* Is an alias for a result set column */ +#define EP_Leaf 0x800000 /* Expr.pLeft, .pRight, .u.pSelect all NULL */ /* ** Combinations of two or more EP_* flags @@ -2781,7 +2802,7 @@ struct Select { */ struct SelectDest { u8 eDest; /* How to dispose of the results. On of SRT_* above. */ - char affSdst; /* Affinity used when eDest==SRT_Set */ + char *zAffSdst; /* Affinity used when eDest==SRT_Set */ int iSDParm; /* A parameter used by the eDest disposal method */ int iSdst; /* Base register where results are written */ int nSdst; /* Number of registers allocated */ @@ -2887,36 +2908,23 @@ struct Parse { u8 okConstFactor; /* OK to factor out constants */ u8 disableLookaside; /* Number of times lookaside has been disabled */ u8 nColCache; /* Number of entries in aColCache[] */ - int aTempReg[8]; /* Holding area for temporary registers */ int nRangeReg; /* Size of the temporary register block */ int iRangeReg; /* First register in temporary register block */ int nErr; /* Number of errors seen */ int nTab; /* Number of previously allocated VDBE cursors */ int nMem; /* Number of memory cells used so far */ - int nSet; /* Number of sets used so far */ - int nOnce; /* Number of OP_Once instructions so far */ int nOpAlloc; /* Number of slots allocated for Vdbe.aOp[] */ int szOpAlloc; /* Bytes of memory space allocated for Vdbe.aOp[] */ - int iFixedOp; /* Never back out opcodes iFixedOp-1 or earlier */ int ckBase; /* Base register of data during check constraints */ int iSelfTab; /* Table of an index whose exprs are being coded */ int iCacheLevel; /* ColCache valid when aColCache[].iLevel<=iCacheLevel */ int iCacheCnt; /* Counter used to generate aColCache[].lru values */ int nLabel; /* Number of labels used */ int *aLabel; /* Space to hold the labels */ - struct yColCache { - int iTable; /* Table cursor number */ - i16 iColumn; /* Table column number */ - u8 tempReg; /* iReg is a temp register that needs to be freed */ - int iLevel; /* Nesting level */ - int iReg; /* Reg with value of this column. 0 means none. */ - int lru; /* Least recently used entry has the smallest value */ - } aColCache[SQLITE_N_COLCACHE]; /* One for each column cache entry */ ExprList *pConstExpr;/* Constant expressions */ Token constraintName;/* Name of the constraint currently being parsed */ yDbMask writeMask; /* Start a write transaction on these databases */ yDbMask cookieMask; /* Bitmask of schema verified databases */ - int cookieValue[SQLITE_MAX_ATTACHED+2]; /* Values of cookies to verify */ int regRowid; /* Register holding rowid of CREATE TABLE entry */ int regRoot; /* Register holding root page number for new objects */ int nMaxArg; /* Max args passed to user function by sub-program */ @@ -2929,8 +2937,6 @@ struct Parse { TableLock *aTableLock; /* Required table locks for shared-cache mode */ #endif AutoincInfo *pAinc; /* Information about AUTOINCREMENT counters */ - - /* Information used while coding trigger programs. */ Parse *pToplevel; /* Parse structure for main program (or NULL) */ Table *pTriggerTab; /* Table triggers are being coded for */ int addrCrTab; /* Address of OP_CreateTable opcode on CREATE TABLE */ @@ -2941,35 +2947,50 @@ struct Parse { u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */ u8 disableTriggers; /* True to disable triggers */ + /************************************************************************** + ** Fields above must be initialized to zero. The fields that follow, + ** down to the beginning of the recursive section, do not need to be + ** initialized as they will be set before being used. The boundary is + ** determined by offsetof(Parse,aColCache). + **************************************************************************/ + + struct yColCache { + int iTable; /* Table cursor number */ + i16 iColumn; /* Table column number */ + u8 tempReg; /* iReg is a temp register that needs to be freed */ + int iLevel; /* Nesting level */ + int iReg; /* Reg with value of this column. 0 means none. */ + int lru; /* Least recently used entry has the smallest value */ + } aColCache[SQLITE_N_COLCACHE]; /* One for each column cache entry */ + int aTempReg[8]; /* Holding area for temporary registers */ + Token sNameToken; /* Token with unqualified schema object name */ + /************************************************************************ ** Above is constant between recursions. Below is reset before and after ** each recursion. The boundary between these two regions is determined - ** using offsetof(Parse,nVar) so the nVar field must be the first field - ** in the recursive region. + ** using offsetof(Parse,sLastToken) so the sLastToken field must be the + ** first field in the recursive region. ************************************************************************/ + Token sLastToken; /* The last token parsed */ ynVar nVar; /* Number of '?' variables seen in the SQL so far */ - int nzVar; /* Number of available slots in azVar[] */ u8 iPkSortOrder; /* ASC or DESC for INTEGER PRIMARY KEY */ u8 explain; /* True if the EXPLAIN flag is found on the query */ #ifndef SQLITE_OMIT_VIRTUALTABLE u8 declareVtab; /* True if inside sqlite3_declare_vtab() */ int nVtabLock; /* Number of virtual tables to lock */ #endif - int nAlias; /* Number of aliased result set columns */ int nHeight; /* Expression tree height of current sub-select */ #ifndef SQLITE_OMIT_EXPLAIN int iSelectId; /* ID of current select for EXPLAIN output */ int iNextSelectId; /* Next available select ID for EXPLAIN output */ #endif - char **azVar; /* Pointers to names of parameters */ + VList *pVList; /* Mapping between variable names and numbers */ Vdbe *pReprepare; /* VM being reprepared (sqlite3Reprepare()) */ const char *zTail; /* All SQL text past the last semicolon parsed */ Table *pNewTable; /* A table being constructed by CREATE TABLE */ Trigger *pNewTrigger; /* Trigger under construct by a CREATE TRIGGER */ const char *zAuthContext; /* The 6th parameter to db->xAuth callbacks */ - Token sNameToken; /* Token with unqualified schema object name */ - Token sLastToken; /* The last token parsed */ #ifndef SQLITE_OMIT_VIRTUALTABLE Token sArg; /* Complete text of a module argument */ Table **apVtabLock; /* Pointer to virtual tables needing locking */ @@ -2980,6 +3001,14 @@ struct Parse { With *pWithToFree; /* Free this WITH object at the end of the parse */ }; +/* +** Sizes and pointers of various parts of the Parse object. +*/ +#define PARSE_HDR_SZ offsetof(Parse,aColCache) /* Recursive part w/o aColCache*/ +#define PARSE_RECURSE_SZ offsetof(Parse,sLastToken) /* Recursive part */ +#define PARSE_TAIL_SZ (sizeof(Parse)-PARSE_RECURSE_SZ) /* Non-recursive part */ +#define PARSE_TAIL(X) (((char*)(X))+PARSE_RECURSE_SZ) /* Pointer to tail */ + /* ** Return true if currently inside an sqlite3_declare_vtab() call. */ @@ -3224,10 +3253,11 @@ struct Sqlite3Config { void (*xVdbeBranch)(void*,int iSrcLine,u8 eThis,u8 eMx); /* Callback */ void *pVdbeBranchArg; /* 1st argument */ #endif -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE int (*xTestCallback)(int); /* Invoked by sqlite3FaultSim() */ #endif int bLocaltimeFault; /* True to fail localtime() calls */ + int iOnceResetThreshold; /* When to reset OP_Once counters */ }; /* @@ -3427,7 +3457,7 @@ void sqlite3ScratchFree(void*); void *sqlite3PageMalloc(int); void sqlite3PageFree(void*); void sqlite3MemSetDefault(void); -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE void sqlite3BenignMallocHooks(void (*)(void), void (*)(void)); #endif int sqlite3HeapNearlyFull(void); @@ -3513,6 +3543,7 @@ char *sqlite3VMPrintf(sqlite3*,const char*, va_list); #if defined(SQLITE_DEBUG) void sqlite3TreeViewExpr(TreeView*, const Expr*, u8); + void sqlite3TreeViewBareExprList(TreeView*, const ExprList*, const char*); void sqlite3TreeViewExprList(TreeView*, const ExprList*, u8, const char*); void sqlite3TreeViewSelect(TreeView*, const Select*, u8); void sqlite3TreeViewWith(TreeView*, const With*, u8); @@ -3537,13 +3568,14 @@ int sqlite3NoTempsInRange(Parse*,int,int); Expr *sqlite3ExprAlloc(sqlite3*,int,const Token*,int); Expr *sqlite3Expr(sqlite3*,int,const char*); void sqlite3ExprAttachSubtrees(sqlite3*,Expr*,Expr*,Expr*); -Expr *sqlite3PExpr(Parse*, int, Expr*, Expr*, const Token*); +Expr *sqlite3PExpr(Parse*, int, Expr*, Expr*); void sqlite3PExprAddSelect(Parse*, Expr*, Select*); Expr *sqlite3ExprAnd(sqlite3*,Expr*, Expr*); Expr *sqlite3ExprFunction(Parse*,ExprList*, Token*); -void sqlite3ExprAssignVarNumber(Parse*, Expr*); +void sqlite3ExprAssignVarNumber(Parse*, Expr*, u32); void sqlite3ExprDelete(sqlite3*, Expr*); ExprList *sqlite3ExprListAppend(Parse*,ExprList*,Expr*); +ExprList *sqlite3ExprListAppendVector(Parse*,ExprList*,IdList*,Expr*); void sqlite3ExprListSetSortOrder(ExprList*,int); void sqlite3ExprListSetName(Parse*,ExprList*,Token*,int); void sqlite3ExprListSetSpan(Parse*,ExprList*,ExprSpan*); @@ -3552,6 +3584,9 @@ u32 sqlite3ExprListFlags(const ExprList*); int sqlite3Init(sqlite3*, char**); int sqlite3InitCallback(void*, int, char**, char**); void sqlite3Pragma(Parse*,Token*,Token*,Token*,int); +#ifndef SQLITE_OMIT_VIRTUALTABLE +Module *sqlite3PragmaVtabRegister(sqlite3*,const char *zName); +#endif void sqlite3ResetAllSchemasOfConnection(sqlite3*); void sqlite3ResetOneSchema(sqlite3*,int); void sqlite3CollapseDatabaseArray(sqlite3*); @@ -3579,9 +3614,8 @@ void sqlite3EndTable(Parse*,Token*,Token*,u8,Select*); int sqlite3ParseUri(const char*,const char*,unsigned int*, sqlite3_vfs**,char**,char **); Btree *sqlite3DbNameToBtree(sqlite3*,const char*); -int sqlite3CodeOnce(Parse *); -#ifdef SQLITE_OMIT_BUILTIN_TEST +#ifdef SQLITE_UNTESTABLE # define sqlite3FaultSim(X) SQLITE_OK #else int sqlite3FaultSim(int); @@ -3594,7 +3628,7 @@ int sqlite3BitvecSet(Bitvec*, u32); void sqlite3BitvecClear(Bitvec*, u32, void*); void sqlite3BitvecDestroy(Bitvec*); u32 sqlite3BitvecSize(Bitvec*); -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE int sqlite3BitvecBuiltinTest(int,int*); #endif @@ -3683,7 +3717,7 @@ void sqlite3ExprCacheAffinityChange(Parse*, int, int); void sqlite3ExprCode(Parse*, Expr*, int); void sqlite3ExprCodeCopy(Parse*, Expr*, int); void sqlite3ExprCodeFactorable(Parse*, Expr*, int); -void sqlite3ExprCodeAtInit(Parse*, Expr*, int, u8); +int sqlite3ExprCodeAtInit(Parse*, Expr*, int); int sqlite3ExprCodeTemp(Parse*, Expr*, int*); int sqlite3ExprCodeTarget(Parse*, Expr*, int); void sqlite3ExprCodeAndCache(Parse*, Expr*, int); @@ -3691,6 +3725,7 @@ int sqlite3ExprCodeExprList(Parse*, ExprList*, int, int, u8); #define SQLITE_ECEL_DUP 0x01 /* Deep, not shallow copies */ #define SQLITE_ECEL_FACTOR 0x02 /* Factor out constant terms */ #define SQLITE_ECEL_REF 0x04 /* Use ExprList.u.x.iOrderByCol */ +#define SQLITE_ECEL_OMITREF 0x08 /* Omit if ExprList.u.x.iOrderByCol */ void sqlite3ExprIfTrue(Parse*, Expr*, int, int); void sqlite3ExprIfFalse(Parse*, Expr*, int, int); void sqlite3ExprIfFalseDup(Parse*, Expr*, int, int); @@ -3702,8 +3737,8 @@ Table *sqlite3LocateTableItem(Parse*,u32 flags,struct SrcList_item *); Index *sqlite3FindIndex(sqlite3*,const char*, const char*); void sqlite3UnlinkAndDeleteTable(sqlite3*,int,const char*); void sqlite3UnlinkAndDeleteIndex(sqlite3*,int,const char*); -void sqlite3Vacuum(Parse*); -int sqlite3RunVacuum(char**, sqlite3*); +void sqlite3Vacuum(Parse*,Token*); +int sqlite3RunVacuum(char**, sqlite3*, int); char *sqlite3NameFromToken(sqlite3*, Token*); int sqlite3ExprCompare(Expr*, Expr*, int); int sqlite3ExprListCompare(ExprList*, ExprList*, int); @@ -3713,7 +3748,7 @@ void sqlite3ExprAnalyzeAggList(NameContext*,ExprList*); int sqlite3ExprCoveredByIndex(Expr*, int iCur, Index *pIdx); int sqlite3FunctionUsesThisSrc(Expr*, SrcList*); Vdbe *sqlite3GetVdbe(Parse*); -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE void sqlite3PrngSaveState(void); void sqlite3PrngRestoreState(void); #endif @@ -3850,6 +3885,9 @@ LogEst sqlite3LogEstFromDouble(double); defined(SQLITE_EXPLAIN_ESTIMATED_ROWS) u64 sqlite3LogEstToInt(LogEst); #endif +VList *sqlite3VListAdd(sqlite3*,VList*,const char*,int,int); +const char *sqlite3VListNumToName(VList*,int); +int sqlite3VListNameToNum(VList*,const char*,int); /* ** Routines to read and write variable-length integers. These used to @@ -3879,6 +3917,7 @@ const char *sqlite3IndexAffinityStr(sqlite3*, Index*); void sqlite3TableAffinity(Vdbe*, Table*, int); char sqlite3CompareAffinity(Expr *pExpr, char aff2); int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity); +char sqlite3TableColumnAffinity(Table*,int); char sqlite3ExprAffinity(Expr *pExpr); int sqlite3Atoi64(const char*, i64*, int, u8); int sqlite3DecOrHexToI64(const char*, i64*); @@ -3944,7 +3983,7 @@ void sqlite3AlterRenameTable(Parse*, SrcList*, Token*); int sqlite3GetToken(const unsigned char *, int *); void sqlite3NestedParse(Parse*, const char*, ...); void sqlite3ExpirePreparedStatements(sqlite3*); -int sqlite3CodeSubselect(Parse *, Expr *, int, int); +int sqlite3CodeSubselect(Parse*, Expr *, int, int); void sqlite3SelectPrep(Parse*, Select*, NameContext*); void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p); int sqlite3MatchSpanName(const char*, const char*, const char*, const char*); @@ -3999,12 +4038,20 @@ Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int); void sqlite3BackupRestart(sqlite3_backup *); void sqlite3BackupUpdate(sqlite3_backup *, Pgno, const u8 *); +#ifndef SQLITE_OMIT_SUBQUERY +int sqlite3ExprCheckIN(Parse*, Expr*); +#else +# define sqlite3ExprCheckIN(x,y) SQLITE_OK +#endif + #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 void sqlite3AnalyzeFunctions(void); -int sqlite3Stat4ProbeSetValue(Parse*,Index*,UnpackedRecord**,Expr*,u8,int,int*); +int sqlite3Stat4ProbeSetValue( + Parse*,Index*,UnpackedRecord**,Expr*,int,int,int*); int sqlite3Stat4ValueFromExpr(Parse*, Expr*, u8, sqlite3_value**); void sqlite3Stat4ProbeFree(UnpackedRecord*); int sqlite3Stat4Column(sqlite3*, const void*, int, int, sqlite3_value**); +char sqlite3IndexColumnAffinity(sqlite3*, Index*, int); #endif /* @@ -4057,6 +4104,13 @@ void sqlite3AutoLoadExtensions(sqlite3*); int sqlite3VtabSavepoint(sqlite3 *, int, int); void sqlite3VtabImportErrmsg(Vdbe*, sqlite3_vtab*); VTable *sqlite3GetVTable(sqlite3*, Table*); + Module *sqlite3VtabCreateModule( + sqlite3*, + const char*, + const sqlite3_module*, + void*, + void(*)(void*) + ); # define sqlite3VtabInSync(db) ((db)->nVTrans>0 && (db)->aVTrans==0) #endif int sqlite3VtabEponymousTableInit(Parse*,Module*); @@ -4132,10 +4186,10 @@ const char *sqlite3JournalModename(int); /* ** The interface to the code in fault.c used for identifying "benign" -** malloc failures. This is only present if SQLITE_OMIT_BUILTIN_TEST +** malloc failures. This is only present if SQLITE_UNTESTABLE ** is not defined. */ -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE void sqlite3BeginBenignMalloc(void); void sqlite3EndBenignMalloc(void); #else @@ -4157,7 +4211,7 @@ const char *sqlite3JournalModename(int); #define IN_INDEX_NOOP_OK 0x0001 /* OK to return IN_INDEX_NOOP */ #define IN_INDEX_MEMBERSHIP 0x0002 /* IN operator used for membership test */ #define IN_INDEX_LOOP 0x0004 /* IN operator used as a loop */ -int sqlite3FindInIndex(Parse *, Expr *, u32, int*); +int sqlite3FindInIndex(Parse *, Expr *, u32, int*, int*); int sqlite3JournalOpen(sqlite3_vfs *, const char *, sqlite3_file *, int, int); int sqlite3JournalSize(sqlite3_vfs *); @@ -4262,4 +4316,10 @@ int sqlite3ThreadJoin(SQLiteThread*, void**); int sqlite3DbstatRegister(sqlite3*); #endif +int sqlite3ExprVectorSize(Expr *pExpr); +int sqlite3ExprIsVector(Expr *pExpr); +Expr *sqlite3VectorFieldSubexpr(Expr*, int); +Expr *sqlite3ExprForVectorField(Parse*,Expr*,int); +void sqlite3VectorErrorMsg(Parse*, Expr*); + #endif /* SQLITEINT_H */ diff --git a/src/tclsqlite.c b/src/tclsqlite.c index 3db4a32c4d..1d87c9b331 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -590,7 +590,8 @@ static int DbProgressHandler(void *cd){ } #endif -#ifndef SQLITE_OMIT_TRACE +#if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT) && \ + !defined(SQLITE_OMIT_DEPRECATED) /* ** This routine is called by the SQLite trace handler whenever a new ** block of SQL is executed. The TCL script in pDb->zTrace is executed. @@ -684,7 +685,8 @@ static int DbTraceV2Handler( } #endif -#ifndef SQLITE_OMIT_TRACE +#if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT) && \ + !defined(SQLITE_OMIT_DEPRECATED) /* ** This routine is called by the SQLite profile handler after a statement ** SQL has executed. The TCL script in pDb->zProfile is evaluated. @@ -2747,7 +2749,8 @@ static int SQLITE_TCLAPI DbObjCmd( }else{ pDb->zProfile = 0; } -#if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT) +#if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT) && \ + !defined(SQLITE_OMIT_DEPRECATED) if( pDb->zProfile ){ pDb->interp = interp; sqlite3_profile(pDb->db, DbProfileHandler, pDb); @@ -2934,8 +2937,8 @@ static int SQLITE_TCLAPI DbObjCmd( }else{ pDb->zTrace = 0; } -#if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT) \ - && !defined(SQLITE_OMIT_DEPRECATED) +#if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT) && \ + !defined(SQLITE_OMIT_DEPRECATED) if( pDb->zTrace ){ pDb->interp = interp; sqlite3_trace(pDb->db, DbTraceHandler, pDb); @@ -3137,7 +3140,8 @@ static int SQLITE_TCLAPI DbObjCmd( */ case DB_PREUPDATE: { #ifndef SQLITE_ENABLE_PREUPDATE_HOOK - Tcl_AppendResult(interp, "preupdate_hook was omitted at compile-time"); + Tcl_AppendResult(interp, "preupdate_hook was omitted at compile-time", + (char*)0); rc = TCL_ERROR; #else static const char *azSub[] = {"count", "depth", "hook", "new", "old", 0}; diff --git a/src/test1.c b/src/test1.c index aced552173..d17fc1022a 100644 --- a/src/test1.c +++ b/src/test1.c @@ -2310,6 +2310,38 @@ static int SQLITE_TCLAPI test_snapshot_get( } #endif /* SQLITE_ENABLE_SNAPSHOT */ +#ifdef SQLITE_ENABLE_SNAPSHOT +/* +** Usage: sqlite3_snapshot_recover DB DBNAME +*/ +static int SQLITE_TCLAPI test_snapshot_recover( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int rc; + sqlite3 *db; + char *zName; + + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME"); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; + zName = Tcl_GetString(objv[2]); + + rc = sqlite3_snapshot_recover(db, zName); + if( rc!=SQLITE_OK ){ + Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); + return TCL_ERROR; + }else{ + Tcl_ResetResult(interp); + } + return TCL_OK; +} +#endif /* SQLITE_ENABLE_SNAPSHOT */ + #ifdef SQLITE_ENABLE_SNAPSHOT /* ** Usage: sqlite3_snapshot_open DB DBNAME SNAPSHOT @@ -2388,6 +2420,136 @@ static int SQLITE_TCLAPI test_snapshot_cmp( } #endif /* SQLITE_ENABLE_SNAPSHOT */ +#ifdef SQLITE_ENABLE_SNAPSHOT +/* +** Usage: sqlite3_snapshot_get_blob DB DBNAME +*/ +static int SQLITE_TCLAPI test_snapshot_get_blob( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int rc; + sqlite3 *db; + char *zName; + sqlite3_snapshot *pSnapshot = 0; + + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME"); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; + zName = Tcl_GetString(objv[2]); + + rc = sqlite3_snapshot_get(db, zName, &pSnapshot); + if( rc!=SQLITE_OK ){ + Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); + return TCL_ERROR; + }else{ + Tcl_SetObjResult(interp, + Tcl_NewByteArrayObj((unsigned char*)pSnapshot, sizeof(sqlite3_snapshot)) + ); + sqlite3_snapshot_free(pSnapshot); + } + return TCL_OK; +} +#endif /* SQLITE_ENABLE_SNAPSHOT */ + +#ifdef SQLITE_ENABLE_SNAPSHOT + /* + ** Usage: sqlite3_snapshot_open_blob DB DBNAME SNAPSHOT +*/ +static int SQLITE_TCLAPI test_snapshot_open_blob( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int rc; + sqlite3 *db; + char *zName; + unsigned char *pBlob; + int nBlob; + + if( objc!=4 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME SNAPSHOT"); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; + zName = Tcl_GetString(objv[2]); + pBlob = Tcl_GetByteArrayFromObj(objv[3], &nBlob); + if( nBlob!=sizeof(sqlite3_snapshot) ){ + Tcl_AppendResult(interp, "bad SNAPSHOT", 0); + return TCL_ERROR; + } + rc = sqlite3_snapshot_open(db, zName, (sqlite3_snapshot*)pBlob); + if( rc!=SQLITE_OK ){ + Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); + return TCL_ERROR; + } + return TCL_OK; +} +#endif /* SQLITE_ENABLE_SNAPSHOT */ + +#ifdef SQLITE_ENABLE_SNAPSHOT +/* +** Usage: sqlite3_snapshot_cmp_blob SNAPSHOT1 SNAPSHOT2 +*/ +static int SQLITE_TCLAPI test_snapshot_cmp_blob( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int res; + unsigned char *p1; + unsigned char *p2; + int n1; + int n2; + + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "SNAPSHOT1 SNAPSHOT2"); + return TCL_ERROR; + } + + p1 = Tcl_GetByteArrayFromObj(objv[1], &n1); + p2 = Tcl_GetByteArrayFromObj(objv[2], &n2); + + if( n1!=sizeof(sqlite3_snapshot) || n1!=n2 ){ + Tcl_AppendResult(interp, "bad SNAPSHOT", 0); + return TCL_ERROR; + } + + res = sqlite3_snapshot_cmp((sqlite3_snapshot*)p1, (sqlite3_snapshot*)p2); + Tcl_SetObjResult(interp, Tcl_NewIntObj(res)); + return TCL_OK; +} +#endif /* SQLITE_ENABLE_SNAPSHOT */ + +/* +** Usage: sqlite3_delete_database FILENAME +*/ +int sqlite3_delete_database(const char*); /* in test_delete.c */ +static int SQLITE_TCLAPI test_delete_database( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int rc; + const char *zFile; + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "FILE"); + return TCL_ERROR; + } + zFile = (const char*)Tcl_GetString(objv[1]); + rc = sqlite3_delete_database(zFile); + + Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); + return TCL_OK; +} + /* ** Usage: sqlite3_next_stmt DB STMT ** @@ -5654,6 +5816,38 @@ static int SQLITE_TCLAPI file_control_win32_av_retry( return TCL_OK; } +/* +** tclcmd: file_control_win32_get_handle DB +** +** This TCL command runs the sqlite3_file_control interface with +** the SQLITE_FCNTL_WIN32_GET_HANDLE opcode. +*/ +static int file_control_win32_get_handle( + ClientData clientData, /* Pointer to sqlite3_enable_XXX function */ + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + sqlite3 *db; + int rc; + HANDLE hFile = NULL; + char z[100]; + + if( objc!=2 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", + Tcl_GetStringFromObj(objv[0], 0), " DB", 0); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){ + return TCL_ERROR; + } + rc = sqlite3_file_control(db, NULL, SQLITE_FCNTL_WIN32_GET_HANDLE, + (void*)&hFile); + sqlite3_snprintf(sizeof(z), z, "%d %p", rc, (void*)hFile); + Tcl_AppendResult(interp, z, (char*)0); + return TCL_OK; +} + /* ** tclcmd: file_control_win32_set_handle DB HANDLE ** @@ -6764,6 +6958,7 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd( extern int sqlite3_nextchar_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_percentile_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_regexp_init(sqlite3*,char**,const sqlite3_api_routines*); + extern int sqlite3_remember_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_series_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_spellfix_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_totype_init(sqlite3*,char**,const sqlite3_api_routines*); @@ -6783,6 +6978,7 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd( { "nextchar", sqlite3_nextchar_init }, { "percentile", sqlite3_percentile_init }, { "regexp", sqlite3_regexp_init }, + { "remember", sqlite3_remember_init }, { "series", sqlite3_series_init }, { "spellfix", sqlite3_spellfix_init }, { "totype", sqlite3_totype_init }, @@ -7166,6 +7362,7 @@ static int SQLITE_TCLAPI test_sqlite3_db_config( { "TRIGGER", SQLITE_DBCONFIG_ENABLE_TRIGGER }, { "FTS3_TOKENIZER", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER }, { "LOAD_EXTENSION", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION }, + { "NO_CKPT_ON_CLOSE",SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE }, }; int i; int v; @@ -7195,6 +7392,29 @@ static int SQLITE_TCLAPI test_sqlite3_db_config( return TCL_OK; } +/* +** Change the name of the main database schema from "main" to "icecube". +*/ +static int SQLITE_TCLAPI test_dbconfig_maindbname_icecube( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int rc; + sqlite3 *db; + extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**); + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB"); + return TCL_ERROR; + }else{ + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; + rc = sqlite3_db_config(db, SQLITE_DBCONFIG_MAINDBNAME, "icecube"); + Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); + return TCL_OK; + } +} + /* ** Register commands with the TCL interpreter. */ @@ -7328,6 +7548,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ { "sqlite3_enable_load_extension", test_enable_load, 0}, { "sqlite3_extended_result_codes", test_extended_result_codes, 0}, { "sqlite3_limit", test_limit, 0}, + { "dbconfig_maindbname_icecube", test_dbconfig_maindbname_icecube }, { "save_prng_state", save_prng_state, 0 }, { "restore_prng_state", restore_prng_state, 0 }, @@ -7394,6 +7615,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ { "file_control_sizehint_test", file_control_sizehint_test, 0 }, #if SQLITE_OS_WIN { "file_control_win32_av_retry", file_control_win32_av_retry, 0 }, + { "file_control_win32_get_handle", file_control_win32_get_handle, 0 }, { "file_control_win32_set_handle", file_control_win32_set_handle, 0 }, #endif { "file_control_persist_wal", file_control_persist_wal, 0 }, @@ -7458,7 +7680,12 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ { "sqlite3_snapshot_open", test_snapshot_open, 0 }, { "sqlite3_snapshot_free", test_snapshot_free, 0 }, { "sqlite3_snapshot_cmp", test_snapshot_cmp, 0 }, + { "sqlite3_snapshot_recover", test_snapshot_recover, 0 }, + { "sqlite3_snapshot_get_blob", test_snapshot_get_blob, 0 }, + { "sqlite3_snapshot_open_blob", test_snapshot_open_blob, 0 }, + { "sqlite3_snapshot_cmp_blob", test_snapshot_cmp_blob, 0 }, #endif + { "sqlite3_delete_database", test_delete_database, 0 }, }; static int bitmask_size = sizeof(Bitmask)*8; static int longdouble_size = sizeof(LONGDOUBLE_TYPE); diff --git a/src/test2.c b/src/test2.c index dfca1d2b31..850e1e1a04 100644 --- a/src/test2.c +++ b/src/test2.c @@ -93,7 +93,7 @@ static int SQLITE_TCLAPI pager_close( return TCL_ERROR; } pPager = sqlite3TestTextToPtr(argv[1]); - rc = sqlite3PagerClose(pPager); + rc = sqlite3PagerClose(pPager, 0); if( rc!=SQLITE_OK ){ Tcl_AppendResult(interp, sqlite3ErrName(rc), 0); return TCL_ERROR; diff --git a/src/test3.c b/src/test3.c index 6995684c29..6b4bfedbd9 100644 --- a/src/test3.c +++ b/src/test3.c @@ -253,7 +253,6 @@ static int SQLITE_TCLAPI btree_close_cursor( const char **argv /* Text of each argument */ ){ BtCursor *pCur; - Btree *pBt; int rc; if( argc!=2 ){ @@ -262,12 +261,18 @@ static int SQLITE_TCLAPI btree_close_cursor( return TCL_ERROR; } pCur = sqlite3TestTextToPtr(argv[1]); - pBt = pCur->pBtree; - sqlite3_mutex_enter(pBt->db->mutex); - sqlite3BtreeEnter(pBt); +#if SQLITE_THREADSAFE>0 + { + Btree *pBt = pCur->pBtree; + sqlite3_mutex_enter(pBt->db->mutex); + sqlite3BtreeEnter(pBt); + rc = sqlite3BtreeCloseCursor(pCur); + sqlite3BtreeLeave(pBt); + sqlite3_mutex_leave(pBt->db->mutex); + } +#else rc = sqlite3BtreeCloseCursor(pCur); - sqlite3BtreeLeave(pBt); - sqlite3_mutex_leave(pBt->db->mutex); +#endif ckfree((char *)pCur); if( rc ){ Tcl_AppendResult(interp, sqlite3ErrName(rc), 0); diff --git a/src/test_blob.c b/src/test_blob.c index 3ac6c11421..7fa733bee9 100644 --- a/src/test_blob.c +++ b/src/test_blob.c @@ -114,7 +114,7 @@ static int SQLITE_TCLAPI test_blob_open( const char *zVarname; int nVarname; - sqlite3_blob *pBlob = (sqlite3_blob*)0xFFFFFFFF; + sqlite3_blob *pBlob = (sqlite3_blob*)&flags; /* Non-zero initialization */ int rc; if( objc!=8 ){ diff --git a/src/test_config.c b/src/test_config.c index fd24a94c87..ff9964157e 100644 --- a/src/test_config.c +++ b/src/test_config.c @@ -268,7 +268,7 @@ static void set_options(Tcl_Interp *interp){ Tcl_SetVar2(interp, "sqlite_options", "between_opt", "1", TCL_GLOBAL_ONLY); #endif -#ifdef SQLITE_OMIT_BUILTIN_TEST +#ifdef SQLITE_UNTESTABLE Tcl_SetVar2(interp, "sqlite_options", "builtin_test", "0", TCL_GLOBAL_ONLY); #else Tcl_SetVar2(interp, "sqlite_options", "builtin_test", "1", TCL_GLOBAL_ONLY); @@ -720,6 +720,12 @@ Tcl_SetVar2(interp, "sqlite_options", "mergesort", "1", TCL_GLOBAL_ONLY); Tcl_SetVar2(interp, "sqlite_options", "sqllog", "0", TCL_GLOBAL_ONLY); #endif +#ifdef SQLITE_ENABLE_URI_00_ERROR + Tcl_SetVar2(interp, "sqlite_options", "uri_00_error", "1", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options", "uri_00_error", "0", TCL_GLOBAL_ONLY); +#endif + #define LINKVAR(x) { \ static const int cv_ ## x = SQLITE_ ## x; \ Tcl_LinkVar(interp, "SQLITE_" #x, (char *)&(cv_ ## x), \ diff --git a/src/test_delete.c b/src/test_delete.c new file mode 100644 index 0000000000..731f11e257 --- /dev/null +++ b/src/test_delete.c @@ -0,0 +1,138 @@ +/* +** 2016 September 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. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains test code to delete an SQLite database and all +** of its associated files. Associated files include: +** +** * The journal file. +** * The wal file. +** * The SQLITE_ENABLE_8_3_NAMES version of the db, journal or wal files. +** * Files created by the test_multiplex.c module to extend any of the +** above. +*/ + +#if SQLITE_OS_WIN +# include +# define F_OK 0 +#else +# include +#endif +#include +#include +#include "sqlite3.h" + +/* The following #defines are copied from test_multiplex.c */ +#ifndef MX_CHUNK_NUMBER +# define MX_CHUNK_NUMBER 299 +#endif +#ifndef SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET +# define SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET 400 +#endif +#ifndef SQLITE_MULTIPLEX_WAL_8_3_OFFSET +# define SQLITE_MULTIPLEX_WAL_8_3_OFFSET 700 +#endif + +/* +** This routine is a copy of (most of) the code from SQLite function +** sqlite3FileSuffix3(). It modifies the filename in buffer z in the +** same way as SQLite does when in 8.3 filenames mode. +*/ +static void sqlite3Delete83Name(char *z){ + int i, sz; + sz = (int)strlen(z); + for(i=sz-1; i>0 && z[i]!='/' && z[i]!='.'; i--){} + if( z[i]=='.' && (sz>i+4) ) memmove(&z[i+1], &z[sz-3], 4); +} + +/* +** zFile is a filename. Assuming no error occurs, if this file exists, +** set *pbExists to true and unlink it. Or, if the file does not exist, +** set *pbExists to false before returning. +** +** If an error occurs, the value of errno is returned. Or, if no error +** occurs, zero is returned. +*/ +static int sqlite3DeleteUnlinkIfExists(const char *zFile, int *pbExists){ + int rc; + rc = access(zFile, F_OK); + if( rc ){ + if( errno==ENOENT ){ + if( pbExists ) *pbExists = 0; + return 0; + } + return errno; + } + if( pbExists ) *pbExists = 1; + rc = unlink(zFile); + if( rc ) return errno; + return 0; +} + +/* +** Delete the database file identified by the string argument passed to this +** function. The string must contain a filename, not an SQLite URI. +*/ +int sqlite3_delete_database( + const char *zFile /* File to delete */ +){ + char *zBuf; /* Buffer to sprintf() filenames to */ + int nBuf; /* Size of buffer in bytes */ + int rc = 0; /* System error code */ + int i; /* Iterate through azFmt[] and aMFile[] */ + + const char *azFmt[] = { "%s", "%s-journal", "%s-wal", "%s-shm" }; + + struct MFile { + const char *zFmt; + int iOffset; + int b83; + } aMFile[] = { + { "%s%03d", 0, 0 }, + { "%s-journal%03d", 0, 0 }, + { "%s-wal%03d", 0, 0 }, + { "%s%03d", 0, 1 }, + { "%s-journal%03d", SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET, 1 }, + { "%s-wal%03d", SQLITE_MULTIPLEX_WAL_8_3_OFFSET, 1 }, + }; + + /* Allocate a buffer large enough for any of the files that need to be + ** deleted. */ + nBuf = (int)strlen(zFile) + 100; + zBuf = (char*)sqlite3_malloc(nBuf); + if( zBuf==0 ) return SQLITE_NOMEM; + + /* Delete both the regular and 8.3 filenames versions of the database, + ** journal, wal and shm files. */ + for(i=0; rc==0 && izFmt, zFile, iChunk+p->iOffset); + if( p->b83 ) sqlite3Delete83Name(zBuf); + rc = sqlite3DeleteUnlinkIfExists(zBuf, &bExists); + if( bExists==0 || rc!=0 ) break; + } + } + + sqlite3_free(zBuf); + return (rc ? SQLITE_ERROR : SQLITE_OK); +} diff --git a/src/test_fs.c b/src/test_fs.c index 87088bdae7..dd590a6623 100644 --- a/src/test_fs.c +++ b/src/test_fs.c @@ -499,9 +499,13 @@ static int fstreeFilter( char aWild[2] = { '\0', '\0' }; #if SQLITE_OS_WIN - zRoot = sqlite3_mprintf("%s%c", getenv("SystemDrive"), '/'); + const char *zDrive = windirent_getenv("fstreeDrive"); + if( zDrive==0 ){ + zDrive = windirent_getenv("SystemDrive"); + } + zRoot = sqlite3_mprintf("%s%c", zDrive, '/'); nRoot = sqlite3Strlen30(zRoot); - zPrefix = sqlite3_mprintf("%s", getenv("SystemDrive")); + zPrefix = sqlite3_mprintf("%s", zDrive); nPrefix = sqlite3Strlen30(zPrefix); #else zRoot = "/"; diff --git a/src/test_func.c b/src/test_func.c index 26f0d369ef..c7860fe887 100644 --- a/src/test_func.c +++ b/src/test_func.c @@ -155,7 +155,7 @@ static void test_destructor_count( ** arguments. It returns the text value returned by the sqlite3_errmsg16() ** API function. */ -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE void sqlite3BeginBenignMalloc(void); void sqlite3EndBenignMalloc(void); #else diff --git a/src/test_multiplex.c b/src/test_multiplex.c index 1027aa1326..ed8c9f7fa7 100644 --- a/src/test_multiplex.c +++ b/src/test_multiplex.c @@ -68,19 +68,6 @@ #define MAX_PAGE_SIZE 0x10000 #define DEFAULT_SECTOR_SIZE 0x1000 -/* -** For a build without mutexes, no-op the mutex calls. -*/ -#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0 -#define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8) -#define sqlite3_mutex_free(X) -#define sqlite3_mutex_enter(X) -#define sqlite3_mutex_try(X) SQLITE_OK -#define sqlite3_mutex_leave(X) -#define sqlite3_mutex_held(X) ((void)(X),1) -#define sqlite3_mutex_notheld(X) ((void)(X),1) -#endif /* SQLITE_THREADSAFE==0 */ - /* Maximum chunk number */ #define MX_CHUNK_NUMBER 299 @@ -139,7 +126,6 @@ struct multiplexGroup { unsigned int szChunk; /* Chunk size used for this group */ unsigned char bEnabled; /* TRUE to use Multiplex VFS for this file */ unsigned char bTruncate; /* TRUE to enable truncation of databases */ - multiplexGroup *pNext, *pPrev; /* Doubly linked list of all group objects */ }; /* @@ -187,28 +173,9 @@ static struct { /* True when this shim has been initialized. */ int isInitialized; - - /* For run-time access any of the other global data structures in this - ** shim, the following mutex must be held. In practice, all this mutex - ** protects is add/remove operations to/from the linked list of group objects - ** starting at pGroups below. More specifically, it protects the value of - ** pGroups itself, and the pNext/pPrev fields of each multiplexGroup - ** structure. */ - sqlite3_mutex *pMutex; - - /* List of multiplexGroup objects. - */ - multiplexGroup *pGroups; } gMultiplex; /************************* Utility Routines *********************************/ -/* -** Acquire and release the mutex used to serialize access to the -** list of multiplexGroups. -*/ -static void multiplexEnter(void){ sqlite3_mutex_enter(gMultiplex.pMutex); } -static void multiplexLeave(void){ sqlite3_mutex_leave(gMultiplex.pMutex); } - /* ** Compute a string length that is limited to what can be stored in ** lower 30 bits of a 32-bit signed integer. @@ -519,7 +486,6 @@ static int multiplexOpen( /* We need to create a group structure and manage ** access to this group of files. */ - multiplexEnter(); pMultiplexOpen = (multiplexConn*)pConn; if( rc==SQLITE_OK ){ @@ -626,16 +592,11 @@ static int multiplexOpen( }else{ pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV2; } - /* place this group at the head of our list */ - pGroup->pNext = gMultiplex.pGroups; - if( gMultiplex.pGroups ) gMultiplex.pGroups->pPrev = pGroup; - gMultiplex.pGroups = pGroup; }else{ multiplexFreeComponents(pGroup); sqlite3_free(pGroup); } } - multiplexLeave(); sqlite3_free(zToFree); return rc; } @@ -738,17 +699,8 @@ static int multiplexClose(sqlite3_file *pConn){ multiplexConn *p = (multiplexConn*)pConn; multiplexGroup *pGroup = p->pGroup; int rc = SQLITE_OK; - multiplexEnter(); multiplexFreeComponents(pGroup); - /* remove from linked list */ - if( pGroup->pNext ) pGroup->pNext->pPrev = pGroup->pPrev; - if( pGroup->pPrev ){ - pGroup->pPrev->pNext = pGroup->pNext; - }else{ - gMultiplex.pGroups = pGroup->pNext; - } sqlite3_free(pGroup); - multiplexLeave(); return rc; } @@ -845,7 +797,6 @@ static int multiplexTruncate(sqlite3_file *pConn, sqlite3_int64 size){ multiplexConn *p = (multiplexConn*)pConn; multiplexGroup *pGroup = p->pGroup; int rc = SQLITE_OK; - multiplexEnter(); if( !pGroup->bEnabled ){ sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0); if( pSubOpen==0 ){ @@ -877,7 +828,6 @@ static int multiplexTruncate(sqlite3_file *pConn, sqlite3_int64 size){ } if( rc ) rc = SQLITE_IOERR_TRUNCATE; } - multiplexLeave(); return rc; } @@ -888,7 +838,6 @@ static int multiplexSync(sqlite3_file *pConn, int flags){ multiplexGroup *pGroup = p->pGroup; int rc = SQLITE_OK; int i; - multiplexEnter(); for(i=0; inReal; i++){ sqlite3_file *pSubOpen = pGroup->aReal[i].p; if( pSubOpen ){ @@ -896,7 +845,6 @@ static int multiplexSync(sqlite3_file *pConn, int flags){ if( rc2!=SQLITE_OK ) rc = rc2; } } - multiplexLeave(); return rc; } @@ -908,7 +856,6 @@ static int multiplexFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){ multiplexGroup *pGroup = p->pGroup; int rc = SQLITE_OK; int i; - multiplexEnter(); if( !pGroup->bEnabled ){ sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0); if( pSubOpen==0 ){ @@ -924,7 +871,6 @@ static int multiplexFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){ *pSize = i*(sqlite3_int64)pGroup->szChunk + sz; } } - multiplexLeave(); return rc; } @@ -1151,11 +1097,6 @@ int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault){ pOrigVfs = sqlite3_vfs_find(zOrigVfsName); if( pOrigVfs==0 ) return SQLITE_ERROR; assert( pOrigVfs!=&gMultiplex.sThisVfs ); - gMultiplex.pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); - if( !gMultiplex.pMutex ){ - return SQLITE_NOMEM; - } - gMultiplex.pGroups = NULL; gMultiplex.isInitialized = 1; gMultiplex.pOrigVfs = pOrigVfs; gMultiplex.sThisVfs = *pOrigVfs; @@ -1214,14 +1155,7 @@ int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault){ int sqlite3_multiplex_shutdown(int eForce){ int rc = SQLITE_OK; if( gMultiplex.isInitialized==0 ) return SQLITE_MISUSE; - if( gMultiplex.pGroups ){ - sqlite3_log(SQLITE_MISUSE, "sqlite3_multiplex_shutdown() called " - "while database connections are still open"); - if( !eForce ) return SQLITE_MISUSE; - rc = SQLITE_MISUSE; - } gMultiplex.isInitialized = 0; - sqlite3_mutex_free(gMultiplex.pMutex); sqlite3_vfs_unregister(&gMultiplex.sThisVfs); memset(&gMultiplex, 0, sizeof(gMultiplex)); return rc; @@ -1299,61 +1233,6 @@ static int SQLITE_TCLAPI test_multiplex_shutdown( return TCL_OK; } -/* -** tclcmd: sqlite3_multiplex_dump -*/ -static int SQLITE_TCLAPI test_multiplex_dump( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - Tcl_Obj *pResult; - Tcl_Obj *pGroupTerm; - multiplexGroup *pGroup; - int i; - int nChunks = 0; - - UNUSED_PARAMETER(clientData); - UNUSED_PARAMETER(objc); - UNUSED_PARAMETER(objv); - - pResult = Tcl_NewObj(); - multiplexEnter(); - for(pGroup=gMultiplex.pGroups; pGroup; pGroup=pGroup->pNext){ - pGroupTerm = Tcl_NewObj(); - - if( pGroup->zName ){ - pGroup->zName[pGroup->nName] = '\0'; - Tcl_ListObjAppendElement(interp, pGroupTerm, - Tcl_NewStringObj(pGroup->zName, -1)); - }else{ - Tcl_ListObjAppendElement(interp, pGroupTerm, Tcl_NewObj()); - } - Tcl_ListObjAppendElement(interp, pGroupTerm, - Tcl_NewIntObj(pGroup->nName)); - Tcl_ListObjAppendElement(interp, pGroupTerm, - Tcl_NewIntObj(pGroup->flags)); - - /* count number of chunks with open handles */ - for(i=0; inReal; i++){ - if( pGroup->aReal[i].p!=0 ) nChunks++; - } - Tcl_ListObjAppendElement(interp, pGroupTerm, - Tcl_NewIntObj(nChunks)); - - Tcl_ListObjAppendElement(interp, pGroupTerm, - Tcl_NewIntObj(pGroup->szChunk)); - Tcl_ListObjAppendElement(interp, pGroupTerm, - Tcl_NewIntObj(pGroup->nReal)); - - Tcl_ListObjAppendElement(interp, pResult, pGroupTerm); - } - multiplexLeave(); - Tcl_SetObjResult(interp, pResult); - return TCL_OK; -} - /* ** Tclcmd: test_multiplex_control HANDLE DBNAME SUB-COMMAND ?INT-VALUE? */ @@ -1428,7 +1307,6 @@ int Sqlitemultiplex_Init(Tcl_Interp *interp){ } aCmd[] = { { "sqlite3_multiplex_initialize", test_multiplex_initialize }, { "sqlite3_multiplex_shutdown", test_multiplex_shutdown }, - { "sqlite3_multiplex_dump", test_multiplex_dump }, { "sqlite3_multiplex_control", test_multiplex_control }, }; int i; diff --git a/src/test_sqllog.c b/src/test_sqllog.c index 31d5ad2f5b..9b207cf07b 100644 --- a/src/test_sqllog.c +++ b/src/test_sqllog.c @@ -313,7 +313,7 @@ static void sqllogCopydb(struct SLConn *p, const char *zSearch, int bLog){ /* Generate a file-name to use for the copy of this database */ iDb = sqllogglobal.iNextDb++; - zInit = sqlite3_mprintf("%s_%d.db", sqllogglobal.zPrefix, iDb); + zInit = sqlite3_mprintf("%s_%02d.db", sqllogglobal.zPrefix, iDb); /* Create the backup */ assert( sqllogglobal.bRec==0 ); @@ -376,7 +376,7 @@ static void sqllogOpenlog(struct SLConn *p){ char *zVar = getenv(ENVIRONMENT_VARIABLE1_NAME); if( zVar==0 || strlen(zVar)+10>=(sizeof(sqllogglobal.zPrefix)) ) return; sqlite3_snprintf(sizeof(sqllogglobal.zPrefix), sqllogglobal.zPrefix, - "%s/sqllog_%d", zVar, getProcessId()); + "%s/sqllog_%05d", zVar, getProcessId()); sqlite3_snprintf(sizeof(sqllogglobal.zIdx), sqllogglobal.zIdx, "%s.idx", sqllogglobal.zPrefix); if( getenv(ENVIRONMENT_VARIABLE2_NAME) ){ @@ -387,7 +387,7 @@ static void sqllogOpenlog(struct SLConn *p){ } /* Open the log file */ - zLog = sqlite3_mprintf("%s_%d.sql", sqllogglobal.zPrefix, p->iLog); + zLog = sqlite3_mprintf("%s_%05d.sql", sqllogglobal.zPrefix, p->iLog); p->fd = fopen(zLog, "w"); sqlite3_free(zLog); if( p->fd==0 ){ diff --git a/src/test_windirent.c b/src/test_windirent.c index 11d7dc07d0..044bc7f414 100644 --- a/src/test_windirent.c +++ b/src/test_windirent.c @@ -17,6 +17,34 @@ #include "test_windirent.h" +/* +** Implementation of the POSIX getenv() function using the Win32 API. +** This function is not thread-safe. +*/ +const char *windirent_getenv( + const char *name +){ + static char value[32768]; /* Maximum length, per MSDN */ + DWORD dwSize = sizeof(value) / sizeof(char); /* Size in chars */ + DWORD dwRet; /* Value returned by GetEnvironmentVariableA() */ + + memset(value, 0, sizeof(value)); + dwRet = GetEnvironmentVariableA(name, value, dwSize); + if( dwRet==0 || dwRet>dwSize ){ + /* + ** The function call to GetEnvironmentVariableA() failed -OR- + ** the buffer is not large enough. Either way, return NULL. + */ + return 0; + }else{ + /* + ** The function call to GetEnvironmentVariableA() succeeded + ** -AND- the buffer contains the entire value. + */ + return value; + } +} + /* ** Implementation of the POSIX opendir() function using the MSVCRT. */ @@ -32,7 +60,7 @@ LPDIR opendir( /* TODO: Remove this if Unix-style root paths are not used. */ if( sqlite3_stricmp(dirname, "/")==0 ){ - dirname = getenv("SystemDrive"); + dirname = windirent_getenv("SystemDrive"); } _snprintf(data.name, namesize, "%s\\*", dirname); diff --git a/src/test_windirent.h b/src/test_windirent.h index 0b8d1a7b51..be454988e3 100644 --- a/src/test_windirent.h +++ b/src/test_windirent.h @@ -92,6 +92,13 @@ struct DIR { DIRENT d_next; /* DIRENT constructed based on "_findnext". */ }; +/* +** Provide the function prototype for the POSIX compatiable getenv() +** function. This function is not thread-safe. +*/ + +extern const char *windirent_getenv(const char *name); + /* ** Finally, we can provide the function prototypes for the opendir(), ** readdir(), readdir_r(), and closedir() POSIX functions. diff --git a/src/tokenize.c b/src/tokenize.c index 9230da2a0f..c400dcd554 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -81,13 +81,13 @@ static const unsigned char aiClass[] = { /* 1x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, /* 2x */ 27, 27, 27, 27, 27, 7, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, /* 3x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, -/* 4x */ 7, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 12, 17, 20, 10, +/* 4x */ 7, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 12, 17, 20, 10, /* 5x */ 24, 27, 27, 27, 27, 27, 27, 27, 27, 27, 15, 4, 21, 18, 19, 27, -/* 6x */ 11, 16, 27, 27, 27, 27, 27, 27, 27, 27, 27, 23, 22, 1, 13, 7, +/* 6x */ 11, 16, 27, 27, 27, 27, 27, 27, 27, 27, 27, 23, 22, 1, 13, 6, /* 7x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 8, 5, 5, 5, 8, 14, 8, /* 8x */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27, /* 9x */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27, -/* 9x */ 25, 1, 1, 1, 1, 1, 1, 0, 1, 1, 27, 27, 27, 27, 27, 27, +/* Ax */ 27, 25, 1, 1, 1, 1, 1, 0, 1, 1, 27, 27, 27, 27, 27, 27, /* Bx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 9, 27, 27, 27, 27, 27, /* Cx */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27, /* Dx */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27, @@ -500,8 +500,7 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ assert( pParse->pNewTable==0 ); assert( pParse->pNewTrigger==0 ); assert( pParse->nVar==0 ); - assert( pParse->nzVar==0 ); - assert( pParse->azVar==0 ); + assert( pParse->pVList==0 ); while( 1 ){ assert( i>=0 ); if( zSql[i]!=0 ){ @@ -588,8 +587,7 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ if( pParse->pWithToFree ) sqlite3WithDelete(db, pParse->pWithToFree); sqlite3DeleteTrigger(db, pParse->pNewTrigger); - for(i=pParse->nzVar-1; i>=0; i--) sqlite3DbFree(db, pParse->azVar[i]); - sqlite3DbFree(db, pParse->azVar); + sqlite3DbFree(db, pParse->pVList); while( pParse->pAinc ){ AutoincInfo *p = pParse->pAinc; pParse->pAinc = p->pNext; diff --git a/src/treeview.c b/src/treeview.c index 27996d46d7..0ea512b5c6 100644 --- a/src/treeview.c +++ b/src/treeview.c @@ -65,6 +65,7 @@ static void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){ va_start(ap, zFormat); sqlite3VXPrintf(&acc, zFormat, ap); va_end(ap); + assert( acc.nChar>0 ); if( zBuf[acc.nChar-1]!='\n' ) sqlite3StrAccumAppend(&acc, "\n", 1); sqlite3StrAccumFinish(&acc); fprintf(stdout,"%s", zBuf); @@ -120,7 +121,7 @@ void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 moreToFollow){ /* -** Generate a human-readable description of a the Select object. +** Generate a human-readable description of a Select object. */ void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 moreToFollow){ int n = 0; @@ -451,6 +452,15 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ sqlite3TreeViewExpr(pView, pExpr->pRight, 0); break; } + case TK_VECTOR: { + sqlite3TreeViewBareExprList(pView, pExpr->x.pList, "VECTOR"); + break; + } + case TK_SELECT_COLUMN: { + sqlite3TreeViewLine(pView, "SELECT-COLUMN %d", pExpr->iColumn); + sqlite3TreeViewSelect(pView, pExpr->pLeft->x.pSelect, 0); + break; + } default: { sqlite3TreeViewLine(pView, "op=%d", pExpr->op); break; @@ -467,21 +477,20 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ sqlite3TreeViewPop(pView); } + /* ** Generate a human-readable explanation of an expression list. */ -void sqlite3TreeViewExprList( +void sqlite3TreeViewBareExprList( TreeView *pView, const ExprList *pList, - u8 moreToFollow, const char *zLabel ){ - int i; - pView = sqlite3TreeViewPush(pView, moreToFollow); if( zLabel==0 || zLabel[0]==0 ) zLabel = "LIST"; if( pList==0 ){ sqlite3TreeViewLine(pView, "%s (empty)", zLabel); }else{ + int i; sqlite3TreeViewLine(pView, "%s", zLabel); for(i=0; inExpr; i++){ int j = pList->a[i].u.x.iOrderByCol; @@ -493,6 +502,15 @@ void sqlite3TreeViewExprList( if( j ) sqlite3TreeViewPop(pView); } } +} +void sqlite3TreeViewExprList( + TreeView *pView, + const ExprList *pList, + u8 moreToFollow, + const char *zLabel +){ + pView = sqlite3TreeViewPush(pView, moreToFollow); + sqlite3TreeViewBareExprList(pView, pList, zLabel); sqlite3TreeViewPop(pView); } diff --git a/src/trigger.c b/src/trigger.c index 72c31eb2a3..bdf964084b 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -96,7 +96,6 @@ void sqlite3BeginTrigger( int iDb; /* The database to store the trigger in */ Token *pName; /* The unqualified db name */ DbFixer sFix; /* State vector for the DB fixer */ - int iTabDb; /* Index of the database holding pTab */ assert( pName1!=0 ); /* pName1->z might be NULL, but not pName1 itself */ assert( pName2!=0 ); @@ -209,13 +208,13 @@ void sqlite3BeginTrigger( " trigger on table: %S", pTableName, 0); goto trigger_cleanup; } - iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema); #ifndef SQLITE_OMIT_AUTHORIZATION { + int iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema); int code = SQLITE_CREATE_TRIGGER; - const char *zDb = db->aDb[iTabDb].zName; - const char *zDbTrig = isTemp ? db->aDb[1].zName : zDb; + const char *zDb = db->aDb[iTabDb].zDbSName; + const char *zDbTrig = isTemp ? db->aDb[1].zDbSName : zDb; if( iTabDb==1 || isTemp ) code = SQLITE_CREATE_TEMP_TRIGGER; if( sqlite3AuthCheck(pParse, code, zName, pTab->zName, zDbTrig) ){ goto trigger_cleanup; @@ -309,7 +308,7 @@ void sqlite3FinishTrigger( z = sqlite3DbStrNDup(db, (char*)pAll->z, pAll->n); sqlite3NestedParse(pParse, "INSERT INTO %Q.%s VALUES('trigger',%Q,%Q,0,'CREATE TRIGGER %q')", - db->aDb[iDb].zName, SCHEMA_TABLE(iDb), zName, + db->aDb[iDb].zDbSName, MASTER_NAME, zName, pTrig->table, z); sqlite3DbFree(db, z); sqlite3ChangeCookie(pParse, iDb); @@ -498,7 +497,7 @@ void sqlite3DropTrigger(Parse *pParse, SrcList *pName, int noErr){ assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) ); for(i=OMIT_TEMPDB; inDb; i++){ int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ - if( zDb && sqlite3StrICmp(db->aDb[j].zName, zDb) ) continue; + if( zDb && sqlite3StrICmp(db->aDb[j].zDbSName, zDb) ) continue; assert( sqlite3SchemaMutexHeld(db, j, 0) ); pTrigger = sqlite3HashFind(&(db->aDb[j].pSchema->trigHash), zName); if( pTrigger ) break; @@ -544,7 +543,7 @@ void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){ #ifndef SQLITE_OMIT_AUTHORIZATION { int code = SQLITE_DROP_TRIGGER; - const char *zDb = db->aDb[iDb].zName; + const char *zDb = db->aDb[iDb].zDbSName; const char *zTab = SCHEMA_TABLE(iDb); if( iDb==1 ) code = SQLITE_DROP_TEMP_TRIGGER; if( sqlite3AuthCheck(pParse, code, pTrigger->zName, pTable->zName, zDb) || @@ -560,7 +559,7 @@ void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){ if( (v = sqlite3GetVdbe(pParse))!=0 ){ sqlite3NestedParse(pParse, "DELETE FROM %Q.%s WHERE name=%Q AND type='trigger'", - db->aDb[iDb].zName, SCHEMA_TABLE(iDb), pTrigger->zName + db->aDb[iDb].zDbSName, MASTER_NAME, pTrigger->zName ); sqlite3ChangeCookie(pParse, iDb); sqlite3VdbeAddOp4(v, OP_DropTrigger, iDb, 0, 0, pTrigger->zName, 0); @@ -663,8 +662,10 @@ static SrcList *targetSrcList( pSrc->a[pSrc->nSrc-1].zName = sqlite3DbStrDup(db, pStep->zTarget); iDb = sqlite3SchemaToIndex(db, pStep->pTrig->pSchema); if( iDb==0 || iDb>=2 ){ + const char *zDb; assert( iDbnDb ); - pSrc->a[pSrc->nSrc-1].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zName); + zDb = db->aDb[iDb].zDbSName; + pSrc->a[pSrc->nSrc-1].zDatabase = sqlite3DbStrDup(db, zDb); } } return pSrc; @@ -878,7 +879,6 @@ static TriggerPrg *codeRowTrigger( } pProgram->nMem = pSubParse->nMem; pProgram->nCsr = pSubParse->nTab; - pProgram->nOnce = pSubParse->nOnce; pProgram->token = (void *)pTrigger; pPrg->aColmask[0] = pSubParse->oldmask; pPrg->aColmask[1] = pSubParse->newmask; diff --git a/src/update.c b/src/update.c index 1436501264..5f89e31dac 100644 --- a/src/update.c +++ b/src/update.c @@ -69,7 +69,7 @@ void sqlite3ColumnDefault(Vdbe *v, Table *pTab, int i, int iReg){ sqlite3ValueFromExpr(sqlite3VdbeDb(v), pCol->pDflt, enc, pCol->affinity, &pValue); if( pValue ){ - sqlite3VdbeChangeP4(v, -1, (const char *)pValue, P4_MEM); + sqlite3VdbeAppendP4(v, pValue, P4_MEM); } #ifndef SQLITE_OMIT_FLOATING_POINT if( pTab->aCol[i].affinity==SQLITE_AFF_REAL ){ @@ -249,7 +249,7 @@ void sqlite3Update( int rc; rc = sqlite3AuthCheck(pParse, SQLITE_UPDATE, pTab->zName, j<0 ? "ROWID" : pTab->aCol[j].zName, - db->aDb[iDb].zName); + db->aDb[iDb].zDbSName); if( rc==SQLITE_DENY ){ goto update_cleanup; }else if( rc==SQLITE_IGNORE ){ @@ -282,12 +282,14 @@ void sqlite3Update( int reg; if( chngKey || hasFK || pIdx->pPartIdxWhere || pIdx==pPk ){ reg = ++pParse->nMem; + pParse->nMem += pIdx->nColumn; }else{ reg = 0; for(i=0; inKeyCol; i++){ i16 iIdxCol = pIdx->aiColumn[i]; if( iIdxCol<0 || aXRef[iIdxCol]>=0 ){ reg = ++pParse->nMem; + pParse->nMem += pIdx->nColumn; break; } } @@ -398,7 +400,7 @@ void sqlite3Update( }else{ sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, regKey, sqlite3IndexAffinityStr(db, pPk), nPk); - sqlite3VdbeAddOp2(v, OP_IdxInsert, iEph, regKey); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iEph, regKey, iPk, nPk); } sqlite3WhereEnd(pWInfo); } @@ -450,7 +452,7 @@ void sqlite3Update( }else if( pPk ){ labelContinue = sqlite3VdbeMakeLabel(v); sqlite3VdbeAddOp2(v, OP_Rewind, iEph, labelBreak); VdbeCoverage(v); - addrTop = sqlite3VdbeAddOp2(v, OP_RowKey, iEph, regKey); + addrTop = sqlite3VdbeAddOp2(v, OP_RowData, iEph, regKey); sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue, regKey, 0); VdbeCoverage(v); }else{ @@ -608,7 +610,7 @@ void sqlite3Update( regNewRowid ); if( !pParse->nested ){ - sqlite3VdbeChangeP4(v, -1, (char*)pTab, P4_TABLE); + sqlite3VdbeAppendP4(v, pTab, P4_TABLE); } #else if( hasFK || chngKey || pPk!=0 ){ @@ -657,15 +659,6 @@ void sqlite3Update( } sqlite3VdbeResolveLabel(v, labelBreak); - /* Close all tables */ - for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ - assert( aRegIdx ); - if( aToOpen[i+1] ){ - sqlite3VdbeAddOp2(v, OP_Close, iIdxCur+i, 0); - } - } - if( iDataCur=4003000 + && (GCC_VERSION>=4003000 || CLANG_VERSION>=3000000) u32 x; memcpy(&x,p,4); return __builtin_bswap32(x); @@ -1159,7 +1159,7 @@ void sqlite3Put4byte(unsigned char *p, u32 v){ #if SQLITE_BYTEORDER==4321 memcpy(p,&v,4); #elif SQLITE_BYTEORDER==1234 && !defined(SQLITE_DISABLE_INTRINSIC) \ - && defined(__GNUC__) && GCC_VERSION>=4003000 + && (GCC_VERSION>=4003000 || CLANG_VERSION>=3000000) u32 x = __builtin_bswap32(v); memcpy(p,&x,4); #elif SQLITE_BYTEORDER==1234 && !defined(SQLITE_DISABLE_INTRINSIC) \ @@ -1279,6 +1279,10 @@ int sqlite3SafetyCheckSickOrOk(sqlite3 *db){ ** overflow, leave *pA unchanged and return 1. */ int sqlite3AddInt64(i64 *pA, i64 iB){ +#if !defined(SQLITE_DISABLE_INTRINSIC) \ + && (GCC_VERSION>=5004000 || CLANG_VERSION>=4000000) + return __builtin_add_overflow(*pA, iB, pA); +#else i64 iA = *pA; testcase( iA==0 ); testcase( iA==1 ); testcase( iB==-1 ); testcase( iB==0 ); @@ -1293,8 +1297,13 @@ int sqlite3AddInt64(i64 *pA, i64 iB){ } *pA += iB; return 0; +#endif } int sqlite3SubInt64(i64 *pA, i64 iB){ +#if !defined(SQLITE_DISABLE_INTRINSIC) \ + && (GCC_VERSION>=5004000 || CLANG_VERSION>=4000000) + return __builtin_sub_overflow(*pA, iB, pA); +#else testcase( iB==SMALLEST_INT64+1 ); if( iB==SMALLEST_INT64 ){ testcase( (*pA)==(-1) ); testcase( (*pA)==0 ); @@ -1304,38 +1313,29 @@ int sqlite3SubInt64(i64 *pA, i64 iB){ }else{ return sqlite3AddInt64(pA, -iB); } +#endif } -#define TWOPOWER32 (((i64)1)<<32) -#define TWOPOWER31 (((i64)1)<<31) int sqlite3MulInt64(i64 *pA, i64 iB){ +#if !defined(SQLITE_DISABLE_INTRINSIC) \ + && (GCC_VERSION>=5004000 || CLANG_VERSION>=4000000) + return __builtin_mul_overflow(*pA, iB, pA); +#else i64 iA = *pA; - i64 iA1, iA0, iB1, iB0, r; - - iA1 = iA/TWOPOWER32; - iA0 = iA % TWOPOWER32; - iB1 = iB/TWOPOWER32; - iB0 = iB % TWOPOWER32; - if( iA1==0 ){ - if( iB1==0 ){ - *pA *= iB; - return 0; + if( iB>0 ){ + if( iA>LARGEST_INT64/iB ) return 1; + if( iA0 ){ + if( iBLARGEST_INT64/-iB ) return 1; } - r = iA0*iB1; - }else if( iB1==0 ){ - r = iA1*iB0; - }else{ - /* If both iA1 and iB1 are non-zero, overflow will result */ - return 1; } - testcase( r==(-TWOPOWER31)-1 ); - testcase( r==(-TWOPOWER31) ); - testcase( r==TWOPOWER31 ); - testcase( r==TWOPOWER31-1 ); - if( r<(-TWOPOWER31) || r>=TWOPOWER31 ) return 1; - r *= TWOPOWER32; - if( sqlite3AddInt64(&r, iA0*iB0) ) return 1; - *pA = r; + *pA = iA*iB; return 0; +#endif } /* @@ -1468,3 +1468,106 @@ u64 sqlite3LogEstToInt(LogEst x){ return x>=3 ? (n+8)<<(x-3) : (n+8)>>(3-x); } #endif /* defined SCANSTAT or STAT4 or ESTIMATED_ROWS */ + +/* +** Add a new name/number pair to a VList. This might require that the +** VList object be reallocated, so return the new VList. If an OOM +** error occurs, the original VList returned and the +** db->mallocFailed flag is set. +** +** A VList is really just an array of integers. To destroy a VList, +** simply pass it to sqlite3DbFree(). +** +** The first integer is the number of integers allocated for the whole +** VList. The second integer is the number of integers actually used. +** Each name/number pair is encoded by subsequent groups of 3 or more +** integers. +** +** Each name/number pair starts with two integers which are the numeric +** value for the pair and the size of the name/number pair, respectively. +** The text name overlays one or more following integers. The text name +** is always zero-terminated. +** +** Conceptually: +** +** struct VList { +** int nAlloc; // Number of allocated slots +** int nUsed; // Number of used slots +** struct VListEntry { +** int iValue; // Value for this entry +** int nSlot; // Slots used by this entry +** // ... variable name goes here +** } a[0]; +** } +** +** During code generation, pointers to the variable names within the +** VList are taken. When that happens, nAlloc is set to zero as an +** indication that the VList may never again be enlarged, since the +** accompanying realloc() would invalidate the pointers. +*/ +VList *sqlite3VListAdd( + sqlite3 *db, /* The database connection used for malloc() */ + VList *pIn, /* The input VList. Might be NULL */ + const char *zName, /* Name of symbol to add */ + int nName, /* Bytes of text in zName */ + int iVal /* Value to associate with zName */ +){ + int nInt; /* number of sizeof(int) objects needed for zName */ + char *z; /* Pointer to where zName will be stored */ + int i; /* Index in pIn[] where zName is stored */ + + nInt = nName/4 + 3; + assert( pIn==0 || pIn[0]>=3 ); /* Verify ok to add new elements */ + if( pIn==0 || pIn[1]+nInt > pIn[0] ){ + /* Enlarge the allocation */ + int nAlloc = (pIn ? pIn[0]*2 : 10) + nInt; + VList *pOut = sqlite3DbRealloc(db, pIn, nAlloc*sizeof(int)); + if( pOut==0 ) return pIn; + if( pIn==0 ) pOut[1] = 2; + pIn = pOut; + pIn[0] = nAlloc; + } + i = pIn[1]; + pIn[i] = iVal; + pIn[i+1] = nInt; + z = (char*)&pIn[i+2]; + pIn[1] = i+nInt; + assert( pIn[1]<=pIn[0] ); + memcpy(z, zName, nName); + z[nName] = 0; + return pIn; +} + +/* +** Return a pointer to the name of a variable in the given VList that +** has the value iVal. Or return a NULL if there is no such variable in +** the list +*/ +const char *sqlite3VListNumToName(VList *pIn, int iVal){ + int i, mx; + if( pIn==0 ) return 0; + mx = pIn[1]; + i = 2; + do{ + if( pIn[i]==iVal ) return (char*)&pIn[i+2]; + i += pIn[i+1]; + }while( iflags&SQLITE_CountRows) ); - return vacuumFinalize(db, pStmt, pzErrMsg); -} - -/* -** Execute zSql on database db. The statement returns exactly -** one column. Execute this as SQL on the same database. -*/ -static int execExecSql(sqlite3 *db, char **pzErrMsg, const char *zSql){ sqlite3_stmt *pStmt; int rc; - rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0); + /* printf("SQL: [%s]\n", zSql); fflush(stdout); */ + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); if( rc!=SQLITE_OK ) return rc; - - while( SQLITE_ROW==sqlite3_step(pStmt) ){ - rc = execSql(db, pzErrMsg, (char*)sqlite3_column_text(pStmt, 0)); - if( rc!=SQLITE_OK ){ - vacuumFinalize(db, pStmt, pzErrMsg); - return rc; + while( SQLITE_ROW==(rc = sqlite3_step(pStmt)) ){ + const char *zSubSql = (const char*)sqlite3_column_text(pStmt,0); + assert( sqlite3_strnicmp(zSql,"SELECT",6)==0 ); + if( zSubSql ){ + assert( zSubSql[0]!='S' ); + rc = execSql(db, pzErrMsg, zSubSql); + if( rc!=SQLITE_OK ) break; } } - - return vacuumFinalize(db, pStmt, pzErrMsg); + assert( rc!=SQLITE_ROW ); + if( rc==SQLITE_DONE ) rc = SQLITE_OK; + if( rc ){ + sqlite3SetString(pzErrMsg, db, sqlite3_errmsg(db)); + } + (void)sqlite3_finalize(pStmt); + return rc; +} +static int execSqlF(sqlite3 *db, char **pzErrMsg, const char *zSql, ...){ + char *z; + va_list ap; + int rc; + va_start(ap, zSql); + z = sqlite3VMPrintf(db, zSql, ap); + va_end(ap); + if( z==0 ) return SQLITE_NOMEM; + rc = execSql(db, pzErrMsg, z); + sqlite3DbFree(db, z); + return rc; } /* @@ -101,11 +96,12 @@ static int execExecSql(sqlite3 *db, char **pzErrMsg, const char *zSql){ ** transient would cause the database file to appear to be deleted ** following reboot. */ -void sqlite3Vacuum(Parse *pParse){ +void sqlite3Vacuum(Parse *pParse, Token *pNm){ Vdbe *v = sqlite3GetVdbe(pParse); - if( v ){ - sqlite3VdbeAddOp2(v, OP_Vacuum, 0, 0); - sqlite3VdbeUsesBtree(v, 0); + int iDb = pNm ? sqlite3TwoPartName(pParse, pNm, pNm, &pNm) : 0; + if( v && (iDb>=2 || iDb==0) ){ + sqlite3VdbeAddOp1(v, OP_Vacuum, iDb); + sqlite3VdbeUsesBtree(v, iDb); } return; } @@ -113,11 +109,10 @@ void sqlite3Vacuum(Parse *pParse){ /* ** This routine implements the OP_Vacuum opcode of the VDBE. */ -int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){ +int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db, int iDb){ int rc = SQLITE_OK; /* Return code from service routines */ Btree *pMain; /* The database being vacuumed */ Btree *pTemp; /* The temporary database we vacuum into */ - char *zSql = 0; /* SQL statements */ int saved_flags; /* Saved value of the db->flags */ int saved_nChange; /* Saved value of db->nChange */ int saved_nTotalChange; /* Saved value of db->nTotalChange */ @@ -126,6 +121,7 @@ int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){ int isMemDb; /* True if vacuuming a :memory: database */ int nRes; /* Bytes of reserved space at the end of each page */ int nDb; /* Number of attached databases */ + const char *zDbMain; /* Schema name of database to vacuum */ if( !db->autoCommit ){ sqlite3SetString(pzErrMsg, db, "cannot VACUUM from within a transaction"); @@ -143,11 +139,13 @@ int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){ saved_nChange = db->nChange; saved_nTotalChange = db->nTotalChange; saved_mTrace = db->mTrace; - db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks | SQLITE_PreferBuiltin; - db->flags &= ~(SQLITE_ForeignKeys | SQLITE_ReverseOrder); + db->flags |= (SQLITE_WriteSchema | SQLITE_IgnoreChecks + | SQLITE_PreferBuiltin | SQLITE_Vacuum); + db->flags &= ~(SQLITE_ForeignKeys | SQLITE_ReverseOrder | SQLITE_CountRows); db->mTrace = 0; - pMain = db->aDb[0].pBt; + zDbMain = db->aDb[iDb].zDbSName; + pMain = db->aDb[iDb].pBt; isMemDb = sqlite3PagerIsMemdb(sqlite3BtreePager(pMain)); /* Attach the temporary database as 'vacuum_db'. The synchronous pragma @@ -165,18 +163,12 @@ int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){ ** to write the journal header file. */ nDb = db->nDb; - if( sqlite3TempInMemory(db) ){ - zSql = "ATTACH ':memory:' AS vacuum_db;"; - }else{ - zSql = "ATTACH '' AS vacuum_db;"; - } - rc = execSql(db, pzErrMsg, zSql); - if( db->nDb>nDb ){ - pDb = &db->aDb[db->nDb-1]; - assert( strcmp(pDb->zName,"vacuum_db")==0 ); - } + rc = execSql(db, pzErrMsg, "ATTACH''AS vacuum_db"); if( rc!=SQLITE_OK ) goto end_of_vacuum; - pTemp = db->aDb[db->nDb-1].pBt; + assert( (db->nDb-1)==nDb ); + pDb = &db->aDb[nDb]; + assert( strcmp(pDb->zDbSName,"vacuum_db")==0 ); + pTemp = pDb->pBt; /* The call to execSql() to attach the temp database has left the file ** locked (as there was more than one active statement when the transaction @@ -197,16 +189,15 @@ int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){ } #endif - sqlite3BtreeSetCacheSize(pTemp, db->aDb[0].pSchema->cache_size); + sqlite3BtreeSetCacheSize(pTemp, db->aDb[iDb].pSchema->cache_size); sqlite3BtreeSetSpillSize(pTemp, sqlite3BtreeSetSpillSize(pMain,0)); - rc = execSql(db, pzErrMsg, "PRAGMA vacuum_db.synchronous=OFF"); - if( rc!=SQLITE_OK ) goto end_of_vacuum; + sqlite3BtreeSetPagerFlags(pTemp, PAGER_SYNCHRONOUS_OFF|PAGER_CACHESPILL); /* Begin a transaction and take an exclusive lock on the main database ** file. This is done before the sqlite3BtreeGetPageSize(pMain) call below, ** to ensure that we do not try to change the page-size on a WAL database. */ - rc = execSql(db, pzErrMsg, "BEGIN;"); + rc = execSql(db, pzErrMsg, "BEGIN"); if( rc!=SQLITE_OK ) goto end_of_vacuum; rc = sqlite3BtreeBeginTrans(pMain, 2); if( rc!=SQLITE_OK ) goto end_of_vacuum; @@ -233,64 +224,48 @@ int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){ /* Query the schema of the main database. Create a mirror schema ** in the temporary database. */ - rc = execExecSql(db, pzErrMsg, - "SELECT 'CREATE TABLE vacuum_db.' || substr(sql,14) " - " FROM sqlite_master WHERE type='table' AND name!='sqlite_sequence'" - " AND coalesce(rootpage,1)>0" + db->init.iDb = nDb; /* force new CREATE statements into vacuum_db */ + rc = execSqlF(db, pzErrMsg, + "SELECT sql FROM \"%w\".sqlite_master" + " WHERE type='table'AND name<>'sqlite_sequence'" + " AND coalesce(rootpage,1)>0", + zDbMain ); if( rc!=SQLITE_OK ) goto end_of_vacuum; - rc = execExecSql(db, pzErrMsg, - "SELECT 'CREATE INDEX vacuum_db.' || substr(sql,14)" - " FROM sqlite_master WHERE sql LIKE 'CREATE INDEX %' "); - if( rc!=SQLITE_OK ) goto end_of_vacuum; - rc = execExecSql(db, pzErrMsg, - "SELECT 'CREATE UNIQUE INDEX vacuum_db.' || substr(sql,21) " - " FROM sqlite_master WHERE sql LIKE 'CREATE UNIQUE INDEX %'"); + rc = execSqlF(db, pzErrMsg, + "SELECT sql FROM \"%w\".sqlite_master" + " WHERE type='index' AND length(sql)>10", + zDbMain + ); if( rc!=SQLITE_OK ) goto end_of_vacuum; + db->init.iDb = 0; /* Loop through the tables in the main database. For each, do ** an "INSERT INTO vacuum_db.xxx SELECT * FROM main.xxx;" to copy ** the contents to the temporary database. */ - assert( (db->flags & SQLITE_Vacuum)==0 ); - db->flags |= SQLITE_Vacuum; - rc = execExecSql(db, pzErrMsg, - "SELECT 'INSERT INTO vacuum_db.' || quote(name) " - "|| ' SELECT * FROM main.' || quote(name) || ';'" - "FROM main.sqlite_master " - "WHERE type = 'table' AND name!='sqlite_sequence' " - " AND coalesce(rootpage,1)>0" + rc = execSqlF(db, pzErrMsg, + "SELECT'INSERT INTO vacuum_db.'||quote(name)" + "||' SELECT*FROM\"%w\".'||quote(name)" + "FROM vacuum_db.sqlite_master " + "WHERE type='table'AND coalesce(rootpage,1)>0", + zDbMain ); assert( (db->flags & SQLITE_Vacuum)!=0 ); db->flags &= ~SQLITE_Vacuum; if( rc!=SQLITE_OK ) goto end_of_vacuum; - /* Copy over the sequence table - */ - rc = execExecSql(db, pzErrMsg, - "SELECT 'DELETE FROM vacuum_db.' || quote(name) || ';' " - "FROM vacuum_db.sqlite_master WHERE name='sqlite_sequence' " - ); - if( rc!=SQLITE_OK ) goto end_of_vacuum; - rc = execExecSql(db, pzErrMsg, - "SELECT 'INSERT INTO vacuum_db.' || quote(name) " - "|| ' SELECT * FROM main.' || quote(name) || ';' " - "FROM vacuum_db.sqlite_master WHERE name=='sqlite_sequence';" - ); - if( rc!=SQLITE_OK ) goto end_of_vacuum; - - /* Copy the triggers, views, and virtual tables from the main database ** over to the temporary database. None of these objects has any ** associated storage, so all we have to do is copy their entries ** from the SQLITE_MASTER table. */ - rc = execSql(db, pzErrMsg, - "INSERT INTO vacuum_db.sqlite_master " - " SELECT type, name, tbl_name, rootpage, sql" - " FROM main.sqlite_master" - " WHERE type='view' OR type='trigger'" - " OR (type='table' AND rootpage=0)" + rc = execSqlF(db, pzErrMsg, + "INSERT INTO vacuum_db.sqlite_master" + " SELECT*FROM \"%w\".sqlite_master" + " WHERE type IN('view','trigger')" + " OR(type='table'AND rootpage=0)", + zDbMain ); if( rc ) goto end_of_vacuum; @@ -344,6 +319,7 @@ int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){ end_of_vacuum: /* Restore the original value of db->flags */ + db->init.iDb = 0; db->flags = saved_flags; db->nChange = saved_nChange; db->nTotalChange = saved_nTotalChange; diff --git a/src/vdbe.c b/src/vdbe.c index 624ac2197e..89ab832989 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -111,7 +111,7 @@ int sqlite3_found_count = 0; ** Test a register to see if it exceeds the current maximum blob size. ** If it does, record the new maximum blob size. */ -#if defined(SQLITE_TEST) && !defined(SQLITE_OMIT_BUILTIN_TEST) +#if defined(SQLITE_TEST) && !defined(SQLITE_UNTESTABLE) # define UPDATE_MAX_BLOBSIZE(P) updateMaxBlobsize(P) #else # define UPDATE_MAX_BLOBSIZE(P) @@ -221,7 +221,7 @@ static VdbeCursor *allocateCursor( } if( SQLITE_OK==sqlite3VdbeMemClearAndResize(pMem, nByte) ){ p->apCsr[iCur] = pCx = (VdbeCursor*)pMem->z; - memset(pCx, 0, sizeof(VdbeCursor)); + memset(pCx, 0, offsetof(VdbeCursor,pAltCursor)); pCx->eCurType = eCurType; pCx->iDb = iDb; pCx->nField = nField; @@ -573,7 +573,7 @@ int sqlite3VdbeExec( sqlite3 *db = p->db; /* The database */ u8 resetSchemaOnFault = 0; /* Reset schema after an error if positive */ u8 encoding = ENC(db); /* The database encoding */ - int iCompare = 0; /* Result of last OP_Compare operation */ + int iCompare = 0; /* Result of last comparison */ unsigned nVmStep = 0; /* Number of virtual machine steps */ #ifndef SQLITE_OMIT_PROGRESS_CALLBACK unsigned nProgressLimit = 0;/* Invoke xProgress() when nVmStep reaches this */ @@ -905,7 +905,7 @@ case OP_Yield: { /* in1, jump */ } /* Opcode: HaltIfNull P1 P2 P3 P4 P5 -** Synopsis: if r[P3]=null halt +** Synopsis: if r[P3]=null halt ** ** Check the value in register P3. If it is NULL then Halt using ** parameter P1, P2, and P4 as if this were a Halt instruction. If the @@ -977,7 +977,7 @@ case OP_Halt: { p->rc = pOp->p1; p->errorAction = (u8)pOp->p2; p->pc = pcx; - assert( pOp->p5>=0 && pOp->p5<=4 ); + assert( pOp->p5<=4 ); if( p->rc ){ if( pOp->p5 ){ static const char * const azType[] = { "NOT NULL", "UNIQUE", "CHECK", @@ -1118,7 +1118,7 @@ case OP_String: { /* out2 */ } /* Opcode: Null P1 P2 P3 * * -** Synopsis: r[P2..P3]=NULL +** Synopsis: r[P2..P3]=NULL ** ** Write a NULL into registers P2. If P3 greater than P2, then also write ** NULL into register P3 and every register in between P2 and P3. If P3 @@ -1136,18 +1136,20 @@ case OP_Null: { /* out2 */ cnt = pOp->p3-pOp->p2; assert( pOp->p3<=(p->nMem+1 - p->nCursor) ); pOut->flags = nullFlag = pOp->p1 ? (MEM_Null|MEM_Cleared) : MEM_Null; + pOut->n = 0; while( cnt>0 ){ pOut++; memAboutToChange(p, pOut); sqlite3VdbeMemSetNull(pOut); pOut->flags = nullFlag; + pOut->n = 0; cnt--; } break; } /* Opcode: SoftNull P1 * * * * -** Synopsis: r[P1]=NULL +** Synopsis: r[P1]=NULL ** ** Set register P1 to have the value NULL as seen by the OP_MakeRecord ** instruction, but do not free any string or blob memory associated with @@ -1188,7 +1190,7 @@ case OP_Variable: { /* out2 */ Mem *pVar; /* Value being transferred */ assert( pOp->p1>0 && pOp->p1<=p->nVar ); - assert( pOp->p4.z==0 || pOp->p4.z==p->azVar[pOp->p1-1] ); + assert( pOp->p4.z==0 || pOp->p4.z==sqlite3VListNumToName(p->pVList,pOp->p1) ); pVar = &p->aVar[pOp->p1 - 1]; if( sqlite3VdbeMemTooBig(pVar) ){ goto too_big; @@ -1200,7 +1202,7 @@ case OP_Variable: { /* out2 */ } /* Opcode: Move P1 P2 P3 * * -** Synopsis: r[P2@P3]=r[P1@P3] +** Synopsis: r[P2@P3]=r[P1@P3] ** ** Move the P3 values in register P1..P1+P3-1 over into ** registers P2..P2+P3-1. Registers P1..P1+P3-1 are @@ -1310,7 +1312,7 @@ case OP_IntCopy: { /* out2 */ } /* Opcode: ResultRow P1 P2 * * * -** Synopsis: output=r[P1@P2] +** Synopsis: output=r[P1@P2] ** ** The registers P1 through P1+P2-1 contain a single row of ** results. This opcode causes the sqlite3_step() call to terminate @@ -1443,14 +1445,14 @@ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */ } /* Opcode: Add P1 P2 P3 * * -** Synopsis: r[P3]=r[P1]+r[P2] +** Synopsis: r[P3]=r[P1]+r[P2] ** ** Add the value in register P1 to the value in register P2 ** and store the result in register P3. ** If either input is NULL, the result is NULL. */ /* Opcode: Multiply P1 P2 P3 * * -** Synopsis: r[P3]=r[P1]*r[P2] +** Synopsis: r[P3]=r[P1]*r[P2] ** ** ** Multiply the value in register P1 by the value in register P2 @@ -1458,14 +1460,14 @@ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */ ** If either input is NULL, the result is NULL. */ /* Opcode: Subtract P1 P2 P3 * * -** Synopsis: r[P3]=r[P2]-r[P1] +** Synopsis: r[P3]=r[P2]-r[P1] ** ** Subtract the value in register P1 from the value in register P2 ** and store the result in register P3. ** If either input is NULL, the result is NULL. */ /* Opcode: Divide P1 P2 P3 * * -** Synopsis: r[P3]=r[P2]/r[P1] +** Synopsis: r[P3]=r[P2]/r[P1] ** ** Divide the value in register P1 by the value in register P2 ** and store the result in register P3 (P3=P2/P1). If the value in @@ -1473,7 +1475,7 @@ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */ ** NULL, the result is NULL. */ /* Opcode: Remainder P1 P2 P3 * * -** Synopsis: r[P3]=r[P2]%r[P1] +** Synopsis: r[P3]=r[P2]%r[P1] ** ** Compute the remainder after integer register P2 is divided by ** register P1 and store the result in register P3. @@ -1706,21 +1708,21 @@ case OP_Function: { } /* Opcode: BitAnd P1 P2 P3 * * -** Synopsis: r[P3]=r[P1]&r[P2] +** Synopsis: r[P3]=r[P1]&r[P2] ** ** Take the bit-wise AND of the values in register P1 and P2 and ** store the result in register P3. ** If either input is NULL, the result is NULL. */ /* Opcode: BitOr P1 P2 P3 * * -** Synopsis: r[P3]=r[P1]|r[P2] +** Synopsis: r[P3]=r[P1]|r[P2] ** ** Take the bit-wise OR of the values in register P1 and P2 and ** store the result in register P3. ** If either input is NULL, the result is NULL. */ /* Opcode: ShiftLeft P1 P2 P3 * * -** Synopsis: r[P3]=r[P2]<>r[P1] +** Synopsis: r[P3]=r[P2]>>r[P1] ** ** Shift the integer value in register P2 to the right by the ** number of bits specified by the integer in register P1. @@ -1788,7 +1790,7 @@ case OP_ShiftRight: { /* same as TK_RSHIFT, in1, in2, out3 */ } /* Opcode: AddImm P1 P2 * * * -** Synopsis: r[P1]=r[P1]+P2 +** Synopsis: r[P1]=r[P1]+P2 ** ** Add the constant P2 to the value in register P1. ** The result is always an integer. @@ -1880,14 +1882,61 @@ case OP_Cast: { /* in1 */ } #endif /* SQLITE_OMIT_CAST */ +/* Opcode: Eq P1 P2 P3 P4 P5 +** Synopsis: IF r[P3]==r[P1] +** +** Compare the values in register P1 and P3. If reg(P3)==reg(P1) then +** jump to address P2. Or if the SQLITE_STOREP2 flag is set in P5, then +** store the result of comparison in register P2. +** +** The SQLITE_AFF_MASK portion of P5 must be an affinity character - +** SQLITE_AFF_TEXT, SQLITE_AFF_INTEGER, and so forth. An attempt is made +** to coerce both inputs according to this affinity before the +** comparison is made. If the SQLITE_AFF_MASK is 0x00, then numeric +** affinity is used. Note that the affinity conversions are stored +** back into the input registers P1 and P3. So this opcode can cause +** persistent changes to registers P1 and P3. +** +** Once any conversions have taken place, and neither value is NULL, +** the values are compared. If both values are blobs then memcmp() is +** used to determine the results of the comparison. If both values +** are text, then the appropriate collating function specified in +** P4 is used to do the comparison. If P4 is not specified then +** memcmp() is used to compare text string. If both values are +** numeric, then a numeric comparison is used. If the two values +** are of different types, then numbers are considered less than +** strings and strings are considered less than blobs. +** +** If SQLITE_NULLEQ is set in P5 then the result of comparison is always either +** true or false and is never NULL. If both operands are NULL then the result +** of comparison is true. If either operand is NULL then the result is false. +** If neither operand is NULL the result is the same as it would be if +** the SQLITE_NULLEQ flag were omitted from P5. +** +** If both SQLITE_STOREP2 and SQLITE_KEEPNULL flags are set then the +** content of r[P2] is only changed if the new value is NULL or 0 (false). +** In other words, a prior r[P2] value will not be overwritten by 1 (true). +*/ +/* Opcode: Ne P1 P2 P3 P4 P5 +** Synopsis: IF r[P3]!=r[P1] +** +** This works just like the Eq opcode except that the jump is taken if +** the operands in registers P1 and P3 are not equal. See the Eq opcode for +** additional information. +** +** If both SQLITE_STOREP2 and SQLITE_KEEPNULL flags are set then the +** content of r[P2] is only changed if the new value is NULL or 1 (true). +** In other words, a prior r[P2] value will not be overwritten by 0 (false). +*/ /* Opcode: Lt P1 P2 P3 P4 P5 -** Synopsis: if r[P1]r[P3] goto P2 +** Synopsis: IF r[P3]>r[P1] ** ** This works just like the Lt opcode except that the jump is taken if ** the content of register P3 is greater than the content of ** register P1. See the Lt opcode for additional information. */ /* Opcode: Ge P1 P2 P3 P4 P5 -** Synopsis: if r[P1]>=r[P3] goto P2 +** Synopsis: IF r[P3]>=r[P1] ** ** This works just like the Lt opcode except that the jump is taken if ** the content of register P3 is greater than or equal to the content of @@ -1968,7 +1984,7 @@ case OP_Lt: /* same as TK_LT, jump, in1, in3 */ case OP_Le: /* same as TK_LE, jump, in1, in3 */ case OP_Gt: /* same as TK_GT, jump, in1, in3 */ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ - int res; /* Result of the comparison of pIn1 against pIn3 */ + int res, res2; /* Result of the comparison of pIn1 against pIn3 */ char affinity; /* Affinity to use for comparison */ u16 flags1; /* Copy of initial value of pIn1->flags */ u16 flags3; /* Copy of initial value of pIn3->flags */ @@ -1987,13 +2003,12 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ assert( pOp->opcode==OP_Eq || pOp->opcode==OP_Ne ); assert( (flags1 & MEM_Cleared)==0 ); assert( (pOp->p5 & SQLITE_JUMPIFNULL)==0 ); - if( (flags1&MEM_Null)!=0 - && (flags3&MEM_Null)!=0 + if( (flags1&flags3&MEM_Null)!=0 && (flags3&MEM_Cleared)==0 ){ - res = 0; /* Results are equal */ + res = 0; /* Operands are equal */ }else{ - res = 1; /* Results are not equal */ + res = 1; /* Operands are not equal */ } }else{ /* SQLITE_NULLEQ is clear and at least one operand is NULL, @@ -2002,6 +2017,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ */ if( pOp->p5 & SQLITE_STOREP2 ){ pOut = &aMem[pOp->p2]; + iCompare = 1; /* Operands are not equal */ memAboutToChange(p, pOut); MemSetTypeFlag(pOut, MEM_Null); REGISTER_TRACE(pOp->p2, pOut); @@ -2020,12 +2036,21 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ if( (flags1 | flags3)&MEM_Str ){ if( (flags1 & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){ applyNumericAffinity(pIn1,0); + testcase( flags3!=pIn3->flags ); /* Possible if pIn1==pIn3 */ flags3 = pIn3->flags; } if( (flags3 & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){ applyNumericAffinity(pIn3,0); } } + /* Handle the common case of integer comparison here, as an + ** optimization, to avoid a call to sqlite3MemCompare() */ + if( (pIn1->flags & pIn3->flags & MEM_Int)!=0 ){ + if( pIn3->u.i > pIn1->u.i ){ res = +1; goto compare_op; } + if( pIn3->u.i < pIn1->u.i ){ res = -1; goto compare_op; } + res = 0; + goto compare_op; + } }else if( affinity==SQLITE_AFF_TEXT ){ if( (flags1 & MEM_Str)==0 && (flags1 & (MEM_Int|MEM_Real))!=0 ){ testcase( pIn1->flags & MEM_Int ); @@ -2033,7 +2058,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ sqlite3VdbeMemStringify(pIn1, encoding, 1); testcase( (flags1&MEM_Dyn) != (pIn1->flags&MEM_Dyn) ); flags1 = (pIn1->flags & ~MEM_TypeMask) | (flags1 & MEM_TypeMask); - flags3 = pIn3->flags; + assert( pIn1!=pIn3 ); } if( (flags3 & MEM_Str)==0 && (flags3 & (MEM_Int|MEM_Real))!=0 ){ testcase( pIn3->flags & MEM_Int ); @@ -2044,23 +2069,16 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ } } assert( pOp->p4type==P4_COLLSEQ || pOp->p4.pColl==0 ); - if( flags1 & MEM_Zero ){ - sqlite3VdbeMemExpandBlob(pIn1); - flags1 &= ~MEM_Zero; - } - if( flags3 & MEM_Zero ){ - sqlite3VdbeMemExpandBlob(pIn3); - flags3 &= ~MEM_Zero; - } res = sqlite3MemCompare(pIn3, pIn1, pOp->p4.pColl); } +compare_op: switch( pOp->opcode ){ - case OP_Eq: res = res==0; break; - case OP_Ne: res = res!=0; break; - case OP_Lt: res = res<0; break; - case OP_Le: res = res<=0; break; - case OP_Gt: res = res>0; break; - default: res = res>=0; break; + case OP_Eq: res2 = res==0; break; + case OP_Ne: res2 = res; break; + case OP_Lt: res2 = res<0; break; + case OP_Le: res2 = res<=0; break; + case OP_Gt: res2 = res>0; break; + default: res2 = res>=0; break; } /* Undo any changes made by applyAffinity() to the input registers. */ @@ -2071,19 +2089,55 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ if( pOp->p5 & SQLITE_STOREP2 ){ pOut = &aMem[pOp->p2]; + iCompare = res; + res2 = res2!=0; /* For this path res2 must be exactly 0 or 1 */ + if( (pOp->p5 & SQLITE_KEEPNULL)!=0 ){ + /* The KEEPNULL flag prevents OP_Eq from overwriting a NULL with 1 + ** and prevents OP_Ne from overwriting NULL with 0. This flag + ** is only used in contexts where either: + ** (1) op==OP_Eq && (r[P2]==NULL || r[P2]==0) + ** (2) op==OP_Ne && (r[P2]==NULL || r[P2]==1) + ** Therefore it is not necessary to check the content of r[P2] for + ** NULL. */ + assert( pOp->opcode==OP_Ne || pOp->opcode==OP_Eq ); + assert( res2==0 || res2==1 ); + testcase( res2==0 && pOp->opcode==OP_Eq ); + testcase( res2==1 && pOp->opcode==OP_Eq ); + testcase( res2==0 && pOp->opcode==OP_Ne ); + testcase( res2==1 && pOp->opcode==OP_Ne ); + if( (pOp->opcode==OP_Eq)==res2 ) break; + } memAboutToChange(p, pOut); MemSetTypeFlag(pOut, MEM_Int); - pOut->u.i = res; + pOut->u.i = res2; REGISTER_TRACE(pOp->p2, pOut); }else{ VdbeBranchTaken(res!=0, (pOp->p5 & SQLITE_NULLEQ)?2:3); - if( res ){ + if( res2 ){ goto jump_to_p2; } } break; } +/* Opcode: ElseNotEq * P2 * * * +** +** This opcode must immediately follow an OP_Lt or OP_Gt comparison operator. +** If result of an OP_Eq comparison on the same two operands +** would have be NULL or false (0), then then jump to P2. +** If the result of an OP_Eq comparison on the two previous operands +** would have been true (1), then fall through. +*/ +case OP_ElseNotEq: { /* same as TK_ESCAPE, jump */ + assert( pOp>aOp ); + assert( pOp[-1].opcode==OP_Lt || pOp[-1].opcode==OP_Gt ); + assert( pOp[-1].p5 & SQLITE_STOREP2 ); + VdbeBranchTaken(iCompare!=0, 2); + if( iCompare!=0 ) goto jump_to_p2; + break; +} + + /* Opcode: Permutation * * * P4 * ** ** Set the permutation used by the OP_Compare operator to be the array @@ -2279,22 +2333,18 @@ case OP_BitNot: { /* same as TK_BITNOT, in1, out2 */ /* Opcode: Once P1 P2 * * * ** -** Check the "once" flag number P1. If it is set, jump to instruction P2. -** Otherwise, set the flag and fall through to the next instruction. -** In other words, this opcode causes all following opcodes up through P2 -** (but not including P2) to run just once and to be skipped on subsequent -** times through the loop. -** -** All "once" flags are initially cleared whenever a prepared statement -** first begins to run. +** If the P1 value is equal to the P1 value on the OP_Init opcode at +** instruction 0, then jump to P2. If the two P1 values differ, then +** set the P1 value on this opcode to equal the P1 value on the OP_Init +** and fall through. */ case OP_Once: { /* jump */ - assert( pOp->p1nOnceFlag ); - VdbeBranchTaken(p->aOnceFlag[pOp->p1]!=0, 2); - if( p->aOnceFlag[pOp->p1] ){ + assert( p->aOp[0].opcode==OP_Init ); + VdbeBranchTaken(p->aOp[0].p1==pOp->p1, 2); + if( p->aOp[0].p1==pOp->p1 ){ goto jump_to_p2; }else{ - p->aOnceFlag[pOp->p1] = 1; + pOp->p1 = p->aOp[0].p1; } break; } @@ -2333,7 +2383,7 @@ case OP_IfNot: { /* jump, in1 */ } /* Opcode: IsNull P1 P2 * * * -** Synopsis: if r[P1]==NULL goto P2 +** Synopsis: if r[P1]==NULL goto P2 ** ** Jump to P2 if the value in register P1 is NULL. */ @@ -2361,7 +2411,7 @@ case OP_NotNull: { /* same as TK_NOTNULL, jump, in1 */ } /* Opcode: Column P1 P2 P3 P4 P5 -** Synopsis: r[P3]=PX +** Synopsis: r[P3]=PX ** ** Interpret the data that cursor P1 points to as a structure built using ** the MakeRecord instruction. (See the MakeRecord opcode for additional @@ -2420,7 +2470,6 @@ case OP_Column: { assert( pC->eCurType!=CURTYPE_VTAB ); assert( pC->eCurType!=CURTYPE_PSEUDO || pC->nullRow ); assert( pC->eCurType!=CURTYPE_SORTER ); - pCrsr = pC->uc.pCursor; if( pC->cacheStatus!=p->cacheCtr ){ /*OPTIMIZATION-IF-FALSE*/ if( pC->nullRow ){ @@ -2436,6 +2485,7 @@ case OP_Column: { goto op_column_out; } }else{ + pCrsr = pC->uc.pCursor; assert( pC->eCurType==CURTYPE_BTREE ); assert( pCrsr ); assert( sqlite3BtreeCursorIsValid(pCrsr) ); @@ -2499,7 +2549,7 @@ case OP_Column: { /* Make sure zData points to enough of the record to cover the header. */ if( pC->aRow==0 ){ memset(&sMem, 0, sizeof(sMem)); - rc = sqlite3VdbeMemFromBtree(pCrsr, 0, aOffset[0], !pC->isTable, &sMem); + rc = sqlite3VdbeMemFromBtree(pC->uc.pCursor, 0, aOffset[0], &sMem); if( rc!=SQLITE_OK ) goto abort_due_to_error; zData = (u8*)sMem.z; }else{ @@ -2612,8 +2662,7 @@ case OP_Column: { static u8 aZero[8]; /* This is the bogus content */ sqlite3VdbeSerialGet(aZero, t, pDest); }else{ - rc = sqlite3VdbeMemFromBtree(pCrsr, aOffset[p2], len, !pC->isTable, - pDest); + rc = sqlite3VdbeMemFromBtree(pC->uc.pCursor, aOffset[p2], len, pDest); if( rc!=SQLITE_OK ) goto abort_due_to_error; sqlite3VdbeSerialGet((const u8*)pDest->z, t, pDest); pDest->flags &= ~MEM_Ephem; @@ -3146,12 +3195,12 @@ case OP_Transaction: { rc = sqlite3BtreeBeginTrans(pBt, pOp->p2); testcase( rc==SQLITE_BUSY_SNAPSHOT ); testcase( rc==SQLITE_BUSY_RECOVERY ); - if( (rc&0xff)==SQLITE_BUSY ){ - p->pc = (int)(pOp - aOp); - p->rc = rc; - goto vdbe_return; - } if( rc!=SQLITE_OK ){ + if( (rc&0xff)==SQLITE_BUSY ){ + p->pc = (int)(pOp - aOp); + p->rc = rc; + goto vdbe_return; + } goto abort_due_to_error; } @@ -3178,10 +3227,9 @@ case OP_Transaction: { } /* Gather the schema version number for checking: - ** IMPLEMENTATION-OF: R-32195-19465 The schema version is used by SQLite - ** each time a query is executed to ensure that the internal cache of the - ** schema used when compiling the SQL query matches the schema of the - ** database against which the compiled query is actually executed. + ** IMPLEMENTATION-OF: R-03189-51135 As each SQL statement runs, the schema + ** version is checked to ensure that the schema has not changed since the + ** SQL statement was prepared. */ sqlite3BtreeGetMeta(pBt, BTREE_SCHEMA_VERSION, (u32 *)&iMeta); iGen = db->aDb[pOp->p1].pSchema->iGeneration; @@ -3510,10 +3558,10 @@ case OP_OpenEphemeral: { if( pCx==0 ) goto no_mem; pCx->nullRow = 1; pCx->isEphemeral = 1; - rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->pBt, + rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->pBtx, BTREE_OMIT_JOURNAL | BTREE_SINGLE | pOp->p5, vfsFlags); if( rc==SQLITE_OK ){ - rc = sqlite3BtreeBeginTrans(pCx->pBt, 1); + rc = sqlite3BtreeBeginTrans(pCx->pBtx, 1); } if( rc==SQLITE_OK ){ /* If a transient index is required, create it by calling @@ -3521,21 +3569,20 @@ case OP_OpenEphemeral: { ** opening it. If a transient table is required, just use the ** automatically created table with root-page 1 (an BLOB_INTKEY table). */ - if( (pKeyInfo = pOp->p4.pKeyInfo)!=0 ){ + if( (pCx->pKeyInfo = pKeyInfo = pOp->p4.pKeyInfo)!=0 ){ int pgno; assert( pOp->p4type==P4_KEYINFO ); - rc = sqlite3BtreeCreateTable(pCx->pBt, &pgno, BTREE_BLOBKEY | pOp->p5); + rc = sqlite3BtreeCreateTable(pCx->pBtx, &pgno, BTREE_BLOBKEY | pOp->p5); if( rc==SQLITE_OK ){ assert( pgno==MASTER_ROOT+1 ); assert( pKeyInfo->db==db ); assert( pKeyInfo->enc==ENC(db) ); - pCx->pKeyInfo = pKeyInfo; - rc = sqlite3BtreeCursor(pCx->pBt, pgno, BTREE_WRCSR, + rc = sqlite3BtreeCursor(pCx->pBtx, pgno, BTREE_WRCSR, pKeyInfo, pCx->uc.pCursor); } pCx->isTable = 0; }else{ - rc = sqlite3BtreeCursor(pCx->pBt, MASTER_ROOT, BTREE_WRCSR, + rc = sqlite3BtreeCursor(pCx->pBtx, MASTER_ROOT, BTREE_WRCSR, 0, pCx->uc.pCursor); pCx->isTable = 1; } @@ -3767,7 +3814,8 @@ case OP_SeekGT: { /* jump, in3 */ if( pC->isTable ){ /* The BTREE_SEEK_EQ flag is only set on index cursors */ - assert( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ)==0 ); + assert( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ)==0 + || CORRUPT_DB ); /* The input value in P3 might be of any type: integer, real, string, ** blob, or NULL. But it needs to be an integer before we can do @@ -3854,7 +3902,6 @@ case OP_SeekGT: { /* jump, in3 */ #ifdef SQLITE_DEBUG { int i; for(i=0; iuc.pCursor, &r, 0, 0, &res); if( rc!=SQLITE_OK ){ @@ -3902,7 +3949,6 @@ seek_not_found: } break; } - /* Opcode: Found P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] @@ -3971,10 +4017,9 @@ case OP_Found: { /* jump, in3 */ int ii; VdbeCursor *pC; int res; - char *pFree; + UnpackedRecord *pFree; UnpackedRecord *pIdxKey; UnpackedRecord r; - char aTempRec[ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem)*4 + 7]; #ifdef SQLITE_TEST if( pOp->opcode!=OP_NoConflict ) sqlite3_found_count++; @@ -3991,26 +4036,24 @@ case OP_Found: { /* jump, in3 */ assert( pC->eCurType==CURTYPE_BTREE ); assert( pC->uc.pCursor!=0 ); assert( pC->isTable==0 ); - pFree = 0; if( pOp->p4.i>0 ){ r.pKeyInfo = pC->pKeyInfo; r.nField = (u16)pOp->p4.i; r.aMem = pIn3; +#ifdef SQLITE_DEBUG for(ii=0; iip3+ii, &r.aMem[ii]); -#endif } +#endif pIdxKey = &r; + pFree = 0; }else{ - pIdxKey = sqlite3VdbeAllocUnpackedRecord( - pC->pKeyInfo, aTempRec, sizeof(aTempRec), &pFree - ); + pFree = pIdxKey = sqlite3VdbeAllocUnpackedRecord(pC->pKeyInfo); if( pIdxKey==0 ) goto no_mem; assert( pIn3->flags & MEM_Blob ); - ExpandBlob(pIn3); + (void)ExpandBlob(pIn3); sqlite3VdbeRecordUnpack(pC->pKeyInfo, pIn3->n, pIn3->z, pIdxKey); } pIdxKey->default_rc = 0; @@ -4027,7 +4070,7 @@ case OP_Found: { /* jump, in3 */ } } rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, pIdxKey, 0, 0, &res); - sqlite3DbFree(db, pFree); + if( pFree ) sqlite3DbFree(db, pFree); if( rc!=SQLITE_OK ){ goto abort_due_to_error; } @@ -4254,7 +4297,7 @@ case OP_NewRowid: { /* out2 */ sqlite3VdbeMemIntegerify(pMem); assert( (pMem->flags & MEM_Int)!=0 ); /* mem(P3) holds an integer */ if( pMem->u.i==MAX_ROWID || pC->useRandomRowid ){ - rc = SQLITE_FULL; /* IMP: R-12275-61338 */ + rc = SQLITE_FULL; /* IMP: R-17817-00630 */ goto abort_due_to_error; } if( vu.i+1 ){ @@ -4306,15 +4349,10 @@ case OP_NewRowid: { /* out2 */ ** then rowid is stored for subsequent return by the ** sqlite3_last_insert_rowid() function (otherwise it is unmodified). ** -** If the OPFLAG_USESEEKRESULT flag of P5 is set and if the result of -** the last seek operation (OP_NotExists or OP_SeekRowid) was a success, -** then this -** operation will not attempt to find the appropriate row before doing -** the insert but will instead overwrite the row that the cursor is -** currently pointing to. Presumably, the prior OP_NotExists or -** OP_SeekRowid opcode -** has already positioned the cursor correctly. This is an optimization -** that boosts performance by avoiding redundant seeks. +** If the OPFLAG_USESEEKRESULT flag of P5 is set, the implementation might +** run faster by avoiding an unnecessary seek on cursor P1. However, +** the OPFLAG_USESEEKRESULT flag must only be set if there have been no prior +** seeks on the cursor or if the most recent seek used a key equal to P3. ** ** If the OPFLAG_ISUPDATE flag is set, then this opcode is part of an ** UPDATE operation. Otherwise (if the flag is clear) then this opcode @@ -4335,7 +4373,7 @@ case OP_NewRowid: { /* out2 */ ** for indices is OP_IdxInsert. */ /* Opcode: InsertInt P1 P2 P3 P4 P5 -** Synopsis: intkey=P3 data=r[P2] +** Synopsis: intkey=P3 data=r[P2] ** ** This works exactly like OP_Insert except that the key is the ** integer value P3, not the value of the integer stored in register P3. @@ -4377,7 +4415,7 @@ case OP_InsertInt: { if( pOp->p4type==P4_TABLE && HAS_UPDATE_HOOK(db) ){ assert( pC->isTable ); assert( pC->iDb>=0 ); - zDb = db->aDb[pC->iDb].zName; + zDb = db->aDb[pC->iDb].zDbSName; pTab = pOp->p4.pTab; assert( HasRowid(pTab) ); op = ((pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT); @@ -4451,7 +4489,7 @@ case OP_InsertInt: { ** P1 must not be pseudo-table. It has to be a real table with ** multiple rows. ** -** If P4 is not NULL then it points to a Table struture. In this case either +** If P4 is not NULL then it points to a Table object. In this case either ** the update or pre-update hook, or both, may be invoked. The P1 cursor must ** have been positioned using OP_NotFound prior to invoking this opcode in ** this case. Specifically, if one is configured, the pre-update hook is @@ -4494,7 +4532,7 @@ case OP_Delete: { if( pOp->p4type==P4_TABLE && HAS_UPDATE_HOOK(db) ){ assert( pC->iDb>=0 ); assert( pOp->p4.pTab!=0 ); - zDb = db->aDb[pC->iDb].zName; + zDb = db->aDb[pC->iDb].zDbSName; pTab = pOp->p4.pTab; if( (pOp->p5 & OPFLAG_SAVEPOSITION)!=0 && pC->isTable ){ pC->movetoTarget = sqlite3BtreeIntegerKey(pC->uc.pCursor); @@ -4538,6 +4576,7 @@ case OP_Delete: { rc = sqlite3BtreeDelete(pC->uc.pCursor, pOp->p5); pC->cacheStatus = CACHE_STALE; + pC->seekResult = 0; if( rc ) goto abort_due_to_error; /* Invoke the update-hook if required. */ @@ -4566,7 +4605,7 @@ case OP_ResetCount: { } /* Opcode: SorterCompare P1 P2 P3 P4 -** Synopsis: if key(P1)!=trim(r[P3],P4) goto P2 +** Synopsis: if key(P1)!=trim(r[P3],P4) goto P2 ** ** P1 is a sorter cursor. This instruction compares a prefix of the ** record blob in register P3 against a prefix of the entry that @@ -4627,26 +4666,18 @@ case OP_SorterData: { /* Opcode: RowData P1 P2 * * * ** Synopsis: r[P2]=data ** -** Write into register P2 the complete row data for cursor P1. +** Write into register P2 the complete row content for the row at +** which cursor P1 is currently pointing. ** There is no interpretation of the data. ** It is just copied onto the P2 register exactly as ** it is found in the database file. ** -** If the P1 cursor must be pointing to a valid row (not a NULL row) -** of a real table, not a pseudo-table. -*/ -/* Opcode: RowKey P1 P2 * * * -** Synopsis: r[P2]=key -** -** Write into register P2 the complete row key for cursor P1. -** There is no interpretation of the data. -** The key is copied onto the P2 register exactly as -** it is found in the database file. +** If cursor P1 is an index, then the content is the key of the row. +** If cursor P2 is a table, then the content extracted is the data. ** ** If the P1 cursor must be pointing to a valid row (not a NULL row) ** of a real table, not a pseudo-table. */ -case OP_RowKey: case OP_RowData: { VdbeCursor *pC; BtCursor *pCrsr; @@ -4655,19 +4686,16 @@ case OP_RowData: { pOut = &aMem[pOp->p2]; memAboutToChange(p, pOut); - /* Note that RowKey and RowData are really exactly the same instruction */ assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( pC->eCurType==CURTYPE_BTREE ); assert( isSorter(pC)==0 ); - assert( pC->isTable || pOp->opcode!=OP_RowData ); - assert( pC->isTable==0 || pOp->opcode==OP_RowData ); assert( pC->nullRow==0 ); assert( pC->uc.pCursor!=0 ); pCrsr = pC->uc.pCursor; - /* The OP_RowKey and OP_RowData opcodes always follow OP_NotExists or + /* The OP_RowData opcodes always follow OP_NotExists or ** OP_SeekRowid or OP_Rewind/Op_Next with no intervening instructions ** that might invalidate the cursor. ** If this where not the case, on of the following assert()s @@ -4692,11 +4720,7 @@ case OP_RowData: { } pOut->n = n; MemSetTypeFlag(pOut, MEM_Blob); - if( pC->isTable==0 ){ - rc = sqlite3BtreeKey(pCrsr, 0, n, pOut->z); - }else{ - rc = sqlite3BtreeData(pCrsr, 0, n, pOut->z); - } + rc = sqlite3BtreePayload(pCrsr, 0, n, pOut->z); if( rc ) goto abort_due_to_error; pOut->enc = SQLITE_UTF8; /* In case the blob is ever cast to text */ UPDATE_MAX_BLOBSIZE(pOut); @@ -4787,6 +4811,13 @@ case OP_NullRow: { ** 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. +** +** If P3 is -1, then the cursor is positioned at the end of the btree +** for the purpose of appending a new entry onto the btree. In that +** case P2 must be 0. It is assumed that the cursor is used only for +** appending and so if the cursor is valid, then the cursor must already +** be pointing at the end of the btree and so no changes are made to +** the cursor. */ case OP_Last: { /* jump */ VdbeCursor *pC; @@ -4800,23 +4831,36 @@ case OP_Last: { /* jump */ pCrsr = pC->uc.pCursor; res = 0; assert( pCrsr!=0 ); - rc = sqlite3BtreeLast(pCrsr, &res); - pC->nullRow = (u8)res; - pC->deferredMoveto = 0; - pC->cacheStatus = CACHE_STALE; pC->seekResult = pOp->p3; #ifdef SQLITE_DEBUG pC->seekOp = OP_Last; #endif - if( rc ) goto abort_due_to_error; - if( pOp->p2>0 ){ - VdbeBranchTaken(res!=0,2); - if( res ) goto jump_to_p2; + if( pOp->p3==0 || !sqlite3BtreeCursorIsValidNN(pCrsr) ){ + rc = sqlite3BtreeLast(pCrsr, &res); + pC->nullRow = (u8)res; + pC->deferredMoveto = 0; + pC->cacheStatus = CACHE_STALE; + if( rc ) goto abort_due_to_error; + if( pOp->p2>0 ){ + VdbeBranchTaken(res!=0,2); + if( res ) goto jump_to_p2; + } + }else{ + assert( pOp->p2==0 ); } break; } +/* Opcode: SorterSort P1 P2 * * * +** +** After all records have been inserted into the Sorter object +** identified by P1, invoke this opcode to actually do the sorting. +** Jump to P2 if there are no records to be sorted. +** +** This opcode is an alias for OP_Sort and OP_Rewind that is used +** for Sorter objects. +*/ /* Opcode: Sort P1 P2 * * * ** ** This opcode does exactly the same thing as OP_Rewind except that @@ -4944,6 +4988,13 @@ case OP_Rewind: { /* jump */ ** This opcode works just like Prev except that if cursor P1 is not ** open it behaves a no-op. */ +/* Opcode: SorterNext P1 P2 * * P5 +** +** This opcode works just like OP_Next except that P1 must be a +** sorter object for which the OP_SorterSort opcode has been +** invoked. This opcode advances the cursor to the next sorted +** record, or jumps to P2 if there are no more sorted records. +*/ case OP_SorterNext: { /* jump */ VdbeCursor *pC; int res; @@ -5000,27 +5051,41 @@ next_tail: goto check_for_interrupt; } -/* Opcode: IdxInsert P1 P2 P3 * P5 +/* Opcode: IdxInsert P1 P2 P3 P4 P5 ** Synopsis: key=r[P2] ** ** Register P2 holds an SQL index key made using the ** MakeRecord instructions. This opcode writes that key ** into the index P1. Data for the entry is nil. ** -** P3 is a flag that provides a hint to the b-tree layer that this -** insert is likely to be an append. +** If P4 is not zero, then it is the number of values in the unpacked +** key of reg(P2). In that case, P3 is the index of the first register +** for the unpacked key. The availability of the unpacked key can sometimes +** be an optimization. +** +** If P5 has the OPFLAG_APPEND bit set, that is a hint to the b-tree layer +** that this insert is likely to be an append. ** ** If P5 has the OPFLAG_NCHANGE bit set, then the change counter is ** incremented by this instruction. If the OPFLAG_NCHANGE bit is clear, ** then the change counter is unchanged. ** -** If P5 has the OPFLAG_USESEEKRESULT bit set, then the cursor must have -** just done a seek to the spot where the new entry is to be inserted. -** This flag avoids doing an extra seek. +** If the OPFLAG_USESEEKRESULT flag of P5 is set, the implementation might +** run faster by avoiding an unnecessary seek on cursor P1. However, +** the OPFLAG_USESEEKRESULT flag must only be set if there have been no prior +** seeks on the cursor or if the most recent seek used a key equivalent +** to P2. ** ** This instruction only works for indices. The equivalent instruction ** for tables is OP_Insert. */ +/* Opcode: SorterInsert P1 P2 * * * +** Synopsis: key=r[P2] +** +** Register P2 holds an SQL index key made using the +** MakeRecord instructions. This opcode writes that key +** into the sorter P1. Data for the entry is nil. +*/ case OP_SorterInsert: /* in2 */ case OP_IdxInsert: { /* in2 */ VdbeCursor *pC; @@ -5042,10 +5107,10 @@ case OP_IdxInsert: { /* in2 */ }else{ x.nKey = pIn2->n; x.pKey = pIn2->z; - x.nData = 0; - x.nZero = 0; - x.pData = 0; - rc = sqlite3BtreeInsert(pC->uc.pCursor, &x, pOp->p3, + x.aMem = aMem + pOp->p3; + x.nMem = (u16)pOp->p4.i; + rc = sqlite3BtreeInsert(pC->uc.pCursor, &x, + (pOp->p5 & OPFLAG_APPEND)!=0, ((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0) ); assert( pC->deferredMoveto==0 ); @@ -5089,11 +5154,12 @@ case OP_IdxDelete: { } assert( pC->deferredMoveto==0 ); pC->cacheStatus = CACHE_STALE; + pC->seekResult = 0; break; } /* Opcode: Seek P1 * P3 P4 * -** Synopsis: Move P3 to P1.rowid +** Synopsis: Move P3 to P1.rowid ** ** P1 is an open index cursor and P3 is a cursor on the corresponding ** table. This opcode does a deferred seek of the P3 table cursor @@ -5458,13 +5524,13 @@ case OP_ParseSchema: { assert( iDb>=0 && iDbnDb ); assert( DbHasProperty(db, iDb, DB_SchemaLoaded) ); /* Used to be a conditional */ { - zMaster = SCHEMA_TABLE(iDb); + zMaster = MASTER_NAME; initData.db = db; initData.iDb = pOp->p1; initData.pzErrMsg = &p->zErrMsg; zSql = sqlite3MPrintf(db, "SELECT name, rootpage, sql FROM '%q'.%s WHERE %s ORDER BY rowid", - db->aDb[iDb].zName, zMaster, pOp->p4.z); + db->aDb[iDb].zDbSName, zMaster, pOp->p4.z); if( zSql==0 ){ rc = SQLITE_NOMEM_BKPT; }else{ @@ -5600,7 +5666,7 @@ case OP_IntegrityCk: { #endif /* SQLITE_OMIT_INTEGRITY_CHECK */ /* Opcode: RowSetAdd P1 P2 * * * -** Synopsis: rowset(P1)=r[P2] +** Synopsis: rowset(P1)=r[P2] ** ** Insert the integer value held by register P2 into a boolean index ** held in register P1. @@ -5620,7 +5686,7 @@ case OP_RowSetAdd: { /* in1, in2 */ } /* Opcode: RowSetRead P1 P2 P3 * * -** Synopsis: r[P3]=rowset(P1) +** Synopsis: r[P3]=rowset(P1) ** ** Extract the smallest value from boolean index P1 and put that value into ** register P3. Or, if boolean index P1 is initially empty, leave P3 @@ -5769,8 +5835,7 @@ case OP_Program: { /* jump */ if( pProgram->nCsr==0 ) nMem++; nByte = ROUND8(sizeof(VdbeFrame)) + nMem * sizeof(Mem) - + pProgram->nCsr * sizeof(VdbeCursor *) - + pProgram->nOnce * sizeof(u8); + + pProgram->nCsr * sizeof(VdbeCursor *); pFrame = sqlite3DbMallocZero(db, nByte); if( !pFrame ){ goto no_mem; @@ -5790,8 +5855,6 @@ case OP_Program: { /* jump */ pFrame->aOp = p->aOp; pFrame->nOp = p->nOp; pFrame->token = pProgram->token; - pFrame->aOnceFlag = p->aOnceFlag; - pFrame->nOnceFlag = p->nOnceFlag; #ifdef SQLITE_ENABLE_STMT_SCANSTATUS pFrame->anExec = p->anExec; #endif @@ -5825,13 +5888,10 @@ case OP_Program: { /* jump */ p->apCsr = (VdbeCursor **)&aMem[p->nMem]; p->aOp = aOp = pProgram->aOp; p->nOp = pProgram->nOp; - p->aOnceFlag = (u8 *)&p->apCsr[p->nCursor]; - p->nOnceFlag = pProgram->nOnce; #ifdef SQLITE_ENABLE_STMT_SCANSTATUS p->anExec = 0; #endif pOp = &aOp[-1]; - memset(p->aOnceFlag, 0, p->nOnceFlag); break; } @@ -5975,29 +6035,42 @@ case OP_IfPos: { /* jump, in1 */ ** Otherwise, r[P2] is set to the sum of r[P1] and r[P3]. */ case OP_OffsetLimit: { /* in1, out2, in3 */ + i64 x; pIn1 = &aMem[pOp->p1]; pIn3 = &aMem[pOp->p3]; pOut = out2Prerelease(p, pOp); assert( pIn1->flags & MEM_Int ); assert( pIn3->flags & MEM_Int ); - pOut->u.i = pIn1->u.i<=0 ? -1 : pIn1->u.i+(pIn3->u.i>0?pIn3->u.i:0); + x = pIn1->u.i; + if( x<=0 || sqlite3AddInt64(&x, pIn3->u.i>0?pIn3->u.i:0) ){ + /* If the LIMIT is less than or equal to zero, loop forever. This + ** is documented. But also, if the LIMIT+OFFSET exceeds 2^63 then + ** also loop forever. This is undocumented. In fact, one could argue + ** that the loop should terminate. But assuming 1 billion iterations + ** per second (far exceeding the capabilities of any current hardware) + ** it would take nearly 300 years to actually reach the limit. So + ** looping forever is a reasonable approximation. */ + pOut->u.i = -1; + }else{ + pOut->u.i = x; + } break; } -/* Opcode: IfNotZero P1 P2 P3 * * -** Synopsis: if r[P1]!=0 then r[P1]-=P3, goto P2 +/* Opcode: IfNotZero P1 P2 * * * +** Synopsis: if r[P1]!=0 then r[P1]--, goto P2 ** ** Register P1 must contain an integer. If the content of register P1 is -** initially nonzero, then subtract P3 from the value in register P1 and -** jump to P2. If register P1 is initially zero, leave it unchanged -** and fall through. +** initially greater than zero, then decrement the value in register P1. +** If it is non-zero (negative or positive) and then also jump to P2. +** If register P1 is initially zero, leave it unchanged and fall through. */ case OP_IfNotZero: { /* jump, in1 */ pIn1 = &aMem[pOp->p1]; assert( pIn1->flags&MEM_Int ); VdbeBranchTaken(pIn1->u.i<0, 2); if( pIn1->u.i ){ - pIn1->u.i -= pOp->p3; + if( pIn1->u.i>0 ) pIn1->u.i--; goto jump_to_p2; } break; @@ -6006,13 +6079,13 @@ case OP_IfNotZero: { /* jump, in1 */ /* Opcode: DecrJumpZero P1 P2 * * * ** Synopsis: if (--r[P1])==0 goto P2 ** -** Register P1 must hold an integer. Decrement the value in register P1 -** then jump to P2 if the new value is exactly zero. +** Register P1 must hold an integer. Decrement the value in P1 +** and jump to P2 if the new value is exactly zero. */ case OP_DecrJumpZero: { /* jump, in1 */ pIn1 = &aMem[pOp->p1]; assert( pIn1->flags&MEM_Int ); - pIn1->u.i--; + if( pIn1->u.i>SMALLEST_INT64 ) pIn1->u.i--; VdbeBranchTaken(pIn1->u.i==0, 2); if( pIn1->u.i==0 ) goto jump_to_p2; break; @@ -6258,7 +6331,7 @@ case OP_JournalMode: { /* out2 */ ** file. An EXCLUSIVE lock may still be held on the database file ** after a successful return. */ - rc = sqlite3PagerCloseWal(pPager); + rc = sqlite3PagerCloseWal(pPager, db); if( rc==SQLITE_OK ){ sqlite3PagerSetJournalMode(pPager, eNew); } @@ -6293,15 +6366,14 @@ case OP_JournalMode: { /* out2 */ #endif /* SQLITE_OMIT_PRAGMA */ #if !defined(SQLITE_OMIT_VACUUM) && !defined(SQLITE_OMIT_ATTACH) -/* Opcode: Vacuum * * * * * +/* Opcode: Vacuum P1 * * * * ** -** Vacuum the entire database. This opcode will cause other virtual -** machines to be created and run. It may not be called from within -** a transaction. +** Vacuum the entire database P1. P1 is 0 for "main", and 2 or more +** for an attached database. The "temp" database may not be vacuumed. */ case OP_Vacuum: { assert( p->readOnly==0 ); - rc = sqlite3RunVacuum(&p->zErrMsg, db); + rc = sqlite3RunVacuum(&p->zErrMsg, db, pOp->p1); if( rc ) goto abort_due_to_error; break; } @@ -6807,8 +6879,8 @@ case OP_MaxPgcnt: { /* out2 */ #endif -/* Opcode: Init * P2 * P4 * -** Synopsis: Start at P2 +/* Opcode: Init P1 P2 * P4 * +** Synopsis: Start at P2 ** ** Programs contain a single instance of this opcode as the very first ** opcode. @@ -6818,9 +6890,13 @@ case OP_MaxPgcnt: { /* out2 */ ** Or if P4 is blank, use the string returned by sqlite3_sql(). ** ** If P2 is not zero, jump to instruction P2. +** +** Increment the value of P1 so that OP_Once opcodes will jump the +** first time they are evaluated for this run. */ case OP_Init: { /* jump */ char *zTrace; + int i; /* If the P4 argument is not NULL, then it must be an SQL comment string. ** The "--" string is broken up to prevent false-positives with srcck1.c. @@ -6832,6 +6908,7 @@ case OP_Init: { /* jump */ ** sqlite3_expanded_sql(P) otherwise. */ assert( pOp->p4.z==0 || strncmp(pOp->p4.z, "-" "- ", 3)==0 ); + assert( pOp==p->aOp ); /* Always instruction 0 */ #ifndef SQLITE_OMIT_TRACE if( (db->mTrace & (SQLITE_TRACE_STMT|SQLITE_TRACE_LEGACY))!=0 @@ -6853,10 +6930,10 @@ case OP_Init: { /* jump */ #ifdef SQLITE_USE_FCNTL_TRACE zTrace = (pOp->p4.z ? pOp->p4.z : p->zSql); if( zTrace ){ - int i; - for(i=0; inDb; i++){ - if( DbMaskTest(p->btreeMask, i)==0 ) continue; - sqlite3_file_control(db, db->aDb[i].zName, SQLITE_FCNTL_TRACE, zTrace); + int j; + for(j=0; jnDb; j++){ + if( DbMaskTest(p->btreeMask, j)==0 ) continue; + sqlite3_file_control(db, db->aDb[j].zDbSName, SQLITE_FCNTL_TRACE, zTrace); } } #endif /* SQLITE_USE_FCNTL_TRACE */ @@ -6868,8 +6945,15 @@ case OP_Init: { /* jump */ } #endif /* SQLITE_DEBUG */ #endif /* SQLITE_OMIT_TRACE */ - if( pOp->p2 ) goto jump_to_p2; - break; + assert( pOp->p2>0 ); + if( pOp->p1>=sqlite3GlobalConfig.iOnceResetThreshold ){ + for(i=1; inOp; i++){ + if( p->aOp[i].opcode==OP_Once ) p->aOp[i].p1 = 0; + } + pOp->p1 = 0; + } + pOp->p1++; + goto jump_to_p2; } #ifdef SQLITE_ENABLE_CURSOR_HINTS diff --git a/src/vdbe.h b/src/vdbe.h index a7bc84cea5..feaf116807 100644 --- a/src/vdbe.h +++ b/src/vdbe.h @@ -88,7 +88,6 @@ struct SubProgram { int nOp; /* Elements in aOp[] */ int nMem; /* Number of memory cells required */ int nCsr; /* Number of cursors required */ - int nOnce; /* Number of OP_Once instructions */ void *token; /* id that may be used to recursive triggers */ SubProgram *pNext; /* Next sub-program already visited */ }; @@ -111,22 +110,21 @@ typedef struct VdbeOpList VdbeOpList; #define P4_NOTUSED 0 /* The P4 parameter is not used */ #define P4_DYNAMIC (-1) /* Pointer to a string obtained from sqliteMalloc() */ #define P4_STATIC (-2) /* Pointer to a static string */ -#define P4_COLLSEQ (-4) /* P4 is a pointer to a CollSeq structure */ -#define P4_FUNCDEF (-5) /* P4 is a pointer to a FuncDef structure */ -#define P4_KEYINFO (-6) /* P4 is a pointer to a KeyInfo structure */ -#define P4_EXPR (-7) /* P4 is a pointer to an Expr tree */ -#define P4_MEM (-8) /* P4 is a pointer to a Mem* structure */ +#define P4_COLLSEQ (-3) /* P4 is a pointer to a CollSeq structure */ +#define P4_FUNCDEF (-4) /* P4 is a pointer to a FuncDef structure */ +#define P4_KEYINFO (-5) /* P4 is a pointer to a KeyInfo structure */ +#define P4_EXPR (-6) /* P4 is a pointer to an Expr tree */ +#define P4_MEM (-7) /* P4 is a pointer to a Mem* structure */ #define P4_TRANSIENT 0 /* P4 is a pointer to a transient string */ -#define P4_VTAB (-10) /* P4 is a pointer to an sqlite3_vtab structure */ -#define P4_MPRINTF (-11) /* P4 is a string obtained from sqlite3_mprintf() */ -#define P4_REAL (-12) /* P4 is a 64-bit floating point value */ -#define P4_INT64 (-13) /* P4 is a 64-bit signed integer */ -#define P4_INT32 (-14) /* P4 is a 32-bit signed integer */ -#define P4_INTARRAY (-15) /* P4 is a vector of 32-bit integers */ -#define P4_SUBPROGRAM (-18) /* P4 is a pointer to a SubProgram structure */ -#define P4_ADVANCE (-19) /* P4 is a pointer to BtreeNext() or BtreePrev() */ -#define P4_TABLE (-20) /* P4 is a pointer to a Table structure */ -#define P4_FUNCCTX (-21) /* P4 is a pointer to an sqlite3_context object */ +#define P4_VTAB (-8) /* P4 is a pointer to an sqlite3_vtab structure */ +#define P4_REAL (-9) /* P4 is a 64-bit floating point value */ +#define P4_INT64 (-10) /* P4 is a 64-bit signed integer */ +#define P4_INT32 (-11) /* P4 is a 32-bit signed integer */ +#define P4_INTARRAY (-12) /* P4 is a vector of 32-bit integers */ +#define P4_SUBPROGRAM (-13) /* P4 is a pointer to a SubProgram structure */ +#define P4_ADVANCE (-14) /* P4 is a pointer to BtreeNext() or BtreePrev() */ +#define P4_TABLE (-15) /* P4 is a pointer to a Table structure */ +#define P4_FUNCCTX (-16) /* P4 is a pointer to an sqlite3_context object */ /* Error message codes for OP_Halt */ #define P5_ConstraintNotNull 1 @@ -185,8 +183,10 @@ int sqlite3VdbeAddOp4Int(Vdbe*,int,int,int,int,int); void sqlite3VdbeEndCoroutine(Vdbe*,int); #if defined(SQLITE_DEBUG) && !defined(SQLITE_TEST_REALLOC_STRESS) void sqlite3VdbeVerifyNoMallocRequired(Vdbe *p, int N); + void sqlite3VdbeVerifyNoResultRow(Vdbe *p); #else # define sqlite3VdbeVerifyNoMallocRequired(A,B) +# define sqlite3VdbeVerifyNoResultRow(A) #endif VdbeOp *sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp, int iLineno); void sqlite3VdbeAddParseSchemaOp(Vdbe*,int,char*); @@ -199,6 +199,7 @@ void sqlite3VdbeJumpHere(Vdbe*, int addr); int sqlite3VdbeChangeToNoop(Vdbe*, int addr); int sqlite3VdbeDeletePriorOpcode(Vdbe*, u8 op); void sqlite3VdbeChangeP4(Vdbe*, int addr, const char *zP4, int N); +void sqlite3VdbeAppendP4(Vdbe*, void *pP4, int p4type); void sqlite3VdbeSetP4KeyInfo(Parse*, Index*); void sqlite3VdbeUsesBtree(Vdbe*, int); VdbeOp *sqlite3VdbeGetOp(Vdbe*, int); @@ -234,7 +235,7 @@ int sqlite3MemCompare(const Mem*, const Mem*, const CollSeq*); void sqlite3VdbeRecordUnpack(KeyInfo*,int,const void*,UnpackedRecord*); int sqlite3VdbeRecordCompare(int,const void*,UnpackedRecord*); int sqlite3VdbeRecordCompareWithSkip(int, const void *, UnpackedRecord *, int); -UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo *, char *, int, char **); +UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo*); typedef int (*RecordCompare)(int,const void*,UnpackedRecord*); RecordCompare sqlite3VdbeFindCompare(UnpackedRecord*); diff --git a/src/vdbeInt.h b/src/vdbeInt.h index ecb4fc5f06..3e6bb06889 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -52,9 +52,6 @@ typedef unsigned Bool; /* Opaque type used by code in vdbesort.c */ typedef struct VdbeSorter VdbeSorter; -/* Opaque type used by the explainer */ -typedef struct Explain Explain; - /* Elements of the linked list at Vdbe.pAuxData */ typedef struct AuxData AuxData; @@ -76,59 +73,68 @@ typedef struct AuxData AuxData; */ typedef struct VdbeCursor VdbeCursor; struct VdbeCursor { - u8 eCurType; /* One of the CURTYPE_* values above */ - i8 iDb; /* Index of cursor database in db->aDb[] (or -1) */ - u8 nullRow; /* True if pointing to a row with no data */ - u8 deferredMoveto; /* A call to sqlite3BtreeMoveto() is needed */ - u8 isTable; /* True for rowid tables. False for indexes */ + u8 eCurType; /* One of the CURTYPE_* values above */ + i8 iDb; /* Index of cursor database in db->aDb[] (or -1) */ + u8 nullRow; /* True if pointing to a row with no data */ + u8 deferredMoveto; /* A call to sqlite3BtreeMoveto() is needed */ + u8 isTable; /* True for rowid tables. False for indexes */ #ifdef SQLITE_DEBUG - u8 seekOp; /* Most recent seek operation on this cursor */ - u8 wrFlag; /* The wrFlag argument to sqlite3BtreeCursor() */ + u8 seekOp; /* Most recent seek operation on this cursor */ + u8 wrFlag; /* The wrFlag argument to sqlite3BtreeCursor() */ #endif - Bool isEphemeral:1; /* True for an ephemeral table */ - Bool useRandomRowid:1;/* Generate new record numbers semi-randomly */ - Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */ - Pgno pgnoRoot; /* Root page of the open btree cursor */ - i16 nField; /* Number of fields in the header */ - u16 nHdrParsed; /* Number of header fields parsed so far */ + Bool isEphemeral:1; /* True for an ephemeral table */ + Bool useRandomRowid:1; /* Generate new record numbers semi-randomly */ + Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */ + Btree *pBtx; /* Separate file holding temporary table */ + i64 seqCount; /* Sequence counter */ + int *aAltMap; /* Mapping from table to index column numbers */ + + /* Cached OP_Column parse information is only valid if cacheStatus matches + ** Vdbe.cacheCtr. Vdbe.cacheCtr will never take on the value of + ** CACHE_STALE (0) and so setting cacheStatus=CACHE_STALE guarantees that + ** the cache is out of date. */ + u32 cacheStatus; /* Cache is valid if this matches Vdbe.cacheCtr */ + int seekResult; /* Result of previous sqlite3BtreeMoveto() or 0 + ** if there have been no prior seeks on the cursor. */ + /* NB: seekResult does not distinguish between "no seeks have ever occurred + ** on this cursor" and "the most recent seek was an exact match". */ + + /* When a new VdbeCursor is allocated, only the fields above are zeroed. + ** The fields that follow are uninitialized, and must be individually + ** initialized prior to first use. */ + VdbeCursor *pAltCursor; /* Associated index cursor from which to read */ union { BtCursor *pCursor; /* CURTYPE_BTREE. Btree cursor */ sqlite3_vtab_cursor *pVCur; /* CURTYPE_VTAB. Vtab cursor */ int pseudoTableReg; /* CURTYPE_PSEUDO. Reg holding content. */ VdbeSorter *pSorter; /* CURTYPE_SORTER. Sorter object */ } uc; - Btree *pBt; /* Separate file holding temporary table */ - KeyInfo *pKeyInfo; /* Info about index keys needed by index cursors */ - int seekResult; /* Result of previous sqlite3BtreeMoveto() */ - i64 seqCount; /* Sequence counter */ - i64 movetoTarget; /* Argument to the deferred sqlite3BtreeMoveto() */ - VdbeCursor *pAltCursor; /* Associated index cursor from which to read */ - int *aAltMap; /* Mapping from table to index column numbers */ + KeyInfo *pKeyInfo; /* Info about index keys needed by index cursors */ + u32 iHdrOffset; /* Offset to next unparsed byte of the header */ + Pgno pgnoRoot; /* Root page of the open btree cursor */ + i16 nField; /* Number of fields in the header */ + u16 nHdrParsed; /* Number of header fields parsed so far */ + i64 movetoTarget; /* Argument to the deferred sqlite3BtreeMoveto() */ + u32 *aOffset; /* Pointer to aType[nField] */ + const u8 *aRow; /* Data for the current row, if all on one page */ + u32 payloadSize; /* Total number of bytes in the record */ + u32 szRow; /* Byte available in aRow */ #ifdef SQLITE_ENABLE_COLUMN_USED_MASK - u64 maskUsed; /* Mask of columns used by this cursor */ + u64 maskUsed; /* Mask of columns used by this cursor */ #endif - /* Cached information about the header for the data record that the - ** cursor is currently pointing to. Only valid if cacheStatus matches - ** Vdbe.cacheCtr. Vdbe.cacheCtr will never take on the value of - ** CACHE_STALE and so setting cacheStatus=CACHE_STALE guarantees that - ** the cache is out of date. - ** - ** aRow might point to (ephemeral) data for the current row, or it might - ** be NULL. - */ - u32 cacheStatus; /* Cache is valid if this matches Vdbe.cacheCtr */ - u32 payloadSize; /* Total number of bytes in the record */ - u32 szRow; /* Byte available in aRow */ - u32 iHdrOffset; /* Offset to next unparsed byte of the header */ - const u8 *aRow; /* Data for the current row, if all on one page */ - u32 *aOffset; /* Pointer to aType[nField] */ - u32 aType[1]; /* Type values for all entries in the record */ /* 2*nField extra array elements allocated for aType[], beyond the one ** static element declared in the structure. nField total array slots for ** aType[] and nField+1 array slots for aOffset[] */ + u32 aType[1]; /* Type values record decode. MUST BE LAST */ }; + +/* +** A value for VdbeCursor.cacheStatus that means the cache is always invalid. +*/ +#define CACHE_STALE 0 + /* ** When a sub-program is executed (OP_Program), a structure of this type ** is allocated to store the current value of the program counter, as @@ -157,7 +163,6 @@ struct VdbeFrame { Op *aOp; /* Program instructions for parent frame */ i64 *anExec; /* Event counters from parent frame */ Mem *aMem; /* Array of memory cells for parent frame */ - u8 *aOnceFlag; /* Array of OP_Once flags for parent frame */ VdbeCursor **apCsr; /* Array of Vdbe cursors for parent frame */ void *token; /* Copy of SubProgram.token */ i64 lastRowid; /* Last insert rowid (sqlite3.lastRowid) */ @@ -166,7 +171,6 @@ struct VdbeFrame { int pc; /* Program Counter in parent (calling) frame */ int nOp; /* Size of aOp array */ int nMem; /* Number of entries in aMem */ - int nOnceFlag; /* Number of entries in aOnceFlag */ int nChildMem; /* Number of memory cells for child frame */ int nChildCsr; /* Number of cursors for child frame */ int nChange; /* Statement changes (Vdbe.nChange) */ @@ -175,11 +179,6 @@ struct VdbeFrame { #define VdbeFrameMem(p) ((Mem *)&((u8 *)p)[ROUND8(sizeof(VdbeFrame))]) -/* -** A value for VdbeCursor.cacheValid that means the cache is always invalid. -*/ -#define CACHE_STALE 0 - /* ** Internally, the vdbe manipulates nearly all SQL values as Mem ** structures. Each Mem struct may cache multiple representations (string, @@ -320,18 +319,6 @@ struct sqlite3_context { sqlite3_value *argv[1]; /* Argument set */ }; -/* -** An Explain object accumulates indented output which is helpful -** in describing recursive data structures. -*/ -struct Explain { - Vdbe *pVdbe; /* Attach the explanation to this Vdbe */ - StrAccum str; /* The string being accumulated */ - int nIndent; /* Number of elements in aIndent */ - u16 aIndent[100]; /* Levels of indentation */ - char zBase[100]; /* Initial space */ -}; - /* A bitfield type for use inside of structures. Always follow with :N where ** N is the number of bits. */ @@ -356,34 +343,46 @@ struct ScanStatus { */ struct Vdbe { sqlite3 *db; /* The database connection that owns this statement */ + Vdbe *pPrev,*pNext; /* Linked list of VDBEs with the same Vdbe.db */ + Parse *pParse; /* Parsing context used to create this Vdbe */ + ynVar nVar; /* Number of entries in aVar[] */ + u32 magic; /* Magic number for sanity checking */ + int nMem; /* Number of memory locations currently allocated */ + int nCursor; /* Number of slots in apCsr[] */ + u32 cacheCtr; /* VdbeCursor row cache generation counter */ + int pc; /* The program counter */ + int rc; /* Value to return */ + int nChange; /* Number of db changes made since last reset */ + int iStatement; /* Statement number (or 0 if has not opened stmt) */ + i64 iCurrentTime; /* Value of julianday('now') for this statement */ + i64 nFkConstraint; /* Number of imm. FK constraints this VM */ + i64 nStmtDefCons; /* Number of def. constraints when stmt started */ + i64 nStmtDefImmCons; /* Number of def. imm constraints when stmt started */ + + /* When allocating a new Vdbe object, all of the fields below should be + ** initialized to zero or NULL */ + Op *aOp; /* Space to hold the virtual machine's program */ Mem *aMem; /* The memory locations */ Mem **apArg; /* Arguments to currently executing user function */ Mem *aColName; /* Column names to return */ Mem *pResultSet; /* Pointer to an array of results */ - Parse *pParse; /* Parsing context used to create this Vdbe */ - int nMem; /* Number of memory locations currently allocated */ - int nOp; /* Number of instructions in the program */ - int nCursor; /* Number of slots in apCsr[] */ - u32 magic; /* Magic number for sanity checking */ char *zErrMsg; /* Error message written here */ - Vdbe *pPrev,*pNext; /* Linked list of VDBEs with the same Vdbe.db */ VdbeCursor **apCsr; /* One element of this array for each open cursor */ Mem *aVar; /* Values for the OP_Variable opcode. */ - char **azVar; /* Name of variables */ - ynVar nVar; /* Number of entries in aVar[] */ - ynVar nzVar; /* Number of entries in azVar[] */ - u32 cacheCtr; /* VdbeCursor row cache generation counter */ - int pc; /* The program counter */ - int rc; /* Value to return */ + VList *pVList; /* Name of variables */ +#ifndef SQLITE_OMIT_TRACE + i64 startTime; /* Time when query started - used for profiling */ +#endif + int nOp; /* Number of instructions in the program */ #ifdef SQLITE_DEBUG int rcApp; /* errcode set by sqlite3_result_error_code() */ #endif u16 nResColumn; /* Number of columns in one row of the result set */ u8 errorAction; /* Recovery action to do in case of an error */ + u8 minWriteFileFormat; /* Minimum file format for writable database files */ bft expired:1; /* True if the VM needs to be recompiled */ bft doingRerun:1; /* True if rerunning after an auto-reprepare */ - u8 minWriteFileFormat; /* Minimum file format for writable database files */ bft explain:2; /* True if EXPLAIN present on SQL command */ bft changeCntOn:1; /* True to update the change-counter */ bft runOnlyOnce:1; /* Automatically expire on reset */ @@ -391,18 +390,9 @@ struct Vdbe { bft readOnly:1; /* True for statements that do not write */ bft bIsReader:1; /* True for statements that read */ bft isPrepareV2:1; /* True if prepared with prepare_v2() */ - int nChange; /* Number of db changes made since last reset */ yDbMask btreeMask; /* Bitmask of db->aDb[] entries referenced */ yDbMask lockMask; /* Subset of btreeMask that requires a lock */ - int iStatement; /* Statement number (or 0 if has not opened stmt) */ u32 aCounter[5]; /* Counters used by sqlite3_stmt_status() */ -#ifndef SQLITE_OMIT_TRACE - i64 startTime; /* Time when query started - used for profiling */ -#endif - i64 iCurrentTime; /* Value of julianday('now') for this statement */ - i64 nFkConstraint; /* Number of imm. FK constraints this VM */ - i64 nStmtDefCons; /* Number of def. constraints when stmt started */ - i64 nStmtDefImmCons; /* Number of def. imm constraints when stmt started */ char *zSql; /* Text of the SQL statement that generated this */ void *pFree; /* Free this when deleting the vdbe */ VdbeFrame *pFrame; /* Parent frame */ @@ -410,8 +400,6 @@ struct Vdbe { int nFrame; /* Number of frames in pFrame list */ u32 expmask; /* Binding to these vars invalidates VM */ SubProgram *pProgram; /* Linked list of all sub-programs used by VM */ - int nOnceFlag; /* Size of array aOnceFlag[] */ - u8 *aOnceFlag; /* Flags for OP_Once */ AuxData *pAuxData; /* Linked list of auxdata allocations */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS i64 *anExec; /* Number of times each op has been executed */ @@ -423,10 +411,11 @@ struct Vdbe { /* ** The following are allowed values for Vdbe.magic */ -#define VDBE_MAGIC_INIT 0x26bceaa5 /* Building a VDBE program */ -#define VDBE_MAGIC_RUN 0xbdf20da3 /* VDBE is ready to execute */ -#define VDBE_MAGIC_HALT 0x519c2973 /* VDBE has completed execution */ -#define VDBE_MAGIC_DEAD 0xb606c3c8 /* The VDBE has been deallocated */ +#define VDBE_MAGIC_INIT 0x16bceaa5 /* Building a VDBE program */ +#define VDBE_MAGIC_RUN 0x2df20da3 /* VDBE is ready to execute */ +#define VDBE_MAGIC_HALT 0x319c2973 /* VDBE has completed execution */ +#define VDBE_MAGIC_RESET 0x48fa9f76 /* Reset and ready to run again */ +#define VDBE_MAGIC_DEAD 0x5606c3c8 /* The VDBE has been deallocated */ /* ** Structure used to store the context required by the @@ -443,8 +432,8 @@ struct PreUpdate { int iNewReg; /* Register for new.* values */ i64 iKey1; /* First key value passed to hook */ i64 iKey2; /* Second key value passed to hook */ - int iPKey; /* If not negative index of IPK column */ Mem *aNew; /* Array of new.* values */ + Table *pTab; /* Schema object being upated */ }; /* @@ -497,7 +486,7 @@ void sqlite3VdbeIntegerAffinity(Mem*); int sqlite3VdbeMemRealify(Mem*); int sqlite3VdbeMemNumerify(Mem*); void sqlite3VdbeMemCast(Mem*,u8,u8); -int sqlite3VdbeMemFromBtree(BtCursor*,u32,u32,int,Mem*); +int sqlite3VdbeMemFromBtree(BtCursor*,u32,u32,Mem*); void sqlite3VdbeMemRelease(Mem *p); int sqlite3VdbeMemFinalize(Mem*, FuncDef*); const char *sqlite3OpcodeName(int); diff --git a/src/vdbeapi.c b/src/vdbeapi.c index b17c0e0a40..32794de9a0 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -169,7 +169,7 @@ int sqlite3_clear_bindings(sqlite3_stmt *pStmt){ const void *sqlite3_value_blob(sqlite3_value *pVal){ Mem *p = (Mem*)pVal; if( p->flags & (MEM_Blob|MEM_Str) ){ - if( sqlite3VdbeMemExpandBlob(p)!=SQLITE_OK ){ + if( ExpandBlob(p)!=SQLITE_OK ){ assert( p->flags==MEM_Null && p->z==0 ); return 0; } @@ -499,7 +499,7 @@ static int doWalCallbacks(sqlite3 *db){ nEntry = sqlite3PagerWalCallback(sqlite3BtreePager(pBt)); sqlite3BtreeLeave(pBt); if( db->xWalCallback && nEntry>0 && rc==SQLITE_OK ){ - rc = db->xWalCallback(db->pWalArg, db, db->aDb[i].zName, nEntry); + rc = db->xWalCallback(db->pWalArg, db, db->aDb[i].zDbSName, nEntry); } } } @@ -952,14 +952,13 @@ static Mem *columnMem(sqlite3_stmt *pStmt, int i){ Mem *pOut; pVm = (Vdbe *)pStmt; - if( pVm && pVm->pResultSet!=0 && inResColumn && i>=0 ){ - sqlite3_mutex_enter(pVm->db->mutex); + if( pVm==0 ) return (Mem*)columnNullValue(); + assert( pVm->db ); + sqlite3_mutex_enter(pVm->db->mutex); + if( pVm->pResultSet!=0 && inResColumn && i>=0 ){ pOut = &pVm->pResultSet[i]; }else{ - if( pVm && ALWAYS(pVm->db) ){ - sqlite3_mutex_enter(pVm->db->mutex); - sqlite3Error(pVm->db, SQLITE_RANGE); - } + sqlite3Error(pVm->db, SQLITE_RANGE); pOut = (Mem*)columnNullValue(); } return pOut; @@ -992,6 +991,8 @@ static void columnMallocFailure(sqlite3_stmt *pStmt) */ Vdbe *p = (Vdbe *)pStmt; if( p ){ + assert( p->db!=0 ); + assert( sqlite3_mutex_held(p->db->mutex) ); p->rc = sqlite3ApiExit(p->db, p->rc); sqlite3_mutex_leave(p->db->mutex); } @@ -1469,10 +1470,8 @@ int sqlite3_bind_parameter_count(sqlite3_stmt *pStmt){ */ const char *sqlite3_bind_parameter_name(sqlite3_stmt *pStmt, int i){ Vdbe *p = (Vdbe*)pStmt; - if( p==0 || i<1 || i>p->nzVar ){ - return 0; - } - return p->azVar[i-1]; + if( p==0 ) return 0; + return sqlite3VListNumToName(p->pVList, i); } /* @@ -1481,19 +1480,8 @@ const char *sqlite3_bind_parameter_name(sqlite3_stmt *pStmt, int i){ ** return 0. */ int sqlite3VdbeParameterIndex(Vdbe *p, const char *zName, int nName){ - int i; - if( p==0 ){ - return 0; - } - if( zName ){ - for(i=0; inzVar; i++){ - const char *z = p->azVar[i]; - if( z && strncmp(z,zName,nName)==0 && z[nName]==0 ){ - return i+1; - } - } - } - return 0; + if( p==0 || zName==0 ) return 0; + return sqlite3VListNameToNum(p->pVList, zName, nName); } int sqlite3_bind_parameter_index(sqlite3_stmt *pStmt, const char *zName){ return sqlite3VdbeParameterIndex((Vdbe*)pStmt, zName, sqlite3Strlen30(zName)); @@ -1568,7 +1556,7 @@ int sqlite3_stmt_readonly(sqlite3_stmt *pStmt){ */ int sqlite3_stmt_busy(sqlite3_stmt *pStmt){ Vdbe *v = (Vdbe*)pStmt; - return v!=0 && v->pc>=0 && v->magic==VDBE_MAGIC_RUN; + return v!=0 && v->magic==VDBE_MAGIC_RUN && v->pc>=0; } /* @@ -1656,10 +1644,9 @@ static UnpackedRecord *vdbeUnpackRecord( int nKey, const void *pKey ){ - char *dummy; /* Dummy argument for AllocUnpackedRecord() */ UnpackedRecord *pRet; /* Return value */ - pRet = sqlite3VdbeAllocUnpackedRecord(pKeyInfo, 0, 0, &dummy); + pRet = sqlite3VdbeAllocUnpackedRecord(pKeyInfo); if( pRet ){ memset(pRet->aMem, 0, sizeof(Mem)*(pKeyInfo->nField+1)); sqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, pRet); @@ -1694,7 +1681,7 @@ int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor); aRec = sqlite3DbMallocRaw(db, nRec); if( !aRec ) goto preupdate_old_out; - rc = sqlite3BtreeData(p->pCsr->uc.pCursor, 0, nRec, aRec); + rc = sqlite3BtreePayload(p->pCsr->uc.pCursor, 0, nRec, aRec); if( rc==SQLITE_OK ){ p->pUnpacked = vdbeUnpackRecord(&p->keyinfo, nRec, aRec); if( !p->pUnpacked ) rc = SQLITE_NOMEM; @@ -1709,9 +1696,14 @@ int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ if( iIdx>=p->pUnpacked->nField ){ *ppValue = (sqlite3_value *)columnNullValue(); }else{ + Mem *pMem = *ppValue = &p->pUnpacked->aMem[iIdx]; *ppValue = &p->pUnpacked->aMem[iIdx]; - if( iIdx==p->iPKey ){ - sqlite3VdbeMemSetInt64(*ppValue, p->iKey1); + if( iIdx==p->pTab->iPKey ){ + sqlite3VdbeMemSetInt64(pMem, p->iKey1); + }else if( p->pTab->aCol[iIdx].affinity==SQLITE_AFF_REAL ){ + if( pMem->flags & MEM_Int ){ + sqlite3VdbeMemRealify(pMem); + } } } @@ -1775,7 +1767,7 @@ int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ UnpackedRecord *pUnpack = p->pNewUnpacked; if( !pUnpack ){ Mem *pData = &p->v->aMem[p->iNewReg]; - rc = sqlite3VdbeMemExpandBlob(pData); + rc = ExpandBlob(pData); if( rc!=SQLITE_OK ) goto preupdate_new_out; pUnpack = vdbeUnpackRecord(&p->keyinfo, pData->n, pData->z); if( !pUnpack ){ @@ -1788,7 +1780,7 @@ int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ pMem = (sqlite3_value *)columnNullValue(); }else{ pMem = &pUnpack->aMem[iIdx]; - if( iIdx==p->iPKey ){ + if( iIdx==p->pTab->iPKey ){ sqlite3VdbeMemSetInt64(pMem, p->iKey2); } } @@ -1809,7 +1801,7 @@ int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ assert( iIdx>=0 && iIdxpCsr->nField ); pMem = &p->aNew[iIdx]; if( pMem->flags==0 ){ - if( iIdx==p->iPKey ){ + if( iIdx==p->pTab->iPKey ){ sqlite3VdbeMemSetInt64(pMem, p->iKey2); }else{ rc = sqlite3VdbeMemCopy(pMem, &p->v->aMem[p->iNewReg+1+iIdx]); diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 5a1c9fa3fb..8e912865fa 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -21,8 +21,9 @@ Vdbe *sqlite3VdbeCreate(Parse *pParse){ sqlite3 *db = pParse->db; Vdbe *p; - p = sqlite3DbMallocZero(db, sizeof(Vdbe) ); + p = sqlite3DbMallocRawNN(db, sizeof(Vdbe) ); if( p==0 ) return 0; + memset(&p->aOp, 0, sizeof(Vdbe)-offsetof(Vdbe,aOp)); p->db = db; if( db->pVdbe ){ db->pVdbe->pPrev = p; @@ -184,9 +185,8 @@ int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){ if( p->db->flags & SQLITE_VdbeAddopTrace ){ int jj, kk; Parse *pParse = p->pParse; - for(jj=kk=0; jjnColCache; jj++){ struct yColCache *x = pParse->aColCache + jj; - if( x->iLevel>pParse->iCacheLevel || x->iReg==0 ) continue; printf(" r[%d]={%d:%d}", x->iReg, x->iTable, x->iColumn); kk++; } @@ -313,7 +313,11 @@ int sqlite3VdbeAddOp4Int( int p4 /* The P4 operand as an integer */ ){ int addr = sqlite3VdbeAddOp3(p, op, p1, p2, p3); - sqlite3VdbeChangeP4(p, addr, SQLITE_INT_TO_PTR(p4), P4_INT32); + if( p->db->mallocFailed==0 ){ + VdbeOp *pOp = &p->aOp[addr]; + pOp->p4type = P4_INT32; + pOp->p4.i = p4; + } return addr; } @@ -374,7 +378,6 @@ void sqlite3VdbeResolveLabel(Vdbe *v, int x){ if( p->aLabel ){ p->aLabel[j] = v->nOp; } - p->iFixedOp = v->nOp - 1; } /* @@ -645,6 +648,22 @@ void sqlite3VdbeVerifyNoMallocRequired(Vdbe *p, int N){ } #endif +/* +** Verify that the VM passed as the only argument does not contain +** an OP_ResultRow opcode. Fail an assert() if it does. This is used +** by code in pragma.c to ensure that the implementation of certain +** pragmas comports with the flags specified in the mkpragmatab.tcl +** script. +*/ +#if defined(SQLITE_DEBUG) && !defined(SQLITE_TEST_REALLOC_STRESS) +void sqlite3VdbeVerifyNoResultRow(Vdbe *p){ + int i; + for(i=0; inOp; i++){ + assert( p->aOp[i].opcode!=OP_ResultRow ); + } +} +#endif + /* ** This function returns a pointer to the array of opcodes associated with ** the Vdbe passed as the first argument. It is the callers responsibility @@ -765,7 +784,8 @@ void sqlite3VdbeChangeP3(Vdbe *p, u32 addr, int val){ sqlite3VdbeGetOp(p,addr)->p3 = val; } void sqlite3VdbeChangeP5(Vdbe *p, u8 p5){ - if( !p->db->mallocFailed ) p->aOp[p->nOp-1].p5 = p5; + assert( p->nOp>0 || p->db->mallocFailed ); + if( p->nOp>0 ) p->aOp[p->nOp-1].p5 = p5; } /* @@ -773,7 +793,6 @@ void sqlite3VdbeChangeP5(Vdbe *p, u8 p5){ ** the address of the next instruction to be coded. */ void sqlite3VdbeJumpHere(Vdbe *p, int addr){ - p->pParse->iFixedOp = p->nOp - 1; sqlite3VdbeChangeP2(p, addr, p->nOp); } @@ -825,10 +844,6 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){ break; } #endif - case P4_MPRINTF: { - if( db->pnBytesFreed==0 ) sqlite3_free(p4); - break; - } case P4_FUNCDEF: { freeEphemeralFunction(db, (FuncDef*)p4); break; @@ -896,7 +911,7 @@ int sqlite3VdbeChangeToNoop(Vdbe *p, int addr){ ** then remove it. Return true if and only if an opcode was removed. */ int sqlite3VdbeDeletePriorOpcode(Vdbe *p, u8 op){ - if( (p->nOp-1)>(p->pParse->iFixedOp) && p->aOp[p->nOp-1].opcode==op ){ + if( p->nOp>0 && p->aOp[p->nOp-1].opcode==op ){ return sqlite3VdbeChangeToNoop(p, p->nOp-1); }else{ return 0; @@ -973,16 +988,42 @@ void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int n){ } } +/* +** Change the P4 operand of the most recently coded instruction +** to the value defined by the arguments. This is a high-speed +** version of sqlite3VdbeChangeP4(). +** +** The P4 operand must not have been previously defined. And the new +** P4 must not be P4_INT32. Use sqlite3VdbeChangeP4() in either of +** those cases. +*/ +void sqlite3VdbeAppendP4(Vdbe *p, void *pP4, int n){ + VdbeOp *pOp; + assert( n!=P4_INT32 && n!=P4_VTAB ); + assert( n<=0 ); + if( p->db->mallocFailed ){ + freeP4(p->db, n, pP4); + }else{ + assert( pP4!=0 ); + assert( p->nOp>0 ); + pOp = &p->aOp[p->nOp-1]; + assert( pOp->p4type==P4_NOTUSED ); + pOp->p4type = n; + pOp->p4.p = pP4; + } +} + /* ** Set the P4 on the most recently added opcode to the KeyInfo for the ** index given. */ void sqlite3VdbeSetP4KeyInfo(Parse *pParse, Index *pIdx){ Vdbe *v = pParse->pVdbe; + KeyInfo *pKeyInfo; assert( v!=0 ); assert( pIdx!=0 ); - sqlite3VdbeChangeP4(v, -1, (char*)sqlite3KeyInfoOfIndex(pParse, pIdx), - P4_KEYINFO); + pKeyInfo = sqlite3KeyInfoOfIndex(pParse, pIdx); + if( pKeyInfo ) sqlite3VdbeAppendP4(v, pKeyInfo, P4_KEYINFO); } #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS @@ -1094,12 +1135,21 @@ static int displayComment( const char *zSynopsis; int nOpName; int ii, jj; + char zAlt[50]; zOpName = sqlite3OpcodeName(pOp->opcode); nOpName = sqlite3Strlen30(zOpName); if( zOpName[nOpName+1] ){ int seenCom = 0; char c; zSynopsis = zOpName += nOpName + 1; + if( strncmp(zSynopsis,"IF ",3)==0 ){ + if( pOp->p5 & SQLITE_STOREP2 ){ + sqlite3_snprintf(sizeof(zAlt), zAlt, "r[P2] = (%s)", zSynopsis+3); + }else{ + sqlite3_snprintf(sizeof(zAlt), zAlt, "if %s goto P2", zSynopsis+3); + } + zSynopsis = zAlt; + } for(ii=jj=0; jjzName, pDef->nArg); break; } -#ifdef SQLITE_DEBUG +#if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) case P4_FUNCCTX: { FuncDef *pDef = pOp->p4.pCtx->pFunc; sqlite3XPrintf(&x, "%s(%d)", pDef->zName, pDef->nArg); @@ -1450,6 +1500,21 @@ void sqlite3VdbePrintOp(FILE *pOut, int pc, Op *pOp){ } #endif +/* +** Initialize an array of N Mem element. +*/ +static void initMemArray(Mem *p, int N, sqlite3 *db, u16 flags){ + while( (N--)>0 ){ + p->db = db; + p->flags = flags; + p->szMalloc = 0; +#ifdef SQLITE_DEBUG + p->pScopyFrom = 0; +#endif + p++; + } +} + /* ** Release an array of N Mem elements */ @@ -1661,6 +1726,7 @@ int sqlite3VdbeList( pMem->flags = MEM_Str|MEM_Term; zP4 = displayP4(pOp, pMem->z, pMem->szMalloc); if( zP4!=pMem->z ){ + pMem->n = 0; sqlite3VdbeMemSetStr(pMem, zP4, -1, SQLITE_UTF8, 0); }else{ assert( pMem->z!=0 ); @@ -1803,7 +1869,7 @@ void sqlite3VdbeRewind(Vdbe *p){ int i; #endif assert( p!=0 ); - assert( p->magic==VDBE_MAGIC_INIT ); + assert( p->magic==VDBE_MAGIC_INIT || p->magic==VDBE_MAGIC_RESET ); /* There should be at least one opcode. */ @@ -1860,7 +1926,6 @@ void sqlite3VdbeMakeReady( int nMem; /* Number of VM memory registers */ int nCursor; /* Number of cursors required */ int nArg; /* Number of arguments in subprograms */ - int nOnce; /* Number of OP_Once instructions */ int n; /* Loop counter */ struct ReusableSpace x; /* Reusable bulk memory */ @@ -1875,8 +1940,6 @@ void sqlite3VdbeMakeReady( nMem = pParse->nMem; nCursor = pParse->nTab; nArg = pParse->nMaxArg; - nOnce = pParse->nOnce; - if( nOnce==0 ) nOnce = 1; /* Ensure at least one byte in p->aOnceFlag[] */ /* Each cursor uses a memory cell. The first cursor (cursor 0) can ** use aMem[0] which is not otherwise used by the VDBE program. Allocate @@ -1895,10 +1958,7 @@ void sqlite3VdbeMakeReady( assert( EIGHT_BYTE_ALIGNMENT(x.pSpace) ); x.nFree = ROUNDDOWN8(pParse->szOpAlloc - n); /* Bytes of unused memory */ assert( x.nFree>=0 ); - if( x.nFree>0 ){ - memset(x.pSpace, 0, x.nFree); - assert( EIGHT_BYTE_ALIGNMENT(&x.pSpace[x.nFree]) ); - } + assert( EIGHT_BYTE_ALIGNMENT(&x.pSpace[x.nFree]) ); resolveP2Values(p, &nArg); p->usesStmtJournal = (u8)(pParse->isMultiWrite && pParse->mayAbort); @@ -1923,36 +1983,32 @@ void sqlite3VdbeMakeReady( p->aVar = allocSpace(&x, p->aVar, nVar*sizeof(Mem)); p->apArg = allocSpace(&x, p->apArg, nArg*sizeof(Mem*)); p->apCsr = allocSpace(&x, p->apCsr, nCursor*sizeof(VdbeCursor*)); - p->aOnceFlag = allocSpace(&x, p->aOnceFlag, nOnce); #ifdef SQLITE_ENABLE_STMT_SCANSTATUS p->anExec = allocSpace(&x, p->anExec, p->nOp*sizeof(i64)); #endif if( x.nNeeded==0 ) break; - x.pSpace = p->pFree = sqlite3DbMallocZero(db, x.nNeeded); + x.pSpace = p->pFree = sqlite3DbMallocRawNN(db, x.nNeeded); x.nFree = x.nNeeded; }while( !db->mallocFailed ); - p->nCursor = nCursor; - p->nOnceFlag = nOnce; - if( p->aVar ){ - p->nVar = (ynVar)nVar; - for(n=0; naVar[n].flags = MEM_Null; - p->aVar[n].db = db; - } - } - p->nzVar = pParse->nzVar; - p->azVar = pParse->azVar; - pParse->nzVar = 0; - pParse->azVar = 0; - if( p->aMem ){ - p->nMem = nMem; - for(n=0; naMem[n].flags = MEM_Undefined; - p->aMem[n].db = db; - } - } + p->pVList = pParse->pVList; + pParse->pVList = 0; p->explain = pParse->explain; + if( db->mallocFailed ){ + p->nVar = 0; + p->nCursor = 0; + p->nMem = 0; + }else{ + p->nCursor = nCursor; + p->nVar = (ynVar)nVar; + initMemArray(p->aVar, nVar, db, MEM_Null); + p->nMem = nMem; + initMemArray(p->aMem, nMem, db, MEM_Undefined); + memset(p->apCsr, 0, nCursor*sizeof(VdbeCursor*)); +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + memset(p->anExec, 0, p->nOp*sizeof(i64)); +#endif + } sqlite3VdbeRewind(p); } @@ -1964,15 +2020,15 @@ void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){ if( pCx==0 ){ return; } - assert( pCx->pBt==0 || pCx->eCurType==CURTYPE_BTREE ); + assert( pCx->pBtx==0 || pCx->eCurType==CURTYPE_BTREE ); switch( pCx->eCurType ){ case CURTYPE_SORTER: { sqlite3VdbeSorterClose(p->db, pCx); break; } case CURTYPE_BTREE: { - if( pCx->pBt ){ - sqlite3BtreeClose(pCx->pBt); + if( pCx->pBtx ){ + sqlite3BtreeClose(pCx->pBtx); /* The pCx->pCursor will be close automatically, if it exists, by ** the call above. */ }else{ @@ -2021,8 +2077,6 @@ int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS v->anExec = pFrame->anExec; #endif - v->aOnceFlag = pFrame->aOnceFlag; - v->nOnceFlag = pFrame->nOnceFlag; v->aOp = pFrame->aOp; v->nOp = pFrame->nOp; v->aMem = pFrame->aMem; @@ -2106,13 +2160,9 @@ void sqlite3VdbeSetNumCols(Vdbe *p, int nResColumn){ sqlite3DbFree(db, p->aColName); n = nResColumn*COLNAME_N; p->nResColumn = (u16)nResColumn; - p->aColName = pColName = (Mem*)sqlite3DbMallocZero(db, sizeof(Mem)*n ); + p->aColName = pColName = (Mem*)sqlite3DbMallocRawNN(db, sizeof(Mem)*n ); if( p->aColName==0 ) return; - while( n-- > 0 ){ - pColName->flags = MEM_Null; - pColName->db = p->db; - pColName++; - } + initMemArray(p->aColName, n, p->db, MEM_Null); } /* @@ -2580,7 +2630,6 @@ int sqlite3VdbeHalt(Vdbe *p){ if( db->mallocFailed ){ p->rc = SQLITE_NOMEM_BKPT; } - if( p->aOnceFlag ) memset(p->aOnceFlag, 0, p->nOnceFlag); closeAllCursors(p); if( p->magic!=VDBE_MAGIC_RUN ){ return SQLITE_OK; @@ -2895,7 +2944,7 @@ int sqlite3VdbeReset(Vdbe *p){ } #endif p->iCurrentTime = 0; - p->magic = VDBE_MAGIC_INIT; + p->magic = VDBE_MAGIC_RESET; return p->rc & db->errMask; } @@ -2957,26 +3006,29 @@ void sqlite3VdbeDeleteAuxData(sqlite3 *db, AuxData **pp, int iOp, int mask){ */ void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ SubProgram *pSub, *pNext; - int i; assert( p->db==0 || p->db==db ); - releaseMemArray(p->aVar, p->nVar); releaseMemArray(p->aColName, p->nResColumn*COLNAME_N); for(pSub=p->pProgram; pSub; pSub=pNext){ pNext = pSub->pNext; vdbeFreeOpArray(db, pSub->aOp, pSub->nOp); sqlite3DbFree(db, pSub); } - for(i=p->nzVar-1; i>=0; i--) sqlite3DbFree(db, p->azVar[i]); - sqlite3DbFree(db, p->azVar); + if( p->magic!=VDBE_MAGIC_INIT ){ + releaseMemArray(p->aVar, p->nVar); + sqlite3DbFree(db, p->pVList); + sqlite3DbFree(db, p->pFree); + } vdbeFreeOpArray(db, p->aOp, p->nOp); sqlite3DbFree(db, p->aColName); sqlite3DbFree(db, p->zSql); - sqlite3DbFree(db, p->pFree); #ifdef SQLITE_ENABLE_STMT_SCANSTATUS - for(i=0; inScan; i++){ - sqlite3DbFree(db, p->aScan[i].zName); + { + int i; + for(i=0; inScan; i++){ + sqlite3DbFree(db, p->aScan[i].zName); + } + sqlite3DbFree(db, p->aScan); } - sqlite3DbFree(db, p->aScan); #endif } @@ -3477,30 +3529,13 @@ u32 sqlite3VdbeSerialGet( ** If an OOM error occurs, NULL is returned. */ UnpackedRecord *sqlite3VdbeAllocUnpackedRecord( - KeyInfo *pKeyInfo, /* Description of the record */ - char *pSpace, /* Unaligned space available */ - int szSpace, /* Size of pSpace[] in bytes */ - char **ppFree /* OUT: Caller should free this pointer */ + KeyInfo *pKeyInfo /* Description of the record */ ){ UnpackedRecord *p; /* Unpacked record to return */ - int nOff; /* Increment pSpace by nOff to align it */ int nByte; /* Number of bytes required for *p */ - - /* We want to shift the pointer pSpace up such that it is 8-byte aligned. - ** Thus, we need to calculate a value, nOff, between 0 and 7, to shift - ** it by. If pSpace is already 8-byte aligned, nOff should be zero. - */ - nOff = (8 - (SQLITE_PTR_TO_INT(pSpace) & 7)) & 7; nByte = ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem)*(pKeyInfo->nField+1); - if( nByte>szSpace+nOff ){ - p = (UnpackedRecord *)sqlite3DbMallocRaw(pKeyInfo->db, nByte); - *ppFree = (char *)p; - if( !p ) return 0; - }else{ - p = (UnpackedRecord*)&pSpace[nOff]; - *ppFree = 0; - } - + p = (UnpackedRecord *)sqlite3DbMallocRaw(pKeyInfo->db, nByte); + if( !p ) return 0; p->aMem = (Mem*)&((char*)p)[ROUND8(sizeof(UnpackedRecord))]; assert( pKeyInfo->aSortOrder!=0 ); p->pKeyInfo = pKeyInfo; @@ -3727,15 +3762,49 @@ static int vdbeCompareMemString( } } +/* +** The input pBlob is guaranteed to be a Blob that is not marked +** with MEM_Zero. Return true if it could be a zero-blob. +*/ +static int isAllZero(const char *z, int n){ + int i; + for(i=0; iz, pB2->z, pB1->n>pB2->n ? pB2->n : pB1->n); + int c; + int n1 = pB1->n; + int n2 = pB2->n; + + /* It is possible to have a Blob value that has some non-zero content + ** followed by zero content. But that only comes up for Blobs formed + ** by the OP_MakeRecord opcode, and such Blobs never get passed into + ** sqlite3MemCompare(). */ + assert( (pB1->flags & MEM_Zero)==0 || n1==0 ); + assert( (pB2->flags & MEM_Zero)==0 || n2==0 ); + + if( (pB1->flags|pB2->flags) & MEM_Zero ){ + if( pB1->flags & pB2->flags & MEM_Zero ){ + return pB1->u.nZero - pB2->u.nZero; + }else if( pB1->flags & MEM_Zero ){ + if( !isAllZero(pB2->z, pB2->n) ) return -1; + return pB1->u.nZero - n2; + }else{ + if( !isAllZero(pB1->z, pB1->n) ) return +1; + return n1 - pB2->u.nZero; + } + } + c = memcmp(pB1->z, pB2->z, n1>n2 ? n2 : n1); if( c ) return c; - return pB1->n - pB2->n; + return n1 - n2; } /* @@ -4041,6 +4110,7 @@ int sqlite3VdbeRecordCompareWithSkip( /* RHS is a blob */ else if( pRhs->flags & MEM_Blob ){ + assert( (pRhs->flags & MEM_Zero)==0 || pRhs->n==0 ); getVarint32(&aKey1[idx1], serial_type); testcase( serial_type==12 ); if( serial_type<12 || (serial_type & 0x01) ){ @@ -4052,6 +4122,12 @@ int sqlite3VdbeRecordCompareWithSkip( if( (d1+nStr) > (unsigned)nKey1 ){ pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT; return 0; /* Corruption */ + }else if( pRhs->flags & MEM_Zero ){ + if( !isAllZero((const char*)&aKey1[d1],nStr) ){ + rc = 1; + }else{ + rc = nStr - pRhs->u.nZero; + } }else{ int nCmp = MIN(nStr, pRhs->n); rc = memcmp(&aKey1[d1], pRhs->z, nCmp); @@ -4122,7 +4198,7 @@ static int vdbeRecordCompareInt( int res; u32 y; u64 x; - i64 v = pPKey2->aMem[0].u.i; + i64 v; i64 lhs; vdbeAssertFieldCountWithinLimits(nKey1, pKey1, pPKey2->pKeyInfo); @@ -4181,6 +4257,7 @@ static int vdbeRecordCompareInt( return sqlite3VdbeRecordCompare(nKey1, pKey1, pPKey2); } + v = pPKey2->aMem[0].u.i; if( v>lhs ){ res = pPKey2->r1; }else if( viPKey; + preupdate.pTab = pTab; db->pPreUpdate = &preupdate; db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2); diff --git a/src/vdbeblob.c b/src/vdbeblob.c index 01827f94d0..520d16c985 100644 --- a/src/vdbeblob.c +++ b/src/vdbeblob.c @@ -176,7 +176,7 @@ int sqlite3_blob_open( goto blob_open_out; } pBlob->pTab = pTab; - pBlob->zDb = db->aDb[sqlite3SchemaToIndex(db, pTab->pSchema)].zName; + pBlob->zDb = db->aDb[sqlite3SchemaToIndex(db, pTab->pSchema)].zDbSName; /* Now search pTab for the exact column. */ for(iCol=0; iColnCol; iCol++) { @@ -262,8 +262,7 @@ int sqlite3_blob_open( {OP_Column, 0, 0, 1}, /* 4 */ {OP_ResultRow, 1, 0, 0}, /* 5 */ {OP_Goto, 0, 2, 0}, /* 6 */ - {OP_Close, 0, 0, 0}, /* 7 */ - {OP_Halt, 0, 0, 0}, /* 8 */ + {OP_Halt, 0, 0, 0}, /* 7 */ }; Vdbe *v = (Vdbe *)pBlob->pStmt; int iDb = sqlite3SchemaToIndex(db, pTab->pSchema); @@ -441,7 +440,7 @@ static int blobReadWrite( ** Read data from a blob handle. */ int sqlite3_blob_read(sqlite3_blob *pBlob, void *z, int n, int iOffset){ - return blobReadWrite(pBlob, z, n, iOffset, sqlite3BtreeData); + return blobReadWrite(pBlob, z, n, iOffset, sqlite3BtreePayloadChecked); } /* diff --git a/src/vdbemem.c b/src/vdbemem.c index 04cb9c5c63..656e19bfa8 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -189,18 +189,18 @@ int sqlite3VdbeMemClearAndResize(Mem *pMem, int szNew){ ** Return SQLITE_OK on success or SQLITE_NOMEM if malloc fails. */ int sqlite3VdbeMemMakeWriteable(Mem *pMem){ - int f; assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( (pMem->flags&MEM_RowSet)==0 ); - ExpandBlob(pMem); - f = pMem->flags; - if( (f&(MEM_Str|MEM_Blob)) && (pMem->szMalloc==0 || pMem->z!=pMem->zMalloc) ){ - if( sqlite3VdbeMemGrow(pMem, pMem->n + 2, 1) ){ - return SQLITE_NOMEM_BKPT; + if( (pMem->flags & (MEM_Str|MEM_Blob))!=0 ){ + if( ExpandBlob(pMem) ) return SQLITE_NOMEM; + if( pMem->szMalloc==0 || pMem->z!=pMem->zMalloc ){ + if( sqlite3VdbeMemGrow(pMem, pMem->n + 2, 1) ){ + return SQLITE_NOMEM_BKPT; + } + pMem->z[pMem->n] = 0; + pMem->z[pMem->n+1] = 0; + pMem->flags |= MEM_Term; } - pMem->z[pMem->n] = 0; - pMem->z[pMem->n+1] = 0; - pMem->flags |= MEM_Term; } pMem->flags &= ~MEM_Ephem; #ifdef SQLITE_DEBUG @@ -216,25 +216,24 @@ int sqlite3VdbeMemMakeWriteable(Mem *pMem){ */ #ifndef SQLITE_OMIT_INCRBLOB int sqlite3VdbeMemExpandBlob(Mem *pMem){ - if( pMem->flags & MEM_Zero ){ - int nByte; - assert( pMem->flags&MEM_Blob ); - assert( (pMem->flags&MEM_RowSet)==0 ); - assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); + int nByte; + assert( pMem->flags & MEM_Zero ); + assert( pMem->flags&MEM_Blob ); + assert( (pMem->flags&MEM_RowSet)==0 ); + assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); - /* Set nByte to the number of bytes required to store the expanded blob. */ - nByte = pMem->n + pMem->u.nZero; - if( nByte<=0 ){ - nByte = 1; - } - if( sqlite3VdbeMemGrow(pMem, nByte, 1) ){ - return SQLITE_NOMEM_BKPT; - } - - memset(&pMem->z[pMem->n], 0, pMem->u.nZero); - pMem->n += pMem->u.nZero; - pMem->flags &= ~(MEM_Zero|MEM_Term); + /* Set nByte to the number of bytes required to store the expanded blob. */ + nByte = pMem->n + pMem->u.nZero; + if( nByte<=0 ){ + nByte = 1; } + if( sqlite3VdbeMemGrow(pMem, nByte, 1) ){ + return SQLITE_NOMEM_BKPT; + } + + memset(&pMem->z[pMem->n], 0, pMem->u.nZero); + pMem->n += pMem->u.nZero; + pMem->flags &= ~(MEM_Zero|MEM_Term); return SQLITE_OK; } #endif @@ -294,6 +293,7 @@ int sqlite3VdbeMemStringify(Mem *pMem, u8 enc, u8 bForce){ if( sqlite3VdbeMemClearAndResize(pMem, nByte) ){ + pMem->enc = 0; return SQLITE_NOMEM_BKPT; } @@ -575,7 +575,7 @@ int sqlite3VdbeMemNumerify(Mem *pMem){ } } assert( (pMem->flags & (MEM_Int|MEM_Real|MEM_Null))!=0 ); - pMem->flags &= ~(MEM_Str|MEM_Blob); + pMem->flags &= ~(MEM_Str|MEM_Blob|MEM_Zero); return SQLITE_OK; } @@ -593,7 +593,7 @@ void sqlite3VdbeMemCast(Mem *pMem, u8 aff, u8 encoding){ if( (pMem->flags & MEM_Blob)==0 ){ sqlite3ValueApplyAffinity(pMem, SQLITE_AFF_TEXT, encoding); assert( pMem->flags & MEM_Str || pMem->db->mallocFailed ); - MemSetTypeFlag(pMem, MEM_Blob); + if( pMem->flags & MEM_Str ) MemSetTypeFlag(pMem, MEM_Blob); }else{ pMem->flags &= ~(MEM_TypeMask&~MEM_Blob); } @@ -934,10 +934,9 @@ int sqlite3VdbeMemSetStr( /* ** Move data out of a btree key or data field and into a Mem structure. -** The data or key is taken from the entry that pCur is currently pointing +** The data is payload from the entry that pCur is currently pointing ** to. offset and amt determine what portion of the data or key to retrieve. -** key is true to get the key or false to get data. The result is written -** into the pMem element. +** The result is written into the pMem element. ** ** The pMem object must have been initialized. This routine will use ** pMem->zMalloc to hold the content from the btree, if possible. New @@ -952,17 +951,12 @@ static SQLITE_NOINLINE int vdbeMemFromBtreeResize( BtCursor *pCur, /* Cursor pointing at record to retrieve. */ u32 offset, /* Offset from the start of data to return bytes from. */ u32 amt, /* Number of bytes to return. */ - int key, /* If true, retrieve from the btree key, not data. */ Mem *pMem /* OUT: Return data in this Mem structure. */ ){ int rc; pMem->flags = MEM_Null; if( SQLITE_OK==(rc = sqlite3VdbeMemClearAndResize(pMem, amt+2)) ){ - if( key ){ - rc = sqlite3BtreeKey(pCur, offset, amt, pMem->z); - }else{ - rc = sqlite3BtreeData(pCur, offset, amt, pMem->z); - } + rc = sqlite3BtreePayload(pCur, offset, amt, pMem->z); if( rc==SQLITE_OK ){ pMem->z[amt] = 0; pMem->z[amt+1] = 0; @@ -978,7 +972,6 @@ int sqlite3VdbeMemFromBtree( BtCursor *pCur, /* Cursor pointing at record to retrieve. */ u32 offset, /* Offset from the start of data to return bytes from. */ u32 amt, /* Number of bytes to return. */ - int key, /* If true, retrieve from the btree key, not data. */ Mem *pMem /* OUT: Return data in this Mem structure. */ ){ char *zData; /* Data from the btree layer */ @@ -999,7 +992,7 @@ int sqlite3VdbeMemFromBtree( pMem->flags = MEM_Blob|MEM_Ephem; pMem->n = (int)amt; }else{ - rc = vdbeMemFromBtreeResize(pCur, offset, amt, key, pMem); + rc = vdbeMemFromBtreeResize(pCur, offset, amt, pMem); } return rc; @@ -1017,10 +1010,8 @@ static SQLITE_NOINLINE const void *valueToText(sqlite3_value* pVal, u8 enc){ assert( (pVal->flags & MEM_RowSet)==0 ); assert( (pVal->flags & (MEM_Null))==0 ); if( pVal->flags & (MEM_Blob|MEM_Str) ){ + if( ExpandBlob(pVal) ) return 0; pVal->flags |= MEM_Str; - if( pVal->flags & MEM_Zero ){ - sqlite3VdbeMemExpandBlob(pVal); - } if( pVal->enc != (enc & ~SQLITE_UTF16_ALIGNED) ){ sqlite3VdbeChangeEncoding(pVal, enc & ~SQLITE_UTF16_ALIGNED); } @@ -1273,10 +1264,7 @@ static int valueFromExpr( const char *zNeg = ""; int rc = SQLITE_OK; - if( !pExpr ){ - *ppVal = 0; - return SQLITE_OK; - } + assert( pExpr!=0 ); while( (op = pExpr->op)==TK_UPLUS || op==TK_SPAN ) pExpr = pExpr->pLeft; if( NEVER(op==TK_REGISTER) ) op = pExpr->op2; @@ -1346,6 +1334,7 @@ static int valueFromExpr( }else if( op==TK_NULL ){ pVal = valueNew(db, pCtx); if( pVal==0 ) goto no_mem; + sqlite3VdbeMemNumerify(pVal); } #ifndef SQLITE_OMIT_BLOB_LITERAL else if( op==TK_BLOB ){ @@ -1400,7 +1389,7 @@ int sqlite3ValueFromExpr( u8 affinity, /* Affinity to use */ sqlite3_value **ppVal /* Write the new value here */ ){ - return valueFromExpr(db, pExpr, enc, affinity, ppVal, 0); + return pExpr ? valueFromExpr(db, pExpr, enc, affinity, ppVal, 0) : 0; } #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 @@ -1520,9 +1509,9 @@ static int stat4ValueFromExpr( ** structures intended to be compared against sample index keys stored ** in the sqlite_stat4 table. ** -** A single call to this function attempts to populates field iVal (leftmost -** is 0 etc.) of the unpacked record with a value extracted from expression -** pExpr. Extraction of values is possible if: +** A single call to this function populates zero or more fields of the +** record starting with field iVal (fields are numbered from left to +** right starting with 0). A single field is populated if: ** ** * (pExpr==0). In this case the value is assumed to be an SQL NULL, ** @@ -1531,10 +1520,14 @@ static int stat4ValueFromExpr( ** * The sqlite3ValueFromExpr() function is able to extract a value ** from the expression (i.e. the expression is a literal value). ** -** If a value can be extracted, the affinity passed as the 5th argument -** is applied to it before it is copied into the UnpackedRecord. Output -** parameter *pbOk is set to true if a value is extracted, or false -** otherwise. +** Or, if pExpr is a TK_VECTOR, one field is populated for each of the +** vector components that match either of the two latter criteria listed +** above. +** +** Before any value is appended to the record, the affinity of the +** corresponding column within index pIdx is applied to it. Before +** this function returns, output parameter *pnExtract is set to the +** number of values appended to the record. ** ** When this function is called, *ppRec must either point to an object ** allocated by an earlier call to this function, or must be NULL. If it @@ -1550,22 +1543,33 @@ int sqlite3Stat4ProbeSetValue( Index *pIdx, /* Index being probed */ UnpackedRecord **ppRec, /* IN/OUT: Probe record */ Expr *pExpr, /* The expression to extract a value from */ - u8 affinity, /* Affinity to use */ + int nElem, /* Maximum number of values to append */ int iVal, /* Array element to populate */ - int *pbOk /* OUT: True if value was extracted */ + int *pnExtract /* OUT: Values appended to the record */ ){ - int rc; - sqlite3_value *pVal = 0; - struct ValueNewStat4Ctx alloc; + int rc = SQLITE_OK; + int nExtract = 0; - alloc.pParse = pParse; - alloc.pIdx = pIdx; - alloc.ppRec = ppRec; - alloc.iVal = iVal; + if( pExpr==0 || pExpr->op!=TK_SELECT ){ + int i; + struct ValueNewStat4Ctx alloc; - rc = stat4ValueFromExpr(pParse, pExpr, affinity, &alloc, &pVal); - assert( pVal==0 || pVal->db==pParse->db ); - *pbOk = (pVal!=0); + alloc.pParse = pParse; + alloc.pIdx = pIdx; + alloc.ppRec = ppRec; + + for(i=0; idb, pIdx, iVal+i); + alloc.iVal = iVal+i; + rc = stat4ValueFromExpr(pParse, pElem, aff, &alloc, &pVal); + if( !pVal ) break; + nExtract++; + } + } + + *pnExtract = nExtract; return rc; } diff --git a/src/vdbesort.c b/src/vdbesort.c index 69619802e8..631fb19616 100644 --- a/src/vdbesort.c +++ b/src/vdbesort.c @@ -959,7 +959,7 @@ int sqlite3VdbeSorterInit( } #endif - assert( pCsr->pKeyInfo && pCsr->pBt==0 ); + assert( pCsr->pKeyInfo && pCsr->pBtx==0 ); assert( pCsr->eCurType==CURTYPE_SORTER ); szKeyInfo = sizeof(KeyInfo) + (pCsr->pKeyInfo->nField-1)*sizeof(CollSeq*); sz = sizeof(VdbeSorter) + nWorker * sizeof(SortSubtask); @@ -1327,12 +1327,8 @@ static int vdbeSorterOpenTempFile( */ static int vdbeSortAllocUnpacked(SortSubtask *pTask){ if( pTask->pUnpacked==0 ){ - char *pFree; - pTask->pUnpacked = sqlite3VdbeAllocUnpackedRecord( - pTask->pSorter->pKeyInfo, 0, 0, &pFree - ); - assert( pTask->pUnpacked==(UnpackedRecord*)pFree ); - if( pFree==0 ) return SQLITE_NOMEM_BKPT; + pTask->pUnpacked = sqlite3VdbeAllocUnpackedRecord(pTask->pSorter->pKeyInfo); + if( pTask->pUnpacked==0 ) return SQLITE_NOMEM_BKPT; pTask->pUnpacked->nField = pTask->pSorter->pKeyInfo->nField; pTask->pUnpacked->errCode = 0; } @@ -2733,9 +2729,7 @@ int sqlite3VdbeSorterCompare( r2 = pSorter->pUnpacked; pKeyInfo = pCsr->pKeyInfo; if( r2==0 ){ - char *p; - r2 = pSorter->pUnpacked = sqlite3VdbeAllocUnpackedRecord(pKeyInfo,0,0,&p); - assert( pSorter->pUnpacked==(UnpackedRecord*)p ); + r2 = pSorter->pUnpacked = sqlite3VdbeAllocUnpackedRecord(pKeyInfo); if( r2==0 ) return SQLITE_NOMEM_BKPT; r2->nField = nKeyCol; } diff --git a/src/vtab.c b/src/vtab.c index 45c5e79abd..30079cbb5e 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -28,6 +28,41 @@ struct VtabCtx { int bDeclared; /* True after sqlite3_declare_vtab() is called */ }; +/* +** Construct and install a Module object for a virtual table. When this +** routine is called, it is guaranteed that all appropriate locks are held +** and the module is not already part of the connection. +*/ +Module *sqlite3VtabCreateModule( + sqlite3 *db, /* Database in which module is registered */ + const char *zName, /* Name assigned to this module */ + const sqlite3_module *pModule, /* The definition of the module */ + void *pAux, /* Context pointer for xCreate/xConnect */ + void (*xDestroy)(void *) /* Module destructor function */ +){ + Module *pMod; + int nName = sqlite3Strlen30(zName); + pMod = (Module *)sqlite3DbMallocRawNN(db, sizeof(Module) + nName + 1); + if( pMod ){ + Module *pDel; + char *zCopy = (char *)(&pMod[1]); + memcpy(zCopy, zName, nName+1); + pMod->zName = zCopy; + pMod->pModule = pModule; + pMod->pAux = pAux; + pMod->xDestroy = xDestroy; + pMod->pEpoTab = 0; + pDel = (Module *)sqlite3HashInsert(&db->aModule,zCopy,(void*)pMod); + assert( pDel==0 || pDel==pMod ); + if( pDel ){ + sqlite3OomFault(db); + sqlite3DbFree(db, pDel); + pMod = 0; + } + } + return pMod; +} + /* ** The actual function that does the work of creating a new module. ** This function implements the sqlite3_create_module() and @@ -41,35 +76,15 @@ static int createModule( void (*xDestroy)(void *) /* Module destructor function */ ){ int rc = SQLITE_OK; - int nName; sqlite3_mutex_enter(db->mutex); - nName = sqlite3Strlen30(zName); if( sqlite3HashFind(&db->aModule, zName) ){ rc = SQLITE_MISUSE_BKPT; }else{ - Module *pMod; - pMod = (Module *)sqlite3DbMallocRawNN(db, sizeof(Module) + nName + 1); - if( pMod ){ - Module *pDel; - char *zCopy = (char *)(&pMod[1]); - memcpy(zCopy, zName, nName+1); - pMod->zName = zCopy; - pMod->pModule = pModule; - pMod->pAux = pAux; - pMod->xDestroy = xDestroy; - pMod->pEpoTab = 0; - pDel = (Module *)sqlite3HashInsert(&db->aModule,zCopy,(void*)pMod); - assert( pDel==0 || pDel==pMod ); - if( pDel ){ - sqlite3OomFault(db); - sqlite3DbFree(db, pDel); - } - } + (void)sqlite3VtabCreateModule(db, zName, pModule, pAux, xDestroy); } rc = sqlite3ApiExit(db, rc); if( rc!=SQLITE_OK && xDestroy ) xDestroy(pAux); - sqlite3_mutex_leave(db->mutex); return rc; } @@ -344,7 +359,7 @@ void sqlite3VtabBeginParse( */ if( pTable->azModuleArg ){ sqlite3AuthCheck(pParse, SQLITE_CREATE_VTABLE, pTable->zName, - pTable->azModuleArg[0], pParse->db->aDb[iDb].zName); + pTable->azModuleArg[0], pParse->db->aDb[iDb].zDbSName); } #endif } @@ -408,7 +423,7 @@ void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){ "UPDATE %Q.%s " "SET type='table', name=%Q, tbl_name=%Q, rootpage=0, sql=%Q " "WHERE rowid=#%d", - db->aDb[iDb].zName, SCHEMA_TABLE(iDb), + db->aDb[iDb].zDbSName, MASTER_NAME, pTab->zName, pTab->zName, zStmt, @@ -518,7 +533,7 @@ static int vtabCallConstructor( pVTable->pMod = pMod; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); - pTab->azModuleArg[1] = db->aDb[iDb].zName; + pTab->azModuleArg[1] = db->aDb[iDb].zDbSName; /* Invoke the virtual table constructor */ assert( &db->pVtabCtx ); @@ -672,7 +687,7 @@ static void addToVTrans(sqlite3 *db, VTable *pVTab){ ** This function is invoked by the vdbe to call the xCreate method ** of the virtual table named zTab in database iDb. ** -** If an error occurs, *pzErr is set to point an an English language +** If an error occurs, *pzErr is set to point to an English language ** description of the error and an SQLITE_XXX error code is returned. ** In this case the caller must call sqlite3DbFree(db, ) on *pzErr. */ @@ -682,7 +697,7 @@ int sqlite3VtabCallCreate(sqlite3 *db, int iDb, const char *zTab, char **pzErr){ Module *pMod; const char *zMod; - pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zName); + pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zDbSName); assert( pTab && (pTab->tabFlags & TF_Virtual)!=0 && !pTab->pVTable ); /* Locate the required virtual table module */ @@ -806,7 +821,7 @@ int sqlite3VtabCallDestroy(sqlite3 *db, int iDb, const char *zTab){ int rc = SQLITE_OK; Table *pTab; - pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zName); + pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zDbSName); if( pTab!=0 && ALWAYS(pTab->pVTable!=0) ){ VTable *p; int (*xDestroy)(sqlite3_vtab *); @@ -1133,7 +1148,7 @@ int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){ return 0; } pMod->pEpoTab = pTab; - pTab->nRef = 1; + pTab->nTabRef = 1; pTab->pSchema = db->aDb[0].pSchema; pTab->tabFlags |= TF_Virtual; pTab->nModuleArg = 0; diff --git a/src/wal.c b/src/wal.c index 4609521d67..c56b1365cb 100644 --- a/src/wal.c +++ b/src/wal.c @@ -1723,6 +1723,7 @@ static void walRestartHdr(Wal *pWal, u32 salt1){ */ static int walCheckpoint( Wal *pWal, /* Wal connection */ + sqlite3 *db, /* Check for interrupts on this handle */ int eMode, /* One of PASSIVE, FULL or RESTART */ int (*xBusy)(void*), /* Function to call when busy */ void *pBusyArg, /* Context argument for xBusyHandler */ @@ -1817,6 +1818,10 @@ static int walCheckpoint( while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){ i64 iOffset; assert( walFramePgno(pWal, iFrame)==iDbpage ); + if( db->u1.isInterrupted ){ + rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_INTERRUPT; + break; + } if( iFrame<=nBackfill || iFrame>mxSafeFrame || iDbpage>mxPage ){ continue; } @@ -1921,6 +1926,7 @@ static void walLimitSize(Wal *pWal, i64 nMax){ */ int sqlite3WalClose( Wal *pWal, /* Wal to close */ + sqlite3 *db, /* For interrupt flag */ int sync_flags, /* Flags to pass to OsSync() (or 0) */ int nBuf, u8 *zBuf /* Buffer of at least nBuf bytes */ @@ -1937,13 +1943,14 @@ int sqlite3WalClose( ** ** The EXCLUSIVE lock is not released before returning. */ - rc = sqlite3OsLock(pWal->pDbFd, SQLITE_LOCK_EXCLUSIVE); - if( rc==SQLITE_OK ){ + if( zBuf!=0 + && SQLITE_OK==(rc = sqlite3OsLock(pWal->pDbFd, SQLITE_LOCK_EXCLUSIVE)) + ){ if( pWal->exclusiveMode==WAL_NORMAL_MODE ){ pWal->exclusiveMode = WAL_EXCLUSIVE_MODE; } - rc = sqlite3WalCheckpoint( - pWal, SQLITE_CHECKPOINT_PASSIVE, 0, 0, sync_flags, nBuf, zBuf, 0, 0 + rc = sqlite3WalCheckpoint(pWal, db, + SQLITE_CHECKPOINT_PASSIVE, 0, 0, sync_flags, nBuf, zBuf, 0, 0 ); if( rc==SQLITE_OK ){ int bPersist = -1; @@ -2388,6 +2395,84 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ return rc; } +#ifdef SQLITE_ENABLE_SNAPSHOT +/* +** Attempt to reduce the value of the WalCkptInfo.nBackfillAttempted +** variable so that older snapshots can be accessed. To do this, loop +** through all wal frames from nBackfillAttempted to (nBackfill+1), +** comparing their content to the corresponding page with the database +** file, if any. Set nBackfillAttempted to the frame number of the +** first frame for which the wal file content matches the db file. +** +** This is only really safe if the file-system is such that any page +** writes made by earlier checkpointers were atomic operations, which +** is not always true. It is also possible that nBackfillAttempted +** may be left set to a value larger than expected, if a wal frame +** contains content that duplicate of an earlier version of the same +** page. +** +** SQLITE_OK is returned if successful, or an SQLite error code if an +** error occurs. It is not an error if nBackfillAttempted cannot be +** decreased at all. +*/ +int sqlite3WalSnapshotRecover(Wal *pWal){ + int rc; + + assert( pWal->readLock>=0 ); + rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); + if( rc==SQLITE_OK ){ + volatile WalCkptInfo *pInfo = walCkptInfo(pWal); + int szPage = (int)pWal->szPage; + i64 szDb; /* Size of db file in bytes */ + + rc = sqlite3OsFileSize(pWal->pDbFd, &szDb); + if( rc==SQLITE_OK ){ + void *pBuf1 = sqlite3_malloc(szPage); + void *pBuf2 = sqlite3_malloc(szPage); + if( pBuf1==0 || pBuf2==0 ){ + rc = SQLITE_NOMEM; + }else{ + u32 i = pInfo->nBackfillAttempted; + for(i=pInfo->nBackfillAttempted; i>pInfo->nBackfill; i--){ + volatile ht_slot *dummy; + volatile u32 *aPgno; /* Array of page numbers */ + u32 iZero; /* Frame corresponding to aPgno[0] */ + u32 pgno; /* Page number in db file */ + i64 iDbOff; /* Offset of db file entry */ + i64 iWalOff; /* Offset of wal file entry */ + + rc = walHashGet(pWal, walFramePage(i), &dummy, &aPgno, &iZero); + if( rc!=SQLITE_OK ) break; + pgno = aPgno[i-iZero]; + iDbOff = (i64)(pgno-1) * szPage; + + if( iDbOff+szPage<=szDb ){ + iWalOff = walFrameOffset(i, szPage) + WAL_FRAME_HDRSIZE; + rc = sqlite3OsRead(pWal->pWalFd, pBuf1, szPage, iWalOff); + + if( rc==SQLITE_OK ){ + rc = sqlite3OsRead(pWal->pDbFd, pBuf2, szPage, iDbOff); + } + + if( rc!=SQLITE_OK || 0==memcmp(pBuf1, pBuf2, szPage) ){ + break; + } + } + + pInfo->nBackfillAttempted = i-1; + } + } + + sqlite3_free(pBuf1); + sqlite3_free(pBuf2); + } + walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1); + } + + return rc; +} +#endif /* SQLITE_ENABLE_SNAPSHOT */ + /* ** Begin a read transaction on the database. ** @@ -2450,7 +2535,11 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ ** has not yet set the pInfo->nBackfillAttempted variable to indicate ** its intent. To avoid the race condition this leads to, ensure that ** there is no checkpointer process by taking a shared CKPT lock - ** before checking pInfo->nBackfillAttempted. */ + ** before checking pInfo->nBackfillAttempted. + ** + ** TODO: Does the aReadMark[] lock prevent a checkpointer from doing + ** this already? + */ rc = walLockShared(pWal, WAL_CKPT_LOCK); if( rc==SQLITE_OK ){ @@ -3415,6 +3504,7 @@ int sqlite3WalFrames( */ int sqlite3WalCheckpoint( Wal *pWal, /* Wal connection */ + sqlite3 *db, /* Check this handle's interrupt flag */ int eMode, /* PASSIVE, FULL, RESTART, or TRUNCATE */ int (*xBusy)(void*), /* Function to call when busy */ void *pBusyArg, /* Context argument for xBusyHandler */ @@ -3488,7 +3578,7 @@ int sqlite3WalCheckpoint( if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){ rc = SQLITE_CORRUPT_BKPT; }else{ - rc = walCheckpoint(pWal, eMode2, xBusy2, pBusyArg, sync_flags, zBuf); + rc = walCheckpoint(pWal, db, eMode2, xBusy2, pBusyArg, sync_flags, zBuf); } /* If no error occurred, set the output variables. */ @@ -3608,9 +3698,14 @@ int sqlite3WalHeapMemory(Wal *pWal){ int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot){ int rc = SQLITE_OK; WalIndexHdr *pRet; + static const u32 aZero[4] = { 0, 0, 0, 0 }; assert( pWal->readLock>=0 && pWal->writeLock==0 ); + if( memcmp(&pWal->hdr.aFrameCksum[0],aZero,16)==0 ){ + *ppSnapshot = 0; + return SQLITE_ERROR; + } pRet = (WalIndexHdr*)sqlite3_malloc(sizeof(WalIndexHdr)); if( pRet==0 ){ rc = SQLITE_NOMEM_BKPT; diff --git a/src/wal.h b/src/wal.h index 0c1fb52a51..5243bca162 100644 --- a/src/wal.h +++ b/src/wal.h @@ -28,7 +28,7 @@ #ifdef SQLITE_OMIT_WAL # define sqlite3WalOpen(x,y,z) 0 # define sqlite3WalLimit(x,y) -# define sqlite3WalClose(w,x,y,z) 0 +# define sqlite3WalClose(v,w,x,y,z) 0 # define sqlite3WalBeginReadTransaction(y,z) 0 # define sqlite3WalEndReadTransaction(z) # define sqlite3WalDbsize(y) 0 @@ -38,7 +38,7 @@ # define sqlite3WalSavepoint(y,z) # define sqlite3WalSavepointUndo(y,z) 0 # define sqlite3WalFrames(u,v,w,x,y,z) 0 -# define sqlite3WalCheckpoint(r,s,t,u,v,w,x,y,z) 0 +# define sqlite3WalCheckpoint(q,r,s,t,u,v,w,x,y,z) 0 # define sqlite3WalCallback(z) 0 # define sqlite3WalExclusiveMode(y,z) 0 # define sqlite3WalHeapMemory(z) 0 @@ -56,7 +56,7 @@ typedef struct Wal Wal; /* Open and close a connection to a write-ahead log. */ int sqlite3WalOpen(sqlite3_vfs*, sqlite3_file*, const char *, int, i64, Wal**); -int sqlite3WalClose(Wal *pWal, int sync_flags, int, u8 *); +int sqlite3WalClose(Wal *pWal, sqlite3*, int sync_flags, int, u8 *); /* Set the limiting size of a WAL file. */ void sqlite3WalLimit(Wal*, i64); @@ -99,6 +99,7 @@ int sqlite3WalFrames(Wal *pWal, int, PgHdr *, Pgno, int, int); /* Copy pages from the log to the database file */ int sqlite3WalCheckpoint( Wal *pWal, /* Write-ahead log connection */ + sqlite3 *db, /* Check this handle's interrupt flag */ int eMode, /* One of PASSIVE, FULL and RESTART */ int (*xBusy)(void*), /* Function to call when busy */ void *pBusyArg, /* Context argument for xBusyHandler */ @@ -130,6 +131,7 @@ int sqlite3WalHeapMemory(Wal *pWal); #ifdef SQLITE_ENABLE_SNAPSHOT int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot); void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot); +int sqlite3WalSnapshotRecover(Wal *pWal); #endif #ifndef SQLITE_OMIT_CONCURRENT diff --git a/src/walker.c b/src/walker.c index 82aae67a61..d1b1e96a2d 100644 --- a/src/walker.c +++ b/src/walker.c @@ -41,12 +41,14 @@ static SQLITE_NOINLINE int walkExpr(Walker *pWalker, Expr *pExpr){ testcase( ExprHasProperty(pExpr, EP_TokenOnly) ); testcase( ExprHasProperty(pExpr, EP_Reduced) ); rc = pWalker->xExprCallback(pWalker, pExpr); - if( rc || ExprHasProperty(pExpr,EP_TokenOnly) ) return rc & WRC_Abort; + if( rc || ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){ + return rc & WRC_Abort; + } if( pExpr->pLeft && walkExpr(pWalker, pExpr->pLeft) ) return WRC_Abort; if( pExpr->pRight && walkExpr(pWalker, pExpr->pRight) ) return WRC_Abort; if( ExprHasProperty(pExpr, EP_xIsSelect) ){ if( sqlite3WalkSelect(pWalker, pExpr->x.pSelect) ) return WRC_Abort; - }else{ + }else if( pExpr->x.pList ){ if( sqlite3WalkExprList(pWalker, pExpr->x.pList) ) return WRC_Abort; } return WRC_Continue; diff --git a/src/where.c b/src/where.c index c2706dc5dd..81cc1f131b 100644 --- a/src/where.c +++ b/src/where.c @@ -198,11 +198,13 @@ static WhereTerm *whereScanNext(WhereScan *pScan){ WhereTerm *pTerm; /* The term being tested */ int k = pScan->k; /* Where to start scanning */ - while( pScan->iEquiv<=pScan->nEquiv ){ - iCur = pScan->aiCur[pScan->iEquiv-1]; + assert( pScan->iEquiv<=pScan->nEquiv ); + pWC = pScan->pWC; + while(1){ iColumn = pScan->aiColumn[pScan->iEquiv-1]; - if( iColumn==XN_EXPR && pScan->pIdxExpr==0 ) return 0; - while( (pWC = pScan->pWC)!=0 ){ + iCur = pScan->aiCur[pScan->iEquiv-1]; + assert( pWC!=0 ); + do{ for(pTerm=pWC->a+k; knTerm; k++, pTerm++){ if( pTerm->leftCursor==iCur && pTerm->u.leftColumn==iColumn @@ -252,15 +254,17 @@ static WhereTerm *whereScanNext(WhereScan *pScan){ testcase( pTerm->eOperator & WO_IS ); continue; } + pScan->pWC = pWC; pScan->k = k+1; return pTerm; } } } - pScan->pWC = pScan->pWC->pOuter; + pWC = pWC->pOuter; k = 0; - } - pScan->pWC = pScan->pOrigWC; + }while( pWC!=0 ); + if( pScan->iEquiv>=pScan->nEquiv ) break; + pWC = pScan->pOrigWC; k = 0; pScan->iEquiv++; } @@ -294,24 +298,24 @@ static WhereTerm *whereScanInit( u32 opMask, /* Operator(s) to scan for */ Index *pIdx /* Must be compatible with this index */ ){ - int j = 0; - - /* memset(pScan, 0, sizeof(*pScan)); */ pScan->pOrigWC = pWC; pScan->pWC = pWC; pScan->pIdxExpr = 0; + pScan->idxaff = 0; + pScan->zCollName = 0; if( pIdx ){ - j = iColumn; + int j = iColumn; iColumn = pIdx->aiColumn[j]; - if( iColumn==XN_EXPR ) pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr; - if( iColumn==pIdx->pTable->iPKey ) iColumn = XN_ROWID; - } - if( pIdx && iColumn>=0 ){ - pScan->idxaff = pIdx->pTable->aCol[iColumn].affinity; - pScan->zCollName = pIdx->azColl[j]; - }else{ - pScan->idxaff = 0; - pScan->zCollName = 0; + if( iColumn==XN_EXPR ){ + pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr; + }else if( iColumn==pIdx->pTable->iPKey ){ + iColumn = XN_ROWID; + }else if( iColumn>=0 ){ + pScan->idxaff = pIdx->pTable->aCol[iColumn].affinity; + pScan->zCollName = pIdx->azColl[j]; + } + }else if( iColumn==XN_EXPR ){ + return 0; } pScan->opMask = opMask; pScan->k = 0; @@ -651,7 +655,7 @@ static void constructAutomaticIndex( ** transient index on 2nd and subsequent iterations of the loop. */ v = pParse->pVdbe; assert( v!=0 ); - addrInit = sqlite3CodeOnce(pParse); VdbeCoverage(v); + addrInit = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); /* Count the number of columns that will be added to the index ** and used to match WHERE clause constraints */ @@ -826,7 +830,8 @@ static sqlite3_index_info *allocateIndexInfo( WhereClause *pWC, Bitmask mUnusable, /* Ignore terms with these prereqs */ struct SrcList_item *pSrc, - ExprList *pOrderBy + ExprList *pOrderBy, + u16 *pmNoOmit /* Mask of terms not to omit */ ){ int i, j; int nTerm; @@ -836,6 +841,7 @@ static sqlite3_index_info *allocateIndexInfo( WhereTerm *pTerm; int nOrderBy; sqlite3_index_info *pIdxInfo; + u16 mNoOmit = 0; /* Count the number of possible WHERE clause constraints referring ** to this virtual table */ @@ -924,6 +930,15 @@ static sqlite3_index_info *allocateIndexInfo( assert( WO_GE==SQLITE_INDEX_CONSTRAINT_GE ); assert( WO_MATCH==SQLITE_INDEX_CONSTRAINT_MATCH ); assert( pTerm->eOperator & (WO_IN|WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE|WO_MATCH) ); + + if( op & (WO_LT|WO_LE|WO_GT|WO_GE) + && sqlite3ExprIsVector(pTerm->pExpr->pRight) + ){ + if( i<16 ) mNoOmit |= (1 << i); + if( op==WO_LT ) pIdxCons[j].op = WO_LE; + if( op==WO_GT ) pIdxCons[j].op = WO_GE; + } + j++; } for(i=0; ia[i].sortOrder; } + *pmNoOmit = mNoOmit; return pIdxInfo; } @@ -1207,7 +1223,7 @@ static LogEst whereRangeAdjust(WhereTerm *pTerm, LogEst nNew){ /* ** Return the affinity for a single column of an index. */ -static char sqlite3IndexColumnAffinity(sqlite3 *db, Index *pIdx, int iCol){ +char sqlite3IndexColumnAffinity(sqlite3 *db, Index *pIdx, int iCol){ assert( iCol>=0 && iColnColumn ); if( !pIdx->zColAff ){ if( sqlite3IndexAffinityStr(db, pIdx)==0 ) return SQLITE_AFF_BLOB; @@ -1384,7 +1400,8 @@ static int whereRangeScanEst( if( nEq==pBuilder->nRecValid ){ UnpackedRecord *pRec = pBuilder->pRec; tRowcnt a[2]; - u8 aff; + int nBtm = pLoop->u.btree.nBtm; + int nTop = pLoop->u.btree.nTop; /* Variable iLower will be set to the estimate of the number of rows in ** the index that are less than the lower bound of the range query. The @@ -1414,8 +1431,6 @@ static int whereRangeScanEst( testcase( pRec->nField!=pBuilder->nRecValid ); pRec->nField = pBuilder->nRecValid; } - aff = sqlite3IndexColumnAffinity(pParse->db, p, nEq); - assert( nEq!=p->nKeyCol || aff==SQLITE_AFF_INTEGER ); /* Determine iLower and iUpper using ($P) only. */ if( nEq==0 ){ iLower = 0; @@ -1434,17 +1449,20 @@ static int whereRangeScanEst( if( p->aSortOrder[nEq] ){ /* The roles of pLower and pUpper are swapped for a DESC index */ SWAP(WhereTerm*, pLower, pUpper); + SWAP(int, nBtm, nTop); } /* If possible, improve on the iLower estimate using ($P:$L). */ if( pLower ){ - int bOk; /* True if value is extracted from pExpr */ + int n; /* Values extracted from pExpr */ Expr *pExpr = pLower->pExpr->pRight; - rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, aff, nEq, &bOk); - if( rc==SQLITE_OK && bOk ){ + rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, nBtm, nEq, &n); + if( rc==SQLITE_OK && n ){ tRowcnt iNew; + u16 mask = WO_GT|WO_LE; + if( sqlite3ExprVectorSize(pExpr)>n ) mask = (WO_LE|WO_LT); iLwrIdx = whereKeyStats(pParse, p, pRec, 0, a); - iNew = a[0] + ((pLower->eOperator & (WO_GT|WO_LE)) ? a[1] : 0); + iNew = a[0] + ((pLower->eOperator & mask) ? a[1] : 0); if( iNew>iLower ) iLower = iNew; nOut--; pLower = 0; @@ -1453,13 +1471,15 @@ static int whereRangeScanEst( /* If possible, improve on the iUpper estimate using ($P:$U). */ if( pUpper ){ - int bOk; /* True if value is extracted from pExpr */ + int n; /* Values extracted from pExpr */ Expr *pExpr = pUpper->pExpr->pRight; - rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, aff, nEq, &bOk); - if( rc==SQLITE_OK && bOk ){ + rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, nTop, nEq, &n); + if( rc==SQLITE_OK && n ){ tRowcnt iNew; + u16 mask = WO_GT|WO_LE; + if( sqlite3ExprVectorSize(pExpr)>n ) mask = (WO_LE|WO_LT); iUprIdx = whereKeyStats(pParse, p, pRec, 1, a); - iNew = a[0] + ((pUpper->eOperator & (WO_GT|WO_LE)) ? a[1] : 0); + iNew = a[0] + ((pUpper->eOperator & mask) ? a[1] : 0); if( iNewpNew->u.btree.pIndex; int nEq = pBuilder->pNew->u.btree.nEq; UnpackedRecord *pRec = pBuilder->pRec; - u8 aff; /* Column affinity */ int rc; /* Subfunction return code */ tRowcnt a[2]; /* Statistics */ int bOk; @@ -1573,8 +1592,7 @@ static int whereEqualScanEst( return SQLITE_OK; } - aff = sqlite3IndexColumnAffinity(pParse->db, p, nEq-1); - rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, aff, nEq-1, &bOk); + rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, 1, nEq-1, &bOk); pBuilder->pRec = pRec; if( rc!=SQLITE_OK ) return rc; if( bOk==0 ) return SQLITE_NOTFOUND; @@ -1663,9 +1681,14 @@ static void whereTermPrint(WhereTerm *pTerm, int iTerm){ sqlite3_snprintf(sizeof(zLeft),zLeft,"left=%d", pTerm->leftCursor); } sqlite3DebugPrintf( - "TERM-%-3d %p %s %-12s prob=%-3d op=0x%03x wtFlags=0x%04x\n", + "TERM-%-3d %p %s %-12s prob=%-3d op=0x%03x wtFlags=0x%04x", iTerm, pTerm, zType, zLeft, pTerm->truthProb, pTerm->eOperator, pTerm->wtFlags); + if( pTerm->iField ){ + sqlite3DebugPrintf(" iField=%d\n", pTerm->iField); + }else{ + sqlite3DebugPrintf("\n"); + } sqlite3TreeViewExpr(0, pTerm->pExpr, 0); } } @@ -2187,6 +2210,72 @@ static void whereLoopOutputAdjust( if( pLoop->nOut > nRow-iReduce ) pLoop->nOut = nRow - iReduce; } +/* +** Term pTerm is a vector range comparison operation. The first comparison +** in the vector can be optimized using column nEq of the index. This +** function returns the total number of vector elements that can be used +** as part of the range comparison. +** +** For example, if the query is: +** +** WHERE a = ? AND (b, c, d) > (?, ?, ?) +** +** and the index: +** +** CREATE INDEX ... ON (a, b, c, d, e) +** +** then this function would be invoked with nEq=1. The value returned in +** this case is 3. +*/ +static int whereRangeVectorLen( + Parse *pParse, /* Parsing context */ + int iCur, /* Cursor open on pIdx */ + Index *pIdx, /* The index to be used for a inequality constraint */ + int nEq, /* Number of prior equality constraints on same index */ + WhereTerm *pTerm /* The vector inequality constraint */ +){ + int nCmp = sqlite3ExprVectorSize(pTerm->pExpr->pLeft); + int i; + + nCmp = MIN(nCmp, (pIdx->nColumn - nEq)); + for(i=1; ipExpr->pLeft->x.pList->a[i].pExpr; + Expr *pRhs = pTerm->pExpr->pRight; + if( pRhs->flags & EP_xIsSelect ){ + pRhs = pRhs->x.pSelect->pEList->a[i].pExpr; + }else{ + pRhs = pRhs->x.pList->a[i].pExpr; + } + + /* Check that the LHS of the comparison is a column reference to + ** the right column of the right source table. And that the sort + ** order of the index column is the same as the sort order of the + ** leftmost index column. */ + if( pLhs->op!=TK_COLUMN + || pLhs->iTable!=iCur + || pLhs->iColumn!=pIdx->aiColumn[i+nEq] + || pIdx->aSortOrder[i+nEq]!=pIdx->aSortOrder[nEq] + ){ + break; + } + + testcase( pLhs->iColumn==XN_ROWID ); + aff = sqlite3CompareAffinity(pRhs, sqlite3ExprAffinity(pLhs)); + idxaff = sqlite3TableColumnAffinity(pIdx->pTable, pLhs->iColumn); + if( aff!=idxaff ) break; + + pColl = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs); + if( pColl==0 ) break; + if( sqlite3StrICmp(pColl->zName, pIdx->azColl[i+nEq]) ) break; + } + return i; +} + /* ** Adjust the cost C by the costMult facter T. This only occurs if ** compiled with -DSQLITE_ENABLE_COSTMULT @@ -2225,6 +2314,8 @@ static int whereLoopAddBtreeIndex( Bitmask saved_prereq; /* Original value of pNew->prereq */ u16 saved_nLTerm; /* Original value of pNew->nLTerm */ u16 saved_nEq; /* Original value of pNew->u.btree.nEq */ + u16 saved_nBtm; /* Original value of pNew->u.btree.nBtm */ + u16 saved_nTop; /* Original value of pNew->u.btree.nTop */ u16 saved_nSkip; /* Original value of pNew->nSkip */ u32 saved_wsFlags; /* Original value of pNew->wsFlags */ LogEst saved_nOut; /* Original value of pNew->nOut */ @@ -2235,12 +2326,15 @@ static int whereLoopAddBtreeIndex( pNew = pBuilder->pNew; if( db->mallocFailed ) return SQLITE_NOMEM_BKPT; + WHERETRACE(0x800, ("BEGIN addBtreeIdx(%s), nEq=%d\n", + pProbe->zName, pNew->u.btree.nEq)); assert( (pNew->wsFlags & WHERE_VIRTUALTABLE)==0 ); assert( (pNew->wsFlags & WHERE_TOP_LIMIT)==0 ); if( pNew->wsFlags & WHERE_BTM_LIMIT ){ opMask = WO_LT|WO_LE; }else{ + assert( pNew->u.btree.nBtm==0 ); opMask = WO_EQ|WO_IN|WO_GT|WO_GE|WO_LT|WO_LE|WO_ISNULL|WO_IS; } if( pProbe->bUnordered ) opMask &= ~(WO_GT|WO_GE|WO_LT|WO_LE); @@ -2248,6 +2342,8 @@ static int whereLoopAddBtreeIndex( assert( pNew->u.btree.nEqnColumn ); saved_nEq = pNew->u.btree.nEq; + saved_nBtm = pNew->u.btree.nBtm; + saved_nTop = pNew->u.btree.nTop; saved_nSkip = pNew->nSkip; saved_nLTerm = pNew->nLTerm; saved_wsFlags = pNew->wsFlags; @@ -2291,6 +2387,8 @@ static int whereLoopAddBtreeIndex( pNew->wsFlags = saved_wsFlags; pNew->u.btree.nEq = saved_nEq; + pNew->u.btree.nBtm = saved_nBtm; + pNew->u.btree.nTop = saved_nTop; pNew->nLTerm = saved_nLTerm; if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */ pNew->aLTerm[pNew->nLTerm++] = pTerm; @@ -2307,14 +2405,23 @@ static int whereLoopAddBtreeIndex( pNew->wsFlags |= WHERE_COLUMN_IN; if( ExprHasProperty(pExpr, EP_xIsSelect) ){ /* "x IN (SELECT ...)": TUNING: the SELECT returns 25 rows */ + int i; nIn = 46; assert( 46==sqlite3LogEst(25) ); + + /* The expression may actually be of the form (x, y) IN (SELECT...). + ** In this case there is a separate term for each of (x) and (y). + ** However, the nIn multiplier should only be applied once, not once + ** for each such term. The following loop checks that pTerm is the + ** first such term in use, and sets nIn back to 0 if it is not. */ + for(i=0; inLTerm-1; i++){ + if( pNew->aLTerm[i] && pNew->aLTerm[i]->pExpr==pExpr ) nIn = 0; + } }else if( ALWAYS(pExpr->x.pList && pExpr->x.pList->nExpr) ){ /* "x IN (value, value, ...)" */ nIn = sqlite3LogEst(pExpr->x.pList->nExpr); + assert( nIn>0 ); /* RHS always has 2 or more terms... The parser + ** changes "x IN (?)" into "x=?". */ } - assert( nIn>0 ); /* RHS always has 2 or more terms... The parser - ** changes "x IN (?)" into "x=?". */ - }else if( eOp & (WO_EQ|WO_IS) ){ int iCol = pProbe->aiColumn[saved_nEq]; pNew->wsFlags |= WHERE_COLUMN_EQ; @@ -2334,6 +2441,9 @@ static int whereLoopAddBtreeIndex( testcase( eOp & WO_GT ); testcase( eOp & WO_GE ); pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_BTM_LIMIT; + pNew->u.btree.nBtm = whereRangeVectorLen( + pParse, pSrc->iCursor, pProbe, saved_nEq, pTerm + ); pBtm = pTerm; pTop = 0; if( pTerm->wtFlags & TERM_LIKEOPT ){ @@ -2346,12 +2456,16 @@ static int whereLoopAddBtreeIndex( if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */ pNew->aLTerm[pNew->nLTerm++] = pTop; pNew->wsFlags |= WHERE_TOP_LIMIT; + pNew->u.btree.nTop = 1; } }else{ assert( eOp & (WO_LT|WO_LE) ); testcase( eOp & WO_LT ); testcase( eOp & WO_LE ); pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_TOP_LIMIT; + pNew->u.btree.nTop = whereRangeVectorLen( + pParse, pSrc->iCursor, pProbe, saved_nEq, pTerm + ); pTop = pTerm; pBtm = (pNew->wsFlags & WHERE_BTM_LIMIT)!=0 ? pNew->aLTerm[pNew->nLTerm-2] : 0; @@ -2451,6 +2565,8 @@ static int whereLoopAddBtreeIndex( } pNew->prereq = saved_prereq; pNew->u.btree.nEq = saved_nEq; + pNew->u.btree.nBtm = saved_nBtm; + pNew->u.btree.nTop = saved_nTop; pNew->nSkip = saved_nSkip; pNew->wsFlags = saved_wsFlags; pNew->nOut = saved_nOut; @@ -2490,6 +2606,8 @@ static int whereLoopAddBtreeIndex( pNew->wsFlags = saved_wsFlags; } + WHERETRACE(0x800, ("END addBtreeIdx(%s), nEq=%d, rc=%d\n", + pProbe->zName, saved_nEq, rc)); return rc; } @@ -2572,7 +2690,7 @@ static int whereUsablePartialIndex(int iTab, WhereClause *pWC, Expr *pWhere){ /* ** Add all WhereLoop objects for a single table of the join where the table -** is idenfied by pBuilder->pNew->iTab. That table is guaranteed to be +** is identified by pBuilder->pNew->iTab. That table is guaranteed to be ** a b-tree table, not a virtual table. ** ** The costs (WhereLoop.rRun) of the b-tree loops added by this function @@ -2726,6 +2844,8 @@ static int whereLoopAddBtree( } rSize = pProbe->aiRowLogEst[0]; pNew->u.btree.nEq = 0; + pNew->u.btree.nBtm = 0; + pNew->u.btree.nTop = 0; pNew->nSkip = 0; pNew->nLTerm = 0; pNew->iSortIdx = 0; @@ -2854,6 +2974,7 @@ static int whereLoopAddVirtualOne( Bitmask mUsable, /* Mask of usable tables */ u16 mExclude, /* Exclude terms using these operators */ sqlite3_index_info *pIdxInfo, /* Populated object for xBestIndex */ + u16 mNoOmit, /* Do not omit these constraints */ int *pbIn /* OUT: True if plan uses an IN(...) op */ ){ WhereClause *pWC = pBuilder->pWC; @@ -2942,6 +3063,7 @@ static int whereLoopAddVirtualOne( } } } + pNew->u.vtab.omitMask &= ~mNoOmit; pNew->nLTerm = mxTerm+1; assert( pNew->nLTerm<=pNew->nLSlot ); @@ -3015,6 +3137,7 @@ static int whereLoopAddVirtual( int bIn; /* True if plan uses IN(...) operator */ WhereLoop *pNew; Bitmask mBest; /* Tables used by best possible plan */ + u16 mNoOmit; assert( (mPrereq & mUnusable)==0 ); pWInfo = pBuilder->pWInfo; @@ -3023,7 +3146,8 @@ static int whereLoopAddVirtual( pNew = pBuilder->pNew; pSrc = &pWInfo->pTabList->a[pNew->iTab]; assert( IsVirtual(pSrc->pTab) ); - p = allocateIndexInfo(pParse, pWC, mUnusable, pSrc, pBuilder->pOrderBy); + p = allocateIndexInfo(pParse, pWC, mUnusable, pSrc, pBuilder->pOrderBy, + &mNoOmit); if( p==0 ) return SQLITE_NOMEM_BKPT; pNew->rSetup = 0; pNew->wsFlags = WHERE_VIRTUALTABLE; @@ -3037,7 +3161,7 @@ static int whereLoopAddVirtual( /* First call xBestIndex() with all constraints usable. */ WHERETRACE(0x40, (" VirtualOne: all usable\n")); - rc = whereLoopAddVirtualOne(pBuilder, mPrereq, ALLBITS, 0, p, &bIn); + rc = whereLoopAddVirtualOne(pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn); /* If the call to xBestIndex() with all terms enabled produced a plan ** that does not require any source tables (IOW: a plan with mBest==0), @@ -3054,7 +3178,8 @@ static int whereLoopAddVirtual( ** xBestIndex again, this time with IN(...) terms disabled. */ if( bIn ){ WHERETRACE(0x40, (" VirtualOne: all usable w/o IN\n")); - rc = whereLoopAddVirtualOne(pBuilder, mPrereq, ALLBITS, WO_IN, p, &bIn); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, ALLBITS, WO_IN, p, mNoOmit, &bIn); assert( bIn==0 ); mBestNoIn = pNew->prereq & ~mPrereq; if( mBestNoIn==0 ){ @@ -3080,7 +3205,8 @@ static int whereLoopAddVirtual( if( mNext==mBest || mNext==mBestNoIn ) continue; WHERETRACE(0x40, (" VirtualOne: mPrev=%04llx mNext=%04llx\n", (sqlite3_uint64)mPrev, (sqlite3_uint64)mNext)); - rc = whereLoopAddVirtualOne(pBuilder, mPrereq, mNext|mPrereq, 0, p, &bIn); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, mNext|mPrereq, 0, p, mNoOmit, &bIn); if( pNew->prereq==mPrereq ){ seenZero = 1; if( bIn==0 ) seenZeroNoIN = 1; @@ -3092,7 +3218,8 @@ static int whereLoopAddVirtual( ** usable), make a call here with all source tables disabled */ if( rc==SQLITE_OK && seenZero==0 ){ WHERETRACE(0x40, (" VirtualOne: all disabled\n")); - rc = whereLoopAddVirtualOne(pBuilder, mPrereq, mPrereq, 0, p, &bIn); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, mPrereq, 0, p, mNoOmit, &bIn); if( bIn==0 ) seenZeroNoIN = 1; } @@ -3101,7 +3228,8 @@ static int whereLoopAddVirtual( ** operator, make a final call to obtain one here. */ if( rc==SQLITE_OK && seenZeroNoIN==0 ){ WHERETRACE(0x40, (" VirtualOne: all disabled and w/o IN\n")); - rc = whereLoopAddVirtualOne(pBuilder, mPrereq, mPrereq, WO_IN, p, &bIn); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, mPrereq, WO_IN, p, mNoOmit, &bIn); } } @@ -3401,6 +3529,14 @@ static i8 wherePathSatisfiesOrderBy( pTerm = sqlite3WhereFindTerm(&pWInfo->sWC, iCur, pOBExpr->iColumn, ~ready, eqOpMask, 0); if( pTerm==0 ) continue; + if( pTerm->eOperator==WO_IN ){ + /* IN terms are only valid for sorting in the ORDER BY LIMIT + ** optimization, and then only if they are actually used + ** by the query plan */ + assert( wctrlFlags & WHERE_ORDERBY_LIMIT ); + for(j=0; jnLTerm && pTerm!=pLoop->aLTerm[j]; j++){} + if( j>=pLoop->nLTerm ) continue; + } if( (pTerm->eOperator&(WO_EQ|WO_IS))!=0 && pOBExpr->iColumn>=0 ){ const char *z1, *z2; pColl = sqlite3ExprCollSeq(pWInfo->pParse, pOrderBy->a[i].pExpr); @@ -3437,20 +3573,42 @@ static i8 wherePathSatisfiesOrderBy( rev = revSet = 0; distinctColumns = 0; for(j=0; ju.btree.nEq - && pLoop->nSkip==0 - && ((i = pLoop->aLTerm[j]->eOperator) & eqOpMask)!=0 - ){ - if( i & WO_ISNULL ){ - testcase( isOrderDistinct ); - isOrderDistinct = 0; + assert( j>=pLoop->u.btree.nEq + || (pLoop->aLTerm[j]==0)==(jnSkip) + ); + if( ju.btree.nEq && j>=pLoop->nSkip ){ + u16 eOp = pLoop->aLTerm[j]->eOperator; + + /* Skip over == and IS and ISNULL terms. (Also skip IN terms when + ** doing WHERE_ORDERBY_LIMIT processing). + ** + ** If the current term is a column of an ((?,?) IN (SELECT...)) + ** expression for which the SELECT returns more than one column, + ** check that it is the only column used by this loop. Otherwise, + ** if it is one of two or more, none of the columns can be + ** considered to match an ORDER BY term. */ + if( (eOp & eqOpMask)!=0 ){ + if( eOp & WO_ISNULL ){ + testcase( isOrderDistinct ); + isOrderDistinct = 0; + } + continue; + }else if( ALWAYS(eOp & WO_IN) ){ + /* ALWAYS() justification: eOp is an equality operator due to the + ** ju.btree.nEq constraint above. Any equality other + ** than WO_IN is captured by the previous "if". So this one + ** always has to be WO_IN. */ + Expr *pX = pLoop->aLTerm[j]->pExpr; + for(i=j+1; iu.btree.nEq; i++){ + if( pLoop->aLTerm[i]->pExpr==pX ){ + assert( (pLoop->aLTerm[i]->eOperator & WO_IN) ); + bOnce = 0; + break; + } + } } - continue; } /* Get the column number in the table (iColumn) and sort order @@ -3479,7 +3637,6 @@ static i8 wherePathSatisfiesOrderBy( /* Find the ORDER BY term that corresponds to the j-th column ** of the index and mark that ORDER BY term off */ - bOnce = 1; isMatch = 0; for(i=0; bOnce && irevMask = pFrom->revLoop; if( pWInfo->nOBSat<=0 ){ pWInfo->nOBSat = 0; - if( nLoop>0 && (pFrom->aLoop[nLoop-1]->wsFlags & WHERE_ONEROW)==0 ){ - Bitmask m = 0; - int rc = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pOrderBy, pFrom, + if( nLoop>0 ){ + u32 wsFlags = pFrom->aLoop[nLoop-1]->wsFlags; + if( (wsFlags & WHERE_ONEROW)==0 + && (wsFlags&(WHERE_IPK|WHERE_COLUMN_IN))!=(WHERE_IPK|WHERE_COLUMN_IN) + ){ + Bitmask m = 0; + int rc = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pOrderBy, pFrom, WHERE_ORDERBY_LIMIT, nLoop-1, pFrom->aLoop[nLoop-1], &m); - if( rc==pWInfo->pOrderBy->nExpr ){ - pWInfo->bOrderedInnerLoop = 1; - pWInfo->revMask = m; + testcase( wsFlags & WHERE_IPK ); + testcase( wsFlags & WHERE_COLUMN_IN ); + if( rc==pWInfo->pOrderBy->nExpr ){ + pWInfo->bOrderedInnerLoop = 1; + pWInfo->revMask = m; + } } } } @@ -4254,22 +4418,25 @@ WhereInfo *sqlite3WhereBegin( ** some architectures. Hence the ROUND8() below. */ nByteWInfo = ROUND8(sizeof(WhereInfo)+(nTabList-1)*sizeof(WhereLevel)); - pWInfo = sqlite3DbMallocZero(db, nByteWInfo + sizeof(WhereLoop)); + pWInfo = sqlite3DbMallocRawNN(db, nByteWInfo + sizeof(WhereLoop)); if( db->mallocFailed ){ sqlite3DbFree(db, pWInfo); pWInfo = 0; goto whereBeginError; } - pWInfo->aiCurOnePass[0] = pWInfo->aiCurOnePass[1] = -1; - pWInfo->nLevel = nTabList; pWInfo->pParse = pParse; pWInfo->pTabList = pTabList; pWInfo->pOrderBy = pOrderBy; pWInfo->pDistinctSet = pDistinctSet; + pWInfo->aiCurOnePass[0] = pWInfo->aiCurOnePass[1] = -1; + pWInfo->nLevel = nTabList; pWInfo->iBreak = pWInfo->iContinue = sqlite3VdbeMakeLabel(v); pWInfo->wctrlFlags = wctrlFlags; pWInfo->iLimit = iAuxArg; pWInfo->savedNQueryLoop = pParse->nQueryLoop; + memset(&pWInfo->nOBSat, 0, + offsetof(WhereInfo,sWC) - offsetof(WhereInfo,nOBSat)); + memset(&pWInfo->a[0], 0, sizeof(WhereLoop)+nTabList*sizeof(WhereLevel)); assert( pWInfo->eOnePass==ONEPASS_OFF ); /* ONEPASS defaults to OFF */ pMaskSet = &pWInfo->sMaskSet; sWLB.pWInfo = pWInfo; @@ -4673,10 +4840,12 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ sqlite3VdbeResolveLabel(v, pLevel->addrNxt); for(j=pLevel->u.in.nIn, pIn=&pLevel->u.in.aInLoop[j-1]; j>0; j--, pIn--){ sqlite3VdbeJumpHere(v, pIn->addrInTop+1); - sqlite3VdbeAddOp2(v, pIn->eEndLoopOp, pIn->iCur, pIn->addrInTop); - VdbeCoverage(v); - VdbeCoverageIf(v, pIn->eEndLoopOp==OP_PrevIfOpen); - VdbeCoverageIf(v, pIn->eEndLoopOp==OP_NextIfOpen); + if( pIn->eEndLoopOp!=OP_Noop ){ + sqlite3VdbeAddOp2(v, pIn->eEndLoopOp, pIn->iCur, pIn->addrInTop); + VdbeCoverage(v); + VdbeCoverageIf(v, pIn->eEndLoopOp==OP_PrevIfOpen); + VdbeCoverageIf(v, pIn->eEndLoopOp==OP_NextIfOpen); + } sqlite3VdbeJumpHere(v, pIn->addrInTop-1); } } @@ -4695,13 +4864,15 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ } #endif if( pLevel->iLeftJoin ){ + int ws = pLoop->wsFlags; addr = sqlite3VdbeAddOp1(v, OP_IfPos, pLevel->iLeftJoin); VdbeCoverage(v); - assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 - || (pLoop->wsFlags & WHERE_INDEXED)!=0 ); - if( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 ){ + assert( (ws & WHERE_IDX_ONLY)==0 || (ws & WHERE_INDEXED)!=0 ); + if( (ws & WHERE_IDX_ONLY)==0 ){ sqlite3VdbeAddOp1(v, OP_NullRow, pTabList->a[i].iCursor); } - if( pLoop->wsFlags & WHERE_INDEXED ){ + if( (ws & WHERE_INDEXED) + || ((ws & WHERE_MULTI_OR) && pLevel->u.pCovidx) + ){ sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iIdxCur); } if( pLevel->op==OP_Return ){ @@ -4740,27 +4911,6 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ continue; } - /* Close all of the cursors that were opened by sqlite3WhereBegin. - ** Except, do not close cursors that will be reused by the OR optimization - ** (WHERE_OR_SUBCLAUSE). And do not close the OP_OpenWrite cursors - ** created for the ONEPASS optimization. - */ - if( (pTab->tabFlags & TF_Ephemeral)==0 - && pTab->pSelect==0 - && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0 - ){ - int ws = pLoop->wsFlags; - if( pWInfo->eOnePass==ONEPASS_OFF && (ws & WHERE_IDX_ONLY)==0 ){ - sqlite3VdbeAddOp1(v, OP_Close, pTabItem->iCursor); - } - if( (ws & WHERE_INDEXED)!=0 - && (ws & (WHERE_IPK|WHERE_AUTO_INDEX))==0 - && pLevel->iIdxCur!=pWInfo->aiCurOnePass[1] - ){ - sqlite3VdbeAddOp1(v, OP_Close, pLevel->iIdxCur); - } - } - /* If this scan uses an index, make VDBE code substitutions to read data ** from the index instead of from the table where possible. In some cases ** this optimization prevents the table from ever being read, which can diff --git a/src/whereInt.h b/src/whereInt.h index 075c04e5e5..fd6ebe77b4 100644 --- a/src/whereInt.h +++ b/src/whereInt.h @@ -122,6 +122,8 @@ struct WhereLoop { union { struct { /* Information for internal btree tables */ u16 nEq; /* Number of equality constraints */ + u16 nBtm; /* Size of BTM vector */ + u16 nTop; /* Size of TOP vector */ Index *pIndex; /* Index used, or NULL */ } btree; struct { /* Information for virtual tables */ @@ -244,19 +246,20 @@ struct WherePath { */ struct WhereTerm { Expr *pExpr; /* Pointer to the subexpression that is this term */ + WhereClause *pWC; /* The clause this term is part of */ + LogEst truthProb; /* Probability of truth for this expression */ + u16 wtFlags; /* TERM_xxx bit flags. See below */ + u16 eOperator; /* A WO_xx value describing */ + u8 nChild; /* Number of children that must disable us */ + u8 eMatchOp; /* Op for vtab MATCH/LIKE/GLOB/REGEXP terms */ int iParent; /* Disable pWC->a[iParent] when this term disabled */ int leftCursor; /* Cursor number of X in "X " */ + int iField; /* Field in (?,?,?) IN (SELECT...) vector */ union { int leftColumn; /* Column number of X in "X " */ WhereOrInfo *pOrInfo; /* Extra information if (eOperator & WO_OR)!=0 */ WhereAndInfo *pAndInfo; /* Extra information if (eOperator& WO_AND)!=0 */ } u; - LogEst truthProb; /* Probability of truth for this expression */ - u16 eOperator; /* A WO_xx value describing */ - u16 wtFlags; /* TERM_xxx bit flags. See below */ - u8 nChild; /* Number of children that must disable us */ - u8 eMatchOp; /* Op for vtab MATCH/LIKE/GLOB/REGEXP terms */ - WhereClause *pWC; /* The clause this term is part of */ Bitmask prereqRight; /* Bitmask of tables used by pExpr->pRight */ Bitmask prereqAll; /* Bitmask of tables referenced by pExpr */ }; @@ -409,25 +412,25 @@ struct WhereInfo { SrcList *pTabList; /* List of tables in the join */ ExprList *pOrderBy; /* The ORDER BY clause or NULL */ ExprList *pDistinctSet; /* DISTINCT over all these values */ - WhereLoop *pLoops; /* List of all WhereLoop objects */ - Bitmask revMask; /* Mask of ORDER BY terms that need reversing */ - LogEst nRowOut; /* Estimated number of output rows */ LogEst iLimit; /* LIMIT if wctrlFlags has WHERE_USE_LIMIT */ + int aiCurOnePass[2]; /* OP_OpenWrite cursors for the ONEPASS opt */ + int iContinue; /* Jump here to continue with next record */ + int iBreak; /* Jump here to break out of the loop */ + int savedNQueryLoop; /* pParse->nQueryLoop outside the WHERE loop */ u16 wctrlFlags; /* Flags originally passed to sqlite3WhereBegin() */ + u8 nLevel; /* Number of nested loop */ i8 nOBSat; /* Number of ORDER BY terms satisfied by indices */ u8 sorted; /* True if really sorted (not just grouped) */ u8 eOnePass; /* ONEPASS_OFF, or _SINGLE, or _MULTI */ u8 untestedTerms; /* Not all WHERE terms resolved by outer loop */ u8 eDistinct; /* One of the WHERE_DISTINCT_* values */ - u8 nLevel; /* Number of nested loop */ u8 bOrderedInnerLoop; /* True if only the inner-most loop is ordered */ int iTop; /* The very beginning of the WHERE loop */ - int iContinue; /* Jump here to continue with next record */ - int iBreak; /* Jump here to break out of the loop */ - int savedNQueryLoop; /* pParse->nQueryLoop outside the WHERE loop */ - int aiCurOnePass[2]; /* OP_OpenWrite cursors for the ONEPASS opt */ - WhereMaskSet sMaskSet; /* Map cursor numbers to bitmasks */ + WhereLoop *pLoops; /* List of all WhereLoop objects */ + Bitmask revMask; /* Mask of ORDER BY terms that need reversing */ + LogEst nRowOut; /* Estimated number of output rows */ WhereClause sWC; /* Decomposition of the WHERE clause */ + WhereMaskSet sMaskSet; /* Map cursor numbers to bitmasks */ WhereLevel a[1]; /* Information about each nest loop in WHERE */ }; diff --git a/src/wherecode.c b/src/wherecode.c index d78982f5b5..032c56fef3 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -21,6 +21,17 @@ #include "whereInt.h" #ifndef SQLITE_OMIT_EXPLAIN + +/* +** Return the name of the i-th column of the pIdx index. +*/ +static const char *explainIndexColumnName(Index *pIdx, int i){ + i = pIdx->aiColumn[i]; + if( i==XN_EXPR ) return ""; + if( i==XN_ROWID ) return "rowid"; + return pIdx->pTable->aCol[i].zName; +} + /* ** This routine is a helper for explainIndexRange() below ** @@ -31,24 +42,32 @@ */ static void explainAppendTerm( StrAccum *pStr, /* The text expression being built */ - int iTerm, /* Index of this term. First is zero */ - const char *zColumn, /* Name of the column */ + Index *pIdx, /* Index to read column names from */ + int nTerm, /* Number of terms */ + int iTerm, /* Zero-based index of first term. */ + int bAnd, /* Non-zero to append " AND " */ const char *zOp /* Name of the operator */ ){ - if( iTerm ) sqlite3StrAccumAppend(pStr, " AND ", 5); - sqlite3StrAccumAppendAll(pStr, zColumn); - sqlite3StrAccumAppend(pStr, zOp, 1); - sqlite3StrAccumAppend(pStr, "?", 1); -} + int i; -/* -** Return the name of the i-th column of the pIdx index. -*/ -static const char *explainIndexColumnName(Index *pIdx, int i){ - i = pIdx->aiColumn[i]; - if( i==XN_EXPR ) return ""; - if( i==XN_ROWID ) return "rowid"; - return pIdx->pTable->aCol[i].zName; + assert( nTerm>=1 ); + if( bAnd ) sqlite3StrAccumAppend(pStr, " AND ", 5); + + if( nTerm>1 ) sqlite3StrAccumAppend(pStr, "(", 1); + for(i=0; i1 ) sqlite3StrAccumAppend(pStr, ")", 1); + + sqlite3StrAccumAppend(pStr, zOp, 1); + + if( nTerm>1 ) sqlite3StrAccumAppend(pStr, "(", 1); + for(i=0; i1 ) sqlite3StrAccumAppend(pStr, ")", 1); } /* @@ -81,12 +100,11 @@ static void explainIndexRange(StrAccum *pStr, WhereLoop *pLoop){ j = i; if( pLoop->wsFlags&WHERE_BTM_LIMIT ){ - const char *z = explainIndexColumnName(pIndex, i); - explainAppendTerm(pStr, i++, z, ">"); + explainAppendTerm(pStr, pIndex, pLoop->u.btree.nBtm, j, i, ">"); + i = 1; } if( pLoop->wsFlags&WHERE_TOP_LIMIT ){ - const char *z = explainIndexColumnName(pIndex, j); - explainAppendTerm(pStr, i, z, "<"); + explainAppendTerm(pStr, pIndex, pLoop->u.btree.nTop, j, i, "<"); } sqlite3StrAccumAppend(pStr, ")", 1); } @@ -276,7 +294,7 @@ void sqlite3WhereAddScanStatus( */ static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){ int nLoop = 0; - while( pTerm + while( ALWAYS(pTerm!=0) && (pTerm->wtFlags & TERM_CODED)==0 && (pLevel->iLeftJoin==0 || ExprHasProperty(pTerm->pExpr, EP_FromJoin)) && (pLevel->notReady & pTerm->prereqAll)==0 @@ -332,16 +350,45 @@ static void codeApplyAffinity(Parse *pParse, int base, int n, char *zAff){ } } +/* +** Expression pRight, which is the RHS of a comparison operation, is +** either a vector of n elements or, if n==1, a scalar expression. +** Before the comparison operation, affinity zAff is to be applied +** to the pRight values. This function modifies characters within the +** affinity string to SQLITE_AFF_BLOB if either: +** +** * the comparison will be performed with no affinity, or +** * the affinity change in zAff is guaranteed not to change the value. +*/ +static void updateRangeAffinityStr( + Expr *pRight, /* RHS of comparison */ + int n, /* Number of vector elements in comparison */ + char *zAff /* Affinity string to modify */ +){ + int i; + for(i=0; ipVdbe; int iReg; /* Register holding results */ + assert( pLevel->pWLoop->aLTerm[iEq]==pTerm ); assert( iTarget>0 ); if( pX->op==TK_EQ || pX->op==TK_IS ){ iReg = sqlite3ExprCodeTarget(pParse, pX->pRight, iTarget); @@ -364,10 +412,13 @@ static int codeEqualityTerm( sqlite3VdbeAddOp2(v, OP_Null, 0, iReg); #ifndef SQLITE_OMIT_SUBQUERY }else{ - int eType; + int eType = IN_INDEX_NOOP; int iTab; struct InLoop *pIn; WhereLoop *pLoop = pLevel->pWLoop; + int i; + int nEq = 0; + int *aiMap = 0; if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 && pLoop->u.btree.pIndex!=0 @@ -379,7 +430,78 @@ static int codeEqualityTerm( } assert( pX->op==TK_IN ); iReg = iTarget; - eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0); + + for(i=0; iaLTerm[i] && pLoop->aLTerm[i]->pExpr==pX ){ + disableTerm(pLevel, pTerm); + return iTarget; + } + } + for(i=iEq;inLTerm; i++){ + if( ALWAYS(pLoop->aLTerm[i]) && pLoop->aLTerm[i]->pExpr==pX ) nEq++; + } + + if( (pX->flags & EP_xIsSelect)==0 || pX->x.pSelect->pEList->nExpr==1 ){ + eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, 0); + }else{ + Select *pSelect = pX->x.pSelect; + sqlite3 *db = pParse->db; + u16 savedDbOptFlags = db->dbOptFlags; + ExprList *pOrigRhs = pSelect->pEList; + ExprList *pOrigLhs = pX->pLeft->x.pList; + ExprList *pRhs = 0; /* New Select.pEList for RHS */ + ExprList *pLhs = 0; /* New pX->pLeft vector */ + + for(i=iEq;inLTerm; i++){ + if( pLoop->aLTerm[i]->pExpr==pX ){ + int iField = pLoop->aLTerm[i]->iField - 1; + Expr *pNewRhs = sqlite3ExprDup(db, pOrigRhs->a[iField].pExpr, 0); + Expr *pNewLhs = sqlite3ExprDup(db, pOrigLhs->a[iField].pExpr, 0); + + pRhs = sqlite3ExprListAppend(pParse, pRhs, pNewRhs); + pLhs = sqlite3ExprListAppend(pParse, pLhs, pNewLhs); + } + } + if( !db->mallocFailed ){ + Expr *pLeft = pX->pLeft; + + if( pSelect->pOrderBy ){ + /* If the SELECT statement has an ORDER BY clause, zero the + ** iOrderByCol variables. These are set to non-zero when an + ** ORDER BY term exactly matches one of the terms of the + ** result-set. Since the result-set of the SELECT statement may + ** have been modified or reordered, these variables are no longer + ** set correctly. Since setting them is just an optimization, + ** it's easiest just to zero them here. */ + ExprList *pOrderBy = pSelect->pOrderBy; + for(i=0; inExpr; i++){ + pOrderBy->a[i].u.x.iOrderByCol = 0; + } + } + + /* Take care here not to generate a TK_VECTOR containing only a + ** single value. Since the parser never creates such a vector, some + ** of the subroutines do not handle this case. */ + if( pLhs->nExpr==1 ){ + pX->pLeft = pLhs->a[0].pExpr; + }else{ + pLeft->x.pList = pLhs; + aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int) * nEq); + testcase( aiMap==0 ); + } + pSelect->pEList = pRhs; + db->dbOptFlags |= SQLITE_QueryFlattener; + eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap); + db->dbOptFlags = savedDbOptFlags; + testcase( aiMap!=0 && aiMap[0]!=0 ); + pSelect->pEList = pOrigRhs; + pLeft->x.pList = pOrigLhs; + pX->pLeft = pLeft; + } + sqlite3ExprListDelete(pParse->db, pLhs); + sqlite3ExprListDelete(pParse->db, pRhs); + } + if( eType==IN_INDEX_INDEX_DESC ){ testcase( bRev ); bRev = !bRev; @@ -389,28 +511,45 @@ static int codeEqualityTerm( VdbeCoverageIf(v, bRev); VdbeCoverageIf(v, !bRev); assert( (pLoop->wsFlags & WHERE_MULTI_OR)==0 ); + pLoop->wsFlags |= WHERE_IN_ABLE; if( pLevel->u.in.nIn==0 ){ pLevel->addrNxt = sqlite3VdbeMakeLabel(v); } - pLevel->u.in.nIn++; + + i = pLevel->u.in.nIn; + pLevel->u.in.nIn += nEq; pLevel->u.in.aInLoop = sqlite3DbReallocOrFree(pParse->db, pLevel->u.in.aInLoop, sizeof(pLevel->u.in.aInLoop[0])*pLevel->u.in.nIn); pIn = pLevel->u.in.aInLoop; if( pIn ){ - pIn += pLevel->u.in.nIn - 1; - pIn->iCur = iTab; - if( eType==IN_INDEX_ROWID ){ - pIn->addrInTop = sqlite3VdbeAddOp2(v, OP_Rowid, iTab, iReg); - }else{ - pIn->addrInTop = sqlite3VdbeAddOp3(v, OP_Column, iTab, 0, iReg); + int iMap = 0; /* Index in aiMap[] */ + pIn += i; + for(i=iEq;inLTerm; i++){ + if( pLoop->aLTerm[i]->pExpr==pX ){ + int iOut = iReg + i - iEq; + if( eType==IN_INDEX_ROWID ){ + testcase( nEq>1 ); /* Happens with a UNIQUE index on ROWID */ + pIn->addrInTop = sqlite3VdbeAddOp2(v, OP_Rowid, iTab, iOut); + }else{ + int iCol = aiMap ? aiMap[iMap++] : 0; + pIn->addrInTop = sqlite3VdbeAddOp3(v,OP_Column,iTab, iCol, iOut); + } + sqlite3VdbeAddOp1(v, OP_IsNull, iOut); VdbeCoverage(v); + if( i==iEq ){ + pIn->iCur = iTab; + pIn->eEndLoopOp = bRev ? OP_PrevIfOpen : OP_NextIfOpen; + }else{ + pIn->eEndLoopOp = OP_Noop; + } + pIn++; + } } - pIn->eEndLoopOp = bRev ? OP_PrevIfOpen : OP_NextIfOpen; - sqlite3VdbeAddOp1(v, OP_IsNull, iReg); VdbeCoverage(v); }else{ pLevel->u.in.nIn = 0; } + sqlite3DbFree(pParse->db, aiMap); #endif } disableTerm(pLevel, pTerm); @@ -536,9 +675,15 @@ static int codeAllEqualityTerms( sqlite3VdbeAddOp2(v, OP_SCopy, r1, regBase+j); } } - testcase( pTerm->eOperator & WO_ISNULL ); - testcase( pTerm->eOperator & WO_IN ); - if( (pTerm->eOperator & (WO_ISNULL|WO_IN))==0 ){ + if( pTerm->eOperator & WO_IN ){ + if( pTerm->pExpr->flags & EP_xIsSelect ){ + /* No affinity ever needs to be (or should be) applied to a value + ** from the RHS of an "? IN (SELECT ...)" expression. The + ** sqlite3FindInIndex() routine has already ensured that the + ** affinity of the comparison has been applied to the value. */ + if( zAff ) zAff[j] = SQLITE_AFF_BLOB; + } + }else if( (pTerm->eOperator & WO_ISNULL)==0 ){ Expr *pRight = pTerm->pExpr->pRight; if( (pTerm->wtFlags & TERM_IS)==0 && sqlite3ExprCanBeNull(pRight) ){ sqlite3VdbeAddOp2(v, OP_IsNull, regBase+j, pLevel->addrBrk); @@ -861,6 +1006,39 @@ static void codeDeferredSeek( } } +/* +** If the expression passed as the second argument is a vector, generate +** code to write the first nReg elements of the vector into an array +** of registers starting with iReg. +** +** If the expression is not a vector, then nReg must be passed 1. In +** this case, generate code to evaluate the expression and leave the +** result in register iReg. +*/ +static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){ + assert( nReg>0 ); + if( sqlite3ExprIsVector(p) ){ +#ifndef SQLITE_OMIT_SUBQUERY + if( (p->flags & EP_xIsSelect) ){ + Vdbe *v = pParse->pVdbe; + int iSelect = sqlite3CodeSubselect(pParse, p, 0, 0); + sqlite3VdbeAddOp3(v, OP_Copy, iSelect, iReg, nReg-1); + }else +#endif + { + int i; + ExprList *pList = p->x.pList; + assert( nReg<=pList->nExpr ); + for(i=0; ia[i].pExpr, iReg+i); + } + } + }else{ + assert( nReg==1 ); + sqlite3ExprCode(pParse, p, iReg); + } +} + /* ** Generate code for the start of the iLevel-th loop in the WHERE clause ** implementation described by pWInfo. @@ -956,14 +1134,15 @@ Bitmask sqlite3WhereCodeOneLoopStart( codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, iTarget); addrNotFound = pLevel->addrNxt; }else{ - sqlite3ExprCode(pParse, pTerm->pExpr->pRight, iTarget); + Expr *pRight = pTerm->pExpr->pRight; + codeExprOrVector(pParse, pRight, iTarget, 1); } } sqlite3VdbeAddOp2(v, OP_Integer, pLoop->u.vtab.idxNum, iReg); sqlite3VdbeAddOp2(v, OP_Integer, nConstraint, iReg+1); sqlite3VdbeAddOp4(v, OP_VFilter, iCur, addrNotFound, iReg, pLoop->u.vtab.idxStr, - pLoop->u.vtab.needFree ? P4_MPRINTF : P4_STATIC); + pLoop->u.vtab.needFree ? P4_DYNAMIC : P4_STATIC); VdbeCoverage(v); pLoop->u.vtab.needFree = 0; pLevel->p1 = iCur; @@ -996,7 +1175,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( /* Generate code that will continue to the next row if ** the IN constraint is not satisfied */ - pCompare = sqlite3PExpr(pParse, TK_EQ, 0, 0, 0); + pCompare = sqlite3PExpr(pParse, TK_EQ, 0, 0); assert( pCompare!=0 || db->mallocFailed ); if( pCompare ){ pCompare->pLeft = pTerm->pExpr->pLeft; @@ -1070,6 +1249,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( if( pStart ){ Expr *pX; /* The expression that defines the start bound */ int r1, rTemp; /* Registers for holding the start boundary */ + int op; /* Cursor seek operation */ /* The following constant maps TK_xx codes into corresponding ** seek opcodes. It depends on a particular ordering of TK_xx @@ -1089,8 +1269,16 @@ Bitmask sqlite3WhereCodeOneLoopStart( pX = pStart->pExpr; assert( pX!=0 ); testcase( pStart->leftCursor!=iCur ); /* transitive constraints */ - r1 = sqlite3ExprCodeTemp(pParse, pX->pRight, &rTemp); - sqlite3VdbeAddOp3(v, aMoveOp[pX->op-TK_GT], iCur, addrBrk, r1); + if( sqlite3ExprIsVector(pX->pRight) ){ + r1 = rTemp = sqlite3GetTempReg(pParse); + codeExprOrVector(pParse, pX->pRight, r1, 1); + op = aMoveOp[(pX->op - TK_GT) | 0x0001]; + }else{ + r1 = sqlite3ExprCodeTemp(pParse, pX->pRight, &rTemp); + disableTerm(pLevel, pStart); + op = aMoveOp[(pX->op - TK_GT)]; + } + sqlite3VdbeAddOp3(v, op, iCur, addrBrk, r1); VdbeComment((v, "pk")); VdbeCoverageIf(v, pX->op==TK_GT); VdbeCoverageIf(v, pX->op==TK_LE); @@ -1098,7 +1286,6 @@ Bitmask sqlite3WhereCodeOneLoopStart( VdbeCoverageIf(v, pX->op==TK_GE); sqlite3ExprCacheAffinityChange(pParse, r1, 1); sqlite3ReleaseTempReg(pParse, rTemp); - disableTerm(pLevel, pStart); }else{ sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iCur, addrBrk); VdbeCoverageIf(v, bRev==0); @@ -1112,13 +1299,17 @@ Bitmask sqlite3WhereCodeOneLoopStart( testcase( pEnd->leftCursor!=iCur ); /* Transitive constraints */ testcase( pEnd->wtFlags & TERM_VIRTUAL ); memEndValue = ++pParse->nMem; - sqlite3ExprCode(pParse, pX->pRight, memEndValue); - if( pX->op==TK_LT || pX->op==TK_GT ){ + codeExprOrVector(pParse, pX->pRight, memEndValue, 1); + if( 0==sqlite3ExprIsVector(pX->pRight) + && (pX->op==TK_LT || pX->op==TK_GT) + ){ testOp = bRev ? OP_Le : OP_Ge; }else{ testOp = bRev ? OP_Lt : OP_Gt; } - disableTerm(pLevel, pEnd); + if( 0==sqlite3ExprIsVector(pX->pRight) ){ + disableTerm(pLevel, pEnd); + } } start = sqlite3VdbeCurrentAddr(v); pLevel->op = bRev ? OP_Prev : OP_Next; @@ -1185,6 +1376,8 @@ Bitmask sqlite3WhereCodeOneLoopStart( OP_IdxLT, /* 3: (end_constraints && bRev && endEq) */ }; u16 nEq = pLoop->u.btree.nEq; /* Number of == or IN terms */ + u16 nBtm = pLoop->u.btree.nBtm; /* Length of BTM vector */ + u16 nTop = pLoop->u.btree.nTop; /* Length of TOP vector */ int regBase; /* Base register holding constraint values */ WhereTerm *pRangeStart = 0; /* Inequality constraint at range start */ WhereTerm *pRangeEnd = 0; /* Inequality constraint at range end */ @@ -1197,7 +1390,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( int nExtraReg = 0; /* Number of extra registers needed */ int op; /* Instruction opcode */ char *zStartAff; /* Affinity for start of range constraint */ - char cEndAff = 0; /* Affinity for end of range constraint */ + char *zEndAff = 0; /* Affinity for end of range constraint */ u8 bSeekPastNull = 0; /* True to seek past initial nulls */ u8 bStopAtNull = 0; /* Add condition to terminate at NULLs */ @@ -1231,14 +1424,14 @@ Bitmask sqlite3WhereCodeOneLoopStart( j = nEq; if( pLoop->wsFlags & WHERE_BTM_LIMIT ){ pRangeStart = pLoop->aLTerm[j++]; - nExtraReg = 1; + nExtraReg = MAX(nExtraReg, pLoop->u.btree.nBtm); /* Like optimization range constraints always occur in pairs */ assert( (pRangeStart->wtFlags & TERM_LIKEOPT)==0 || (pLoop->wsFlags & WHERE_TOP_LIMIT)!=0 ); } if( pLoop->wsFlags & WHERE_TOP_LIMIT ){ pRangeEnd = pLoop->aLTerm[j++]; - nExtraReg = 1; + nExtraReg = MAX(nExtraReg, pLoop->u.btree.nTop); #ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS if( (pRangeEnd->wtFlags & TERM_LIKEOPT)!=0 ){ assert( pRangeStart!=0 ); /* LIKE opt constraints */ @@ -1256,11 +1449,11 @@ Bitmask sqlite3WhereCodeOneLoopStart( pLevel->iLikeRepCntr |= bRev ^ (pIdx->aSortOrder[nEq]==SQLITE_SO_DESC); } #endif - if( pRangeStart==0 - && (j = pIdx->aiColumn[nEq])>=0 - && pIdx->pTable->aCol[j].notNull==0 - ){ - bSeekPastNull = 1; + if( pRangeStart==0 ){ + j = pIdx->aiColumn[nEq]; + if( (j>=0 && pIdx->pTable->aCol[j].notNull==0) || j==XN_EXPR ){ + bSeekPastNull = 1; + } } } assert( pRangeEnd==0 || (pRangeEnd->wtFlags & TERM_VNULL)==0 ); @@ -1274,6 +1467,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( ){ SWAP(WhereTerm *, pRangeEnd, pRangeStart); SWAP(u8, bSeekPastNull, bStopAtNull); + SWAP(u8, nBtm, nTop); } /* Generate code to evaluate all constraint terms using == or IN @@ -1283,7 +1477,9 @@ Bitmask sqlite3WhereCodeOneLoopStart( codeCursorHint(pTabItem, pWInfo, pLevel, pRangeEnd); regBase = codeAllEqualityTerms(pParse,pLevel,bRev,nExtraReg,&zStartAff); assert( zStartAff==0 || sqlite3Strlen30(zStartAff)>=nEq ); - if( zStartAff ) cEndAff = zStartAff[nEq]; + if( zStartAff && nTop ){ + zEndAff = sqlite3DbStrDup(db, &zStartAff[nEq]); + } addrNxt = pLevel->addrNxt; testcase( pRangeStart && (pRangeStart->eOperator & WO_LE)!=0 ); @@ -1298,7 +1494,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( nConstraint = nEq; if( pRangeStart ){ Expr *pRight = pRangeStart->pExpr->pRight; - sqlite3ExprCode(pParse, pRight, regBase+nEq); + codeExprOrVector(pParse, pRight, regBase+nEq, nBtm); whereLikeOptimizationStringFixup(v, pLevel, pRangeStart); if( (pRangeStart->wtFlags & TERM_VNULL)==0 && sqlite3ExprCanBeNull(pRight) @@ -1307,18 +1503,15 @@ Bitmask sqlite3WhereCodeOneLoopStart( VdbeCoverage(v); } if( zStartAff ){ - if( sqlite3CompareAffinity(pRight, zStartAff[nEq])==SQLITE_AFF_BLOB){ - /* Since the comparison is to be performed with no conversions - ** applied to the operands, set the affinity to apply to pRight to - ** SQLITE_AFF_BLOB. */ - zStartAff[nEq] = SQLITE_AFF_BLOB; - } - if( sqlite3ExprNeedsNoAffinityChange(pRight, zStartAff[nEq]) ){ - zStartAff[nEq] = SQLITE_AFF_BLOB; - } + updateRangeAffinityStr(pRight, nBtm, &zStartAff[nEq]); } - nConstraint++; + nConstraint += nBtm; testcase( pRangeStart->wtFlags & TERM_VIRTUAL ); + if( sqlite3ExprIsVector(pRight)==0 ){ + disableTerm(pLevel, pRangeStart); + }else{ + startEq = 1; + } bSeekPastNull = 0; }else if( bSeekPastNull ){ sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq); @@ -1351,7 +1544,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( if( pRangeEnd ){ Expr *pRight = pRangeEnd->pExpr->pRight; sqlite3ExprCacheRemove(pParse, regBase+nEq, 1); - sqlite3ExprCode(pParse, pRight, regBase+nEq); + codeExprOrVector(pParse, pRight, regBase+nEq, nTop); whereLikeOptimizationStringFixup(v, pLevel, pRangeEnd); if( (pRangeEnd->wtFlags & TERM_VNULL)==0 && sqlite3ExprCanBeNull(pRight) @@ -1359,19 +1552,27 @@ Bitmask sqlite3WhereCodeOneLoopStart( sqlite3VdbeAddOp2(v, OP_IsNull, regBase+nEq, addrNxt); VdbeCoverage(v); } - if( sqlite3CompareAffinity(pRight, cEndAff)!=SQLITE_AFF_BLOB - && !sqlite3ExprNeedsNoAffinityChange(pRight, cEndAff) - ){ - codeApplyAffinity(pParse, regBase+nEq, 1, &cEndAff); + if( zEndAff ){ + updateRangeAffinityStr(pRight, nTop, zEndAff); + codeApplyAffinity(pParse, regBase+nEq, nTop, zEndAff); + }else{ + assert( pParse->db->mallocFailed ); } - nConstraint++; + nConstraint += nTop; testcase( pRangeEnd->wtFlags & TERM_VIRTUAL ); + + if( sqlite3ExprIsVector(pRight)==0 ){ + disableTerm(pLevel, pRangeEnd); + }else{ + endEq = 1; + } }else if( bStopAtNull ){ sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq); endEq = 0; nConstraint++; } sqlite3DbFree(db, zStartAff); + sqlite3DbFree(db, zEndAff); /* Top of the loop body */ pLevel->p2 = sqlite3VdbeCurrentAddr(v); @@ -1387,8 +1588,6 @@ Bitmask sqlite3WhereCodeOneLoopStart( } /* Seek the table cursor, if required */ - disableTerm(pLevel, pRangeStart); - disableTerm(pLevel, pRangeEnd); if( omitTable ){ /* pIdx is a covering index. No need to access the main table. */ }else if( HasRowid(pIdx->pTable) ){ @@ -1412,9 +1611,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( iRowidReg, pPk->nKeyCol); VdbeCoverage(v); } - /* Record the instruction used to terminate the loop. Disable - ** WHERE clause terms made redundant by the index range scan. - */ + /* Record the instruction used to terminate the loop. */ if( pLoop->wsFlags & WHERE_ONEROW ){ pLevel->op = OP_Noop; }else if( bRev ){ @@ -1491,7 +1688,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( u16 wctrlFlags; /* Flags for sub-WHERE clause */ Expr *pAndExpr = 0; /* An ".. AND (...)" expression */ Table *pTab = pTabItem->pTab; - + pTerm = pLoop->aLTerm[0]; assert( pTerm!=0 ); assert( pTerm->eOperator & WO_OR ); @@ -1577,7 +1774,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( pAndExpr = sqlite3ExprAnd(db, pAndExpr, pExpr); } if( pAndExpr ){ - pAndExpr = sqlite3PExpr(pParse, TK_AND|TKFLG_DONTFOLD, 0, pAndExpr, 0); + pAndExpr = sqlite3PExpr(pParse, TK_AND|TKFLG_DONTFOLD, 0, pAndExpr); } } @@ -1650,7 +1847,8 @@ Bitmask sqlite3WhereCodeOneLoopStart( } if( iSet>=0 ){ sqlite3VdbeAddOp3(v, OP_MakeRecord, r, nPk, regRowid); - sqlite3VdbeAddOp3(v, OP_IdxInsert, regRowset, regRowid, 0); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, regRowset, regRowid, + r, nPk); if( iSet ) sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); } @@ -1792,7 +1990,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( ** the implied "t1.a=123" constraint. */ for(pTerm=pWC->a, j=pWC->nTerm; j>0; j--, pTerm++){ - Expr *pE, *pEAlt; + Expr *pE, sEAlt; WhereTerm *pAlt; if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; if( (pTerm->eOperator & (WO_EQ|WO_IS))==0 ) continue; @@ -1810,13 +2008,9 @@ Bitmask sqlite3WhereCodeOneLoopStart( testcase( pAlt->eOperator & WO_IS ); testcase( pAlt->eOperator & WO_IN ); VdbeModuleComment((v, "begin transitive constraint")); - pEAlt = sqlite3StackAllocRaw(db, sizeof(*pEAlt)); - if( pEAlt ){ - *pEAlt = *pAlt->pExpr; - pEAlt->pLeft = pE->pLeft; - sqlite3ExprIfFalse(pParse, pEAlt, addrCont, SQLITE_JUMPIFNULL); - sqlite3StackFree(db, pEAlt); - } + sEAlt = *pAlt->pExpr; + sEAlt.pLeft = pE->pLeft; + sqlite3ExprIfFalse(pParse, &sEAlt, addrCont, SQLITE_JUMPIFNULL); } /* For a LEFT OUTER JOIN, generate code that will record the fact that diff --git a/src/whereexpr.c b/src/whereexpr.c index 2768428e2d..4bb161044e 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -77,7 +77,6 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u16 wtFlags){ sqlite3DbFree(db, pOld); } pWC->nSlot = sqlite3DbMallocSize(db, pWC->a)/sizeof(pWC->a[0]); - memset(&pWC->a[pWC->nTerm], 0, sizeof(pWC->a[0])*(pWC->nSlot-pWC->nTerm)); } pTerm = &pWC->a[idx = pWC->nTerm++]; if( p && ExprHasProperty(p, EP_Unlikely) ){ @@ -89,13 +88,15 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u16 wtFlags){ pTerm->wtFlags = wtFlags; pTerm->pWC = pWC; pTerm->iParent = -1; + memset(&pTerm->eOperator, 0, + sizeof(WhereTerm) - offsetof(WhereTerm,eOperator)); return idx; } /* ** Return TRUE if the given operator is one of the operators that is ** allowed for an indexable WHERE clause term. The allowed operators are -** "=", "<", ">", "<=", ">=", "IN", and "IS NULL" +** "=", "<", ">", "<=", ">=", "IN", "IS", and "IS NULL" */ static int allowedOp(int op){ assert( TK_GT>TK_EQ && TK_GT, <, >= or <=), perform the processing + ** on the first element of the vector. */ + assert( TK_GT+1==TK_LE && TK_GT+2==TK_LT && TK_GT+3==TK_GE ); + assert( TK_ISop==TK_VECTOR && (op>=TK_GT && ALWAYS(op<=TK_GE)) ){ + pExpr = pExpr->x.pList->a[0].pExpr; + } + if( pExpr->op==TK_COLUMN ){ *piCur = pExpr->iTable; *piColumn = pExpr->iColumn; @@ -851,10 +865,10 @@ static int exprMightBeIndexed( for(pIdx=pFrom->a[i].pTab->pIndex; pIdx; pIdx=pIdx->pNext){ if( pIdx->aColExpr==0 ) continue; for(i=0; inKeyCol; i++){ - if( pIdx->aiColumn[i]!=(-2) ) continue; + if( pIdx->aiColumn[i]!=XN_EXPR ) continue; if( sqlite3ExprCompare(pExpr, pIdx->aColExpr->a[i].pExpr, iCur)==0 ){ *piCur = iCur; - *piColumn = -2; + *piColumn = XN_EXPR; return 1; } } @@ -911,6 +925,7 @@ static void exprAnalyze( op = pExpr->op; if( op==TK_IN ){ assert( pExpr->pRight==0 ); + if( sqlite3ExprCheckIN(pParse, pExpr) ) return; if( ExprHasProperty(pExpr, EP_xIsSelect) ){ pTerm->prereqRight = exprSelectUsage(pMaskSet, pExpr->x.pSelect); }else{ @@ -937,18 +952,26 @@ static void exprAnalyze( Expr *pLeft = sqlite3ExprSkipCollate(pExpr->pLeft); Expr *pRight = sqlite3ExprSkipCollate(pExpr->pRight); u16 opMask = (pTerm->prereqRight & prereqLeft)==0 ? WO_ALL : WO_EQUIV; - if( exprMightBeIndexed(pSrc, prereqLeft, pLeft, &iCur, &iColumn) ){ + + if( pTerm->iField>0 ){ + assert( op==TK_IN ); + assert( pLeft->op==TK_VECTOR ); + pLeft = pLeft->x.pList->a[pTerm->iField-1].pExpr; + } + + if( exprMightBeIndexed(pSrc, op, prereqLeft, pLeft, &iCur, &iColumn) ){ pTerm->leftCursor = iCur; pTerm->u.leftColumn = iColumn; pTerm->eOperator = operatorMask(op) & opMask; } if( op==TK_IS ) pTerm->wtFlags |= TERM_IS; if( pRight - && exprMightBeIndexed(pSrc, pTerm->prereqRight, pRight, &iCur, &iColumn) + && exprMightBeIndexed(pSrc, op, pTerm->prereqRight, pRight, &iCur,&iColumn) ){ WhereTerm *pNew; Expr *pDup; u16 eExtraOp = 0; /* Extra bits for pNew->eOperator */ + assert( pTerm->iField==0 ); if( pTerm->leftCursor>=0 ){ int idxNew; pDup = sqlite3ExprDup(db, pExpr, 0); @@ -1009,7 +1032,7 @@ static void exprAnalyze( int idxNew; pNewExpr = sqlite3PExpr(pParse, ops[i], sqlite3ExprDup(db, pExpr->pLeft, 0), - sqlite3ExprDup(db, pList->a[i].pExpr, 0), 0); + sqlite3ExprDup(db, pList->a[i].pExpr, 0)); transferJoinMarkings(pNewExpr, pExpr); idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC); testcase( idxNew==0 ); @@ -1094,7 +1117,7 @@ static void exprAnalyze( pNewExpr1 = sqlite3ExprDup(db, pLeft, 0); pNewExpr1 = sqlite3PExpr(pParse, TK_GE, sqlite3ExprAddCollateString(pParse,pNewExpr1,zCollSeqName), - pStr1, 0); + pStr1); transferJoinMarkings(pNewExpr1, pExpr); idxNew1 = whereClauseInsert(pWC, pNewExpr1, wtFlags); testcase( idxNew1==0 ); @@ -1102,7 +1125,7 @@ static void exprAnalyze( pNewExpr2 = sqlite3ExprDup(db, pLeft, 0); pNewExpr2 = sqlite3PExpr(pParse, TK_LT, sqlite3ExprAddCollateString(pParse,pNewExpr2,zCollSeqName), - pStr2, 0); + pStr2); transferJoinMarkings(pNewExpr2, pExpr); idxNew2 = whereClauseInsert(pWC, pNewExpr2, wtFlags); testcase( idxNew2==0 ); @@ -1135,7 +1158,7 @@ static void exprAnalyze( if( (prereqExpr & prereqColumn)==0 ){ Expr *pNewExpr; pNewExpr = sqlite3PExpr(pParse, TK_MATCH, - 0, sqlite3ExprDup(db, pRight, 0), 0); + 0, sqlite3ExprDup(db, pRight, 0)); idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC); testcase( idxNew==0 ); pNewTerm = &pWC->a[idxNew]; @@ -1152,6 +1175,60 @@ static void exprAnalyze( } #endif /* SQLITE_OMIT_VIRTUALTABLE */ + /* If there is a vector == or IS term - e.g. "(a, b) == (?, ?)" - create + ** new terms for each component comparison - "a = ?" and "b = ?". The + ** new terms completely replace the original vector comparison, which is + ** no longer used. + ** + ** This is only required if at least one side of the comparison operation + ** is not a sub-select. */ + if( pWC->op==TK_AND + && (pExpr->op==TK_EQ || pExpr->op==TK_IS) + && sqlite3ExprIsVector(pExpr->pLeft) + && ( (pExpr->pLeft->flags & EP_xIsSelect)==0 + || (pExpr->pRight->flags & EP_xIsSelect)==0 + )){ + int nLeft = sqlite3ExprVectorSize(pExpr->pLeft); + int i; + assert( nLeft==sqlite3ExprVectorSize(pExpr->pRight) ); + for(i=0; ipLeft, i); + Expr *pRight = sqlite3ExprForVectorField(pParse, pExpr->pRight, i); + + pNew = sqlite3PExpr(pParse, pExpr->op, pLeft, pRight); + transferJoinMarkings(pNew, pExpr); + idxNew = whereClauseInsert(pWC, pNew, TERM_DYNAMIC); + exprAnalyze(pSrc, pWC, idxNew); + } + pTerm = &pWC->a[idxTerm]; + pTerm->wtFlags = TERM_CODED|TERM_VIRTUAL; /* Disable the original */ + pTerm->eOperator = 0; + } + + /* If there is a vector IN term - e.g. "(a, b) IN (SELECT ...)" - create + ** a virtual term for each vector component. The expression object + ** used by each such virtual term is pExpr (the full vector IN(...) + ** expression). The WhereTerm.iField variable identifies the index within + ** the vector on the LHS that the virtual term represents. + ** + ** This only works if the RHS is a simple SELECT, not a compound + */ + if( pWC->op==TK_AND && pExpr->op==TK_IN && pTerm->iField==0 + && pExpr->pLeft->op==TK_VECTOR + && pExpr->x.pSelect->pPrior==0 + ){ + int i; + for(i=0; ipLeft); i++){ + int idxNew; + idxNew = whereClauseInsert(pWC, pExpr, TERM_VIRTUAL); + pWC->a[idxNew].iField = i+1; + exprAnalyze(pSrc, pWC, idxNew); + markTermAsChild(pWC, idxNew, idxTerm); + } + } + #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 /* When sqlite_stat3 histogram data is available an operator of the ** form "x IS NOT NULL" can sometimes be evaluated more efficiently @@ -1172,7 +1249,7 @@ static void exprAnalyze( pNewExpr = sqlite3PExpr(pParse, TK_GT, sqlite3ExprDup(db, pLeft, 0), - sqlite3PExpr(pParse, TK_NULL, 0, 0, 0), 0); + sqlite3ExprAlloc(db, TK_NULL, 0, 0)); idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC|TERM_VNULL); @@ -1193,6 +1270,8 @@ static void exprAnalyze( /* Prevent ON clause terms of a LEFT JOIN from being used to drive ** an index for tables to the left of the join. */ + testcase( pTerm!=&pWC->a[idxTerm] ); + pTerm = &pWC->a[idxTerm]; pTerm->prereqRight |= extraRight; } @@ -1350,13 +1429,13 @@ void sqlite3WhereTabFuncArgs( pTab->zName, j); return; } - pColRef = sqlite3PExpr(pParse, TK_COLUMN, 0, 0, 0); + pColRef = sqlite3ExprAlloc(pParse->db, TK_COLUMN, 0, 0); if( pColRef==0 ) return; pColRef->iTable = pItem->iCursor; pColRef->iColumn = k++; pColRef->pTab = pTab; pTerm = sqlite3PExpr(pParse, TK_EQ, pColRef, - sqlite3ExprDup(pParse->db, pArgs->a[j].pExpr, 0), 0); + sqlite3ExprDup(pParse->db, pArgs->a[j].pExpr, 0)); whereClauseInsert(pWC, pTerm, TERM_DYNAMIC); } } diff --git a/test/alter.test b/test/alter.test index ebfe97a764..76c18cedc9 100644 --- a/test/alter.test +++ b/test/alter.test @@ -77,7 +77,7 @@ do_test alter-1.2 { ifcapable tempdb { execsql { INSERT INTO objlist SELECT type, name, tbl_name - FROM sqlite_temp_master WHERE NAME!='objlist'; + FROM temp.sqlite_master WHERE NAME!='objlist'; } } @@ -153,7 +153,7 @@ ifcapable tempdb { CREATE TEMP TABLE objlist(type, name, tbl_name); INSERT INTO objlist SELECT type, name, tbl_name FROM sqlite_master; INSERT INTO objlist - SELECT type, name, tbl_name FROM sqlite_temp_master + SELECT type, name, tbl_name FROM temp.sqlite_master WHERE NAME!='objlist'; SELECT type, name, tbl_name FROM objlist ORDER BY tbl_name, type desc, name; @@ -524,7 +524,7 @@ do_test alter-3.3.7 { ifcapable tempdb { do_test alter-3.3.8 { execsql { - SELECT * FROM sqlite_temp_master WHERE type = 'trigger'; + SELECT * FROM temp.sqlite_master WHERE type = 'trigger'; } } {} } diff --git a/test/alter4.test b/test/alter4.test index 2cd78777a2..5155110cf5 100644 --- a/test/alter4.test +++ b/test/alter4.test @@ -45,18 +45,33 @@ do_test alter4-1.1 { SELECT sql FROM sqlite_temp_master; } } {{CREATE TABLE abc(a, b, c)}} +do_test alter4-1.1b { + execsql { + SELECT sql FROM temp.sqlite_master; + } +} {{CREATE TABLE abc(a, b, c)}} do_test alter4-1.2 { execsql {ALTER TABLE abc ADD d INTEGER;} execsql { SELECT sql FROM sqlite_temp_master; } } {{CREATE TABLE abc(a, b, c, d INTEGER)}} +do_test alter4-1.2b { + execsql { + SELECT sql FROM temp.sqlite_master; + } +} {{CREATE TABLE abc(a, b, c, d INTEGER)}} do_test alter4-1.3 { execsql {ALTER TABLE abc ADD e} execsql { SELECT sql FROM sqlite_temp_master; } } {{CREATE TABLE abc(a, b, c, d INTEGER, e)}} +do_test alter4-1.3b { + execsql { + SELECT sql FROM temp.sqlite_master; + } +} {{CREATE TABLE abc(a, b, c, d INTEGER, e)}} do_test alter4-1.4 { execsql { CREATE TABLE temp.t1(a, b); @@ -64,6 +79,11 @@ do_test alter4-1.4 { SELECT sql FROM sqlite_temp_master WHERE tbl_name = 't1'; } } {{CREATE TABLE t1(a, b, c)}} +do_test alter4-1.4b { + execsql { + SELECT sql FROM temp.sqlite_master WHERE tbl_name = 't1'; + } +} {{CREATE TABLE t1(a, b, c)}} do_test alter4-1.5 { execsql { ALTER TABLE t1 ADD d CHECK (a>d); diff --git a/test/analyzeF.test b/test/analyzeF.test index 3cbc5f47be..76891ddfe3 100644 --- a/test/analyzeF.test +++ b/test/analyzeF.test @@ -120,5 +120,31 @@ do_catchsql_test 4.4 { SELECT * FROM t1 WHERE x = test_zeroblob(1100000) AND y = 4; } {1 {string or blob too big}} +# 2016-12-08: Constraints of the form "x=? AND x IS NOT NULL" were being +# mishandled. The sqlite3Stat4ProbeSetValue() routine was assuming that +# valueNew() was returning a Mem object that was preset to NULL, which is +# not the case. The consequence was the the "x IS NOT NULL" constraint +# was used to drive the index (via the "x>NULL" pseudo-constraint) rather +# than the "x=?" constraint. +# +do_execsql_test 5.1 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT, c INT); + WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<10000) + INSERT INTO t1(a, c) SELECT x, x FROM c; + UPDATE t1 SET b=printf('x%02x',a/500) WHERE a>4000; + UPDATE t1 SET b='xyz' where a>=9998; + CREATE INDEX t1b ON t1(b); + ANALYZE; + SELECT count(*), b FROM t1 GROUP BY 2 ORDER BY 2; +} {4000 {} 499 x08 500 x09 500 x0a 500 x0b 500 x0c 500 x0d 500 x0e 500 x0f 500 x10 500 x11 500 x12 498 x13 3 xyz} +do_execsql_test 5.2 { + explain query plan + SELECT * FROM t1 WHERE b='xyz' AND b IS NOT NULL ORDER BY +a; + /* v---- Should be "=", not ">" */ +} {/USING INDEX t1b .b=/} +do_execsql_test 5.3 { + SELECT * FROM t1 WHERE b='xyz' AND b IS NOT NULL ORDER BY +a; +} {9998 xyz 9998 9999 xyz 9999 10000 xyz 10000} finish_test diff --git a/test/attach.test b/test/attach.test index 31c24e61d9..54be6a41ac 100644 --- a/test/attach.test +++ b/test/attach.test @@ -193,7 +193,7 @@ do_test attach-1.20.2 { } ;# ifcapable schema_pragmas integrity_check attach-1.20.3 ifcapable tempdb { - execsql {select * from sqlite_temp_master} + execsql {select * from temp.sqlite_master} } do_test attach-1.21 { catchsql { diff --git a/test/attach3.test b/test/attach3.test index 1ac10d97a4..1c8601c7b3 100644 --- a/test/attach3.test +++ b/test/attach3.test @@ -207,7 +207,7 @@ ifcapable tempdb { CREATE TEMP TRIGGER tst_trigger BEFORE INSERT ON aux.t4 BEGIN SELECT 'hello world'; END; - SELECT count(*) FROM sqlite_temp_master; + SELECT count(*) FROM temp.sqlite_master; } } {1} do_test attach3-9.1 { @@ -219,7 +219,7 @@ ifcapable tempdb { do_test attach3-9.2 { execsql { DROP TABLE aux.t4; - SELECT count(*) FROM sqlite_temp_master; + SELECT count(*) FROM temp.sqlite_master; } } {0} } diff --git a/test/auth.test b/test/auth.test index d9e12edc3c..0044fddebe 100644 --- a/test/auth.test +++ b/test/auth.test @@ -91,7 +91,7 @@ ifcapable tempdb { catchsql {CREATE TEMP TABLE t1(a,b,c)} } {1 {not authorized}} do_test auth-1.6 { - execsql {SELECT name FROM sqlite_temp_master} + execsql {SELECT name FROM temp.sqlite_master} } {} do_test auth-1.7.1 { proc auth {code arg1 arg2 arg3 arg4 args} { @@ -148,7 +148,7 @@ ifcapable tempdb { catchsql {CREATE TEMP TABLE t1(a,b,c)} } {0 {}} do_test auth-1.14 { - execsql {SELECT name FROM sqlite_temp_master} + execsql {SELECT name FROM temp.sqlite_master} } {} do_test auth-1.15 { proc auth {code arg1 arg2 arg3 arg4 args} { @@ -561,7 +561,7 @@ ifcapable tempdb { catchsql {DROP TABLE t1} } {0 {}} do_test auth-1.78 { - execsql {SELECT name FROM sqlite_temp_master} + execsql {SELECT name FROM temp.sqlite_master} } {t1} } @@ -632,7 +632,7 @@ ifcapable tempdb { set ::authargs } {v1 {} temp {}} do_test auth-1.90 { - execsql {SELECT name FROM sqlite_temp_master} + execsql {SELECT name FROM temp.sqlite_master} } {t1} } @@ -779,7 +779,7 @@ ifcapable tempdb { } } {1 {not authorized}} do_test auth-1.113 { - execsql {SELECT name FROM sqlite_temp_master} + execsql {SELECT name FROM temp.sqlite_master} } {t1 v1} do_test auth-1.114 { proc auth {code arg1 arg2 arg3 arg4 args} { @@ -823,7 +823,7 @@ ifcapable tempdb { set ::authargs } {v1 {} temp {}} do_test auth-1.121 { - execsql {SELECT name FROM sqlite_temp_master} + execsql {SELECT name FROM temp.sqlite_master} } {t1 v1} do_test auth-1.122 { proc auth {code arg1 arg2 arg3 arg4 args} { @@ -980,7 +980,7 @@ do_test auth-1.139 { set ::authargs } {r1 t1 temp {}} do_test auth-1.140 { - execsql {SELECT name FROM sqlite_temp_master} + execsql {SELECT name FROM temp.sqlite_master} } {t1} do_test auth-1.141 { proc auth {code arg1 arg2 arg3 arg4 args} { @@ -1016,7 +1016,7 @@ do_test auth-1.144 { set ::authargs } {r1 t1 temp {}} do_test auth-1.145 { - execsql {SELECT name FROM sqlite_temp_master} + execsql {SELECT name FROM temp.sqlite_master} } {t1} do_test auth-1.146 { proc auth {code arg1 arg2 arg3 arg4 args} { @@ -1052,7 +1052,7 @@ do_test auth-1.149 { set ::authargs } {r1 t1 temp {}} do_test auth-1.150 { - execsql {SELECT name FROM sqlite_temp_master} + execsql {SELECT name FROM temp.sqlite_master} } {t1 r1} do_test auth-1.151 { @@ -1142,7 +1142,7 @@ do_test auth-1.164 { catchsql {DROP TRIGGER r1} } {1 {not authorized}} do_test auth-1.165 { - execsql {SELECT name FROM sqlite_temp_master} + execsql {SELECT name FROM temp.sqlite_master} } {t1 r1} do_test auth-1.166 { proc auth {code arg1 arg2 arg3 arg4 args} { @@ -1170,7 +1170,7 @@ do_test auth-1.169 { catchsql {DROP TRIGGER r1} } {0 {}} do_test auth-1.170 { - execsql {SELECT name FROM sqlite_temp_master} + execsql {SELECT name FROM temp.sqlite_master} } {t1 r1} do_test auth-1.171 { proc auth {code arg1 arg2 arg3 arg4 args} { @@ -1202,7 +1202,7 @@ do_test auth-1.175 { set ::authargs } {r1 t1 temp {}} do_test auth-1.176 { - execsql {SELECT name FROM sqlite_temp_master} + execsql {SELECT name FROM temp.sqlite_master} } {t1} } ;# ifcapable trigger @@ -1306,7 +1306,7 @@ ifcapable tempdb { catchsql {CREATE INDEX i1 ON t1(b)} } {1 {not authorized}} do_test auth-1.194 { - execsql {SELECT name FROM sqlite_temp_master} + execsql {SELECT name FROM temp.sqlite_master} } {t1} do_test auth-1.195 { proc auth {code arg1 arg2 arg3 arg4 args} { @@ -1350,7 +1350,7 @@ ifcapable tempdb { set ::authargs } {i1 t1 temp {}} do_test auth-1.202 { - execsql {SELECT name FROM sqlite_temp_master} + execsql {SELECT name FROM temp.sqlite_master} } {t1 i1} } @@ -1466,7 +1466,7 @@ ifcapable tempdb { catchsql {DROP INDEX i1} } {0 {}} do_test auth-1.222 { - execsql {SELECT name FROM sqlite_temp_master} + execsql {SELECT name FROM temp.sqlite_master} } {t1 i1} do_test auth-1.223 { proc auth {code arg1 arg2 arg3 arg4 args} { @@ -1482,7 +1482,7 @@ ifcapable tempdb { set ::authargs } {i1 t1 temp {}} do_test auth-1.225 { - execsql {SELECT name FROM sqlite_temp_master} + execsql {SELECT name FROM temp.sqlite_master} } {t1 i1} do_test auth-1.226 { proc auth {code arg1 arg2 arg3 arg4 args} { @@ -1498,7 +1498,7 @@ ifcapable tempdb { set ::authargs } {i1 t1 temp {}} do_test auth-1.228 { - execsql {SELECT name FROM sqlite_temp_master} + execsql {SELECT name FROM temp.sqlite_master} } {t1} } @@ -1765,7 +1765,7 @@ ifcapable attach { } } {0 {}} do_test auth-1.267 { - execsql {SELECT name FROM sqlite_temp_master WHERE type='table'} + execsql {SELECT name FROM temp.sqlite_master WHERE type='table'} } {t1x} do_test auth-1.268 { set authargs @@ -2070,7 +2070,7 @@ ifcapable {altertable} { } } {1 {not authorized}} do_test auth-1.307 { - set x [execsql {SELECT sql FROM sqlite_temp_master WHERE type='t5'}] + set x [execsql {SELECT sql FROM temp.sqlite_master WHERE type='t5'}] regexp new_col_3 $x } {0} @@ -2373,7 +2373,7 @@ ifcapable compound&&subquery { do_test auth-5.2 { execsql { SELECT name FROM ( - SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) + SELECT * FROM sqlite_master UNION ALL SELECT * FROM temp.sqlite_master) WHERE type='table' ORDER BY name } diff --git a/test/auth3.test b/test/auth3.test index ca7e16c84c..013486e5a0 100644 --- a/test/auth3.test +++ b/test/auth3.test @@ -122,7 +122,7 @@ do_execsql_test auth3-3.0 { key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE, value TEXT NOT NULL ON CONFLICT FAIL); ALTER TABLE TempTable RENAME TO DoNotRead; - SELECT name FROM sqlite_temp_master; + SELECT name FROM temp.sqlite_master; } {DoNotRead sqlite_autoindex_DoNotRead_1} finish_test diff --git a/test/autoinc.test b/test/autoinc.test index 239600616b..8ac12b1772 100644 --- a/test/autoinc.test +++ b/test/autoinc.test @@ -344,7 +344,7 @@ ifcapable tempdb { do_test autoinc-4.1 { execsql { SELECT 1, name FROM sqlite_master WHERE type='table'; - SELECT 2, name FROM sqlite_temp_master WHERE type='table'; + SELECT 2, name FROM temp.sqlite_master WHERE type='table'; } } {1 sqlite_sequence} do_test autoinc-4.2 { @@ -663,6 +663,18 @@ ifcapable trigger { } {1 124 2 10123} } +# 2016-10-03 ticket https://www.sqlite.org/src/tktview/7b3328086a5c1 +# Make sure autoincrement plays nicely with the xfer optimization +# +do_execsql_test autoinc-10.1 { + DELETE FROM sqlite_sequence; + CREATE TABLE t10a(a INTEGER PRIMARY KEY AUTOINCREMENT, b UNIQUE); + INSERT INTO t10a VALUES(888,9999); + CREATE TABLE t10b(x INTEGER PRIMARY KEY AUTOINCREMENT, y UNIQUE); + INSERT INTO t10b SELECT * FROM t10a; + SELECT * FROM sqlite_sequence; +} {t10a 888 t10b 888} + finish_test diff --git a/test/backup_malloc.test b/test/backup_malloc.test index f556861fb1..5444a70bdf 100644 --- a/test/backup_malloc.test +++ b/test/backup_malloc.test @@ -84,4 +84,36 @@ do_malloc_test backup_malloc-2 -tclprep { db2 close } +reset_db +do_execsql_test 3.0 { + PRAGMA page_size = 16384; + BEGIN; + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + COMMIT; +} + +do_faultsim_test 3 -faults oom* -prep { + catch { db close } + catch { db2 close } + + forcedelete test2.db + sqlite3 db2 test2.db + sqlite3 db test.db + sqlite3_backup B db2 main db main +} -body { + + set rc [B step 50] + if {$rc == "SQLITE_NOMEM" || $rc == "SQLITE_IOERR_NOMEM"} { + error "out of memory" + } + +} -test { + faultsim_test_result {0 {}} + faultsim_integrity_check + + # Finalize the backup. + catch { B finish } +} + finish_test diff --git a/test/bestindex3.test b/test/bestindex3.test index 8baf854254..70c60d66dd 100644 --- a/test/bestindex3.test +++ b/test/bestindex3.test @@ -172,4 +172,3 @@ do_execsql_test 3.1 { CREATE VIRTUAL TABLE t3 USING tcl('vvv_command') } do_execsql_test 3.2 { CREATE VIRTUAL TABLE t4 USING tcl('yyy_command') } finish_test - diff --git a/test/bestindex4.test b/test/bestindex4.test new file mode 100644 index 0000000000..64727bd0d7 --- /dev/null +++ b/test/bestindex4.test @@ -0,0 +1,120 @@ +# 2016 November 11 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# Test the virtual table interface. In particular the xBestIndex +# method. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix bestindex4 + +ifcapable !vtab { + finish_test + return +} + +#------------------------------------------------------------------------- +# Virtual table callback for a virtual table named $tbl. +# +# The table created is: +# +# "CREATE TABLE t1 (id, host, class)" +# +# The virtual table supports == operators on a subset of its columns. The +# exact subset depends on the value of bitmask paramater $param. +# +# 0x01 - == on "id" supported +# 0x02 - == on "host" supported +# 0x04 - == on "class" supported +# +# $param also supports the following bits: +# +# 0x08 - ignore the "usable" flag (malfunction) +# +# +# +proc vtab_cmd {param method args} { + switch -- $method { + xConnect { + return "CREATE TABLE t1(id TEXT, host TEXT, class TEXT)" + } + + xBestIndex { + foreach {clist orderby mask} $args {} + + set ret [list] + + set use use + + + for {set i 0} {$i < [llength $clist]} {incr i} { + array unset C + array set C [lindex $clist $i] + if { ($C(usable) || ($param & 0x08)) + && $C(op)=="eq" && ($param & 1<<$C(column)) + } { + lappend ret $use $i + break + } + } + + set score 1000000 + if {$ret!=""} { + set score [expr $score / [llength $ret]] + } + lappend ret cost $score rows $score + + return $ret + } + + xFilter { + } + } + return "" +} + +register_tcl_module db + +for {set param1 0} {$param1<16} {incr param1} { + for {set param2 0} {$param2<16} {incr param2} { + reset_db + register_tcl_module db + do_execsql_test 1.$param1.$param2.1 " + CREATE VIRTUAL TABLE t1 USING tcl('vtab_cmd $param1'); + CREATE VIRTUAL TABLE t2 USING tcl('vtab_cmd $param2'); + " + + foreach {tn sql} { + 2 "select t1.id as ID from t1, t2 where t1.id=t2.host and t2.class='xx'" + 3 { + select t1.id as ID from t1, t2 where t2.class ='xx' and t2.id = t1.host + } + 4 { + select t1.id as ID from t1, t2 where t1.host = t2.id and t2. class ='xx' + } + } { + + if {($param1 & 0x08)==0 && ($param2 & 0x08)==0} { + + do_execsql_test 1.$param1.$param2.$tn.a $sql {} + + } else { + do_test 1.$param1.$param2.$tn.b { + catchsql $sql + set {} {} + } {} + } + } + + } +} + +finish_test diff --git a/test/corruptC.test b/test/corruptC.test index 80c3c09f61..e2fb1f33c4 100644 --- a/test/corruptC.test +++ b/test/corruptC.test @@ -100,6 +100,12 @@ do_test corruptC-2.1 { } {1 {database disk image is malformed}} # test that a corrupt content offset size is handled (seed 5649) +# +# Update 2016-12-27: As of check-in [0b86fbca66] "In sqlite3BtreeInsert() when +# replacing a re-existing row, try to overwrite the cell directly rather than +# deallocate and reallocate the cell" on 2016-12-09, this test case no longer +# detects the offset size problem during the UPDATE. We have to run a subsequent +# integrity_check to see it. do_test corruptC-2.2 { db close forcecopy test.bu test.db @@ -117,8 +123,9 @@ do_test corruptC-2.2 { hexio_write test.db 3746 [format %02x 0x9a] sqlite3 db test.db - catchsql {UPDATE t1 SET y=1} -} {1 {database disk image is malformed}} + db eval {UPDATE t1 SET y=1} + db eval {PRAGMA integrity_check} +} {/Offset .* out of range/} # test that a corrupt free cell size is handled (seed 13329) do_test corruptC-2.3 { diff --git a/test/ctime.test b/test/ctime.test index efdf170851..8ed556358f 100644 --- a/test/ctime.test +++ b/test/ctime.test @@ -224,5 +224,17 @@ do_test ctime-2.5.$tc { } ] } {0 {{}}} +#-------------------------------------------------------------------------- +# Test that SQLITE_DIRECT_OVERFLOW_READ is reflected in the output of +# "PRAGMA compile_options". +# +ifcapable direct_read { + set res 1 +} else { + set res 0 +} +do_test ctime-3.0.1 { + expr [lsearch [db eval {PRAGMA compile_options}] DIRECT_OVERFLOW_READ]>=0 +} $res finish_test diff --git a/test/cursorhint2.test b/test/cursorhint2.test index 3444fdf441..bc447d0f22 100644 --- a/test/cursorhint2.test +++ b/test/cursorhint2.test @@ -164,17 +164,19 @@ do_extract_hints_test 2.10 { x2 {AND(EQ(c1,ADD(32,32)),EQ(c0,r[2]))} } -do_extract_hints_test 2.11 { - SELECT * FROM x1 LEFT JOIN x2 ON (a=x) WHERE x2.b LIKE 'abc%' -} { - x2 {AND(expr,EQ(c0,r[2]))} +ifcapable !icu { + # This test only works using the built-in LIKE, not the ICU LIKE extension. + do_extract_hints_test 2.11 { + SELECT * FROM x1 LEFT JOIN x2 ON (a=x) WHERE x2.b LIKE 'abc%' + } { + x2 {AND(expr,EQ(c0,r[2]))} + } } -do_extract_hints_test 2.11 { +do_extract_hints_test 2.12 { SELECT * FROM x1 LEFT JOIN x2 ON (a=x) WHERE coalesce(x2.b, 1) } { x2 {EQ(c0,r[2])} } finish_test - diff --git a/test/date.test b/test/date.test index 2f48b111e6..2d336e6c00 100644 --- a/test/date.test +++ b/test/date.test @@ -62,7 +62,7 @@ datetest 1.20 {julianday('2000-01-01 12:00:00.01')} 2451545.00000012 datetest 1.21 {julianday('2000-01-01 12:00:00.001')} 2451545.00000001 datetest 1.22 {julianday('2000-01-01 12:00:00.')} NULL datetest 1.23 julianday(12345.6) 12345.6 -datetest 1.23b julianday('12345.6') 12345.6 +datetest 1.23b julianday(1721059.5) 1721059.5 datetest 1.24 {julianday('2001-01-01 12:00:00 bogus')} NULL datetest 1.25 {julianday('2001-01-01 bogus')} NULL datetest 1.26 {julianday('2001-01-01 12:60:00')} NULL @@ -560,4 +560,41 @@ do_test date-15.2 { } } {1} +# Tests of extreme values in date/time functions. Run with UBSan or the +# equivalent to verify no signed interger overflow warnings. +# +datetest 16.1 {date(147483649)} NULL +datetest 16.2 {datetime(0)} {-4713-11-24 12:00:00} +datetest 16.3 {datetime(5373484.49999999)} {9999-12-31 23:59:59} +datetest 16.4 {julianday('-4713-11-24 12:00:00')} 0.0 +datetest 16.5 {julianday('9999-12-31 23:59:59.999')} 5373484.49999999 +datetest 16.6 {datetime(0,'+464269060799 seconds')} {9999-12-31 23:59:59} +datetest 16.7 {datetime(0,'+464269060800 seconds')} NULL +datetest 16.8 {datetime(0,'+7737817679 minutes')} {9999-12-31 23:59:00} +datetest 16.9 {datetime(0,'+7737817680 minutes')} NULL +datetest 16.10 {datetime(0,'+128963627 hours')} {9999-12-31 23:00:00} +datetest 16.11 {datetime(0,'+128963628 hours')} NULL +datetest 16.12 {datetime(0,'+5373484 days')} {9999-12-31 12:00:00} +datetest 16.13 {datetime(0,'+5373485 days')} NULL +datetest 16.14 {datetime(0,'+176545 months')} {9999-12-24 12:00:00} +datetest 16.15 {datetime(0,'+176546 months')} NULL +datetest 16.16 {datetime(0,'+14712 years')} {9999-11-24 12:00:00} +datetest 16.17 {datetime(0,'+14713 years')} NULL +datetest 16.20 {datetime(5373484.4999999,'-464269060799 seconds')} \ + {-4713-11-24 12:00:00} +datetest 16.21 {datetime(5373484,'-464269060800 seconds')} NULL +datetest 16.22 {datetime(5373484.4999999,'-7737817679 minutes')} \ + {-4713-11-24 12:00:59} +datetest 16.23 {datetime(5373484,'-7737817680 minutes')} NULL +datetest 16.24 {datetime(5373484.4999999,'-128963627 hours')} \ + {-4713-11-24 12:59:59} +datetest 16.25 {datetime(5373484,'-128963628 hours')} NULL +datetest 16.26 {datetime(5373484,'-5373484 days')} {-4713-11-24 12:00:00} +datetest 16.27 {datetime(5373484,'-5373485 days')} NULL +datetest 16.28 {datetime(5373484,'-176545 months')} {-4713-12-01 12:00:00} +datetest 16.29 {datetime(5373484,'-176546 months')} NULL +datetest 16.30 {datetime(5373484,'-14712 years')} {-4713-12-31 12:00:00} +datetest 16.31 {datetime(5373484,'-14713 years')} NULL + + finish_test diff --git a/test/dbfuzz.c b/test/dbfuzz.c new file mode 100644 index 0000000000..2de97c35cb --- /dev/null +++ b/test/dbfuzz.c @@ -0,0 +1,754 @@ +/* +** 2016-12-17 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This program is designed for fuzz-testing SQLite database files. +** +** This program reads fuzzed database files from the disk files named +** on the command-line. Each database is loaded into an in-memory +** filesystem so that the original database file is unmolested. +** +** The fuzzed database is then opened, and series of SQL statements +** are run against the database to ensure that SQLite can safely handle +** the fuzzed database. +*/ +#include +#include +#include +#include +#include +#include +#define ISSPACE(X) isspace((unsigned char)(X)) +#define ISDIGIT(X) isdigit((unsigned char)(X)) +#include "sqlite3.h" +#ifdef __unix__ +# include +# include +#endif + +/* +** Print sketchy documentation for this utility program +*/ +static void showHelp(const char *zArgv0){ + printf("Usage: %s [options] DATABASE ...\n", zArgv0); + printf( +"Read databases into an in-memory filesystem. Run test SQL as specified\n" +"by command-line arguments or from\n" +"\n" +" SELECT group_concat(sql) FROM autoexec;\n" +"\n" +"Options:\n" +" --help Show this help text\n" +" -q|--quiet Reduced output\n" +" --limit-mem N Limit memory used by test SQLite instances to N bytes\n" +" --limit-vdbe Panic if any test runs for more than 100,000 cycles\n" +" --no-lookaside Disable the lookaside memory allocator\n" +" --timeout N Timeout after N seconds.\n" +" --trace Show the results of each SQL command\n" +" -v|--verbose Increased output. Repeat for more output.\n" + ); + exit(0); +} + +/* +** Print an error message and quit. +*/ +static void fatalError(const char *zFormat, ...){ + va_list ap; + va_start(ap, zFormat); + vfprintf(stderr, zFormat, ap); + va_end(ap); + fprintf(stderr, "\n"); + exit(1); +} + +/* +** Files in the virtual file system. +*/ +typedef struct VFile VFile; +typedef struct VHandle VHandle; +struct VFile { + char *zFilename; /* Filename. NULL for delete-on-close. From malloc() */ + int sz; /* Size of the file in bytes */ + int nRef; /* Number of references to this file */ + unsigned char *a; /* Content of the file. From malloc() */ +}; +struct VHandle { + sqlite3_file base; /* Base class. Must be first */ + VFile *pVFile; /* The underlying file */ +}; + +/* +** Maximum number of files in the in-memory virtual filesystem. +*/ +#define MX_FILE 10 + +/* +** Maximum allowed file size +*/ +#define MX_FILE_SZ 1000000 + +/* +** All global variables are gathered into the "g" singleton. +*/ +static struct GlobalVars { + VFile aFile[MX_FILE]; /* The virtual filesystem */ +} g; + + +/* +** Initialize the virtual file system. +*/ +static void formatVfs(void){ + int i; + for(i=0; i0 ){ + fatalError("file %d still open. nRef=%d", i, g.aFile[i].nRef); + } + g.aFile[i].sz = -1; + free(g.aFile[i].a); + g.aFile[i].a = 0; + g.aFile[i].nRef = 0; + } +} + +/* +** Find a VFile by name +*/ +static VFile *findVFile(const char *zName){ + int i; + if( zName==0 ) return 0; + for(i=0; i=0; i++){} + if( i>=MX_FILE ) return 0; + if( zDiskFile ){ + in = fopen(zDiskFile, "rb"); + if( in==0 ) fatalError("no such file: \"%s\"", zDiskFile); + fseek(in, 0, SEEK_END); + sz = ftell(in); + rewind(in); + } + pNew = &g.aFile[i]; + if( zName ){ + int nName = (int)strlen(zName)+1; + pNew->zFilename = malloc(nName); + if( pNew->zFilename==0 ){ + if( in ) fclose(in); + return 0; + } + memcpy(pNew->zFilename, zName, nName); + }else{ + pNew->zFilename = 0; + } + pNew->nRef = 0; + pNew->sz = sz; + pNew->a = malloc(sz); + if( sz>0 ){ + if( pNew->a==0 || fread(pNew->a, sz, 1, in)<1 ){ + free(pNew->zFilename); + free(pNew->a); + pNew->a = 0; + pNew->zFilename = 0; + pNew->sz = -1; + pNew = 0; + } + } + if( in ) fclose(in); + return pNew; +} + +/* Methods for the VHandle object +*/ +static int inmemClose(sqlite3_file *pFile){ + VHandle *p = (VHandle*)pFile; + VFile *pVFile = p->pVFile; + pVFile->nRef--; + if( pVFile->nRef==0 && pVFile->zFilename==0 ){ + pVFile->sz = -1; + free(pVFile->a); + pVFile->a = 0; + } + return SQLITE_OK; +} +static int inmemRead( + sqlite3_file *pFile, /* Read from this open file */ + void *pData, /* Store content in this buffer */ + int iAmt, /* Bytes of content */ + sqlite3_int64 iOfst /* Start reading here */ +){ + VHandle *pHandle = (VHandle*)pFile; + VFile *pVFile = pHandle->pVFile; + if( iOfst<0 || iOfst>=pVFile->sz ){ + memset(pData, 0, iAmt); + return SQLITE_IOERR_SHORT_READ; + } + if( iOfst+iAmt>pVFile->sz ){ + memset(pData, 0, iAmt); + iAmt = (int)(pVFile->sz - iOfst); + memcpy(pData, pVFile->a, iAmt); + return SQLITE_IOERR_SHORT_READ; + } + memcpy(pData, pVFile->a + iOfst, iAmt); + return SQLITE_OK; +} +static int inmemWrite( + sqlite3_file *pFile, /* Write to this file */ + const void *pData, /* Content to write */ + int iAmt, /* bytes to write */ + sqlite3_int64 iOfst /* Start writing here */ +){ + VHandle *pHandle = (VHandle*)pFile; + VFile *pVFile = pHandle->pVFile; + if( iOfst+iAmt > pVFile->sz ){ + unsigned char *aNew; + if( iOfst+iAmt >= MX_FILE_SZ ){ + return SQLITE_FULL; + } + aNew = realloc(pVFile->a, (int)(iOfst+iAmt)); + if( aNew==0 ){ + return SQLITE_FULL; + } + pVFile->a = aNew; + if( iOfst > pVFile->sz ){ + memset(pVFile->a + pVFile->sz, 0, (int)(iOfst - pVFile->sz)); + } + pVFile->sz = (int)(iOfst + iAmt); + } + memcpy(pVFile->a + iOfst, pData, iAmt); + return SQLITE_OK; +} +static int inmemTruncate(sqlite3_file *pFile, sqlite3_int64 iSize){ + VHandle *pHandle = (VHandle*)pFile; + VFile *pVFile = pHandle->pVFile; + if( pVFile->sz>iSize && iSize>=0 ) pVFile->sz = (int)iSize; + return SQLITE_OK; +} +static int inmemSync(sqlite3_file *pFile, int flags){ + return SQLITE_OK; +} +static int inmemFileSize(sqlite3_file *pFile, sqlite3_int64 *pSize){ + *pSize = ((VHandle*)pFile)->pVFile->sz; + return SQLITE_OK; +} +static int inmemLock(sqlite3_file *pFile, int type){ + return SQLITE_OK; +} +static int inmemUnlock(sqlite3_file *pFile, int type){ + return SQLITE_OK; +} +static int inmemCheckReservedLock(sqlite3_file *pFile, int *pOut){ + *pOut = 0; + return SQLITE_OK; +} +static int inmemFileControl(sqlite3_file *pFile, int op, void *pArg){ + return SQLITE_NOTFOUND; +} +static int inmemSectorSize(sqlite3_file *pFile){ + return 512; +} +static int inmemDeviceCharacteristics(sqlite3_file *pFile){ + return + SQLITE_IOCAP_SAFE_APPEND | + SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN | + SQLITE_IOCAP_POWERSAFE_OVERWRITE; +} + + +/* Method table for VHandle +*/ +static sqlite3_io_methods VHandleMethods = { + /* iVersion */ 1, + /* xClose */ inmemClose, + /* xRead */ inmemRead, + /* xWrite */ inmemWrite, + /* xTruncate */ inmemTruncate, + /* xSync */ inmemSync, + /* xFileSize */ inmemFileSize, + /* xLock */ inmemLock, + /* xUnlock */ inmemUnlock, + /* xCheck... */ inmemCheckReservedLock, + /* xFileCtrl */ inmemFileControl, + /* xSectorSz */ inmemSectorSize, + /* xDevchar */ inmemDeviceCharacteristics, + /* xShmMap */ 0, + /* xShmLock */ 0, + /* xShmBarrier */ 0, + /* xShmUnmap */ 0, + /* xFetch */ 0, + /* xUnfetch */ 0 +}; + +/* +** Open a new file in the inmem VFS. All files are anonymous and are +** delete-on-close. +*/ +static int inmemOpen( + sqlite3_vfs *pVfs, + const char *zFilename, + sqlite3_file *pFile, + int openFlags, + int *pOutFlags +){ + VFile *pVFile = createVFile(zFilename, 0); + VHandle *pHandle = (VHandle*)pFile; + if( pVFile==0 ){ + return SQLITE_FULL; + } + pHandle->pVFile = pVFile; + pVFile->nRef++; + pFile->pMethods = &VHandleMethods; + if( pOutFlags ) *pOutFlags = openFlags; + return SQLITE_OK; +} + +/* +** Delete a file by name +*/ +static int inmemDelete( + sqlite3_vfs *pVfs, + const char *zFilename, + int syncdir +){ + VFile *pVFile = findVFile(zFilename); + if( pVFile==0 ) return SQLITE_OK; + if( pVFile->nRef==0 ){ + free(pVFile->zFilename); + pVFile->zFilename = 0; + pVFile->sz = -1; + free(pVFile->a); + pVFile->a = 0; + return SQLITE_OK; + } + return SQLITE_IOERR_DELETE; +} + +/* Check for the existance of a file +*/ +static int inmemAccess( + sqlite3_vfs *pVfs, + const char *zFilename, + int flags, + int *pResOut +){ + VFile *pVFile = findVFile(zFilename); + *pResOut = pVFile!=0; + return SQLITE_OK; +} + +/* Get the canonical pathname for a file +*/ +static int inmemFullPathname( + sqlite3_vfs *pVfs, + const char *zFilename, + int nOut, + char *zOut +){ + sqlite3_snprintf(nOut, zOut, "%s", zFilename); + return SQLITE_OK; +} + +/* +** Register the VFS that reads from the g.aFile[] set of files. +*/ +static void inmemVfsRegister(void){ + static sqlite3_vfs inmemVfs; + sqlite3_vfs *pDefault = sqlite3_vfs_find(0); + inmemVfs.iVersion = 3; + inmemVfs.szOsFile = sizeof(VHandle); + inmemVfs.mxPathname = 200; + inmemVfs.zName = "inmem"; + inmemVfs.xOpen = inmemOpen; + inmemVfs.xDelete = inmemDelete; + inmemVfs.xAccess = inmemAccess; + inmemVfs.xFullPathname = inmemFullPathname; + inmemVfs.xRandomness = pDefault->xRandomness; + inmemVfs.xSleep = pDefault->xSleep; + inmemVfs.xCurrentTimeInt64 = pDefault->xCurrentTimeInt64; + sqlite3_vfs_register(&inmemVfs, 0); +}; + +/* +** Timeout handler +*/ +#ifdef __unix__ +static void timeoutHandler(int NotUsed){ + (void)NotUsed; + fatalError("timeout\n"); +} +#endif + +/* +** Set the an alarm to go off after N seconds. Disable the alarm +** if N==0 +*/ +static void setAlarm(int N){ +#ifdef __unix__ + alarm(N); +#else + (void)N; +#endif +} +/*************************************************************************** +** String accumulator object +*/ +typedef struct Str Str; +struct Str { + char *z; /* The string. Memory from malloc() */ + sqlite3_uint64 n; /* Bytes of input used */ + sqlite3_uint64 nAlloc; /* Bytes allocated to z[] */ + int oomErr; /* OOM error has been seen */ +}; + +/* Initialize a Str object */ +static void StrInit(Str *p){ + memset(p, 0, sizeof(*p)); +} + +/* Append text to the end of a Str object */ +static void StrAppend(Str *p, const char *z){ + sqlite3_uint64 n = strlen(z); + if( p->n + n >= p->nAlloc ){ + char *zNew; + sqlite3_uint64 nNew; + if( p->oomErr ) return; + nNew = p->nAlloc*2 + 100 + n; + zNew = sqlite3_realloc(p->z, nNew); + if( zNew==0 ){ + sqlite3_free(p->z); + memset(p, 0, sizeof(*p)); + p->oomErr = 1; + return; + } + p->z = zNew; + p->nAlloc = nNew; + } + memcpy(p->z + p->n, z, n); + p->n += n; + p->z[p->n] = 0; +} + +/* Return the current string content */ +static char *StrStr(Str *p){ + return p->z; +} + +/* Free the string */ +static void StrFree(Str *p){ + sqlite3_free(p->z); + StrInit(p); +} + +/* +** Return the value of a hexadecimal digit. Return -1 if the input +** is not a hex digit. +*/ +static int hexDigitValue(char c){ + if( c>='0' && c<='9' ) return c - '0'; + if( c>='a' && c<='f' ) return c - 'a' + 10; + if( c>='A' && c<='F' ) return c - 'A' + 10; + return -1; +} + +/* +** Interpret zArg as an integer value, possibly with suffixes. +*/ +static int integerValue(const char *zArg){ + sqlite3_int64 v = 0; + static const struct { char *zSuffix; int iMult; } aMult[] = { + { "KiB", 1024 }, + { "MiB", 1024*1024 }, + { "GiB", 1024*1024*1024 }, + { "KB", 1000 }, + { "MB", 1000000 }, + { "GB", 1000000000 }, + { "K", 1000 }, + { "M", 1000000 }, + { "G", 1000000000 }, + }; + int i; + int isNeg = 0; + if( zArg[0]=='-' ){ + isNeg = 1; + zArg++; + }else if( zArg[0]=='+' ){ + zArg++; + } + if( zArg[0]=='0' && zArg[1]=='x' ){ + int x; + zArg += 2; + while( (x = hexDigitValue(zArg[0]))>=0 ){ + v = (v<<4) + x; + zArg++; + } + }else{ + while( ISDIGIT(zArg[0]) ){ + v = v*10 + zArg[0] - '0'; + zArg++; + } + } + for(i=0; i0x7fffffff ) fatalError("parameter too large - max 2147483648"); + return (int)(isNeg? -v : v); +} + +/* +** This callback is invoked by sqlite3_log(). +*/ +static void sqlLog(void *pNotUsed, int iErrCode, const char *zMsg){ + printf("LOG: (%d) %s\n", iErrCode, zMsg); + fflush(stdout); +} + +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK +/* +** This an SQL progress handler. After an SQL statement has run for +** many steps, we want to interrupt it. This guards against infinite +** loops from recursive common table expressions. +** +** *pVdbeLimitFlag is true if the --limit-vdbe command-line option is used. +** In that case, hitting the progress handler is a fatal error. +*/ +static int progressHandler(void *pVdbeLimitFlag){ + if( *(int*)pVdbeLimitFlag ) fatalError("too many VDBE cycles"); + return 1; +} +#endif + +/* +** Allowed values for the runFlags parameter to runSql() +*/ +#define SQL_TRACE 0x0001 /* Print each SQL statement as it is prepared */ +#define SQL_OUTPUT 0x0002 /* Show the SQL output */ + +/* +** Run multiple commands of SQL. Similar to sqlite3_exec(), but does not +** stop if an error is encountered. +*/ +static void runSql(sqlite3 *db, const char *zSql, unsigned runFlags){ + const char *zMore; + const char *zEnd = &zSql[strlen(zSql)]; + sqlite3_stmt *pStmt; + + while( zSql && zSql[0] ){ + zMore = 0; + pStmt = 0; + sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zMore); + assert( zMore<=zEnd ); + if( zMore==zSql ) break; + if( runFlags & SQL_TRACE ){ + const char *z = zSql; + int n; + while( z0 && ISSPACE(z[n-1]) ) n--; + if( n==0 ) break; + if( pStmt==0 ){ + printf("TRACE: %.*s (error: %s)\n", n, z, sqlite3_errmsg(db)); + }else{ + printf("TRACE: %.*s\n", n, z); + } + } + zSql = zMore; + if( pStmt ){ + if( (runFlags & SQL_OUTPUT)==0 ){ + while( SQLITE_ROW==sqlite3_step(pStmt) ){} + }else{ + int nCol = -1; + int nRow; + for(nRow=0; SQLITE_ROW==sqlite3_step(pStmt); nRow++){ + int i; + if( nCol<0 ){ + nCol = sqlite3_column_count(pStmt); + } + for(i=0; i0 ){ + void *pHeap = malloc( nHeap ); + if( pHeap==0 ) fatalError("cannot allocate %d-byte heap\n", nHeap); + rc = sqlite3_config(SQLITE_CONFIG_HEAP, pHeap, nHeap, 32); + if( rc ) fatalError("heap configuration failed: %d\n", rc); + } + if( noLookaside ){ + sqlite3_config(SQLITE_CONFIG_LOOKASIDE, 0, 0); + } + inmemVfsRegister(); + formatVfs(); + StrInit(&sql); +#ifdef __unix__ + signal(SIGALRM, timeoutHandler); +#endif + for(i=0; i1 ){ + printf("DATABASE-FILE: %s\n", azDb[i]); + fflush(stdout); + } + if( iTimeout ) setAlarm(iTimeout); + createVFile("test.db", azDb[i]); + rc = sqlite3_open_v2("test.db", &db, SQLITE_OPEN_READWRITE, "inmem"); + if( rc ){ + printf("cannot open test.db for \"%s\"\n", azDb[i]); + reformatVfs(); + continue; + } +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + if( vdbeLimitFlag ){ + sqlite3_progress_handler(db, 100000, progressHandler, &vdbeLimitFlag); + } +#endif + rc = sqlite3_prepare_v2(db, "SELECT sql FROM autoexec", -1, &pStmt, 0); + if( rc==SQLITE_OK ){ + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + StrAppend(&sql, (const char*)sqlite3_column_text(pStmt, 0)); + StrAppend(&sql, "\n"); + } + } + sqlite3_finalize(pStmt); + StrAppend(&sql, "PRAGMA integrity_check;\n"); + runSql(db, StrStr(&sql), runFlags); + sqlite3_close(db); + reformatVfs(); + StrFree(&sql); + if( sqlite3_memory_used()>0 ){ + free(azDb); + reformatVfs(); + fatalError("memory leak of %lld bytes", sqlite3_memory_used()); + } + } + StrFree(&sql); + reformatVfs(); + return 0; +} diff --git a/test/delete.test b/test/delete.test index d2dc106495..8e84ed4665 100644 --- a/test/delete.test +++ b/test/delete.test @@ -389,5 +389,19 @@ do_test delete-9.5 { set res } {1 a b 1 c d 2 a b 2 c d} +do_execsql_test delete-10.0 { + CREATE TABLE t1(a INT UNIQUE, b INT); + INSERT INTO t1(a,b) VALUES('1','2'); + SELECT * FROM t1 WHERE a='1' AND b='2'; +} {1 2} + +do_execsql_test delete-10.1 { + DELETE FROM t1 WHERE a='1' AND b='2'; +} + +do_execsql_test delete-10.2 { + SELECT * FROM t1 WHERE a='1' AND b='2'; +} + finish_test diff --git a/test/delete_db.test b/test/delete_db.test new file mode 100644 index 0000000000..09c44ff9f3 --- /dev/null +++ b/test/delete_db.test @@ -0,0 +1,217 @@ +# 2016 September 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. +# 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 code in test_delete.c (the +# sqlite3_delete_database() API). +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix delete_db + +proc delete_all {} { + foreach f [glob -nocomplain test2*] { file delete $f } + foreach f [glob -nocomplain test3*] { file delete $f } +} + +proc copydb {} { + foreach f [glob -nocomplain test3*] { file delete $f } + foreach f [glob -nocomplain test2*] { + set p [string range $f 5 end] + file copy "test2$p" "test3$p" + } +} + +proc files {} { + lsort [glob -nocomplain test3*] +} + +db close +delete_all +sqlite3 db test2.database + +#------------------------------------------------------------------------- +# +# 1.1: Journal files. +# 1.2: Wal files. +# 1.3: Multiplexor with journal file. +# 1.4: Multiplexor with wal file. +# +# 2.* are a copy of 1.* with the multiplexor enabled. +# +# 3.* tests errors. +# + +do_test 1.1.0 { + execsql { + CREATE TABLE t1(x, y); + BEGIN; + INSERT INTO t1 VALUES(1, 2); + } + copydb + files +} {test3.database test3.database-journal} + +do_test 1.1.1 { + sqlite3_delete_database test3.database + files +} {} + +do_test 1.2.0 { + execsql { + COMMIT; + PRAGMA journal_mode = wal; + INSERT INTO t1 VALUES(3, 4); + } + copydb + files +} {test3.database test3.database-shm test3.database-wal} +do_test 1.2.1 { + sqlite3_delete_database test3.database + files +} {} + +db close +delete_all +sqlite3_multiplex_initialize "" 0 +sqlite3 db test2.database -vfs multiplex +sqlite3_multiplex_control db "main" chunk_size 32768 + +do_test 1.3.0 { + execsql { PRAGMA auto_vacuum = 0; } + execsql { + CREATE TABLE x1(a, b); + WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<1000 ) + INSERT INTO x1 SELECT randomblob(100), randomblob(100) FROM s; + BEGIN; + UPDATE x1 SET a=randomblob(101) + } + copydb + files +} [list {*}{ + test3.database test3.database-journal test3.database001 + test3.database002 test3.database003 +}] +do_test 1.3.1 { + sqlite3_delete_database test3.database + files +} {} + + +do_test 1.4.0 { + execsql { + COMMIT; + PRAGMA journal_mode = wal; + UPDATE x1 SET a=randomblob(102) + } + copydb + files +} [list {*}{ + test3.database test3.database-shm test3.database-wal test3.database001 + test3.database002 test3.database003 +}] +do_test 1.4.1 { + sqlite3_delete_database test3.database + files +} {} + + +ifcapable 8_3_names { + db close + delete_all + sqlite3 db file:test2.db?8_3_names=1 -uri 1 + + do_test 2.1.0 { + execsql { + CREATE TABLE t1(x, y); + BEGIN; + INSERT INTO t1 VALUES(1, 2); + } + copydb + files + } {test3.db test3.nal} + + do_test 2.1.1 { + sqlite3_delete_database test3.db + files + } {} + + do_test 2.2.0 { + execsql { + COMMIT; + PRAGMA journal_mode = wal; + INSERT INTO t1 VALUES(3, 4); + } + copydb + files + } {test3.db test3.shm test3.wal} + do_test 2.2.1 { + sqlite3_delete_database test3.db + files + } {} + + + db close + delete_all + sqlite3_multiplex_initialize "" 0 + sqlite3 db file:test2.db?8_3_names=1 -uri 1 -vfs multiplex + sqlite3_multiplex_control db "main" chunk_size 32768 + + do_test 2.3.0 { + execsql { PRAGMA auto_vacuum = 0; } + execsql { + CREATE TABLE x1(a, b); + WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<1000 ) + INSERT INTO x1 SELECT randomblob(100), randomblob(100) FROM s; + BEGIN; + UPDATE x1 SET a=randomblob(101) + } + copydb + files + } [list {*}{ + test3.001 test3.002 test3.003 test3.db test3.nal + }] + do_test 2.3.1 { + sqlite3_delete_database test3.db + files + } {} + + + do_test 2.4.0 { + execsql { + COMMIT; + PRAGMA journal_mode = wal; + UPDATE x1 SET a=randomblob(102) + } + copydb + files + } [list {*}{ + test3.001 test3.002 test3.003 test3.db test3.db-shm test3.wal + }] + do_test 2.4.1 { + sqlite3_delete_database test3.db + files + } {} +} + +db close +delete_all +sqlite3_multiplex_shutdown + +do_test 3.0 { + file mkdir dir2.db + sqlite3_delete_database dir2.db +} {SQLITE_ERROR} +do_test 3.1 { + sqlite3_delete_database dir2.db/test.db +} {SQLITE_OK} + +finish_test diff --git a/test/e_dropview.test b/test/e_dropview.test index 143dce2907..04c4ad8c4b 100644 --- a/test/e_dropview.test +++ b/test/e_dropview.test @@ -45,7 +45,7 @@ proc list_all_views {{db db}} { set res [list] $db eval { PRAGMA database_list } { set tbl "$name.sqlite_master" - if {$name == "temp"} { set tbl sqlite_temp_master } + if {$name == "temp"} { set tbl temp.sqlite_master } set sql "SELECT '$name.' || name FROM $tbl WHERE type = 'view'" lappend res {*}[$db eval $sql] diff --git a/test/e_expr.test b/test/e_expr.test index 8c0957f8d3..de7b43c92e 100644 --- a/test/e_expr.test +++ b/test/e_expr.test @@ -847,6 +847,9 @@ foreach {tn x expr res nEval} { 3 5 "x() >= 5 AND x() <= 5" 1 2 4 5 "x() BETWEEN 5 AND 5" 1 1 + + 5 9 "(x(),8) >= (9,7) AND (x(),8)<=(9,10)" 1 2 + 6 9 "(x(),8) BETWEEN (9,7) AND (9,10)" 1 1 } { do_test e_expr-13.1.$tn { set ::xcount 0 @@ -1434,6 +1437,13 @@ do_expr_test e_expr-27.2.2 { CAST(NULL AS text) } null {} do_expr_test e_expr-27.2.3 { CAST(NULL AS blob) } null {} do_expr_test e_expr-27.2.4 { CAST(NULL AS number) } null {} +# EVIDENCE-OF: R-29283-15561 Otherwise, the storage class of the result +# is determined by applying the rules for determining column affinity to +# the type-name. +# +# The R-29283-15561 requirement above is demonstrated by all of the +# subsequent e_expr-26 tests. +# # EVIDENCE-OF: R-43522-35548 Casting a value to a type-name with no # affinity causes the value to be converted into a BLOB. # @@ -1783,8 +1793,8 @@ do_test e_expr-35.0 { } } {} -# EVIDENCE-OF: R-00980-39256 A SELECT statement enclosed in parentheses -# may appear as a scalar quantity. +# EVIDENCE-OF: R-43573-23448 A SELECT statement enclosed in parentheses +# is a subquery. # # EVIDENCE-OF: R-56294-03966 All types of SELECT statement, including # aggregate and compound SELECT queries (queries with keywords like @@ -1803,13 +1813,14 @@ do_expr_test e_expr-35.1.6 { (SELECT a FROM t2 UNION SELECT COALESCE(b, 55) FROM t2 ORDER BY 1) } integer 4 -# EVIDENCE-OF: R-46899-53765 A SELECT used as a scalar quantity must -# return a result set with a single column. +# EVIDENCE-OF: R-22239-33740 A subquery that returns two or more columns +# is a row value subquery and can only be used as the operand of a +# comparison operator. # # The following block tests that errors are returned in a bunch of cases # where a subquery returns more than one column. # -set M {only a single result allowed for a SELECT that is part of an expression} +set M {/1 {sub-select returns [23] columns - expected 1}/} foreach {tn sql} { 1 { SELECT (SELECT * FROM t2 UNION SELECT a+1, b+1 FROM t2) } 2 { SELECT (SELECT * FROM t2 UNION SELECT a+1, b+1 FROM t2 ORDER BY 1) } @@ -1818,14 +1829,14 @@ foreach {tn sql} { 5 { SELECT (SELECT * FROM t2) } 6 { SELECT (SELECT * FROM (SELECT 1, 2, 3)) } } { - do_catchsql_test e_expr-35.2.$tn $sql [list 1 $M] + do_catchsql_test e_expr-35.2.$tn $sql $M } -# EVIDENCE-OF: R-35764-28041 The result of the expression is the value -# of the only column in the first row returned by the SELECT statement. +# EVIDENCE-OF: R-18318-14995 The value of a subquery expression is the +# first row of the result from the enclosed SELECT statement. # -# EVIDENCE-OF: R-41898-06686 If the SELECT yields more than one result -# row, all rows after the first are ignored. +# EVIDENCE-OF: R-15900-52156 In other words, an implied "LIMIT 1" is +# added to the subquery, overriding an explicitly coded LIMIT. # do_execsql_test e_expr-36.3.1 { CREATE TABLE t4(x, y); @@ -1849,8 +1860,8 @@ foreach {tn expr restype resval} { do_expr_test e_expr-36.3.$tn $expr $restype $resval } -# EVIDENCE-OF: R-25492-41572 If the SELECT yields no rows, then the -# value of the expression is NULL. +# EVIDENCE-OF: R-52325-25449 The value of a subquery expression is NULL +# if the enclosed SELECT statement returns no rows. # foreach {tn expr} { 1 { ( SELECT x FROM t4 WHERE x>3 ORDER BY x ) } diff --git a/test/e_fkey.test b/test/e_fkey.test index 09756505c3..99f1ae8e62 100644 --- a/test/e_fkey.test +++ b/test/e_fkey.test @@ -52,11 +52,11 @@ ifcapable trigger&&foreignkey { #------------------------------------------------------------------------- # Test the effects of defining OMIT_TRIGGER but not OMIT_FOREIGN_KEY. # -# EVIDENCE-OF: R-44697-61543 If SQLITE_OMIT_TRIGGER is defined but +# EVIDENCE-OF: R-10109-20452 If SQLITE_OMIT_TRIGGER is defined but # SQLITE_OMIT_FOREIGN_KEY is not, then SQLite behaves as it did prior to -# version 3.6.19 - foreign key definitions are parsed and may be queried -# using PRAGMA foreign_key_list, but foreign key constraints are not -# enforced. +# version 3.6.19 (2009-10-14) - foreign key definitions are parsed and +# may be queried using PRAGMA foreign_key_list, but foreign key +# constraints are not enforced. # # Specifically, test that "PRAGMA foreign_keys" is a no-op in this case. # When using the pragma to query the current setting, 0 rows are returned. diff --git a/test/e_select.test b/test/e_select.test index 89d61b53bc..9aa2de6038 100644 --- a/test/e_select.test +++ b/test/e_select.test @@ -1252,8 +1252,8 @@ do_select_tests e_select-5.5 { 1 "SELECT DISTINCT d FROM h3" {{} 2 2,3 2,4 3} } -# EVIDENCE-OF: R-58359-52112 The normal rules for selecting a collation -# sequence to compare text values with apply. +# EVIDENCE-OF: R-47709-27231 The usual rules apply for selecting a +# collation sequence to compare text values. # do_select_tests e_select-5.6 { 1 "SELECT DISTINCT b FROM h1" {one I i four IV iv} diff --git a/test/e_vacuum.test b/test/e_vacuum.test index c3e517a892..3fd34afd43 100644 --- a/test/e_vacuum.test +++ b/test/e_vacuum.test @@ -190,8 +190,8 @@ if {![nonzero_reserved_bytes]} { } } -# EVIDENCE-OF: R-38001-03952 VACUUM only works on the main database. It -# is not possible to VACUUM an attached database file. +# EVIDENCE-OF: R-55119-57913 By default, VACUUM only works only on the +# main database. forcedelete test.db2 create_db { PRAGMA auto_vacuum = NONE } do_execsql_test e_vacuum-2.1.1 { @@ -202,14 +202,15 @@ do_execsql_test e_vacuum-2.1.1 { } {} set original_size [file size test.db2] -# Try everything we can think of to get the aux database vacuumed: +# Vacuuming the main database does not affect aux do_execsql_test e_vacuum-2.1.3 { VACUUM } {} -do_execsql_test e_vacuum-2.1.4 { VACUUM aux } {} -do_execsql_test e_vacuum-2.1.5 { VACUUM 'test.db2' } {} - -# Despite our efforts, space in the aux database has not been reclaimed: do_test e_vacuum-2.1.6 { expr {[file size test.db2]==$::original_size} } 1 +# EVIDENCE-OF: R-36598-60500 Attached databases can be vacuumed by +# appending the appropriate schema-name to the VACUUM statement. +do_execsql_test e_vacuum-2.1.7 { VACUUM aux; } {} +do_test e_vacuum-2.1.8 { expr {[file size test.db2]<$::original_size} } 1 + # EVIDENCE-OF: R-17495-17419 The VACUUM command may change the ROWIDs of # entries in any tables that do not have an explicit INTEGER PRIMARY # KEY. @@ -271,9 +272,9 @@ do_test e_vacuum-3.2.2.1 { } {1 {cannot VACUUM - SQL statements in progress}} -# EVIDENCE-OF: R-38735-12540 As of SQLite version 3.1, an alternative to -# using the VACUUM command to reclaim space after data has been deleted -# is auto-vacuum mode, enabled using the auto_vacuum pragma. +# EVIDENCE-OF: R-55138-13241 An alternative to using the VACUUM command +# to reclaim space after data has been deleted is auto-vacuum mode, +# enabled using the auto_vacuum pragma. # do_test e_vacuum-3.3.1 { create_db { PRAGMA auto_vacuum = FULL } diff --git a/test/expr.test b/test/expr.test index 7d7b8ce5a7..7a6d477259 100644 --- a/test/expr.test +++ b/test/expr.test @@ -308,6 +308,33 @@ ifcapable floatingpoint {if {[working_64bit_int]} { test_realnum_expr expr-1.257\ {i1=-4294967296, i2=-2147483647} {i1*i2} 9223372032559808512 + test_realnum_expr expr-1.260\ + {i1=3037000500, i2=3037000500} {i1*i2} 9.22337203700025e+18 + test_realnum_expr expr-1.261\ + {i1=3037000500, i2=-3037000500} {i1*i2} -9.22337203700025e+18 + test_realnum_expr expr-1.262\ + {i1=-3037000500, i2=3037000500} {i1*i2} -9.22337203700025e+18 + test_realnum_expr expr-1.263\ + {i1=-3037000500, i2=-3037000500} {i1*i2} 9.22337203700025e+18 + + test_realnum_expr expr-1.264\ + {i1=3037000500, i2=3037000499} {i1*i2} 9223372033963249500 + test_realnum_expr expr-1.265\ + {i1=3037000500, i2=-3037000499} {i1*i2} -9223372033963249500 + test_realnum_expr expr-1.266\ + {i1=-3037000500, i2=3037000499} {i1*i2} -9223372033963249500 + test_realnum_expr expr-1.267\ + {i1=-3037000500, i2=-3037000499} {i1*i2} 9223372033963249500 + + test_realnum_expr expr-1.268\ + {i1=3037000499, i2=3037000500} {i1*i2} 9223372033963249500 + test_realnum_expr expr-1.269\ + {i1=3037000499, i2=-3037000500} {i1*i2} -9223372033963249500 + test_realnum_expr expr-1.270\ + {i1=-3037000499, i2=3037000500} {i1*i2} -9223372033963249500 + test_realnum_expr expr-1.271\ + {i1=-3037000499, i2=-3037000500} {i1*i2} 9223372033963249500 + }} ifcapable floatingpoint { diff --git a/test/filectrl.test b/test/filectrl.test index 28fecee92f..460b71e257 100644 --- a/test/filectrl.test +++ b/test/filectrl.test @@ -43,4 +43,30 @@ do_test filectrl-1.6 { } {/etilqs_/} db close forcedelete .test_control_lockproxy.db-conch test.proxy +forcedelete test.db test2.db + +if {$tcl_platform(platform)=="windows"} { + do_test filectrl-2.1 { + sqlite3 db test2.db + set size [file size test2.db] + set handle [file_control_win32_get_handle db] + db close + forcedelete test2.db + list $size $handle [expr {$handle != 0}] + } {/^0 \{0 [0-9A-Fa-f]+\} 1$/} + + do_test filectrl-2.2 { + sqlite3 db test2.db + execsql { + CREATE TABLE t1(x); + INSERT INTO t1 (x) VALUES(RANDOMBLOB(1048576)); + } + set size [file size test2.db] + set handle [file_control_win32_get_handle db] + db close + forcedelete test2.db + list $size $handle [expr {$handle != 0}] + } {/^1\d+ \{0 [0-9A-Fa-f]+\} 1$/} +} + finish_test diff --git a/test/fkey1.test b/test/fkey1.test index e10781ac52..d9b038a022 100644 --- a/test/fkey1.test +++ b/test/fkey1.test @@ -15,6 +15,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +set testprefix fkey1 ifcapable {!foreignkey} { finish_test @@ -185,4 +186,25 @@ do_catchsql_test fkey1-5.4 { INSERT OR REPLACE INTO Foo(Id, ParentId, C1) VALUES (2, 3, 'A-2-3'); } {1 {FOREIGN KEY constraint failed}} +#------------------------------------------------------------------------- +# Check that foreign key processing is not fooled by partial indexes +# on the parent table. +# +do_execsql_test 6.0 { + CREATE TABLE p1(x, y); + CREATE UNIQUE INDEX p1x ON p1(x) WHERE y<2; + INSERT INTO p1 VALUES(1, 1); + CREATE TABLE c1(a REFERENCES p1(x)); +} + +do_catchsql_test 6.1 { + INSERT INTO c1 VALUES(1); +} {1 {foreign key mismatch - "c1" referencing "p1"}} + +do_execsql_test 6.2 { + CREATE UNIQUE INDEX p1x2 ON p1(x); + INSERT INTO c1 VALUES(1); +} {} + + finish_test diff --git a/test/fkey2.test b/test/fkey2.test index aec75ed693..c2ae3788f5 100644 --- a/test/fkey2.test +++ b/test/fkey2.test @@ -1062,7 +1062,7 @@ ifcapable altertable { PRAGMA foreign_keys = off; ALTER TABLE t2 ADD COLUMN h DEFAULT 'text' REFERENCES t1; PRAGMA foreign_keys = on; - SELECT sql FROM sqlite_temp_master WHERE name='t2'; + SELECT sql FROM temp.sqlite_master WHERE name='t2'; } } {{CREATE TABLE t2(a, b, c REFERENCES t1, d DEFAULT NULL REFERENCES t1, e REFERENCES t1 DEFAULT NULL, h DEFAULT 'text' REFERENCES t1)}} @@ -1093,7 +1093,7 @@ ifcapable altertable { ] do_test fkey2-14.2tmp.2.2 { execsql { ALTER TABLE t1 RENAME TO t4 } - execsql { SELECT sql FROM sqlite_temp_master WHERE type = 'table'} + execsql { SELECT sql FROM temp.sqlite_master WHERE type = 'table'} } [list \ {CREATE TABLE "t4"(a PRIMARY KEY, b REFERENCES "t4")} \ {CREATE TABLE t2(a PRIMARY KEY, b REFERENCES "t4", c REFERENCES t2)} \ diff --git a/test/fkey8.test b/test/fkey8.test index b4b6bb75e7..4269c20a92 100644 --- a/test/fkey8.test +++ b/test/fkey8.test @@ -101,5 +101,67 @@ foreach {tn use_stmt sql schema} { } $use_stmt } +#------------------------------------------------------------------------- +# The following tests check that foreign key constaint counters are +# correctly updated for any implicit DELETE operations that occur +# when a REPLACE command is executed against a WITHOUT ROWID table +# that has no triggers or auxiliary indexes. +# +reset_db +do_execsql_test 2.1.0 { + PRAGMA foreign_keys = on; + CREATE TABLE p1(a PRIMARY KEY, b) WITHOUT ROWID; + CREATE TABLE c1(x REFERENCES p1 DEFERRABLE INITIALLY DEFERRED); + + INSERT INTO p1 VALUES(1, 'one'); + INSERT INTO p1 VALUES(2, 'two'); + INSERT INTO c1 VALUES(1); + INSERT INTO c1 VALUES(2); +} + +do_catchsql_test 2.1.2 { + BEGIN; + DELETE FROM p1 WHERE a=1; + INSERT OR REPLACE INTO p1 VALUES(2, 'two'); + COMMIT; +} {1 {FOREIGN KEY constraint failed}} + +reset_db +do_execsql_test 2.2.0 { + PRAGMA foreign_keys = on; + CREATE TABLE p2(a PRIMARY KEY, b); + CREATE TABLE c2( + x PRIMARY KEY, + y REFERENCES p2 DEFERRABLE INITIALLY DEFERRED + ) WITHOUT ROWID; +} + +do_catchsql_test 2.2.1 { + BEGIN; + INSERT INTO c2 VALUES(13, 13); + INSERT OR REPLACE INTO c2 VALUES(13, 13); + DELETE FROM c2; + COMMIT; +} {0 {}} + +reset_db +do_execsql_test 2.3.0 { + PRAGMA foreign_keys = on; + CREATE TABLE p3(a PRIMARY KEY, b) WITHOUT ROWID; + CREATE TABLE c3(x REFERENCES p3); + + INSERT INTO p3 VALUES(1, 'one'); + INSERT INTO p3 VALUES(2, 'two'); + INSERT INTO c3 VALUES(1); + INSERT INTO c3 VALUES(2); + + CREATE TRIGGER p3d AFTER DELETE ON p3 WHEN old.a=1 BEGIN + INSERT OR REPLACE INTO p3 VALUES(2, 'three'); + END; +} + +do_catchsql_test 2.3.1 { + DELETE FROM p3 WHERE a=1 +} {1 {FOREIGN KEY constraint failed}} finish_test diff --git a/test/fts3aux1.test b/test/fts3aux1.test index d17ac85df5..b6111f8470 100644 --- a/test/fts3aux1.test +++ b/test/fts3aux1.test @@ -117,12 +117,12 @@ do_test 2.1.2.1 { set cnt 0 execsql { SELECT * FROM terms_v WHERE rec('cnt', term) AND term='braid' } set cnt -} {2} +} {1} do_test 2.1.2.2 { set cnt 0 execsql { SELECT * FROM terms_v WHERE rec('cnt', term) AND +term='braid' } set cnt -} {38} +} {19} # Similar to the test immediately above, but using a term ("breakfast") that # is not featured in the dataset. @@ -136,7 +136,7 @@ do_test 2.1.3.2 { set cnt 0 execsql { SELECT * FROM terms_v WHERE rec('cnt', term) AND +term='breakfast' } set cnt -} {38} +} {19} do_execsql_test 2.1.4.1 { SELECT * FROM terms_v WHERE term='braid' } {braid 1 1} do_execsql_test 2.1.4.2 { SELECT * FROM terms_v WHERE +term='braid'} {braid 1 1} diff --git a/test/fuzz-oss1.test b/test/fuzz-oss1.test index 08bc670845..e77b7ed0f8 100644 --- a/test/fuzz-oss1.test +++ b/test/fuzz-oss1.test @@ -1997,5 +1997,9 @@ NULL AND ("9_u" COLLATE NOCASE = ? COLLATE NOCASE))) FROM (SELECT } } {/.* Goto .*/} +# Crash reported by OSS-FUZZ on 2016-11-10 +do_catchsql_test fuzz-oss1-detach { + DETACH x IS #1; +} {1 {near "#1": syntax error}} finish_test diff --git a/test/fuzzcheck.c b/test/fuzzcheck.c index fec65881ce..75d519566f 100644 --- a/test/fuzzcheck.c +++ b/test/fuzzcheck.c @@ -70,7 +70,6 @@ #include #include #include "sqlite3.h" -#include #define ISSPACE(X) isspace((unsigned char)(X)) #define ISDIGIT(X) isdigit((unsigned char)(X)) @@ -80,6 +79,11 @@ # include #endif +#ifdef SQLITE_OSS_FUZZ +# include +# include +#endif + /* ** Files in the virtual file system. */ @@ -128,6 +132,7 @@ static struct GlobalVars { Blob *pFirstDb; /* Content of first template database */ int nSql; /* Number of SQL scripts */ Blob *pFirstSql; /* First SQL script */ + unsigned int uRandom; /* Seed for the SQLite PRNG */ char zTestName[100]; /* Name of current test */ } g; @@ -590,10 +595,18 @@ static int inmemFullPathname( return SQLITE_OK; } +/* Always use the same random see, for repeatability. +*/ +static int inmemRandomness(sqlite3_vfs *NotUsed, int nBuf, char *zBuf){ + memset(zBuf, 0, nBuf); + memcpy(zBuf, &g.uRandom, nBufxRandomness; + inmemVfs.xRandomness = inmemRandomness; inmemVfs.xSleep = pDefault->xSleep; inmemVfs.xCurrentTimeInt64 = pDefault->xCurrentTimeInt64; - sqlite3_vfs_register(&inmemVfs, 0); + sqlite3_vfs_register(&inmemVfs, makeDefault); }; /* @@ -622,14 +635,12 @@ static void inmemVfsRegister(void){ */ static void runSql(sqlite3 *db, const char *zSql, unsigned runFlags){ const char *zMore; - const char *zEnd = &zSql[strlen(zSql)]; sqlite3_stmt *pStmt; while( zSql && zSql[0] ){ zMore = 0; pStmt = 0; sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zMore); - assert( zMore<=zEnd ); if( zMore==zSql ) break; if( runFlags & SQL_TRACE ){ const char *z = zSql; @@ -794,6 +805,8 @@ static void showHelp(void){ " --load-db ARGS... Load template databases from files into SOURCE_DB\n" " -m TEXT Add a description to the database\n" " --native-vfs Use the native VFS for initially empty database files\n" +" --oss-fuzz Enable OSS-FUZZ testing\n" +" --prng-seed N Seed value for the PRGN inside of SQLite\n" " --rebuild Rebuild and vacuum the database file\n" " --result-trace Show the results of each SQL command\n" " --sqlid N Use only SQL where sqlid=N\n" @@ -832,9 +845,13 @@ int main(int argc, char **argv){ int sqlFuzz = 0; /* True for SQL fuzz testing. False for DB fuzz */ int iTimeout = 120; /* Default 120-second timeout */ int nMem = 0; /* Memory limit */ + int nMemThisDb = 0; /* Memory limit set by the CONFIG table */ char *zExpDb = 0; /* Write Databases to files in this directory */ char *zExpSql = 0; /* Write SQL to files in this directory */ void *pHeap = 0; /* Heap for use by SQLite */ + int ossFuzz = 0; /* enable OSS-FUZZ testing */ + int ossFuzzThisDb = 0; /* ossFuzz value for this particular database */ + sqlite3_vfs *pDfltVfs; /* The default VFS */ iBegin = timeOfDay(); #ifdef __unix__ @@ -842,6 +859,8 @@ int main(int argc, char **argv){ #endif g.zArgv0 = argv[0]; zFailCode = getenv("TEST_FAILURE"); + pDfltVfs = sqlite3_vfs_find(0); + inmemVfsRegister(1); for(i=1; i=argc-1 ) fatalError("missing arguments on %s", argv[i]); + g.uRandom = atoi(argv[++i]); + }else if( strcmp(z,"quiet")==0 || strcmp(z,"q")==0 ){ quietFlag = 1; verboseFlag = 0; @@ -945,7 +971,8 @@ int main(int argc, char **argv){ /* Process each source database separately */ for(iSrcDb=0; iSrcDbzName); if( rc ){ fatalError("cannot open source database %s - %s", azSrcDb[iSrcDb], sqlite3_errmsg(db)); @@ -971,6 +998,34 @@ int main(int argc, char **argv){ sqlite3_free(zSql); if( rc ) fatalError("cannot change description: %s", sqlite3_errmsg(db)); } + ossFuzzThisDb = ossFuzz; + + /* If the CONFIG(name,value) table exists, read db-specific settings + ** from that table */ + if( sqlite3_table_column_metadata(db,0,"config",0,0,0,0,0,0)==SQLITE_OK ){ + rc = sqlite3_prepare_v2(db, "SELECT name, value FROM config", -1, &pStmt, 0); + if( rc ) fatalError("cannot prepare query of CONFIG table: %s", + sqlite3_errmsg(db)); + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + const char *zName = (const char *)sqlite3_column_text(pStmt,0); + if( zName==0 ) continue; + if( strcmp(zName, "oss-fuzz")==0 ){ + ossFuzzThisDb = sqlite3_column_int(pStmt,1); + if( verboseFlag ) printf("Config: oss-fuzz=%d\n", ossFuzzThisDb); + } + if( strcmp(zName, "limit-mem")==0 ){ +#if !defined(SQLITE_ENABLE_MEMSYS3) && !defined(SQLITE_ENABLE_MEMSYS5) + fatalError("the limit-mem option requires -DSQLITE_ENABLE_MEMSYS5" + " or _MEMSYS3"); +#else + nMemThisDb = sqlite3_column_int(pStmt,1); + if( verboseFlag ) printf("Config: limit-mem=%d\n", nMemThisDb); +#endif + } + } + sqlite3_finalize(pStmt); + } + if( zInsSql ){ sqlite3_create_function(db, "readfile", 1, SQLITE_UTF8, 0, readfileFunc, 0, 0); @@ -1086,19 +1141,17 @@ int main(int argc, char **argv){ } /* Limit available memory, if requested */ - if( nMem>0 ){ + if( nMemThisDb>0 ){ sqlite3_shutdown(); - pHeap = malloc(nMem); + pHeap = realloc(pHeap, nMemThisDb); if( pHeap==0 ){ fatalError("failed to allocate %d bytes of heap memory", nMem); } - sqlite3_config(SQLITE_CONFIG_HEAP, pHeap, nMem, 128); + sqlite3_config(SQLITE_CONFIG_HEAP, pHeap, nMemThisDb, 128); } - /* Register the in-memory virtual filesystem - */ + /* Reset the in-memory virtual filesystem */ formatVfs(); - inmemVfsRegister(); /* Run a test using each SQL script against each database. */ @@ -1123,25 +1176,37 @@ int main(int argc, char **argv){ } } createVFile("main.db", pDb->sz, pDb->a); - openFlags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE; - if( nativeFlag && pDb->sz==0 ){ - openFlags |= SQLITE_OPEN_MEMORY; - zVfs = 0; - } - rc = sqlite3_open_v2("main.db", &db, openFlags, zVfs); - if( rc ) fatalError("cannot open inmem database"); - if( cellSzCkFlag ) runSql(db, "PRAGMA cell_size_check=ON", runFlags); - setAlarm(iTimeout); -#ifndef SQLITE_OMIT_PROGRESS_CALLBACK - if( sqlFuzz || vdbeLimitFlag ){ - sqlite3_progress_handler(db, 100000, progressHandler, &vdbeLimitFlag); - } + sqlite3_randomness(0,0); + if( ossFuzzThisDb ){ +#ifndef SQLITE_OSS_FUZZ + fatalError("--oss-fuzz not supported: recompile with -DSQLITE_OSS_FUZZ"); +#else + extern int LLVMFuzzerTestOneInput(const uint8_t*, size_t); + LLVMFuzzerTestOneInput((const uint8_t*)pSql->a, (size_t)pSql->sz); #endif - do{ - runSql(db, (char*)pSql->a, runFlags); - }while( timeoutTest ); - setAlarm(0); - sqlite3_close(db); + }else{ + openFlags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE; + if( nativeFlag && pDb->sz==0 ){ + openFlags |= SQLITE_OPEN_MEMORY; + zVfs = 0; + } + rc = sqlite3_open_v2("main.db", &db, openFlags, zVfs); + if( rc ) fatalError("cannot open inmem database"); + sqlite3_limit(db, SQLITE_LIMIT_LENGTH, 100000000); + sqlite3_limit(db, SQLITE_LIMIT_LIKE_PATTERN_LENGTH, 50); + if( cellSzCkFlag ) runSql(db, "PRAGMA cell_size_check=ON", runFlags); + setAlarm(iTimeout); +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + if( sqlFuzz || vdbeLimitFlag ){ + sqlite3_progress_handler(db, 100000, progressHandler, &vdbeLimitFlag); + } +#endif + do{ + runSql(db, (char*)pSql->a, runFlags); + }while( timeoutTest ); + setAlarm(0); + sqlite3_close(db); + } if( sqlite3_memory_used()>0 ) fatalError("memory leak"); reformatVfs(); nTest++; diff --git a/test/fuzzdata5.db b/test/fuzzdata5.db new file mode 100644 index 0000000000..4645b1921b Binary files /dev/null and b/test/fuzzdata5.db differ diff --git a/test/gcfault.test b/test/gcfault.test new file mode 100644 index 0000000000..d54b78fafc --- /dev/null +++ b/test/gcfault.test @@ -0,0 +1,56 @@ +# 2016 December 30 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The +# focus of this file is testing OOM error handling within the built-in +# group_concat() function. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix gcfault + + +foreach {enc} { + utf16 + utf8 +} { + reset_db + sqlite3_db_config_lookaside db 0 0 0 + execsql "PRAGMA encoding = $enc" + + do_execsql_test 1.$enc.1 { + CREATE TABLE s(i, s); + INSERT INTO s VALUES(1, ',0123456789,'); + INSERT INTO s VALUES(2, X'2c303132333435363738392c'); + + CREATE TABLE e(e); + INSERT INTO e VALUES('v1'), ('v2'); + } {} + + do_faultsim_test 1.$enc.1 -faults oom* -body { + execsql { SELECT group_concat(e, (SELECT s FROM s WHERE i=1)) FROM e } + } + + do_faultsim_test 1.$enc.2 -faults oom-t* -body { + execsql { SELECT group_concat(e, (SELECT s FROM s WHERE i=2)) FROM e } + } + + do_faultsim_test 1.$enc.3 -faults oom-t* -prep { + set ::STMT [sqlite3_prepare db {SELECT group_concat(e, ?) FROM e} -1 dummy] + sqlite3_bind_text $::STMT 1 ",0123456789," 12 + } -body { + while { "SQLITE_ROW"==[sqlite3_step $::STMT] } { } + } -test { + sqlite3_finalize $::STMT + } +} + +finish_test diff --git a/test/hexlit.test b/test/hexlit.test index c48930b49b..2161f27a12 100644 --- a/test/hexlit.test +++ b/test/hexlit.test @@ -112,6 +112,9 @@ do_catchsql_test hexlist-400 { do_catchsql_test hexlist-401 { SELECT DISTINCT 0x10000000000000000; } {1 {hex literal too big: 0x10000000000000000}} +do_catchsql_test hexlist-402 { + SELECT DISTINCT -0x08000000000000000; +} {1 {hex literal too big: -0x08000000000000000}} do_catchsql_test hexlist-410 { DROP TABLE IF EXISTS t1; CREATE TABLE t1(x); diff --git a/test/hook.test b/test/hook.test index 12e5bc0656..8f095c9e8d 100644 --- a/test/hook.test +++ b/test/hook.test @@ -848,4 +848,10 @@ do_preupdate_test 7.6.4 { DELETE main t1 1 1 0 abc } +# No preupdate callbacks for modifying sqlite_master. +do_preupdate_test 8.1 { + CREATE TABLE x1(x, y); +} { +} + finish_test diff --git a/test/in.test b/test/in.test index 3c87a2a86f..4e10800481 100644 --- a/test/in.test +++ b/test/in.test @@ -314,7 +314,7 @@ do_test in-9.4 { catchsql { SELECT b FROM t1 WHERE a NOT IN tb; } -} {1 {only a single result allowed for a SELECT that is part of an expression}} +} {1 {sub-select returns 2 columns - expected 1}} # IN clauses in CHECK constraints. Ticket #1645 # @@ -391,28 +391,28 @@ do_test in-12.2 { SELECT a, b FROM t3 UNION ALL SELECT a, b FROM t2 ); } -} {1 {only a single result allowed for a SELECT that is part of an expression}} +} {1 {sub-select returns 2 columns - expected 1}} do_test in-12.3 { catchsql { SELECT * FROM t2 WHERE a IN ( SELECT a, b FROM t3 UNION SELECT a, b FROM t2 ); } -} {1 {only a single result allowed for a SELECT that is part of an expression}} +} {1 {sub-select returns 2 columns - expected 1}} do_test in-12.4 { catchsql { SELECT * FROM t2 WHERE a IN ( SELECT a, b FROM t3 EXCEPT SELECT a, b FROM t2 ); } -} {1 {only a single result allowed for a SELECT that is part of an expression}} +} {1 {sub-select returns 2 columns - expected 1}} do_test in-12.5 { catchsql { SELECT * FROM t2 WHERE a IN ( SELECT a, b FROM t3 INTERSECT SELECT a, b FROM t2 ); } -} {1 {only a single result allowed for a SELECT that is part of an expression}} +} {1 {sub-select returns 2 columns - expected 1}} do_test in-12.6 { catchsql { SELECT * FROM t2 WHERE a IN ( @@ -478,7 +478,7 @@ do_test in-12.14 { SELECT a, b FROM t3 UNION ALL SELECT a, b FROM t2 ); } -} {1 {only a single result allowed for a SELECT that is part of an expression}} +} {1 {sub-select returns 2 columns - expected 1}} do_test in-12.15 { catchsql { SELECT * FROM t2 WHERE a IN ( @@ -629,11 +629,12 @@ do_test in-13.14 { } } {} +breakpoint do_test in-13.15 { catchsql { SELECT 0 WHERE (SELECT 0,0) OR (0 IN (1,2)); } -} {1 {only a single result allowed for a SELECT that is part of an expression}} +} {1 {sub-select returns 2 columns - expected 1}} do_test in-13.X { diff --git a/test/in5.test b/test/in5.test index b49c93e15b..ac60cfe58e 100644 --- a/test/in5.test +++ b/test/in5.test @@ -214,8 +214,27 @@ do_execsql_test 7.3.2 { SELECT a FROM y1 WHERE b IN (SELECT a FROM y2); } {two} -finish_test - +#------------------------------------------------------------------------- +# Tests to confirm that indexes on the rowid column do not confuse +# the query planner. See ticket [0eab1ac7591f511d]. +# +do_execsql_test 8.0 { + CREATE TABLE n1(a INTEGER PRIMARY KEY, b VARCHAR(500)); + CREATE UNIQUE INDEX n1a ON n1(a); +} +do_execsql_test 8.1 { + SELECT count(*) FROM n1 WHERE a IN (1, 2, 3) +} 0 +do_execsql_test 8.2 { + SELECT count(*) FROM n1 WHERE a IN (SELECT +a FROM n1) +} 0 +do_execsql_test 8.3 { + INSERT INTO n1 VALUES(1, NULL), (2, NULL), (3, NULL); + SELECT count(*) FROM n1 WHERE a IN (1, 2, 3) +} 3 +do_execsql_test 8.4 { + SELECT count(*) FROM n1 WHERE a IN (SELECT +a FROM n1) +} 3 finish_test diff --git a/test/incrblob4.test b/test/incrblob4.test index a92e373536..dbff8eb7d5 100644 --- a/test/incrblob4.test +++ b/test/incrblob4.test @@ -86,4 +86,24 @@ do_test 3.3 { execsql { INSERT INTO t1(v) VALUES($new) } } {} +#------------------------------------------------------------------------- +# Test that it is not possible to DROP a table with an incremental blob +# cursor open on it. +# +do_execsql_test 4.1 { + CREATE TABLE t2(a INTEGER PRIMARY KEY, b); + INSERT INTO t2 VALUES(456, '0123456789'); +} +do_test 4.2 { + set blob [db incrblob -readonly t2 b 456] + read $blob 5 +} {01234} +do_catchsql_test 4.3 { + DROP TABLE t2 +} {1 {database table is locked}} +do_test 4.4 { + sqlite3_extended_errcode db +} {SQLITE_LOCKED} +close $blob + finish_test diff --git a/test/index6.test b/test/index6.test index 9501a6c864..f6177b44e9 100644 --- a/test/index6.test +++ b/test/index6.test @@ -65,12 +65,15 @@ do_test index6-1.5 { catchsql { CREATE INDEX bad1 ON t1(a,b) WHERE a!=random(); } -} {1 {functions prohibited in partial index WHERE clauses}} +} {1 {non-deterministic functions prohibited in partial index WHERE clauses}} do_test index6-1.6 { catchsql { CREATE INDEX bad1 ON t1(a,b) WHERE a NOT LIKE 'abc%'; } -} {1 {functions prohibited in partial index WHERE clauses}} +} {0 {}} +do_execsql_test index6-1.7 { + DROP INDEX IF EXISTS bad1; +} do_test index6-1.10 { execsql { diff --git a/test/index7.test b/test/index7.test index 557fe21324..0037a8a44d 100644 --- a/test/index7.test +++ b/test/index7.test @@ -99,12 +99,25 @@ do_test index7-1.5 { catchsql { CREATE INDEX bad1 ON t1(a,b) WHERE a!=random(); } -} {1 {functions prohibited in partial index WHERE clauses}} +} {1 {non-deterministic functions prohibited in partial index WHERE clauses}} do_test index7-1.6 { catchsql { CREATE INDEX bad1 ON t1(a,b) WHERE a NOT LIKE 'abc%'; } -} {1 {functions prohibited in partial index WHERE clauses}} +} {0 {}} +do_execsql_test index7-1.7 { + INSERT INTO t1(a,b,c) + VALUES('abcde',1,101),('abdef',2,102),('xyz',3,103),('abcz',4,104); + SELECT c FROM t1 WHERE a NOT LIKE 'abc%' AND a=7 ORDER BY +b; +} {7} +do_execsql_test index7-1.7eqp { + EXPLAIN QUERY PLAN + SELECT b FROM t1 WHERE a NOT LIKE 'abc%' AND a=7 ORDER BY +b; +} {/SEARCH TABLE t1 USING COVERING INDEX bad1 /} +do_execsql_test index7-1.8 { + DELETE FROM t1 WHERE c>=101; + DROP INDEX IF EXISTS bad1; +} {} do_test index7-1.10 { execsql { diff --git a/test/indexexpr1.test b/test/indexexpr1.test index a8a74f259e..cd72430d91 100644 --- a/test/indexexpr1.test +++ b/test/indexexpr1.test @@ -323,5 +323,51 @@ do_execsql_test indexexpr1-1010 { SELECT *, '|' FROM t0 ORDER BY +a; } {0 88 2 | 2 99 4 | 5 99 7 |} +# 2016-10-10 +# Make sure indexes on expressions skip over initial NULL values in the +# index as they are suppose to do. +# Ticket https://www.sqlite.org/src/tktview/4baa46491212947 +# +do_execsql_test indexexpr1-1100 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a); + INSERT INTO t1 VALUES(NULL),(1); + SELECT '1:', typeof(a), a FROM t1 WHERE a<10; + SELECT '2:', typeof(a), a FROM t1 WHERE a+0<10; + CREATE INDEX t1x1 ON t1(a); + CREATE INDEX t1x2 ON t1(a+0); + SELECT '3:', typeof(a), a FROM t1 WHERE a<10; + SELECT '4:', typeof(a), a FROM t1 WHERE a+0<10; +} {1: integer 1 2: integer 1 3: integer 1 4: integer 1} + +do_execsql_test indexexpr1-1200 { + CREATE TABLE t10(a int, b int, c int, d int); + INSERT INTO t10(a, b, c, d) VALUES(0, 0, 2, 2); + INSERT INTO t10(a, b, c, d) VALUES(0, 0, 0, 0); + INSERT INTO t10(a, b, c, d) VALUES(0, 0, 1, 1); + INSERT INTO t10(a, b, c, d) VALUES(1, 1, 1, 1); + INSERT INTO t10(a, b, c, d) VALUES(1, 1, 0, 0); + INSERT INTO t10(a, b, c, d) VALUES(2, 2, 0, 0); + + SELECT a+b, c+d FROM t10 ORDER BY a+b, c+d; +} { + 0 0 0 2 0 4 2 0 2 2 4 0 +} +do_execsql_test indexexpr1-1200.1 { + CREATE INDEX t10_ab ON t10(a+b); +} +do_execsql_test indexexpr1-1200.2 { + SELECT a+b, c+d FROM t10 ORDER BY a+b, c+d; +} { + 0 0 0 2 0 4 2 0 2 2 4 0 +} +do_execsql_test indexexpr1-1200.3 { + CREATE INDEX t10_abcd ON t10(a+b,c+d); +} +do_execsql_test indexexpr1-1200.4 { + SELECT a+b, c+d FROM t10 ORDER BY a+b, c+d; +} { + 0 0 0 2 0 4 2 0 2 2 4 0 +} finish_test diff --git a/test/instr.test b/test/instr.test index c8be4862b9..2caf3bce3b 100644 --- a/test/instr.test +++ b/test/instr.test @@ -248,4 +248,13 @@ do_execsql_test instr-1.62 { SELECT coalesce(instr(NULL,NULL), 999); } {999} +do_execsql_test instr-1.63 { + SELECT instr(X'', 'abc') +} 0 +do_execsql_test instr-1.64 { + CREATE TABLE x1(a, b); + INSERT INTO x1 VALUES(X'', 'abc'); + SELECT instr(a, b) FROM x1; +} 0 + finish_test diff --git a/test/instrfault.test b/test/instrfault.test new file mode 100644 index 0000000000..0ddb12c84b --- /dev/null +++ b/test/instrfault.test @@ -0,0 +1,92 @@ +# 2016 November 4 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The +# focus of this file is testing OOM error handling within the built-in +# INSTR() function. +# + + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix instrfault + +# Use big NEEDLE and HAYSTACK strings. Strings so large they cannot +# use lookaside buffers. +# +set ::NEEDLE [string repeat "abcdefghijklmnopqrstuvwxyz" 10] +set ::HAYSTACK "[string repeat 123 10]$NEEDLE[string repeat 456 10]" + +foreach {enc} { + utf8 + utf16 +} { + reset_db + sqlite3_db_config_lookaside db 0 0 0 + + execsql "PRAGMA encoding = $enc" + do_execsql_test 1.$enc.1 { + CREATE TABLE t1(n, h); + INSERT INTO t1 VALUES($::NEEDLE, $::HAYSTACK); + } {} + + do_faultsim_test 1.$enc.1 -faults oom-t* -prep { + execsql { SELECT instr(h, n) FROM t1 } + } -body { + execsql { SELECT instr(h, n) FROM t1 } + } -test { + faultsim_test_result {0 31} + } + + do_faultsim_test 1.$enc.2 -faults oom-t* -prep { + execsql { SELECT instr($::HAYSTACK, $::NEEDLE) FROM t1 } + } -body { + execsql { SELECT instr($::HAYSTACK, $::NEEDLE) FROM t1 } + } -test { + faultsim_test_result {0 31} + } + + do_faultsim_test 1.$enc.3 -faults oom-t* -prep { + set ::stmt [sqlite3_prepare_v2 db "SELECT instr(?, ?)" -1 dummy] + sqlite3_bind_text $::stmt 1 $::HAYSTACK [string length $::HAYSTACK] + sqlite3_bind_text $::stmt 2 $::NEEDLE [string length $::NEEDLE] + } -body { + set rc [sqlite3_step $::stmt] + if {$rc=="SQLITE_NOMEM"} { error "out of memory" } + sqlite3_column_int $::stmt 0 + } -test { + faultsim_test_result {0 31} + sqlite3_finalize $::stmt + } + + do_faultsim_test 1.$enc.4 -faults oom-t* -prep { + set ::stmt [sqlite3_prepare_v2 db "SELECT instr(?, ?)" -1 dummy] + sqlite3_bind_blob $::stmt 1 $::HAYSTACK [string length $::HAYSTACK] + sqlite3_bind_text $::stmt 2 $::NEEDLE [string length $::NEEDLE] + } -body { + set rc [sqlite3_step $::stmt] + if {$rc=="SQLITE_NOMEM"} { error "out of memory" } + sqlite3_column_int $::stmt 0 + } -test { + faultsim_test_result {0 31} + sqlite3_finalize $::stmt + } + + do_execsql_test 1.$enc.5.0 { + CREATE TABLE h1(a, b); + INSERT INTO h1 VALUES('abcdefg%200hijkl', randomblob(200)); + INSERT INTO h1 SELECT b, a FROM h1; + } + do_faultsim_test 1.$enc.5 -faults oom-t* -body { + execsql { SELECT rowid FROM h1 WHERE instr(a,b) } + } -test {} +} + +finish_test diff --git a/test/intarray.test b/test/intarray.test index 2aba080bd4..5c70ebaf1a 100644 --- a/test/intarray.test +++ b/test/intarray.test @@ -42,7 +42,7 @@ do_test intarray-1.1 { set ia3 [sqlite3_intarray_create db ia3] set ia4 [sqlite3_intarray_create db ia4] db eval { - SELECT type, name FROM sqlite_temp_master + SELECT type, name FROM temp.sqlite_master ORDER BY name } } {table ia1 table ia2 table ia3 table ia4} diff --git a/test/interrupt.test b/test/interrupt.test index 92ab4c3265..8774aee860 100644 --- a/test/interrupt.test +++ b/test/interrupt.test @@ -128,7 +128,7 @@ ifcapable tempdb { } {1 interrupted} do_test interrupt-3.$i.3 { execsql { - SELECT name FROM sqlite_temp_master; + SELECT name FROM temp.sqlite_master; } } {} do_test interrupt-3.$i.4 { @@ -139,7 +139,7 @@ ifcapable tempdb { do_test interrupt-3.$i.5 { catchsql {SELECT name FROM sqlite_temp_master}; execsql { - SELECT name FROM sqlite_temp_master; + SELECT name FROM temp.sqlite_master; } } {} } diff --git a/test/interrupt2.test b/test/interrupt2.test new file mode 100644 index 0000000000..b9c66bd1f4 --- /dev/null +++ b/test/interrupt2.test @@ -0,0 +1,159 @@ +# 2016 Aug 12 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The +# focus of this script is using the sqlite_interrupt() API to +# interrupt WAL checkpoint operations. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/wal_common.tcl +set testprefix interrupt2 + +if {[permutation]=="journaltest" || [permutation]=="inmemory_journal"} { + finish_test + return +} + +db close +testvfs tvfs -default 1 + +tvfs filter xWrite +tvfs script write_cb + +set ::trigger_interrupt 0 +proc write_cb {method args} { + set filename [lindex $args 0] + if {[file tail $filename]=="test.db" && $::trigger_interrupt} { + if {$::trigger_interrupt} { + incr ::trigger_interrupt -1 + if {$::trigger_interrupt==0} { sqlite3_interrupt db } + } + } + return 0 +} + +sqlite3 db test.db +do_execsql_test 1.0 { + CREATE TABLE t1(a, b); + CREATE INDEX t1a ON t1(a); + CREATE INDEX t1b ON t1(b); + PRAGMA journal_mode = wal; + + WITH ii(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM ii WHERE i<1000 ) + INSERT INTO t1 SELECT i, i FROM ii; +} {wal} + +foreach idelay { + 5 + 10 + 15 + 20 +} { + + set ::trigger_interrupt $idelay + do_catchsql_test 1.$idelay.1 { PRAGMA wal_checkpoint; } {1 interrupted} + do_execsql_test 1.$idelay.2 { SELECT count(*) FROM t1 } 1000 + + set ::trigger_interrupt $idelay + do_test 1.$idelay.3 { + list [catch { sqlite3_wal_checkpoint_v2 db truncate } msg] $msg + } {1 {SQLITE_INTERRUPT - interrupted}} + do_execsql_test 1.$idelay.4 { SELECT count(*) FROM t1 } 1000 +} + +#------------------------------------------------------------------------- +# Check that if there are other SQL statements running, a checkpoint does +# not clear the isInterrupted flag. +# +do_execsql_test 2.0 { + CREATE TEMP TABLE z1(a, b); + INSERT INTO z1 SELECT * FROM t1; +} + +do_test 2.1 { + set i 10 + set res [list [catch { + set i 10 + db eval {SELECT * FROM z1} { + incr i -1 + if {$i==0} { + set ::trigger_interrupt 10 + set cres [catch { sqlite3_wal_checkpoint_v2 db truncate } msg] + lappend cres $msg + } + } + } msg] $msg] + + list $cres $res +} {{1 {SQLITE_INTERRUPT - interrupted}} {1 interrupted}} + +do_execsql_test 2.0 { + SELECT count(*) FROM t1 + UNION ALL + SELECT count(*) FROM z1 +} {1000 1000} + +#------------------------------------------------------------------------- +# Check the effect of an interrupt during sqlite3_close(). +# +db_save_and_close + +db_restore_and_reopen +do_test 3.1.1 { + set ::trigger_interrupt 10 + db eval { SELECT * FROM sqlite_master } + db close + set {} {} +} {} +do_test 3.1.2 { + list [file exists test.db] [file exists test.db-wal] +} {1 1} + +db_restore_and_reopen +do_test 3.2.1 { + db eval { SELECT * FROM sqlite_master } + db close + set {} {} +} {} +do_test 3.2.2 { + list [file exists test.db] [file exists test.db-wal] +} {1 0} + +#------------------------------------------------------------------------- +# Check the effect of an interrupt during an automatic checkpoint +# +db_restore_and_reopen +do_test 4.0 { + execsql { PRAGMA wal_autocheckpoint = 10 } + set ::trigger_interrupt 10 + execsql { CREATE TABLE t2(x, y) } +} {} + +# The auto-checkpoint in test 4.0 should have been interrupted. So this +# db write should cause the wal file to grow. +do_test 4.1 { + set nFrame1 [wal_frame_count test.db-wal 1024] + execsql { CREATE TABLE t3(x, y) } + set nFrame2 [wal_frame_count test.db-wal 1024] + expr $nFrame2 > $nFrame1 +} {1} + +# The auto-checkpoint in test 4.0 should not have been interrupted. So +# this db write should not cause the wal file to grow. +do_test 4.2 { + set nFrame1 [wal_frame_count test.db-wal 1024] + execsql { CREATE TABLE t4(x, y) } + set nFrame2 [wal_frame_count test.db-wal 1024] + expr $nFrame2 == $nFrame1 +} {1} + +finish_test diff --git a/test/join5.test b/test/join5.test index 543cd4d27f..352ffd40a6 100644 --- a/test/join5.test +++ b/test/join5.test @@ -13,10 +13,10 @@ # This file implements tests for left outer joins containing ON # clauses that restrict the scope of the left term of the join. # -# $Id: join5.test,v 1.2 2007/06/08 00:20:48 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl +set testprefix join5 do_test join5-1.1 { @@ -184,4 +184,32 @@ do_execsql_test join6-4.2 { LEFT JOIN (SELECT 1) ON fruit='banana'; } {apple {} banana 1} +#------------------------------------------------------------------------- +do_execsql_test 5.0 { + CREATE TABLE y1(x, y, z); + INSERT INTO y1 VALUES(0, 0, 1); + CREATE TABLE y2(a); +} + +do_execsql_test 5.1 { + SELECT count(z) FROM y1 LEFT JOIN y2 ON x GROUP BY y; +} 1 + +do_execsql_test 5.2 { + SELECT count(z) FROM ( SELECT * FROM y1 ) LEFT JOIN y2 ON x GROUP BY y; +} 1 + +do_execsql_test 5.3 { + CREATE VIEW v1 AS SELECT x, y, z FROM y1; + SELECT count(z) FROM v1 LEFT JOIN y2 ON x GROUP BY y; +} 1 + +do_execsql_test 5.4 { + SELECT count(z) FROM ( SELECT * FROM y1 ) LEFT JOIN y2 ON x +} 1 + +do_execsql_test 5.5 { + SELECT * FROM ( SELECT * FROM y1 ) LEFT JOIN y2 ON x +} {0 0 1 {}} + finish_test diff --git a/test/json101.test b/test/json101.test index 3ee007c1cb..37123eef1d 100644 --- a/test/json101.test +++ b/test/json101.test @@ -382,8 +382,311 @@ do_catchsql_test json-9.7 { SELECT json_quote() } {1 {wrong number of arguments to function json_quote()}} - - +# Make sure only valid backslash-escapes are accepted. +# +do_execsql_test json-10.1 { + SELECT json_valid('" \ "'); +} {0} +do_execsql_test json-10.2 { + SELECT json_valid('" \! "'); +} {0} +do_execsql_test json-10.3 { + SELECT json_valid('" \" "'); +} {1} +do_execsql_test json-10.4 { + SELECT json_valid('" \# "'); +} {0} +do_execsql_test json-10.5 { + SELECT json_valid('" \$ "'); +} {0} +do_execsql_test json-10.6 { + SELECT json_valid('" \% "'); +} {0} +do_execsql_test json-10.7 { + SELECT json_valid('" \& "'); +} {0} +do_execsql_test json-10.8 { + SELECT json_valid('" \'' "'); +} {0} +do_execsql_test json-10.9 { + SELECT json_valid('" \( "'); +} {0} +do_execsql_test json-10.10 { + SELECT json_valid('" \) "'); +} {0} +do_execsql_test json-10.11 { + SELECT json_valid('" \* "'); +} {0} +do_execsql_test json-10.12 { + SELECT json_valid('" \+ "'); +} {0} +do_execsql_test json-10.13 { + SELECT json_valid('" \, "'); +} {0} +do_execsql_test json-10.14 { + SELECT json_valid('" \- "'); +} {0} +do_execsql_test json-10.15 { + SELECT json_valid('" \. "'); +} {0} +do_execsql_test json-10.16 { + SELECT json_valid('" \/ "'); +} {1} +do_execsql_test json-10.17 { + SELECT json_valid('" \0 "'); +} {0} +do_execsql_test json-10.18 { + SELECT json_valid('" \1 "'); +} {0} +do_execsql_test json-10.19 { + SELECT json_valid('" \2 "'); +} {0} +do_execsql_test json-10.20 { + SELECT json_valid('" \3 "'); +} {0} +do_execsql_test json-10.21 { + SELECT json_valid('" \4 "'); +} {0} +do_execsql_test json-10.22 { + SELECT json_valid('" \5 "'); +} {0} +do_execsql_test json-10.23 { + SELECT json_valid('" \6 "'); +} {0} +do_execsql_test json-10.24 { + SELECT json_valid('" \7 "'); +} {0} +do_execsql_test json-10.25 { + SELECT json_valid('" \8 "'); +} {0} +do_execsql_test json-10.26 { + SELECT json_valid('" \9 "'); +} {0} +do_execsql_test json-10.27 { + SELECT json_valid('" \: "'); +} {0} +do_execsql_test json-10.28 { + SELECT json_valid('" \; "'); +} {0} +do_execsql_test json-10.29 { + SELECT json_valid('" \< "'); +} {0} +do_execsql_test json-10.30 { + SELECT json_valid('" \= "'); +} {0} +do_execsql_test json-10.31 { + SELECT json_valid('" \> "'); +} {0} +do_execsql_test json-10.32 { + SELECT json_valid('" \? "'); +} {0} +do_execsql_test json-10.33 { + SELECT json_valid('" \@ "'); +} {0} +do_execsql_test json-10.34 { + SELECT json_valid('" \A "'); +} {0} +do_execsql_test json-10.35 { + SELECT json_valid('" \B "'); +} {0} +do_execsql_test json-10.36 { + SELECT json_valid('" \C "'); +} {0} +do_execsql_test json-10.37 { + SELECT json_valid('" \D "'); +} {0} +do_execsql_test json-10.38 { + SELECT json_valid('" \E "'); +} {0} +do_execsql_test json-10.39 { + SELECT json_valid('" \F "'); +} {0} +do_execsql_test json-10.40 { + SELECT json_valid('" \G "'); +} {0} +do_execsql_test json-10.41 { + SELECT json_valid('" \H "'); +} {0} +do_execsql_test json-10.42 { + SELECT json_valid('" \I "'); +} {0} +do_execsql_test json-10.43 { + SELECT json_valid('" \J "'); +} {0} +do_execsql_test json-10.44 { + SELECT json_valid('" \K "'); +} {0} +do_execsql_test json-10.45 { + SELECT json_valid('" \L "'); +} {0} +do_execsql_test json-10.46 { + SELECT json_valid('" \M "'); +} {0} +do_execsql_test json-10.47 { + SELECT json_valid('" \N "'); +} {0} +do_execsql_test json-10.48 { + SELECT json_valid('" \O "'); +} {0} +do_execsql_test json-10.49 { + SELECT json_valid('" \P "'); +} {0} +do_execsql_test json-10.50 { + SELECT json_valid('" \Q "'); +} {0} +do_execsql_test json-10.51 { + SELECT json_valid('" \R "'); +} {0} +do_execsql_test json-10.52 { + SELECT json_valid('" \S "'); +} {0} +do_execsql_test json-10.53 { + SELECT json_valid('" \T "'); +} {0} +do_execsql_test json-10.54 { + SELECT json_valid('" \U "'); +} {0} +do_execsql_test json-10.55 { + SELECT json_valid('" \V "'); +} {0} +do_execsql_test json-10.56 { + SELECT json_valid('" \W "'); +} {0} +do_execsql_test json-10.57 { + SELECT json_valid('" \X "'); +} {0} +do_execsql_test json-10.58 { + SELECT json_valid('" \Y "'); +} {0} +do_execsql_test json-10.59 { + SELECT json_valid('" \Z "'); +} {0} +do_execsql_test json-10.60 { + SELECT json_valid('" \[ "'); +} {0} +do_execsql_test json-10.61 { + SELECT json_valid('" \\ "'); +} {1} +do_execsql_test json-10.62 { + SELECT json_valid('" \] "'); +} {0} +do_execsql_test json-10.63 { + SELECT json_valid('" \^ "'); +} {0} +do_execsql_test json-10.64 { + SELECT json_valid('" \_ "'); +} {0} +do_execsql_test json-10.65 { + SELECT json_valid('" \` "'); +} {0} +do_execsql_test json-10.66 { + SELECT json_valid('" \a "'); +} {0} +do_execsql_test json-10.67 { + SELECT json_valid('" \b "'); +} {1} +do_execsql_test json-10.68 { + SELECT json_valid('" \c "'); +} {0} +do_execsql_test json-10.69 { + SELECT json_valid('" \d "'); +} {0} +do_execsql_test json-10.70 { + SELECT json_valid('" \e "'); +} {0} +do_execsql_test json-10.71 { + SELECT json_valid('" \f "'); +} {1} +do_execsql_test json-10.72 { + SELECT json_valid('" \g "'); +} {0} +do_execsql_test json-10.73 { + SELECT json_valid('" \h "'); +} {0} +do_execsql_test json-10.74 { + SELECT json_valid('" \i "'); +} {0} +do_execsql_test json-10.75 { + SELECT json_valid('" \j "'); +} {0} +do_execsql_test json-10.76 { + SELECT json_valid('" \k "'); +} {0} +do_execsql_test json-10.77 { + SELECT json_valid('" \l "'); +} {0} +do_execsql_test json-10.78 { + SELECT json_valid('" \m "'); +} {0} +do_execsql_test json-10.79 { + SELECT json_valid('" \n "'); +} {1} +do_execsql_test json-10.80 { + SELECT json_valid('" \o "'); +} {0} +do_execsql_test json-10.81 { + SELECT json_valid('" \p "'); +} {0} +do_execsql_test json-10.82 { + SELECT json_valid('" \q "'); +} {0} +do_execsql_test json-10.83 { + SELECT json_valid('" \r "'); +} {1} +do_execsql_test json-10.84 { + SELECT json_valid('" \s "'); +} {0} +do_execsql_test json-10.85 { + SELECT json_valid('" \t "'); +} {1} +do_execsql_test json-10.86.0 { + SELECT json_valid('" \u "'); +} {0} +do_execsql_test json-10.86.1 { + SELECT json_valid('" \ua "'); +} {0} +do_execsql_test json-10.86.2 { + SELECT json_valid('" \uab "'); +} {0} +do_execsql_test json-10.86.3 { + SELECT json_valid('" \uabc "'); +} {0} +do_execsql_test json-10.86.4 { + SELECT json_valid('" \uabcd "'); +} {1} +do_execsql_test json-10.86.5 { + SELECT json_valid('" \uFEDC "'); +} {1} +do_execsql_test json-10.86.6 { + SELECT json_valid('" \u1234 "'); +} {1} +do_execsql_test json-10.87 { + SELECT json_valid('" \v "'); +} {0} +do_execsql_test json-10.88 { + SELECT json_valid('" \w "'); +} {0} +do_execsql_test json-10.89 { + SELECT json_valid('" \x "'); +} {0} +do_execsql_test json-10.90 { + SELECT json_valid('" \y "'); +} {0} +do_execsql_test json-10.91 { + SELECT json_valid('" \z "'); +} {0} +do_execsql_test json-10.92 { + SELECT json_valid('" \{ "'); +} {0} +do_execsql_test json-10.93 { + SELECT json_valid('" \| "'); +} {0} +do_execsql_test json-10.94 { + SELECT json_valid('" \} "'); +} {0} +do_execsql_test json-10.95 { + SELECT json_valid('" \~ "'); +} {0} finish_test diff --git a/test/kvtest.c b/test/kvtest.c new file mode 100644 index 0000000000..877605aced --- /dev/null +++ b/test/kvtest.c @@ -0,0 +1,583 @@ +/* +** 2016-12-28 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file implements "key-value" performance test for SQLite. The +** purpose is to compare the speed of SQLite for accessing large BLOBs +** versus reading those same BLOB values out of individual files in the +** filesystem. +** +** Run "kvtest" with no arguments for on-line help, or see comments below. +** +** HOW TO COMPILE: +** +** (1) Gather this source file and a recent SQLite3 amalgamation with its +** header into the working directory. You should have: +** +** kvtest.c >--- this file +** sqlite3.c \___ SQLite +** sqlite3.h / amlagamation & header +** +** (2) Run you compiler against the two C source code files. +** +** (a) On linux or mac: +** +** OPTS="-DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION" +** gcc -Os -I. $OPTS kvtest.c sqlite3.c -o kvtest +** +** The $OPTS options can be omitted. The $OPTS merely omit +** the need to link against -ldl and -lpthread, or whatever +** the equivalent libraries are called on your system. +** +** (b) Windows with MSVC: +** +** cl -I. kvtest.c sqlite3.c +** +** USAGE: +** +** (1) Create a test database by running "kvtest init" with appropriate +** options. See the help message for available options. +** +** (2) Construct the corresponding pile-of-files database on disk using +** the "kvtest export" command. +** +** (3) Run tests using "kvtest run" against either the SQLite database or +** the pile-of-files database and with appropriate options. +** +** For example: +** +** ./kvtest init x1.db --count 100000 --size 10000 +** mkdir x1 +** ./kvtest export x1.db x1 +** ./kvtest run x1.db --count 10000 --max-id 1000000 +** ./kvtest run x1 --count 10000 --max-id 1000000 +*/ +static const char zHelp[] = +"Usage: kvhelp COMMAND ARGS...\n" +"\n" +" kvhelp init DBFILE --count N --size M --pagesize X\n" +"\n" +" Generate a new test database file named DBFILE containing N\n" +" BLOBs each of size M bytes. The page size of the new database\n" +" file will be X\n" +"\n" +" kvhelp export DBFILE DIRECTORY\n" +"\n" +" Export all the blobs in the kv table of DBFILE into separate\n" +" files in DIRECTORY.\n" +"\n" +" kvhelp run DBFILE [options]\n" +"\n" +" Run a performance test. DBFILE can be either the name of a\n" +" database or a directory containing sample files. Options:\n" +"\n" +" --asc Read blobs in ascending order\n" +" --blob-api Use the BLOB API\n" +" --cache-size N Database cache size\n" +" --count N Read N blobs\n" +" --desc Read blobs in descending order\n" +" --max-id N Maximum blob key to use\n" +" --random Read blobs in a random order\n" +" --start N Start reading with this blob key\n" +; + +/* Reference resources used */ +#include +#include +#include +#include +#include +#include +#include "sqlite3.h" + +#ifndef _WIN32 +# include +#else + /* Provide Windows equivalent for the needed parts of unistd.h */ +# include +# define R_OK 2 +# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +# define access _access +#endif + + +/* +** Show thqe help text and quit. +*/ +static void showHelp(void){ + fprintf(stdout, "%s", zHelp); + exit(1); +} + +/* +** Show an error message an quit. +*/ +static void fatalError(const char *zFormat, ...){ + va_list ap; + fprintf(stdout, "ERROR: "); + va_start(ap, zFormat); + vfprintf(stdout, zFormat, ap); + va_end(ap); + fprintf(stdout, "\n"); + exit(1); +} + +/* +** Check the filesystem object zPath. Determine what it is: +** +** PATH_DIR A directory +** PATH_DB An SQLite database +** PATH_NEXIST Does not exist +** PATH_OTHER Something else +*/ +#define PATH_DIR 1 +#define PATH_DB 2 +#define PATH_NEXIST 0 +#define PATH_OTHER 99 +static int pathType(const char *zPath){ + struct stat x; + int rc; + if( access(zPath,R_OK) ) return PATH_NEXIST; + memset(&x, 0, sizeof(x)); + rc = stat(zPath, &x); + if( rc<0 ) return PATH_OTHER; + if( S_ISDIR(x.st_mode) ) return PATH_DIR; + if( (x.st_size%512)==0 ) return PATH_DB; + return PATH_OTHER; +} + +/* +** Return the size of a file in bytes. Or return -1 if the +** named object is not a regular file or does not exist. +*/ +static sqlite3_int64 fileSize(const char *zPath){ + struct stat x; + int rc; + memset(&x, 0, sizeof(x)); + rc = stat(zPath, &x); + if( rc<0 ) return -1; + if( !S_ISREG(x.st_mode) ) return -1; + return x.st_size; +} + +/* +** A Pseudo-random number generator with a fixed seed. Use this so +** that the same sequence of "random" numbers are generated on each +** run, for repeatability. +*/ +static unsigned int randInt(void){ + static unsigned int x = 0x333a13cd; + static unsigned int y = 0xecb2adea; + x = (x>>1) ^ ((1+~(x&1)) & 0xd0000001); + y = y*1103515245 + 12345; + return x^y; +} + +/* +** Do database initialization. +*/ +static int initMain(int argc, char **argv){ + char *zDb; + int i, rc; + int nCount = 1000; + int sz = 10000; + int pgsz = 4096; + sqlite3 *db; + char *zSql; + char *zErrMsg = 0; + + assert( strcmp(argv[1],"init")==0 ); + assert( argc>=3 ); + zDb = argv[2]; + for(i=3; i65536 || ((pgsz-1)&pgsz)!=0 ){ + fatalError("the --pagesize must be power of 2 between 512 and 65536"); + } + continue; + } + fatalError("unknown option: \"%s\"", argv[i]); + } + rc = sqlite3_open(zDb, &db); + if( rc ){ + fatalError("cannot open database \"%s\": %s", zDb, sqlite3_errmsg(db)); + } + zSql = sqlite3_mprintf( + "DROP TABLE IF EXISTS kv;\n" + "PRAGMA page_size=%d;\n" + "VACUUM;\n" + "BEGIN;\n" + "CREATE TABLE kv(k INTEGER PRIMARY KEY, v BLOB);\n" + "WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<%d)" + " INSERT INTO kv(k,v) SELECT x, randomblob(%d) FROM c;\n" + "COMMIT;\n", + pgsz, nCount, sz + ); + rc = sqlite3_exec(db, zSql, 0, 0, &zErrMsg); + if( rc ) fatalError("database create failed: %s", zErrMsg); + sqlite3_free(zSql); + sqlite3_close(db); + return 0; +} + +/* +** Implementation of the "writefile(X,Y)" SQL function. The argument Y +** is written into file X. The number of bytes written is returned. Or +** NULL is returned if something goes wrong, such as being unable to open +** file X for writing. +*/ +static void writefileFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + FILE *out; + const char *z; + sqlite3_int64 rc; + const char *zFile; + + zFile = (const char*)sqlite3_value_text(argv[0]); + if( zFile==0 ) return; + out = fopen(zFile, "wb"); + if( out==0 ) return; + z = (const char*)sqlite3_value_blob(argv[1]); + if( z==0 ){ + rc = 0; + }else{ + rc = fwrite(z, 1, sqlite3_value_bytes(argv[1]), out); + } + fclose(out); + printf("\r%s ", zFile); fflush(stdout); + sqlite3_result_int64(context, rc); +} + +/* +** Export the kv table to individual files in the filesystem +*/ +static int exportMain(int argc, char **argv){ + char *zDb; + char *zDir; + sqlite3 *db; + char *zSql; + int rc; + char *zErrMsg = 0; + + assert( strcmp(argv[1],"export")==0 ); + assert( argc>=3 ); + zDb = argv[2]; + if( argc!=4 ) fatalError("Usage: kvtest export DATABASE DIRECTORY"); + zDir = argv[3]; + if( pathType(zDir)!=PATH_DIR ){ + fatalError("object \"%s\" is not a directory", zDir); + } + rc = sqlite3_open(zDb, &db); + if( rc ){ + fatalError("cannot open database \"%s\": %s", zDb, sqlite3_errmsg(db)); + } + sqlite3_create_function(db, "writefile", 2, SQLITE_UTF8, 0, + writefileFunc, 0, 0); + zSql = sqlite3_mprintf( + "SELECT writefile(printf('%s/%%06d',k),v) FROM kv;", + zDir + ); + rc = sqlite3_exec(db, zSql, 0, 0, &zErrMsg); + if( rc ) fatalError("database create failed: %s", zErrMsg); + sqlite3_free(zSql); + sqlite3_close(db); + printf("\n"); + return 0; +} + +/* +** Read the content of file zName into memory obtained from sqlite3_malloc64() +** and return a pointer to the buffer. The caller is responsible for freeing +** the memory. +** +** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes +** read. +** +** For convenience, a nul-terminator byte is always appended to the data read +** from the file before the buffer is returned. This byte is not included in +** the final value of (*pnByte), if applicable. +** +** NULL is returned if any error is encountered. The final value of *pnByte +** is undefined in this case. +*/ +static unsigned char *readFile(const char *zName, int *pnByte){ + FILE *in; /* FILE from which to read content of zName */ + sqlite3_int64 nIn; /* Size of zName in bytes */ + size_t nRead; /* Number of bytes actually read */ + unsigned char *pBuf; /* Content read from disk */ + + nIn = fileSize(zName); + if( nIn<0 ) return 0; + in = fopen(zName, "rb"); + if( in==0 ) return 0; + pBuf = sqlite3_malloc64( nIn ); + if( pBuf==0 ) return 0; + nRead = fread(pBuf, nIn, 1, in); + fclose(in); + if( nRead!=1 ){ + sqlite3_free(pBuf); + return 0; + } + if( pnByte ) *pnByte = nIn; + return pBuf; +} + +/* +** Return the current time in milliseconds since the beginning of +** the Julian epoch. +*/ +static sqlite3_int64 timeOfDay(void){ + static sqlite3_vfs *clockVfs = 0; + sqlite3_int64 t; + if( clockVfs==0 ) clockVfs = sqlite3_vfs_find(0); + if( clockVfs->iVersion>=2 && clockVfs->xCurrentTimeInt64!=0 ){ + clockVfs->xCurrentTimeInt64(clockVfs, &t); + }else{ + double r; + clockVfs->xCurrentTime(clockVfs, &r); + t = (sqlite3_int64)(r*86400000.0); + } + return t; +} + +/* Blob access order */ +#define ORDER_ASC 1 +#define ORDER_DESC 2 +#define ORDER_RANDOM 3 + +/* +** Run a performance test +*/ +static int runMain(int argc, char **argv){ + int eType; /* Is zDb a database or a directory? */ + char *zDb; /* Database or directory name */ + int i; /* Loop counter */ + int rc; /* Return code from SQLite calls */ + int nCount = 1000; /* Number of blob fetch operations */ + int nExtra = 0; /* Extra cycles */ + int iKey = 1; /* Next blob key */ + int iMax = 1000; /* Largest allowed key */ + int iPagesize = 0; /* Database page size */ + int iCache = 1000; /* Database cache size in kibibytes */ + int bBlobApi = 0; /* Use the incremental blob I/O API */ + int eOrder = ORDER_ASC; /* Access order */ + sqlite3 *db = 0; /* Database connection */ + sqlite3_stmt *pStmt = 0; /* Prepared statement for SQL access */ + sqlite3_blob *pBlob = 0; /* Handle for incremental Blob I/O */ + sqlite3_int64 tmStart; /* Start time */ + sqlite3_int64 tmElapsed; /* Elapsed time */ + int nData = 0; /* Bytes of data */ + sqlite3_int64 nTotal = 0; /* Total data read */ + unsigned char *pData; /* Content of the blob */ + + + assert( strcmp(argv[1],"run")==0 ); + assert( argc>=3 ); + zDb = argv[2]; + eType = pathType(zDb); + if( eType==PATH_OTHER ) fatalError("unknown object type: \"%s\"", zDb); + if( eType==PATH_NEXIST ) fatalError("object does not exist: \"%s\"", zDb); + for(i=3; iiMax ) iKey = 1; + }else if( eOrder==ORDER_DESC ){ + iKey--; + if( iKey<=0 ) iKey = iMax; + }else{ + iKey = (randInt()%iMax)+1; + } + nTotal += nData; + if( nData==0 ){ nCount++; nExtra++; } + } + if( pStmt ) sqlite3_finalize(pStmt); + if( pBlob ) sqlite3_blob_close(pBlob); + if( db ) sqlite3_close(db); + tmElapsed = timeOfDay() - tmStart; + if( nExtra ){ + printf("%d cycles due to %d misses\n", nCount, nExtra); + } + if( eType==PATH_DB ){ + printf("SQLite version: %s\n", sqlite3_libversion()); + } + printf("--count %d --max-id %d", nCount-nExtra, iMax); + if( eType==PATH_DB ){ + printf(" --cache-size %d", iCache); + } + switch( eOrder ){ + case ORDER_RANDOM: printf(" --random\n"); break; + case ORDER_DESC: printf(" --desc\n"); break; + default: printf(" --asc\n"); break; + } + if( iPagesize ) printf("Database page size: %d\n", iPagesize); + printf("Total elapsed time: %.3f\n", tmElapsed/1000.0); + printf("Microseconds per BLOB read: %.3f\n", tmElapsed*1000.0/nCount); + printf("Content read rate: %.1f MB/s\n", nTotal/(1000.0*tmElapsed)); + return 0; +} + + +int main(int argc, char **argv){ + if( argc<3 ) showHelp(); + if( strcmp(argv[1],"init")==0 ){ + return initMain(argc, argv); + } + if( strcmp(argv[1],"export")==0 ){ + return exportMain(argc, argv); + } + if( strcmp(argv[1],"run")==0 ){ + return runMain(argc, argv); + } + showHelp(); + return 0; +} diff --git a/test/like.test b/test/like.test index fba89e9037..8082b35123 100644 --- a/test/like.test +++ b/test/like.test @@ -980,6 +980,24 @@ do_execsql_test like-13.4 { SELECT char(0x4d) LIKE char(0x6d); } {1} - +# Performance testing for patterns with many wildcards. These LIKE and GLOB +# patterns were quite slow with SQLite 3.15.2 and earlier. +# +do_test like-14.1 { + set x [lindex [time { + db one {SELECT 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz'GLOB'*a*a*a*a*a*a*a*a*y'} + }] 0] + puts -nonewline " ($x ms - want less than 1000) " + expr {$x<1000} +} {1} +ifcapable !icu { + do_test like-14.2 { + set x [lindex [time { + db one {SELECT 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz'LIKE'%a%a%a%a%a%a%a%a%y'} + }] 0] + puts -nonewline " ($x ms - want less than 1000) " + expr {$x<1000} + } {1} +} finish_test diff --git a/test/limit2.test b/test/limit2.test index fb4c89644f..f6eaefb8aa 100644 --- a/test/limit2.test +++ b/test/limit2.test @@ -17,6 +17,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +set testprefix limit2 do_execsql_test limit2-100 { CREATE TABLE t1(a,b); @@ -96,9 +97,57 @@ do_execsql_test limit2-210 { SELECT *, '|' FROM t200 LEFT JOIN t201 ON x=b ORDER BY y LIMIT 3; } {1 1 {} {} | 3 3 {} {} | 4 4 {} {} |} +# Bug in the ORDER BY LIMIT optimization reported on 2016-09-06. +# Ticket https://www.sqlite.org/src/info/559733b09e96 +# +do_execsql_test limit2-300 { + CREATE TABLE t300(a,b,c); + CREATE INDEX t300x ON t300(a,b,c); + INSERT INTO t300 VALUES(0,1,99),(0,1,0),(0,0,0); + SELECT *,'.' FROM t300 WHERE a=0 AND (c=0 OR c=99) ORDER BY c DESC; +} {0 1 99 . 0 0 0 . 0 1 0 .} +do_execsql_test limit2-310 { + SELECT *,'.' FROM t300 WHERE a=0 AND (c=0 OR c=99) ORDER BY c DESC LIMIT 1; +} {0 1 99 .} + +# Make sure the SELECT loop is ordered correctly for the direction of +# the ORDER BY +# +do_execsql_test limit2-400 { + CREATE TABLE t400(a,b); + CREATE INDEX t400_ab ON t400(a,b); + INSERT INTO t400(a,b) VALUES(1,90),(1,40),(2,80),(2,30),(3,70),(3,20); + SELECT *,'x' FROM t400 WHERE a IN (1,2,3) ORDER BY b DESC LIMIT 3; + SELECT *,'y' FROM t400 WHERE a IN (1,2,3) ORDER BY +b DESC LIMIT 3; +} {1 90 x 2 80 x 3 70 x 1 90 y 2 80 y 3 70 y} - - +do_execsql_test 500 { + CREATE TABLE t500(i INTEGER PRIMARY KEY, j); + INSERT INTO t500 VALUES(1, 1); + INSERT INTO t500 VALUES(2, 2); + INSERT INTO t500 VALUES(3, 3); + INSERT INTO t500 VALUES(4, 0); + INSERT INTO t500 VALUES(5, 5); + SELECT j FROM t500 WHERE i IN (1,2,3,4,5) ORDER BY j DESC LIMIT 3; +} {5 3 2} +do_execsql_test 501 { + CREATE TABLE t501(i INTEGER PRIMARY KEY, j); + INSERT INTO t501 VALUES(1, 5); + INSERT INTO t501 VALUES(2, 4); + INSERT INTO t501 VALUES(3, 3); + INSERT INTO t501 VALUES(4, 6); + INSERT INTO t501 VALUES(5, 1); + SELECT j FROM t501 WHERE i IN (1,2,3,4,5) ORDER BY j LIMIT 3; +} {1 3 4} +do_execsql_test 502 { + CREATE TABLE t502(i INT PRIMARY KEY, j); + INSERT INTO t502 VALUES(1, 5); + INSERT INTO t502 VALUES(2, 4); + INSERT INTO t502 VALUES(3, 3); + INSERT INTO t502 VALUES(4, 6); + INSERT INTO t502 VALUES(5, 1); + SELECT j FROM t502 WHERE i IN (1,2,3,4,5) ORDER BY j LIMIT 3; +} {1 3 4} finish_test diff --git a/test/mallocI.test b/test/mallocI.test index 1229455505..ee4a603dee 100644 --- a/test/mallocI.test +++ b/test/mallocI.test @@ -61,4 +61,15 @@ do_malloc_test mallocI-4 -tclprep { } catch { db2 close } +do_faultsim_test mallocI-5 -faults oom* -prep { + catch { db close } + sqlite3 db test.db + sqlite3_db_config_lookaside db 0 0 0 +} -body { + db eval { Select CAST(1 AS blob) } +} -test { + faultsim_test_result {0 1} +} + + finish_test diff --git a/test/misc8.test b/test/misc8.test index 3817cadf94..b639dd1463 100644 --- a/test/misc8.test +++ b/test/misc8.test @@ -108,4 +108,30 @@ do_execsql_test misc8-3.0 { ORDER BY 1; } {0 1 6 0 1 7} +# The SQLITE_DBCONFIG_MAINDBNAME interface +# +db close +forcedelete test.db test2.db +sqlite3 db test.db +do_execsql_test misc8-4.0 { + CREATE TABLE t1(a,b,c); + INSERT INTO t1 VALUES(1,2,3); + ATTACH 'test2.db' AS aux2; + CREATE TABLE aux2.t2(c,d,e); + INSERT INTO t2 VALUES(4,5,6); + SELECT * FROM t1, t2; +} {1 2 3 4 5 6} +do_execsql_test misc8-4.1 { + PRAGMA database_list; +} {/0 main .* 2 aux2/} +dbconfig_maindbname_icecube db +do_execsql_test misc8-4.2 { + SELECT name FROM icecube.sqlite_master; +} {t1} +do_execsql_test misc8-4.3 { + PRAGMA database_list; +} {/0 icecube .* 2 aux2/} + + + finish_test diff --git a/test/multiplex.test b/test/multiplex.test index 5db56f264a..6ea328906e 100644 --- a/test/multiplex.test +++ b/test/multiplex.test @@ -195,15 +195,16 @@ do_test multiplex-2.3.1 { unset -nocomplain ::log -do_test multiplex-2.4.1 { - sqlite3_multiplex_shutdown -} {SQLITE_MISUSE} +#do_test multiplex-2.4.1 { +# sqlite3_multiplex_shutdown +#} {SQLITE_MISUSE} do_test multiplex-2.4.2 { execsql { INSERT INTO t1 VALUES(3, randomblob(1100)) } } {} -do_test multiplex-2.4.3 { - set ::log -} {SQLITE_MISUSE {sqlite3_multiplex_shutdown() called while database connections are still open}} +#do_test multiplex-2.4.3 { +# set ::log +#} {SQLITE_MISUSE {sqlite3_multiplex_shutdown() called while database connections are still open}} + do_test multiplex-2.4.4 { file size [multiplex_name test.x 0] } {7168} do_test multiplex-2.4.5 { db close @@ -445,11 +446,7 @@ multiplex_set db main 32768 16 # Return a list of all currently defined multiplexs. proc multiplex_list {} { - set allq {} - foreach q [sqlite3_multiplex_dump] { - lappend allq [lindex $q 0] - } - return [lsort $allq] + glob -nocomplain test2.db* } do_test multiplex-4.1.6 { @@ -494,7 +491,7 @@ do_test multiplex-4.1.11 { do_test multiplex-4.1.12 { db close multiplex_list -} {} +} {test2.db} #------------------------------------------------------------------------- diff --git a/test/nockpt.test b/test/nockpt.test new file mode 100644 index 0000000000..bd3953f1ee --- /dev/null +++ b/test/nockpt.test @@ -0,0 +1,66 @@ +# 2016 October 31 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The +# focus of this file is testing the SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE +# option. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl +source $testdir/malloc_common.tcl +source $testdir/wal_common.tcl +ifcapable !wal {finish_test ; return } +if {[permutation]=="journaltest" || [permutation]=="inmemory_journal"} { + finish_test + return +} + +set testprefix nockpt + +do_execsql_test 1.0 { + PRAGMA auto_vacuum=OFF; + PRAGMA page_size = 1024; + PRAGMA journal_mode = wal; + CREATE TABLE c1(x, y, z); + INSERT INTO c1 VALUES(1, 2, 3); +} {wal} + +do_test 1.1 { file exists test.db-wal } 1 +do_test 1.2 { file size test.db-wal } [wal_file_size 3 1024] +do_test 1.3 { db close } {} +do_test 1.4 { file exists test.db-wal } 0 + +sqlite3 db test.db +do_execsql_test 1.5 { + INSERT INTO c1 VALUES(4, 5, 6); + INSERT INTO c1 VALUES(7, 8, 9); +} +do_test 1.6 { file exists test.db-wal } 1 +do_test 1.7 { sqlite3_db_config db NO_CKPT_ON_CLOSE 1 } {1} +do_test 1.8 { file size test.db-wal } [wal_file_size 2 1024] +do_test 1.9 { db close } {} +do_test 1.10 { file exists test.db-wal } 1 +do_test 1.11 { file size test.db-wal } [wal_file_size 2 1024] + +sqlite3 db test.db +do_execsql_test 1.12 { + SELECT * FROM c1 +} {1 2 3 4 5 6 7 8 9} + +do_execsql_test 1.13 { PRAGMA main.journal_mode } {wal} +do_test 1.14 { sqlite3_db_config db NO_CKPT_ON_CLOSE 1 } {1} +do_execsql_test 1.14 { PRAGMA main.journal_mode = delete } {delete} +do_test 1.15 { file exists test.db-wal } {0} + + + +finish_test diff --git a/test/ossfuzz.c b/test/ossfuzz.c new file mode 100644 index 0000000000..3a3e852c34 --- /dev/null +++ b/test/ossfuzz.c @@ -0,0 +1,86 @@ +/* +** This module interfaces SQLite to the Google OSS-Fuzz, fuzzer as a service. +** (https://github.com/google/oss-fuzz) +*/ +#include +#include +#include "sqlite3.h" + +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK +/* +** Progress handler callback +*/ +static int progress_handler(void *pReturn) { + return *(int*)pReturn; +} +#endif + +/* +** Callback for sqlite3_exec(). +*/ +static int exec_handler(void *pCnt, int argc, char **argv, char **namev){ + int i; + if( argv ){ + for(i=0; i>= 1; + progressArg = uSelector & 1; uSelector >>= 1; + + /* Bit 2 of the selector enables foreign key constraints */ + sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_FKEY, uSelector&1, &rc); + uSelector >>= 1; + + /* Remaining bits of the selector determine a limit on the number of + ** output rows */ + execCnt = uSelector + 1; + + /* Run the SQL. The sqlite_exec() interface expects a zero-terminated + ** string, so make a copy. */ + zSql = sqlite3_mprintf("%.*s", (int)size, data); + sqlite3_exec(db, zSql, exec_handler, (void*)&execCnt, &zErrMsg); + + /* Cleanup and return */ + sqlite3_free(zErrMsg); + sqlite3_free(zSql); + sqlite3_close(db); + return 0; +} diff --git a/test/ossshell.c b/test/ossshell.c new file mode 100644 index 0000000000..15902a9122 --- /dev/null +++ b/test/ossshell.c @@ -0,0 +1,61 @@ +/* +** This is a test interface for the ossfuzz.c module. The ossfuzz.c module +** is an adaptor for OSS-FUZZ. (https://github.com/google/oss-fuzz) +** +** This program links against ossfuzz.c. It reads files named on the +** command line and passes them one by one into ossfuzz.c. +*/ +#include +#include +#include +#include +#include "sqlite3.h" + +/* +** The entry point in ossfuzz.c that this routine will be calling +*/ +int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size); + + +/* +** Read files named on the command-line and invoke the fuzzer for +** each one. +*/ +int main(int argc, char **argv){ + FILE *in; + int i; + int nErr = 0; + uint8_t *zBuf = 0; + size_t sz; + + for(i=1; i $v2" $gt \ + 2.$tn.le "$v1 <= $v2" $le \ + 2.$tn.ge "$v1 >= $v2" $ge \ + ] { + do_execsql_test $tn2 "SELECT $expr" [list $res] + + set map(0) [list] + set map() [list] + set map(1) [list 1] + do_execsql_test $tn2.where1 "SELECT * FROM one WHERE $expr" $map($res) + + set map(0) [list 1] + set map() [list] + set map(1) [list] + do_execsql_test $tn2.where2 "SELECT * FROM one WHERE NOT $expr" $map($res) + } +} + +do_execsql_test 3.0 { + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES(1, 1); + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(2, 3); + INSERT INTO t1 VALUES(2, 4); + INSERT INTO t1 VALUES(3, 5); + INSERT INTO t1 VALUES(3, 6); +} + +foreach {tn r order} { + 1 "(1, 1)" "ORDER BY y" + 2 "(1, 1)" "ORDER BY x, y" + 3 "(1, 2)" "ORDER BY x, y DESC" + 4 "(3, 6)" "ORDER BY x DESC, y DESC" + 5 "((3, 5))" "ORDER BY x DESC, y" + 6 "(SELECT 3, 5)" "ORDER BY x DESC, y" +} { + do_execsql_test 3.$tn.1 "SELECT $r == (SELECT x,y FROM t1 $order)" 1 + do_execsql_test 3.$tn.2 "SELECT $r == (SELECT * FROM t1 $order)" 1 + + do_execsql_test 3.$tn.3 " + SELECT (SELECT * FROM t1 $order) == (SELECT * FROM t1 $order) + " 1 + do_execsql_test 3.$tn.4 " + SELECT (SELECT 0, 0) == (SELECT * FROM t1 $order) + " 0 +} + +foreach {tn expr res} { + 1 {(2, 2) BETWEEN (2, 2) AND (3, 3)} 1 + 2 {(2, 2) BETWEEN (2, NULL) AND (3, 3)} {} + 3 {(2, 2) BETWEEN (3, NULL) AND (3, 3)} 0 +} { + do_execsql_test 4.$tn "SELECT $expr" [list $res] +} + +foreach {tn expr res} { + 1 {(2, 4) IN (SELECT * FROM t1)} 1 + 2 {(3, 4) IN (SELECT * FROM t1)} 0 + + 3 {(NULL, 4) IN (SELECT * FROM t1)} {} + 4 {(NULL, 0) IN (SELECT * FROM t1)} 0 + + 5 {(NULL, 4) NOT IN (SELECT * FROM t1)} {} + 6 {(NULL, 0) NOT IN (SELECT * FROM t1)} 1 +} { + do_execsql_test 5.$tn "SELECT $expr" [list $res] +} + +do_execsql_test 6.0 { + CREATE TABLE hh(a, b, c); + INSERT INTO hh VALUES('abc', 1, 'i'); + INSERT INTO hh VALUES('ABC', 1, 'ii'); + INSERT INTO hh VALUES('def', 2, 'iii'); + INSERT INTO hh VALUES('DEF', 2, 'iv'); + INSERT INTO hh VALUES('GHI', 3, 'v'); + INSERT INTO hh VALUES('ghi', 3, 'vi'); + + CREATE INDEX hh_ab ON hh(a, b); +} + +do_execsql_test 6.1 { + SELECT c FROM hh WHERE (a, b) = (SELECT 'abc', 1); +} {i} +do_execsql_test 6.2 { + SELECT c FROM hh WHERE (a, b) = (SELECT 'abc' COLLATE nocase, 1); +} {i} +do_execsql_test 6.3 { + SELECT c FROM hh WHERE a = (SELECT 'abc' COLLATE nocase) AND b = (SELECT 1); +} {i} +do_execsql_test 6.4 { + SELECT c FROM hh WHERE +a = (SELECT 'abc' COLLATE nocase) AND b = (SELECT 1); +} {i} +do_execsql_test 6.5 { + SELECT c FROM hh WHERE a = (SELECT 'abc') COLLATE nocase AND b = (SELECT 1); +} {i ii} +do_catchsql_test 6.6 { + SELECT c FROM hh WHERE (a, b) = (SELECT 'abc', 1) COLLATE nocase; +} {1 {row value misused}} +do_catchsql_test 6.7 { + SELECT c FROM hh WHERE (a, b) = 1; +} {1 {row value misused}} +do_execsql_test 6.8 { + SELECT c FROM hh WHERE (a COLLATE nocase, b) = (SELECT 'def', 2); +} {iii iv} +do_execsql_test 6.9 { + SELECT c FROM hh WHERE (a COLLATE nocase, b) IS NOT (SELECT 'def', 2); +} {i ii v vi} +do_execsql_test 6.10 { + SELECT c FROM hh WHERE (b, a) = (SELECT 2, 'def'); +} {iii} + +do_execsql_test 7.0 { + CREATE TABLE xy(i INTEGER PRIMARY KEY, j, k); + INSERT INTO xy VALUES(1, 1, 1); + INSERT INTO xy VALUES(2, 2, 2); + INSERT INTO xy VALUES(3, 3, 3); + INSERT INTO xy VALUES(4, 4, 4); +} + + +foreach {tn sql res eqp} { + 1 "SELECT * FROM xy WHERE (i, j) IS (2, 2)" {2 2 2} + "0 0 0 {SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid=?)}" + + 2 "SELECT * FROM xy WHERE (k, j) < (2, 3)" {1 1 1 2 2 2} + "0 0 0 {SCAN TABLE xy}" + + 3 "SELECT * FROM xy WHERE (i, j) < (2, 3)" {1 1 1 2 2 2} + "0 0 0 {SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid (2, 1)" {2 2 2 3 3 3 4 4 4} + "0 0 0 {SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid>?)}" + + 5 "SELECT * FROM xy WHERE (i, j) > ('2', 1)" {2 2 2 3 3 3 4 4 4} + "0 0 0 {SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid>?)}" + +} { + do_eqp_test 7.$tn.1 $sql $eqp + do_execsql_test 7.$tn.2 $sql $res +} + +do_execsql_test 8.0 { + CREATE TABLE j1(a); +} +do_execsql_test 8.1 { + SELECT * FROM j1 WHERE (select min(a) FROM j1) IN (?, ?, ?) +} + +do_execsql_test 9.0 { + CREATE TABLE t2(a INTEGER PRIMARY KEY, b, c); + INSERT INTO t2 VALUES(1, 1, 1); + INSERT INTO t2 VALUES(2, 2, 2); + INSERT INTO t2 VALUES(3, 3, 3); + INSERT INTO t2 VALUES(4, 4, 4); + INSERT INTO t2 VALUES(5, 5, 5); +} + +foreach {tn q res} { + 1 "(a, b) > (2, 1)" {2 3 4 5} + 2 "(a, b) > (2, 2)" {3 4 5} + 3 "(a, b) < (4, 5)" {1 2 3 4} + 4 "(a, b) < (4, 3)" {1 2 3} +} { + do_execsql_test 9.$tn "SELECT c FROM t2 WHERE $q" $res +} + +do_execsql_test 10.0 { + CREATE TABLE dual(dummy); INSERT INTO dual(dummy) VALUES('X'); + CREATE TABLE t3(a TEXT,b TEXT,c TEXT,d TEXT,e TEXT,f TEXT); + CREATE INDEX t3x ON t3(b,c,d,e,f); + + SELECT a FROM t3 + WHERE (c,d) IN (SELECT 'c','d' FROM dual) + AND (a,b,e) IN (SELECT 'a','b','d' FROM dual); +} + +do_catchsql_test 11.1 { + CREATE TABLE t11(a); + SELECT * FROM t11 WHERE (a,a)<=1; +} {1 {row value misused}} +do_catchsql_test 11.2 { + SELECT * FROM t11 WHERE (a,a)<1; +} {1 {row value misused}} +do_catchsql_test 11.3 { + SELECT * FROM t11 WHERE (a,a)>=1; +} {1 {row value misused}} +do_catchsql_test 11.4 { + SELECT * FROM t11 WHERE (a,a)>1; +} {1 {row value misused}} +do_catchsql_test 11.5 { + SELECT * FROM t11 WHERE (a,a)==1; +} {1 {row value misused}} +do_catchsql_test 11.6 { + SELECT * FROM t11 WHERE (a,a)<>1; +} {1 {row value misused}} +do_catchsql_test 11.7 { + SELECT * FROM t11 WHERE (a,a) IS 1; +} {1 {row value misused}} +do_catchsql_test 11.8 { + SELECT * FROM t11 WHERE (a,a) IS NOT 1; +} {1 {row value misused}} + +# 2016-10-27: https://www.sqlite.org/src/tktview/fef4bb4bd9185ec8f +# Incorrect result from a LEFT JOIN with a row-value constraint +# +do_execsql_test 12.1 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a,b); INSERT INTO t1 VALUES(1,2); + DROP TABLE IF EXISTS t2; + CREATE TABLE t2(x,y); INSERT INTO t2 VALUES(3,4); + SELECT *,'x' FROM t1 LEFT JOIN t2 ON (a,b)=(x,y); +} {1 2 {} {} x} + + +foreach {tn sql} { + 0 "SELECT (1,2) AS x WHERE x=3" + 1 "SELECT (1,2) BETWEEN 1 AND 2" + 2 "SELECT 1 BETWEEN (1,2) AND 2" + 3 "SELECT 2 BETWEEN 1 AND (1,2)" + 4 "SELECT (1,2) FROM (SELECT 1) ORDER BY 1" + 5 "SELECT (1,2) FROM (SELECT 1) GROUP BY 1" +} { + do_catchsql_test 13.$tn $sql {1 {row value misused}} +} + +do_execsql_test 14.0 { + CREATE TABLE t12(x); + INSERT INTO t12 VALUES(2), (4); +} +do_execsql_test 14.1 "SELECT 1 WHERE (2,2) BETWEEN (1,1) AND (3,3)" 1 +do_execsql_test 14.2 "SELECT CASE (2,2) WHEN (1, 1) THEN 2 ELSE 1 END" 1 +do_execsql_test 14.3 "SELECT CASE (SELECT 2,2) WHEN (1, 1) THEN 2 ELSE 1 END" 1 +do_execsql_test 14.4 "SELECT 1 WHERE (SELECT 2,2) BETWEEN (1,1) AND (3,3)" 1 +do_execsql_test 14.5 "SELECT 1 FROM t12 WHERE (x,1) BETWEEN (1,1) AND (3,3)" 1 +do_execsql_test 14.6 { + SELECT 1 FROM t12 WHERE (1,x) BETWEEN (1,1) AND (3,3) +} {1 1} + +#------------------------------------------------------------------------- +# Test that errors are not concealed by the SELECT flattening or +# WHERE-clause push-down optimizations. +do_execsql_test 14.1 { + CREATE TABLE x1(a PRIMARY KEY, b); + CREATE TABLE x2(a INTEGER PRIMARY KEY, b); +} + +foreach {tn n sql} { + 1 0 "SELECT * FROM (SELECT (1, 1) AS c FROM x1) WHERE c=1" + 2 2 "SELECT * FROM (SELECT 1 AS x, (SELECT 8,9) AS y) WHERE y<1" + 3 3 "SELECT * FROM (SELECT 1 AS x, (SELECT 8,9,10) AS y) WHERE y<1" + 4 0 "SELECT * FROM (SELECT (a, b) AS c FROM x1), x2 WHERE c=a" + 5 0 "SELECT * FROM (SELECT a AS c, (1, 2, 3) FROM x1), x2 WHERE c=a" + 6 0 "SELECT * FROM (SELECT 1 AS c, (1, 2, 3) FROM x1) WHERE c=1" +} { + if {$n==0} { + set err "row value misused" + } else { + set err "sub-select returns $n columns - expected 1" + } + do_catchsql_test 14.2.$tn $sql [list 1 $err] +} + +#-------------------------------------------------------------------------- +# Test for vector size mismatches concealed by unexpanded subqueries. +# +do_catchsql_test 15.1 { + DETACH (SELECT * FROM (SELECT 1,2))<3; +} {1 {row value misused}} +do_catchsql_test 15.2 { + UPDATE x1 SET a=(SELECT * FROM (SELECT b,2))<3; +} {1 {row value misused}} +do_catchsql_test 15.3 { + UPDATE x1 SET a=NULL WHERE a<(SELECT * FROM (SELECT b,2)); +} {1 {sub-select returns 2 columns - expected 1}} +do_catchsql_test 15.4 { + DELETE FROM x1 WHERE a<(SELECT * FROM (SELECT b,2)); +} {1 {sub-select returns 2 columns - expected 1}} +do_catchsql_test 15.5 { + INSERT INTO x1(a,b) VALUES(1,(SELECT * FROM (SELECT 1,2))<3); +} {1 {row value misused}} + +#------------------------------------------------------------------------- +# Row-values used in UPDATE statements within TRIGGERs +# +# Ticket https://www.sqlite.org/src/info/8c9458e703666e1a +# +do_execsql_test 16.1 { + CREATE TABLE t16a(a,b,c); + INSERT INTO t16a VALUES(1,2,3); + CREATE TABLE t16b(x); + INSERT INTO t16b(x) VALUES(1); + CREATE TRIGGER t16r AFTER UPDATE ON t16b BEGIN + UPDATE t16a SET (a,b,c)=(SELECT new.x,new.x+1,new.x+2); + END; + UPDATE t16b SET x=7; + SELECT * FROM t16a; +} {7 8 9} +do_execsql_test 16.2 { + UPDATE t16b SET x=97; + SELECT * FROM t16a; +} {97 98 99} + +do_execsql_test 16.3 { + CREATE TABLE t16c(a, b, c, d, e); + INSERT INTO t16c VALUES(1, 'a', 'b', 'c', 'd'); + CREATE TRIGGER t16c1 AFTER INSERT ON t16c BEGIN + UPDATE t16c SET (c, d) = (SELECT 'A', 'B'), (e, b) = (SELECT 'C', 'D') + WHERE a = new.a-1; + END; + + SELECT * FROM t16c; +} {1 a b c d} + +do_execsql_test 16.4 { + INSERT INTO t16c VALUES(2, 'w', 'x', 'y', 'z'); + SELECT * FROM t16c; +} { + 1 D A B C + 2 w x y z +} + +do_execsql_test 16.5 { + DROP TRIGGER t16c1; + PRAGMA recursive_triggers = 1; + INSERT INTO t16c VALUES(3, 'i', 'ii', 'iii', 'iv'); + CREATE TRIGGER t16c1 AFTER UPDATE ON t16c WHEN new.a>1 BEGIN + UPDATE t16c SET (e, d) = ( + SELECT b, c FROM t16c WHERE a = new.a-1 + ), (c, b) = ( + SELECT d, e FROM t16c WHERE a = new.a-1 + ) WHERE a = new.a-1; + END; + + UPDATE t16c SET a=a WHERE a=3; + SELECT * FROM t16c; +} { + 1 C B A D + 2 z y x w + 3 i ii iii iv +} + +finish_test diff --git a/test/rowvalue2.test b/test/rowvalue2.test new file mode 100644 index 0000000000..1502e31a54 --- /dev/null +++ b/test/rowvalue2.test @@ -0,0 +1,279 @@ +# 2016 June 17 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The +# focus of this file is testing the SELECT statement. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix rowvalue2 + +do_execsql_test 1.0 { + CREATE TABLE t1(a, b, c); + INSERT INTO t1 VALUES(0, 0, 0); + INSERT INTO t1 VALUES(0, 1, 1); + INSERT INTO t1 VALUES(1, 0, 2); + INSERT INTO t1 VALUES(1, 1, 3); + + CREATE INDEX i1 ON t1(a, b); +} + +do_execsql_test 1.1.1 { SELECT c FROM t1 WHERE (a, b) >= (1, 0) } {2 3} +do_execsql_test 1.1.2 { SELECT c FROM t1 WHERE (a, b) > (1, 0) } {3} + +#------------------------------------------------------------------------- + +do_execsql_test 2.0.1 { + CREATE TABLE t2(a INTEGER, b INTEGER, c INTEGER, d INTEGER); + CREATE INDEX i2 ON t2(a, b, c); +} +do_test 2.0.2 { + foreach a {0 1 2 3} { + foreach b {0 1 2 3} { + foreach c {0 1 2 3} { + execsql { INSERT INTO t2 VALUES($a, $b, $c, $c + $b*4 + $a*16); } + }}} +} {} + +do_execsql_test 2.1 { + SELECT d FROM t2 WHERE (a, b) > (2, 2); +} [db eval { SELECT d FROM t2 WHERE a>2 OR (a=2 AND b>2) }] + +do_execsql_test 2.2 { + SELECT d FROM t2 WHERE (a, b) >= (2, 2); +} [db eval { SELECT d FROM t2 WHERE a>2 OR (a=2 AND b>=2) }] + +do_execsql_test 2.3 { + SELECT d FROM t2 WHERE a=1 AND (b, c) >= (1, 2); +} [db eval { SELECT d FROM t2 WHERE +a=1 AND (b>1 OR (b==1 AND c>=2)) }] + +do_execsql_test 2.4 { + SELECT d FROM t2 WHERE a=1 AND (b, c) > (1, 2); +} [db eval { SELECT d FROM t2 WHERE +a=1 AND (b>1 OR (b==1 AND c>2)) }] + +#------------------------------------------------------------------------- + +set words { +airfare airfield airfields airflow airfoil +airfoils airframe airframes airily airing +airings airless airlift airlifts airline +airliner airlines airlock airlocks airmail +airmails airman airmen airplane airplanes + +arraignment arraignments arraigns arrange arranged +arrangement arrangements arranger arrangers arranges +arranging arrant array arrayed arrays +arrears arrest arrested arrester arresters +arresting arrestingly arrestor arrestors arrests + +edifices edit edited editing edition +editions editor editorial editorially editorials +editors edits educable educate educated +educates educating education educational educationally +educations educator educators eel eelgrass +} + +do_test 3.0 { + execsql { CREATE TABLE t3(a, b, c, w); } + foreach w $words { + set a [string range $w 0 2] + set b [string range $w 3 5] + set c [string range $w 6 end] + execsql { INSERT INTO t3 VALUES($a, $b, $c, $w) } + } +} {} + + +foreach {tn idx} { + IDX1 {} + IDX2 { CREATE INDEX i3 ON t3(a, b, c); } + IDX3 { CREATE INDEX i3 ON t3(a, b); } + IDX4 { CREATE INDEX i3 ON t3(a); } +} { + execsql { DROP INDEX IF EXISTS i3 } + execsql $idx + + foreach w $words { + set a [string range $w 0 2] + set b [string range $w 3 5] + set c [string range $w 6 end] + + foreach op [list > >= < <= == IS] { + do_execsql_test 3.1.$tn.$w.$op [subst -novar { + SELECT rowid FROM t3 WHERE (a, b, c) [set op] ($a, $b, $c) + ORDER BY +rowid + }] [db eval [subst -novar { + SELECT rowid FROM t3 WHERE w [set op] $w ORDER BY +rowid + }]] + + do_execsql_test 3.1.$tn.$w.$op.subselect [subst -novar { + SELECT rowid FROM t3 WHERE (a, b, c) [set op] ( + SELECT a, b, c FROM t3 WHERE w = $w + ) + ORDER BY +rowid + }] [db eval [subst -novar { + SELECT rowid FROM t3 WHERE w [set op] $w ORDER BY +rowid + }]] + } + + } +} + +#------------------------------------------------------------------------- +# + +do_execsql_test 4.0 { + CREATE TABLE t4(a, b, c); + INSERT INTO t4 VALUES(NULL, NULL, NULL); + INSERT INTO t4 VALUES(NULL, NULL, 0); + INSERT INTO t4 VALUES(NULL, NULL, 1); + INSERT INTO t4 VALUES(NULL, 0, NULL); + INSERT INTO t4 VALUES(NULL, 0, 0); + INSERT INTO t4 VALUES(NULL, 0, 1); + INSERT INTO t4 VALUES(NULL, 1, NULL); + INSERT INTO t4 VALUES(NULL, 1, 0); + INSERT INTO t4 VALUES(NULL, 1, 1); + + INSERT INTO t4 VALUES( 0, NULL, NULL); + INSERT INTO t4 VALUES( 0, NULL, 0); + INSERT INTO t4 VALUES( 0, NULL, 1); + INSERT INTO t4 VALUES( 0, 0, NULL); + INSERT INTO t4 VALUES( 0, 0, 0); + INSERT INTO t4 VALUES( 0, 0, 1); + INSERT INTO t4 VALUES( 0, 1, NULL); + INSERT INTO t4 VALUES( 0, 1, 0); + INSERT INTO t4 VALUES( 0, 1, 1); + + INSERT INTO t4 VALUES( 1, NULL, NULL); + INSERT INTO t4 VALUES( 1, NULL, 0); + INSERT INTO t4 VALUES( 1, NULL, 1); + INSERT INTO t4 VALUES( 1, 0, NULL); + INSERT INTO t4 VALUES( 1, 0, 0); + INSERT INTO t4 VALUES( 1, 0, 1); + INSERT INTO t4 VALUES( 1, 1, NULL); + INSERT INTO t4 VALUES( 1, 1, 0); + INSERT INTO t4 VALUES( 1, 1, 1); +} + +proc make_expr1 {cList vList op} { + return "([join $cList ,]) $op ([join $vList ,])" +} + +proc make_expr3 {cList vList op} { + set n [llength $cList] + + set aList [list] + foreach c [lrange $cList 0 end-1] v [lrange $vList 0 end-1] { + lappend aList "$c == $v" + } + lappend aList "[lindex $cList end] $op [lindex $vList end]" + + return "([join $aList { AND }])" +} + +proc make_expr2 {cList vList op} { + set ret "" + + switch -- $op { + == - IS { + set aList [list] + foreach c $cList v $vList { lappend aList "($c $op $v)" } + set ret [join $aList " AND "] + } + + < - > { + set oList [list] + for {set i 0} {$i < [llength $cList]} {incr i} { + lappend oList [make_expr3 [lrange $cList 0 $i] [lrange $vList 0 $i] $op] + } + set ret [join $oList " OR "] + } + + <= - >= { + set o2 [string range $op 0 0] + set oList [list] + for {set i 0} {$i < [llength $cList]-1} {incr i} { + lappend oList [make_expr3 [lrange $cList 0 $i] [lrange $vList 0 $i] $o2] + } + lappend oList [make_expr3 $cList $vList $op] + set ret [join $oList " OR "] + } + + + default { + error "Unknown op: $op" + } + } + + set ret +} + +foreach {tn idx} { + IDX1 {} + IDX2 { CREATE INDEX i4 ON t4(a, b, c); } + IDX3 { CREATE INDEX i4 ON t4(a, b); } + IDX4 { CREATE INDEX i4 ON t4(a); } +} { + execsql { DROP INDEX IF EXISTS i4 } + execsql $idx + + foreach {tn2 vector} { + 1 {0 0 0} + 2 {1 1 1} + 3 {0 0 NULL} + 4 {0 NULL 0} + 5 {NULL 0 0} + 6 {1 1 NULL} + 7 {1 NULL 1} + 8 {NULL 1 1} + } { + foreach op { IS == < <= > >= } { + set e1 [make_expr1 {a b c} $vector $op] + set e2 [make_expr2 {a b c} $vector $op] + + do_execsql_test 4.$tn.$tn2.$op \ + "SELECT rowid FROM t4 WHERE $e2 ORDER BY +rowid" [ + db eval "SELECT rowid FROM t4 WHERE $e1 ORDER BY +rowid" + ] + } + } +} + +do_execsql_test 5.0 { + CREATE TABLE r1(a TEXT, iB TEXT); + CREATE TABLE r2(x TEXT, zY INTEGER); + CREATE INDEX r1ab ON r1(a, iB); + + INSERT INTO r1 VALUES(35, 35); + INSERT INTO r2 VALUES(35, 36); + INSERT INTO r2 VALUES(35, 4); + INSERT INTO r2 VALUES(35, 35); +} {} + +foreach {tn lhs rhs} { + 1 {x +zY} {a iB} + 2 {x zY} {a iB} + 3 {x zY} {a +iB} + 4 {+x zY} {a iB} + 5 {x zY} {+a iB} +} { + foreach op { IS == < <= > >= } { + set e1 [make_expr1 $lhs $rhs $op] + set e2 [make_expr2 $lhs $rhs $op] + do_execsql_test 5.$tn.$op \ + "SELECT * FROM r1, r2 WHERE $e2 ORDER BY iB" [db eval \ + "SELECT * FROM r1, r2 WHERE $e1 ORDER BY iB" + ] + } +} + + +finish_test diff --git a/test/rowvalue3.test b/test/rowvalue3.test new file mode 100644 index 0000000000..988eea9008 --- /dev/null +++ b/test/rowvalue3.test @@ -0,0 +1,220 @@ +# 2016 June 17 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The +# focus of this file is testing "(...) IN (SELECT ...)" expressions +# where the SELECT statement returns more than one column. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix rowvalue3 + +do_execsql_test 1.0 { + CREATE TABLE t1(a, b, c); + CREATE INDEX i1 ON t1(a, b); + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(4, 5, 6); + INSERT INTO t1 VALUES(7, 8, 9); +} + +foreach {tn sql res} { + 1 "SELECT 1 WHERE (4, 5) IN (SELECT a, b FROM t1)" 1 + 2 "SELECT 1 WHERE (5, 5) IN (SELECT a, b FROM t1)" {} + 3 "SELECT 1 WHERE (5, 4) IN (SELECT a, b FROM t1)" {} + 4 "SELECT 1 WHERE (5, 4) IN (SELECT b, a FROM t1)" 1 + 5 "SELECT 1 WHERE (SELECT a, b FROM t1 WHERE c=6) IN (SELECT a, b FROM t1)" 1 + 6 "SELECT (5, 4) IN (SELECT a, b FROM t1)" 0 + 7 "SELECT 1 WHERE (5, 4) IN (SELECT +b, +a FROM t1)" 1 + 8 "SELECT (5, 4) IN (SELECT +b, +a FROM t1)" 1 + 9 "SELECT (1, 2) IN (SELECT rowid, b FROM t1)" 1 + 10 "SELECT 1 WHERE (1, 2) IN (SELECT rowid, b FROM t1)" 1 + 11 "SELECT 1 WHERE (1, NULL) IN (SELECT rowid, b FROM t1)" {} + 12 "SELECT 1 FROM t1 WHERE (a, b) = (SELECT +a, +b FROM t1)" {1} +} { + do_execsql_test 1.$tn $sql $res +} + +#------------------------------------------------------------------------- + +do_execsql_test 2.0 { + CREATE TABLE z1(x, y, z); + CREATE TABLE kk(a, b); + + INSERT INTO z1 VALUES('a', 'b', 'c'); + INSERT INTO z1 VALUES('d', 'e', 'f'); + INSERT INTO z1 VALUES('g', 'h', 'i'); + + -- INSERT INTO kk VALUES('y', 'y'); + INSERT INTO kk VALUES('d', 'e'); + -- INSERT INTO kk VALUES('x', 'x'); + +} + +foreach {tn idx} { + 1 { } + 2 { CREATE INDEX z1idx ON z1(x, y) } + 3 { CREATE UNIQUE INDEX z1idx ON z1(x, y) } + 4 { CREATE INDEX z1idx ON kk(a, b) } +} { + execsql "DROP INDEX IF EXISTS z1idx" + execsql $idx + + do_execsql_test 2.$tn.1 { + SELECT * FROM z1 WHERE x IN (SELECT a FROM kk) + } {d e f} + + do_execsql_test 2.$tn.2 { + SELECT * FROM z1 WHERE (x,y) IN (SELECT a, b FROM kk) + } {d e f} + + do_execsql_test 2.$tn.3 { + SELECT * FROM z1 WHERE (x, +y) IN (SELECT a, b FROM kk) + } {d e f} + + do_execsql_test 2.$tn.4 { + SELECT * FROM z1 WHERE (x, +y) IN (SELECT a, b||'x' FROM kk) + } {} + + do_execsql_test 2.$tn.5 { + SELECT * FROM z1 WHERE (+x, y) IN (SELECT a, b FROM kk) + } {d e f} +} + +#------------------------------------------------------------------------- +# + +do_execsql_test 3.0 { + CREATE TABLE c1(a, b, c, d); + INSERT INTO c1(rowid, a, b) VALUES(1, NULL, 1); + INSERT INTO c1(rowid, a, b) VALUES(2, 2, NULL); + INSERT INTO c1(rowid, a, b) VALUES(3, 2, 2); + INSERT INTO c1(rowid, a, b) VALUES(4, 3, 3); + + INSERT INTO c1(rowid, a, b, c, d) VALUES(101, 'a', 'b', 1, 1); + INSERT INTO c1(rowid, a, b, c, d) VALUES(102, 'a', 'b', 1, 2); + INSERT INTO c1(rowid, a, b, c, d) VALUES(103, 'a', 'b', 1, 3); + INSERT INTO c1(rowid, a, b, c, d) VALUES(104, 'a', 'b', 2, 1); + INSERT INTO c1(rowid, a, b, c, d) VALUES(105, 'a', 'b', 2, 2); + INSERT INTO c1(rowid, a, b, c, d) VALUES(106, 'a', 'b', 2, 3); + INSERT INTO c1(rowid, a, b, c, d) VALUES(107, 'a', 'b', 3, 1); + INSERT INTO c1(rowid, a, b, c, d) VALUES(108, 'a', 'b', 3, 2); + INSERT INTO c1(rowid, a, b, c, d) VALUES(109, 'a', 'b', 3, 3); +} + + +foreach {tn idx} { + 1 { } + 2 { CREATE INDEX c1ab ON c1(a, b); } + 3 { CREATE INDEX c1ba ON c1(b, a); } + + 4 { CREATE INDEX c1cd ON c1(c, d); } + 5 { CREATE INDEX c1dc ON c1(d, c); } +} { + drop_all_indexes + + foreach {tn2 sql res} { + 1 "SELECT (1, 2) IN (SELECT a, b FROM c1)" {0} + 2 "SELECT (1, 1) IN (SELECT a, b FROM c1)" {{}} + 3 "SELECT (2, 1) IN (SELECT a, b FROM c1)" {{}} + 4 "SELECT (2, 2) IN (SELECT a, b FROM c1)" {1} + 5 "SELECT c, d FROM c1 WHERE (c, d) IN (SELECT d, c FROM c1)" + { 1 1 1 2 1 3 2 1 2 2 2 3 3 1 3 2 3 3 } + + 6 "SELECT c, d FROM c1 WHERE (c,d) IN (SELECT d, c FROM c1) ORDER BY c DESC" + { 3 1 3 2 3 3 2 1 2 2 2 3 1 1 1 2 1 3 } + + 7 { + SELECT c, d FROM c1 WHERE (c,d) IN (SELECT d, c FROM c1) + ORDER BY c DESC, d ASC + } { 3 1 3 2 3 3 2 1 2 2 2 3 1 1 1 2 1 3 } + + 8 { + SELECT c, d FROM c1 WHERE (c,d) IN (SELECT d, c FROM c1) + ORDER BY c ASC, d DESC + } { 1 3 1 2 1 1 2 3 2 2 2 1 3 3 3 2 3 1 } + + 9 { + SELECT c, d FROM c1 WHERE (c,d) IN (SELECT d, c FROM c1) + ORDER BY c ASC, d ASC + } { 1 1 1 2 1 3 2 1 2 2 2 3 3 1 3 2 3 3 } + 10 { + SELECT c, d FROM c1 WHERE (c,d) IN (SELECT d, c FROM c1) + ORDER BY c DESC, d DESC + } { 3 3 3 2 3 1 2 3 2 2 2 1 1 3 1 2 1 1 } + + } { + do_execsql_test 3.$tn.$tn2 $sql $res + } +} + +#------------------------------------------------------------------------- + +do_execsql_test 4.0 { + CREATE TABLE hh(a, b, c); + + INSERT INTO hh VALUES('a', 'a', 1); + INSERT INTO hh VALUES('a', 'b', 2); + INSERT INTO hh VALUES('b', 'a', 3); + INSERT INTO hh VALUES('b', 'b', 4); + + CREATE TABLE k1(x, y); + INSERT INTO k1 VALUES('a', 'a'); + INSERT INTO k1 VALUES('b', 'b'); + INSERT INTO k1 VALUES('a', 'b'); + INSERT INTO k1 VALUES('b', 'a'); +} + +foreach {tn idx} { + 1 { } + 2 { CREATE INDEX h1 ON hh(a, b); } + 3 { CREATE UNIQUE INDEX k1idx ON k1(x, y) } + 4 { CREATE UNIQUE INDEX k1idx ON k1(x, y DESC) } + 5 { + CREATE INDEX h1 ON hh(a, b); + CREATE UNIQUE INDEX k1idx ON k1(x, y); + } + 6 { + CREATE INDEX h1 ON hh(a, b); + CREATE UNIQUE INDEX k1idx ON k1(x, y DESC); + } +} { + drop_all_indexes + execsql $idx + foreach {tn2 orderby res} { + 1 "a ASC, b ASC" {1 2 3 4} + 2 "a ASC, b DESC" {2 1 4 3} + 3 "a DESC, b ASC" {3 4 1 2} + 4 "a DESC, b DESC" {4 3 2 1} + } { + do_execsql_test 4.$tn.$tn2 " + SELECT c FROM hh WHERE (a, b) in (SELECT x, y FROM k1) ORDER BY $orderby + " $res + } +} + +#------------------------------------------------------------------------- + +# 2016-11-17. Query flattening in a vector SELECT on the RHS of an IN +# operator. Ticket https://www.sqlite.org/src/info/da7841375186386c +# +do_execsql_test 5.0 { + DROP TABLE IF EXISTS t1; + DROP TABLE IF EXISTS t2; + CREATE TABLE T1(a TEXT); + INSERT INTO T1(a) VALUES ('aaa'); + CREATE TABLE T2(a TEXT PRIMARY KEY,n INT); + INSERT INTO T2(a, n) VALUES('aaa',0); + SELECT * FROM T2 + WHERE (a,n) IN (SELECT T1.a, V.n FROM T1, (SELECT * FROM (SELECT 0 n)) V); +} {aaa 0} + + +finish_test diff --git a/test/rowvalue4.test b/test/rowvalue4.test new file mode 100644 index 0000000000..2e6a28c230 --- /dev/null +++ b/test/rowvalue4.test @@ -0,0 +1,313 @@ +# 2016 July 29 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The +# focus of this file is syntax errors involving row-value constructors +# and sub-selects that return multiple arguments. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix rowvalue4 + +#------------------------------------------------------------------------- +# Test some error conditions: +# +# * row values used where they are not supported, +# * row values or sub-selects that contain/return the wrong number +# of elements. +# +do_execsql_test 1.0 { + CREATE TABLE t1(a, b, c); + CREATE INDEX t1bac ON t1(b, a, c); +} + +foreach {tn e} { + 1 "(1, 2, 3)" + 2 "1 + (1, 2)" + 3 "(1,2,3) == (1, 2)" +} { + do_catchsql_test 1.$tn "SELECT $e" {1 {row value misused}} +} + +foreach {tn s error} { + 1 "SELECT * FROM t1 WHERE a = (1, 2)" {row value misused} + 2 "SELECT * FROM t1 WHERE b = (1, 2)" {row value misused} + 3 "SELECT * FROM t1 WHERE NOT (b = (1, 2))" {row value misused} + 4 "SELECT * FROM t1 LIMIT (1, 2)" {row value misused} + 5 "SELECT (a, b) IN (SELECT * FROM t1) FROM t1" + {sub-select returns 3 columns - expected 2} + + 6 "SELECT * FROM t1 WHERE (a, b) IN (SELECT * FROM t1)" + {sub-select returns 3 columns - expected 2} + 7 "SELECT * FROM t1 WHERE (c, c) <= 1" {row value misused} + 8 "SELECT * FROM t1 WHERE (b, b) <= 1" {row value misused} +} { + do_catchsql_test 2.$tn "$s" [list 1 $error] +} + +#------------------------------------------------------------------------- +do_execsql_test 2.0 { + CREATE TABLE t2(a, b, c, d); + INSERT INTO t2 VALUES(1, 1, 1, 1); + INSERT INTO t2 VALUES(1, 1, 2, 2); + INSERT INTO t2 VALUES(1, 1, 3, 3); + INSERT INTO t2 VALUES(1, 2, 1, 4); + INSERT INTO t2 VALUES(1, 2, 2, 5); + INSERT INTO t2 VALUES(1, 2, 3, 6); + INSERT INTO t2 VALUES(1, 3, 1, 7); + INSERT INTO t2 VALUES(1, 3, 2, 8); + INSERT INTO t2 VALUES(1, 3, 3, 9); + + INSERT INTO t2 VALUES(2, 1, 1, 10); + INSERT INTO t2 VALUES(2, 1, 2, 11); + INSERT INTO t2 VALUES(2, 1, 3, 12); + INSERT INTO t2 VALUES(2, 2, 1, 13); + INSERT INTO t2 VALUES(2, 2, 2, 14); + INSERT INTO t2 VALUES(2, 2, 3, 15); + INSERT INTO t2 VALUES(2, 3, 1, 16); + INSERT INTO t2 VALUES(2, 3, 2, 17); + INSERT INTO t2 VALUES(2, 3, 3, 18); + + INSERT INTO t2 VALUES(3, 1, 1, 19); + INSERT INTO t2 VALUES(3, 1, 2, 20); + INSERT INTO t2 VALUES(3, 1, 3, 21); + INSERT INTO t2 VALUES(3, 2, 1, 22); + INSERT INTO t2 VALUES(3, 2, 2, 23); + INSERT INTO t2 VALUES(3, 2, 3, 24); + INSERT INTO t2 VALUES(3, 3, 1, 25); + INSERT INTO t2 VALUES(3, 3, 2, 26); + INSERT INTO t2 VALUES(3, 3, 3, 27); +} + +foreach {nm idx} { + idx1 {} + idx2 { CREATE INDEX t2abc ON t2(a, b, c); } + idx3 { CREATE INDEX t2abc ON t2(a, b DESC, c); } + idx4 { CREATE INDEX t2abc ON t2(a DESC, b DESC, c DESC); } + idx5 { CREATE INDEX t2abc ON t2(a ASC, b ASC, c ASC); } + idx6 { CREATE INDEX t2abc ON t2(a DESC, b, c); } + idx7 { CREATE INDEX t2abc ON t2(a DESC, b DESC) } + idx8 { CREATE INDEX t2abc ON t2(c, b, a); } + idx9 { CREATE INDEX t2d ON t2(d); } + idx10 { CREATE INDEX t2abc ON t2(a DESC, b, c DESC); } +} { + drop_all_indexes + execsql $idx + + foreach {tn where res} { + 1 "(a, b, c) < (2, 2, 2)" {1 2 3 4 5 6 7 8 9 10 11 12 13} + 2 "(a, b, c) <= (2, 2, 2)" {1 2 3 4 5 6 7 8 9 10 11 12 13 14} + 3 "(a, b, c) > (2, 2, 2)" {15 16 17 18 19 20 21 22 23 24 25 26 27} + 4 "(a, b, c) >= (2, 2, 2)" {14 15 16 17 18 19 20 21 22 23 24 25 26 27} + 5 "(a, b, c) >= (2, 2, NULL)" {16 17 18 19 20 21 22 23 24 25 26 27} + 6 "(a, b, c) <= (2, 2, NULL)" {1 2 3 4 5 6 7 8 9 10 11 12} + 7 "(a, b, c) >= (2, NULL, NULL)" {19 20 21 22 23 24 25 26 27} + 8 "(a, b, c) <= (2, NULL, NULL)" {1 2 3 4 5 6 7 8 9} + + 9 "(a, b, c) < (SELECT a, b, c FROM t2 WHERE d=14)" + {1 2 3 4 5 6 7 8 9 10 11 12 13} + + 10 "(a, b, c) = (SELECT a, b, c FROM t2 WHERE d=14)" 14 + + 11 "a = 2 AND (b, c) > (2, 2)" {15 16 17 18} + 12 "a = 2 AND (b, c) < (3, 3) AND (b, c) > (1, 1)" {11 12 13 14 15 16 17} + } { + set result [db eval "SELECT d FROM t2 WHERE $where"] + do_test 2.1.$nm.$tn { lsort -integer $result } $res + } + + foreach {tn e res} { + 1 "(2, 1) IN (SELECT a, b FROM t2)" 1 + 2 "(2, 1) IN (SELECT a, b FROM t2 ORDER BY d)" 1 + 3 "(2, 1) IN (SELECT a, b FROM t2 ORDER BY d LIMIT 9)" 0 + 4 "(2, 1) IN (SELECT a, b FROM t2 ORDER BY d LIMIT 10)" 1 + + 5 "(3, 3) = (SELECT a, b FROM t2 ORDER BY d DESC LIMIT 1)" 1 + 6 "(3, 3) = (SELECT a, b FROM t2 ORDER BY d ASC LIMIT 1)" 0 + 7 "(1, NULL) = (SELECT a, b FROM t2 ORDER BY d ASC LIMIT 1)" {{}} + + 8 "(3, 1) = (SELECT b, c FROM t2 ORDER BY d DESC LIMIT 1 OFFSET 2)" 1 + 9 "(3, 1) = (SELECT b, c FROM t2 ORDER BY d ASC LIMIT 1 OFFSET 2)" 0 + 10 "(1, NULL) = (SELECT b, c FROM t2 ORDER BY d ASC LIMIT 1 OFFSET 2)" {{}} + + 11 "(3, 3) = (SELECT max(a), max(b) FROM t2)" 1 + 12 "(3, 1) = (SELECT max(a), min(b) FROM t2)" 1 + 13 "(NULL, NULL) = (SELECT max(a), min(b) FROM t2)" {{}} + + 14 "(2, 1) IN (SELECT a, b FROM t2 ORDER BY d LIMIT 5 OFFSET 11)" 1 + 15 "(2, 1) IN (SELECT a, b FROM t2 ORDER BY d LIMIT 5 OFFSET 12)" 0 + } { + do_execsql_test 2.2.$nm.$tn "SELECT $e" $res + } +} + +ifcapable stat4 { + do_execsql_test 3.0 { + CREATE TABLE c1(a, b, c, d); + INSERT INTO c1(a, b) VALUES(1, 'a'); + INSERT INTO c1(a, b) VALUES(1, 'b'); + INSERT INTO c1(a, b) VALUES(1, 'c'); + INSERT INTO c1(a, b) VALUES(1, 'd'); + INSERT INTO c1(a, b) VALUES(1, 'e'); + INSERT INTO c1(a, b) VALUES(1, 'f'); + INSERT INTO c1(a, b) VALUES(1, 'g'); + INSERT INTO c1(a, b) VALUES(1, 'h'); + INSERT INTO c1(a, b) VALUES(1, 'i'); + INSERT INTO c1(a, b) VALUES(1, 'j'); + INSERT INTO c1(a, b) VALUES(1, 'k'); + INSERT INTO c1(a, b) VALUES(1, 'l'); + INSERT INTO c1(a, b) VALUES(1, 'm'); + INSERT INTO c1(a, b) VALUES(1, 'n'); + INSERT INTO c1(a, b) VALUES(1, 'o'); + INSERT INTO c1(a, b) VALUES(1, 'p'); + INSERT INTO c1(a, b) VALUES(2, 'a'); + INSERT INTO c1(a, b) VALUES(2, 'b'); + INSERT INTO c1(a, b) VALUES(2, 'c'); + INSERT INTO c1(a, b) VALUES(2, 'd'); + INSERT INTO c1(a, b) VALUES(2, 'e'); + INSERT INTO c1(a, b) VALUES(2, 'f'); + INSERT INTO c1(a, b) VALUES(2, 'g'); + INSERT INTO c1(a, b) VALUES(2, 'h'); + + INSERT INTO c1(c, d) SELECT a, b FROM c1; + + CREATE INDEX c1ab ON c1(a, b); + CREATE INDEX c1cd ON c1(c, d); + ANALYZE; + } + + do_eqp_test 3.1.1 { SELECT * FROM c1 WHERE a=1 AND c=2 } { + 0 0 0 {SEARCH TABLE c1 USING INDEX c1cd (c=?)} + } + do_eqp_test 3.1.2 { SELECT * FROM c1 WHERE a=1 AND b>'d' AND c=2 } { + 0 0 0 {SEARCH TABLE c1 USING INDEX c1cd (c=?)} + } + do_eqp_test 3.1.3 { SELECT * FROM c1 WHERE a=1 AND b>'l' AND c=2 } { + 0 0 0 {SEARCH TABLE c1 USING INDEX c1ab (a=? AND b>?)} + } + + do_eqp_test 3.2.1 { SELECT * FROM c1 WHERE a=1 AND c>1 } { + 0 0 0 {SEARCH TABLE c1 USING INDEX c1cd (c>?)} + } + do_eqp_test 3.2.2 { SELECT * FROM c1 WHERE a=1 AND c>0 } { + 0 0 0 {SEARCH TABLE c1 USING INDEX c1ab (a=?)} + } + do_eqp_test 3.2.3 { SELECT * FROM c1 WHERE a=1 AND c>=1 } { + 0 0 0 {SEARCH TABLE c1 USING INDEX c1ab (a=?)} + } + do_eqp_test 3.2.4 { SELECT * FROM c1 WHERE a=1 AND (c, d)>(1, 'c') } { + 0 0 0 {SEARCH TABLE c1 USING INDEX c1ab (a=?)} + } + do_eqp_test 3.2.5 { SELECT * FROM c1 WHERE a=1 AND (c, d)>(1, 'o') } { + 0 0 0 {SEARCH TABLE c1 USING INDEX c1cd ((c,d)>(?,?))} + } + do_eqp_test 3.2.6 { SELECT * FROM c1 WHERE a=1 AND (c, +b)>(1, 'c') } { + 0 0 0 {SEARCH TABLE c1 USING INDEX c1ab (a=?)} + } +} + +#------------------------------------------------------------------------ + +do_execsql_test 5.0 { + CREATE TABLE d1(x, y); + CREATE TABLE d2(a, b, c); + CREATE INDEX d2ab ON d2(a, b); + CREATE INDEX d2c ON d2(c); + + WITH i(i) AS ( + VALUES(1) UNION ALL SELECT i+1 FROM i WHERE i<1000 + ) + INSERT INTO d2 SELECT i/3, i%3, i/3 FROM i; + ANALYZE; +} + +do_eqp_test 5.1 { + SELECT * FROM d2 WHERE + (a, b) IN (SELECT x, y FROM d1) AND + (c) IN (SELECT y FROM d1) +} { + 0 0 0 {SEARCH TABLE d2 USING INDEX d2ab (a=? AND b=?)} + 0 0 0 {EXECUTE LIST SUBQUERY 1} + 1 0 0 {SCAN TABLE d1} + 0 0 0 {EXECUTE LIST SUBQUERY 2} + 2 0 0 {SCAN TABLE d1} +} + +do_execsql_test 6.0 { + CREATE TABLE e1(a, b, c, d, e); + CREATE INDEX e1ab ON e1(a, b); + CREATE INDEX e1cde ON e1(c, d, e); +} + +do_eqp_test 6.1 { + SELECT * FROM e1 WHERE (a, b) > (?, ?) +} { + 0 0 0 {SEARCH TABLE e1 USING INDEX e1ab ((a,b)>(?,?))} +} +do_eqp_test 6.2 { + SELECT * FROM e1 WHERE (a, b) < (?, ?) +} { + 0 0 0 {SEARCH TABLE e1 USING INDEX e1ab ((a,b)<(?,?))} +} +do_eqp_test 6.3 { + SELECT * FROM e1 WHERE c = ? AND (d, e) > (?, ?) +} { + 0 0 0 {SEARCH TABLE e1 USING INDEX e1cde (c=? AND (d,e)>(?,?))} +} +do_eqp_test 6.4 { + SELECT * FROM e1 WHERE c = ? AND (d, e) < (?, ?) +} { + 0 0 0 {SEARCH TABLE e1 USING INDEX e1cde (c=? AND (d,e)<(?,?))} +} + +do_eqp_test 6.5 { + SELECT * FROM e1 WHERE (d, e) BETWEEN (?, ?) AND (?, ?) AND c = ? +} { + 0 0 0 + {SEARCH TABLE e1 USING INDEX e1cde (c=? AND (d,e)>(?,?) AND (d,e)<(?,?))} +} + +#------------------------------------------------------------------------- + +do_execsql_test 7.1 { + CREATE TABLE f1(a, b, c); + CREATE INDEX f1ab ON f1(a, b); +} + +do_catchsql_test 7.2 { + SELECT (a COLLATE nocase, b) IN (SELECT a, b FROM f1) FROM f1; +} {0 {}} + +do_catchsql_test 7.3 { + SELECT (a COLLATE nose, b) IN (SELECT a, b FROM f1) FROM f1; +} {1 {no such collation sequence: nose}} + +do_catchsql_test 7.4 { + SELECT * FROM f1 WHERE (?, ? COLLATE nose) > (a, b); +} {1 {no such collation sequence: nose}} + +#------------------------------------------------------------------------- +drop_all_tables +do_execsql_test 8.1 { + CREATE TABLE c1(x, y); + CREATE TABLE c2(a, b, c); + CREATE INDEX c2ab ON c2(a, b); + CREATE INDEX c2c ON c2(c); + + CREATE TABLE c3(d); +} +do_catchsql_test 8.2 { + SELECT * FROM c2 CROSS JOIN c3 WHERE + ( (a, b) == (SELECT x, y FROM c1) AND c3.d = c ) OR + ( c == (SELECT x, y FROM c1) AND c3.d = c ) +} {1 {row value misused}} + +finish_test diff --git a/test/rowvalue5.test b/test/rowvalue5.test new file mode 100644 index 0000000000..58b39f2793 --- /dev/null +++ b/test/rowvalue5.test @@ -0,0 +1,115 @@ +# 2016 July 29 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The +# focus of this file is syntax errors involving row-values and +# virtual tables. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix rowvalue5 + +ifcapable !vtab { + finish_test + return +} + +proc vtab_command {method args} { + switch -- $method { + xConnect { + return "CREATE TABLE t1(a, b, c, d, expr)" + } + + xBestIndex { + set COL(0) a + set COL(1) b + set COL(2) c + set COL(3) d + set COL(4) expr + + set OP(eq) = + set OP(ne) != + set OP(gt) > + set OP(le) <= + set OP(lt) < + set OP(ge) >= + set OP(match) MATCH + set OP(like) LIKE + set OP(glob) GLOB + set OP(regexp) REGEXP + + set clist [lindex $args 0] + set ret [list] + set elist [list] + set i 0 + foreach c $clist { + array set C $c + if {$C(usable)} { + lappend ret omit $i + lappend elist "$COL($C(column)) $OP($C(op)) %$i%" + } + incr i + } + + lappend ret idxstr [join $elist " AND "] + #puts "xBestIndex: $ret" + return $ret + } + + xFilter { + foreach {idxnum idxstr arglist} $args {} + set i 0 + set ee $idxstr + foreach a $arglist { + if {[string is double $a]==0} { + set a "'[string map {' ''} $a]'" + } + set ee [string map [list "%$i%" $a] $ee] + incr i + } + set ee [string map [list "'" "''"] $ee] + + set ret [list sql "SELECT 1, 'a', 'b', 'c', 'd', '$ee'"] + #puts "xFilter: $ret" + return $ret + } + } + + return {} +} + +register_tcl_module db +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE x1 USING tcl(vtab_command); +} {} + + +foreach {tn where res} { + 1 "1" {{}} + 2 "a=1" {{a = 1}} + 3 "a=1 AND 4 = b" {{a = 1 AND b = 4}} + 4 "c>'hello'" {{c > 'hello'}} + 5 "c<='hel''lo'" {{c <= 'hel''lo'}} + 6 "(a, b) = (SELECT 9, 10)" {{a = 9 AND b = 10}} + 7 "(+a, b) = (SELECT 'a', 'b')" {{b = 'b'}} + 8 "(a, +b) = (SELECT 'a', 'b')" {{a = 'a'}} + 11 "(+a, b) IN (SELECT 'a', 'b')" {{b = 'b'}} + 12 "(a, +b) IN (SELECT 'a', 'b')" {{a = 'a'}} + + 13 "(a, b) < ('d', 'e')" {{a <= 'd'}} + 14 "(a, b) < ('a', 'c')" {{a <= 'a'}} + 15 "(a, b) <= ('a', 'b')" {{a <= 'a'}} + 16 "(a, b) < ('a', 'b')" {} +} { + do_execsql_test 1.$tn "SELECT expr FROM x1 WHERE $where" $res +} + +finish_test diff --git a/test/rowvalue6.test b/test/rowvalue6.test new file mode 100644 index 0000000000..d90e61c811 --- /dev/null +++ b/test/rowvalue6.test @@ -0,0 +1,36 @@ +# 2016-08-18 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# The focus of this file is handling of NULL values in row-value IN +# expressions. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix rowvalue6 + +do_execsql_test 1.1 { + CREATE TABLE t1(a,b,c); + CREATE INDEX t1x1 ON t1(a,b); + INSERT INTO t1 VALUES(1,NULL,200); + + CREATE TABLE t2(x,y,z); + INSERT INTO t2 VALUES(1,NULL,55); + + SELECT c FROM t1 WHERE (a,b) IN (SELECT x,y FROM t2 WHERE z==55); +} {} +do_execsql_test 1.2 { + INSERT INTO t1 VALUES(2,3,400); + INSERT INTO t2 VALUES(2,3,55); + + SELECT c FROM t1 WHERE (a,b) IN (SELECT x,y FROM t2 WHERE z==55); +} {400} + +finish_test diff --git a/test/rowvalue7.test b/test/rowvalue7.test new file mode 100644 index 0000000000..f6764f301e --- /dev/null +++ b/test/rowvalue7.test @@ -0,0 +1,58 @@ +# 2016-08-18 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# The focus of this file is vector assignments in the SET clause of +# an UPDATE statement. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix rowvalue7 + +do_execsql_test 1.1 { + CREATE TABLE t1(a,b,c,d); + CREATE INDEX t1x ON t1(a,b); + INSERT INTO t1(a,b,c,d) VALUES(1,2,0,0),(3,4,0,0),(5,6,0,0); + CREATE TABLE t2(w,x,y,z); + CREATE INDEX t2x ON t2(w,x); + INSERT INTO t2(w,x,y,z) VALUES(1,2,11,22),(8,9,88,99),(3,5,33,55),(5,6,55,66); + + SELECT *,'|' FROM t1 ORDER BY a; +} {1 2 0 0 | 3 4 0 0 | 5 6 0 0 |} + +do_execsql_test 1.2 { + UPDATE t1 SET (c,d) = (SELECT y,z FROM t2 WHERE (w,x)=(a,b)); + SELECT *,'|' FROM t1 ORDER BY a; +} {1 2 11 22 | 3 4 {} {} | 5 6 55 66 |} + +do_execsql_test 1.3 { + UPDATE t1 SET (c,d) = (SELECT y,z FROM t2 WHERE w=a); + SELECT *,'|' FROM t1 ORDER BY a; +} {1 2 11 22 | 3 4 33 55 | 5 6 55 66 |} + +do_execsql_test 1.4 { + UPDATE t1 SET (c) = 99 WHERE a=3; + SELECT *,'|' FROM t1 ORDER BY a; +} {1 2 11 22 | 3 4 99 55 | 5 6 55 66 |} + +do_execsql_test 1.5 { + UPDATE t1 SET b = 8, (c,d) = (SELECT 123,456) WHERE a=3; + SELECT *,'|' FROM t1 ORDER BY a; +} {1 2 11 22 | 3 8 123 456 | 5 6 55 66 |} + +do_catchsql_test 2.1 { + UPDATE t1 SET (c,d) = (SELECT x,y,z FROM t2 WHERE w=a); +} {1 {2 columns assigned 3 values}} + +do_catchsql_test 2.2 { + UPDATE t1 SET (b,c,d) = (SELECT x,y FROM t2 WHERE w=a); +} {1 {3 columns assigned 2 values}} + +finish_test diff --git a/test/rowvalue8.test b/test/rowvalue8.test new file mode 100644 index 0000000000..432dad1278 --- /dev/null +++ b/test/rowvalue8.test @@ -0,0 +1,59 @@ +# 2016-08-22 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# Use of row values in CASE statements. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix rowvalue8 + +do_execsql_test 1.1 { + CREATE TABLE t1(a INTEGER PRIMARY KEY,b,c,d); + INSERT INTO t1(a,b,c,d) VALUES + (1,1,2,3), + (2,2,3,4), + (3,1,2,4), + (4,2,3,5), + (5,3,4,6), + (6,4,5,9); + SELECT a, CASE (b,c) WHEN (1,2) THEN 'aleph' + WHEN (2,3) THEN 'bet' + WHEN (3,4) THEN 'gimel' + ELSE '-' END, + '|' + FROM t1 + ORDER BY a; +} {1 aleph | 2 bet | 3 aleph | 4 bet | 5 gimel | 6 - |} +do_execsql_test 1.2 { + SELECT a, CASE (b,c,d) WHEN (1,2,3) THEN 'aleph' + WHEN (2,3,4) THEN 'bet' + WHEN (3,4,6) THEN 'gimel' + ELSE '-' END, + '|' + FROM t1 + ORDER BY a; +} {1 aleph | 2 bet | 3 - | 4 - | 5 gimel | 6 - |} + +do_execsql_test 2.1 { + CREATE TABLE t2(x INTEGER PRIMARY KEY, y); + INSERT INTO t2(x,y) VALUES(1,6),(2,5),(3,4),(4,3),(5,2),(6,1); + SELECT x, CASE (SELECT b,c FROM t1 WHERE a=y) + WHEN (1,2) THEN 'aleph' + WHEN (2,3) THEN 'bet' + WHEN (3,4) THEN 'gimel' + ELSE '-' END, + '|' + FROM t2 + ORDER BY +x; +} {1 - | 2 gimel | 3 bet | 4 aleph | 5 bet | 6 aleph |} + + +finish_test diff --git a/test/rowvalue9.test b/test/rowvalue9.test new file mode 100644 index 0000000000..0fa652f97d --- /dev/null +++ b/test/rowvalue9.test @@ -0,0 +1,301 @@ +# 2016 September 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. The +# focus of this file is testing SQL statements that use row value +# constructors. +# + + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix rowvalue9 + +# Tests: +# +# 1.*: Test that affinities are handled correctly by various row-value +# operations without indexes. +# +# 2.*: Test an affinity bug that came up during testing. +# +# 3.*: Test a row-value version of the bug tested by 2.*. +# +# 4.*: Test that affinities are handled correctly by various row-value +# operations with assorted indexes. +# + +do_execsql_test 1.0.1 { + CREATE TABLE a1(c, b INTEGER, a TEXT, PRIMARY KEY(a, b)); + + INSERT INTO a1 (rowid, c, b, a) VALUES(3, '0x03', 1, 1); + INSERT INTO a1 (rowid, c, b, a) VALUES(14, '0x0E', 2, 2); + INSERT INTO a1 (rowid, c, b, a) VALUES(15, '0x0F', 3, 3); + INSERT INTO a1 (rowid, c, b, a) VALUES(92, '0x5C', 4, 4); + + CREATE TABLE a2(x BLOB, y BLOB); + INSERT INTO a2(x, y) VALUES(1, 1); + INSERT INTO a2(x, y) VALUES(2, '2'); + INSERT INTO a2(x, y) VALUES('3', 3); + INSERT INTO a2(x, y) VALUES('4', '4'); +} + +do_execsql_test 1.0.2 { + SELECT x, typeof(x), y, typeof(y) FROM a2 ORDER BY rowid +} { + 1 integer 1 integer + 2 integer 2 text + 3 text 3 integer + 4 text 4 text +} + +do_execsql_test 1.1.1 { + SELECT (SELECT rowid FROM a1 WHERE a=x AND b=y) FROM a2 +} {{} {} 15 92} +do_execsql_test 1.1.2 { + SELECT (SELECT rowid FROM a1 WHERE (a, b) = (x, y)) FROM a2 +} {{} {} 15 92} + +do_execsql_test 1.2.3 { + SELECT a1.rowid FROM a1, a2 WHERE a=x AND b=y; +} {15 92} +do_execsql_test 1.2.4 { + SELECT a1.rowid FROM a1, a2 WHERE (a, b) = (x, y) +} {15 92} + + +do_execsql_test 1.3.1 { + SELECT a1.rowid FROM a1, a2 WHERE coalesce(NULL,x)=a AND coalesce(NULL,y)=b +} {3 14 15 92} +do_execsql_test 1.3.2 { + SELECT a1.rowid FROM a1, a2 + WHERE (coalesce(NULL,x), coalesce(NULL,y)) = (a, b) +} {3 14 15 92} + +do_execsql_test 1.4.1 { + SELECT a1.rowid FROM a1, a2 WHERE +x=a AND +y=b +} {3 14 15 92} +do_execsql_test 1.4.2 { + SELECT a1.rowid FROM a1, a2 WHERE (+x, +y) = (a, b) +} {3 14 15 92} + +do_execsql_test 1.5.1 { + SELECT (SELECT rowid FROM a1 WHERE a=+x AND b=+y) FROM a2 +} {3 14 15 92} +do_execsql_test 1.5.2 { + SELECT (SELECT rowid FROM a1 WHERE (a, b) = (+x, +y)) FROM a2 +} {3 14 15 92} +do_execsql_test 1.5.3 { + SELECT (SELECT rowid FROM a1 WHERE (+x, +y) = (a, b)) FROM a2 +} {3 14 15 92} + +do_execsql_test 1.6.1 { + SELECT a1.rowid FROM a1 WHERE (a, b) IN (SELECT x, y FROM a2) +} {15 92} +do_execsql_test 1.6.2 { + SELECT a1.rowid FROM a1, a2 WHERE EXISTS ( + SELECT 1 FROM a1 WHERE a=x AND b=y + ) +} {3 14 15 92 3 14 15 92} + +# Test that [199df416] is fixed. +# +do_execsql_test 2.1 { + CREATE TABLE b1(a TEXT); + CREATE TABLE b2(x BLOB); + INSERT INTO b1 VALUES(1); + INSERT INTO b2 VALUES(1); +} +do_execsql_test 2.2 { SELECT * FROM b1, b2 WHERE a=x; } {} +do_execsql_test 2.3 { SELECT * FROM b1 WHERE a IN (SELECT x FROM b2) } {} +do_execsql_test 2.4 { CREATE UNIQUE INDEX b1a ON b1(a); } +do_execsql_test 2.5 { SELECT * FROM b1 WHERE a IN (SELECT x FROM b2) } {} + +# Test that a multi-column version of the query that revealed problem +# [199df416] also works. +# +do_execsql_test 3.1 { + CREATE TABLE c1(a INTEGER, b TEXT); + INSERT INTO c1 VALUES(1, 1); + CREATE TABLE c2(x BLOB, y BLOB); + INSERT INTO c2 VALUES(1, 1); +} +do_execsql_test 3.2 { + SELECT * FROM c1 WHERE (a, b) IN (SELECT x, y FROM c2) +} {} +do_execsql_test 3.3 { + CREATE UNIQUE INDEX c1ab ON c1(a, b); + SELECT * FROM c1 WHERE (a, b) IN (SELECT x, y FROM c2) +} {} +do_execsql_test 3.4 { + SELECT * FROM c1 WHERE (a, +b) IN (SELECT x, y FROM c2) +} {} + +do_execsql_test 3.5 { + SELECT c1.rowid FROM c1 WHERE b = (SELECT y FROM c2); +} {} +do_execsql_test 3.6 { + SELECT c1.rowid FROM c1 WHERE (a, b) = (SELECT x, y FROM c2); +} {} + + +#------------------------------------------------------------------------- +# +do_execsql_test 4.0 { + CREATE TABLE d1(a TEXT, b INTEGER, c NUMERIC); + CREATE TABLE d2(x BLOB, y BLOB); + + INSERT INTO d1 VALUES(1, 1, 1); + INSERT INTO d1 VALUES(2, 2, 2); + INSERT INTO d1 VALUES(3, 3, 3); + INSERT INTO d1 VALUES(4, 4, 4); + + INSERT INTO d2 VALUES (1, 1); + INSERT INTO d2 VALUES (2, '2'); + INSERT INTO d2 VALUES ('3', 3); + INSERT INTO d2 VALUES ('4', '4'); +} + +foreach {tn idx} { + 1 {} + 2 { CREATE INDEX idx ON d1(a) } + 3 { CREATE INDEX idx ON d1(a, c) } + 4 { CREATE INDEX idx ON d1(c) } + 5 { CREATE INDEX idx ON d1(c, a) } + + 6 { + CREATE INDEX idx ON d1(c, a) ; + CREATE INDEX idx1 ON d2(x, y); + } + + 7 { + CREATE INDEX idx ON d1(c, a) ; + CREATE UNIQUE INDEX idx2 ON d2(x, y) ; + } + + 8 { + CREATE INDEX idx ON d1(c) ; + CREATE UNIQUE INDEX idx2 ON d2(x); + } + +} { + execsql { DROP INDEX IF EXISTS idx } + execsql { DROP INDEX IF EXISTS idx2 } + execsql { DROP INDEX IF EXISTS idx3 } + execsql $idx + + do_execsql_test 4.$tn.1 { + SELECT rowid FROM d1 WHERE (a, c) IN (SELECT x, y FROM d2); + } {3 4} + + do_execsql_test 4.$tn.2 { + SELECT rowid FROM d1 WHERE (c, a) IN (SELECT x, y FROM d2); + } {2 4} + + do_execsql_test 4.$tn.3 { + SELECT rowid FROM d1 WHERE (+c, a) IN (SELECT x, y FROM d2); + } {2} + + do_execsql_test 4.$tn.4 { + SELECT rowid FROM d1 WHERE (c, a) = ( + SELECT x, y FROM d2 WHERE d2.rowid=d1.rowid + ); + } {2 4} + + do_execsql_test 4.$tn.5 { + SELECT d1.rowid FROM d1, d2 WHERE a = y; + } {2 4} + + do_execsql_test 4.$tn.6 { + SELECT d1.rowid FROM d1 WHERE a = ( + SELECT y FROM d2 where d2.rowid=d1.rowid + ); + } {2 4} +} + +do_execsql_test 5.0 { + CREATE TABLE e1(a TEXT, c NUMERIC); + CREATE TABLE e2(x BLOB, y BLOB); + + INSERT INTO e1 VALUES(2, 2); + + INSERT INTO e2 VALUES ('2', 2); + INSERT INTO e2 VALUES ('2', '2'); + INSERT INTO e2 VALUES ('2', '2.0'); + + CREATE INDEX e1c ON e1(c); +} + +do_execsql_test 5.1 { + SELECT rowid FROM e1 WHERE (a, c) IN (SELECT x, y FROM e2); +} {1} +do_execsql_test 5.2 { + SELECT rowid FROM e2 WHERE rowid IN (SELECT +c FROM e1); +} {2} +do_execsql_test 5.3 { + SELECT rowid FROM e2 WHERE rowid IN (SELECT 0+c FROM e1); +} {2} + +#------------------------------------------------------------------------- +# +do_execsql_test 6.0 { + CREATE TABLE f1(a, b); + CREATE TABLE f2(c, d); + CREATE TABLE f3(e, f); +} + +do_execsql_test 6.1 { + SELECT * FROM f3 WHERE (e, f) IN ( + SELECT a, b FROM f1 UNION ALL SELECT c, d FROM f2 + ); +} +do_execsql_test 6.2 { + CREATE INDEX f3e ON f3(e); + SELECT * FROM f3 WHERE (e, f) IN ( + SELECT a, b FROM f1 UNION ALL SELECT c, d FROM f2 + ); +} + + +#------------------------------------------------------------------------- +# +do_execsql_test 7.0 { + CREATE TABLE g1(a, b); + INSERT INTO g1 VALUES + (1, 1), (1, 2), (1, 3), (1, 'i'), (1, 'j'), + (1, 6), (1, 7), (1, 8), (1, 9), (1, 10), + (1, 4), (1, 5); + + CREATE TABLE g2(x, y); + CREATE INDEX g2x ON g2(x); + + INSERT INTO g2 VALUES(1, 4); + INSERT INTO g2 VALUES(1, 5); +} + +do_execsql_test 7.1 { + SELECT * FROM g2 WHERE (x, y) IN ( + SELECT a, b FROM g1 ORDER BY +a, +b LIMIT 10 + ); +} { 1 4 1 5 } + +do_execsql_test 7.2 { + SELECT * FROM g2 WHERE (x, y) IN ( + SELECT a, b FROM g1 ORDER BY a, b LIMIT 10 + ); +} { 1 4 1 5 } + +do_execsql_test 7.3 { + SELECT * FROM g2 WHERE (x, y) IN ( + SELECT a, b FROM g1 ORDER BY 1, 2 LIMIT 10 + ); +} { 1 4 1 5 } + + +finish_test diff --git a/test/rowvaluefault.test b/test/rowvaluefault.test new file mode 100644 index 0000000000..ac1b236ba4 --- /dev/null +++ b/test/rowvaluefault.test @@ -0,0 +1,71 @@ +# 2016 June 17 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. +# + + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/malloc_common.tcl +set ::testprefix rowvaluefault + +do_execsql_test 1.0 { + CREATE TABLE xyz(one, two, thr, fou); + INSERT INTO xyz VALUES('A', 'A', 'A', 1); + INSERT INTO xyz VALUES('B', 'B', 'B', 2); + INSERT INTO xyz VALUES('C', 'C', 'C', 3); + INSERT INTO xyz VALUES('D', 'D', 'D', 4); + + CREATE UNIQUE INDEX xyz_one_two ON xyz(one, two); +} + +do_faultsim_test 1 -faults oom* -body { + execsql { SELECT fou FROM xyz WHERE (one, two, thr) = ('B', 'B', 'B') } +} -test { + faultsim_test_result {0 2} +} + +do_faultsim_test 2 -faults oom* -body { + execsql { SELECT fou FROM xyz WHERE (two, thr) IS ('C', 'C') } +} -test { + faultsim_test_result {0 3} +} + +do_faultsim_test 3 -faults oom* -body { + execsql { SELECT fou FROM xyz WHERE (one, two, thr) > ('B', 'B', 'B') } +} -test { + faultsim_test_result {0 {3 4}} +} + +do_faultsim_test 4 -faults oom* -body { + execsql { SELECT fou FROM xyz WHERE (one, two) IN (SELECT one, two FROM xyz) } +} -test { + faultsim_test_result {0 {1 2 3 4}} +} + +do_faultsim_test 5 -faults oom* -body { + execsql { + SELECT fou FROM xyz + WHERE (one, two, thr) IN (SELECT one, two, thr FROM xyz) + } +} -test { + faultsim_test_result {0 {1 2 3 4}} +} + +do_faultsim_test 6 -faults oom* -body { + execsql { + SELECT fou FROM xyz + WHERE (one, two, thr) BETWEEN ('B', 'B', 'B') AND ('C', 'C', 'C') } +} -test { + faultsim_test_result {0 {2 3}} +} + +finish_test diff --git a/test/schema4.test b/test/schema4.test index 6618d75388..9b794ee555 100644 --- a/test/schema4.test +++ b/test/schema4.test @@ -149,7 +149,7 @@ ifcapable altertable { } {} do_execsql_test schema4-2.8 { - select sql from sqlite_temp_master WHERE type='table'; + select sql from temp.sqlite_master WHERE type='table'; } {{CREATE TABLE x1(x)}} do_execsql_test schema4-2.7 { ALTER TABLE tbl RENAME TO tbl2 } {} diff --git a/test/select7.test b/test/select7.test index 0df84e13d9..d705ebfaf4 100644 --- a/test/select7.test +++ b/test/select7.test @@ -114,26 +114,22 @@ ifcapable {subquery && compound} { CREATE TABLE t2(a,b); SELECT 5 IN (SELECT a,b FROM t2); } - } [list 1 \ - {only a single result allowed for a SELECT that is part of an expression}] + } {1 {sub-select returns 2 columns - expected 1}} do_test select7-5.2 { catchsql { SELECT 5 IN (SELECT * FROM t2); } - } [list 1 \ - {only a single result allowed for a SELECT that is part of an expression}] + } {1 {sub-select returns 2 columns - expected 1}} do_test select7-5.3 { catchsql { SELECT 5 IN (SELECT a,b FROM t2 UNION SELECT b,a FROM t2); } - } [list 1 \ - {only a single result allowed for a SELECT that is part of an expression}] + } {1 {sub-select returns 2 columns - expected 1}} do_test select7-5.4 { catchsql { SELECT 5 IN (SELECT * FROM t2 UNION SELECT * FROM t2); } - } [list 1 \ - {only a single result allowed for a SELECT that is part of an expression}] + } {1 {sub-select returns 2 columns - expected 1}} } # Verify that an error occurs if you have too many terms on a diff --git a/test/selectC.test b/test/selectC.test index dedac41fc4..4d79963007 100644 --- a/test/selectC.test +++ b/test/selectC.test @@ -14,6 +14,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +set testprefix selectC # Ticket # do_test selectC-1.1 { @@ -233,4 +234,37 @@ do_execsql_test selectC-4.3 { select a, udf() from (select distinct a, b from t_distinct_bug) } {1 1 1 2 1 3} +#------------------------------------------------------------------------- +# Test that the problem in ticket #190c2507 has been fixed. +# +do_execsql_test 5.0 { + CREATE TABLE x1(a); + CREATE TABLE x2(b); + CREATE TABLE x3(c); + CREATE VIEW vvv AS SELECT b FROM x2 ORDER BY 1; + + INSERT INTO x1 VALUES('a'), ('b'); + INSERT INTO x2 VALUES(22), (23), (25), (24), (21); + INSERT INTO x3 VALUES(302), (303), (301); +} + +do_execsql_test 5.1 { + CREATE TABLE x4 AS SELECT b FROM vvv UNION ALL SELECT c from x3; + SELECT * FROM x4; +} {21 22 23 24 25 302 303 301} + +do_execsql_test 5.2 { + SELECT * FROM x1, x4 +} { + a 21 a 22 a 23 a 24 a 25 a 302 a 303 a 301 + b 21 b 22 b 23 b 24 b 25 b 302 b 303 b 301 +} + +do_execsql_test 5.3 { + SELECT * FROM x1, (SELECT b FROM vvv UNION ALL SELECT c from x3); +} { + a 21 a 22 a 23 a 24 a 25 a 302 a 303 a 301 + b 21 b 22 b 23 b 24 b 25 b 302 b 303 b 301 +} + finish_test diff --git a/test/shell1.test b/test/shell1.test index 9a966fd8db..95c3130bdd 100644 --- a/test/shell1.test +++ b/test/shell1.test @@ -199,10 +199,10 @@ do_test shell1-2.2.4 { } {0 {}} do_test shell1-2.2.5 { catchcmd "test.db" ".mode \"insert FOO" -} {1 {Error: mode should be one of: ascii column csv html insert line list tabs tcl}} +} {1 {Error: mode should be one of: ascii column csv html insert line list quote tabs tcl}} do_test shell1-2.2.6 { catchcmd "test.db" ".mode \'insert FOO" -} {1 {Error: mode should be one of: ascii column csv html insert line list tabs tcl}} +} {1 {Error: mode should be one of: ascii column csv html insert line list quote tabs tcl}} # check multiple tokens, and quoted tokens do_test shell1-2.3.1 { @@ -230,7 +230,7 @@ do_test shell1-2.3.7 { # check quoted args are unquoted do_test shell1-2.4.1 { catchcmd "test.db" ".mode FOO" -} {1 {Error: mode should be one of: ascii column csv html insert line list tabs tcl}} +} {1 {Error: mode should be one of: ascii column csv html insert line list quote tabs tcl}} do_test shell1-2.4.2 { catchcmd "test.db" ".mode csv" } {0 {}} @@ -273,6 +273,7 @@ do_test shell1-3.2.4 { catchcmd "test.db" ".bail OFF BAD" } {1 {Usage: .bail on|off}} +ifcapable vtab { # .databases List names and files of attached databases do_test shell1-3.3.1 { catchcmd "-csv test.db" ".databases" @@ -281,6 +282,7 @@ do_test shell1-3.3.2 { # extra arguments ignored catchcmd "test.db" ".databases BAD" } "/0.+main.+[string map {/ ".{1,2}"} [string range [get_pwd] 0 10]].*/" +} # .dump ?TABLE? ... Dump the database in an SQL text format # If TABLE specified, only dump tables matching @@ -427,10 +429,10 @@ do_test shell1-3.12.3 { # tcl TCL list elements do_test shell1-3.13.1 { catchcmd "test.db" ".mode" -} {1 {Error: mode should be one of: ascii column csv html insert line list tabs tcl}} +} {1 {Error: mode should be one of: ascii column csv html insert line list quote tabs tcl}} do_test shell1-3.13.2 { catchcmd "test.db" ".mode FOO" -} {1 {Error: mode should be one of: ascii column csv html insert line list tabs tcl}} +} {1 {Error: mode should be one of: ascii column csv html insert line list quote tabs tcl}} do_test shell1-3.13.3 { catchcmd "test.db" ".mode csv" } {0 {}} @@ -463,10 +465,10 @@ do_test shell1-3.13.11 { # don't allow partial mode type matches do_test shell1-3.13.12 { catchcmd "test.db" ".mode l" -} {1 {Error: mode should be one of: ascii column csv html insert line list tabs tcl}} +} {1 {Error: mode should be one of: ascii column csv html insert line list quote tabs tcl}} do_test shell1-3.13.13 { catchcmd "test.db" ".mode li" -} {1 {Error: mode should be one of: ascii column csv html insert line list tabs tcl}} +} {1 {Error: mode should be one of: ascii column csv html insert line list quote tabs tcl}} do_test shell1-3.13.14 { catchcmd "test.db" ".mode lin" } {0 {}} diff --git a/test/shell3.test b/test/shell3.test index 6e38021d0c..bb2524c1cc 100644 --- a/test/shell3.test +++ b/test/shell3.test @@ -96,6 +96,6 @@ do_test shell3-2.6 { } {0 {}} do_test shell3-2.7 { catchcmd "foo.db" "CREATE TABLE" -} {1 {Error: incomplete SQL: CREATE TABLE}} +} {1 {Error: near line 1: near "TABLE": syntax error}} finish_test diff --git a/test/shell4.test b/test/shell4.test index 3e4ae55816..88e5e69a28 100644 --- a/test/shell4.test +++ b/test/shell4.test @@ -18,6 +18,7 @@ # # shell4-1.*: Basic tests specific to the "stats" command. # shell4-2.*: Basic tests for ".trace" +# shell4-3.*: The ".read" command takes the shell out of interactive mode # set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -124,5 +125,17 @@ do_test shell4-2.5 { } {0 {SELECT * FROM t1;}} } +do_test shell4-3.1 { + set fd [open t1.txt wb] + puts $fd "SELECT 'squirrel';" + close $fd + exec $::CLI :memory: --interactive ".read t1.txt" +} {squirrel} +do_test shell4-3.2 { + set fd [open t1.txt wb] + puts $fd "SELECT 'pound: \302\243';" + close $fd + exec $::CLI :memory: --interactive ".read t1.txt" +} {pound: £} finish_test diff --git a/test/shell6.test b/test/shell6.test new file mode 100644 index 0000000000..a70c4b4298 --- /dev/null +++ b/test/shell6.test @@ -0,0 +1,101 @@ +# 2016 December 15 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# Test the shell tool ".lint fkey-indexes" command. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +ifcapable !vtab {finish_test; return} +set testprefix shell6 +set CLI [test_find_cli] +db close +forcedelete test.db test.db-journal test.db-wal + +foreach {tn schema output} { + 1 { + CREATE TABLE p1(a PRIMARY KEY, b); + CREATE TABLE c1(x, y REFERENCES p1); + } { + CREATE INDEX 'c1_y' ON 'c1'('y'); --> p1(a) + } + + 2 { + CREATE TABLE p1(a PRIMARY KEY, b); + CREATE TABLE c2(x REFERENCES p1, y REFERENCES p1); + } { + CREATE INDEX 'c2_y' ON 'c2'('y'); --> p1(a) + CREATE INDEX 'c2_x' ON 'c2'('x'); --> p1(a) + } + + 3 { + CREATE TABLE 'p 1'(a, b, c, PRIMARY KEY(c, b)); + CREATE TABLE 'c 1'(x, y, z, FOREIGN KEY (z, y) REFERENCES 'p 1'); + } { + CREATE INDEX 'c 1_z_y' ON 'c 1'('z', 'y'); --> p 1(c,b) + } + + 4 { + CREATE TABLE p1(a, 'b b b' PRIMARY KEY); + CREATE TABLE c1('x y z' REFERENCES p1); + CREATE INDEX i1 ON c1('x y z') WHERE "x y z" IS NOT NULL; + } { + } + + 5 { + CREATE TABLE p1(a, 'b b b' PRIMARY KEY); + CREATE TABLE c1('x y z' REFERENCES p1); + CREATE INDEX i1 ON c1('x y z') WHERE "x y z" IS NOT 12; + } { + CREATE INDEX 'c1_x y z' ON 'c1'('x y z'); --> p1(b b b) + } + + 6 { + CREATE TABLE x1(a, b, c, UNIQUE(a, b)); + CREATE TABLE y1(a, b, c, FOREIGN KEY(b, a) REFERENCES x1(a, b)); + CREATE INDEX y1i ON y1(a, c, b); + } { + CREATE INDEX 'y1_b_a' ON 'y1'('b', 'a'); --> x1(a,b) + } + + 6 { + CREATE TABLE x1(a COLLATE nocase, b, UNIQUE(a)); + CREATE TABLE y1(a COLLATE rtrim REFERENCES x1(a)); + } { + CREATE INDEX 'y1_a' ON 'y1'('a' COLLATE nocase); --> x1(a) + } + +} { + forcedelete test.db + sqlite3 db test.db + execsql $schema + + set expected "" + foreach line [split $output "\n"] { + set line [string trim $line] + if {$line!=""} { + append expected "$line\n" + } + } + + do_test 1.$tn.1 { + set RES [catchcmd test.db [list .lint fkey-indexes]] + } [list 0 [string trim $expected]] + + do_test 1.$tn.2 { + execsql [lindex $RES 1] + catchcmd test.db [list .lint fkey-indexes] + } {0 {}} + + db close +} + +finish_test diff --git a/test/shell7.test b/test/shell7.test new file mode 100644 index 0000000000..a062885140 --- /dev/null +++ b/test/shell7.test @@ -0,0 +1,54 @@ +# 2016 December 17 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# Test the readfile() function built into the shell tool. Specifically, +# that it does not truncate the blob read at the first embedded 0x00 +# byte. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix shell7 +set CLI [test_find_cli] + + +do_execsql_test 1.0 { + CREATE TABLE f1(tn INTEGER PRIMARY KEY, x BLOB); + CREATE TABLE f2(tn INTEGER PRIMARY KEY, x BLOB); + + INSERT INTO f1 VALUES(1, X'01020304'); + INSERT INTO f1 VALUES(2, X'01000304'); + INSERT INTO f1 VALUES(3, randomblob(200)); +} + +foreach {tn l x} [db eval { SELECT tn, length(x) AS l, x FROM f1 }] { + forcedelete shell7_test.bin + set fd [open shell7_test.bin w] + fconfigure $fd -encoding binary + fconfigure $fd -translation binary + puts -nonewline $fd $x + close $fd + + do_test 1.$tn.1 { file size shell7_test.bin } $l + do_test 1.$tn.2 { + catchcmd test.db "INSERT INTO f2 VALUES($tn, readfile('shell7_test.bin'));" + } {0 {}} + + do_execsql_test 1.$tn.3 { + SELECT (SELECT x FROM f1 WHERE tn=1)==(SELECT x FROM f2 WHERE tn=1) + } {1} +} + + + +finish_test + + diff --git a/test/skipscan1.test b/test/skipscan1.test index 6d77e63498..6cdeed5439 100644 --- a/test/skipscan1.test +++ b/test/skipscan1.test @@ -322,4 +322,19 @@ do_execsql_test skipscan1-8.2 { ORDER BY +x; } {1 AB 2 CD} +# Segfault reported on the mailing list by Keith Medcalf on 2016-09-18. +# A skip-scan with a "column IN (SELECT ...)" on the second term of the +# index. +# +do_execsql_test skipscan1-9.2 { + CREATE TABLE t9a(a,b,c); + CREATE INDEX t9a_ab ON t9a(a,b); + CREATE TABLE t9b(x,y); + ANALYZE sqlite_master; + INSERT INTO sqlite_stat1 VALUES('t9a','t9a_ab','1000000 250000 1'); + ANALYZE sqlite_master; + EXPLAIN QUERY PLAN + SELECT * FROM t9a WHERE b IN (SELECT x FROM t9b WHERE y!=5); +} {/USING INDEX t9a_ab .ANY.a. AND b=./} + finish_test diff --git a/test/snapshot.test b/test/snapshot.test index b2d61941a7..99d3ed47c3 100644 --- a/test/snapshot.test +++ b/test/snapshot.test @@ -26,413 +26,457 @@ if {[permutation]=="inmemory_journal"} { return } -#------------------------------------------------------------------------- -# Check some error conditions in snapshot_get(). It is an error if: -# -# 1) snapshot_get() is called on a non-WAL database, or -# 2) there is an open write transaction on the database. -# -do_execsql_test 1.0 { - CREATE TABLE t1(a, b); - INSERT INTO t1 VALUES(1, 2); - INSERT INTO t1 VALUES(3, 4); -} - -do_test 1.1.1 { - execsql { BEGIN; SELECT * FROM t1; } - list [catch { sqlite3_snapshot_get db main } msg] $msg -} {1 SQLITE_ERROR} -do_execsql_test 1.1.2 COMMIT - -do_test 1.2.1 { - execsql { - PRAGMA journal_mode = WAL; - BEGIN; - INSERT INTO t1 VALUES(5, 6); - INSERT INTO t1 VALUES(7, 8); +foreach {tn tcl} { + 1 { + proc snapshot_get {DB DBNAME} { + uplevel [list sqlite3_snapshot_get $DB $DBNAME] + } + proc snapshot_open {DB DBNAME SNAPSHOT} { + uplevel [list sqlite3_snapshot_open $DB $DBNAME $SNAPSHOT] + } + proc snapshot_free {SNAPSHOT} { + uplevel [list sqlite3_snapshot_free $SNAPSHOT] + } + proc snapshot_cmp {SNAPSHOT1 SNAPSHOT2} { + uplevel [list sqlite3_snapshot_cmp $SNAPSHOT1 $SNAPSHOT2] + } } - list [catch { sqlite3_snapshot_get db main } msg] $msg -} {1 SQLITE_ERROR} -do_execsql_test 1.3.2 COMMIT -#------------------------------------------------------------------------- -# Check that a simple case works. Reuse the database created by the -# block of tests above. -# -do_execsql_test 2.1.0 { - BEGIN; - SELECT * FROM t1; -} {1 2 3 4 5 6 7 8} - -do_test 2.1.1 { - set snapshot [sqlite3_snapshot_get db main] - execsql { - COMMIT; - INSERT INTO t1 VALUES(9, 10); - SELECT * FROM t1; + 2 { + proc snapshot_get {DB DBNAME} { + uplevel [list sqlite3_snapshot_get_blob $DB $DBNAME] + } + proc snapshot_open {DB DBNAME SNAPSHOT} { + uplevel [list sqlite3_snapshot_open_blob $DB $DBNAME $SNAPSHOT] + } + proc snapshot_free {SNAPSHOT} { + } + proc snapshot_cmp {SNAPSHOT1 SNAPSHOT2} { + uplevel [list sqlite3_snapshot_cmp_blob $SNAPSHOT1 $SNAPSHOT2] + } } -} {1 2 3 4 5 6 7 8 9 10} +} { -do_test 2.1.2 { - execsql BEGIN - sqlite3_snapshot_open db main $snapshot - execsql { - SELECT * FROM t1; + reset_db + eval $tcl + + #------------------------------------------------------------------------- + # Check some error conditions in snapshot_get(). It is an error if: + # + # 1) snapshot_get() is called on a non-WAL database, or + # 2) there is an open write transaction on the database. + # 3) the database handle is in auto-commit mode + # + do_execsql_test $tn.1.0 { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(3, 4); } -} {1 2 3 4 5 6 7 8} -do_test 2.1.3 { - sqlite3_snapshot_free $snapshot - execsql COMMIT -} {} + do_test $tn.1.1.1 { + execsql { BEGIN; SELECT * FROM t1; } + list [catch { snapshot_get db main } msg] $msg + } {1 SQLITE_ERROR} + do_execsql_test $tn.1.1.2 COMMIT -do_test 2.2.0 { - sqlite3 db2 test.db - execsql { + do_test $tn.1.2.1 { + execsql { + PRAGMA journal_mode = WAL; + BEGIN; + INSERT INTO t1 VALUES(5, 6); + INSERT INTO t1 VALUES(7, 8); + } + list [catch { snapshot_get db main } msg] $msg + } {1 SQLITE_ERROR} + do_execsql_test $tn.1.2.2 COMMIT + + do_test $tn.1.3.1 { + list [catch { snapshot_get db main } msg] $msg + } {1 SQLITE_ERROR} + do_test $tn.1.3.2 { + db trans { set snap [snapshot_get db main] } + snapshot_free $snap + } {} + + #------------------------------------------------------------------------- + # Check that a simple case works. Reuse the database created by the + # block of tests above. + # + do_execsql_test $tn.2.1.0 { BEGIN; SELECT * FROM t1; - } db2 -} {1 2 3 4 5 6 7 8 9 10} + } {1 2 3 4 5 6 7 8} -do_test 2.2.1 { - set snapshot [sqlite3_snapshot_get db2 main] - execsql { - INSERT INTO t1 VALUES(11, 12); - SELECT * FROM t1; - } -} {1 2 3 4 5 6 7 8 9 10 11 12} + do_test $tn.2.1.1 { + set snapshot [snapshot_get db main] + execsql { + COMMIT; + INSERT INTO t1 VALUES(9, 10); + SELECT * FROM t1; + } + } {1 2 3 4 5 6 7 8 9 10} -do_test 2.2.2 { - execsql BEGIN - sqlite3_snapshot_open db main $snapshot - execsql { - SELECT * FROM t1; - } -} {1 2 3 4 5 6 7 8 9 10} + do_test $tn.2.1.2 { + execsql BEGIN + snapshot_open db main $snapshot + execsql { + SELECT * FROM t1; + } + } {1 2 3 4 5 6 7 8} -do_test 2.2.3 { - sqlite3_snapshot_free $snapshot - execsql COMMIT - execsql COMMIT db2 - db2 close -} {} + do_test $tn.2.1.3 { + snapshot_free $snapshot + execsql COMMIT + } {} -do_test 2.3.1 { - execsql { DELETE FROM t1 WHERE a>6 } - set snapshot [sqlite3_snapshot_get db main] - execsql { - INSERT INTO t1 VALUES('a', 'b'); - INSERT INTO t1 VALUES('c', 'd'); - SELECT * FROM t1; - } -} {1 2 3 4 5 6 a b c d} -do_test 2.3.2 { - execsql BEGIN - sqlite3_snapshot_open db main $snapshot - execsql { SELECT * FROM t1 } -} {1 2 3 4 5 6} + do_test $tn.2.2.0 { + sqlite3 db2 test.db + execsql { + BEGIN; + SELECT * FROM t1; + } db2 + } {1 2 3 4 5 6 7 8 9 10} -do_test 2.3.3 { - catchsql { - INSERT INTO t1 VALUES('x','y') - } -} {1 {database is locked}} -do_test 2.3.4 { - execsql COMMIT - sqlite3_snapshot_free $snapshot -} {} + do_test $tn.2.2.1 { + set snapshot [snapshot_get db2 main] + execsql { + INSERT INTO t1 VALUES(11, 12); + SELECT * FROM t1; + } + } {1 2 3 4 5 6 7 8 9 10 11 12} -#------------------------------------------------------------------------- -# Check some errors in sqlite3_snapshot_open(). It is an error if: -# -# 1) the db is in auto-commit mode, -# 2) the db has an open (read or write) transaction, -# 3) the db is not a wal database, -# -# Reuse the database created by earlier tests. -# -do_execsql_test 3.0.0 { - CREATE TABLE t2(x, y); - INSERT INTO t2 VALUES('a', 'b'); - INSERT INTO t2 VALUES('c', 'd'); - BEGIN; - SELECT * FROM t2; -} {a b c d} -do_test 3.0.1 { - set snapshot [sqlite3_snapshot_get db main] - execsql { COMMIT } - execsql { INSERT INTO t2 VALUES('e', 'f'); } -} {} + do_test $tn.2.2.2 { + execsql BEGIN + snapshot_open db main $snapshot + execsql { + SELECT * FROM t1; + } + } {1 2 3 4 5 6 7 8 9 10} -do_test 3.1 { - list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg -} {1 SQLITE_ERROR} + do_test $tn.2.2.3 { + snapshot_free $snapshot + execsql COMMIT + execsql COMMIT db2 + db2 close + } {} -do_test 3.2.1 { - execsql { + do_test $tn.2.3.1 { + execsql { DELETE FROM t1 WHERE a>6 } + db trans { set snapshot [snapshot_get db main] } + execsql { + INSERT INTO t1 VALUES('a', 'b'); + INSERT INTO t1 VALUES('c', 'd'); + SELECT * FROM t1; + } + } {1 2 3 4 5 6 a b c d} + do_test $tn.2.3.2 { + execsql BEGIN + snapshot_open db main $snapshot + execsql { SELECT * FROM t1 } + } {1 2 3 4 5 6} + + do_test $tn.2.3.3 { + catchsql { + INSERT INTO t1 VALUES('x','y') + } + } {1 {database is locked}} + do_test $tn.2.3.4 { + execsql COMMIT + snapshot_free $snapshot + } {} + + #------------------------------------------------------------------------- + # Check some errors in snapshot_open(). It is an error if: + # + # 1) the db is in auto-commit mode, + # 2) the db has an open (read or write) transaction, + # 3) the db is not a wal database, + # + # Reuse the database created by earlier tests. + # + do_execsql_test $tn.3.0.0 { + CREATE TABLE t2(x, y); + INSERT INTO t2 VALUES('a', 'b'); + INSERT INTO t2 VALUES('c', 'd'); BEGIN; SELECT * FROM t2; - } -} {a b c d e f} -do_test 3.2.2 { - list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg -} {1 SQLITE_ERROR} + } {a b c d} + do_test $tn.3.0.1 { + set snapshot [snapshot_get db main] + execsql { COMMIT } + execsql { INSERT INTO t2 VALUES('e', 'f'); } + } {} -do_test 3.2.3 { - execsql { - COMMIT; - BEGIN; - INSERT INTO t2 VALUES('g', 'h'); - } - list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg -} {1 SQLITE_ERROR} -do_execsql_test 3.2.4 COMMIT + do_test $tn.3.1 { + list [catch {snapshot_open db main $snapshot } msg] $msg + } {1 SQLITE_ERROR} -do_test 3.3.1 { - execsql { PRAGMA journal_mode = DELETE } - execsql { BEGIN } - list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg -} {1 SQLITE_ERROR} + do_test $tn.3.2.1 { + execsql { + BEGIN; + SELECT * FROM t2; + } + } {a b c d e f} + do_test $tn.3.2.2 { + list [catch {snapshot_open db main $snapshot } msg] $msg + } {1 SQLITE_ERROR} -do_test 3.3.2 { - sqlite3_snapshot_free $snapshot - execsql COMMIT -} {} + do_test $tn.3.2.3 { + execsql { + COMMIT; + BEGIN; + INSERT INTO t2 VALUES('g', 'h'); + } + list [catch {snapshot_open db main $snapshot } msg] $msg + } {1 SQLITE_ERROR} + do_execsql_test $tn.3.2.4 COMMIT -#------------------------------------------------------------------------- -# Check that SQLITE_BUSY_SNAPSHOT is returned if the specified snapshot -# no longer exists because the wal file has been checkpointed. -# -# 1. Reading a snapshot from the middle of a wal file is not possible -# after the wal file has been checkpointed. -# -# 2. That a snapshot from the end of a wal file can not be read once -# the wal file has been wrapped. -# -do_execsql_test 4.1.0 { - PRAGMA journal_mode = wal; - CREATE TABLE t3(i, j); - INSERT INTO t3 VALUES('o', 't'); - INSERT INTO t3 VALUES('t', 'f'); - BEGIN; - SELECT * FROM t3; -} {wal o t t f} + do_test $tn.3.3.1 { + execsql { PRAGMA journal_mode = DELETE } + execsql { BEGIN } + list [catch {snapshot_open db main $snapshot } msg] $msg + } {1 SQLITE_ERROR} -do_test 4.1.1 { - set snapshot [sqlite3_snapshot_get db main] - execsql COMMIT -} {} -do_test 4.1.2 { - execsql { - INSERT INTO t3 VALUES('f', 's'); - BEGIN; - } - sqlite3_snapshot_open db main $snapshot - execsql { SELECT * FROM t3 } -} {o t t f} + do_test $tn.$tn.3.3.2 { + snapshot_free $snapshot + execsql COMMIT + } {} -do_test 4.1.3 { - execsql { - COMMIT; - PRAGMA wal_checkpoint; - BEGIN; - } - list [catch {sqlite3_snapshot_open db main $snapshot} msg] $msg -} {1 SQLITE_BUSY_SNAPSHOT} -do_test 4.1.4 { - sqlite3_snapshot_free $snapshot - execsql COMMIT -} {} - -do_test 4.2.1 { - execsql { - INSERT INTO t3 VALUES('s', 'e'); - INSERT INTO t3 VALUES('n', 't'); + #------------------------------------------------------------------------- + # Check that SQLITE_BUSY_SNAPSHOT is returned if the specified snapshot + # no longer exists because the wal file has been checkpointed. + # + # 1. Reading a snapshot from the middle of a wal file is not possible + # after the wal file has been checkpointed. + # + # 2. That a snapshot from the end of a wal file can not be read once + # the wal file has been wrapped. + # + do_execsql_test $tn.4.1.0 { + PRAGMA journal_mode = wal; + CREATE TABLE t3(i, j); + INSERT INTO t3 VALUES('o', 't'); + INSERT INTO t3 VALUES('t', 'f'); BEGIN; SELECT * FROM t3; - } -} {o t t f f s s e n t} -do_test 4.2.2 { - set snapshot [sqlite3_snapshot_get db main] - execsql { - COMMIT; - PRAGMA wal_checkpoint; + } {wal o t t f} + + do_test $tn.4.1.1 { + set snapshot [snapshot_get db main] + execsql COMMIT + } {} + do_test $tn.4.1.2 { + execsql { + INSERT INTO t3 VALUES('f', 's'); + BEGIN; + } + snapshot_open db main $snapshot + execsql { SELECT * FROM t3 } + } {o t t f} + + do_test $tn.4.1.3 { + execsql { + COMMIT; + PRAGMA wal_checkpoint; + BEGIN; + } + list [catch {snapshot_open db main $snapshot} msg] $msg + } {1 SQLITE_BUSY_SNAPSHOT} + do_test $tn.4.1.4 { + snapshot_free $snapshot + execsql COMMIT + } {} + + do_test $tn.4.2.1 { + execsql { + INSERT INTO t3 VALUES('s', 'e'); + INSERT INTO t3 VALUES('n', 't'); + BEGIN; + SELECT * FROM t3; + } + } {o t t f f s s e n t} + do_test $tn.4.2.2 { + set snapshot [snapshot_get db main] + execsql { + COMMIT; + PRAGMA wal_checkpoint; + BEGIN; + } + snapshot_open db main $snapshot + execsql { SELECT * FROM t3 } + } {o t t f f s s e n t} + do_test $tn.4.2.3 { + execsql { + COMMIT; + INSERT INTO t3 VALUES('e', 't'); + BEGIN; + } + list [catch {snapshot_open db main $snapshot} msg] $msg + } {1 SQLITE_BUSY_SNAPSHOT} + do_test $tn.4.2.4 { + snapshot_free $snapshot + } {} + + #------------------------------------------------------------------------- + # Check that SQLITE_BUSY is returned if a checkpoint is running when + # sqlite3_snapshot_open() is called. + # + reset_db + db close + testvfs tvfs + sqlite3 db test.db -vfs tvfs + + do_execsql_test $tn.5.1 { + PRAGMA journal_mode = wal; + CREATE TABLE x1(x, xx, xxx); + INSERT INTO x1 VALUES('z', 'zz', 'zzz'); BEGIN; + SELECT * FROM x1; + } {wal z zz zzz} + + do_test $tn.5.2 { + set ::snapshot [snapshot_get db main] + sqlite3 db2 test.db -vfs tvfs + execsql { + INSERT INTO x1 VALUES('a', 'aa', 'aaa'); + COMMIT; + } + } {} + + set t53 0 + proc write_callback {args} { + do_test $tn.5.3.[incr ::t53] { + execsql BEGIN + list [catch { snapshot_open db main $::snapshot } msg] $msg + } {1 SQLITE_BUSY} + catchsql COMMIT } - sqlite3_snapshot_open db main $snapshot - execsql { SELECT * FROM t3 } -} {o t t f f s s e n t} -do_test 4.2.3 { - execsql { - COMMIT; - INSERT INTO t3 VALUES('e', 't'); - BEGIN; - } - list [catch {sqlite3_snapshot_open db main $snapshot} msg] $msg -} {1 SQLITE_BUSY_SNAPSHOT} -do_test 4.2.4 { - sqlite3_snapshot_free $snapshot -} {} -#------------------------------------------------------------------------- -# Check that SQLITE_BUSY is returned if a checkpoint is running when -# sqlite3_snapshot_open() is called. -# -reset_db -db close -testvfs tvfs -sqlite3 db test.db -vfs tvfs - -do_execsql_test 5.1 { - PRAGMA journal_mode = wal; - CREATE TABLE x1(x, xx, xxx); - INSERT INTO x1 VALUES('z', 'zz', 'zzz'); - BEGIN; - SELECT * FROM x1; -} {wal z zz zzz} - -do_test 5.2 { - set ::snapshot [sqlite3_snapshot_get db main] - sqlite3 db2 test.db -vfs tvfs - execsql { - INSERT INTO x1 VALUES('a', 'aa', 'aaa'); - COMMIT; - } -} {} - -set t53 0 -proc write_callback {args} { - do_test 5.3.[incr ::t53] { - execsql BEGIN - list [catch { sqlite3_snapshot_open db main $::snapshot } msg] $msg - } {1 SQLITE_BUSY} - catchsql COMMIT -} - -tvfs filter xWrite -tvfs script write_callback -db2 eval { PRAGMA wal_checkpoint } -db close -db2 close -tvfs delete -sqlite3_snapshot_free $snapshot - -#------------------------------------------------------------------------- -# Test that sqlite3_snapshot_get() may be called immediately after -# "BEGIN; PRAGMA user_version;". And that sqlite3_snapshot_open() may -# be called after opening the db handle and running the script -# "PRAGMA user_version; BEGIN". -reset_db -do_execsql_test 6.1 { - PRAGMA journal_mode = wal; - CREATE TABLE x1(x, xx, xxx); - INSERT INTO x1 VALUES('z', 'zz', 'zzz'); - BEGIN; - PRAGMA user_version; -} {wal 0} -do_test 6.2 { - set ::snapshot [sqlite3_snapshot_get db main] - execsql { - INSERT INTO x1 VALUES('a', 'aa', 'aaa'); - COMMIT; - } -} {} -do_test 6.3 { - sqlite3 db2 test.db - db2 eval "PRAGMA user_version ; BEGIN" - sqlite3_snapshot_open db2 main $::snapshot - db2 eval { SELECT * FROM x1 } -} {z zz zzz} -do_test 6.4 { + tvfs filter xWrite + tvfs script write_callback + db2 eval { PRAGMA wal_checkpoint } + db close db2 close - sqlite3 db2 test.db - db2 eval "PRAGMA application_id" - db2 eval "BEGIN" - sqlite3_snapshot_open db2 main $::snapshot - db2 eval { SELECT * FROM x1 } -} {z zz zzz} + tvfs delete + snapshot_free $snapshot -do_test 6.5 { - db2 close - sqlite3 db2 test.db - db2 eval "BEGIN" - list [catch {sqlite3_snapshot_open db2 main $::snapshot} msg] $msg -} {1 SQLITE_ERROR} + #------------------------------------------------------------------------- + # Test that sqlite3_snapshot_get() may be called immediately after + # "BEGIN; PRAGMA user_version;". And that sqlite3_snapshot_open() may + # be called after opening the db handle and running the script + # "PRAGMA user_version; BEGIN". + reset_db + do_execsql_test $tn.6.1 { + PRAGMA journal_mode = wal; + CREATE TABLE x1(x, xx, xxx); + INSERT INTO x1 VALUES('z', 'zz', 'zzz'); + BEGIN; + PRAGMA user_version; + } {wal 0} + do_test $tn.6.2 { + set ::snapshot [snapshot_get db main] + execsql { + INSERT INTO x1 VALUES('a', 'aa', 'aaa'); + COMMIT; + } + } {} + do_test $tn.6.3 { + sqlite3 db2 test.db + db2 eval "PRAGMA user_version ; BEGIN" + snapshot_open db2 main $::snapshot + db2 eval { SELECT * FROM x1 } + } {z zz zzz} + do_test $tn.6.4 { + db2 close + sqlite3 db2 test.db + db2 eval "PRAGMA application_id" + db2 eval "BEGIN" + snapshot_open db2 main $::snapshot + db2 eval { SELECT * FROM x1 } + } {z zz zzz} -sqlite3_snapshot_free $snapshot + do_test $tn.6.5 { + db2 close + sqlite3 db2 test.db + db2 eval "BEGIN" + list [catch {snapshot_open db2 main $::snapshot} msg] $msg + } {1 SQLITE_ERROR} -#------------------------------------------------------------------------- -# The following tests investigate the sqlite3_snapshot_cmp() API. -# + snapshot_free $snapshot -# Compare snapshots $p1 and $p2, checking that the result is $r. -# -proc do_snapshot_cmp_test {tn p1 p2 r} { - uplevel [list do_test $tn.1 [list sqlite3_snapshot_cmp $p1 $p2] $r] - uplevel [list do_test $tn.2 [list sqlite3_snapshot_cmp $p2 $p1] [expr $r*-1]] - uplevel [list do_test $tn.3 [list sqlite3_snapshot_cmp $p1 $p1] 0] - uplevel [list do_test $tn.4 [list sqlite3_snapshot_cmp $p2 $p2] 0] + #------------------------------------------------------------------------- + # The following tests investigate the sqlite3_snapshot_cmp() API. + # + + # Compare snapshots $p1 and $p2, checking that the result is $r. + # + proc do_snapshot_cmp_test {tn p1 p2 r} { + uplevel [list do_test $tn.1 [list snapshot_cmp $p1 $p2] $r] + uplevel [list do_test $tn.2 [list snapshot_cmp $p2 $p1] [expr $r*-1]] + uplevel [list do_test $tn.3 [list snapshot_cmp $p1 $p1] 0] + uplevel [list do_test $tn.4 [list snapshot_cmp $p2 $p2] 0] + } + + catch { db2 close } + reset_db + + do_execsql_test $tn.7.1 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(x); + } wal + + do_test $tn.7.1.2 { + execsql { BEGIN ; PRAGMA application_id } + set p1 [snapshot_get db main] + execsql { + INSERT INTO t1 VALUES(10); + COMMIT; + } + execsql { BEGIN ; PRAGMA application_id } + set p2 [snapshot_get db main] + execsql COMMIT + } {} + + do_snapshot_cmp_test $tn.7.1.3 $p1 $p2 -1 + snapshot_free $p1 + snapshot_free $p2 + + do_execsql_test $tn.7.2.1 { + INSERT INTO t1 VALUES(11); + INSERT INTO t1 VALUES(12); + INSERT INTO t1 VALUES(13); + BEGIN; + PRAGMA application_id; + } {0} + do_test $tn.7.2.2 { + set p1 [snapshot_get db main] + execsql { + COMMIT; + INSERT INTO t1 VALUES(14); + PRAGMA wal_checkpoint; + BEGIN; + PRAGMA application_id; + } + set p2 [snapshot_get db main] + execsql COMMIT + } {} + + do_snapshot_cmp_test $tn.7.2.3 $p1 $p2 -1 + snapshot_free $p2 + + do_test $tn.7.3.1 { + execsql { + INSERT INTO t1 VALUES(14); + BEGIN; + PRAGMA application_id; + } + set p2 [snapshot_get db main] + execsql COMMIT + } {} + + do_snapshot_cmp_test $tn.7.3.2 $p1 $p2 -1 + snapshot_free $p1 + snapshot_free $p2 } -catch { db2 close } -reset_db - -do_execsql_test 7.1 { - PRAGMA journal_mode = wal; - CREATE TABLE t1(x); -} wal - -do_test 7.1.2 { - execsql { BEGIN ; PRAGMA application_id } - set p1 [sqlite3_snapshot_get db main] - execsql { - INSERT INTO t1 VALUES(10); - COMMIT; - } - execsql { BEGIN ; PRAGMA application_id } - set p2 [sqlite3_snapshot_get db main] - execsql COMMIT -} {} - -do_snapshot_cmp_test 7.1.3 $p1 $p2 -1 -sqlite3_snapshot_free $p1 -sqlite3_snapshot_free $p2 - -do_execsql_test 7.2.1 { - INSERT INTO t1 VALUES(11); - INSERT INTO t1 VALUES(12); - INSERT INTO t1 VALUES(13); - BEGIN; - PRAGMA application_id; -} {0} -do_test 7.2.2 { - set p1 [sqlite3_snapshot_get db main] - execsql { - COMMIT; - INSERT INTO t1 VALUES(14); - PRAGMA wal_checkpoint; - BEGIN; - PRAGMA application_id; - } - set p2 [sqlite3_snapshot_get db main] - execsql COMMIT -} {} - -do_snapshot_cmp_test 7.2.3 $p1 $p2 -1 -sqlite3_snapshot_free $p2 - -do_test 7.3.1 { - execsql { - INSERT INTO t1 VALUES(14); - BEGIN; - PRAGMA application_id; - } - set p2 [sqlite3_snapshot_get db main] - execsql COMMIT -} {} - -do_snapshot_cmp_test 7.3.2 $p1 $p2 -1 -sqlite3_snapshot_free $p1 -sqlite3_snapshot_free $p2 - finish_test diff --git a/test/snapshot2.test b/test/snapshot2.test new file mode 100644 index 0000000000..41e555258b --- /dev/null +++ b/test/snapshot2.test @@ -0,0 +1,200 @@ +# 2016 November 18 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The focus +# of this file is the sqlite3_snapshot_xxx() APIs. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +ifcapable !snapshot {finish_test; return} +set testprefix snapshot2 + +# This test does not work with the inmemory_journal permutation. The reason +# is that each connection opened as part of this permutation executes +# "PRAGMA journal_mode=memory", which fails if the database is in wal mode +# and there are one or more existing connections. +if {[permutation]=="inmemory_journal"} { + finish_test + return +} + +#------------------------------------------------------------------------- +# Check that it is not possible to obtain a snapshot immediately after +# a wal mode database with an empty wal file is opened. But it is after +# the file has been written, even by some other connection. +# +do_execsql_test 1.0 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(a, b, c); + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(4, 5, 6); +} {wal} + +db close +do_test 1.1.1 { list [file exists test.db] [file exists test.db-wal] } {1 0} + +sqlite3 db test.db +do_execsql_test 1.1.2 { SELECT * FROM t1 } {1 2 3 4 5 6} + +do_test 1.1.3 { + execsql BEGIN + list [catch { sqlite3_snapshot_get_blob db main } msg] $msg +} {1 SQLITE_ERROR} +execsql COMMIT + +do_test 1.1.4 { + execsql { INSERT INTO t1 VALUES(7, 8, 9) } + execsql BEGIN + string length [sqlite3_snapshot_get_blob db main] +} 48 +execsql COMMIT + +db close +do_test 1.2.1 { list [file exists test.db] [file exists test.db-wal] } {1 0} + +sqlite3 db test.db +do_execsql_test 1.2.2 { SELECT * FROM t1 } {1 2 3 4 5 6 7 8 9} + +do_test 1.2.3 { + execsql BEGIN + list [catch { sqlite3_snapshot_get_blob db main } msg] $msg +} {1 SQLITE_ERROR} +execsql COMMIT + +do_test 1.2.4 { + sqlite3 db2 test.db + execsql { INSERT INTO t1 VALUES(10, 11, 12) } db2 + execsql BEGIN + string length [sqlite3_snapshot_get_blob db main] +} 48 +execsql COMMIT +db2 close + +#------------------------------------------------------------------------- +# Simple tests for sqlite3_snapshot_recover(). +# +reset_db +do_execsql_test 2.0 { + CREATE TABLE t1(x); + PRAGMA journal_mode = wal; + INSERT INTO t1 VALUES(1); + INSERT INTO t1 VALUES(2); +} {wal} + +do_test 2.1 { + db trans { set snap [sqlite3_snapshot_get_blob db main] } + sqlite3_db_config db NO_CKPT_ON_CLOSE 1 + db close + sqlite3 db test.db + + execsql {SELECT * FROM sqlite_master} + execsql BEGIN + sqlite3_snapshot_open_blob db main $snap + execsql COMMIT; + execsql { INSERT INTO t1 VALUES(3); } +} {} + +do_test 2.2 { + sqlite3_db_config db NO_CKPT_ON_CLOSE 1 + db close + sqlite3 db test.db + + execsql {SELECT * FROM sqlite_master} + execsql BEGIN + list [catch { sqlite3_snapshot_open_blob db main $snap } msg] $msg +} {1 SQLITE_BUSY_SNAPSHOT} + +do_test 2.3 { + execsql COMMIT + sqlite3_snapshot_recover db main + execsql BEGIN + sqlite3_snapshot_open_blob db main $snap + execsql { SELECT * FROM t1 } +} {1 2} + +do_test 2.4 { + execsql COMMIT + execsql { SELECT * FROM t1 } +} {1 2 3} + +do_test 2.5 { + execsql { PRAGMA wal_checkpoint } + sqlite3_db_config db NO_CKPT_ON_CLOSE 1 + db close + sqlite3 db test.db + + sqlite3_snapshot_recover db main + execsql BEGIN + list [catch { sqlite3_snapshot_open_blob db main $snap } msg] $msg +} {1 SQLITE_BUSY_SNAPSHOT} + +#------------------------------------------------------------------------- +# Check that calling sqlite3_snapshot_recover() does not confuse the +# pager cache. +reset_db +do_execsql_test 3.0 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES('a', 'b'); + INSERT INTO t1 VALUES('c', 'd'); +} {wal} +do_test 3.1 { + sqlite3 db2 test.db + execsql { INSERT INTO t1 VALUES('e', 'f') } db2 + db2 close + sqlite3_snapshot_recover db main +} {} +do_execsql_test 3.2 { + SELECT * FROM t1; +} {a b c d e f} + +#------------------------------------------------------------------------- +# Check that sqlite3_snapshot_recover() returns an error if it is called +# with an open read-transaction. Or on a database that does not exist. Or +# on the temp database. Or on a db that is not in wal mode. +# +do_test 4.1 { + sqlite3_snapshot_recover db main +} {} +do_test 4.2 { + execsql { + BEGIN; + SELECT * FROM sqlite_master; + } + list [catch { sqlite3_snapshot_recover db main } msg] $msg +} {1 SQLITE_ERROR} +do_test 4.3 { + execsql COMMIT + sqlite3_snapshot_recover db main +} {} +do_test 4.4 { + list [catch { sqlite3_snapshot_recover db aux } msg] $msg +} {1 SQLITE_ERROR} +do_test 4.5 { + forcedelete test.db2 + execsql { + ATTACH 'test.db2' AS aux; + PRAGMA aux.journal_mode = wal; + CREATE TABLE aux.t2(x, y); + } + list [catch { sqlite3_snapshot_recover db aux } msg] $msg +} {0 {}} +do_test 4.6 { + list [catch { sqlite3_snapshot_recover db temp } msg] $msg +} {1 SQLITE_ERROR} +do_test 4.7 { + execsql { + PRAGMA aux.journal_mode = delete; + } + list [catch { sqlite3_snapshot_recover db aux } msg] $msg +} {1 SQLITE_ERROR} + +finish_test diff --git a/test/snapshot_fault.test b/test/snapshot_fault.test index f90690e3f6..5c6cf40a6f 100644 --- a/test/snapshot_fault.test +++ b/test/snapshot_fault.test @@ -159,6 +159,68 @@ do_faultsim_test 3.0 -prep { sqlite3_snapshot_free $::snapshot } +#------------------------------------------------------------------------- +# Test the handling of faults that occur within sqlite3_snapshot_recover(). +# +reset_db +do_execsql_test 4.0 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(zzz); + INSERT INTO t1 VALUES('abc'); + INSERT INTO t1 VALUES('def'); +} {wal} +faultsim_save_and_close + +do_test 4.0.1 { + faultsim_restore_and_reopen + db eval { SELECT * FROM sqlite_master } + sqlite3_snapshot_recover db main +} {} +db close + +do_faultsim_test 4.0 -faults oom* -prep { + faultsim_restore_and_reopen + db eval { SELECT * FROM sqlite_master } +} -body { + sqlite3_snapshot_recover db main +} -test { + faultsim_test_result {0 {}} {1 SQLITE_NOMEM} {1 SQLITE_IOERR_NOMEM} +} + +# The following test cases contrive to call sqlite3_snapshot_recover() +# before all pages of the *-shm file have been mapped. This tests an +# extra branch of error handling logic in snapshot_recover(). +# +reset_db +do_execsql_test 4.1.0 { + PRAGMA page_size = 512; + PRAGMA journal_mode = wal; + PRAGMA wal_autocheckpoint = 0; + CREATE TABLE t1(zzz); + INSERT INTO t1 VALUES(randomblob( 500 * 9500 )); + PRAGMA user_version = 211; +} {wal 0} + +do_test 4.1.1 { + list [file size test.db-shm] [file size test.db] +} {98304 512} + +faultsim_save_and_close +do_faultsim_test 4.1 -faults shm* -prep { + catch { db2 close } + catch { db close } + faultsim_restore_and_reopen + sqlite3 db2 test.db + db2 eval { SELECT * FROM sqlite_master } + db eval BEGIN + sqlite3_snapshot_get_blob db main + db eval COMMIT +} -body { + sqlite3_snapshot_recover db main +} -test { + faultsim_test_result {0 {}} {1 SQLITE_IOERR} +} + finish_test diff --git a/test/speed3.test b/test/speed3.test index 1beaeb74f3..4aa90943ba 100644 --- a/test/speed3.test +++ b/test/speed3.test @@ -105,7 +105,7 @@ proc io_log {db} { puts "Normal : Read $stats2(read), wrote $stats2(write)" } -proc reset_db {} { +proc speed3_reset_db {} { db close sqlite3 db test.db db eval { @@ -117,7 +117,7 @@ proc reset_db {} { } forcedelete test2.db test2.db-journal -reset_db +speed3_reset_db # Set up a database in auto-vacuum mode and create a database schema. # @@ -154,7 +154,7 @@ do_test speed3-0.4 { # Delete all content in a table, one row at a time. # #io_log db -reset_db +speed3_reset_db speed_trial speed3-1.incrvacuum $::NROW row {DELETE FROM main.t1 WHERE 1} speed_trial speed3-1.normal $::NROW row {DELETE FROM aux.t1 WHERE 1} io_log db @@ -164,7 +164,7 @@ io_log db # #db eval {PRAGMA incremental_vacuum(500000)} populate_t1 db -reset_db +speed3_reset_db speed_trial speed3-2.incrvacuum $::NROW row {SELECT c FROM main.t1} speed_trial speed3-2.normal $::NROW row {SELECT c FROM aux.t1} io_log db diff --git a/test/speedtest1.c b/test/speedtest1.c index c69c6d5fc5..02d55d5974 100644 --- a/test/speedtest1.c +++ b/test/speedtest1.c @@ -15,6 +15,7 @@ static const char zHelp[] = " --journal M Set the journal_mode to M\n" " --key KEY Set the encryption key to KEY\n" " --lookaside N SZ Configure lookaside for N slots of SZ bytes each\n" + " --mmap SZ MMAP the first SZ bytes of the database file\n" " --multithread Set multithreaded mode\n" " --nomemstat Disable memory statistics\n" " --nosync Set PRAGMA synchronous=OFF\n" @@ -22,6 +23,7 @@ static const char zHelp[] = " --pagesize N Set the page size to N\n" " --pcache N SZ Configure N pages of pagecache each of size SZ bytes\n" " --primarykey Use PRIMARY KEY instead of UNIQUE where appropriate\n" + " --repeat N Repeat each SELECT N times (default: 1)\n" " --reprepare Reprepare each statement upon every invocation\n" " --scratch N SZ Configure scratch memory for N slots of SZ bytes each\n" " --serialized Set serialized threading mode\n" @@ -48,6 +50,11 @@ static const char zHelp[] = #include #include #include +#ifndef _WIN32 +# include +#else +# include +#endif #define ISSPACE(X) isspace((unsigned char)(X)) #define ISDIGIT(X) isdigit((unsigned char)(X)) @@ -72,6 +79,7 @@ static struct Global { int bMemShrink; /* Call sqlite3_db_release_memory() often */ int eTemp; /* 0: no TEMP. 9: always TEMP. */ int szTest; /* Scale factor for test iterations */ + int nRepeat; /* Repeat selects this many times */ const char *zWR; /* Might be WITHOUT ROWID */ const char *zNN; /* Might be NOT NULL */ const char *zPK; /* Might be UNIQUE or PRIMARY KEY */ @@ -421,12 +429,14 @@ void speedtest1_run(void){ speedtest1_shrink_memory(); } +#ifndef SQLITE_OMIT_DEPRECATED /* The sqlite3_trace() callback function */ static void traceCallback(void *NotUsed, const char *zSql){ int n = (int)strlen(zSql); while( n>0 && (zSql[n-1]==';' || ISSPACE(zSql[n-1])) ) n--; fprintf(stderr,"%.*s;\n", n, zSql); } +#endif /* SQLITE_OMIT_DEPRECATED */ /* Substitute random() function that gives the same random ** sequence on each run, for repeatability. */ @@ -451,6 +461,68 @@ static int est_square_root(int x){ return y0; } + +#if SQLITE_VERSION_NUMBER<3005004 +/* +** An implementation of group_concat(). Used only when testing older +** versions of SQLite that lack the built-in group_concat(). +*/ +struct groupConcat { + char *z; + int nAlloc; + int nUsed; +}; +static void groupAppend(struct groupConcat *p, const char *z, int n){ + if( p->nUsed+n >= p->nAlloc ){ + int n2 = (p->nAlloc+n+1)*2; + char *z2 = sqlite3_realloc(p->z, n2); + if( z2==0 ) return; + p->z = z2; + p->nAlloc = n2; + } + memcpy(p->z+p->nUsed, z, n); + p->nUsed += n; +} +static void groupStep( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zVal; + struct groupConcat *p; + const char *zSep; + int nVal, nSep; + assert( argc==1 || argc==2 ); + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + p= (struct groupConcat*)sqlite3_aggregate_context(context, sizeof(*p)); + + if( p ){ + int firstTerm = p->nUsed==0; + if( !firstTerm ){ + if( argc==2 ){ + zSep = (char*)sqlite3_value_text(argv[1]); + nSep = sqlite3_value_bytes(argv[1]); + }else{ + zSep = ","; + nSep = 1; + } + if( nSep ) groupAppend(p, zSep, nSep); + } + zVal = (char*)sqlite3_value_text(argv[0]); + nVal = sqlite3_value_bytes(argv[0]); + if( zVal ) groupAppend(p, zVal, nVal); + } +} +static void groupFinal(sqlite3_context *context){ + struct groupConcat *p; + p = sqlite3_aggregate_context(context, 0); + if( p && p->z ){ + p->z[p->nUsed] = 0; + sqlite3_result_text(context, p->z, p->nUsed, sqlite3_free); + } +} +#endif + /* ** The main and default testset */ @@ -459,11 +531,12 @@ void testset_main(void){ int n; /* iteration count */ int sz; /* Size of the tables */ int maxb; /* Maximum swizzled value */ - unsigned x1, x2; /* Parameters */ - int len; /* Length of the zNum[] string */ + unsigned x1 = 0, x2 = 0; /* Parameters */ + int len = 0; /* Length of the zNum[] string */ char zNum[2000]; /* A number name */ sz = n = g.szTest*500; + zNum[0] = 0; maxb = roundup_allones(sz); speedtest1_begin_test(100, "%d INSERTs into table with no index", n); speedtest1_exec("BEGIN"); @@ -519,17 +592,23 @@ void testset_main(void){ speedtest1_exec("COMMIT"); speedtest1_end_test(); +#if SQLITE_VERSION_NUMBER<3005004 + sqlite3_create_function(g.db, "group_concat", 1, SQLITE_UTF8, 0, + 0, groupStep, groupFinal); +#endif n = 25; speedtest1_begin_test(130, "%d SELECTS, numeric BETWEEN, unindexed", n); speedtest1_exec("BEGIN"); speedtest1_prepare( - "SELECT count(*), avg(b), sum(length(c)) FROM t1\n" + "SELECT count(*), avg(b), sum(length(c)), group_concat(c) FROM t1\n" " WHERE b BETWEEN ?1 AND ?2; -- %d times", n ); for(i=1; i<=n; i++){ - x1 = speedtest1_random()%maxb; - x2 = speedtest1_random()%10 + sz/5000 + x1; + if( (i-1)%g.nRepeat==0 ){ + x1 = speedtest1_random()%maxb; + x2 = speedtest1_random()%10 + sz/5000 + x1; + } sqlite3_bind_int(g.pStmt, 1, x1); sqlite3_bind_int(g.pStmt, 2, x2); speedtest1_run(); @@ -542,16 +621,18 @@ void testset_main(void){ speedtest1_begin_test(140, "%d SELECTS, LIKE, unindexed", n); speedtest1_exec("BEGIN"); speedtest1_prepare( - "SELECT count(*), avg(b), sum(length(c)) FROM t1\n" + "SELECT count(*), avg(b), sum(length(c)), group_concat(c) FROM t1\n" " WHERE c LIKE ?1; -- %d times", n ); for(i=1; i<=n; i++){ - x1 = speedtest1_random()%maxb; - zNum[0] = '%'; - len = speedtest1_numbername(i, zNum+1, sizeof(zNum)-2); - zNum[len] = '%'; - zNum[len+1] = 0; - sqlite3_bind_text(g.pStmt, 1, zNum, len, SQLITE_STATIC); + if( (i-1)%g.nRepeat==0 ){ + x1 = speedtest1_random()%maxb; + zNum[0] = '%'; + len = speedtest1_numbername(i, zNum+1, sizeof(zNum)-2); + zNum[len] = '%'; + zNum[len+1] = 0; + } + sqlite3_bind_text(g.pStmt, 1, zNum, len+1, SQLITE_STATIC); speedtest1_run(); } speedtest1_exec("COMMIT"); @@ -566,12 +647,14 @@ void testset_main(void){ " ORDER BY a; -- %d times", n ); for(i=1; i<=n; i++){ - x1 = speedtest1_random()%maxb; - zNum[0] = '%'; - len = speedtest1_numbername(i, zNum+1, sizeof(zNum)-2); - zNum[len] = '%'; - zNum[len+1] = 0; - sqlite3_bind_text(g.pStmt, 1, zNum, len, SQLITE_STATIC); + if( (i-1)%g.nRepeat==0 ){ + x1 = speedtest1_random()%maxb; + zNum[0] = '%'; + len = speedtest1_numbername(i, zNum+1, sizeof(zNum)-2); + zNum[len] = '%'; + zNum[len+1] = 0; + } + sqlite3_bind_text(g.pStmt, 1, zNum, len+1, SQLITE_STATIC); speedtest1_run(); } speedtest1_exec("COMMIT"); @@ -585,12 +668,14 @@ void testset_main(void){ " ORDER BY a LIMIT 10; -- %d times", n ); for(i=1; i<=n; i++){ - x1 = speedtest1_random()%maxb; - zNum[0] = '%'; - len = speedtest1_numbername(i, zNum+1, sizeof(zNum)-2); - zNum[len] = '%'; - zNum[len+1] = 0; - sqlite3_bind_text(g.pStmt, 1, zNum, len, SQLITE_STATIC); + if( (i-1)%g.nRepeat==0 ){ + x1 = speedtest1_random()%maxb; + zNum[0] = '%'; + len = speedtest1_numbername(i, zNum+1, sizeof(zNum)-2); + zNum[len] = '%'; + zNum[len+1] = 0; + } + sqlite3_bind_text(g.pStmt, 1, zNum, len+1, SQLITE_STATIC); speedtest1_run(); } speedtest1_exec("COMMIT"); @@ -612,12 +697,14 @@ void testset_main(void){ speedtest1_begin_test(160, "%d SELECTS, numeric BETWEEN, indexed", n); speedtest1_exec("BEGIN"); speedtest1_prepare( - "SELECT count(*), avg(b), sum(length(c)) FROM t1\n" + "SELECT count(*), avg(b), sum(length(c)), group_concat(a) FROM t1\n" " WHERE b BETWEEN ?1 AND ?2; -- %d times", n ); for(i=1; i<=n; i++){ - x1 = speedtest1_random()%maxb; - x2 = speedtest1_random()%10 + sz/5000 + x1; + if( (i-1)%g.nRepeat==0 ){ + x1 = speedtest1_random()%maxb; + x2 = speedtest1_random()%10 + sz/5000 + x1; + } sqlite3_bind_int(g.pStmt, 1, x1); sqlite3_bind_int(g.pStmt, 2, x2); speedtest1_run(); @@ -630,12 +717,14 @@ void testset_main(void){ speedtest1_begin_test(161, "%d SELECTS, numeric BETWEEN, PK", n); speedtest1_exec("BEGIN"); speedtest1_prepare( - "SELECT count(*), avg(b), sum(length(c)) FROM t2\n" + "SELECT count(*), avg(b), sum(length(c)), group_concat(a) FROM t2\n" " WHERE a BETWEEN ?1 AND ?2; -- %d times", n ); for(i=1; i<=n; i++){ - x1 = speedtest1_random()%maxb; - x2 = speedtest1_random()%10 + sz/5000 + x1; + if( (i-1)%g.nRepeat==0 ){ + x1 = speedtest1_random()%maxb; + x2 = speedtest1_random()%10 + sz/5000 + x1; + } sqlite3_bind_int(g.pStmt, 1, x1); sqlite3_bind_int(g.pStmt, 2, x2); speedtest1_run(); @@ -648,12 +737,14 @@ void testset_main(void){ speedtest1_begin_test(170, "%d SELECTS, text BETWEEN, indexed", n); speedtest1_exec("BEGIN"); speedtest1_prepare( - "SELECT count(*), avg(b), sum(length(c)) FROM t1\n" + "SELECT count(*), avg(b), sum(length(c)), group_concat(a) FROM t1\n" " WHERE c BETWEEN ?1 AND (?1||'~'); -- %d times", n ); for(i=1; i<=n; i++){ - x1 = swizzle(i, maxb); - len = speedtest1_numbername(x1, zNum, sizeof(zNum)-1); + if( (i-1)%g.nRepeat==0 ){ + x1 = swizzle(i, maxb); + len = speedtest1_numbername(x1, zNum, sizeof(zNum)-1); + } sqlite3_bind_text(g.pStmt, 1, zNum, len, SQLITE_STATIC); speedtest1_run(); } @@ -812,6 +903,65 @@ void testset_main(void){ speedtest1_run(); speedtest1_end_test(); + sz = n = g.szTest*700; + zNum[0] = 0; + maxb = roundup_allones(sz/3); + speedtest1_begin_test(400, "%d REPLACE ops on an IPK", n); + speedtest1_exec("BEGIN"); + speedtest1_exec("CREATE%s TABLE t5(a INTEGER PRIMARY KEY, b %s);", + isTemp(9), g.zNN); + speedtest1_prepare("REPLACE INTO t5 VALUES(?1,?2); -- %d times",n); + for(i=1; i<=n; i++){ + x1 = swizzle(i,maxb); + speedtest1_numbername(i, zNum, sizeof(zNum)); + sqlite3_bind_int(g.pStmt, 1, (sqlite3_int64)x1); + sqlite3_bind_text(g.pStmt, 2, zNum, -1, SQLITE_STATIC); + speedtest1_run(); + } + speedtest1_exec("COMMIT"); + speedtest1_end_test(); + speedtest1_begin_test(410, "%d SELECTS on an IPK", n); + speedtest1_prepare("SELECT b FROM t5 WHERE a=?1; -- %d times",n); + for(i=1; i<=n; i++){ + x1 = swizzle(i,maxb); + sqlite3_bind_int(g.pStmt, 1, (sqlite3_int64)x1); + speedtest1_run(); + } + speedtest1_end_test(); + + sz = n = g.szTest*700; + zNum[0] = 0; + maxb = roundup_allones(sz/3); + speedtest1_begin_test(500, "%d REPLACE on TEXT PK", n); + speedtest1_exec("BEGIN"); + speedtest1_exec("CREATE%s TABLE t6(a TEXT PRIMARY KEY, b %s)%s;", + isTemp(9), g.zNN, + sqlite3_libversion_number()>=3008002 ? "WITHOUT ROWID" : ""); + speedtest1_prepare("REPLACE INTO t6 VALUES(?1,?2); -- %d times",n); + for(i=1; i<=n; i++){ + x1 = swizzle(i,maxb); + speedtest1_numbername(x1, zNum, sizeof(zNum)); + sqlite3_bind_int(g.pStmt, 2, i); + sqlite3_bind_text(g.pStmt, 1, zNum, -1, SQLITE_STATIC); + speedtest1_run(); + } + speedtest1_exec("COMMIT"); + speedtest1_end_test(); + speedtest1_begin_test(510, "%d SELECTS on a TEXT PK", n); + speedtest1_prepare("SELECT b FROM t6 WHERE a=?1; -- %d times",n); + for(i=1; i<=n; i++){ + x1 = swizzle(i,maxb); + speedtest1_numbername(x1, zNum, sizeof(zNum)); + sqlite3_bind_text(g.pStmt, 1, zNum, -1, SQLITE_STATIC); + speedtest1_run(); + } + speedtest1_end_test(); + speedtest1_begin_test(520, "%d SELECT DISTINCT", n); + speedtest1_exec("SELECT DISTINCT b FROM t5;"); + speedtest1_exec("SELECT DISTINCT b FROM t6;"); + speedtest1_end_test(); + + speedtest1_begin_test(980, "PRAGMA integrity_check"); speedtest1_exec("PRAGMA integrity_check"); speedtest1_end_test(); @@ -1228,7 +1378,7 @@ int main(int argc, char **argv){ int doIncrvac = 0; /* True for --incrvacuum */ const char *zJMode = 0; /* Journal mode */ const char *zKey = 0; /* Encryption key */ - int nLook = 0, szLook = 0; /* --lookaside configuration */ + int nLook = -1, szLook = 0; /* --lookaside configuration */ int noSync = 0; /* True for --nosync */ int pageSize = 0; /* Desired page size. 0 means default */ int nPCache = 0, szPCache = 0;/* --pcache configuration */ @@ -1236,6 +1386,7 @@ int main(int argc, char **argv){ int nScratch = 0, szScratch=0;/* --scratch configuration */ int showStats = 0; /* True for --stats */ int nThread = 0; /* --threads value */ + int mmapSize = 0; /* How big of a memory map to use */ const char *zTSet = "main"; /* Which --testset torun */ int doTrace = 0; /* True for --trace */ const char *zEncoding = 0; /* --utf16be or --utf16le */ @@ -1258,6 +1409,7 @@ int main(int argc, char **argv){ g.zNN = ""; g.zPK = "UNIQUE"; g.szTest = 100; + g.nRepeat = 1; for(i=1; i=3006000 }else if( strcmp(z,"multithread")==0 ){ sqlite3_config(SQLITE_CONFIG_MULTITHREAD); }else if( strcmp(z,"nomemstat")==0 ){ sqlite3_config(SQLITE_CONFIG_MEMSTATUS, 0); +#endif +#if SQLITE_VERSION_NUMBER>=3007017 + }else if( strcmp(z, "mmap")==0 ){ + if( i>=argc-1 ) fatal_error("missing argument on %s\n", argv[i]); + mmapSize = integerValue(argv[++i]); + #endif }else if( strcmp(z,"nosync")==0 ){ noSync = 1; }else if( strcmp(z,"notnull")==0 ){ @@ -1315,6 +1474,10 @@ int main(int argc, char **argv){ i += 2; }else if( strcmp(z,"primarykey")==0 ){ g.zPK = "PRIMARY KEY"; + }else if( strcmp(z,"repeat")==0 ){ + if( i>=argc-1 ) fatal_error("missing arguments on %s\n", argv[i]); + g.nRepeat = integerValue(argv[i+1]); + i += 1; }else if( strcmp(z,"reprepare")==0 ){ g.bReprepare = 1; }else if( strcmp(z,"scratch")==0 ){ @@ -1322,10 +1485,12 @@ int main(int argc, char **argv){ nScratch = integerValue(argv[i+1]); szScratch = integerValue(argv[i+2]); i += 2; +#if SQLITE_VERSION_NUMBER>=3006000 }else if( strcmp(z,"serialized")==0 ){ sqlite3_config(SQLITE_CONFIG_SERIALIZED); }else if( strcmp(z,"singlethread")==0 ){ sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); +#endif }else if( strcmp(z,"sqlonly")==0 ){ g.bSqlOnly = 1; }else if( strcmp(z,"shrink-memory")==0 ){ @@ -1397,7 +1562,7 @@ int main(int argc, char **argv){ rc = sqlite3_config(SQLITE_CONFIG_SCRATCH, pScratch, szScratch, nScratch); if( rc ) fatal_error("scratch configuration failed: %d\n", rc); } - if( nLook>0 ){ + if( nLook>=0 ){ sqlite3_config(SQLITE_CONFIG_LOOKASIDE, 0, 0); } #endif @@ -1416,7 +1581,12 @@ int main(int argc, char **argv){ /* Set database connection options */ sqlite3_create_function(g.db, "random", 0, SQLITE_UTF8, 0, randomFunc, 0, 0); +#ifndef SQLITE_OMIT_DEPRECATED if( doTrace ) sqlite3_trace(g.db, traceCallback, 0); +#endif + if( mmapSize>0 ){ + speedtest1_exec("PRAGMA mmap_size=%d", mmapSize); + } speedtest1_exec("PRAGMA threads=%d", nThread); if( zKey ){ speedtest1_exec("PRAGMA key('%s')", zKey); diff --git a/test/subselect.test b/test/subselect.test index 247f68ee80..4d0efde47a 100644 --- a/test/subselect.test +++ b/test/subselect.test @@ -40,7 +40,7 @@ do_test subselect-1.1 { do_test subselect-1.2 { set v [catch {execsql {SELECT * FROM t1 WHERE a = (SELECT * FROM t1)}} msg] lappend v $msg -} {1 {only a single result allowed for a SELECT that is part of an expression}} +} {1 {row value misused}} # A subselect without an aggregate. # diff --git a/test/tabfunc01.test b/test/tabfunc01.test index 19d3cc66d5..f25d070847 100644 --- a/test/tabfunc01.test +++ b/test/tabfunc01.test @@ -23,6 +23,7 @@ ifcapable !vtab { } load_static_extension db series load_static_extension db carray +load_static_extension db remember do_execsql_test tabfunc01-1.1 { SELECT *, '|' FROM generate_series WHERE start=1 AND stop=9 AND step=2; @@ -172,6 +173,19 @@ do_test tabfunc01-720 { SELECT b FROM t600, carray($PTR,5,'int64') WHERE a=value; } } {(005) (007) (013) (017) (023)} +do_test tabfunc01-721 { + db eval { + SELECT remember(123,$PTR); + SELECT value FROM carray($PTR,5,'int64'); + } +} {123 123 7 13 17 23} +do_test tabfunc01-722 { + set PTR2 [expr {$PTR+16}] + db eval { + SELECT remember(987,$PTR2); + SELECT value FROM carray($PTR,5,'int64'); + } +} {987 123 7 987 17 23} do_test tabfunc01-730 { set PTR [doublearray_addr 5.0 7.0 13.0 17.0 23.0] diff --git a/test/tempdb2.test b/test/tempdb2.test index bc2822e71e..5f019bd117 100644 --- a/test/tempdb2.test +++ b/test/tempdb2.test @@ -74,4 +74,3 @@ do_execsql_test 1.4 { } {1 1 1} finish_test - diff --git a/test/temptrigger.test b/test/temptrigger.test index 551c620479..e4277adf65 100644 --- a/test/temptrigger.test +++ b/test/temptrigger.test @@ -236,7 +236,7 @@ do_test 5.1 { do_execsql_test 5.2 { SELECT * FROM sqlite_master; - SELECT * FROM sqlite_temp_master; + SELECT * FROM temp.sqlite_master; } { trigger tr1 t1 0 {CREATE TRIGGER tr1 BEFORE INSERT ON t1 BEGIN SELECT 1,2,3; END} diff --git a/test/tester.tcl b/test/tester.tcl index 3fe84c641f..814788ba45 100644 --- a/test/tester.tcl +++ b/test/tester.tcl @@ -25,6 +25,7 @@ # copy_file FROM TO # delete_file FILENAME # drop_all_tables ?DB? +# drop_all_indexes ?DB? # forcecopy FROM TO # forcedelete FILENAME # @@ -1289,9 +1290,9 @@ proc explain_i {sql {db db}} { set D "" } foreach opcode { - Seek SeekGe SeekGt SeekLe SeekLt NotFound Last Rewind + Seek SeekGE SeekGT SeekLE SeekLT NotFound Last Rewind NoConflict Next Prev VNext VPrev VFilter - SorterSort SorterNext + SorterSort SorterNext NextIfOpen } { set color($opcode) $B } @@ -1312,9 +1313,15 @@ proc explain_i {sql {db db}} { set bSeenGoto 1 } + if {$opcode=="Once"} { + for {set i $addr} {$i<$p2} {incr i} { + set star($i) $addr + } + } + if {$opcode=="Next" || $opcode=="Prev" || $opcode=="VNext" || $opcode=="VPrev" - || $opcode=="SorterNext" + || $opcode=="SorterNext" || $opcode=="NextIfOpen" } { for {set i $p2} {$i<$addr} {incr i} { incr x($i) 2 @@ -1338,6 +1345,12 @@ proc explain_i {sql {db db}} { } set I [string repeat " " $x($addr)] + if {[info exists star($addr)]} { + set ii [expr $x($star($addr))] + append I " " + set I [string replace $I $ii $ii *] + } + set col "" catch { set col $color($opcode) } @@ -1939,6 +1952,16 @@ proc drop_all_tables {{db db}} { } } +# Drop all auxiliary indexes from the main database opened by handle [db]. +# +proc drop_all_indexes {{db db}} { + set L [$db eval { + SELECT name FROM sqlite_master WHERE type='index' AND sql LIKE 'create%' + }] + foreach idx $L { $db eval "DROP INDEX $idx" } +} + + #------------------------------------------------------------------------- # If a test script is executed with global variable $::G(perm:name) set to # "wal", then the tests are run in WAL mode. Otherwise, they should be run diff --git a/test/tkt-80e031a00f.test b/test/tkt-80e031a00f.test index af1d636eed..4ad0a772a5 100644 --- a/test/tkt-80e031a00f.test +++ b/test/tkt-80e031a00f.test @@ -20,10 +20,6 @@ source $testdir/tester.tcl source $testdir/lock_common.tcl source $testdir/malloc_common.tcl -# EVIDENCE-OF: R-58875-56087 The IN and NOT IN operators take a single -# scalar operand on the left and a vector operand on the right formed by -# an explicit list of zero or more scalars or by a single subquery. -# # EVIDENCE-OF: R-52275-55503 When the right operand is an empty set, the # result of IN is false and the result of NOT IN is true, regardless of # the left operand and even if the left operand is NULL. diff --git a/test/tkt3630.test b/test/tkt3630.test index b329c48dcf..1fe7ce70fa 100644 --- a/test/tkt3630.test +++ b/test/tkt3630.test @@ -23,7 +23,7 @@ source $testdir/tester.tcl do_test tkt3630-1 { db eval { CREATE TEMP TABLE temp1(a,b,c); - SELECT * FROM sqlite_temp_master WHERE sql GLOB '*TEMP*'; + SELECT * FROM temp.sqlite_master WHERE sql GLOB '*TEMP*'; } } {} do_test tkt3630-2 { @@ -39,7 +39,7 @@ ifcapable altertable { db eval { ALTER TABLE temp2 ADD COLUMN d; ALTER TABLE temp2 RENAME TO temp2rn; - SELECT name FROM sqlite_temp_master WHERE name LIKE 'temp2%'; + SELECT name FROM temp.sqlite_master WHERE name LIKE 'temp2%'; } } {temp2rn} } diff --git a/test/tkt3810.test b/test/tkt3810.test index 7e1b2ec5c9..f15eb7d63a 100644 --- a/test/tkt3810.test +++ b/test/tkt3810.test @@ -62,7 +62,7 @@ do_test tkt3810-3 { # an orphan. # do_test tkt3810-4 { - execsql {SELECT name FROM sqlite_temp_master ORDER BY name} + execsql {SELECT name FROM temp.sqlite_master ORDER BY name} } {r1} # Because it is an orphan, it cannot be dropped. diff --git a/test/trace3.test b/test/trace3.test index 0809759d80..271009a3fc 100644 --- a/test/trace3.test +++ b/test/trace3.test @@ -128,7 +128,7 @@ do_test trace3-5.1 { SELECT a, b FROM t1 ORDER BY a; } set ::stmtlist(record) -} "/^[string trim [string repeat {\d+ } 16]]\$/" +} "/^[string trim [string repeat {-?\d+ } 16]]\$/" do_test trace3-5.2 { set ::stmtlist(record) {} db trace_v2 trace_v2_record 4 @@ -136,7 +136,7 @@ do_test trace3-5.2 { SELECT a, b FROM t1 ORDER BY a; } set ::stmtlist(record) -} "/^[string trim [string repeat {\d+ } 16]]\$/" +} "/^[string trim [string repeat {-?\d+ } 16]]\$/" do_test trace3-6.1 { set ::stmtlist(record) {} diff --git a/test/triggerF.test b/test/triggerF.test new file mode 100644 index 0000000000..d5e3565edd --- /dev/null +++ b/test/triggerF.test @@ -0,0 +1,72 @@ +# 2017 January 4 +# +# 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 triggerF +ifcapable {!trigger} { + finish_test + return +} + + +foreach {tn sql log} { + 1 { } { } + + 2 { + CREATE TRIGGER trd AFTER DELETE ON t1 BEGIN + INSERT INTO log VALUES(old.a || old.b || (SELECT count(*) FROM t1)); + END; + } {1one2 2two1 3three1} + + 3 { + CREATE TRIGGER trd BEFORE DELETE ON t1 BEGIN + INSERT INTO log VALUES(old.a || old.b || (SELECT count(*) FROM t1)); + END; + } {1one3 2two2 3three2} + + 4 { + CREATE TRIGGER tr1 AFTER DELETE ON t1 BEGIN + INSERT INTO log VALUES(old.a || old.b || (SELECT count(*) FROM t1)); + END; + CREATE TRIGGER tr2 BEFORE DELETE ON t1 BEGIN + INSERT INTO log VALUES(old.a || old.b || (SELECT count(*) FROM t1)); + END; + } {1one3 1one2 2two2 2two1 3three2 3three1} + +} { + reset_db + do_execsql_test 1.$tn.0 { + PRAGMA recursive_triggers = on; + CREATE TABLE t1(a INT PRIMARY KEY, b) WITHOUT ROWID; + CREATE TABLE log(t); + } + + execsql $sql + + do_execsql_test 1.$tn.1 { + INSERT INTO t1 VALUES(1, 'one'); + INSERT INTO t1 VALUES(2, 'two'); + INSERT INTO t1 VALUES(3, 'three'); + + DELETE FROM t1 WHERE a=1; + INSERT OR REPLACE INTO t1 VALUES(2, 'three'); + UPDATE OR REPLACE t1 SET a=3 WHERE a=2; + } + + do_execsql_test 1.$tn.2 { + SELECT * FROM log ORDER BY rowid; + } $log +} + +finish_test + diff --git a/test/types2.test b/test/types2.test index 4a70aa5fe2..d6d84e65b5 100644 --- a/test/types2.test +++ b/test/types2.test @@ -333,7 +333,7 @@ ifcapable subquery { test_boolset types2-8.8 {o IN (SELECT t FROM t4)} {7} test_boolset types2-8.9 {i IN (SELECT o FROM t4)} {9 10 11 12} test_boolset types2-8.6 {n IN (SELECT o FROM t4)} {9 10 11 12} - test_boolset types2-8.7 {t IN (SELECT o FROM t4)} {9 11} + test_boolset types2-8.7 {t IN (SELECT o FROM t4)} {} test_boolset types2-8.8 {o IN (SELECT o FROM t4)} {9 10} } diff --git a/test/uri.test b/test/uri.test index a0a88d2263..0931a25e51 100644 --- a/test/uri.test +++ b/test/uri.test @@ -55,6 +55,10 @@ foreach {tn uri file} { ifcapable !curdir { if {$tn==3} break } + ifcapable uri_00_error { + if {[string first %00 $uri]>=0} continue + } + if {$tcl_platform(platform)=="windows"} { # # NOTE: Due to limits on legal characters for file names imposed by @@ -124,6 +128,10 @@ foreach {tn uri kvlist} { 14 http:test.db?hello&world {} } { + ifcapable uri_00_error { + if {[string first %00 $uri]>=0} continue + } + if {$tcl_platform(platform) == "windows" && $tn>12} { continue } diff --git a/test/uri2.test b/test/uri2.test new file mode 100644 index 0000000000..52feb7b50f --- /dev/null +++ b/test/uri2.test @@ -0,0 +1,63 @@ +# 2016 October 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. +# +#*********************************************************************** +# +# Tests for SQLITE_ENABLE_URI_00_ERROR builds. + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +ifcapable !uri_00_error { + finish_test + return +} + +set testprefix uri2 +db close +sqlite3_shutdown +sqlite3_config_uri 1 + +foreach {tn uri} { + 1 file:test.db%00trailing + 2 file:test.db?%00trailing=1 + 3 file:test.db?trailing=1%00 + 4 file:test.db?trailing=1&abc%00def + 5 file:test.db?trailing=1&abc%00def +} { + do_test 1.$tn.1 { + set rc [catch { sqlite3 db $uri } msg] + list $rc $msg + } {1 {unexpected %00 in uri}} + + do_test 1.$tn.2 { + set DB2 [sqlite3_open $uri] + sqlite3_errcode $DB2 + } {SQLITE_ERROR} + + catch { sqlite3_close $DB2 } + + do_test 1.$tn.2 { + sqlite3 db "" + catchsql { ATTACH $uri AS aux } + } {1 {unexpected %00 in uri}} + + do_test 1.$tn.3 { + sqlite3_errcode db + } {SQLITE_ERROR} + + catch { db close } +} + +reset_db +do_test 2.0 { + expr {[lsearch [execsql {PRAGMA compile_options}] ENABLE_URI_00_ERROR] >= 0} +} 1 + +finish_test diff --git a/test/vacuum5.test b/test/vacuum5.test new file mode 100644 index 0000000000..8e76a9393c --- /dev/null +++ b/test/vacuum5.test @@ -0,0 +1,153 @@ +# 2016-08-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. +# +#*********************************************************************** +# +# This file implements a test for VACUUM on attached databases. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix vacuum5 + +# If the VACUUM statement is disabled in the current build, skip all +# the tests in this file. +# +ifcapable !vacuum { + finish_test + return +} + +forcedelete test2.db test3.db +do_execsql_test vacuum5-1.1 { + PRAGMA auto_vacuum = 0; + CREATE TABLE main.t1(a,b); + WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<1000) + INSERT INTO t1(a,b) SELECT x, randomblob(1000) FROM c; + CREATE TEMP TABLE ttemp(x,y); + INSERT INTO ttemp SELECT * FROM t1; + ATTACH 'test2.db' AS x2; + ATTACH 'test3.db' AS x3; + CREATE TABLE x2.t2(c,d); + INSERT INTO t2 SELECT * FROM t1; + CREATE TABLE x3.t3(e,f); + INSERT INTO t3 SELECT * FROM t1; + DELETE FROM t1 WHERE (rowid%3)!=0; + DELETE FROM t2 WHERE (rowid%4)!=0; + DELETE FROM t3 WHERE (rowid%5)!=0; + PRAGMA main.integrity_check; + PRAGMA x2.integrity_check; + PRAGMA x3.integrity_check; +} {ok ok ok} +set size1 [file size test.db] +set size2 [file size test2.db] +set size3 [file size test3.db] + +do_execsql_test vacuum5-1.2.1 { + VACUUM main; +} {} +do_test vacuum5-1.2.2 { + expr {[file size test.db]<$size1} +} {1} +do_test vacuum5-1.2.3 { + file size test2.db +} $size2 +do_test vacuum5-1.2.4 { + file size test3.db +} $size3 +set size1 [file size test.db] +do_execsql_test vacuum-1.2.5 { + DELETE FROM t1; + PRAGMA main.integrity_check; +} {ok} + +do_execsql_test vacuum5-1.3.1 { + VACUUM x2; +} {} +do_test vacuum5-1.3.2 { + file size test.db +} $size1 +do_test vacuum5-1.3.3 { + expr {[file size test2.db]<$size2} +} 1 +do_test vacuum5-1.3.4 { + file size test3.db +} $size3 +set size2 [file size test2.db] +do_execsql_test vacuum-1.3.5 { + DELETE FROM t2; + PRAGMA x2.integrity_check; +} {ok} + +do_execsql_test vacuum5-1.4.1 { + VACUUM x3; +} {} +do_test vacuum5-1.3.2 { + file size test.db +} $size1 +do_test vacuum5-1.3.3 { + file size test2.db +} $size2 +do_test vacuum5-1.3.4 { + expr {[file size test3.db]<$size3} +} 1 + +# VACUUM is a no-op on the TEMP table +# +set sizeTemp [db one {PRAGMA temp.page_count}] +do_execsql_test vacuum5-1.4.1 { + VACUUM temp; +} {} +do_execsql_test vacuum5-1.4.2 { + PRAGMA temp.page_count; +} $sizeTemp + +do_catchsql_test vacuum5-2.0 { + VACUUM olaf; +} {1 {unknown database olaf}} + +#------------------------------------------------------------------------- +# Test that a temp file is opened as part of VACUUM. +# +if {$::TEMP_STORE<3 && [permutation]!="inmemory_journal"} { + db close + testvfs tvfs + tvfs filter xOpen + tvfs script open_cb + forcedelete test.db + + set ::openfiles [list] + proc open_cb {method args} { + lappend ::openfiles [file tail [lindex $args 0]] + } + sqlite3 db test.db -vfs tvfs + + do_execsql_test 3.0 { + PRAGMA temp_store = file; + PRAGMA page_size = 1024; + PRAGMA cache_size = 50; + CREATE TABLE t1(i INTEGER PRIMARY KEY, j UNIQUE); + WITH s(i) AS ( + VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<1000 + ) + INSERT INTO t1 SELECT NULL, randomblob(100) FROM s; + } + + do_execsql_test 3.1 { VACUUM } + + db close + tvfs delete + do_test 3.2 { + lrange $::openfiles 0 4 + } {test.db test.db-journal test.db-journal {} test.db-journal} +} + + + +finish_test diff --git a/test/vacuummem.test b/test/vacuummem.test index b214c83b3d..0f77c1b400 100644 --- a/test/vacuummem.test +++ b/test/vacuummem.test @@ -22,6 +22,19 @@ if {[permutation]=="memsubsys1"} { return } +# If ENABLE_MEMORY_MANAGEMENT is defined, when VACUUM is run the temp db +# is able to borrow space from the main db (and it does, because the +# temp db is configure with a very small cache). When the VACUUM is +# finished and the temp db closed, all the page-cache memory currently +# assigned to the temp db is freed. If ENABLE_MEMORY_MANAGEMENT is defined +# this causes the total memory usage to drop much more than expected, +# causing tests in this file to fail. +# +ifcapable memorymanage { + finish_test + return +} + proc memory_used {} { set stat [sqlite3_status SQLITE_STATUS_MEMORY_USED 1] diff --git a/test/vtabF.test b/test/vtabF.test index b9341ea597..7b11cb8c71 100644 --- a/test/vtabF.test +++ b/test/vtabF.test @@ -21,7 +21,7 @@ ifcapable !vtab||!schema_pragmas { finish_test ; return } # Register the echo module register_echo_module [sqlite3_connection_pointer db] -do_test vtabE-1.1 { +do_test vtabF-1.1 { execsql { CREATE TABLE t1(a, b); CREATE INDEX i1 ON t1(a); @@ -37,7 +37,7 @@ do_test vtabE-1.1 { SELECT b FROM t1 WHERE a IS NOT NULL; } } {110 111 112 113} -do_test vtabE-1.2 { +do_test vtabF-1.2 { execsql {SELECT b FROM tv1 WHERE a IS NOT NULL} } {110 111 112 113} diff --git a/test/vtabH.test b/test/vtabH.test index d16db13674..f2a116f350 100644 --- a/test/vtabH.test +++ b/test/vtabH.test @@ -108,7 +108,12 @@ foreach ::tclvar_set_omit {0 1} { #------------------------------------------------------------------------- # -if {1} { +if {$tcl_platform(platform)=="windows"} { + set drive [string range [pwd] 0 1] + set ::env(fstreeDrive) $drive +} +if {$tcl_platform(platform)!="windows" || \ + [regexp -nocase -- {^[A-Z]:} $drive]} { reset_db register_fs_module db do_execsql_test 3.0 { @@ -119,7 +124,7 @@ if {1} { proc list_root_files {} { if {$::tcl_platform(platform) eq "windows"} { set res [list] - foreach name [glob -directory $::env(SystemDrive)/ -- *] { + foreach name [glob -directory $::env(fstreeDrive)/ -- *] { if {[string index [file tail $name] 0] eq "."} continue lappend res $name } @@ -142,6 +147,25 @@ if {1} { } } + # Read the first 5 entries from the root directory. Except, ignore + # files that contain the "$" character in their names as these are + # special files on some Windows platforms. + # + set res [list] + set root_files [list_root_files] + set num_root_files [llength $root_files] + set lim_root_files [expr {$num_root_files > 5 ? 5 : $num_root_files}] + foreach p [lrange $root_files 0 [expr {$lim_root_files - 1}]] { + if {$::tcl_platform(platform) eq "windows"} { + if {[regexp {\$} $p]} {incr lim_root_files -1} else {lappend res $p} + } else { + lappend res "/$p" + } + } + do_execsql_test 3.1 [subst { + SELECT path FROM fstree WHERE path NOT GLOB '*\$*' LIMIT $lim_root_files; + }] $res + # Read all entries in the current directory. # proc contents {pattern} { diff --git a/test/wal6.test b/test/wal6.test index 2574a64a84..9bbc58409d 100644 --- a/test/wal6.test +++ b/test/wal6.test @@ -234,5 +234,36 @@ do_test 4.4.2 { catchsql { SELECT * FROM t2 } db2 } {1 {database disk image is malformed}} +#------------------------------------------------------------------------- +# Confirm that it is possible to get an SQLITE_BUSY_SNAPSHOT error from +# "BEGIN EXCLUSIVE" if the connection already has an open read-transaction. +# +db close +db2 close +reset_db +sqlite3 db2 test.db +do_execsql_test 5.1 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(3, 4); +} {wal} +do_test 5.2 { + set res [list] + db eval { + SELECT * FROM t1 + } { + if {$x==1} { + db2 eval { INSERT INTO t1 VALUES(5, 6) } + } + if {$x==3} { + set res [catchsql {BEGIN EXCLUSIVE}] + lappend res [sqlite3_extended_errcode db] + } + } + set res +} {1 {database is locked} SQLITE_BUSY_SNAPSHOT} + + finish_test diff --git a/test/whereD.test b/test/whereD.test index de0f9618d2..8ced0ffc72 100644 --- a/test/whereD.test +++ b/test/whereD.test @@ -337,4 +337,81 @@ do_searchcount_test 6.6.4 { SELECT c FROM x1 WHERE b=6 OR c=11 OR a=1 } {7 11 3 search 7} +#------------------------------------------------------------------------- +# +do_execsql_test 7.0 { + CREATE TABLE y1(a, b); + CREATE TABLE y2(x, y); + CREATE INDEX y2xy ON y2(x, y); + INSERT INTO y1 VALUES(1, 1); + INSERT INTO y2 VALUES(3, 3); +} + +do_execsql_test 7.1 { + SELECT * FROM y1 LEFT JOIN y2 ON ((x=1 AND y=b) OR (x=2 AND y=b)) +} {1 1 {} {}} + +do_execsql_test 7.3 { + CREATE TABLE foo (Id INTEGER PRIMARY KEY, fa INTEGER, fb INTEGER); + CREATE TABLE bar (Id INTEGER PRIMARY KEY, ba INTEGER, bb INTEGER); + + INSERT INTO foo VALUES(1, 1, 1); + INSERT INTO foo VALUES(2, 1, 2); + INSERT INTO foo VALUES(3, 1, 3); + INSERT INTO foo VALUES(4, 1, 4); + INSERT INTO foo VALUES(5, 1, 5); + INSERT INTO foo VALUES(6, 1, 6); + INSERT INTO foo VALUES(7, 1, 7); + INSERT INTO foo VALUES(8, 1, 8); + INSERT INTO foo VALUES(9, 1, 9); + + INSERT INTO bar VALUES(NULL, 1, 1); + INSERT INTO bar VALUES(NULL, 2, 2); + INSERT INTO bar VALUES(NULL, 3, 3); + INSERT INTO bar VALUES(NULL, 1, 4); + INSERT INTO bar VALUES(NULL, 2, 5); + INSERT INTO bar VALUES(NULL, 3, 6); + INSERT INTO bar VALUES(NULL, 1, 7); + INSERT INTO bar VALUES(NULL, 2, 8); + INSERT INTO bar VALUES(NULL, 3, 9); +} + +do_execsql_test 7.4 { + SELECT + bar.Id, bar.ba, bar.bb, foo.fb + FROM foo LEFT JOIN bar + ON (bar.ba = 1 AND bar.bb = foo.fb) + OR (bar.ba = 5 AND bar.bb = foo.fb); +} { + 1 1 1 1 + {} {} {} 2 + {} {} {} 3 + 4 1 4 4 + {} {} {} 5 + {} {} {} 6 + 7 1 7 7 + {} {} {} 8 + {} {} {} 9 +} + +do_execsql_test 7.5 { + CREATE INDEX idx_bar ON bar(ba, bb); + SELECT + bar.Id, bar.ba, bar.bb, foo.fb + FROM foo LEFT JOIN bar + ON (bar.ba = 1 AND bar.bb = foo.fb) + OR (bar.ba = 5 AND bar.bb = foo.fb); +} { + 1 1 1 1 + {} {} {} 2 + {} {} {} 3 + 4 1 4 4 + {} {} {} 5 + {} {} {} 6 + 7 1 7 7 + {} {} {} 8 + {} {} {} 9 +} + + finish_test diff --git a/test/with3.test b/test/with3.test index 62e88441ab..5540a7fca6 100644 --- a/test/with3.test +++ b/test/with3.test @@ -61,4 +61,60 @@ do_execsql_test 2.1 { SELECT * FROM t1; } {200} +#------------------------------------------------------------------------- +# Test that the planner notices LIMIT clauses on recursive WITH queries. +# + +ifcapable analyze { + do_execsql_test 3.1.1 { + CREATE TABLE y1(a, b); + CREATE INDEX y1a ON y1(a); + + WITH cnt(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM cnt LIMIT 1000) + INSERT INTO y1 SELECT i%10, i FROM cnt; + ANALYZE; + + } + + do_eqp_test 3.1.2 { + WITH cnt(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM cnt LIMIT 1) + SELECT * FROM cnt, y1 WHERE i=a + } { + 3 0 0 {SCAN TABLE cnt} + 1 0 0 {COMPOUND SUBQUERIES 0 AND 0 (UNION ALL)} + 0 0 0 {SCAN SUBQUERY 1} + 0 1 1 {SEARCH TABLE y1 USING INDEX y1a (a=?)} + } + + do_eqp_test 3.1.3 { + WITH cnt(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM cnt LIMIT 1000000) + SELECT * FROM cnt, y1 WHERE i=a + } { + 3 0 0 {SCAN TABLE cnt} + 1 0 0 {COMPOUND SUBQUERIES 0 AND 0 (UNION ALL)} + 0 0 1 {SCAN TABLE y1} + 0 1 0 {SEARCH SUBQUERY 1 USING AUTOMATIC COVERING INDEX (i=?)} + } +} + +do_execsql_test 3.2.1 { + CREATE TABLE w1(pk INTEGER PRIMARY KEY, x INTEGER); + CREATE TABLE w2(pk INTEGER PRIMARY KEY); +} + +do_eqp_test 3.2.2 { + WITH RECURSIVE c(w,id) AS (SELECT 0, (SELECT pk FROM w2 LIMIT 1) + UNION ALL SELECT c.w + 1, x FROM w1, c LIMIT 1) + SELECT * FROM c, w2, w1 + WHERE c.id=w2.pk AND c.id=w1.pk; +} { + 2 0 0 {EXECUTE SCALAR SUBQUERY 3} + 3 0 0 {SCAN TABLE w2} + 4 0 0 {SCAN TABLE w1} + 4 1 1 {SCAN TABLE c} + 1 0 0 {COMPOUND SUBQUERIES 0 AND 0 (UNION ALL)} 0 0 0 {SCAN SUBQUERY 1} + 0 1 1 {SEARCH TABLE w2 USING INTEGER PRIMARY KEY (rowid=?)} + 0 2 2 {SEARCH TABLE w1 USING INTEGER PRIMARY KEY (rowid=?)} +} + finish_test diff --git a/test/without_rowid3.test b/test/without_rowid3.test index 2af43a9191..387a213b99 100644 --- a/test/without_rowid3.test +++ b/test/without_rowid3.test @@ -1031,7 +1031,7 @@ ifcapable altertable { PRAGMA foreign_keys = off; ALTER TABLE t2 ADD COLUMN h DEFAULT 'text' REFERENCES t1; PRAGMA foreign_keys = on; - SELECT sql FROM sqlite_temp_master WHERE name='t2'; + SELECT sql FROM temp.sqlite_master WHERE name='t2'; } } {{CREATE TABLE t2(a, b, c REFERENCES t1, d DEFAULT NULL REFERENCES t1, e REFERENCES t1 DEFAULT NULL, h DEFAULT 'text' REFERENCES t1)}} @@ -1064,7 +1064,7 @@ ifcapable altertable { ] do_test without_rowid3-14.2tmp.2.2 { execsql { ALTER TABLE t1 RENAME TO t4 } - execsql { SELECT sql FROM sqlite_temp_master WHERE type = 'table'} + execsql { SELECT sql FROM temp.sqlite_master WHERE type = 'table'} } [list \ {CREATE TABLE "t4"(a PRIMARY KEY, b REFERENCES "t4") WITHOUT rowid} \ {CREATE TABLE t2(a PRIMARY KEY, b REFERENCES "t4", c REFERENCES t2) diff --git a/test/wordcount.c b/test/wordcount.c index 5002918c5e..bc1d499f15 100644 --- a/test/wordcount.c +++ b/test/wordcount.c @@ -13,24 +13,6 @@ ** ** Option: ** -** --without-rowid Use a WITHOUT ROWID table to store the words. -** --insert Use INSERT mode (the default) -** --replace Use REPLACE mode -** --select Use SELECT mode -** --update Use UPDATE mode -** --delete Use DELETE mode -** --query Use QUERY mode -** --nocase Add the NOCASE collating sequence to the words. -** --trace Enable sqlite3_trace() output. -** --summary Show summary information on the collected data. -** --stats Show sqlite3_status() results at the end. -** --pagesize NNN Use a page size of NNN -** --cachesize NNN Use a cache size of NNN -** --commit NNN Commit after every NNN operations -** --nosync Use PRAGMA synchronous=OFF -** --journal MMMM Use PRAGMA journal_mode=MMMM -** --timer Time the operation of this program -** --tag NAME Tag all output using NAME. Use only stdout. ** ** Modes: ** @@ -81,8 +63,36 @@ #include #include #include "sqlite3.h" +#ifndef _WIN32 +# include +#else +# include +#endif #define ISALPHA(X) isalpha((unsigned char)(X)) +const char zHelp[] = +"Usage: wordcount [OPTIONS] DATABASE [INPUT]\n" +" --all Repeat the test for all test modes\n" +" --cachesize NNN Use a cache size of NNN\n" +" --commit NNN Commit after every NNN operations\n" +" --delete Use DELETE mode\n" +" --insert Use INSERT mode (the default)\n" +" --journal MMMM Use PRAGMA journal_mode=MMMM\n" +" --nocase Add the NOCASE collating sequence to the words.\n" +" --nosync Use PRAGMA synchronous=OFF\n" +" --pagesize NNN Use a page size of NNN\n" +" --query Use QUERY mode\n" +" --replace Use REPLACE mode\n" +" --select Use SELECT mode\n" +" --stats Show sqlite3_status() results at the end.\n" +" --summary Show summary information on the collected data.\n" +" --tag NAME Tag all output using NAME. Use only stdout.\n" +" --timer Time the operation of this program\n" +" --trace Enable sqlite3_trace() output.\n" +" --update Use UPDATE mode\n" +" --without-rowid Use a WITHOUT ROWID table to store the words.\n" +; + /* Output tag */ char *zTag = "--"; @@ -110,6 +120,12 @@ static void fatal_error(const char *zMsg, ...){ exit(1); } +/* Print a usage message and quit */ +static void usage(void){ + printf("%s",zHelp); + exit(0); +} + /* The sqlite3_trace() callback function */ static void traceCallback(void *NotUsed, const char *zSql){ printf("%s;\n", zSql); @@ -189,7 +205,6 @@ static void checksumFinalize(sqlite3_context *context){ } } - /* Define operating modes */ #define MODE_INSERT 0 #define MODE_REPLACE 1 @@ -197,12 +212,50 @@ static void checksumFinalize(sqlite3_context *context){ #define MODE_UPDATE 3 #define MODE_DELETE 4 #define MODE_QUERY 5 +#define MODE_COUNT 6 +#define MODE_ALL (-1) + +/* Mode names */ +static const char *azMode[] = { + "--insert", + "--replace", + "--select", + "--update", + "--delete", + "--query" +}; + +/* +** Determine if another iteration of the test is required. Return true +** if so. Return zero if all iterations have finished. +*/ +static int allLoop( + int iMode, /* The selected test mode */ + int *piLoopCnt, /* Iteration loop counter */ + int *piMode2, /* The test mode to use on the next iteration */ + int *pUseWithoutRowid /* Whether or not to use --without-rowid */ +){ + int i; + if( iMode!=MODE_ALL ){ + if( *piLoopCnt ) return 0; + *piMode2 = iMode; + *piLoopCnt = 1; + return 1; + } + if( (*piLoopCnt)>=MODE_COUNT*2 ) return 0; + i = (*piLoopCnt)++; + *pUseWithoutRowid = i&1; + *piMode2 = i>>1; + return 1; +} int main(int argc, char **argv){ const char *zFileToRead = 0; /* Input file. NULL for stdin */ const char *zDbName = 0; /* Name of the database file to create */ int useWithoutRowid = 0; /* True for --without-rowid */ int iMode = MODE_INSERT; /* One of MODE_xxxxx */ + int iMode2; /* Mode to use for current --all iteration */ + int iLoopCnt = 0; /* Which iteration when running --all */ int useNocase = 0; /* True for --nocase */ int doTrace = 0; /* True for --trace */ int showStats = 0; /* True for --stats */ @@ -226,7 +279,8 @@ int main(int argc, char **argv){ int iCur, iHiwtr; /* Statistics values, current and "highwater" */ FILE *pTimer = stderr; /* Output channel for the timer */ sqlite3_int64 sumCnt = 0; /* Sum in QUERY mode */ - sqlite3_int64 startTime; + sqlite3_int64 startTime; /* Time of start */ + sqlite3_int64 totalTime = 0; /* Total time */ char zInput[2000]; /* A single line of input */ /* Process command-line arguments */ @@ -248,6 +302,9 @@ int main(int argc, char **argv){ iMode = MODE_DELETE; }else if( strcmp(z,"query")==0 ){ iMode = MODE_QUERY; + }else if( strcmp(z,"all")==0 ){ + iMode = MODE_ALL; + showTimer = -99; }else if( strcmp(z,"nocase")==0 ){ useNocase = 1; }else if( strcmp(z,"trace")==0 ){ @@ -274,23 +331,30 @@ int main(int argc, char **argv){ }else if( strcmp(z,"tag")==0 && i0 && (nOp%commitInterval)==0 ){ + sqlite3_exec(db, "COMMIT; BEGIN IMMEDIATE", 0, 0, 0); } } - i = j-1; - - /* Increment the operation counter. Do a COMMIT if it is time. */ - nOp++; - if( commitInterval>0 && (nOp%commitInterval)==0 ){ - sqlite3_exec(db, "COMMIT; BEGIN IMMEDIATE", 0, 0, 0); + } + sqlite3_exec(db, "COMMIT", 0, 0, 0); + sqlite3_finalize(pInsert); pInsert = 0; + sqlite3_finalize(pUpdate); pUpdate = 0; + sqlite3_finalize(pSelect); pSelect = 0; + sqlite3_finalize(pDelete); pDelete = 0; + + if( iMode2==MODE_QUERY && iMode!=MODE_ALL ){ + printf("%s sum of cnt: %lld\n", zTag, sumCnt); + rc = sqlite3_prepare_v2(db,"SELECT sum(cnt*cnt) FROM wordcount", -1, + &pSelect, 0); + if( rc==SQLITE_OK && sqlite3_step(pSelect)==SQLITE_ROW ){ + printf("%s double-check: %lld\n", zTag,sqlite3_column_int64(pSelect,0)); + } + sqlite3_finalize(pSelect); + } + + + if( showTimer ){ + sqlite3_int64 elapseTime = realTime() - startTime; + totalTime += elapseTime; + fprintf(pTimer, "%3d.%03d wordcount", (int)(elapseTime/1000), + (int)(elapseTime%1000)); + if( iMode==MODE_ALL ){ + fprintf(pTimer, " %s%s\n", azMode[iMode2], + useWithoutRowid? " --without-rowid" : ""); + }else{ + for(i=1; in + n >= p->nAlloc ){ + char *zNew; + sqlite3_uint64 nNew; + if( p->oomErr ) return; + nNew = p->nAlloc*2 + 100 + n; + zNew = sqlite3_realloc(p->z, nNew); + if( zNew==0 ){ + sqlite3_free(p->z); + memset(p, 0, sizeof(*p)); + p->oomErr = 1; + return; + } + p->z = zNew; + p->nAlloc = nNew; + } + memcpy(p->z + p->n, z, n); + p->n += n; + p->z[p->n] = 0; +} + +/* Return the current string content */ +static char *StrStr(Str *p){ + return p->z; +} + +/* Free the string */ +static void StrFree(Str *p){ + sqlite3_free(p->z); + StrInit(p); +} + /*************************************************************************** ** eval() implementation copied from ../ext/misc/eval.c */ @@ -312,6 +364,326 @@ static void sqlEvalFunc( /* End of the eval() implementation ******************************************************************************/ +/****************************************************************************** +** The generate_series(START,END,STEP) eponymous table-valued function. +** +** This code is copy/pasted from ext/misc/series.c in the SQLite source tree. +*/ +/* series_cursor is a subclass of sqlite3_vtab_cursor which will +** serve as the underlying representation of a cursor that scans +** over rows of the result +*/ +typedef struct series_cursor series_cursor; +struct series_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + int isDesc; /* True to count down rather than up */ + sqlite3_int64 iRowid; /* The rowid */ + sqlite3_int64 iValue; /* Current value ("value") */ + sqlite3_int64 mnValue; /* Mimimum value ("start") */ + sqlite3_int64 mxValue; /* Maximum value ("stop") */ + sqlite3_int64 iStep; /* Increment ("step") */ +}; + +/* +** The seriesConnect() method is invoked to create a new +** series_vtab that describes the generate_series virtual table. +** +** Think of this routine as the constructor for series_vtab objects. +** +** All this routine needs to do is: +** +** (1) Allocate the series_vtab object and initialize all fields. +** +** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the +** result set of queries against generate_series will look like. +*/ +static int seriesConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + sqlite3_vtab *pNew; + int rc; + +/* Column numbers */ +#define SERIES_COLUMN_VALUE 0 +#define SERIES_COLUMN_START 1 +#define SERIES_COLUMN_STOP 2 +#define SERIES_COLUMN_STEP 3 + + rc = sqlite3_declare_vtab(db, + "CREATE TABLE x(value,start hidden,stop hidden,step hidden)"); + if( rc==SQLITE_OK ){ + pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) ); + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + } + return rc; +} + +/* +** This method is the destructor for series_cursor objects. +*/ +static int seriesDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Constructor for a new series_cursor object. +*/ +static int seriesOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + series_cursor *pCur; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* +** Destructor for a series_cursor. +*/ +static int seriesClose(sqlite3_vtab_cursor *cur){ + sqlite3_free(cur); + return SQLITE_OK; +} + + +/* +** Advance a series_cursor to its next row of output. +*/ +static int seriesNext(sqlite3_vtab_cursor *cur){ + series_cursor *pCur = (series_cursor*)cur; + if( pCur->isDesc ){ + pCur->iValue -= pCur->iStep; + }else{ + pCur->iValue += pCur->iStep; + } + pCur->iRowid++; + return SQLITE_OK; +} + +/* +** Return values of columns for the row at which the series_cursor +** is currently pointing. +*/ +static int seriesColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + series_cursor *pCur = (series_cursor*)cur; + sqlite3_int64 x = 0; + switch( i ){ + case SERIES_COLUMN_START: x = pCur->mnValue; break; + case SERIES_COLUMN_STOP: x = pCur->mxValue; break; + case SERIES_COLUMN_STEP: x = pCur->iStep; break; + default: x = pCur->iValue; break; + } + sqlite3_result_int64(ctx, x); + return SQLITE_OK; +} + +/* +** Return the rowid for the current row. In this implementation, the +** rowid is the same as the output value. +*/ +static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + series_cursor *pCur = (series_cursor*)cur; + *pRowid = pCur->iRowid; + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int seriesEof(sqlite3_vtab_cursor *cur){ + series_cursor *pCur = (series_cursor*)cur; + if( pCur->isDesc ){ + return pCur->iValue < pCur->mnValue; + }else{ + return pCur->iValue > pCur->mxValue; + } +} + +/* True to cause run-time checking of the start=, stop=, and/or step= +** parameters. The only reason to do this is for testing the +** constraint checking logic for virtual tables in the SQLite core. +*/ +#ifndef SQLITE_SERIES_CONSTRAINT_VERIFY +# define SQLITE_SERIES_CONSTRAINT_VERIFY 0 +#endif + +/* +** This method is called to "rewind" the series_cursor object back +** to the first row of output. This method is always called at least +** once prior to any call to seriesColumn() or seriesRowid() or +** seriesEof(). +** +** 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. +** +** 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. +*/ +static int seriesFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + series_cursor *pCur = (series_cursor *)pVtabCursor; + int i = 0; + if( idxNum & 1 ){ + pCur->mnValue = sqlite3_value_int64(argv[i++]); + }else{ + pCur->mnValue = 0; + } + if( idxNum & 2 ){ + pCur->mxValue = sqlite3_value_int64(argv[i++]); + }else{ + pCur->mxValue = 0xffffffff; + } + if( idxNum & 4 ){ + pCur->iStep = sqlite3_value_int64(argv[i++]); + if( pCur->iStep<1 ) pCur->iStep = 1; + }else{ + pCur->iStep = 1; + } + if( idxNum & 8 ){ + pCur->isDesc = 1; + pCur->iValue = pCur->mxValue; + if( pCur->iStep>0 ){ + pCur->iValue -= (pCur->mxValue - pCur->mnValue)%pCur->iStep; + } + }else{ + pCur->isDesc = 0; + pCur->iValue = pCur->mnValue; + } + pCur->iRowid = 1; + return SQLITE_OK; +} + +/* +** SQLite will invoke this method one or more times while planning a query +** that uses the generate_series virtual table. This routine needs to create +** a query plan for each invocation and compute an estimated cost for that +** plan. +** +** 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 +*/ +static int seriesBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; /* Loop over constraints */ + int idxNum = 0; /* The query plan bitmask */ + int startIdx = -1; /* Index of the start= constraint, or -1 if none */ + int stopIdx = -1; /* Index of the stop= constraint, or -1 if none */ + int stepIdx = -1; /* Index of the step= constraint, or -1 if none */ + int nArg = 0; /* Number of arguments that seriesFilter() expects */ + + const struct sqlite3_index_constraint *pConstraint; + pConstraint = pIdxInfo->aConstraint; + for(i=0; inConstraint; i++, pConstraint++){ + if( pConstraint->usable==0 ) continue; + if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + switch( pConstraint->iColumn ){ + case SERIES_COLUMN_START: + startIdx = i; + idxNum |= 1; + break; + case SERIES_COLUMN_STOP: + stopIdx = i; + idxNum |= 2; + break; + case SERIES_COLUMN_STEP: + stepIdx = i; + idxNum |= 4; + break; + } + } + if( startIdx>=0 ){ + pIdxInfo->aConstraintUsage[startIdx].argvIndex = ++nArg; + pIdxInfo->aConstraintUsage[startIdx].omit= !SQLITE_SERIES_CONSTRAINT_VERIFY; + } + if( stopIdx>=0 ){ + pIdxInfo->aConstraintUsage[stopIdx].argvIndex = ++nArg; + pIdxInfo->aConstraintUsage[stopIdx].omit = !SQLITE_SERIES_CONSTRAINT_VERIFY; + } + if( stepIdx>=0 ){ + pIdxInfo->aConstraintUsage[stepIdx].argvIndex = ++nArg; + pIdxInfo->aConstraintUsage[stepIdx].omit = !SQLITE_SERIES_CONSTRAINT_VERIFY; + } + if( (idxNum & 3)==3 ){ + /* 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 ){ + if( pIdxInfo->aOrderBy[0].desc ) idxNum |= 8; + pIdxInfo->orderByConsumed = 1; + } + }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->estimatedCost = (double)2147483647; + pIdxInfo->estimatedRows = 2147483647; + } + pIdxInfo->idxNum = idxNum; + return SQLITE_OK; +} + +/* +** This following structure defines all the methods for the +** generate_series virtual table. +*/ +static sqlite3_module seriesModule = { + 0, /* iVersion */ + 0, /* xCreate */ + seriesConnect, /* xConnect */ + seriesBestIndex, /* xBestIndex */ + seriesDisconnect, /* xDisconnect */ + 0, /* xDestroy */ + seriesOpen, /* xOpen - open a cursor */ + seriesClose, /* xClose - close a cursor */ + seriesFilter, /* xFilter - configure scan constraints */ + seriesNext, /* xNext - advance a cursor */ + seriesEof, /* xEof - check for end of scan */ + seriesColumn, /* xColumn - read data */ + seriesRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ +}; +/* END the generate_series(START,END,STEP) implementation +*********************************************************************************/ + /* ** Print sketchy documentation for this utility program */ @@ -706,6 +1078,8 @@ int main(int argc, char **argv){ oomCnt = 0; } do{ + Str sql; + StrInit(&sql); if( zDbName ){ rc = sqlite3_open_v2(zDbName, &db, SQLITE_OPEN_READWRITE, 0); if( rc!=SQLITE_OK ){ @@ -729,11 +1103,31 @@ int main(int argc, char **argv){ #endif sqlite3_create_function(db, "eval", 1, SQLITE_UTF8, 0, sqlEvalFunc, 0, 0); sqlite3_create_function(db, "eval", 2, SQLITE_UTF8, 0, sqlEvalFunc, 0, 0); + sqlite3_create_module(db, "generate_series", &seriesModule, 0); sqlite3_limit(db, SQLITE_LIMIT_LENGTH, 1000000); if( zEncoding ) sqlexec(db, "PRAGMA encoding=%s", zEncoding); if( pageSize ) sqlexec(db, "PRAGMA pagesize=%d", pageSize); if( doAutovac ) sqlexec(db, "PRAGMA auto_vacuum=FULL"); iStart = timeOfDay(); + + /* If using an input database file and that database contains a table + ** named "autoexec" with a column "sql", then replace the input SQL + ** with the concatenated text of the autoexec table. In this way, + ** if the database file is the input being fuzzed, the SQL text is + ** fuzzed at the same time. */ + if( sqlite3_table_column_metadata(db,0,"autoexec","sql",0,0,0,0,0)==0 ){ + sqlite3_stmt *pStmt; + rc = sqlite3_prepare_v2(db, "SELECT sql FROM autoexec", -1, &pStmt, 0); + if( rc==SQLITE_OK ){ + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + StrAppend(&sql, (const char*)sqlite3_column_text(pStmt, 0)); + StrAppend(&sql, "\n"); + } + } + sqlite3_finalize(pStmt); + zSql = StrStr(&sql); + } + g.bOomEnable = 1; if( verboseFlag ){ zErrMsg = 0; @@ -747,6 +1141,7 @@ int main(int argc, char **argv){ } g.bOomEnable = 0; iEnd = timeOfDay(); + StrFree(&sql); rc = sqlite3_close(db); if( rc ){ abendError("sqlite3_close() failed with rc=%d", rc); diff --git a/tool/lemon.c b/tool/lemon.c index df758a5da6..aa0f4e3e9c 100644 --- a/tool/lemon.c +++ b/tool/lemon.c @@ -263,7 +263,8 @@ struct symbol { int useCnt; /* Number of times used */ char *destructor; /* Code which executes whenever this symbol is ** popped from the stack during error processing */ - int destLineno; /* Line number for start of destructor */ + int destLineno; /* Line number for start of destructor. Set to + ** -1 for duplicate destructors. */ char *datatype; /* The data type of information held by this ** object. Only used if type==NONTERMINAL */ int dtnum; /* The data type number. In the parser, the value @@ -4158,7 +4159,6 @@ void ReportTable( */ for(rp=lemp->rule; rp; rp=rp->next) rp->doesReduce = LEMON_FALSE; for(i=0; inxstate; i++){ - struct action *ap; for(ap=lemp->sorted[i]->ap; ap; ap=ap->next){ if( ap->type==REDUCE || ap->type==SHIFTREDUCE ){ ap->x.rp->doesReduce = i; @@ -4385,6 +4385,7 @@ void ReportTable( for(i=0; insymbol; i++){ struct symbol *sp = lemp->symbols[i]; if( sp==0 || sp->type==TERMINAL || sp->destructor==0 ) continue; + if( sp->destLineno<0 ) continue; /* Already emitted */ fprintf(out," case %d: /* %s */\n", sp->index, sp->name); lineno++; /* Combine duplicate destructors into a single case */ @@ -4395,7 +4396,7 @@ void ReportTable( && strcmp(sp->destructor,sp2->destructor)==0 ){ fprintf(out," case %d: /* %s */\n", sp2->index, sp2->name); lineno++; - sp2->destructor = 0; + sp2->destLineno = -1; /* Avoid emitting this destructor again */ } } diff --git a/tool/lempar.c b/tool/lempar.c index 61d19451e9..dab8343f3a 100644 --- a/tool/lempar.c +++ b/tool/lempar.c @@ -538,7 +538,6 @@ static int yy_find_reduce_action( */ static void yyStackOverflow(yyParser *yypParser){ ParseARG_FETCH; - yypParser->yytos--; #ifndef NDEBUG if( yyTraceFILE ){ fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt); @@ -592,12 +591,14 @@ static void yy_shift( #endif #if YYSTACKDEPTH>0 if( yypParser->yytos>=&yypParser->yystack[YYSTACKDEPTH] ){ + yypParser->yytos--; yyStackOverflow(yypParser); return; } #else if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz] ){ if( yyGrowStack(yypParser) ){ + yypParser->yytos--; yyStackOverflow(yypParser); return; } @@ -872,7 +873,7 @@ void Parse( yy_destructor(yypParser, (YYCODETYPE)yymajor, &yyminorunion); yymajor = YYNOCODE; }else{ - while( yypParser->yytos >= &yypParser->yystack + while( yypParser->yytos >= yypParser->yystack && yymx != YYERRORSYMBOL && (yyact = yy_find_reduce_action( yypParser->yytos->stateno, diff --git a/tool/mkpragmatab.tcl b/tool/mkpragmatab.tcl index 145a365c54..c22f72d491 100644 --- a/tool/mkpragmatab.tcl +++ b/tool/mkpragmatab.tcl @@ -10,6 +10,16 @@ # new pragma in ../src/pragma.c. # +# Flag meanings: +set flagMeaning(NeedSchema) {Force schema load before running} +set flagMeaning(ReadOnly) {Read-only HEADER_VALUE} +set flagMeaning(Result0) {Acts as query when no argument} +set flagMeaning(Result1) {Acts as query when has one argument} +set flagMeaning(SchemaReq) {Schema required - "main" is default} +set flagMeaning(SchemaOpt) {Schema restricts name search if present} +set flagMeaning(NoColumns) {OP_ResultRow called with zero columns} +set flagMeaning(NoColumns1) {zero columns if RHS argument is present} + set pragma_def { NAME: full_column_names TYPE: FLAG @@ -47,6 +57,7 @@ set pragma_def { IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS) NAME: cache_spill + FLAG: Result0 SchemaReq NoColumns1 IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS) NAME: reverse_unordered_selects @@ -139,108 +150,128 @@ set pragma_def { ARG: SQLITE_CellSizeCk NAME: default_cache_size - FLAG: NeedSchema + FLAG: NeedSchema Result0 SchemaReq NoColumns1 + COLS: cache_size IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED) NAME: page_size + FLAG: Result0 SchemaReq NoColumns1 IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: secure_delete + FLAG: Result0 IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: page_count - FLAG: NeedSchema + FLAG: NeedSchema Result0 SchemaReq IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: max_page_count TYPE: PAGE_COUNT - FLAG: NeedSchema + FLAG: NeedSchema Result0 SchemaReq IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: locking_mode + FLAG: Result0 SchemaReq IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: journal_mode - FLAG: NeedSchema + FLAG: NeedSchema Result0 SchemaReq IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: journal_size_limit + FLAG: Result0 SchemaReq IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: cache_size - FLAG: NeedSchema + FLAG: NeedSchema Result0 SchemaReq NoColumns1 IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: mmap_size IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: auto_vacuum - FLAG: NeedSchema + FLAG: NeedSchema Result0 SchemaReq NoColumns1 IF: !defined(SQLITE_OMIT_AUTOVACUUM) NAME: incremental_vacuum - FLAG: NeedSchema + FLAG: NeedSchema NoColumns IF: !defined(SQLITE_OMIT_AUTOVACUUM) NAME: temp_store + FLAG: Result0 NoColumns1 IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: temp_store_directory + FLAG: NoColumns1 IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: data_store_directory + FLAG: NoColumns1 IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) && SQLITE_OS_WIN NAME: lock_proxy_file + FLAG: NoColumns1 IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) && SQLITE_ENABLE_LOCKING_STYLE NAME: synchronous - FLAG: NeedSchema + FLAG: NeedSchema Result0 SchemaReq NoColumns1 IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: table_info - FLAG: NeedSchema + FLAG: NeedSchema Result1 SchemaOpt + COLS: cid name type notnull dflt_value pk IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) NAME: stats - FLAG: NeedSchema + FLAG: NeedSchema Result0 SchemaReq + COLS: table index width height IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) NAME: index_info TYPE: INDEX_INFO ARG: 0 - FLAG: NeedSchema + FLAG: NeedSchema Result1 SchemaOpt + COLS: seqno cid name IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) NAME: index_xinfo TYPE: INDEX_INFO ARG: 1 - FLAG: NeedSchema + FLAG: NeedSchema Result1 SchemaOpt + COLS: seqno cid name desc coll key IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) NAME: index_list - FLAG: NeedSchema + FLAG: NeedSchema Result1 SchemaOpt + COLS: seq name unique origin partial IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) NAME: database_list - FLAG: NeedSchema + FLAG: NeedSchema Result0 + COLS: seq name file IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) NAME: collation_list + FLAG: Result0 + COLS: seq name IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) NAME: foreign_key_list - FLAG: NeedSchema + FLAG: NeedSchema Result1 SchemaOpt + COLS: id seq table from to on_update on_delete match IF: !defined(SQLITE_OMIT_FOREIGN_KEY) NAME: foreign_key_check FLAG: NeedSchema + COLS: table rowid parent fkid IF: !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) NAME: parser_trace IF: defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_PARSER_TRACE) NAME: case_sensitive_like + FLAG: NoColumns NAME: integrity_check FLAG: NeedSchema @@ -252,50 +283,61 @@ set pragma_def { IF: !defined(SQLITE_OMIT_INTEGRITY_CHECK) NAME: encoding + FLAG: Result0 NoColumns1 IF: !defined(SQLITE_OMIT_UTF16) NAME: schema_version TYPE: HEADER_VALUE ARG: BTREE_SCHEMA_VERSION + FLAG: NoColumns1 Result0 IF: !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) NAME: user_version TYPE: HEADER_VALUE ARG: BTREE_USER_VERSION + FLAG: NoColumns1 Result0 IF: !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) NAME: data_version TYPE: HEADER_VALUE ARG: BTREE_DATA_VERSION - FLAG: ReadOnly + FLAG: ReadOnly Result0 IF: !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) NAME: freelist_count TYPE: HEADER_VALUE ARG: BTREE_FREE_PAGE_COUNT - FLAG: ReadOnly + FLAG: ReadOnly Result0 IF: !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) NAME: application_id TYPE: HEADER_VALUE ARG: BTREE_APPLICATION_ID + FLAG: NoColumns1 Result0 IF: !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) NAME: compile_options + FLAG: Result0 IF: !defined(SQLITE_OMIT_COMPILEOPTION_DIAGS) NAME: wal_checkpoint FLAG: NeedSchema + COLS: busy log checkpointed IF: !defined(SQLITE_OMIT_WAL) NAME: wal_autocheckpoint IF: !defined(SQLITE_OMIT_WAL) NAME: shrink_memory + FLAG: NoColumns NAME: busy_timeout + FLAG: Result0 + COLS: timeout NAME: lock_status + FLAG: Result0 + COLS: database status IF: defined(SQLITE_DEBUG) || defined(SQLITE_TEST) NAME: key @@ -315,8 +357,10 @@ set pragma_def { IF: defined(SQLITE_HAS_CODEC) || defined(SQLITE_ENABLE_CEROD) NAME: soft_heap_limit + FLAG: Result0 NAME: threads + FLAG: Result0 } # Open the output file @@ -336,15 +380,29 @@ set name {} set type {} set if {} set flags {} +set cols {} +set cols_list {} set arg 0 proc record_one {} { - global name type if arg allbyname typebyif flags + global name type if arg allbyname typebyif flags cols allcols + global cols_list colUsedBy if {$name==""} return - set allbyname($name) [list $type $arg $if $flags] + if {$cols!=""} { + if {![info exists allcols($cols)]} { + lappend cols_list $cols + set allcols($cols) [llength $cols_list] + } + set cx $allcols($cols) + lappend colUsedBy($cols) $name + } else { + set cx 0 + } + set allbyname($name) [list $type $arg $if $flags $cx] set name {} set type {} set if {} set flags {} + set cols {} set arg 0 } foreach line [split $pragma_def \n] { @@ -358,8 +416,13 @@ foreach line [split $pragma_def \n] { set type [string toupper $val] } elseif {$id=="TYPE"} { set type $val + if {$type=="FLAG"} { + lappend flags Result0 NoColumns1 + } } elseif {$id=="ARG"} { set arg $val + } elseif {$id=="COLS"} { + set cols $val } elseif {$id=="IF"} { lappend if $val } elseif {$id=="FLAG"} { @@ -378,6 +441,7 @@ set allnames [lsort [array names allbyname]] # omit in default builds (defined(SQLITE_DEBUG) and defined(SQLITE_HAS_CODEC)) # at the end. # +puts $fd "\n/* The various pragma types */" set pnum 0 foreach name $allnames { set type [lindex $allbyname($name) 0] @@ -407,25 +471,58 @@ foreach name $allnames { # Generate #defines for flags # +puts $fd "\n/* Property flags associated with various pragma. */" set fv 1 foreach f [lsort [array names allflags]] { - puts $fd [format {#define PragFlag_%-20s 0x%02x} $f $fv] + puts $fd [format {#define PragFlg_%-10s 0x%02x /* %s */} \ + $f $fv $flagMeaning($f)] set fv [expr {$fv*2}] } +# Generate the array of column names used by pragmas that act like +# queries. +# +puts $fd "\n/* Names of columns for pragmas that return multi-column result" +puts $fd "** or that return single-column results where the name of the" +puts $fd "** result column is different from the name of the pragma\n*/" +puts $fd "static const char *const pragCName\[\] = {" +set offset 0 +foreach cols $cols_list { + set cols_offset($allcols($cols)) $offset + set ub " /* Used by: $colUsedBy($cols) */" + foreach c $cols { + puts $fd [format " /* %3d */ %-14s%s" $offset \"$c\", $ub] + set ub "" + incr offset + } +} +puts $fd "\175;" + # Generate the lookup table # -puts $fd "static const struct sPragmaNames \173" -puts $fd " const char *const zName; /* Name of pragma */" -puts $fd " u8 ePragTyp; /* PragTyp_XXX value */" -puts $fd " u8 mPragFlag; /* Zero or more PragFlag_XXX values */" -puts $fd " u32 iArg; /* Extra argument */" -puts $fd "\175 aPragmaNames\[\] = \173" +puts $fd "\n/* Definitions of all built-in pragmas */" +puts $fd "typedef struct PragmaName \173" +puts $fd " const char *const zName; /* Name of pragma */" +puts $fd " u8 ePragTyp; /* PragTyp_XXX value */" +puts $fd " u8 mPragFlg; /* Zero or more PragFlg_XXX values */" +puts $fd { u8 iPragCName; /* Start of column names in pragCName[] */} +puts $fd " u8 nPragCName; \ +/* Num of col names. 0 means use pragma name */" +puts $fd " u32 iArg; /* Extra argument */" +puts $fd "\175 PragmaName;" +puts $fd "static const PragmaName aPragmaName\[\] = \173" set current_if {} set spacer [format { %26s } {}] foreach name $allnames { - foreach {type arg if flag} $allbyname($name) break + foreach {type arg if flag cx} $allbyname($name) break + if {$cx==0} { + set cy 0 + set nx 0 + } else { + set cy $cols_offset($cx) + set nx [llength [lindex $cols_list [expr {$cx-1}]]] + } if {$if!=$current_if} { if {$current_if!=""} { foreach this_if $current_if { @@ -443,12 +540,13 @@ foreach name $allnames { if {$flag==""} { set flagx "0" } else { - set flagx PragFlag_[join $flag {|PragFlag_}] + set flagx PragFlg_[join $flag {|PragFlg_}] } - puts $fd " \173 /* zName: */ \"$name\"," - puts $fd " /* ePragTyp: */ PragTyp_$type," - puts $fd " /* ePragFlag: */ $flagx," - puts $fd " /* iArg: */ $arg \175," + puts $fd " \173/* zName: */ \"$name\"," + puts $fd " /* ePragTyp: */ PragTyp_$type," + puts $fd " /* ePragFlg: */ $flagx," + puts $fd " /* ColNames: */ $cy, $nx," + puts $fd " /* iArg: */ $arg \175," } if {$current_if!=""} { foreach this_if $current_if { diff --git a/tool/mksqlite3c-noext.tcl b/tool/mksqlite3c-noext.tcl index fad6c9e584..a16b0591eb 100644 --- a/tool/mksqlite3c-noext.tcl +++ b/tool/mksqlite3c-noext.tcl @@ -1,7 +1,7 @@ #!/usr/bin/tclsh # # To build a single huge source file holding all of SQLite (or at -# least the core components - the test harness, shell, and TCL +# least the core components - the test harness, shell, and TCL # interface are omitted.) first do # # make target_source @@ -11,7 +11,7 @@ # there and will not work if they are not found.) There are a few # generated C code files that are also added to the tsrc directory. # For example, the "parse.c" and "parse.h" files to implement the -# the parser are derived from "parse.y" using lemon. And the +# the parser are derived from "parse.y" using lemon. And the # "keywordhash.h" files is generated by a program named "mkkeywordhash". # # After the "tsrc" directory has been created and populated, run @@ -26,15 +26,20 @@ # from in this file. The version number is needed to generate the header # comment of the amalgamation. # -if {[lsearch $argv --nostatic]>=0} { - set addstatic 0 -} else { - set addstatic 1 -} -if {[lsearch $argv --linemacros]>=0} { - set linemacros 1 -} else { - set linemacros 0 +set addstatic 1 +set linemacros 0 +set useapicall 0 +for {set i 0} {$i<[llength $argv]} {incr i} { + set x [lindex $argv $i] + if {[regexp {^-+nostatic$} $x]} { + set addstatic 0 + } elseif {[regexp {^-+linemacros} $x]} { + set linemacros 1 + } elseif {[regexp {^-+useapicall} $x]} { + set useapicall 1 + } else { + error "unknown command-line option: $x" + } } set in [open tsrc/sqlite3.h] set cnt 0 @@ -57,7 +62,7 @@ set today [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S UTC" -gmt 1] puts $out [subst \ {/****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite -** version $VERSION. By combining all the individual C code files into this +** version $VERSION. By combining all the individual C code files into this ** single large file, the entire code can be compiled as a single translation ** unit. This allows many compilers to do optimizations that would not be ** possible if the files were compiled separately. Performance improvements @@ -66,7 +71,7 @@ puts $out [subst \ ** ** This file is all you need to compile SQLite. To use SQLite in other ** programs, you need this file and the "sqlite3.h" header file that defines -** the programming interface to the SQLite library. (If you do not have +** the programming interface to the SQLite library. (If you do not have ** the "sqlite3.h" header file at hand, you will find a copy embedded within ** the text of this file. Search for "Begin file sqlite3.h" to find the start ** of the embedded sqlite3.h header file.) Additional code files may be needed @@ -83,7 +88,7 @@ if {$addstatic} { #endif} } -# These are the header files used by SQLite. The first time any of these +# These are the header files used by SQLite. The first time any of these # files are seen in a #include statement in the C code, include the complete # text of the file in-line. The file only needs to be included once. # @@ -104,8 +109,8 @@ foreach hdr { parse.h pcache.h pragma.h - sqlite3ext.h sqlite3.h + sqlite3ext.h sqliteicu.h sqliteInt.h sqliteLimit.h @@ -155,7 +160,8 @@ proc section_comment {text} { # process them appropriately. # proc copy_file {filename} { - global seen_hdr available_hdr varonly_hdr cdecllist out addstatic linemacros + global seen_hdr available_hdr varonly_hdr cdecllist out + global addstatic linemacros useapicall set ln 0 set tail [file tail $filename] section_comment "Begin file $tail" @@ -203,7 +209,8 @@ proc copy_file {filename} { puts $out "#if 0" } elseif {!$linemacros && [regexp {^#line} $line]} { # Skip #line directives. - } elseif {$addstatic && ![regexp {^(static|typedef)} $line]} { + } elseif {$addstatic + && ![regexp {^(static|typedef|SQLITE_PRIVATE)} $line]} { # Skip adding the SQLITE_PRIVATE or SQLITE_API keyword before # functions if this header file does not need it. if {![info exists varonly_hdr($tail)] @@ -211,18 +218,20 @@ proc copy_file {filename} { regsub {^SQLITE_API } $line {} line # Add the SQLITE_PRIVATE or SQLITE_API keyword before functions. # so that linkage can be modified at compile-time. - if {[regexp {^sqlite3_} $funcname]} { + if {[regexp {^sqlite3[a-z]*_} $funcname]} { set line SQLITE_API append line " " [string trim $rettype] if {[string index $rettype end] ne "*"} { append line " " } - if {[lsearch -exact $cdecllist $funcname] >= 0} { - append line SQLITE_CDECL - } else { - append line SQLITE_APICALL + if {$useapicall} { + if {[lsearch -exact $cdecllist $funcname] >= 0} { + append line SQLITE_CDECL " " + } else { + append line SQLITE_APICALL " " + } } - append line " " $funcname $rest + append line $funcname $rest puts $out $line } else { puts $out "SQLITE_PRIVATE $line" @@ -285,6 +294,7 @@ foreach file { mutex_w32.c malloc.c printf.c + treeview.c random.c threads.c utf.c @@ -313,7 +323,6 @@ foreach file { vdbe.c vdbeblob.c vdbesort.c - journal.c memjournal.c walker.c @@ -339,6 +348,8 @@ foreach file { update.c vacuum.c vtab.c + wherecode.c + whereexpr.c where.c parse.c diff --git a/tool/mksqlite3c.tcl b/tool/mksqlite3c.tcl index 7e8558d2ab..55179c4b80 100644 --- a/tool/mksqlite3c.tcl +++ b/tool/mksqlite3c.tcl @@ -1,7 +1,7 @@ #!/usr/bin/tclsh # # To build a single huge source file holding all of SQLite (or at -# least the core components - the test harness, shell, and TCL +# least the core components - the test harness, shell, and TCL # interface are omitted.) first do # # make target_source @@ -11,7 +11,7 @@ # there and will not work if they are not found.) There are a few # generated C code files that are also added to the tsrc directory. # For example, the "parse.c" and "parse.h" files to implement the -# the parser are derived from "parse.y" using lemon. And the +# the parser are derived from "parse.y" using lemon. And the # "keywordhash.h" files is generated by a program named "mkkeywordhash". # # After the "tsrc" directory has been created and populated, run @@ -28,12 +28,15 @@ # set addstatic 1 set linemacros 0 +set useapicall 0 for {set i 0} {$i<[llength $argv]} {incr i} { set x [lindex $argv $i] if {[regexp {^-+nostatic$} $x]} { set addstatic 0 } elseif {[regexp {^-+linemacros} $x]} { set linemacros 1 + } elseif {[regexp {^-+useapicall} $x]} { + set useapicall 1 } else { error "unknown command-line option: $x" } @@ -59,7 +62,7 @@ set today [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S UTC" -gmt 1] puts $out [subst \ {/****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite -** version $VERSION. By combining all the individual C code files into this +** version $VERSION. By combining all the individual C code files into this ** single large file, the entire code can be compiled as a single translation ** unit. This allows many compilers to do optimizations that would not be ** possible if the files were compiled separately. Performance improvements @@ -68,7 +71,7 @@ puts $out [subst \ ** ** This file is all you need to compile SQLite. To use SQLite in other ** programs, you need this file and the "sqlite3.h" header file that defines -** the programming interface to the SQLite library. (If you do not have +** the programming interface to the SQLite library. (If you do not have ** the "sqlite3.h" header file at hand, you will find a copy embedded within ** the text of this file. Search for "Begin file sqlite3.h" to find the start ** of the embedded sqlite3.h header file.) Additional code files may be needed @@ -85,7 +88,7 @@ if {$addstatic} { #endif} } -# These are the header files used by SQLite. The first time any of these +# These are the header files used by SQLite. The first time any of these # files are seen in a #include statement in the C code, include the complete # text of the file in-line. The file only needs to be included once. # @@ -112,7 +115,6 @@ foreach hdr { pragma.h rtree.h sqlite3session.h - sqlite3ext.h sqlite3.h sqlite3ext.h sqlite3rbu.h @@ -166,7 +168,8 @@ proc section_comment {text} { # process them appropriately. # proc copy_file {filename} { - global seen_hdr available_hdr varonly_hdr cdecllist out addstatic linemacros + global seen_hdr available_hdr varonly_hdr cdecllist out + global addstatic linemacros useapicall set ln 0 set tail [file tail $filename] section_comment "Begin file $tail" @@ -229,12 +232,14 @@ proc copy_file {filename} { if {[string index $rettype end] ne "*"} { append line " " } - if {[lsearch -exact $cdecllist $funcname] >= 0} { - append line SQLITE_CDECL - } else { - append line SQLITE_STDCALL + if {$useapicall} { + if {[lsearch -exact $cdecllist $funcname] >= 0} { + append line SQLITE_CDECL " " + } else { + append line SQLITE_APICALL " " + } } - append line " " $funcname $rest + append line $funcname $rest puts $out $line } else { puts $out "SQLITE_PRIVATE $line" diff --git a/tool/mksqlite3h.tcl b/tool/mksqlite3h.tcl index 1af6d75730..4f8a1fd497 100644 --- a/tool/mksqlite3h.tcl +++ b/tool/mksqlite3h.tcl @@ -10,17 +10,19 @@ # # Run this script by specifying the root directory of the source tree # on the command-line. -# +# # This script performs processing on src/sqlite.h.in. It: # # 1) Adds SQLITE_EXTERN in front of the declaration of global variables, # 2) Adds SQLITE_API in front of the declaration of API functions, -# 3) Replaces the string --VERS-- with the current library version, +# 3) Replaces the string --VERS-- with the current library version, # formatted as a string (e.g. "3.6.17"), and # 4) Replaces the string --VERSION-NUMBER-- with current library version, # formatted as an integer (e.g. "3006017"). -# 5) Replaces the string --SOURCE-ID-- with the date and time and sha1 +# 5) Replaces the string --SOURCE-ID-- with the date and time and sha1 # hash of the fossil-scm manifest for the source tree. +# 6) Adds the SQLITE_CALLBACK calling convention macro in front of all +# callback declarations. # # This script outputs to stdout. # @@ -34,6 +36,14 @@ # set TOP [lindex $argv 0] +# Enable use of SQLITE_APICALL macros at the right points? +# +set useapicall 0 + +if {[lsearch -regexp [lrange $argv 1 end] {^-+useapicall}] != -1} { + set useapicall 1 +} + # Get the SQLite version number (ex: 3.6.18) from the $TOP/VERSION file. # set in [open $TOP/VERSION] @@ -96,14 +106,14 @@ foreach file $filelist { puts "/******** Begin file [file tail $file] *********/" } while {![eof $in]} { - + set line [gets $in] # File sqlite3rtree.h contains a line "#include ". Omit this # line when copying sqlite3rtree.h into sqlite3.h. # if {[string match {*#include*[<"]sqlite3.h[>"]*} $line]} continue - + regsub -- --VERS-- $line $zVersion line regsub -- --VERSION-NUMBER-- $line $nVersion line regsub -- --SOURCE-ID-- $line "$zDate $zUuid" line @@ -117,14 +127,21 @@ foreach file $filelist { if {[string index $rettype end] ne "*"} { append line " " } - if {[lsearch -exact $cdecllist $funcname] >= 0} { - append line SQLITE_CDECL - } else { - append line SQLITE_STDCALL + if {$useapicall} { + if {[lsearch -exact $cdecllist $funcname] >= 0} { + append line SQLITE_CDECL " " + } else { + append line SQLITE_APICALL " " + } } - append line " " $funcname $rest + append line $funcname $rest } } + if {$useapicall} { + set line [string map [list (*sqlite3_syscall_ptr) \ + "(SQLITE_SYSAPI *sqlite3_syscall_ptr)"] $line] + regsub {\(\*} $line {(SQLITE_CALLBACK *} line + } puts $line } close $in diff --git a/tool/mkvsix.tcl b/tool/mkvsix.tcl index b7350f9c84..c874d538fb 100644 --- a/tool/mkvsix.tcl +++ b/tool/mkvsix.tcl @@ -77,7 +77,7 @@ # command prompt: # # CD /D C:\dev\sqlite\core -# tclsh85 tool\mkvsix.tcl C:\Temp +# tclsh tool\mkvsix.tcl C:\Temp # # In the example above, "C:\dev\sqlite\core" represents the root of the source # tree for SQLite and "C:\Temp" represents the top-level directory containing diff --git a/tool/showstat4.c b/tool/showstat4.c index c7f9b10fbc..e31150f0c0 100644 --- a/tool/showstat4.c +++ b/tool/showstat4.c @@ -130,11 +130,11 @@ int main(int argc, char **argv){ } printf("'"); }else{ - printf("%s\"", zSep); + printf("%s'", zSep); for(j=0; j " + } else { + puts -nonewline "% " + } + flush stdout + append line [gets stdin] + if {[info complete $line]} { + if {[catch {uplevel #0 $line} result]} { + puts stderr "Error: $result" + } elseif {$result!=""} { + puts $result + } + set line {} + } else { + append line \n + } + } +} + + # Get the name of the database to analyze # proc usage {} { @@ -34,22 +61,37 @@ information for the database and its constituent tables and indexes. Options: - --stats Output SQL text that creates a new database containing - statistics about the database that was analyzed + --pageinfo Show how each page of the database-file is used - --pageinfo Show how each page of the database-file is used + --stats Output SQL text that creates a new database containing + statistics about the database that was analyzed + + --tclsh Run the built-in TCL interpreter interactively (for debugging) + + --version Show the version number of SQLite } exit 1 } set file_to_analyze {} set flags(-pageinfo) 0 set flags(-stats) 0 +set flags(-debug) 0 append argv {} foreach arg $argv { if {[regexp {^-+pageinfo$} $arg]} { set flags(-pageinfo) 1 } elseif {[regexp {^-+stats$} $arg]} { set flags(-stats) 1 + } elseif {[regexp {^-+debug$} $arg]} { + set flags(-debug) 1 + } elseif {[regexp {^-+tclsh$} $arg]} { + tclsh + exit 0 + } elseif {[regexp {^-+version$} $arg]} { + sqlite3 mem :memory: + puts [mem one {SELECT sqlite_version()||' '||sqlite_source_id()}] + mem close + exit 0 } elseif {[regexp {^-} $arg]} { puts stderr "Unknown option: $arg" usage @@ -100,6 +142,10 @@ if {[catch {sqlite3 db $file_to_analyze -uri 1} msg]} { puts stderr "error trying to open $file_to_analyze: $msg" exit 1 } +if {$flags(-debug)} { + proc dbtrace {txt} {puts $txt; flush stdout;} + db trace ::dbtrace +} db eval {SELECT count(*) FROM sqlite_master} set pageSize [expr {wide([db one {PRAGMA page_size}])}] @@ -142,12 +188,17 @@ if {$flags(-stats)} { exit 0 } + # In-memory database for collecting statistics. This script loops through # the tables and indices in the database being analyzed, adding a row for each # to an in-memory database (for which the schema is shown below). It then # queries the in-memory db to produce the space-analysis report. # sqlite3 mem :memory: +if {$flags(-debug)} { + proc dbtrace {txt} {puts $txt; flush stdout;} + mem trace ::dbtrace +} set tabledef {CREATE TABLE space_used( name clob, -- Name of a table or index in the database file tblname clob, -- Name of associated table diff --git a/tool/speed-check.sh b/tool/speed-check.sh index d10f172d59..d6e7eb6d25 100644 --- a/tool/speed-check.sh +++ b/tool/speed-check.sh @@ -21,9 +21,20 @@ then fi NAME=$1 shift -CC_OPTS="-DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_MEMSYS5" -SPEEDTEST_OPTS="--shrink-memory --reprepare --heap 10000000 64" +#CC_OPTS="-DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_MEMSYS5" +CC_OPTS="-DSQLITE_ENABLE_MEMSYS5" +SPEEDTEST_OPTS="--shrink-memory --reprepare --stats --heap 10000000 64" SIZE=5 +LEAN_OPTS="-DSQLITE_THREADSAFE=0" +LEAN_OPTS="$LEAN_OPTS -DSQLITE_DEFAULT_MEMSTATUS=0" +LEAN_OPTS="$LEAN_OPTS -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1" +LEAN_OPTS="$LEAN_OPTS -DSQLITE_LIKE_DOESNT_MATCH_BLOB" +LEAN_OPTS="$LEAN_OPTS -DSQLITE_MAX_EXPR_DEPTH=0" +LEAN_OPTS="$LEAN_OPTS -DSQLITE_OMIT_DECLTYPE" +LEAN_OPTS="$LEAN_OPTS -DSQLITE_OMIT_DEPRECATED" +LEAN_OPTS="$LEAN_OPTS -DSQLITE_OMIT_PROGRESS_CALLBACK" +LEAN_OPTS="$LEAN_OPTS -DSQLITE_OMIT_SHARED_CACHE" +LEAN_OPTS="$LEAN_OPTS -DSQLITE_USE_ALLOCA" doExplain=0 doCachegrind=1 while test "$1" != ""; do @@ -55,6 +66,9 @@ while test "$1" != ""; do --size) shift; SIZE=$1 ;; + --cachesize) + shift; SPEEDTEST_OPTS="$SPEEDTEST_OPTS --cachesize $1" + ;; --explain) doExplain=1 ;; @@ -63,11 +77,23 @@ while test "$1" != ""; do CC_OPTS="$CC_OPTS -DVDBE_PROFILE" doCachegrind=0 ;; + --lean) + CC_OPTS="$CC_OPTS $LEAN_OPTS" + ;; --heap) CC_OPTS="$CC_OPTS -DSQLITE_ENABLE_MEMSYS5" shift; SPEEDTEST_OPTS="$SPEEDTEST_OPTS --heap $1 64" ;; + --repeat) + CC_OPTS="$CC_OPTS -DSQLITE_ENABLE_RCACHE" + shift; + SPEEDTEST_OPTS="$SPEEDTEST_OPTS --repeat $1" + ;; + --mmap) + shift; + SPEEDTEST_OPTS="$SPEEDTEST_OPTS --mmap $1" + ;; *) CC_OPTS="$CC_OPTS $1" ;; diff --git a/tool/sqldiff.c b/tool/sqldiff.c index b1363bb341..67f3197bbf 100644 --- a/tool/sqldiff.c +++ b/tool/sqldiff.c @@ -403,7 +403,8 @@ static void printQuoted(FILE *out, sqlite3_value *X){ } fprintf(out, "'"); }else{ - fprintf(out, "NULL"); + /* Could be an OOM, could be a zero-byte blob */ + fprintf(out, "X''"); } break; } @@ -1179,8 +1180,9 @@ static void getRbudiffQuery( strPrintf(pSql, " FROM aux.%Q AS n WHERE NOT EXISTS (\n", zTab); strPrintf(pSql, " SELECT 1 FROM ", zTab); strPrintf(pSql, " main.%Q AS o WHERE ", zTab); - strPrintfArray(pSql, " AND ", "(n.%Q IS o.%Q)", azCol, nPK); - strPrintf(pSql, "\n)"); + strPrintfArray(pSql, " AND ", "(n.%Q = o.%Q)", azCol, nPK); + strPrintf(pSql, "\n) AND "); + strPrintfArray(pSql, " AND ", "(n.%Q IS NOT NULL)", azCol, nPK); /* Deleted rows: */ strPrintf(pSql, "\nUNION ALL\nSELECT "); @@ -1194,8 +1196,9 @@ static void getRbudiffQuery( strPrintf(pSql, " FROM main.%Q AS n WHERE NOT EXISTS (\n", zTab); strPrintf(pSql, " SELECT 1 FROM ", zTab); strPrintf(pSql, " aux.%Q AS o WHERE ", zTab); - strPrintfArray(pSql, " AND ", "(n.%Q IS o.%Q)", azCol, nPK); - strPrintf(pSql, "\n) "); + strPrintfArray(pSql, " AND ", "(n.%Q = o.%Q)", azCol, nPK); + strPrintf(pSql, "\n) AND "); + strPrintfArray(pSql, " AND ", "(n.%Q IS NOT NULL)", azCol, nPK); /* Updated rows. If all table columns are part of the primary key, there ** can be no updates. In this case this part of the compound SELECT can @@ -1226,7 +1229,7 @@ static void getRbudiffQuery( ); strPrintf(pSql, "\nFROM main.%Q AS o, aux.%Q AS n\nWHERE ", zTab, zTab); - strPrintfArray(pSql, " AND ", "(n.%Q IS o.%Q)", azCol, nPK); + strPrintfArray(pSql, " AND ", "(n.%Q = o.%Q)", azCol, nPK); strPrintf(pSql, " AND ota_control LIKE '%%x%%'"); } diff --git a/tool/warnings.sh b/tool/warnings.sh index cee639dc45..ae40cfb1ae 100644 --- a/tool/warnings.sh +++ b/tool/warnings.sh @@ -3,15 +3,26 @@ # Run this script in a directory with a working makefile to check for # compiler warnings in SQLite. # + +# Use these for testing on Linux and Mac OSX: +WARNING_OPTS="-Wshadow -Wall -Wextra -pedantic-errors -Wno-long-long" +WARNING_ANDROID_OPTS="-Wshadow -Wall -Wextra" + +# Use these for testing on OpenBSD: +# WARNING_OPTS=-Wall +# WARNING_ANDROID_OPTS=-Wall + rm -f sqlite3.c make sqlite3.c echo '********** No optimizations. Includes FTS4/5, RTREE, JSON1 ***' -gcc -c -Wshadow -Wall -Wextra -pedantic-errors -Wno-long-long -std=c89 \ +echo '********** ' Options: $WARNING_OPTS +gcc -c $WARNING_OPTS -std=c89 \ -ansi -DHAVE_STDINT_H -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_RTREE \ -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_JSON1 \ sqlite3.c if test x`uname` = 'xLinux'; then echo '********** Android configuration ******************************' +echo '********** ' Options: $WARNING_ANDROID_OPTS gcc -c \ -DHAVE_USLEEP=1 \ -DSQLITE_HAVE_ISNAN \ @@ -31,15 +42,17 @@ gcc -c \ -DSQLITE_DEFAULT_FILE_PERMISSIONS=0600 \ -DSQLITE_ENABLE_ICU \ -DUSE_PREAD64 \ - -Wshadow -Wall -Wextra \ + $WARNING_ANDROID_OPTS \ -Os sqlite3.c shell.c fi echo '********** No optimizations. ENABLE_STAT4. THREADSAFE=0 *******' -gcc -c -Wshadow -Wall -Wextra -pedantic-errors -Wno-long-long -std=c89 \ +echo '********** ' Options: $WARNING_OPTS +gcc -c $WARNING_OPTS -std=c89 \ -ansi -DSQLITE_ENABLE_STAT4 -DSQLITE_THREADSAFE=0 \ sqlite3.c echo '********** Optimized -O3. Includes FTS4/5, RTREE, JSON1 ******' -gcc -O3 -c -Wshadow -Wall -Wextra -pedantic-errors -Wno-long-long -std=c89 \ +echo '********** ' Options: $WARNING_OPTS +gcc -O3 -c $WARNING_OPTS -std=c89 \ -ansi -DHAVE_STDINT_H -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_RTREE \ -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_JSON1 \ sqlite3.c