diff --git a/.fossil-settings/empty-dirs b/.fossil-settings/empty-dirs new file mode 100644 index 0000000000..64fb6839df --- /dev/null +++ b/.fossil-settings/empty-dirs @@ -0,0 +1 @@ +compat diff --git a/.fossil-settings/ignore-glob b/.fossil-settings/ignore-glob new file mode 100644 index 0000000000..5282ca9c3d --- /dev/null +++ b/.fossil-settings/ignore-glob @@ -0,0 +1 @@ +compat/* diff --git a/Makefile.in b/Makefile.in index 567f31215f..1766ca580e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -89,6 +89,9 @@ TCC += $(OPT_FEATURE_FLAGS) # ie. make "OPTS=-DSQLITE_ENABLE_FOO=1 -DSQLITE_OMIT_FOO=1". TCC += $(OPTS) +# Add in compile-time options for some libraries used by extensions +TCC += @HAVE_ZLIB@ + # Version numbers and release number for the SQLite being compiled. # VERSION = @VERSION@ @@ -422,6 +425,8 @@ TESTSRC = \ # Statically linked extensions # TESTSRC += \ + $(TOP)/ext/expert/sqlite3expert.c \ + $(TOP)/ext/expert/test_expert.c \ $(TOP)/ext/misc/amatch.c \ $(TOP)/ext/misc/carray.c \ $(TOP)/ext/misc/closure.c \ @@ -435,6 +440,7 @@ TESTSRC += \ $(TOP)/ext/misc/ieee754.c \ $(TOP)/ext/misc/mmapwarm.c \ $(TOP)/ext/misc/nextchar.c \ + $(TOP)/ext/misc/normalize.c \ $(TOP)/ext/misc/percentile.c \ $(TOP)/ext/misc/regexp.c \ $(TOP)/ext/misc/remember.c \ @@ -442,7 +448,8 @@ TESTSRC += \ $(TOP)/ext/misc/spellfix.c \ $(TOP)/ext/misc/totype.c \ $(TOP)/ext/misc/unionvtab.c \ - $(TOP)/ext/misc/wholenumber.c + $(TOP)/ext/misc/wholenumber.c \ + $(TOP)/ext/misc/zipfile.c # Source code to the library files needed by the test fixture # @@ -553,7 +560,8 @@ TESTPROGS = \ sqlite3$(TEXE) \ sqlite3_analyzer$(TEXE) \ sqldiff$(TEXE) \ - dbhash$(TEXE) + dbhash$(TEXE) \ + sqltclsh$(TEXE) # Databases containing fuzzer test cases # @@ -571,15 +579,19 @@ TESTOPTS = --verbose=file --output=test-out.txt # Extra compiler options for various shell tools # SHELL_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS4 -# SHELL_OPT += -DSQLITE_ENABLE_FTS5 +#SHELL_OPT += -DSQLITE_ENABLE_FTS5 +SHELL_OPT += -DSQLITE_ENABLE_RTREE SHELL_OPT += -DSQLITE_ENABLE_EXPLAIN_COMMENTS SHELL_OPT += -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION SHELL_OPT += -DSQLITE_ENABLE_STMTVTAB SHELL_OPT += -DSQLITE_ENABLE_DBPAGE_VTAB SHELL_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB +SHELL_OPT += -DSQLITE_ENABLE_OFFSET_SQL_FUNC +SHELL_OPT += -DSQLITE_INTROSPECTION_PRAGMAS FUZZERSHELL_OPT = -DSQLITE_ENABLE_JSON1 FUZZCHECK_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_OSS_FUZZ FUZZCHECK_OPT += -DSQLITE_MAX_MEMORY=50000000 +FUZZCHECK_OPT += -DSQLITE_PRINTF_PRECISION_LIMIT=1000 FUZZCHECK_SRC = $(TOP)/test/fuzzcheck.c $(TOP)/test/ossfuzz.c DBFUZZ_OPT = @@ -985,9 +997,15 @@ keywordhash.h: $(TOP)/tool/mkkeywordhash.c # Source files that go into making shell.c SHELL_SRC = \ $(TOP)/src/shell.c.in \ + $(TOP)/ext/misc/appendvfs.c \ $(TOP)/ext/misc/shathree.c \ $(TOP)/ext/misc/fileio.c \ - $(TOP)/ext/misc/completion.c + $(TOP)/ext/misc/completion.c \ + $(TOP)/ext/misc/sqlar.c \ + $(TOP)/ext/expert/sqlite3expert.c \ + $(TOP)/ext/expert/sqlite3expert.h \ + $(TOP)/ext/misc/zipfile.c \ + $(TOP)/src/test_windirent.c shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl $(TCLSH_CMD) $(TOP)/tool/mkshellc.tcl >shell.c @@ -1187,6 +1205,15 @@ sqlite3_analyzer.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/spaceanal.tcl $ sqlite3_analyzer$(TEXE): sqlite3_analyzer.c $(LTLINK) sqlite3_analyzer.c -o $@ $(LIBTCL) $(TLIBS) +sqltclsh.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/sqltclsh.tcl $(TOP)/ext/misc/appendvfs.c $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqltclsh.c.in + $(TCLSH_CMD) $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqltclsh.c.in >sqltclsh.c + +sqltclsh$(TEXE): sqltclsh.c + $(LTLINK) sqltclsh.c -o $@ $(LIBTCL) $(TLIBS) + +sqlite3_expert$(TEXE): $(TOP)/ext/expert/sqlite3expert.h $(TOP)/ext/expert/sqlite3expert.c $(TOP)/ext/expert/expert.c sqlite3.c + $(LTLINK) $(TOP)/ext/expert/sqlite3expert.h $(TOP)/ext/expert/sqlite3expert.c $(TOP)/ext/expert/expert.c sqlite3.c -o sqlite3_expert $(TLIBS) + CHECKER_DEPS =\ $(TOP)/tool/mkccode.tcl \ sqlite3.c \ diff --git a/Makefile.msc b/Makefile.msc index 9c834931d7..1b2bcb55ac 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -92,6 +92,29 @@ SPLIT_AMALGAMATION = 0 !ENDIF # <> +# Set this non-0 to have this makefile assume the Tcl shell executable +# (tclsh*.exe) is available in the PATH. By default, this is disabled +# for compatibility with older build environments. This setting only +# applies if TCLSH_CMD is not set manually. +# +!IFNDEF USE_TCLSH_IN_PATH +USE_TCLSH_IN_PATH = 0 +!ENDIF + +# Set this non-0 to use zlib, possibly compiling it from source code. +# +!IFNDEF USE_ZLIB +USE_ZLIB = 0 +!ENDIF + +# Set this non-0 to build zlib from source code. This is enabled by +# default and in that case it will be assumed that the ZLIBDIR macro +# points to the top-level source code directory for zlib. +# +!IFNDEF BUILD_ZLIB +BUILD_ZLIB = 1 +!ENDIF + # Set this non-0 to use the International Components for Unicode (ICU). # !IFNDEF USE_ICU @@ -612,6 +635,15 @@ SHELL_CORE_DEP = !ENDIF !ENDIF +# <> +# If zlib support is enabled, add the dependencies for it. +# +!IF $(USE_ZLIB)!=0 && $(BUILD_ZLIB)!=0 +SHELL_CORE_DEP = zlib $(SHELL_CORE_DEP) +TESTFIXTURE_DEP = zlib $(TESTFIXTURE_DEP) +!ENDIF +# <> + # This is the core library that the shell executable should link with. # !IFNDEF SHELL_CORE_LIB @@ -802,12 +834,16 @@ RCC = $(RCC) -DSQLITE_WIN32_MALLOC_VALIDATE=1 # prior to running nmake in order to match the actual installed location and # version on this machine. # +!IFNDEF TCLDIR +TCLDIR = $(TOP)\compat\tcl +!ENDIF + !IFNDEF TCLINCDIR -TCLINCDIR = c:\tcl\include +TCLINCDIR = $(TCLDIR)\include !ENDIF !IFNDEF TCLLIBDIR -TCLLIBDIR = c:\tcl\lib +TCLLIBDIR = $(TCLDIR)\lib !ENDIF !IFNDEF LIBTCL @@ -819,7 +855,32 @@ LIBTCLSTUB = tclstub86.lib !ENDIF !IFNDEF LIBTCLPATH -LIBTCLPATH = c:\tcl\bin +LIBTCLPATH = $(TCLDIR)\bin +!ENDIF + +# The locations of the zlib header and library files. These variables +# (ZLIBINCDIR, ZLIBLIBDIR, and ZLIBLIB) may be overridden via the environment +# prior to running nmake in order to match the actual installed (or source +# code) location on this machine. +# +!IFNDEF ZLIBDIR +ZLIBDIR = $(TOP)\compat\zlib +!ENDIF + +!IFNDEF ZLIBINCDIR +ZLIBINCDIR = $(ZLIBDIR) +!ENDIF + +!IFNDEF ZLIBLIBDIR +ZLIBLIBDIR = $(ZLIBDIR) +!ENDIF + +!IFNDEF ZLIBLIB +!IF $(DYNAMIC_SHELL)!=0 +ZLIBLIB = zdll.lib +!ELSE +ZLIBLIB = zlib.lib +!ENDIF !ENDIF # The locations of the ICU header and library files. These variables @@ -827,12 +888,16 @@ LIBTCLPATH = c:\tcl\bin # prior to running nmake in order to match the actual installed location on # this machine. # +!IFNDEF ICUDIR +ICUDIR = $(TOP)\compat\icu +!ENDIF + !IFNDEF ICUINCDIR -ICUINCDIR = c:\icu\include +ICUINCDIR = $(ICUDIR)\include !ENDIF !IFNDEF ICULIBDIR -ICULIBDIR = c:\icu\lib +ICULIBDIR = $(ICUDIR)\lib !ENDIF !IFNDEF LIBICU @@ -845,7 +910,11 @@ LIBICU = icuuc.lib icuin.lib # specific Tcl shell to use. # !IFNDEF TCLSH_CMD +!IF $(USE_TCLSH_IN_PATH)!=0 || !EXIST("$(TCLDIR)\bin\tclsh.exe") TCLSH_CMD = tclsh +!ELSE +TCLSH_CMD = $(TCLDIR)\bin\tclsh.exe +!ENDIF !ENDIF # <> @@ -951,6 +1020,15 @@ BCC = $(BCC) -Zi !ENDIF # <> +# If zlib support is enabled, add the compiler options for it. +# +!IF $(USE_ZLIB)!=0 +TCC = $(TCC) -DSQLITE_HAVE_ZLIB=1 +RCC = $(RCC) -DSQLITE_HAVE_ZLIB=1 +TCC = $(TCC) -I$(ZLIBINCDIR) +RCC = $(RCC) -I$(ZLIBINCDIR) +!ENDIF + # If ICU support is enabled, add the compiler options for it. # !IF $(USE_ICU)!=0 @@ -974,7 +1052,7 @@ LTLINK = $(TCC) -Fe$@ # If requested, link to the RPCRT4 library. # !IF $(USE_RPCRT4_LIB)!=0 -LTLINK = $(LTLINK) rpcrt4.lib +LTLIBS = $(LTLIBS) rpcrt4.lib !ENDIF # If a platform was set, force the linker to target that. @@ -1071,8 +1149,15 @@ LDFLAGS = $(LDOPTS) # Start with the Tcl related linker options. # !IF $(NO_TCL)==0 -LTLIBPATHS = /LIBPATH:$(TCLLIBDIR) -LTLIBS = $(LIBTCL) +TCLLIBPATHS = $(TCLLIBPATHS) /LIBPATH:$(TCLLIBDIR) +TCLLIBS = $(TCLLIBS) $(LIBTCL) +!ENDIF + +# If zlib support is enabled, add the linker options for it. +# +!IF $(USE_ZLIB)!=0 +LTLIBPATHS = $(LTLIBPATHS) /LIBPATH:$(ZLIBLIBDIR) +LTLIBS = $(LTLIBS) $(ZLIBLIB) !ENDIF # If ICU support is enabled, add the linker options for it. @@ -1400,6 +1485,8 @@ TESTSRC = \ # Statically linked extensions. # TESTEXT = \ + $(TOP)\ext\expert\sqlite3expert.c \ + $(TOP)\ext\expert\test_expert.c \ $(TOP)\ext\misc\amatch.c \ $(TOP)\ext\misc\carray.c \ $(TOP)\ext\misc\closure.c \ @@ -1413,6 +1500,7 @@ TESTEXT = \ $(TOP)\ext\misc\ieee754.c \ $(TOP)\ext\misc\mmapwarm.c \ $(TOP)\ext\misc\nextchar.c \ + $(TOP)\ext\misc\normalize.c \ $(TOP)\ext\misc\percentile.c \ $(TOP)\ext\misc\regexp.c \ $(TOP)\ext\misc\remember.c \ @@ -1422,6 +1510,12 @@ TESTEXT = \ $(TOP)\ext\misc\unionvtab.c \ $(TOP)\ext\misc\wholenumber.c +# If use of zlib is enabled, add the "zipfile.c" source file. +# +!IF $(USE_ZLIB)!=0 +TESTEXT = $(TESTEXT) $(TOP)\ext\misc\zipfile.c +!ENDIF + # Source code to the library files needed by the test fixture # (non-amalgamation) # @@ -1493,7 +1587,8 @@ TESTPROGS = \ sqlite3_analyzer.exe \ sqlite3_checker.exe \ sqldiff.exe \ - dbhash.exe + dbhash.exe \ + sqltclsh.exe # Databases containing fuzzer test cases # @@ -1511,6 +1606,8 @@ FUZZDATA = \ !IF $(DYNAMIC_SHELL)==0 && $(FOR_WIN10)==0 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_STMTVTAB SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ENABLE_DBSTAT_VTAB +SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC -DSQLITE_INTROSPECTION_PRAGMAS +SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_RTREE !ENDIF # <> @@ -1518,7 +1615,7 @@ SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ # MPTESTER_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 FUZZERSHELL_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1 -FUZZCHECK_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_OSS_FUZZ -DSQLITE_MAX_MEMORY=50000000 +FUZZCHECK_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_OSS_FUZZ -DSQLITE_MAX_MEMORY=50000000 -DSQLITE_PRINTF_PRECISION_LIMIT=1000 FUZZCHECK_SRC = $(TOP)\test\fuzzcheck.c $(TOP)\test\ossfuzz.c OSSSHELL_SRC = $(TOP)\test\ossshell.c $(TOP)\test\ossfuzz.c DBFUZZ_COMPILE_OPTS = -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION @@ -1542,7 +1639,15 @@ ALL_TCL_TARGETS = # This is the default Makefile target. The objects listed here # are what get build when you type just "make" with no arguments. # -all: dll libsqlite3.lib shell $(ALL_TCL_TARGETS) +core: dll libsqlite3.lib shell + +# Targets that require the Tcl library. +# +tcl: $(ALL_TCL_TARGETS) + +# This Makefile target builds all of the standard binaries. +# +all: core tcl # Dynamic link library section. # @@ -1937,7 +2042,7 @@ tclsqlite-shell.lo: $(TOP)\src\tclsqlite.c $(HDR) $(SQLITE_TCL_DEP) $(LTCOMPILE) $(NO_WARN) -DTCLSH -DBUILD_sqlite -I$(TCLINCDIR) -c $(TOP)\src\tclsqlite.c tclsqlite3.exe: tclsqlite-shell.lo $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS) - $(LTLINK) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(LTLIBPATHS) /OUT:$@ tclsqlite-shell.lo $(LIBRESOBJS) $(LTLIBS) $(TLIBS) + $(LTLINK) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(TCLLIBPATHS) $(LTLIBPATHS) /OUT:$@ tclsqlite-shell.lo $(LIBRESOBJS) $(TCLLIBS) $(LTLIBS) $(TLIBS) # Rules to build opcodes.c and opcodes.h # @@ -1980,13 +2085,26 @@ keywordhash.h: $(TOP)\tool\mkkeywordhash.c mkkeywordhash.exe # Source files that go into making shell.c SHELL_SRC = \ $(TOP)\src\shell.c.in \ + $(TOP)\ext\misc\appendvfs.c \ $(TOP)\ext\misc\shathree.c \ $(TOP)\ext\misc\fileio.c \ - $(TOP)\ext\misc\completion.c + $(TOP)\ext\misc\completion.c \ + $(TOP)\ext\expert\sqlite3expert.c \ + $(TOP)\ext\expert\sqlite3expert.h \ + $(TOP)\src\test_windirent.c + +# If use of zlib is enabled, add the "zipfile.c" source file. +# +!IF $(USE_ZLIB)!=0 +SHELL_SRC = $(SHELL_SRC) $(TOP)\ext\misc\sqlar.c +SHELL_SRC = $(SHELL_SRC) $(TOP)\ext\misc\zipfile.c +!ENDIF shell.c: $(SHELL_SRC) $(TOP)\tool\mkshellc.tcl $(TCLSH_CMD) $(TOP)\tool\mkshellc.tcl > shell.c +zlib: + pushd $(ZLIBDIR) && $(MAKE) /f win32\Makefile.msc clean $(ZLIBLIB) && popd # Rules to build the extension objects. # @@ -2075,6 +2193,24 @@ FTS5_SRC = \ $(TOP)\ext\fts5\fts5_varint.c \ $(TOP)\ext\fts5\fts5_vocab.c +LSM1_SRC = \ + $(TOP)\ext\lsm1\lsm.h \ + $(TOP)\ext\lsm1\lsmInt.h \ + $(TOP)\ext\lsm1\lsm_ckpt.c \ + $(TOP)\ext\lsm1\lsm_file.c \ + $(TOP)\ext\lsm1\lsm_log.c \ + $(TOP)\ext\lsm1\lsm_main.c \ + $(TOP)\ext\lsm1\lsm_mem.c \ + $(TOP)\ext\lsm1\lsm_mutex.c \ + $(TOP)\ext\lsm1\lsm_shared.c \ + $(TOP)\ext\lsm1\lsm_sorted.c \ + $(TOP)\ext\lsm1\lsm_str.c \ + $(TOP)\ext\lsm1\lsm_tree.c \ + $(TOP)\ext\lsm1\lsm_unix.c \ + $(TOP)\ext\lsm1\lsm_varint.c \ + $(TOP)\ext\lsm1\lsm_vtab.c \ + $(TOP)\ext\lsm1\lsm_win32.c + fts5parse.c: $(TOP)\ext\fts5\fts5parse.y lemon.exe copy $(TOP)\ext\fts5\fts5parse.y . del /Q fts5parse.h 2>NUL @@ -2086,6 +2222,10 @@ fts5.c: $(FTS5_SRC) $(TCLSH_CMD) $(TOP)\ext\fts5\tool\mkfts5c.tcl copy $(TOP)\ext\fts5\fts5.h . +lsm1.c: $(LSM1_SRC) + $(TCLSH_CMD) $(TOP)\ext\lsm1\tool\mklsm1c.tcl + copy $(TOP)\ext\lsm1\lsm.h . + fts5.lo: fts5.c $(HDR) $(EXTHDR) $(LTCOMPILE) $(CORE_COMPILE_OPTS) $(NO_WARN) -DSQLITE_CORE -c fts5.c @@ -2112,6 +2252,7 @@ TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_SERIES_CONSTRAINT_VERIFY=1 TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_DEFAULT_PAGE_SIZE=1024 TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_ENABLE_STMTVTAB TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_ENABLE_DBPAGE_VTAB +TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_ENABLE_JSON1 TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) $(TEST_CCONV_OPTS) TESTFIXTURE_SRC0 = $(TESTEXT) $(TESTSRC2) @@ -2142,11 +2283,11 @@ sqlite_tcl.h: | $(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) +testfixture.exe: $(TESTFIXTURE_SRC) $(TESTFIXTURE_DEP) $(SQLITE3H) $(LIBRESOBJS) $(HDR) $(SQLITE_TCL_DEP) $(LTLINK) -DSQLITE_NO_SYNC=1 $(TESTFIXTURE_FLAGS) \ -DBUILD_sqlite -I$(TCLINCDIR) \ $(TESTFIXTURE_SRC) \ - /link $(LDFLAGS) $(LTLINKOPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LTLIBS) $(TLIBS) + /link $(LDFLAGS) $(LTLINKOPTS) $(TCLLIBPATHS) $(LTLIBPATHS) $(LIBRESOBJS) $(TCLLIBS) $(LTLIBS) $(TLIBS) extensiontest: testfixture.exe testloadext.dll @set PATH=$(LIBTCLPATH);$(PATH) @@ -2196,7 +2337,17 @@ sqlite3_analyzer.c: $(SQLITE3C) $(SQLITE3H) $(TOP)\src\tclsqlite.c $(TOP)\tool\s sqlite3_analyzer.exe: sqlite3_analyzer.c $(LIBRESOBJS) $(LTLINK) $(NO_WARN) -DBUILD_sqlite -I$(TCLINCDIR) sqlite3_analyzer.c \ - /link $(LDFLAGS) $(LTLINKOPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LTLIBS) $(TLIBS) + /link $(LDFLAGS) $(LTLINKOPTS) $(TCLLIBPATHS) $(LTLIBPATHS) $(LIBRESOBJS) $(TCLLIBS) $(LTLIBS) $(TLIBS) + +sqltclsh.c: sqlite3.c $(TOP)\src\tclsqlite.c $(TOP)\tool\sqltclsh.tcl $(TOP)\ext\misc\appendvfs.c $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqltclsh.c.in + $(TCLSH_CMD) $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqltclsh.c.in >sqltclsh.c + +sqltclsh.exe: sqltclsh.c $(SHELL_CORE_DEP) $(LIBRESOBJS) + $(LTLINK) $(NO_WARN) -DBUILD_sqlite -I$(TCLINCDIR) sqltclsh.c \ + /link $(LDFLAGS) $(LTLINKOPTS) $(TCLLIBPATHS) $(LTLIBPATHS) $(LIBRESOBJS) $(TCLLIBS) $(LTLIBS) $(TLIBS) + +sqlite3_expert.exe: $(SQLITE3C) $(TOP)\ext\expert\sqlite3expert.h $(TOP)\ext\expert\sqlite3expert.c $(TOP)\ext\expert\expert.c + $(LTLINK) $(NO_WARN) $(TOP)\ext\expert\sqlite3expert.c $(TOP)\ext\expert\expert.c $(SQLITE3C) $(TLIBS) CHECKER_DEPS =\ $(TOP)/tool/mkccode.tcl \ @@ -2213,7 +2364,7 @@ sqlite3_checker.c: $(CHECKER_DEPS) sqlite3_checker.exe: sqlite3_checker.c $(LIBRESOBJS) $(LTLINK) $(NO_WARN) -DBUILD_sqlite -I$(TCLINCDIR) sqlite3_checker.c \ - /link $(LDFLAGS) $(LTLINKOPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LTLIBS) $(TLIBS) + /link $(LDFLAGS) $(LTLINKOPTS) $(TCLLIBPATHS) $(LTLIBPATHS) $(LIBRESOBJS) $(TCLLIBS) $(LTLIBS) $(TLIBS) dbdump.exe: $(TOP)\ext\misc\dbdump.c $(SQLITE3C) $(SQLITE3H) $(LTLINK) $(NO_WARN) -DDBDUMP_STANDALONE $(TOP)\ext\misc\dbdump.c $(SQLITE3C) \ @@ -2314,5 +2465,7 @@ clean: del /Q sqlite3_analyzer.exe sqlite3_analyzer.c 2>NUL del /Q sqlite-*-output.vsix 2>NUL del /Q fuzzershell.exe fuzzcheck.exe sqldiff.exe dbhash.exe 2>NUL + del /Q sqltclsh.exe 2>NUL del /Q fts5.* fts5parse.* 2>NUL + del /Q lsm.h lsm1.c 2>NUL # <> diff --git a/README.md b/README.md index a7b8701106..a92e5ae24e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

SQLite Source Repository

This repository contains the complete source code for the SQLite database -engine. Some test scripts are also include. However, many other test scripts +engine. Some test scripts are also included. However, many other test scripts and most of the documentation are managed separately. If you are reading this on a Git mirror someplace, you are doing it wrong. @@ -14,15 +14,17 @@ SQLite sources are managed using the [Fossil](https://www.fossil-scm.org/), a distributed version control system that was specifically designed to support SQLite development. If you do not want to use Fossil, you can download tarballs or ZIP -archives as follows: +archives or [SQLite archives](https://sqlite.org/cli.html#sqlar) as follows: - * Lastest trunk check-in: - or - . + * Lastest trunk check-in as + [Tarball](https://www.sqlite.org/src/tarball/sqlite.tar.gz), + [ZIP-archive](https://www.sqlite.org/src/zip/sqlite.zip), or + [SQLite-archive](https://www.sqlite.org/src/sqlar/sqlite.sqlar). - * Latest release: - or - . + * Latest release as + [Tarball](https://www.sqlite.org/src/tarball/sqlite.tar.gz?r=release), + [ZIP-archive](https://www.sqlite.org/src/zip/sqlite.zip?r=release), or + [SQLite-archive](https://www.sqlite.org/src/sqlar/sqlite.sqlar?r=release). * For other check-ins, substitute an appropriate branch name or tag or hash prefix for "release" in the URLs of the previous @@ -104,7 +106,6 @@ recommended. SQLite does not require [Tcl](http://www.tcl.tk/) to run, but a Tcl installation is required by the makefiles (including those for MSVC). SQLite contains a lot of generated code and Tcl is used to do much of that code generation. -The makefiles also require AWK. ## Source Code Tour @@ -116,7 +117,7 @@ The **src/** also contains the "shell.c" file which is the main program for the "sqlite3.exe" [command-line shell](https://sqlite.org/cli.html) and the "tclsqlite.c" file which implements the -[TCL bindings](https://sqlite.org/tclsqlite.html) for SQLite. +[Tcl bindings](https://sqlite.org/tclsqlite.html) for SQLite. (Historical note: SQLite began as a Tcl extension and only later escaped to the wild as an independent library.) @@ -163,14 +164,14 @@ template for generating its parser. Lemon also generates the **parse.h** header file, at the same time it generates parse.c. But the parse.h header file is -modified further (to add additional symbols) using the ./addopcodes.awk -AWK script. +modified further (to add additional symbols) using the ./addopcodes.tcl +Tcl script. The **opcodes.h** header file contains macros that define the numbers corresponding to opcodes in the "VDBE" virtual machine. The opcodes.h file is generated by the scanning the src/vdbe.c source file. The -AWK script at ./mkopcodeh.awk does this scan and generates opcodes.h. -A second AWK script, ./mkopcodec.awk, then scans opcodes.h to generate +Tcl script at ./mkopcodeh.tcl does this scan and generates opcodes.h. +A second Tcl script, ./mkopcodec.tcl, then scans opcodes.h to generate the **opcodes.c** source file, which contains a reverse mapping from opcode-number to opcode-name that is used for EXPLAIN output. @@ -207,8 +208,8 @@ The amalgamation source file is more than 200K lines long. Some symbolic debuggers (most notably MSVC) are unable to deal with files longer than 64K lines. To work around this, a separate Tcl script, tool/split-sqlite3c.tcl, can be run on the amalgamation to break it up into a single small C file -called **sqlite3-all.c** that does #include on about five other files -named **sqlite3-1.c**, **sqlite3-2.c**, ..., **sqlite3-5.c**. In this way, +called **sqlite3-all.c** that does #include on about seven other files +named **sqlite3-1.c**, **sqlite3-2.c**, ..., **sqlite3-7.c**. In this way, all of the source code is contained within a single translation unit so that the compiler can do extra cross-procedure optimization, but no individual source file exceeds 32K lines in length. @@ -237,7 +238,8 @@ Key files: trying to understand how the library works internally. * **sqliteInt.h** - this header file defines many of the data objects - used internally by SQLite. + used internally by SQLite. In addition to "sqliteInt.h", some + subsystems have their own header files. * **parse.y** - This file describes the LALR(1) grammar that SQLite uses to parse SQL statements, and the actions that are taken at each step @@ -249,29 +251,44 @@ Key files: which defines internal data objects. The rest of SQLite interacts with the VDBE through an interface defined by vdbe.h. - * **where.c** - This file analyzes the WHERE clause and generates + * **where.c** - This file (together with its helper files named + by "where*.c") analyzes the WHERE clause and generates virtual machine code to run queries efficiently. This file is sometimes called the "query optimizer". It has its own private header file, whereInt.h, that defines data objects used internally. * **btree.c** - This file contains the implementation of the B-Tree - storage engine used by SQLite. + storage engine used by SQLite. The interface to the rest of the system + is defined by "btree.h". The "btreeInt.h" header defines objects + used internally by btree.c and not published to the rest of the system. * **pager.c** - This file contains the "pager" implementation, the - module that implements transactions. + module that implements transactions. The "pager.h" header file + defines the interface between pager.c and the rest of the system. * **os_unix.c** and **os_win.c** - These two files implement the interface between SQLite and the underlying operating system using the run-time pluggable VFS interface. - * **shell.c** - This file is not part of the core SQLite library. This + * **shell.c.in** - This file is not part of the core SQLite library. This is the file that, when linked against sqlite3.a, generates the - "sqlite3.exe" command-line shell. + "sqlite3.exe" command-line shell. The "shell.c.in" file is transformed + into "shell.c" as part of the build process. * **tclsqlite.c** - This file implements the Tcl bindings for SQLite. It is not part of the core SQLite library. But as most of the tests in this repository are written in Tcl, the Tcl language bindings are important. + * **test*.c** - Files in the src/ folder that begin with "test" go into + building the "testfixture.exe" program. The testfixture.exe program is + an enhanced Tcl shell. The testfixture.exe program runs scripts in the + test/ folder to validate the core SQLite code. The testfixture program + (and some other test programs too) is build and run when you type + "make test". + + * **ext/misc/json1.c** - This file implements the various JSON functions + that are build into SQLite. + There are many other source files. Each has a succinct header comment that describes its purpose and role within the larger system. diff --git a/VERSION b/VERSION index a7e7070f80..ee893b7e0e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.22.0 +3.23.0 diff --git a/autoconf/Makefile.am b/autoconf/Makefile.am index 8c046f822d..db246924fe 100644 --- a/autoconf/Makefile.am +++ b/autoconf/Makefile.am @@ -1,5 +1,5 @@ -AM_CFLAGS = @THREADSAFE_FLAGS@ @DYNAMIC_EXTENSION_FLAGS@ @FTS5_FLAGS@ @JSON1_FLAGS@ @SESSION_FLAGS@ -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_RTREE +AM_CFLAGS = @THREADSAFE_FLAGS@ @DYNAMIC_EXTENSION_FLAGS@ @FTS5_FLAGS@ @JSON1_FLAGS@ @ZLIB_FLAGS@ @SESSION_FLAGS@ -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_RTREE lib_LTLIBRARIES = libsqlite3.la libsqlite3_la_SOURCES = sqlite3.c @@ -10,7 +10,7 @@ sqlite3_SOURCES = shell.c sqlite3.h EXTRA_sqlite3_SOURCES = sqlite3.c sqlite3_LDADD = @EXTRA_SHELL_OBJ@ @READLINE_LIBS@ sqlite3_DEPENDENCIES = @EXTRA_SHELL_OBJ@ -sqlite3_CFLAGS = $(AM_CFLAGS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ENABLE_STMTVTAB -DSQLITE_ENABLE_DBSTAT_VTAB +sqlite3_CFLAGS = $(AM_CFLAGS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ENABLE_STMTVTAB -DSQLITE_ENABLE_DBSTAT_VTAB $(SHELL_CFLAGS) include_HEADERS = sqlite3.h sqlite3ext.h diff --git a/autoconf/Makefile.msc b/autoconf/Makefile.msc index 39831da03e..5f7c693d42 100644 --- a/autoconf/Makefile.msc +++ b/autoconf/Makefile.msc @@ -561,6 +561,7 @@ SHELL_CORE_DEP = !ENDIF !ENDIF + # This is the core library that the shell executable should link with. # !IFNDEF SHELL_CORE_LIB @@ -808,7 +809,7 @@ LTLINK = $(TCC) -Fe$@ # If requested, link to the RPCRT4 library. # !IF $(USE_RPCRT4_LIB)!=0 -LTLINK = $(LTLINK) rpcrt4.lib +LTLIBS = $(LTLIBS) rpcrt4.lib !ENDIF # If a platform was set, force the linker to target that. @@ -929,13 +930,22 @@ LIBRESOBJS = !IF $(DYNAMIC_SHELL)==0 && $(FOR_WIN10)==0 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_STMTVTAB SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ENABLE_DBSTAT_VTAB +SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC -DSQLITE_INTROSPECTION_PRAGMAS !ENDIF # This is the default Makefile target. The objects listed here # are what get build when you type just "make" with no arguments. # -all: dll shell +core: dll shell + +# Targets that require the Tcl library. +# +tcl: $(ALL_TCL_TARGETS) + +# This Makefile target builds all of the standard binaries. +# +all: core tcl # Dynamic link library section. # diff --git a/autoconf/configure.ac b/autoconf/configure.ac index c207bdf74d..2680becbb0 100644 --- a/autoconf/configure.ac +++ b/autoconf/configure.ac @@ -164,6 +164,13 @@ AC_SUBST(EXTRA_SHELL_OBJ) #----------------------------------------------------------------------- AC_CHECK_FUNCS(posix_fallocate) +AC_CHECK_HEADERS(zlib.h,[ + AC_SEARCH_LIBS(deflate,z,[ZLIB_FLAGS="-DSQLITE_HAVE_ZLIB"]) +]) +AC_SUBST(ZLIB_FLAGS) + +AC_SEARCH_LIBS(system,,,[SHELL_CFLAGS="-DSQLITE_NOHAVE_SYSTEM"]) +AC_SUBST(SHELL_CFLAGS) #----------------------------------------------------------------------- # UPDATE: Maybe it's better if users just set CFLAGS before invoking diff --git a/configure b/configure index 9e7aa526a2..3df19e8ec5 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.22.0. +# Generated by GNU Autoconf 2.69 for sqlite 3.23.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.22.0' -PACKAGE_STRING='sqlite 3.22.0' +PACKAGE_VERSION='3.23.0' +PACKAGE_STRING='sqlite 3.23.0' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -772,6 +772,7 @@ LIBOBJS BUILD_CFLAGS USE_GCOV OPT_FEATURE_FLAGS +HAVE_ZLIB USE_AMALGAMATION TARGET_DEBUG TARGET_HAVE_EDITLINE @@ -1464,7 +1465,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.22.0 to adapt to many kinds of systems. +\`configure' configures sqlite 3.23.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1529,7 +1530,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.22.0:";; + short | recursive ) echo "Configuration of sqlite 3.23.0:";; esac cat <<\_ACEOF @@ -1654,7 +1655,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.22.0 +sqlite configure 3.23.0 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2073,7 +2074,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.22.0, which was +It was created by sqlite $as_me 3.23.0, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -3931,13 +3932,13 @@ if ${lt_cv_nm_interface+:} false; then : else lt_cv_nm_interface="BSD nm" echo "int some_variable = 0;" > conftest.$ac_ext - (eval echo "\"\$as_me:3934: $ac_compile\"" >&5) + (eval echo "\"\$as_me:3935: $ac_compile\"" >&5) (eval "$ac_compile" 2>conftest.err) cat conftest.err >&5 - (eval echo "\"\$as_me:3937: $NM \\\"conftest.$ac_objext\\\"\"" >&5) + (eval echo "\"\$as_me:3938: $NM \\\"conftest.$ac_objext\\\"\"" >&5) (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) cat conftest.err >&5 - (eval echo "\"\$as_me:3940: output\"" >&5) + (eval echo "\"\$as_me:3941: output\"" >&5) cat conftest.out >&5 if $GREP 'External.*some_variable' conftest.out > /dev/null; then lt_cv_nm_interface="MS dumpbin" @@ -5143,7 +5144,7 @@ ia64-*-hpux*) ;; *-*-irix6*) # Find out which ABI we are using. - echo '#line 5146 "configure"' > conftest.$ac_ext + echo '#line 5147 "configure"' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? @@ -6668,11 +6669,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:6671: $lt_compile\"" >&5) + (eval echo "\"\$as_me:6672: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:6675: \$? = $ac_status" >&5 + echo "$as_me:6676: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. @@ -7007,11 +7008,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:7010: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7011: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:7014: \$? = $ac_status" >&5 + echo "$as_me:7015: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. @@ -7112,11 +7113,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:7115: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7116: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:7119: \$? = $ac_status" >&5 + echo "$as_me:7120: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized @@ -7167,11 +7168,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:7170: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7171: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:7174: \$? = $ac_status" >&5 + echo "$as_me:7175: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized @@ -9547,7 +9548,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 9550 "configure" +#line 9551 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -9643,7 +9644,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 9646 "configure" +#line 9647 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -11274,6 +11275,80 @@ if test "${use_amalgamation}" != "yes" ; then fi +######### +# Look for zlib. Only needed by extensions and by the sqlite3.exe shell +for ac_header in zlib.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "zlib.h" "ac_cv_header_zlib_h" "$ac_includes_default" +if test "x$ac_cv_header_zlib_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_ZLIB_H 1 +_ACEOF + +fi + +done + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing deflate" >&5 +$as_echo_n "checking for library containing deflate... " >&6; } +if ${ac_cv_search_deflate+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char deflate (); +int +main () +{ +return deflate (); + ; + return 0; +} +_ACEOF +for ac_lib in '' z; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_deflate=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_deflate+:} false; then : + break +fi +done +if ${ac_cv_search_deflate+:} false; then : + +else + ac_cv_search_deflate=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_deflate" >&5 +$as_echo "$ac_cv_search_deflate" >&6; } +ac_res=$ac_cv_search_deflate +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + HAVE_ZLIB="-DSQLITE_HAVE_ZLIB=1" +else + HAVE_ZLIB="" +fi + + + ######### # See whether we should allow loadable extensions # Check whether --enable-load-extension was given. @@ -12167,7 +12242,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.22.0, which was +This file was extended by sqlite $as_me 3.23.0, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12233,7 +12308,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.22.0 +sqlite config.status 3.23.0 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index ace5d5d667..7089772d19 100644 --- a/configure.ac +++ b/configure.ac @@ -576,6 +576,12 @@ if test "${use_amalgamation}" != "yes" ; then fi AC_SUBST(USE_AMALGAMATION) +######### +# Look for zlib. Only needed by extensions and by the sqlite3.exe shell +AC_CHECK_HEADERS(zlib.h) +AC_SEARCH_LIBS(deflate, z, [HAVE_ZLIB="-DSQLITE_HAVE_ZLIB=1"], [HAVE_ZLIB=""]) +AC_SUBST(HAVE_ZLIB) + ######### # See whether we should allow loadable extensions AC_ARG_ENABLE(load-extension, AC_HELP_STRING([--disable-load-extension], diff --git a/ext/expert/README.md b/ext/expert/README.md new file mode 100644 index 0000000000..28886fd1f2 --- /dev/null +++ b/ext/expert/README.md @@ -0,0 +1,83 @@ +## SQLite Expert Extension + +This folder contains code for a simple system to propose useful indexes +given a database and a set of SQL queries. It works as follows: + + 1. The user database schema is copied to a temporary database. + + 1. All SQL queries are prepared against the temporary database. + Information regarding the WHERE and ORDER BY clauses, and other query + features that affect index selection are recorded. + + 1. The information gathered in step 2 is used to create candidate + indexes - indexes that the planner might have made use of in the previous + step, had they been available. + + 1. A subset of the data in the user database is used to generate statistics + for all existing indexes and the candidate indexes generated in step 3 + above. + + 1. The SQL queries are prepared a second time. If the planner uses any + of the indexes created in step 3, they are recommended to the user. + +# C API + +The SQLite expert C API is defined in sqlite3expert.h. Most uses will proceed +as follows: + + 1. An sqlite3expert object is created by calling **sqlite3\_expert\_new()**. + A database handle opened by the user is passed as an argument. + + 1. The sqlite3expert object is configured with one or more SQL statements + by making one or more calls to **sqlite3\_expert\_sql()**. Each call may + specify a single SQL statement, or multiple statements separated by + semi-colons. + + 1. Optionally, the **sqlite3\_expert\_config()** API may be used to + configure the size of the data subset used to generate index statistics. + Using a smaller subset of the data can speed up the analysis. + + 1. **sqlite3\_expert\_analyze()** is called to run the analysis. + + 1. One or more calls are made to **sqlite3\_expert\_report()** to extract + components of the results of the analysis. + + 1. **sqlite3\_expert\_destroy()** is called to free all resources. + +Refer to comments in sqlite3expert.h for further details. + +# sqlite3_expert application + +The file "expert.c" contains the code for a command line application that +uses the API described above. It can be compiled with (for example): + +
+  gcc -O2 sqlite3.c expert.c sqlite3expert.c -o sqlite3_expert
+
+ +Assuming the database is named "test.db", it can then be run to analyze a +single query: + +
+  ./sqlite3_expert -sql <sql-query> test.db
+
+ +Or an entire text file worth of queries with: + +
+  ./sqlite3_expert -file <text-file> test.db
+
+ +By default, sqlite3\_expert generates index statistics using all the data in +the user database. For a large database, this may be prohibitively time +consuming. The "-sample" option may be used to configure sqlite3\_expert to +generate statistics based on an integer percentage of the user database as +follows: + +
+  # Generate statistics based on 25% of the user database rows:
+  ./sqlite3_expert -sample 25 -sql <sql-query> test.db
+
+  # Do not generate any statistics at all:
+  ./sqlite3_expert -sample 0 -sql <sql-query> test.db
+
diff --git a/ext/expert/expert.c b/ext/expert/expert.c new file mode 100644 index 0000000000..13fc87ea89 --- /dev/null +++ b/ext/expert/expert.c @@ -0,0 +1,155 @@ +/* +** 2017 April 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. +** +************************************************************************* +*/ + + +#include +#include +#include +#include +#include "sqlite3expert.h" + + +static void option_requires_argument(const char *zOpt){ + fprintf(stderr, "Option requires an argument: %s\n", zOpt); + exit(-3); +} + +static int option_integer_arg(const char *zVal){ + return atoi(zVal); +} + +static void usage(char **argv){ + fprintf(stderr, "\n"); + fprintf(stderr, "Usage %s ?OPTIONS? DATABASE\n", argv[0]); + fprintf(stderr, "\n"); + fprintf(stderr, "Options are:\n"); + fprintf(stderr, " -sql SQL (analyze SQL statements passed as argument)\n"); + fprintf(stderr, " -file FILE (read SQL statements from file FILE)\n"); + fprintf(stderr, " -verbose LEVEL (integer verbosity level. default 1)\n"); + fprintf(stderr, " -sample PERCENT (percent of db to sample. default 100)\n"); + exit(-1); +} + +static int readSqlFromFile(sqlite3expert *p, const char *zFile, char **pzErr){ + FILE *in = fopen(zFile, "rb"); + long nIn; + size_t nRead; + char *pBuf; + int rc; + if( in==0 ){ + *pzErr = sqlite3_mprintf("failed to open file %s\n", zFile); + return SQLITE_ERROR; + } + fseek(in, 0, SEEK_END); + nIn = ftell(in); + rewind(in); + pBuf = sqlite3_malloc64( nIn+1 ); + nRead = fread(pBuf, nIn, 1, in); + fclose(in); + if( nRead!=1 ){ + sqlite3_free(pBuf); + *pzErr = sqlite3_mprintf("failed to read file %s\n", zFile); + return SQLITE_ERROR; + } + pBuf[nIn] = 0; + rc = sqlite3_expert_sql(p, pBuf, pzErr); + sqlite3_free(pBuf); + return rc; +} + +int main(int argc, char **argv){ + const char *zDb; + int rc = 0; + char *zErr = 0; + int i; + int iVerbose = 1; /* -verbose option */ + + sqlite3 *db = 0; + sqlite3expert *p = 0; + + if( argc<2 ) usage(argv); + zDb = argv[argc-1]; + if( zDb[0]=='-' ) usage(argv); + rc = sqlite3_open(zDb, &db); + if( rc!=SQLITE_OK ){ + fprintf(stderr, "Cannot open db file: %s - %s\n", zDb, sqlite3_errmsg(db)); + exit(-2); + } + + p = sqlite3_expert_new(db, &zErr); + if( p==0 ){ + fprintf(stderr, "Cannot run analysis: %s\n", zErr); + rc = 1; + }else{ + for(i=1; i<(argc-1); i++){ + char *zArg = argv[i]; + if( zArg[0]=='-' && zArg[1]=='-' && zArg[2]!=0 ) zArg++; + int nArg = (int)strlen(zArg); + if( nArg>=2 && 0==sqlite3_strnicmp(zArg, "-file", nArg) ){ + if( ++i==(argc-1) ) option_requires_argument("-file"); + rc = readSqlFromFile(p, argv[i], &zErr); + } + + else if( nArg>=3 && 0==sqlite3_strnicmp(zArg, "-sql", nArg) ){ + if( ++i==(argc-1) ) option_requires_argument("-sql"); + rc = sqlite3_expert_sql(p, argv[i], &zErr); + } + + else if( nArg>=3 && 0==sqlite3_strnicmp(zArg, "-sample", nArg) ){ + int iSample; + if( ++i==(argc-1) ) option_requires_argument("-sample"); + iSample = option_integer_arg(argv[i]); + sqlite3_expert_config(p, EXPERT_CONFIG_SAMPLE, iSample); + } + + else if( nArg>=2 && 0==sqlite3_strnicmp(zArg, "-verbose", nArg) ){ + if( ++i==(argc-1) ) option_requires_argument("-verbose"); + iVerbose = option_integer_arg(argv[i]); + } + + else{ + usage(argv); + } + } + } + + if( rc==SQLITE_OK ){ + rc = sqlite3_expert_analyze(p, &zErr); + } + + if( rc==SQLITE_OK ){ + int nQuery = sqlite3_expert_count(p); + if( iVerbose>0 ){ + const char *zCand = sqlite3_expert_report(p,0,EXPERT_REPORT_CANDIDATES); + fprintf(stdout, "-- Candidates -------------------------------\n"); + fprintf(stdout, "%s\n", zCand); + } + for(i=0; i0 ){ + fprintf(stdout, "-- Query %d ----------------------------------\n",i+1); + fprintf(stdout, "%s\n\n", zSql); + } + fprintf(stdout, "%s\n%s\n", zIdx, zEQP); + } + }else{ + fprintf(stderr, "Error: %s\n", zErr ? zErr : "?"); + } + + sqlite3_expert_destroy(p); + sqlite3_free(zErr); + return rc; +} diff --git a/ext/expert/expert1.test b/ext/expert/expert1.test new file mode 100644 index 0000000000..3d693ec2e7 --- /dev/null +++ b/ext/expert/expert1.test @@ -0,0 +1,382 @@ +# 2009 Nov 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. +# +#*********************************************************************** +# +# The focus of this file is testing the CLI shell tool. Specifically, +# the ".recommend" command. +# +# + +# Test plan: +# +# +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source $testdir/tester.tcl +set testprefix expert1 + +if {[info commands sqlite3_expert_new]==""} { + finish_test + return +} + +set CLI [test_binary_name sqlite3] +set CMD [test_binary_name sqlite3_expert] + +proc squish {txt} { + regsub -all {[[:space:]]+} $txt { } +} + +proc do_setup_rec_test {tn setup sql res} { + reset_db + db eval $setup + uplevel [list do_rec_test $tn $sql $res] +} + +foreach {tn setup} { + 1 { + if {![file executable $CMD]} { continue } + + proc do_rec_test {tn sql res} { + set res [squish [string trim $res]] + set tst [subst -nocommands { + squish [string trim [exec $::CMD -verbose 0 -sql {$sql;} test.db]] + }] + uplevel [list do_test $tn $tst $res] + } + } + 2 { + if {[info commands sqlite3_expert_new]==""} { continue } + + proc do_rec_test {tn sql res} { + set expert [sqlite3_expert_new db] + $expert sql $sql + $expert analyze + + set result [list] + for {set i 0} {$i < [$expert count]} {incr i} { + set idx [string trim [$expert report $i indexes]] + if {$idx==""} {set idx "(no new indexes)"} + lappend result $idx + lappend result [string trim [$expert report $i plan]] + } + + $expert destroy + + set tst [subst -nocommands {set {} [squish [join {$result}]]}] + uplevel [list do_test $tn $tst [string trim [squish $res]]] + } + } + 3 { + if {![file executable $CLI]} { continue } + + proc do_rec_test {tn sql res} { + set res [squish [string trim $res]] + set tst [subst -nocommands { + squish [string trim [exec $::CLI test.db ".expert" {$sql;}]] + }] + uplevel [list do_test $tn $tst $res] + } + } +} { + + eval $setup + + +do_setup_rec_test $tn.1 { CREATE TABLE t1(a, b, c) } { + SELECT * FROM t1 +} { + (no new indexes) + 0|0|0|SCAN TABLE t1 +} + +do_setup_rec_test $tn.2 { + CREATE TABLE t1(a, b, c); +} { + SELECT * FROM t1 WHERE b>?; +} { + CREATE INDEX t1_idx_00000062 ON t1(b); + 0|0|0|SEARCH TABLE t1 USING INDEX t1_idx_00000062 (b>?) +} + +do_setup_rec_test $tn.3 { + CREATE TABLE t1(a, b, c); +} { + SELECT * FROM t1 WHERE b COLLATE nocase BETWEEN ? AND ? +} { + CREATE INDEX t1_idx_3e094c27 ON t1(b COLLATE NOCASE); + 0|0|0|SEARCH TABLE t1 USING INDEX t1_idx_3e094c27 (b>? AND b? AND b +#include +#include + +#ifndef SQLITE_OMIT_VIRTUALTABLE + +typedef sqlite3_int64 i64; +typedef sqlite3_uint64 u64; + +typedef struct IdxColumn IdxColumn; +typedef struct IdxConstraint IdxConstraint; +typedef struct IdxScan IdxScan; +typedef struct IdxStatement IdxStatement; +typedef struct IdxTable IdxTable; +typedef struct IdxWrite IdxWrite; + +#define STRLEN (int)strlen + +/* +** A temp table name that we assume no user database will actually use. +** If this assumption proves incorrect triggers on the table with the +** conflicting name will be ignored. +*/ +#define UNIQUE_TABLE_NAME "t592690916721053953805701627921227776" + +/* +** A single constraint. Equivalent to either "col = ?" or "col < ?" (or +** any other type of single-ended range constraint on a column). +** +** pLink: +** Used to temporarily link IdxConstraint objects into lists while +** creating candidate indexes. +*/ +struct IdxConstraint { + char *zColl; /* Collation sequence */ + int bRange; /* True for range, false for eq */ + int iCol; /* Constrained table column */ + int bFlag; /* Used by idxFindCompatible() */ + int bDesc; /* True if ORDER BY DESC */ + IdxConstraint *pNext; /* Next constraint in pEq or pRange list */ + IdxConstraint *pLink; /* See above */ +}; + +/* +** A single scan of a single table. +*/ +struct IdxScan { + IdxTable *pTab; /* Associated table object */ + int iDb; /* Database containing table zTable */ + i64 covering; /* Mask of columns required for cov. index */ + IdxConstraint *pOrder; /* ORDER BY columns */ + IdxConstraint *pEq; /* List of == constraints */ + IdxConstraint *pRange; /* List of < constraints */ + IdxScan *pNextScan; /* Next IdxScan object for same analysis */ +}; + +/* +** Information regarding a single database table. Extracted from +** "PRAGMA table_info" by function idxGetTableInfo(). +*/ +struct IdxColumn { + char *zName; + char *zColl; + int iPk; +}; +struct IdxTable { + int nCol; + char *zName; /* Table name */ + IdxColumn *aCol; + IdxTable *pNext; /* Next table in linked list of all tables */ +}; + +/* +** An object of the following type is created for each unique table/write-op +** seen. The objects are stored in a singly-linked list beginning at +** sqlite3expert.pWrite. +*/ +struct IdxWrite { + IdxTable *pTab; + int eOp; /* SQLITE_UPDATE, DELETE or INSERT */ + IdxWrite *pNext; +}; + +/* +** Each statement being analyzed is represented by an instance of this +** structure. +*/ +struct IdxStatement { + int iId; /* Statement number */ + char *zSql; /* SQL statement */ + char *zIdx; /* Indexes */ + char *zEQP; /* Plan */ + IdxStatement *pNext; +}; + + +/* +** A hash table for storing strings. With space for a payload string +** with each entry. Methods are: +** +** idxHashInit() +** idxHashClear() +** idxHashAdd() +** idxHashSearch() +*/ +#define IDX_HASH_SIZE 1023 +typedef struct IdxHashEntry IdxHashEntry; +typedef struct IdxHash IdxHash; +struct IdxHashEntry { + char *zKey; /* nul-terminated key */ + char *zVal; /* nul-terminated value string */ + char *zVal2; /* nul-terminated value string 2 */ + IdxHashEntry *pHashNext; /* Next entry in same hash bucket */ + IdxHashEntry *pNext; /* Next entry in hash */ +}; +struct IdxHash { + IdxHashEntry *pFirst; + IdxHashEntry *aHash[IDX_HASH_SIZE]; +}; + +/* +** sqlite3expert object. +*/ +struct sqlite3expert { + int iSample; /* Percentage of tables to sample for stat1 */ + sqlite3 *db; /* User database */ + sqlite3 *dbm; /* In-memory db for this analysis */ + sqlite3 *dbv; /* Vtab schema for this analysis */ + IdxTable *pTable; /* List of all IdxTable objects */ + IdxScan *pScan; /* List of scan objects */ + IdxWrite *pWrite; /* List of write objects */ + IdxStatement *pStatement; /* List of IdxStatement objects */ + int bRun; /* True once analysis has run */ + char **pzErrmsg; + int rc; /* Error code from whereinfo hook */ + IdxHash hIdx; /* Hash containing all candidate indexes */ + char *zCandidates; /* For EXPERT_REPORT_CANDIDATES */ +}; + + +/* +** Allocate and return nByte bytes of zeroed memory using sqlite3_malloc(). +** If the allocation fails, set *pRc to SQLITE_NOMEM and return NULL. +*/ +static void *idxMalloc(int *pRc, int nByte){ + void *pRet; + assert( *pRc==SQLITE_OK ); + assert( nByte>0 ); + pRet = sqlite3_malloc(nByte); + if( pRet ){ + memset(pRet, 0, nByte); + }else{ + *pRc = SQLITE_NOMEM; + } + return pRet; +} + +/* +** Initialize an IdxHash hash table. +*/ +static void idxHashInit(IdxHash *pHash){ + memset(pHash, 0, sizeof(IdxHash)); +} + +/* +** Reset an IdxHash hash table. +*/ +static void idxHashClear(IdxHash *pHash){ + int i; + for(i=0; iaHash[i]; pEntry; pEntry=pNext){ + pNext = pEntry->pHashNext; + sqlite3_free(pEntry->zVal2); + sqlite3_free(pEntry); + } + } + memset(pHash, 0, sizeof(IdxHash)); +} + +/* +** Return the index of the hash bucket that the string specified by the +** arguments to this function belongs. +*/ +static int idxHashString(const char *z, int n){ + unsigned int ret = 0; + int i; + for(i=0; i=0 ); + for(pEntry=pHash->aHash[iHash]; pEntry; pEntry=pEntry->pHashNext){ + if( STRLEN(pEntry->zKey)==nKey && 0==memcmp(pEntry->zKey, zKey, nKey) ){ + return 1; + } + } + pEntry = idxMalloc(pRc, sizeof(IdxHashEntry) + nKey+1 + nVal+1); + if( pEntry ){ + pEntry->zKey = (char*)&pEntry[1]; + memcpy(pEntry->zKey, zKey, nKey); + if( zVal ){ + pEntry->zVal = &pEntry->zKey[nKey+1]; + memcpy(pEntry->zVal, zVal, nVal); + } + pEntry->pHashNext = pHash->aHash[iHash]; + pHash->aHash[iHash] = pEntry; + + pEntry->pNext = pHash->pFirst; + pHash->pFirst = pEntry; + } + return 0; +} + +/* +** If zKey/nKey is present in the hash table, return a pointer to the +** hash-entry object. +*/ +static IdxHashEntry *idxHashFind(IdxHash *pHash, const char *zKey, int nKey){ + int iHash; + IdxHashEntry *pEntry; + if( nKey<0 ) nKey = STRLEN(zKey); + iHash = idxHashString(zKey, nKey); + assert( iHash>=0 ); + for(pEntry=pHash->aHash[iHash]; pEntry; pEntry=pEntry->pHashNext){ + if( STRLEN(pEntry->zKey)==nKey && 0==memcmp(pEntry->zKey, zKey, nKey) ){ + return pEntry; + } + } + return 0; +} + +/* +** If the hash table contains an entry with a key equal to the string +** passed as the final two arguments to this function, return a pointer +** to the payload string. Otherwise, if zKey/nKey is not present in the +** hash table, return NULL. +*/ +static const char *idxHashSearch(IdxHash *pHash, const char *zKey, int nKey){ + IdxHashEntry *pEntry = idxHashFind(pHash, zKey, nKey); + if( pEntry ) return pEntry->zVal; + return 0; +} + +/* +** Allocate and return a new IdxConstraint object. Set the IdxConstraint.zColl +** variable to point to a copy of nul-terminated string zColl. +*/ +static IdxConstraint *idxNewConstraint(int *pRc, const char *zColl){ + IdxConstraint *pNew; + int nColl = STRLEN(zColl); + + assert( *pRc==SQLITE_OK ); + pNew = (IdxConstraint*)idxMalloc(pRc, sizeof(IdxConstraint) * nColl + 1); + if( pNew ){ + pNew->zColl = (char*)&pNew[1]; + memcpy(pNew->zColl, zColl, nColl+1); + } + return pNew; +} + +/* +** An error associated with database handle db has just occurred. Pass +** the error message to callback function xOut. +*/ +static void idxDatabaseError( + sqlite3 *db, /* Database handle */ + char **pzErrmsg /* Write error here */ +){ + *pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db)); +} + +/* +** Prepare an SQL statement. +*/ +static int idxPrepareStmt( + sqlite3 *db, /* Database handle to compile against */ + sqlite3_stmt **ppStmt, /* OUT: Compiled SQL statement */ + char **pzErrmsg, /* OUT: sqlite3_malloc()ed error message */ + const char *zSql /* SQL statement to compile */ +){ + int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0); + if( rc!=SQLITE_OK ){ + *ppStmt = 0; + idxDatabaseError(db, pzErrmsg); + } + return rc; +} + +/* +** Prepare an SQL statement using the results of a printf() formatting. +*/ +static int idxPrintfPrepareStmt( + sqlite3 *db, /* Database handle to compile against */ + sqlite3_stmt **ppStmt, /* OUT: Compiled SQL statement */ + char **pzErrmsg, /* OUT: sqlite3_malloc()ed error message */ + const char *zFmt, /* printf() format of SQL statement */ + ... /* Trailing printf() arguments */ +){ + va_list ap; + int rc; + char *zSql; + va_start(ap, zFmt); + zSql = sqlite3_vmprintf(zFmt, ap); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = idxPrepareStmt(db, ppStmt, pzErrmsg, zSql); + sqlite3_free(zSql); + } + va_end(ap); + return rc; +} + + +/************************************************************************* +** Beginning of virtual table implementation. +*/ +typedef struct ExpertVtab ExpertVtab; +struct ExpertVtab { + sqlite3_vtab base; + IdxTable *pTab; + sqlite3expert *pExpert; +}; + +typedef struct ExpertCsr ExpertCsr; +struct ExpertCsr { + sqlite3_vtab_cursor base; + sqlite3_stmt *pData; +}; + +static char *expertDequote(const char *zIn){ + int n = STRLEN(zIn); + char *zRet = sqlite3_malloc(n); + + assert( zIn[0]=='\'' ); + assert( zIn[n-1]=='\'' ); + + if( zRet ){ + int iOut = 0; + int iIn = 0; + for(iIn=1; iIn<(n-1); iIn++){ + if( zIn[iIn]=='\'' ){ + assert( zIn[iIn+1]=='\'' ); + iIn++; + } + zRet[iOut++] = zIn[iIn]; + } + zRet[iOut] = '\0'; + } + + return zRet; +} + +/* +** This function is the implementation of both the xConnect and xCreate +** methods of the r-tree virtual table. +** +** argv[0] -> module name +** argv[1] -> database name +** argv[2] -> table name +** argv[...] -> column names... +*/ +static int expertConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + sqlite3expert *pExpert = (sqlite3expert*)pAux; + ExpertVtab *p = 0; + int rc; + + if( argc!=4 ){ + *pzErr = sqlite3_mprintf("internal error!"); + rc = SQLITE_ERROR; + }else{ + char *zCreateTable = expertDequote(argv[3]); + if( zCreateTable ){ + rc = sqlite3_declare_vtab(db, zCreateTable); + if( rc==SQLITE_OK ){ + p = idxMalloc(&rc, sizeof(ExpertVtab)); + } + if( rc==SQLITE_OK ){ + p->pExpert = pExpert; + p->pTab = pExpert->pTable; + assert( sqlite3_stricmp(p->pTab->zName, argv[2])==0 ); + } + sqlite3_free(zCreateTable); + }else{ + rc = SQLITE_NOMEM; + } + } + + *ppVtab = (sqlite3_vtab*)p; + return rc; +} + +static int expertDisconnect(sqlite3_vtab *pVtab){ + ExpertVtab *p = (ExpertVtab*)pVtab; + sqlite3_free(p); + return SQLITE_OK; +} + +static int expertBestIndex(sqlite3_vtab *pVtab, sqlite3_index_info *pIdxInfo){ + ExpertVtab *p = (ExpertVtab*)pVtab; + int rc = SQLITE_OK; + int n = 0; + IdxScan *pScan; + const int opmask = + SQLITE_INDEX_CONSTRAINT_EQ | SQLITE_INDEX_CONSTRAINT_GT | + SQLITE_INDEX_CONSTRAINT_LT | SQLITE_INDEX_CONSTRAINT_GE | + SQLITE_INDEX_CONSTRAINT_LE; + + pScan = idxMalloc(&rc, sizeof(IdxScan)); + if( pScan ){ + int i; + + /* Link the new scan object into the list */ + pScan->pTab = p->pTab; + pScan->pNextScan = p->pExpert->pScan; + p->pExpert->pScan = pScan; + + /* Add the constraints to the IdxScan object */ + for(i=0; inConstraint; i++){ + struct sqlite3_index_constraint *pCons = &pIdxInfo->aConstraint[i]; + if( pCons->usable + && pCons->iColumn>=0 + && p->pTab->aCol[pCons->iColumn].iPk==0 + && (pCons->op & opmask) + ){ + IdxConstraint *pNew; + const char *zColl = sqlite3_vtab_collation(pIdxInfo, i); + pNew = idxNewConstraint(&rc, zColl); + if( pNew ){ + pNew->iCol = pCons->iColumn; + if( pCons->op==SQLITE_INDEX_CONSTRAINT_EQ ){ + pNew->pNext = pScan->pEq; + pScan->pEq = pNew; + }else{ + pNew->bRange = 1; + pNew->pNext = pScan->pRange; + pScan->pRange = pNew; + } + } + n++; + pIdxInfo->aConstraintUsage[i].argvIndex = n; + } + } + + /* Add the ORDER BY to the IdxScan object */ + for(i=pIdxInfo->nOrderBy-1; i>=0; i--){ + int iCol = pIdxInfo->aOrderBy[i].iColumn; + if( iCol>=0 ){ + IdxConstraint *pNew = idxNewConstraint(&rc, p->pTab->aCol[iCol].zColl); + if( pNew ){ + pNew->iCol = iCol; + pNew->bDesc = pIdxInfo->aOrderBy[i].desc; + pNew->pNext = pScan->pOrder; + pNew->pLink = pScan->pOrder; + pScan->pOrder = pNew; + n++; + } + } + } + } + + pIdxInfo->estimatedCost = 1000000.0 / (n+1); + return rc; +} + +static int expertUpdate( + sqlite3_vtab *pVtab, + int nData, + sqlite3_value **azData, + sqlite_int64 *pRowid +){ + (void)pVtab; + (void)nData; + (void)azData; + (void)pRowid; + return SQLITE_OK; +} + +/* +** Virtual table module xOpen method. +*/ +static int expertOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ + int rc = SQLITE_OK; + ExpertCsr *pCsr; + (void)pVTab; + pCsr = idxMalloc(&rc, sizeof(ExpertCsr)); + *ppCursor = (sqlite3_vtab_cursor*)pCsr; + return rc; +} + +/* +** Virtual table module xClose method. +*/ +static int expertClose(sqlite3_vtab_cursor *cur){ + ExpertCsr *pCsr = (ExpertCsr*)cur; + sqlite3_finalize(pCsr->pData); + sqlite3_free(pCsr); + return SQLITE_OK; +} + +/* +** Virtual table module xEof method. +** +** Return non-zero if the cursor does not currently point to a valid +** record (i.e if the scan has finished), or zero otherwise. +*/ +static int expertEof(sqlite3_vtab_cursor *cur){ + ExpertCsr *pCsr = (ExpertCsr*)cur; + return pCsr->pData==0; +} + +/* +** Virtual table module xNext method. +*/ +static int expertNext(sqlite3_vtab_cursor *cur){ + ExpertCsr *pCsr = (ExpertCsr*)cur; + int rc = SQLITE_OK; + + assert( pCsr->pData ); + rc = sqlite3_step(pCsr->pData); + if( rc!=SQLITE_ROW ){ + rc = sqlite3_finalize(pCsr->pData); + pCsr->pData = 0; + }else{ + rc = SQLITE_OK; + } + + return rc; +} + +/* +** Virtual table module xRowid method. +*/ +static int expertRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + (void)cur; + *pRowid = 0; + return SQLITE_OK; +} + +/* +** Virtual table module xColumn method. +*/ +static int expertColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ + ExpertCsr *pCsr = (ExpertCsr*)cur; + sqlite3_value *pVal; + pVal = sqlite3_column_value(pCsr->pData, i); + if( pVal ){ + sqlite3_result_value(ctx, pVal); + } + return SQLITE_OK; +} + +/* +** Virtual table module xFilter method. +*/ +static int expertFilter( + sqlite3_vtab_cursor *cur, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + ExpertCsr *pCsr = (ExpertCsr*)cur; + ExpertVtab *pVtab = (ExpertVtab*)(cur->pVtab); + sqlite3expert *pExpert = pVtab->pExpert; + int rc; + + (void)idxNum; + (void)idxStr; + (void)argc; + (void)argv; + rc = sqlite3_finalize(pCsr->pData); + pCsr->pData = 0; + if( rc==SQLITE_OK ){ + rc = idxPrintfPrepareStmt(pExpert->db, &pCsr->pData, &pVtab->base.zErrMsg, + "SELECT * FROM main.%Q WHERE sample()", pVtab->pTab->zName + ); + } + + if( rc==SQLITE_OK ){ + rc = expertNext(cur); + } + return rc; +} + +static int idxRegisterVtab(sqlite3expert *p){ + static sqlite3_module expertModule = { + 2, /* iVersion */ + expertConnect, /* xCreate - create a table */ + expertConnect, /* xConnect - connect to an existing table */ + expertBestIndex, /* xBestIndex - Determine search strategy */ + expertDisconnect, /* xDisconnect - Disconnect from a table */ + expertDisconnect, /* xDestroy - Drop a table */ + expertOpen, /* xOpen - open a cursor */ + expertClose, /* xClose - close a cursor */ + expertFilter, /* xFilter - configure scan constraints */ + expertNext, /* xNext - advance a cursor */ + expertEof, /* xEof */ + expertColumn, /* xColumn - read data */ + expertRowid, /* xRowid - read data */ + expertUpdate, /* 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 */ + }; + + return sqlite3_create_module(p->dbv, "expert", &expertModule, (void*)p); +} +/* +** End of virtual table implementation. +*************************************************************************/ +/* +** Finalize SQL statement pStmt. If (*pRc) is SQLITE_OK when this function +** is called, set it to the return value of sqlite3_finalize() before +** returning. Otherwise, discard the sqlite3_finalize() return value. +*/ +static void idxFinalize(int *pRc, sqlite3_stmt *pStmt){ + int rc = sqlite3_finalize(pStmt); + if( *pRc==SQLITE_OK ) *pRc = rc; +} + +/* +** Attempt to allocate an IdxTable structure corresponding to table zTab +** in the main database of connection db. If successful, set (*ppOut) to +** point to the new object and return SQLITE_OK. Otherwise, return an +** SQLite error code and set (*ppOut) to NULL. In this case *pzErrmsg may be +** set to point to an error string. +** +** It is the responsibility of the caller to eventually free either the +** IdxTable object or error message using sqlite3_free(). +*/ +static int idxGetTableInfo( + sqlite3 *db, /* Database connection to read details from */ + const char *zTab, /* Table name */ + IdxTable **ppOut, /* OUT: New object (if successful) */ + char **pzErrmsg /* OUT: Error message (if not) */ +){ + sqlite3_stmt *p1 = 0; + int nCol = 0; + int nTab = STRLEN(zTab); + int nByte = sizeof(IdxTable) + nTab + 1; + IdxTable *pNew = 0; + int rc, rc2; + char *pCsr = 0; + + rc = idxPrintfPrepareStmt(db, &p1, pzErrmsg, "PRAGMA table_info=%Q", zTab); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(p1) ){ + const char *zCol = (const char*)sqlite3_column_text(p1, 1); + nByte += 1 + STRLEN(zCol); + rc = sqlite3_table_column_metadata( + db, "main", zTab, zCol, 0, &zCol, 0, 0, 0 + ); + nByte += 1 + STRLEN(zCol); + nCol++; + } + rc2 = sqlite3_reset(p1); + if( rc==SQLITE_OK ) rc = rc2; + + nByte += sizeof(IdxColumn) * nCol; + if( rc==SQLITE_OK ){ + pNew = idxMalloc(&rc, nByte); + } + if( rc==SQLITE_OK ){ + pNew->aCol = (IdxColumn*)&pNew[1]; + pNew->nCol = nCol; + pCsr = (char*)&pNew->aCol[nCol]; + } + + nCol = 0; + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(p1) ){ + const char *zCol = (const char*)sqlite3_column_text(p1, 1); + int nCopy = STRLEN(zCol) + 1; + pNew->aCol[nCol].zName = pCsr; + pNew->aCol[nCol].iPk = sqlite3_column_int(p1, 5); + memcpy(pCsr, zCol, nCopy); + pCsr += nCopy; + + rc = sqlite3_table_column_metadata( + db, "main", zTab, zCol, 0, &zCol, 0, 0, 0 + ); + if( rc==SQLITE_OK ){ + nCopy = STRLEN(zCol) + 1; + pNew->aCol[nCol].zColl = pCsr; + memcpy(pCsr, zCol, nCopy); + pCsr += nCopy; + } + + nCol++; + } + idxFinalize(&rc, p1); + + if( rc!=SQLITE_OK ){ + sqlite3_free(pNew); + pNew = 0; + }else{ + pNew->zName = pCsr; + memcpy(pNew->zName, zTab, nTab+1); + } + + *ppOut = pNew; + return rc; +} + +/* +** This function is a no-op if *pRc is set to anything other than +** SQLITE_OK when it is called. +** +** If *pRc is initially set to SQLITE_OK, then the text specified by +** the printf() style arguments is appended to zIn and the result returned +** in a buffer allocated by sqlite3_malloc(). sqlite3_free() is called on +** zIn before returning. +*/ +static char *idxAppendText(int *pRc, char *zIn, const char *zFmt, ...){ + va_list ap; + char *zAppend = 0; + char *zRet = 0; + int nIn = zIn ? STRLEN(zIn) : 0; + int nAppend = 0; + va_start(ap, zFmt); + if( *pRc==SQLITE_OK ){ + zAppend = sqlite3_vmprintf(zFmt, ap); + if( zAppend ){ + nAppend = STRLEN(zAppend); + zRet = (char*)sqlite3_malloc(nIn + nAppend + 1); + } + if( zAppend && zRet ){ + if( nIn ) memcpy(zRet, zIn, nIn); + memcpy(&zRet[nIn], zAppend, nAppend+1); + }else{ + sqlite3_free(zRet); + zRet = 0; + *pRc = SQLITE_NOMEM; + } + sqlite3_free(zAppend); + sqlite3_free(zIn); + } + va_end(ap); + return zRet; +} + +/* +** Return true if zId must be quoted in order to use it as an SQL +** identifier, or false otherwise. +*/ +static int idxIdentifierRequiresQuotes(const char *zId){ + int i; + for(i=0; zId[i]; i++){ + if( !(zId[i]=='_') + && !(zId[i]>='0' && zId[i]<='9') + && !(zId[i]>='a' && zId[i]<='z') + && !(zId[i]>='A' && zId[i]<='Z') + ){ + return 1; + } + } + return 0; +} + +/* +** This function appends an index column definition suitable for constraint +** pCons to the string passed as zIn and returns the result. +*/ +static char *idxAppendColDefn( + int *pRc, /* IN/OUT: Error code */ + char *zIn, /* Column defn accumulated so far */ + IdxTable *pTab, /* Table index will be created on */ + IdxConstraint *pCons +){ + char *zRet = zIn; + IdxColumn *p = &pTab->aCol[pCons->iCol]; + if( zRet ) zRet = idxAppendText(pRc, zRet, ", "); + + if( idxIdentifierRequiresQuotes(p->zName) ){ + zRet = idxAppendText(pRc, zRet, "%Q", p->zName); + }else{ + zRet = idxAppendText(pRc, zRet, "%s", p->zName); + } + + if( sqlite3_stricmp(p->zColl, pCons->zColl) ){ + if( idxIdentifierRequiresQuotes(pCons->zColl) ){ + zRet = idxAppendText(pRc, zRet, " COLLATE %Q", pCons->zColl); + }else{ + zRet = idxAppendText(pRc, zRet, " COLLATE %s", pCons->zColl); + } + } + + if( pCons->bDesc ){ + zRet = idxAppendText(pRc, zRet, " DESC"); + } + return zRet; +} + +/* +** Search database dbm for an index compatible with the one idxCreateFromCons() +** would create from arguments pScan, pEq and pTail. If no error occurs and +** such an index is found, return non-zero. Or, if no such index is found, +** return zero. +** +** If an error occurs, set *pRc to an SQLite error code and return zero. +*/ +static int idxFindCompatible( + int *pRc, /* OUT: Error code */ + sqlite3* dbm, /* Database to search */ + IdxScan *pScan, /* Scan for table to search for index on */ + IdxConstraint *pEq, /* List of == constraints */ + IdxConstraint *pTail /* List of range constraints */ +){ + const char *zTbl = pScan->pTab->zName; + sqlite3_stmt *pIdxList = 0; + IdxConstraint *pIter; + int nEq = 0; /* Number of elements in pEq */ + int rc; + + /* Count the elements in list pEq */ + for(pIter=pEq; pIter; pIter=pIter->pLink) nEq++; + + rc = idxPrintfPrepareStmt(dbm, &pIdxList, 0, "PRAGMA index_list=%Q", zTbl); + while( rc==SQLITE_OK && sqlite3_step(pIdxList)==SQLITE_ROW ){ + int bMatch = 1; + IdxConstraint *pT = pTail; + sqlite3_stmt *pInfo = 0; + const char *zIdx = (const char*)sqlite3_column_text(pIdxList, 1); + + /* Zero the IdxConstraint.bFlag values in the pEq list */ + for(pIter=pEq; pIter; pIter=pIter->pLink) pIter->bFlag = 0; + + rc = idxPrintfPrepareStmt(dbm, &pInfo, 0, "PRAGMA index_xInfo=%Q", zIdx); + while( rc==SQLITE_OK && sqlite3_step(pInfo)==SQLITE_ROW ){ + int iIdx = sqlite3_column_int(pInfo, 0); + int iCol = sqlite3_column_int(pInfo, 1); + const char *zColl = (const char*)sqlite3_column_text(pInfo, 4); + + if( iIdxpLink){ + if( pIter->bFlag ) continue; + if( pIter->iCol!=iCol ) continue; + if( sqlite3_stricmp(pIter->zColl, zColl) ) continue; + pIter->bFlag = 1; + break; + } + if( pIter==0 ){ + bMatch = 0; + break; + } + }else{ + if( pT ){ + if( pT->iCol!=iCol || sqlite3_stricmp(pT->zColl, zColl) ){ + bMatch = 0; + break; + } + pT = pT->pLink; + } + } + } + idxFinalize(&rc, pInfo); + + if( rc==SQLITE_OK && bMatch ){ + sqlite3_finalize(pIdxList); + return 1; + } + } + idxFinalize(&rc, pIdxList); + + *pRc = rc; + return 0; +} + +static int idxCreateFromCons( + sqlite3expert *p, + IdxScan *pScan, + IdxConstraint *pEq, + IdxConstraint *pTail +){ + sqlite3 *dbm = p->dbm; + int rc = SQLITE_OK; + if( (pEq || pTail) && 0==idxFindCompatible(&rc, dbm, pScan, pEq, pTail) ){ + IdxTable *pTab = pScan->pTab; + char *zCols = 0; + char *zIdx = 0; + IdxConstraint *pCons; + unsigned int h = 0; + const char *zFmt; + + for(pCons=pEq; pCons; pCons=pCons->pLink){ + zCols = idxAppendColDefn(&rc, zCols, pTab, pCons); + } + for(pCons=pTail; pCons; pCons=pCons->pLink){ + zCols = idxAppendColDefn(&rc, zCols, pTab, pCons); + } + + if( rc==SQLITE_OK ){ + /* Hash the list of columns to come up with a name for the index */ + const char *zTable = pScan->pTab->zName; + char *zName; /* Index name */ + int i; + for(i=0; zCols[i]; i++){ + h += ((h<<3) + zCols[i]); + } + zName = sqlite3_mprintf("%s_idx_%08x", zTable, h); + if( zName==0 ){ + rc = SQLITE_NOMEM; + }else{ + if( idxIdentifierRequiresQuotes(zTable) ){ + zFmt = "CREATE INDEX '%q' ON %Q(%s)"; + }else{ + zFmt = "CREATE INDEX %s ON %s(%s)"; + } + zIdx = sqlite3_mprintf(zFmt, zName, zTable, zCols); + if( !zIdx ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_exec(dbm, zIdx, 0, 0, p->pzErrmsg); + idxHashAdd(&rc, &p->hIdx, zName, zIdx); + } + sqlite3_free(zName); + sqlite3_free(zIdx); + } + } + + sqlite3_free(zCols); + } + return rc; +} + +/* +** Return true if list pList (linked by IdxConstraint.pLink) contains +** a constraint compatible with *p. Otherwise return false. +*/ +static int idxFindConstraint(IdxConstraint *pList, IdxConstraint *p){ + IdxConstraint *pCmp; + for(pCmp=pList; pCmp; pCmp=pCmp->pLink){ + if( p->iCol==pCmp->iCol ) return 1; + } + return 0; +} + +static int idxCreateFromWhere( + sqlite3expert *p, + IdxScan *pScan, /* Create indexes for this scan */ + IdxConstraint *pTail /* range/ORDER BY constraints for inclusion */ +){ + IdxConstraint *p1 = 0; + IdxConstraint *pCon; + int rc; + + /* Gather up all the == constraints. */ + for(pCon=pScan->pEq; pCon; pCon=pCon->pNext){ + if( !idxFindConstraint(p1, pCon) && !idxFindConstraint(pTail, pCon) ){ + pCon->pLink = p1; + p1 = pCon; + } + } + + /* Create an index using the == constraints collected above. And the + ** range constraint/ORDER BY terms passed in by the caller, if any. */ + rc = idxCreateFromCons(p, pScan, p1, pTail); + + /* If no range/ORDER BY passed by the caller, create a version of the + ** index for each range constraint. */ + if( pTail==0 ){ + for(pCon=pScan->pRange; rc==SQLITE_OK && pCon; pCon=pCon->pNext){ + assert( pCon->pLink==0 ); + if( !idxFindConstraint(p1, pCon) && !idxFindConstraint(pTail, pCon) ){ + rc = idxCreateFromCons(p, pScan, p1, pCon); + } + } + } + + return rc; +} + +/* +** Create candidate indexes in database [dbm] based on the data in +** linked-list pScan. +*/ +static int idxCreateCandidates(sqlite3expert *p){ + int rc = SQLITE_OK; + IdxScan *pIter; + + for(pIter=p->pScan; pIter && rc==SQLITE_OK; pIter=pIter->pNextScan){ + rc = idxCreateFromWhere(p, pIter, 0); + if( rc==SQLITE_OK && pIter->pOrder ){ + rc = idxCreateFromWhere(p, pIter, pIter->pOrder); + } + } + + return rc; +} + +/* +** Free all elements of the linked list starting at pConstraint. +*/ +static void idxConstraintFree(IdxConstraint *pConstraint){ + IdxConstraint *pNext; + IdxConstraint *p; + + for(p=pConstraint; p; p=pNext){ + pNext = p->pNext; + sqlite3_free(p); + } +} + +/* +** Free all elements of the linked list starting from pScan up until pLast +** (pLast is not freed). +*/ +static void idxScanFree(IdxScan *pScan, IdxScan *pLast){ + IdxScan *p; + IdxScan *pNext; + for(p=pScan; p!=pLast; p=pNext){ + pNext = p->pNextScan; + idxConstraintFree(p->pOrder); + idxConstraintFree(p->pEq); + idxConstraintFree(p->pRange); + sqlite3_free(p); + } +} + +/* +** Free all elements of the linked list starting from pStatement up +** until pLast (pLast is not freed). +*/ +static void idxStatementFree(IdxStatement *pStatement, IdxStatement *pLast){ + IdxStatement *p; + IdxStatement *pNext; + for(p=pStatement; p!=pLast; p=pNext){ + pNext = p->pNext; + sqlite3_free(p->zEQP); + sqlite3_free(p->zIdx); + sqlite3_free(p); + } +} + +/* +** Free the linked list of IdxTable objects starting at pTab. +*/ +static void idxTableFree(IdxTable *pTab){ + IdxTable *pIter; + IdxTable *pNext; + for(pIter=pTab; pIter; pIter=pNext){ + pNext = pIter->pNext; + sqlite3_free(pIter); + } +} + +/* +** Free the linked list of IdxWrite objects starting at pTab. +*/ +static void idxWriteFree(IdxWrite *pTab){ + IdxWrite *pIter; + IdxWrite *pNext; + for(pIter=pTab; pIter; pIter=pNext){ + pNext = pIter->pNext; + sqlite3_free(pIter); + } +} + + + +/* +** This function is called after candidate indexes have been created. It +** runs all the queries to see which indexes they prefer, and populates +** IdxStatement.zIdx and IdxStatement.zEQP with the results. +*/ +int idxFindIndexes( + sqlite3expert *p, + char **pzErr /* OUT: Error message (sqlite3_malloc) */ +){ + IdxStatement *pStmt; + sqlite3 *dbm = p->dbm; + int rc = SQLITE_OK; + + IdxHash hIdx; + idxHashInit(&hIdx); + + for(pStmt=p->pStatement; rc==SQLITE_OK && pStmt; pStmt=pStmt->pNext){ + IdxHashEntry *pEntry; + sqlite3_stmt *pExplain = 0; + idxHashClear(&hIdx); + rc = idxPrintfPrepareStmt(dbm, &pExplain, pzErr, + "EXPLAIN QUERY PLAN %s", pStmt->zSql + ); + while( rc==SQLITE_OK && sqlite3_step(pExplain)==SQLITE_ROW ){ + int iSelectid = sqlite3_column_int(pExplain, 0); + int iOrder = sqlite3_column_int(pExplain, 1); + int iFrom = sqlite3_column_int(pExplain, 2); + const char *zDetail = (const char*)sqlite3_column_text(pExplain, 3); + int nDetail = STRLEN(zDetail); + int i; + + for(i=0; ihIdx, zIdx, nIdx); + if( zSql ){ + idxHashAdd(&rc, &hIdx, zSql, 0); + if( rc ) goto find_indexes_out; + } + break; + } + } + + pStmt->zEQP = idxAppendText(&rc, pStmt->zEQP, "%d|%d|%d|%s\n", + iSelectid, iOrder, iFrom, zDetail + ); + } + + for(pEntry=hIdx.pFirst; pEntry; pEntry=pEntry->pNext){ + pStmt->zIdx = idxAppendText(&rc, pStmt->zIdx, "%s;\n", pEntry->zKey); + } + + idxFinalize(&rc, pExplain); + } + + find_indexes_out: + idxHashClear(&hIdx); + return rc; +} + +static int idxAuthCallback( + void *pCtx, + int eOp, + const char *z3, + const char *z4, + const char *zDb, + const char *zTrigger +){ + int rc = SQLITE_OK; + (void)z4; + (void)zTrigger; + if( eOp==SQLITE_INSERT || eOp==SQLITE_UPDATE || eOp==SQLITE_DELETE ){ + if( sqlite3_stricmp(zDb, "main")==0 ){ + sqlite3expert *p = (sqlite3expert*)pCtx; + IdxTable *pTab; + for(pTab=p->pTable; pTab; pTab=pTab->pNext){ + if( 0==sqlite3_stricmp(z3, pTab->zName) ) break; + } + if( pTab ){ + IdxWrite *pWrite; + for(pWrite=p->pWrite; pWrite; pWrite=pWrite->pNext){ + if( pWrite->pTab==pTab && pWrite->eOp==eOp ) break; + } + if( pWrite==0 ){ + pWrite = idxMalloc(&rc, sizeof(IdxWrite)); + if( rc==SQLITE_OK ){ + pWrite->pTab = pTab; + pWrite->eOp = eOp; + pWrite->pNext = p->pWrite; + p->pWrite = pWrite; + } + } + } + } + } + return rc; +} + +static int idxProcessOneTrigger( + sqlite3expert *p, + IdxWrite *pWrite, + char **pzErr +){ + static const char *zInt = UNIQUE_TABLE_NAME; + static const char *zDrop = "DROP TABLE " UNIQUE_TABLE_NAME; + IdxTable *pTab = pWrite->pTab; + const char *zTab = pTab->zName; + const char *zSql = + "SELECT 'CREATE TEMP' || substr(sql, 7) FROM sqlite_master " + "WHERE tbl_name = %Q AND type IN ('table', 'trigger') " + "ORDER BY type;"; + sqlite3_stmt *pSelect = 0; + int rc = SQLITE_OK; + char *zWrite = 0; + + /* Create the table and its triggers in the temp schema */ + rc = idxPrintfPrepareStmt(p->db, &pSelect, pzErr, zSql, zTab, zTab); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSelect) ){ + const char *zCreate = (const char*)sqlite3_column_text(pSelect, 0); + rc = sqlite3_exec(p->dbv, zCreate, 0, 0, pzErr); + } + idxFinalize(&rc, pSelect); + + /* Rename the table in the temp schema to zInt */ + if( rc==SQLITE_OK ){ + char *z = sqlite3_mprintf("ALTER TABLE temp.%Q RENAME TO %Q", zTab, zInt); + if( z==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_exec(p->dbv, z, 0, 0, pzErr); + sqlite3_free(z); + } + } + + switch( pWrite->eOp ){ + case SQLITE_INSERT: { + int i; + zWrite = idxAppendText(&rc, zWrite, "INSERT INTO %Q VALUES(", zInt); + for(i=0; inCol; i++){ + zWrite = idxAppendText(&rc, zWrite, "%s?", i==0 ? "" : ", "); + } + zWrite = idxAppendText(&rc, zWrite, ")"); + break; + } + case SQLITE_UPDATE: { + int i; + zWrite = idxAppendText(&rc, zWrite, "UPDATE %Q SET ", zInt); + for(i=0; inCol; i++){ + zWrite = idxAppendText(&rc, zWrite, "%s%Q=?", i==0 ? "" : ", ", + pTab->aCol[i].zName + ); + } + break; + } + default: { + assert( pWrite->eOp==SQLITE_DELETE ); + if( rc==SQLITE_OK ){ + zWrite = sqlite3_mprintf("DELETE FROM %Q", zInt); + if( zWrite==0 ) rc = SQLITE_NOMEM; + } + } + } + + if( rc==SQLITE_OK ){ + sqlite3_stmt *pX = 0; + rc = sqlite3_prepare_v2(p->dbv, zWrite, -1, &pX, 0); + idxFinalize(&rc, pX); + if( rc!=SQLITE_OK ){ + idxDatabaseError(p->dbv, pzErr); + } + } + sqlite3_free(zWrite); + + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(p->dbv, zDrop, 0, 0, pzErr); + } + + return rc; +} + +static int idxProcessTriggers(sqlite3expert *p, char **pzErr){ + int rc = SQLITE_OK; + IdxWrite *pEnd = 0; + IdxWrite *pFirst = p->pWrite; + + while( rc==SQLITE_OK && pFirst!=pEnd ){ + IdxWrite *pIter; + for(pIter=pFirst; rc==SQLITE_OK && pIter!=pEnd; pIter=pIter->pNext){ + rc = idxProcessOneTrigger(p, pIter, pzErr); + } + pEnd = pFirst; + pFirst = p->pWrite; + } + + return rc; +} + + +static int idxCreateVtabSchema(sqlite3expert *p, char **pzErrmsg){ + int rc = idxRegisterVtab(p); + sqlite3_stmt *pSchema = 0; + + /* For each table in the main db schema: + ** + ** 1) Add an entry to the p->pTable list, and + ** 2) Create the equivalent virtual table in dbv. + */ + rc = idxPrepareStmt(p->db, &pSchema, pzErrmsg, + "SELECT type, name, sql, 1 FROM sqlite_master " + "WHERE type IN ('table','view') AND name NOT LIKE 'sqlite_%%' " + " UNION ALL " + "SELECT type, name, sql, 2 FROM sqlite_master " + "WHERE type = 'trigger'" + " AND tbl_name IN(SELECT name FROM sqlite_master WHERE type = 'view') " + "ORDER BY 4, 1" + ); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSchema) ){ + const char *zType = (const char*)sqlite3_column_text(pSchema, 0); + const char *zName = (const char*)sqlite3_column_text(pSchema, 1); + const char *zSql = (const char*)sqlite3_column_text(pSchema, 2); + + if( zType[0]=='v' || zType[1]=='r' ){ + rc = sqlite3_exec(p->dbv, zSql, 0, 0, pzErrmsg); + }else{ + IdxTable *pTab; + rc = idxGetTableInfo(p->db, zName, &pTab, pzErrmsg); + if( rc==SQLITE_OK ){ + int i; + char *zInner = 0; + char *zOuter = 0; + pTab->pNext = p->pTable; + p->pTable = pTab; + + /* The statement the vtab will pass to sqlite3_declare_vtab() */ + zInner = idxAppendText(&rc, 0, "CREATE TABLE x("); + for(i=0; inCol; i++){ + zInner = idxAppendText(&rc, zInner, "%s%Q COLLATE %s", + (i==0 ? "" : ", "), pTab->aCol[i].zName, pTab->aCol[i].zColl + ); + } + zInner = idxAppendText(&rc, zInner, ")"); + + /* The CVT statement to create the vtab */ + zOuter = idxAppendText(&rc, 0, + "CREATE VIRTUAL TABLE %Q USING expert(%Q)", zName, zInner + ); + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(p->dbv, zOuter, 0, 0, pzErrmsg); + } + sqlite3_free(zInner); + sqlite3_free(zOuter); + } + } + } + idxFinalize(&rc, pSchema); + return rc; +} + +struct IdxSampleCtx { + int iTarget; + double target; /* Target nRet/nRow value */ + double nRow; /* Number of rows seen */ + double nRet; /* Number of rows returned */ +}; + +static void idxSampleFunc( + sqlite3_context *pCtx, + int argc, + sqlite3_value **argv +){ + struct IdxSampleCtx *p = (struct IdxSampleCtx*)sqlite3_user_data(pCtx); + int bRet; + + (void)argv; + assert( argc==0 ); + if( p->nRow==0.0 ){ + bRet = 1; + }else{ + bRet = (p->nRet / p->nRow) <= p->target; + if( bRet==0 ){ + unsigned short rnd; + sqlite3_randomness(2, (void*)&rnd); + bRet = ((int)rnd % 100) <= p->iTarget; + } + } + + sqlite3_result_int(pCtx, bRet); + p->nRow += 1.0; + p->nRet += (double)bRet; +} + +struct IdxRemCtx { + int nSlot; + struct IdxRemSlot { + int eType; /* SQLITE_NULL, INTEGER, REAL, TEXT, BLOB */ + i64 iVal; /* SQLITE_INTEGER value */ + double rVal; /* SQLITE_FLOAT value */ + int nByte; /* Bytes of space allocated at z */ + int n; /* Size of buffer z */ + char *z; /* SQLITE_TEXT/BLOB value */ + } aSlot[1]; +}; + +/* +** Implementation of scalar function rem(). +*/ +static void idxRemFunc( + sqlite3_context *pCtx, + int argc, + sqlite3_value **argv +){ + struct IdxRemCtx *p = (struct IdxRemCtx*)sqlite3_user_data(pCtx); + struct IdxRemSlot *pSlot; + int iSlot; + assert( argc==2 ); + + iSlot = sqlite3_value_int(argv[0]); + assert( iSlot<=p->nSlot ); + pSlot = &p->aSlot[iSlot]; + + switch( pSlot->eType ){ + case SQLITE_NULL: + /* no-op */ + break; + + case SQLITE_INTEGER: + sqlite3_result_int64(pCtx, pSlot->iVal); + break; + + case SQLITE_FLOAT: + sqlite3_result_double(pCtx, pSlot->rVal); + break; + + case SQLITE_BLOB: + sqlite3_result_blob(pCtx, pSlot->z, pSlot->n, SQLITE_TRANSIENT); + break; + + case SQLITE_TEXT: + sqlite3_result_text(pCtx, pSlot->z, pSlot->n, SQLITE_TRANSIENT); + break; + } + + pSlot->eType = sqlite3_value_type(argv[1]); + switch( pSlot->eType ){ + case SQLITE_NULL: + /* no-op */ + break; + + case SQLITE_INTEGER: + pSlot->iVal = sqlite3_value_int64(argv[1]); + break; + + case SQLITE_FLOAT: + pSlot->rVal = sqlite3_value_double(argv[1]); + break; + + case SQLITE_BLOB: + case SQLITE_TEXT: { + int nByte = sqlite3_value_bytes(argv[1]); + if( nByte>pSlot->nByte ){ + char *zNew = (char*)sqlite3_realloc(pSlot->z, nByte*2); + if( zNew==0 ){ + sqlite3_result_error_nomem(pCtx); + return; + } + pSlot->nByte = nByte*2; + pSlot->z = zNew; + } + pSlot->n = nByte; + if( pSlot->eType==SQLITE_BLOB ){ + memcpy(pSlot->z, sqlite3_value_blob(argv[1]), nByte); + }else{ + memcpy(pSlot->z, sqlite3_value_text(argv[1]), nByte); + } + break; + } + } +} + +static int idxLargestIndex(sqlite3 *db, int *pnMax, char **pzErr){ + int rc = SQLITE_OK; + const char *zMax = + "SELECT max(i.seqno) FROM " + " sqlite_master AS s, " + " pragma_index_list(s.name) AS l, " + " pragma_index_info(l.name) AS i " + "WHERE s.type = 'table'"; + sqlite3_stmt *pMax = 0; + + *pnMax = 0; + rc = idxPrepareStmt(db, &pMax, pzErr, zMax); + if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pMax) ){ + *pnMax = sqlite3_column_int(pMax, 0) + 1; + } + idxFinalize(&rc, pMax); + + return rc; +} + +static int idxPopulateOneStat1( + sqlite3expert *p, + sqlite3_stmt *pIndexXInfo, + sqlite3_stmt *pWriteStat, + const char *zTab, + const char *zIdx, + char **pzErr +){ + char *zCols = 0; + char *zOrder = 0; + char *zQuery = 0; + int nCol = 0; + int i; + sqlite3_stmt *pQuery = 0; + int *aStat = 0; + int rc = SQLITE_OK; + + assert( p->iSample>0 ); + + /* Formulate the query text */ + sqlite3_bind_text(pIndexXInfo, 1, zIdx, -1, SQLITE_STATIC); + while( SQLITE_OK==rc && SQLITE_ROW==sqlite3_step(pIndexXInfo) ){ + const char *zComma = zCols==0 ? "" : ", "; + const char *zName = (const char*)sqlite3_column_text(pIndexXInfo, 0); + const char *zColl = (const char*)sqlite3_column_text(pIndexXInfo, 1); + zCols = idxAppendText(&rc, zCols, + "%sx.%Q IS rem(%d, x.%Q) COLLATE %s", zComma, zName, nCol, zName, zColl + ); + zOrder = idxAppendText(&rc, zOrder, "%s%d", zComma, ++nCol); + } + sqlite3_reset(pIndexXInfo); + if( rc==SQLITE_OK ){ + if( p->iSample==100 ){ + zQuery = sqlite3_mprintf( + "SELECT %s FROM %Q x ORDER BY %s", zCols, zTab, zOrder + ); + }else{ + zQuery = sqlite3_mprintf( + "SELECT %s FROM temp."UNIQUE_TABLE_NAME" x ORDER BY %s", zCols, zOrder + ); + } + } + sqlite3_free(zCols); + sqlite3_free(zOrder); + + /* Formulate the query text */ + if( rc==SQLITE_OK ){ + sqlite3 *dbrem = (p->iSample==100 ? p->db : p->dbv); + rc = idxPrepareStmt(dbrem, &pQuery, pzErr, zQuery); + } + sqlite3_free(zQuery); + + if( rc==SQLITE_OK ){ + aStat = (int*)idxMalloc(&rc, sizeof(int)*(nCol+1)); + } + if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pQuery) ){ + IdxHashEntry *pEntry; + char *zStat = 0; + for(i=0; i<=nCol; i++) aStat[i] = 1; + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pQuery) ){ + aStat[0]++; + for(i=0; ihIdx, zIdx, STRLEN(zIdx)); + if( pEntry ){ + assert( pEntry->zVal2==0 ); + pEntry->zVal2 = zStat; + }else{ + sqlite3_free(zStat); + } + } + sqlite3_free(aStat); + idxFinalize(&rc, pQuery); + + return rc; +} + +static int idxBuildSampleTable(sqlite3expert *p, const char *zTab){ + int rc; + char *zSql; + + rc = sqlite3_exec(p->dbv,"DROP TABLE IF EXISTS temp."UNIQUE_TABLE_NAME,0,0,0); + if( rc!=SQLITE_OK ) return rc; + + zSql = sqlite3_mprintf( + "CREATE TABLE temp." UNIQUE_TABLE_NAME " AS SELECT * FROM %Q", zTab + ); + if( zSql==0 ) return SQLITE_NOMEM; + rc = sqlite3_exec(p->dbv, zSql, 0, 0, 0); + sqlite3_free(zSql); + + return rc; +} + +/* +** This function is called as part of sqlite3_expert_analyze(). Candidate +** indexes have already been created in database sqlite3expert.dbm, this +** function populates sqlite_stat1 table in the same database. +** +** The stat1 data is generated by querying the +*/ +static int idxPopulateStat1(sqlite3expert *p, char **pzErr){ + int rc = SQLITE_OK; + int nMax =0; + struct IdxRemCtx *pCtx = 0; + struct IdxSampleCtx samplectx; + int i; + i64 iPrev = -100000; + sqlite3_stmt *pAllIndex = 0; + sqlite3_stmt *pIndexXInfo = 0; + sqlite3_stmt *pWrite = 0; + + const char *zAllIndex = + "SELECT s.rowid, s.name, l.name FROM " + " sqlite_master AS s, " + " pragma_index_list(s.name) AS l " + "WHERE s.type = 'table'"; + const char *zIndexXInfo = + "SELECT name, coll FROM pragma_index_xinfo(?) WHERE key"; + const char *zWrite = "INSERT INTO sqlite_stat1 VALUES(?, ?, ?)"; + + /* If iSample==0, no sqlite_stat1 data is required. */ + if( p->iSample==0 ) return SQLITE_OK; + + rc = idxLargestIndex(p->dbm, &nMax, pzErr); + if( nMax<=0 || rc!=SQLITE_OK ) return rc; + + rc = sqlite3_exec(p->dbm, "ANALYZE; PRAGMA writable_schema=1", 0, 0, 0); + + if( rc==SQLITE_OK ){ + int nByte = sizeof(struct IdxRemCtx) + (sizeof(struct IdxRemSlot) * nMax); + pCtx = (struct IdxRemCtx*)idxMalloc(&rc, nByte); + } + + if( rc==SQLITE_OK ){ + sqlite3 *dbrem = (p->iSample==100 ? p->db : p->dbv); + rc = sqlite3_create_function( + dbrem, "rem", 2, SQLITE_UTF8, (void*)pCtx, idxRemFunc, 0, 0 + ); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function( + p->db, "sample", 0, SQLITE_UTF8, (void*)&samplectx, idxSampleFunc, 0, 0 + ); + } + + if( rc==SQLITE_OK ){ + pCtx->nSlot = nMax+1; + rc = idxPrepareStmt(p->dbm, &pAllIndex, pzErr, zAllIndex); + } + if( rc==SQLITE_OK ){ + rc = idxPrepareStmt(p->dbm, &pIndexXInfo, pzErr, zIndexXInfo); + } + if( rc==SQLITE_OK ){ + rc = idxPrepareStmt(p->dbm, &pWrite, pzErr, zWrite); + } + + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pAllIndex) ){ + i64 iRowid = sqlite3_column_int64(pAllIndex, 0); + const char *zTab = (const char*)sqlite3_column_text(pAllIndex, 1); + const char *zIdx = (const char*)sqlite3_column_text(pAllIndex, 2); + if( p->iSample<100 && iPrev!=iRowid ){ + samplectx.target = (double)p->iSample / 100.0; + samplectx.iTarget = p->iSample; + samplectx.nRow = 0.0; + samplectx.nRet = 0.0; + rc = idxBuildSampleTable(p, zTab); + if( rc!=SQLITE_OK ) break; + } + rc = idxPopulateOneStat1(p, pIndexXInfo, pWrite, zTab, zIdx, pzErr); + iPrev = iRowid; + } + if( rc==SQLITE_OK && p->iSample<100 ){ + rc = sqlite3_exec(p->dbv, + "DROP TABLE IF EXISTS temp." UNIQUE_TABLE_NAME, 0,0,0 + ); + } + + idxFinalize(&rc, pAllIndex); + idxFinalize(&rc, pIndexXInfo); + idxFinalize(&rc, pWrite); + + for(i=0; inSlot; i++){ + sqlite3_free(pCtx->aSlot[i].z); + } + sqlite3_free(pCtx); + + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(p->dbm, "ANALYZE sqlite_master", 0, 0, 0); + } + + sqlite3_exec(p->db, "DROP TABLE IF EXISTS temp."UNIQUE_TABLE_NAME,0,0,0); + return rc; +} + +/* +** Allocate a new sqlite3expert object. +*/ +sqlite3expert *sqlite3_expert_new(sqlite3 *db, char **pzErrmsg){ + int rc = SQLITE_OK; + sqlite3expert *pNew; + + pNew = (sqlite3expert*)idxMalloc(&rc, sizeof(sqlite3expert)); + + /* Open two in-memory databases to work with. The "vtab database" (dbv) + ** will contain a virtual table corresponding to each real table in + ** the user database schema, and a copy of each view. It is used to + ** collect information regarding the WHERE, ORDER BY and other clauses + ** of the user's query. + */ + if( rc==SQLITE_OK ){ + pNew->db = db; + pNew->iSample = 100; + rc = sqlite3_open(":memory:", &pNew->dbv); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_open(":memory:", &pNew->dbm); + if( rc==SQLITE_OK ){ + sqlite3_db_config(pNew->dbm, SQLITE_DBCONFIG_TRIGGER_EQP, 1, (int*)0); + } + } + + + /* Copy the entire schema of database [db] into [dbm]. */ + if( rc==SQLITE_OK ){ + sqlite3_stmt *pSql; + rc = idxPrintfPrepareStmt(pNew->db, &pSql, pzErrmsg, + "SELECT sql FROM sqlite_master WHERE name NOT LIKE 'sqlite_%%'" + " AND sql NOT LIKE 'CREATE VIRTUAL %%'" + ); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ + const char *zSql = (const char*)sqlite3_column_text(pSql, 0); + rc = sqlite3_exec(pNew->dbm, zSql, 0, 0, pzErrmsg); + } + idxFinalize(&rc, pSql); + } + + /* Create the vtab schema */ + if( rc==SQLITE_OK ){ + rc = idxCreateVtabSchema(pNew, pzErrmsg); + } + + /* Register the auth callback with dbv */ + if( rc==SQLITE_OK ){ + sqlite3_set_authorizer(pNew->dbv, idxAuthCallback, (void*)pNew); + } + + /* If an error has occurred, free the new object and reutrn NULL. Otherwise, + ** return the new sqlite3expert handle. */ + if( rc!=SQLITE_OK ){ + sqlite3_expert_destroy(pNew); + pNew = 0; + } + return pNew; +} + +/* +** Configure an sqlite3expert object. +*/ +int sqlite3_expert_config(sqlite3expert *p, int op, ...){ + int rc = SQLITE_OK; + va_list ap; + va_start(ap, op); + switch( op ){ + case EXPERT_CONFIG_SAMPLE: { + int iVal = va_arg(ap, int); + if( iVal<0 ) iVal = 0; + if( iVal>100 ) iVal = 100; + p->iSample = iVal; + break; + } + default: + rc = SQLITE_NOTFOUND; + break; + } + + va_end(ap); + return rc; +} + +/* +** Add an SQL statement to the analysis. +*/ +int sqlite3_expert_sql( + sqlite3expert *p, /* From sqlite3_expert_new() */ + const char *zSql, /* SQL statement to add */ + char **pzErr /* OUT: Error message (if any) */ +){ + IdxScan *pScanOrig = p->pScan; + IdxStatement *pStmtOrig = p->pStatement; + int rc = SQLITE_OK; + const char *zStmt = zSql; + + if( p->bRun ) return SQLITE_MISUSE; + + while( rc==SQLITE_OK && zStmt && zStmt[0] ){ + sqlite3_stmt *pStmt = 0; + rc = sqlite3_prepare_v2(p->dbv, zStmt, -1, &pStmt, &zStmt); + if( rc==SQLITE_OK ){ + if( pStmt ){ + IdxStatement *pNew; + const char *z = sqlite3_sql(pStmt); + int n = STRLEN(z); + pNew = (IdxStatement*)idxMalloc(&rc, sizeof(IdxStatement) + n+1); + if( rc==SQLITE_OK ){ + pNew->zSql = (char*)&pNew[1]; + memcpy(pNew->zSql, z, n+1); + pNew->pNext = p->pStatement; + if( p->pStatement ) pNew->iId = p->pStatement->iId+1; + p->pStatement = pNew; + } + sqlite3_finalize(pStmt); + } + }else{ + idxDatabaseError(p->dbv, pzErr); + } + } + + if( rc!=SQLITE_OK ){ + idxScanFree(p->pScan, pScanOrig); + idxStatementFree(p->pStatement, pStmtOrig); + p->pScan = pScanOrig; + p->pStatement = pStmtOrig; + } + + return rc; +} + +int sqlite3_expert_analyze(sqlite3expert *p, char **pzErr){ + int rc; + IdxHashEntry *pEntry; + + /* Do trigger processing to collect any extra IdxScan structures */ + rc = idxProcessTriggers(p, pzErr); + + /* Create candidate indexes within the in-memory database file */ + if( rc==SQLITE_OK ){ + rc = idxCreateCandidates(p); + } + + /* Generate the stat1 data */ + if( rc==SQLITE_OK ){ + rc = idxPopulateStat1(p, pzErr); + } + + /* Formulate the EXPERT_REPORT_CANDIDATES text */ + for(pEntry=p->hIdx.pFirst; pEntry; pEntry=pEntry->pNext){ + p->zCandidates = idxAppendText(&rc, p->zCandidates, + "%s;%s%s\n", pEntry->zVal, + pEntry->zVal2 ? " -- stat1: " : "", pEntry->zVal2 + ); + } + + /* Figure out which of the candidate indexes are preferred by the query + ** planner and report the results to the user. */ + if( rc==SQLITE_OK ){ + rc = idxFindIndexes(p, pzErr); + } + + if( rc==SQLITE_OK ){ + p->bRun = 1; + } + return rc; +} + +/* +** Return the total number of statements that have been added to this +** sqlite3expert using sqlite3_expert_sql(). +*/ +int sqlite3_expert_count(sqlite3expert *p){ + int nRet = 0; + if( p->pStatement ) nRet = p->pStatement->iId+1; + return nRet; +} + +/* +** Return a component of the report. +*/ +const char *sqlite3_expert_report(sqlite3expert *p, int iStmt, int eReport){ + const char *zRet = 0; + IdxStatement *pStmt; + + if( p->bRun==0 ) return 0; + for(pStmt=p->pStatement; pStmt && pStmt->iId!=iStmt; pStmt=pStmt->pNext); + switch( eReport ){ + case EXPERT_REPORT_SQL: + if( pStmt ) zRet = pStmt->zSql; + break; + case EXPERT_REPORT_INDEXES: + if( pStmt ) zRet = pStmt->zIdx; + break; + case EXPERT_REPORT_PLAN: + if( pStmt ) zRet = pStmt->zEQP; + break; + case EXPERT_REPORT_CANDIDATES: + zRet = p->zCandidates; + break; + } + return zRet; +} + +/* +** Free an sqlite3expert object. +*/ +void sqlite3_expert_destroy(sqlite3expert *p){ + if( p ){ + sqlite3_close(p->dbm); + sqlite3_close(p->dbv); + idxScanFree(p->pScan, 0); + idxStatementFree(p->pStatement, 0); + idxTableFree(p->pTable); + idxWriteFree(p->pWrite); + idxHashClear(&p->hIdx); + sqlite3_free(p->zCandidates); + sqlite3_free(p); + } +} + +#endif /* ifndef SQLITE_OMIT_VIRTUAL_TABLE */ diff --git a/ext/expert/sqlite3expert.h b/ext/expert/sqlite3expert.h new file mode 100644 index 0000000000..39135dc274 --- /dev/null +++ b/ext/expert/sqlite3expert.h @@ -0,0 +1,168 @@ +/* +** 2017 April 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. +** +************************************************************************* +*/ + + +#include "sqlite3.h" + +typedef struct sqlite3expert sqlite3expert; + +/* +** Create a new sqlite3expert object. +** +** If successful, a pointer to the new object is returned and (*pzErr) set +** to NULL. Or, if an error occurs, NULL is returned and (*pzErr) set to +** an English-language error message. In this case it is the responsibility +** of the caller to eventually free the error message buffer using +** sqlite3_free(). +*/ +sqlite3expert *sqlite3_expert_new(sqlite3 *db, char **pzErr); + +/* +** Configure an sqlite3expert object. +** +** EXPERT_CONFIG_SAMPLE: +** By default, sqlite3_expert_analyze() generates sqlite_stat1 data for +** each candidate index. This involves scanning and sorting the entire +** contents of each user database table once for each candidate index +** associated with the table. For large databases, this can be +** prohibitively slow. This option allows the sqlite3expert object to +** be configured so that sqlite_stat1 data is instead generated based on a +** subset of each table, or so that no sqlite_stat1 data is used at all. +** +** A single integer argument is passed to this option. If the value is less +** than or equal to zero, then no sqlite_stat1 data is generated or used by +** the analysis - indexes are recommended based on the database schema only. +** Or, if the value is 100 or greater, complete sqlite_stat1 data is +** generated for each candidate index (this is the default). Finally, if the +** value falls between 0 and 100, then it represents the percentage of user +** table rows that should be considered when generating sqlite_stat1 data. +** +** Examples: +** +** // Do not generate any sqlite_stat1 data +** sqlite3_expert_config(pExpert, EXPERT_CONFIG_SAMPLE, 0); +** +** // Generate sqlite_stat1 data based on 10% of the rows in each table. +** sqlite3_expert_config(pExpert, EXPERT_CONFIG_SAMPLE, 10); +*/ +int sqlite3_expert_config(sqlite3expert *p, int op, ...); + +#define EXPERT_CONFIG_SAMPLE 1 /* int */ + +/* +** Specify zero or more SQL statements to be included in the analysis. +** +** Buffer zSql must contain zero or more complete SQL statements. This +** function parses all statements contained in the buffer and adds them +** to the internal list of statements to analyze. If successful, SQLITE_OK +** is returned and (*pzErr) set to NULL. Or, if an error occurs - for example +** due to a error in the SQL - an SQLite error code is returned and (*pzErr) +** may be set to point to an English language error message. In this case +** the caller is responsible for eventually freeing the error message buffer +** using sqlite3_free(). +** +** If an error does occur while processing one of the statements in the +** buffer passed as the second argument, none of the statements in the +** buffer are added to the analysis. +** +** This function must be called before sqlite3_expert_analyze(). If a call +** to this function is made on an sqlite3expert object that has already +** been passed to sqlite3_expert_analyze() SQLITE_MISUSE is returned +** immediately and no statements are added to the analysis. +*/ +int sqlite3_expert_sql( + sqlite3expert *p, /* From a successful sqlite3_expert_new() */ + const char *zSql, /* SQL statement(s) to add */ + char **pzErr /* OUT: Error message (if any) */ +); + + +/* +** This function is called after the sqlite3expert object has been configured +** with all SQL statements using sqlite3_expert_sql() to actually perform +** the analysis. Once this function has been called, it is not possible to +** add further SQL statements to the analysis. +** +** If successful, SQLITE_OK is returned and (*pzErr) is set to NULL. Or, if +** an error occurs, an SQLite error code is returned and (*pzErr) set to +** point to a buffer containing an English language error message. In this +** case it is the responsibility of the caller to eventually free the buffer +** using sqlite3_free(). +** +** If an error does occur within this function, the sqlite3expert object +** is no longer useful for any purpose. At that point it is no longer +** possible to add further SQL statements to the object or to re-attempt +** the analysis. The sqlite3expert object must still be freed using a call +** sqlite3_expert_destroy(). +*/ +int sqlite3_expert_analyze(sqlite3expert *p, char **pzErr); + +/* +** Return the total number of statements loaded using sqlite3_expert_sql(). +** The total number of SQL statements may be different from the total number +** to calls to sqlite3_expert_sql(). +*/ +int sqlite3_expert_count(sqlite3expert*); + +/* +** Return a component of the report. +** +** This function is called after sqlite3_expert_analyze() to extract the +** results of the analysis. Each call to this function returns either a +** NULL pointer or a pointer to a buffer containing a nul-terminated string. +** The value passed as the third argument must be one of the EXPERT_REPORT_* +** #define constants defined below. +** +** For some EXPERT_REPORT_* parameters, the buffer returned contains +** information relating to a specific SQL statement. In these cases that +** SQL statement is identified by the value passed as the second argument. +** SQL statements are numbered from 0 in the order in which they are parsed. +** If an out-of-range value (less than zero or equal to or greater than the +** value returned by sqlite3_expert_count()) is passed as the second argument +** along with such an EXPERT_REPORT_* parameter, NULL is always returned. +** +** EXPERT_REPORT_SQL: +** Return the text of SQL statement iStmt. +** +** EXPERT_REPORT_INDEXES: +** Return a buffer containing the CREATE INDEX statements for all recommended +** indexes for statement iStmt. If there are no new recommeded indexes, NULL +** is returned. +** +** EXPERT_REPORT_PLAN: +** Return a buffer containing the EXPLAIN QUERY PLAN output for SQL query +** iStmt after the proposed indexes have been added to the database schema. +** +** EXPERT_REPORT_CANDIDATES: +** Return a pointer to a buffer containing the CREATE INDEX statements +** for all indexes that were tested (for all SQL statements). The iStmt +** parameter is ignored for EXPERT_REPORT_CANDIDATES calls. +*/ +const char *sqlite3_expert_report(sqlite3expert*, int iStmt, int eReport); + +/* +** Values for the third argument passed to sqlite3_expert_report(). +*/ +#define EXPERT_REPORT_SQL 1 +#define EXPERT_REPORT_INDEXES 2 +#define EXPERT_REPORT_PLAN 3 +#define EXPERT_REPORT_CANDIDATES 4 + +/* +** Free an (sqlite3expert*) handle and all associated resources. There +** should be one call to this function for each successful call to +** sqlite3-expert_new(). +*/ +void sqlite3_expert_destroy(sqlite3expert*); + + diff --git a/ext/expert/test_expert.c b/ext/expert/test_expert.c new file mode 100644 index 0000000000..064c1908a9 --- /dev/null +++ b/ext/expert/test_expert.c @@ -0,0 +1,220 @@ +/* +** 2017 April 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. +** +************************************************************************* +*/ + +#if defined(SQLITE_TEST) + +#include "sqlite3expert.h" +#include +#include + +#if defined(INCLUDE_SQLITE_TCL_H) +# include "sqlite_tcl.h" +#else +# include "tcl.h" +# ifndef SQLITE_TCLAPI +# define SQLITE_TCLAPI +# endif +#endif + +#ifndef SQLITE_OMIT_VIRTUALTABLE + +/* +** 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; +} + + +/* +** Tclcmd: $expert sql SQL +** $expert analyze +** $expert count +** $expert report STMT EREPORT +** $expert destroy +*/ +static int SQLITE_TCLAPI testExpertCmd( + void *clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + sqlite3expert *pExpert = (sqlite3expert*)clientData; + struct Subcmd { + const char *zSub; + int nArg; + const char *zMsg; + } aSub[] = { + { "sql", 1, "TABLE", }, /* 0 */ + { "analyze", 0, "", }, /* 1 */ + { "count", 0, "", }, /* 2 */ + { "report", 2, "STMT EREPORT", }, /* 3 */ + { "destroy", 0, "", }, /* 4 */ + { 0 } + }; + int iSub; + int rc = TCL_OK; + char *zErr = 0; + + if( objc<2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ..."); + return TCL_ERROR; + } + rc = Tcl_GetIndexFromObjStruct(interp, + objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub + ); + if( rc!=TCL_OK ) return rc; + if( objc!=2+aSub[iSub].nArg ){ + Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg); + return TCL_ERROR; + } + + switch( iSub ){ + case 0: { /* sql */ + char *zArg = Tcl_GetString(objv[2]); + rc = sqlite3_expert_sql(pExpert, zArg, &zErr); + break; + } + + case 1: { /* analyze */ + rc = sqlite3_expert_analyze(pExpert, &zErr); + break; + } + + case 2: { /* count */ + int n = sqlite3_expert_count(pExpert); + Tcl_SetObjResult(interp, Tcl_NewIntObj(n)); + break; + } + + case 3: { /* report */ + const char *aEnum[] = { + "sql", "indexes", "plan", "candidates", 0 + }; + int iEnum; + int iStmt; + const char *zReport; + + if( Tcl_GetIntFromObj(interp, objv[2], &iStmt) + || Tcl_GetIndexFromObj(interp, objv[3], aEnum, "report", 0, &iEnum) + ){ + return TCL_ERROR; + } + + assert( EXPERT_REPORT_SQL==1 ); + assert( EXPERT_REPORT_INDEXES==2 ); + assert( EXPERT_REPORT_PLAN==3 ); + assert( EXPERT_REPORT_CANDIDATES==4 ); + zReport = sqlite3_expert_report(pExpert, iStmt, 1+iEnum); + Tcl_SetObjResult(interp, Tcl_NewStringObj(zReport, -1)); + break; + } + + default: /* destroy */ + assert( iSub==4 ); + Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); + break; + } + + if( rc!=TCL_OK ){ + if( zErr ){ + Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1)); + }else{ + extern const char *sqlite3ErrName(int); + Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); + } + } + sqlite3_free(zErr); + return rc; +} + +static void SQLITE_TCLAPI testExpertDel(void *clientData){ + sqlite3expert *pExpert = (sqlite3expert*)clientData; + sqlite3_expert_destroy(pExpert); +} + +/* +** sqlite3_expert_new DB +*/ +static int SQLITE_TCLAPI test_sqlite3_expert_new( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + static int iCmd = 0; + sqlite3 *db; + char *zCmd = 0; + char *zErr = 0; + sqlite3expert *pExpert; + int rc = TCL_OK; + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB"); + return TCL_ERROR; + } + if( dbHandleFromObj(interp, objv[1], &db) ){ + return TCL_ERROR; + } + + zCmd = sqlite3_mprintf("sqlite3expert%d", ++iCmd); + if( zCmd==0 ){ + Tcl_AppendResult(interp, "out of memory", (char*)0); + return TCL_ERROR; + } + + pExpert = sqlite3_expert_new(db, &zErr); + if( pExpert==0 ){ + Tcl_AppendResult(interp, zErr, (char*)0); + rc = TCL_ERROR; + }else{ + void *p = (void*)pExpert; + Tcl_CreateObjCommand(interp, zCmd, testExpertCmd, p, testExpertDel); + Tcl_SetObjResult(interp, Tcl_NewStringObj(zCmd, -1)); + } + + sqlite3_free(zCmd); + sqlite3_free(zErr); + return rc; +} + +#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ + +int TestExpert_Init(Tcl_Interp *interp){ +#ifndef SQLITE_OMIT_VIRTUALTABLE + struct Cmd { + const char *zCmd; + Tcl_ObjCmdProc *xProc; + } aCmd[] = { + { "sqlite3_expert_new", test_sqlite3_expert_new }, + }; + int i; + + for(i=0; izCmd, p->xProc, 0, 0); + } +#endif + return TCL_OK; +} + +#endif diff --git a/ext/fts3/fts3_write.c b/ext/fts3/fts3_write.c index daf3399a43..0baf82b76e 100644 --- a/ext/fts3/fts3_write.c +++ b/ext/fts3/fts3_write.c @@ -1908,6 +1908,7 @@ static int fts3WriteSegment( sqlite3_bind_blob(pStmt, 2, z, n, SQLITE_STATIC); sqlite3_step(pStmt); rc = sqlite3_reset(pStmt); + sqlite3_bind_null(pStmt, 2); } return rc; } @@ -1964,6 +1965,7 @@ static int fts3WriteSegdir( sqlite3_bind_blob(pStmt, 6, zRoot, nRoot, SQLITE_STATIC); sqlite3_step(pStmt); rc = sqlite3_reset(pStmt); + sqlite3_bind_null(pStmt, 6); } return rc; } @@ -3443,6 +3445,7 @@ static void fts3UpdateDocTotals( sqlite3_bind_blob(pStmt, 2, pBlob, nBlob, SQLITE_STATIC); sqlite3_step(pStmt); *pRC = sqlite3_reset(pStmt); + sqlite3_bind_null(pStmt, 2); sqlite3_free(a); } @@ -4631,6 +4634,7 @@ static int fts3TruncateSegment( sqlite3_bind_int(pChomp, 4, iIdx); sqlite3_step(pChomp); rc = sqlite3_reset(pChomp); + sqlite3_bind_null(pChomp, 2); } } @@ -4710,6 +4714,7 @@ static int fts3IncrmergeHintStore(Fts3Table *p, Blob *pHint){ sqlite3_bind_blob(pReplace, 2, pHint->a, pHint->n, SQLITE_STATIC); sqlite3_step(pReplace); rc = sqlite3_reset(pReplace); + sqlite3_bind_null(pReplace, 2); } return rc; @@ -5524,7 +5529,6 @@ int sqlite3Fts3UpdateMethod( ){ Fts3Table *p = (Fts3Table *)pVtab; int rc = SQLITE_OK; /* Return Code */ - int isRemove = 0; /* True for an UPDATE or DELETE */ u32 *aSzIns = 0; /* Sizes of inserted documents */ u32 *aSzDel = 0; /* Sizes of deleted documents */ int nChng = 0; /* Net change in number of documents */ @@ -5622,7 +5626,6 @@ int sqlite3Fts3UpdateMethod( if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){ assert( sqlite3_value_type(apVal[0])==SQLITE_INTEGER ); rc = fts3DeleteByRowid(p, apVal[0], &nChng, aSzDel); - isRemove = 1; } /* If this is an INSERT or UPDATE operation, insert the new record. */ @@ -5634,7 +5637,7 @@ int sqlite3Fts3UpdateMethod( rc = FTS_CORRUPT_VTAB; } } - if( rc==SQLITE_OK && (!isRemove || *pRowid!=p->iPrevDocid ) ){ + if( rc==SQLITE_OK ){ rc = fts3PendingTermsDocid(p, 0, iLangid, *pRowid); } if( rc==SQLITE_OK ){ diff --git a/ext/fts5/fts5Int.h b/ext/fts5/fts5Int.h index 63dc082687..c87583676b 100644 --- a/ext/fts5/fts5Int.h +++ b/ext/fts5/fts5Int.h @@ -722,6 +722,8 @@ Fts5ExprPhrase *sqlite3Fts5ParseTerm( int bPrefix ); +void sqlite3Fts5ParseSetCaret(Fts5ExprPhrase*); + Fts5ExprNearset *sqlite3Fts5ParseNearset( Fts5Parse*, Fts5ExprNearset*, diff --git a/ext/fts5/fts5_aux.c b/ext/fts5/fts5_aux.c index 219ea6fff8..594b981dda 100644 --- a/ext/fts5/fts5_aux.c +++ b/ext/fts5/fts5_aux.c @@ -358,6 +358,16 @@ static int fts5SnippetScore( return rc; } +/* +** Return the value in pVal interpreted as utf-8 text. Except, if pVal +** contains a NULL value, return a pointer to a static string zero +** bytes in length instead of a NULL pointer. +*/ +static const char *fts5ValueToText(sqlite3_value *pVal){ + const char *zRet = (const char*)sqlite3_value_text(pVal); + return zRet ? zRet : ""; +} + /* ** Implementation of snippet() function. */ @@ -393,9 +403,9 @@ static void fts5SnippetFunction( 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]); + ctx.zOpen = fts5ValueToText(apVal[1]); + ctx.zClose = fts5ValueToText(apVal[2]); + zEllips = fts5ValueToText(apVal[3]); nToken = sqlite3_value_int(apVal[4]); iBestCol = (iCol>=0 ? iCol : 0); diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c index aa7141cfee..a86dbebdf9 100644 --- a/ext/fts5/fts5_expr.c +++ b/ext/fts5/fts5_expr.c @@ -87,7 +87,8 @@ struct Fts5ExprNode { ** or term prefix. */ struct Fts5ExprTerm { - int bPrefix; /* True for a prefix term */ + u8 bPrefix; /* True for a prefix term */ + u8 bFirst; /* True if token must be first in column */ char *zTerm; /* nul-terminated term */ Fts5IndexIter *pIter; /* Iterator for this term */ Fts5ExprTerm *pSynonym; /* Pointer to first in list of synonyms */ @@ -168,6 +169,7 @@ static int fts5ExprGetToken( case '+': tok = FTS5_PLUS; break; case '*': tok = FTS5_STAR; break; case '-': tok = FTS5_MINUS; break; + case '^': tok = FTS5_CARET; break; case '\0': tok = FTS5_EOF; break; case '"': { @@ -427,6 +429,7 @@ static int fts5ExprPhraseIsMatch( Fts5PoslistReader *aIter = aStatic; int i; int rc = SQLITE_OK; + int bFirst = pPhrase->aTerm[0].bFirst; fts5BufferZero(&pPhrase->poslist); @@ -481,8 +484,10 @@ static int fts5ExprPhraseIsMatch( }while( bMatch==0 ); /* Append position iPos to the output */ - rc = sqlite3Fts5PoslistWriterAppend(&pPhrase->poslist, &writer, iPos); - if( rc!=SQLITE_OK ) goto ismatch_out; + if( bFirst==0 || FTS5_POS2OFFSET(iPos)==0 ){ + rc = sqlite3Fts5PoslistWriterAppend(&pPhrase->poslist, &writer, iPos); + if( rc!=SQLITE_OK ) goto ismatch_out; + } for(i=0; inTerm; i++){ if( sqlite3Fts5PoslistReaderNext(&aIter[i]) ) goto ismatch_out; @@ -736,7 +741,9 @@ static int fts5ExprNearTest( ** phrase is not a match, break out of the loop early. */ for(i=0; rc==SQLITE_OK && inPhrase; i++){ Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; - if( pPhrase->nTerm>1 || pPhrase->aTerm[0].pSynonym || pNear->pColset ){ + if( pPhrase->nTerm>1 || pPhrase->aTerm[0].pSynonym + || pNear->pColset || pPhrase->aTerm[0].bFirst + ){ int bMatch = 0; rc = fts5ExprPhraseIsMatch(pNode, pPhrase, &bMatch); if( bMatch==0 ) break; @@ -917,6 +924,7 @@ static int fts5ExprNodeTest_STRING( assert( pNear->nPhrase>1 || pNear->apPhrase[0]->nTerm>1 || pNear->apPhrase[0]->aTerm[0].pSynonym + || pNear->apPhrase[0]->aTerm[0].bFirst ); /* Initialize iLast, the "lastest" rowid any iterator points to. If the @@ -1441,6 +1449,16 @@ static void fts5ExprPhraseFree(Fts5ExprPhrase *pPhrase){ } } +/* +** Set the "bFirst" flag on the first token of the phrase passed as the +** only argument. +*/ +void sqlite3Fts5ParseSetCaret(Fts5ExprPhrase *pPhrase){ + if( pPhrase && pPhrase->nTerm ){ + pPhrase->aTerm[0].bFirst = 1; + } +} + /* ** If argument pNear is NULL, then a new Fts5ExprNearset object is allocated ** and populated with pPhrase. Or, if pNear is not NULL, phrase pPhrase is @@ -1719,6 +1737,7 @@ int sqlite3Fts5ExprClonePhrase( } if( rc==SQLITE_OK ){ sCtx.pPhrase->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix; + sCtx.pPhrase->aTerm[i].bFirst = pOrig->aTerm[i].bFirst; } } }else{ @@ -1737,7 +1756,10 @@ int sqlite3Fts5ExprClonePhrase( pNew->pRoot->pNear->nPhrase = 1; sCtx.pPhrase->pNode = pNew->pRoot; - if( pOrig->nTerm==1 && pOrig->aTerm[0].pSynonym==0 ){ + if( pOrig->nTerm==1 + && pOrig->aTerm[0].pSynonym==0 + && pOrig->aTerm[0].bFirst==0 + ){ pNew->pRoot->eType = FTS5_TERM; pNew->pRoot->xNext = fts5ExprNodeNext_TERM; }else{ @@ -2011,6 +2033,7 @@ static void fts5ExprAssignXNext(Fts5ExprNode *pNode){ Fts5ExprNearset *pNear = pNode->pNear; if( pNear->nPhrase==1 && pNear->apPhrase[0]->nTerm==1 && pNear->apPhrase[0]->aTerm[0].pSynonym==0 + && pNear->apPhrase[0]->aTerm[0].bFirst==0 ){ pNode->eType = FTS5_TERM; pNode->xNext = fts5ExprNodeNext_TERM; @@ -2097,20 +2120,23 @@ Fts5ExprNode *sqlite3Fts5ParseNode( } } - if( pParse->pConfig->eDetail!=FTS5_DETAIL_FULL - && (pNear->nPhrase!=1 || pNear->apPhrase[0]->nTerm>1) - ){ - assert( pParse->rc==SQLITE_OK ); - pParse->rc = SQLITE_ERROR; - assert( pParse->zErr==0 ); - pParse->zErr = sqlite3_mprintf( - "fts5: %s queries are not supported (detail!=full)", - pNear->nPhrase==1 ? "phrase": "NEAR" - ); - sqlite3_free(pRet); - pRet = 0; + if( pParse->pConfig->eDetail!=FTS5_DETAIL_FULL ){ + Fts5ExprPhrase *pPhrase = pNear->apPhrase[0]; + if( pNear->nPhrase!=1 + || pPhrase->nTerm>1 + || (pPhrase->nTerm>0 && pPhrase->aTerm[0].bFirst) + ){ + assert( pParse->rc==SQLITE_OK ); + pParse->rc = SQLITE_ERROR; + assert( pParse->zErr==0 ); + pParse->zErr = sqlite3_mprintf( + "fts5: %s queries are not supported (detail!=full)", + pNear->nPhrase==1 ? "phrase": "NEAR" + ); + sqlite3_free(pRet); + pRet = 0; + } } - }else{ fts5ExprAddChildren(pRet, pLeft); fts5ExprAddChildren(pRet, pRight); diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index c94122838d..412a04faca 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -758,6 +758,7 @@ static void fts5DataWrite(Fts5Index *p, i64 iRowid, const u8 *pData, int nData){ sqlite3_bind_blob(p->pWriter, 2, pData, nData, SQLITE_STATIC); sqlite3_step(p->pWriter); p->rc = sqlite3_reset(p->pWriter); + sqlite3_bind_null(p->pWriter, 2); } /* @@ -2386,6 +2387,7 @@ static void fts5SegIterSeekInit( bDlidx = (val & 0x0001); } p->rc = sqlite3_reset(pIdxSelect); + sqlite3_bind_null(pIdxSelect, 2); if( iPgpgnoFirst ){ iPg = pSeg->pgnoFirst; @@ -3598,6 +3600,7 @@ static int fts5AllocateSegid(Fts5Index *p, Fts5Structure *pStruct){ sqlite3_bind_blob(pIdxSelect, 2, aBlob, 2, SQLITE_STATIC); assert( sqlite3_step(pIdxSelect)!=SQLITE_ROW ); p->rc = sqlite3_reset(pIdxSelect); + sqlite3_bind_null(pIdxSelect, 2); } } #endif @@ -3724,6 +3727,7 @@ static void fts5WriteFlushBtree(Fts5Index *p, Fts5SegWriter *pWriter){ sqlite3_bind_int64(p->pIdxWriter, 3, bFlag + ((i64)pWriter->iBtPage<<1)); sqlite3_step(p->pIdxWriter); p->rc = sqlite3_reset(p->pIdxWriter); + sqlite3_bind_null(p->pIdxWriter, 2); } pWriter->iBtPage = 0; } @@ -4909,7 +4913,13 @@ static void fts5MergePrefixLists( Fts5Buffer out = {0, 0, 0}; Fts5Buffer tmp = {0, 0, 0}; - if( sqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n) ) return; + /* The maximum size of the output is equal to the sum of the two + ** input sizes + 1 varint (9 bytes). The extra varint is because if the + ** first rowid in one input is a large negative number, and the first in + ** the other a non-negative number, the delta for the non-negative + ** number will be larger on disk than the literal integer value + ** was. */ + if( sqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n + 9) ) return; fts5DoclistIterInit(p1, &i1); fts5DoclistIterInit(p2, &i2); @@ -5003,6 +5013,7 @@ static void fts5MergePrefixLists( fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid); fts5BufferSafeAppendBlob(&out, i2.aPoslist, i2.aEof - i2.aPoslist); } + assert( out.n<=(p1->n+p2->n+9) ); fts5BufferSet(&p->rc, p1, out.n, out.p); fts5BufferFree(&tmp); diff --git a/ext/fts5/fts5_storage.c b/ext/fts5/fts5_storage.c index 59336fc7ac..70d7135113 100644 --- a/ext/fts5/fts5_storage.c +++ b/ext/fts5/fts5_storage.c @@ -458,6 +458,7 @@ static int fts5StorageInsertDocsize( sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC); sqlite3_step(pReplace); rc = sqlite3_reset(pReplace); + sqlite3_bind_null(pReplace, 2); } } return rc; @@ -1118,6 +1119,7 @@ int sqlite3Fts5StorageConfigValue( } sqlite3_step(pReplace); rc = sqlite3_reset(pReplace); + sqlite3_bind_null(pReplace, 1); } if( rc==SQLITE_OK && pVal ){ int iNew = p->pConfig->iCookie + 1; diff --git a/ext/fts5/fts5_tcl.c b/ext/fts5/fts5_tcl.c index e8d4c32a46..f33dcae033 100644 --- a/ext/fts5/fts5_tcl.c +++ b/ext/fts5/fts5_tcl.c @@ -433,7 +433,7 @@ static int SQLITE_TCLAPI xF5tApi( int iVal; int bClear; if( Tcl_GetBooleanFromObj(interp, objv[2], &bClear) ) return TCL_ERROR; - iVal = ((char*)p->pApi->xGetAuxdata(p->pFts, bClear) - (char*)0); + iVal = (int)((char*)p->pApi->xGetAuxdata(p->pFts, bClear) - (char*)0); Tcl_SetObjResult(interp, Tcl_NewIntObj(iVal)); break; } @@ -482,7 +482,7 @@ static int SQLITE_TCLAPI xF5tApi( rc = p->pApi->xPhraseFirstColumn(p->pFts, iPhrase, &iter, &iCol); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, sqlite3ErrName(rc), 0); + Tcl_SetResult(interp, (char*)sqlite3ErrName(rc), TCL_VOLATILE); return TCL_ERROR; } for( ; iCol>=0; p->pApi->xPhraseNextColumn(p->pFts, &iter, &iCol)){ @@ -924,7 +924,7 @@ static int SQLITE_TCLAPI f5tTokenizerReturn( rc = p->xToken(p->pCtx, tflags, zToken, nToken, iStart, iEnd); Tcl_SetResult(interp, (char*)sqlite3ErrName(rc), TCL_VOLATILE); - return TCL_OK; + return rc==SQLITE_OK ? TCL_OK : TCL_ERROR; usage: Tcl_WrongNumArgs(interp, 1, objv, "?-colocated? TEXT START END"); diff --git a/ext/fts5/fts5parse.y b/ext/fts5/fts5parse.y index 1582909aa8..134125db1f 100644 --- a/ext/fts5/fts5parse.y +++ b/ext/fts5/fts5parse.y @@ -148,7 +148,11 @@ cnearset(A) ::= colset(X) COLON nearset(Y). { %destructor nearset { sqlite3Fts5ParseNearsetFree($$); } %destructor nearphrases { sqlite3Fts5ParseNearsetFree($$); } -nearset(A) ::= phrase(X). { A = sqlite3Fts5ParseNearset(pParse, 0, X); } +nearset(A) ::= phrase(Y). { A = sqlite3Fts5ParseNearset(pParse, 0, Y); } +nearset(A) ::= CARET phrase(Y). { + sqlite3Fts5ParseSetCaret(Y); + A = sqlite3Fts5ParseNearset(pParse, 0, Y); +} nearset(A) ::= STRING(X) LP nearphrases(Y) neardist_opt(Z) RP. { sqlite3Fts5ParseNear(pParse, &X); sqlite3Fts5ParseSetDistance(pParse, Y, &Z); @@ -189,6 +193,5 @@ phrase(A) ::= STRING(Y) star_opt(Z). { ** Optional "*" character. */ %type star_opt {int} - star_opt(A) ::= STAR. { A = 1; } star_opt(A) ::= . { A = 0; } diff --git a/ext/fts5/test/fts5aa.test b/ext/fts5/test/fts5aa.test index a3ea0afc28..67cb62012a 100644 --- a/ext/fts5/test/fts5aa.test +++ b/ext/fts5/test/fts5aa.test @@ -593,5 +593,5 @@ do_execsql_test 22.1 { } - +expand_all_sql db finish_test diff --git a/ext/fts5/test/fts5af.test b/ext/fts5/test/fts5af.test index fa4ebd2955..86c8f753fa 100644 --- a/ext/fts5/test/fts5af.test +++ b/ext/fts5/test/fts5af.test @@ -175,6 +175,16 @@ do_execsql_test 5.1 { SELECT snippet(p1, 0, '[', ']', '...', 6) FROM p1('x'); } {{[x] a a a a a...}} +do_execsql_test 5.2 { + SELECT snippet(p1, 0, '[', ']', NULL, 6) FROM p1('x'); +} {{[x] a a a a a}} +do_execsql_test 5.3 { + SELECT snippet(p1, 0, NULL, ']', '...', 6) FROM p1('x'); +} {{x] a a a a a...}} +do_execsql_test 5.4 { + SELECT snippet(p1, 0, '[', NULL, '...', 6) FROM p1('x'); +} {{[x a a a a a...}} + } ;# foreach_detail_mode finish_test diff --git a/ext/fts5/test/fts5fault6.test b/ext/fts5/test/fts5fault6.test index 1c1c9f20c1..a39063a356 100644 --- a/ext/fts5/test/fts5fault6.test +++ b/ext/fts5/test/fts5fault6.test @@ -253,7 +253,7 @@ do_faultsim_test 5.2 -faults oom* -prep { SELECT rowid, mit(matchinfo(t1, 'x')) FROM t1 WHERE t1 MATCH 'a AND c' } } -test { - faultsim_test_result [list 0 $::res] + faultsim_test_result [list 0 $::res] {1 {SQL logic error}} } do_faultsim_test 5.3 -faults oom* -prep { @@ -264,7 +264,7 @@ do_faultsim_test 5.3 -faults oom* -prep { SELECT count(*) FROM t1 WHERE t1 MATCH 'd AND e AND f' } } -test { - faultsim_test_result {0 29} + faultsim_test_result {0 29} {1 {SQL logic error}} } do_faultsim_test 5.4 -faults oom* -prep { @@ -275,7 +275,7 @@ do_faultsim_test 5.4 -faults oom* -prep { SELECT count(*) FROM t1 WHERE t1 MATCH 'x + e' } } -test { - faultsim_test_result {0 1} + faultsim_test_result {0 1} {1 {SQL logic error}} } #------------------------------------------------------------------------- diff --git a/ext/fts5/test/fts5fault9.test b/ext/fts5/test/fts5fault9.test index 1daa5c1cc9..669b13efe7 100644 --- a/ext/fts5/test/fts5fault9.test +++ b/ext/fts5/test/fts5fault9.test @@ -24,6 +24,8 @@ ifcapable !fts5 { foreach_detail_mode $testprefix { +if {"%DETAIL%" != "none"} continue + fts5_aux_test_functions db do_execsql_test 1.0 { @@ -98,14 +100,16 @@ do_faultsim_test 4.1 -faults oom-t* -body { execsql { SELECT rowid, fts5_test_collist(t4) FROM t4('2') } } -test { faultsim_test_result \ - {0 {1 {0.0 0.1 0.2} 2 {0.0 0.1 0.2} 3 {0.0 0.1 0.2}}} {1 SQLITE_NOMEM} + {0 {1 {0.0 0.1 0.2} 2 {0.0 0.1 0.2} 3 {0.0 0.1 0.2}}} \ + {1 SQLITE_NOMEM} {1 SQLITE_ERROR} {1 {SQL logic error}} } do_faultsim_test 4.2 -faults oom-t* -body { execsql { SELECT rowid, fts5_test_collist(t4) FROM t4('a5 OR b5 OR c5') } } -test { faultsim_test_result \ - {0 {4 {0.0 0.1 0.2} 5 {1.0 1.1 1.2} 6 {2.0 2.1 2.2}}} {1 SQLITE_NOMEM} + {0 {4 {0.0 0.1 0.2} 5 {1.0 1.1 1.2} 6 {2.0 2.1 2.2}}} \ + {1 SQLITE_NOMEM} {1 SQLITE_ERROR} {1 {SQL logic error}} } diff --git a/ext/fts5/test/fts5faultB.test b/ext/fts5/test/fts5faultB.test index a4fef523f5..2faec706d5 100644 --- a/ext/fts5/test/fts5faultB.test +++ b/ext/fts5/test/fts5faultB.test @@ -130,5 +130,22 @@ do_faultsim_test 4.2 -faults oom* -body { faultsim_test_result {0 {2 3}} } +#------------------------------------------------------------------------- +# Test OOM injection while parsing a CARET expression +# +reset_db +do_execsql_test 5.0 { + CREATE VIRTUAL TABLE t1 USING fts5(a); + INSERT INTO t1 VALUES('a b c d'); -- 1 + INSERT INTO t1 VALUES('d a b c'); -- 2 + INSERT INTO t1 VALUES('c d a b'); -- 3 + INSERT INTO t1 VALUES('b c d a'); -- 4 +} +do_faultsim_test 5.1 -faults oom* -body { + execsql { SELECT rowid FROM t1('^a OR ^b') } +} -test { + faultsim_test_result {0 {1 4}} +} + finish_test diff --git a/ext/fts5/test/fts5first.test b/ext/fts5/test/fts5first.test new file mode 100644 index 0000000000..b2cac1fdf1 --- /dev/null +++ b/ext/fts5/test/fts5first.test @@ -0,0 +1,96 @@ +# 2017 November 25 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5first + +ifcapable !fts5 { + finish_test + return +} + + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE x1 USING fts5(a, b); +} + +foreach {tn expr ok} { + 1 {^abc} 1 + 2 {^abc + def} 1 + 3 {^ "abc def"} 1 + 4 {^"abc def"} 1 + 5 {abc ^def} 1 + 6 {abc + ^def} 0 + 7 {abc ^+ def} 0 + 8 {"^abc"} 1 + 9 {NEAR(^abc def)} 0 +} { + set res(0) {/1 {fts5: syntax error near .*}/} + set res(1) {0 {}} + + do_catchsql_test 1.$tn { SELECT * FROM x1($expr) } $res($ok) +} + +#------------------------------------------------------------------------- +# +do_execsql_test 2.0 { + INSERT INTO x1 VALUES('a b c', 'b c a'); +} + +foreach {tn expr match} { + 1 {^a} 1 + 2 {^b} 1 + 3 {^c} 0 + 4 {^a + b} 1 + 5 {^b + c} 1 + 6 {^c + a} 0 + 7 {^"c a"} 0 + 8 {a:^a} 1 + 9 {a:^b} 0 + 10 {a:^"a b"} 1 +} { + do_execsql_test 2.$tn { SELECT EXISTS (SELECT rowid FROM x1($expr)) } $match +} + +#------------------------------------------------------------------------- +# +do_execsql_test 3.0 { + DELETE FROM x1; + INSERT INTO x1 VALUES('b a', 'c a'); + INSERT INTO x1 VALUES('a a', 'c c'); + INSERT INTO x1 VALUES('a b', 'a a'); +} +fts5_aux_test_functions db + +foreach {tn expr expect} { + 1 {^a} {{2 1}} + 2 {^c AND ^b} {{0 2} {1 0}} +} { + do_execsql_test 3.$tn { + SELECT fts5_test_queryphrase(x1) FROM x1($expr) LIMIT 1 + } [list $expect] +} + +#------------------------------------------------------------------------- +# +do_execsql_test 3.1 { + CREATE VIRTUAL TABLE x2 USING fts5(a, b, c, detail=column); +} + +do_catchsql_test 3.2 { + SELECT * FROM x2('a + b'); +} {1 {fts5: phrase queries are not supported (detail!=full)}} + +do_catchsql_test 3.3 { + SELECT * FROM x2('^a'); +} {1 {fts5: phrase queries are not supported (detail!=full)}} +finish_test + diff --git a/ext/fts5/test/fts5query.test b/ext/fts5/test/fts5query.test index 854651ef4f..5237e8e250 100644 --- a/ext/fts5/test/fts5query.test +++ b/ext/fts5/test/fts5query.test @@ -64,7 +64,7 @@ for {set tn 1 ; set pgsz 64} {$tn<32} {incr tn; incr pgsz 16} { execsql COMMIT } {} - do_execsql_test 1.$tn.2 { + do_execsql_test 2.$tn.2 { INSERT INTO t1(t1) VALUES('integrity-check'); } @@ -77,5 +77,15 @@ for {set tn 1 ; set pgsz 64} {$tn<32} {incr tn; incr pgsz 16} { } } +reset_db +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE x1 USING fts5(a); + INSERT INTO x1(rowid, a) VALUES(-1000000000000, 'toyota'); + INSERT INTO x1(rowid, a) VALUES(1, 'tarago'); +} +do_execsql_test 3.1 { + SELECT rowid FROM x1('t*'); +} {-1000000000000 1} + finish_test diff --git a/ext/icu/icu.c b/ext/icu/icu.c index 33c4aa76f3..13524ebc2a 100644 --- a/ext/icu/icu.c +++ b/ext/icu/icu.c @@ -28,7 +28,9 @@ ** provide case-independent matching. */ -#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU) +#if !defined(SQLITE_CORE) \ + || defined(SQLITE_ENABLE_ICU) \ + || defined(SQLITE_ENABLE_ICU_COLLATIONS) /* Include ICU headers */ #include @@ -45,6 +47,26 @@ #include "sqlite3.h" #endif +/* +** This function is called when an ICU function called from within +** the implementation of an SQL scalar function returns an error. +** +** The scalar function context passed as the first argument is +** loaded with an error message based on the following two args. +*/ +static void icuFunctionError( + sqlite3_context *pCtx, /* SQLite scalar function context */ + const char *zName, /* Name of ICU function that failed */ + UErrorCode e /* Error code returned by ICU function */ +){ + char zBuf[128]; + sqlite3_snprintf(128, zBuf, "ICU error: %s(): %s", zName, u_errorName(e)); + zBuf[127] = '\0'; + sqlite3_result_error(pCtx, zBuf, -1); +} + +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU) + /* ** Maximum length (in bytes) of the pattern in a LIKE or GLOB ** operator. @@ -224,24 +246,6 @@ static void icuLikeFunc( } } -/* -** This function is called when an ICU function called from within -** the implementation of an SQL scalar function returns an error. -** -** The scalar function context passed as the first argument is -** loaded with an error message based on the following two args. -*/ -static void icuFunctionError( - sqlite3_context *pCtx, /* SQLite scalar function context */ - const char *zName, /* Name of ICU function that failed */ - UErrorCode e /* Error code returned by ICU function */ -){ - char zBuf[128]; - sqlite3_snprintf(128, zBuf, "ICU error: %s(): %s", zName, u_errorName(e)); - zBuf[127] = '\0'; - sqlite3_result_error(pCtx, zBuf, -1); -} - /* ** Function to delete compiled regexp objects. Registered as ** a destructor function with sqlite3_set_auxdata(). @@ -407,6 +411,8 @@ static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){ assert( 0 ); /* Unreachable */ } +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU) */ + /* ** Collation sequence destructor function. The pCtx argument points to ** a UCollator structure previously allocated using ucol_open(). @@ -501,6 +507,7 @@ int sqlite3IcuInit(sqlite3 *db){ void (*xFunc)(sqlite3_context*,int,sqlite3_value**); } scalars[] = { {"icu_load_collation", 2, SQLITE_UTF8, 1, icuLoadCollation}, +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU) {"regexp", 2, SQLITE_ANY|SQLITE_DETERMINISTIC, 0, icuRegexpFunc}, {"lower", 1, SQLITE_UTF16|SQLITE_DETERMINISTIC, 0, icuCaseFunc16}, {"lower", 2, SQLITE_UTF16|SQLITE_DETERMINISTIC, 0, icuCaseFunc16}, @@ -512,10 +519,10 @@ int sqlite3IcuInit(sqlite3 *db){ {"upper", 2, SQLITE_UTF8|SQLITE_DETERMINISTIC, 1, icuCaseFunc16}, {"like", 2, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuLikeFunc}, {"like", 3, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuLikeFunc}, +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU) */ }; int rc = SQLITE_OK; int i; - for(i=0; rc==SQLITE_OK && i<(int)(sizeof(scalars)/sizeof(scalars[0])); i++){ const struct IcuScalar *p = &scalars[i]; diff --git a/ext/lsm1/lsm-test/lsmtest.h b/ext/lsm1/lsm-test/lsmtest.h index 249bc999e0..ca60424add 100644 --- a/ext/lsm1/lsm-test/lsmtest.h +++ b/ext/lsm1/lsm-test/lsmtest.h @@ -121,6 +121,7 @@ int test_mdb_scan(TestDb *, void *, int, void *, int, void *, int, */ int test_lsm_open(const char*, const char *zFile, int bClear, TestDb **ppDb); int test_lsm_lomem_open(const char*, const char*, int bClear, TestDb **ppDb); +int test_lsm_lomem2_open(const char*, const char*, int bClear, TestDb **ppDb); int test_lsm_zip_open(const char*, const char*, int bClear, TestDb **ppDb); int test_lsm_small_open(const char*, const char*, int bClear, TestDb **ppDb); int test_lsm_mt2(const char*, const char *zFile, int bClear, TestDb **ppDb); diff --git a/ext/lsm1/lsm-test/lsmtest_tdb.c b/ext/lsm1/lsm-test/lsmtest_tdb.c index 8377bc2ed2..9c4f9df8a4 100644 --- a/ext/lsm1/lsm-test/lsmtest_tdb.c +++ b/ext/lsm1/lsm-test/lsmtest_tdb.c @@ -721,6 +721,7 @@ static struct Lib { { "sqlite3", "testdb.sqlite", sql_open }, { "lsm_small", "testdb.lsm_small", test_lsm_small_open }, { "lsm_lomem", "testdb.lsm_lomem", test_lsm_lomem_open }, + { "lsm_lomem2", "testdb.lsm_lomem2", test_lsm_lomem2_open }, #ifdef HAVE_ZLIB { "lsm_zip", "testdb.lsm_zip", test_lsm_zip_open }, #endif diff --git a/ext/lsm1/lsm-test/lsmtest_tdb3.c b/ext/lsm1/lsm-test/lsmtest_tdb3.c index 1862023bad..c21e243b5c 100644 --- a/ext/lsm1/lsm-test/lsmtest_tdb3.c +++ b/ext/lsm1/lsm-test/lsmtest_tdb3.c @@ -1033,6 +1033,19 @@ int test_lsm_lomem_open( return testLsmOpen(zCfg, zFilename, bClear, ppDb); } +int test_lsm_lomem2_open( + const char *zSpec, + const char *zFilename, + int bClear, + TestDb **ppDb +){ + /* "max_freelist=4 autocheckpoint=32" */ + const char *zCfg = + "page_size=512 block_size=64 autoflush=0 mmap=0 " + ; + return testLsmOpen(zCfg, zFilename, bClear, ppDb); +} + int test_lsm_zip_open( const char *zSpec, const char *zFilename, diff --git a/ext/lsm1/lsmInt.h b/ext/lsm1/lsmInt.h index b346d8dded..0f822e4793 100644 --- a/ext/lsm1/lsmInt.h +++ b/ext/lsm1/lsmInt.h @@ -110,7 +110,7 @@ typedef unsigned long long int u64; #endif /* A page number is a 64-bit integer. */ -typedef i64 Pgno; +typedef i64 LsmPgno; #ifdef LSM_DEBUG int lsmErrorBkpt(int); @@ -402,9 +402,9 @@ struct lsm_db { }; struct Segment { - Pgno iFirst; /* First page of this run */ - Pgno iLastPg; /* Last page of this run */ - Pgno iRoot; /* Root page number (if any) */ + LsmPgno iFirst; /* First page of this run */ + LsmPgno iLastPg; /* Last page of this run */ + LsmPgno iRoot; /* Root page number (if any) */ int nSize; /* Size of this run in pages */ Redirect *pRedirect; /* Block redirects (or NULL) */ @@ -456,7 +456,7 @@ struct Level { ** output segment. */ struct MergeInput { - Pgno iPg; /* Page on which next input is stored */ + LsmPgno iPg; /* Page on which next input is stored */ int iCell; /* Cell containing next input to merge */ }; struct Merge { @@ -465,7 +465,7 @@ struct Merge { MergeInput splitkey; /* Location in file of current splitkey */ int nSkip; /* Number of separators entries to skip */ int iOutputOff; /* Write offset on output page */ - Pgno iCurrentPtr; /* Current pointer value */ + LsmPgno iCurrentPtr; /* Current pointer value */ }; /* @@ -579,10 +579,10 @@ struct Snapshot { Redirect redirect; /* Block redirection array */ /* Used by worker snapshots only */ - int nBlock; /* Number of blocks in database file */ - Pgno aiAppend[LSM_APPLIST_SZ]; /* Append point list */ - Freelist freelist; /* Free block list */ - u32 nWrite; /* Total number of pages written to disk */ + int nBlock; /* Number of blocks in database file */ + LsmPgno aiAppend[LSM_APPLIST_SZ]; /* Append point list */ + Freelist freelist; /* Free block list */ + u32 nWrite; /* Total number of pages written to disk */ }; #define LSM_INITIAL_SNAPSHOT_ID 11 @@ -710,7 +710,7 @@ void lsmFsSetPageSize(FileSystem *, int); int lsmFsFileid(lsm_db *pDb, void **ppId, int *pnId); /* Creating, populating, gobbling and deleting sorted runs. */ -void lsmFsGobble(lsm_db *, Segment *, Pgno *, int); +void lsmFsGobble(lsm_db *, Segment *, LsmPgno *, int); int lsmFsSortedDelete(FileSystem *, Snapshot *, int, Segment *); int lsmFsSortedFinish(FileSystem *, Segment *); int lsmFsSortedAppend(FileSystem *, Snapshot *, Level *, int, Page **); @@ -727,14 +727,14 @@ void lsmSortedSplitkey(lsm_db *, Level *, int *); /* Reading sorted run content. */ int lsmFsDbPageLast(FileSystem *pFS, Segment *pSeg, Page **ppPg); -int lsmFsDbPageGet(FileSystem *, Segment *, Pgno, Page **); +int lsmFsDbPageGet(FileSystem *, Segment *, LsmPgno, Page **); int lsmFsDbPageNext(Segment *, Page *, int eDir, Page **); u8 *lsmFsPageData(Page *, int *); int lsmFsPageRelease(Page *); int lsmFsPagePersist(Page *); void lsmFsPageRef(Page *); -Pgno lsmFsPageNumber(Page *); +LsmPgno lsmFsPageNumber(Page *); int lsmFsNRead(FileSystem *); int lsmFsNWrite(FileSystem *); @@ -748,7 +748,7 @@ int lsmFsDbPageIsLast(Segment *pSeg, Page *pPg); int lsmFsIntegrityCheck(lsm_db *); #endif -Pgno lsmFsRedirectPage(FileSystem *, Redirect *, Pgno); +LsmPgno lsmFsRedirectPage(FileSystem *, Redirect *, LsmPgno); int lsmFsPageWritable(Page *); @@ -768,8 +768,8 @@ int lsmFsSyncDb(FileSystem *, int); void lsmFsFlushWaiting(FileSystem *, int *); /* Used by lsm_info(ARRAY_STRUCTURE) and lsm_config(MMAP) */ -int lsmInfoArrayStructure(lsm_db *pDb, int bBlock, Pgno iFirst, char **pzOut); -int lsmInfoArrayPages(lsm_db *pDb, Pgno iFirst, char **pzOut); +int lsmInfoArrayStructure(lsm_db *pDb, int bBlock, LsmPgno iFirst, char **pz); +int lsmInfoArrayPages(lsm_db *pDb, LsmPgno iFirst, char **pzOut); int lsmConfigMmap(lsm_db *pDb, int *piParam); int lsmEnvOpen(lsm_env *, const char *, int, lsm_file **); @@ -785,7 +785,7 @@ void lsmEnvSleep(lsm_env *, int); int lsmFsReadSyncedId(lsm_db *db, int, i64 *piVal); -int lsmFsSegmentContainsPg(FileSystem *pFS, Segment *, Pgno, int *); +int lsmFsSegmentContainsPg(FileSystem *pFS, Segment *, LsmPgno, int *); void lsmFsPurgeCache(FileSystem *); @@ -796,7 +796,7 @@ void lsmFsPurgeCache(FileSystem *); /* ** Functions from file "lsm_sorted.c". */ -int lsmInfoPageDump(lsm_db *, Pgno, int, char **); +int lsmInfoPageDump(lsm_db *, LsmPgno, int, char **); void lsmSortedCleanup(lsm_db *); int lsmSortedAutoWork(lsm_db *, int nUnit); diff --git a/ext/lsm1/lsm_ckpt.c b/ext/lsm1/lsm_ckpt.c index cf4c55bf84..ba92a823cf 100644 --- a/ext/lsm1/lsm_ckpt.c +++ b/ext/lsm1/lsm_ckpt.c @@ -389,7 +389,7 @@ static void ckptExportAppendlist( int *pRc /* IN/OUT: Error code */ ){ int i; - Pgno *aiAppend = db->pWorker->aiAppend; + LsmPgno *aiAppend = db->pWorker->aiAppend; for(i=0; inPagesize <= pFS->nMapLimit); } @@ -540,7 +540,7 @@ static int fsMmapPage(FileSystem *pFS, Pgno iReal){ ** Given that there are currently nHash slots in the hash table, return ** the hash key for file iFile, page iPg. */ -static int fsHashKey(int nHash, Pgno iPg){ +static int fsHashKey(int nHash, LsmPgno iPg){ return (iPg % nHash); } @@ -880,13 +880,13 @@ void lsmFsSetBlockSize(FileSystem *pFS, int nBlocksize){ ** page on each block is the byte offset immediately following the 4-byte ** "previous block" pointer at the start of each block. */ -static Pgno fsFirstPageOnBlock(FileSystem *pFS, int iBlock){ - Pgno iPg; +static LsmPgno fsFirstPageOnBlock(FileSystem *pFS, int iBlock){ + LsmPgno iPg; if( pFS->pCompress ){ if( iBlock==1 ){ iPg = pFS->nMetasize * 2 + 4; }else{ - iPg = pFS->nBlocksize * (Pgno)(iBlock-1) + 4; + iPg = pFS->nBlocksize * (LsmPgno)(iBlock-1) + 4; } }else{ const int nPagePerBlock = (pFS->nBlocksize / pFS->nPagesize); @@ -907,9 +907,9 @@ static Pgno fsFirstPageOnBlock(FileSystem *pFS, int iBlock){ ** page on each block is the byte offset of the byte immediately before ** the 4-byte "next block" pointer at the end of each block. */ -static Pgno fsLastPageOnBlock(FileSystem *pFS, int iBlock){ +static LsmPgno fsLastPageOnBlock(FileSystem *pFS, int iBlock){ if( pFS->pCompress ){ - return pFS->nBlocksize * (Pgno)iBlock - 1 - 4; + return pFS->nBlocksize * (LsmPgno)iBlock - 1 - 4; }else{ const int nPagePerBlock = (pFS->nBlocksize / pFS->nPagesize); return iBlock * nPagePerBlock; @@ -920,7 +920,7 @@ static Pgno fsLastPageOnBlock(FileSystem *pFS, int iBlock){ ** Return the block number of the block that page iPg is located on. ** Blocks are numbered starting from 1. */ -static int fsPageToBlock(FileSystem *pFS, Pgno iPg){ +static int fsPageToBlock(FileSystem *pFS, LsmPgno iPg){ if( pFS->pCompress ){ return (int)((iPg / pFS->nBlocksize) + 1); }else{ @@ -933,7 +933,7 @@ static int fsPageToBlock(FileSystem *pFS, Pgno iPg){ ** ** This function is only called in non-compressed database mode. */ -static int fsIsLast(FileSystem *pFS, Pgno iPg){ +static int fsIsLast(FileSystem *pFS, LsmPgno iPg){ const int nPagePerBlock = (pFS->nBlocksize / pFS->nPagesize); assert( !pFS->pCompress ); return ( iPg && (iPg % nPagePerBlock)==0 ); @@ -944,7 +944,7 @@ static int fsIsLast(FileSystem *pFS, Pgno iPg){ ** ** This function is only called in non-compressed database mode. */ -static int fsIsFirst(FileSystem *pFS, Pgno iPg){ +static int fsIsFirst(FileSystem *pFS, LsmPgno iPg){ const int nPagePerBlock = (pFS->nBlocksize / pFS->nPagesize); assert( !pFS->pCompress ); return ( (iPg % nPagePerBlock)==1 @@ -967,7 +967,7 @@ u8 *lsmFsPageData(Page *pPage, int *pnData){ /* ** Return the page number of a page. */ -Pgno lsmFsPageNumber(Page *pPage){ +LsmPgno lsmFsPageNumber(Page *pPage){ /* assert( (pPage->flags & PAGE_DIRTY)==0 ); */ return pPage ? pPage->iPg : 0; } @@ -1058,7 +1058,7 @@ void lsmFsPurgeCache(FileSystem *pFS){ ** Either way, if argument piHash is not NULL set *piHash to the hash slot ** number that page iPg would be stored in before returning. */ -static Page *fsPageFindInHash(FileSystem *pFS, Pgno iPg, int *piHash){ +static Page *fsPageFindInHash(FileSystem *pFS, LsmPgno iPg, int *piHash){ Page *p; /* Return value */ int iHash = fsHashKey(pFS->nHash, iPg); @@ -1189,8 +1189,8 @@ static int fsRedirectBlock(Redirect *p, int iBlk){ ** object passed as the second argument, return the destination page to ** which it is redirected. Otherwise, return a copy of iPg. */ -Pgno lsmFsRedirectPage(FileSystem *pFS, Redirect *pRedir, Pgno iPg){ - Pgno iReal = iPg; +LsmPgno lsmFsRedirectPage(FileSystem *pFS, Redirect *pRedir, LsmPgno iPg){ + LsmPgno iReal = iPg; if( pRedir ){ const int nPagePerBlock = ( @@ -1203,7 +1203,7 @@ Pgno lsmFsRedirectPage(FileSystem *pFS, Redirect *pRedir, Pgno iPg){ if( iFrom>iBlk ) break; if( iFrom==iBlk ){ int iTo = pRedir->a[i].iTo; - iReal = iPg - (Pgno)(iFrom - iTo) * nPagePerBlock; + iReal = iPg - (LsmPgno)(iFrom - iTo) * nPagePerBlock; if( iTo==1 ){ iReal += (fsFirstPageOnBlock(pFS, 1)-1); } @@ -1217,7 +1217,7 @@ Pgno lsmFsRedirectPage(FileSystem *pFS, Redirect *pRedir, Pgno iPg){ } /* Required by the circular fsBlockNext<->fsPageGet dependency. */ -static int fsPageGet(FileSystem *, Segment *, Pgno, int, Page **, int *); +static int fsPageGet(FileSystem *, Segment *, LsmPgno, int, Page **, int *); /* ** Parameter iBlock is a database file block. This function reads the value @@ -1269,7 +1269,7 @@ static int fsBlockNext( /* ** Return the page number of the last page on the same block as page iPg. */ -Pgno fsLastPageOnPagesBlock(FileSystem *pFS, Pgno iPg){ +LsmPgno fsLastPageOnPagesBlock(FileSystem *pFS, LsmPgno iPg){ return fsLastPageOnBlock(pFS, fsPageToBlock(pFS, iPg)); } @@ -1537,7 +1537,7 @@ static int fsReadPagedata( static int fsPageGet( FileSystem *pFS, /* File-system handle */ Segment *pSeg, /* Block redirection to use (or NULL) */ - Pgno iPg, /* Page id */ + LsmPgno iPg, /* Page id */ int noContent, /* True to not load content from disk */ Page **ppPg, /* OUT: New page handle */ int *pnSpace /* OUT: Bytes of free space */ @@ -1549,7 +1549,7 @@ static int fsPageGet( /* In most cases iReal is the same as iPg. Except, if pSeg->pRedirect is ** not NULL, and the block containing iPg has been redirected, then iReal ** is the page number after redirection. */ - Pgno iReal = lsmFsRedirectPage(pFS, (pSeg ? pSeg->pRedirect : 0), iPg); + LsmPgno iReal = lsmFsRedirectPage(pFS, (pSeg ? pSeg->pRedirect : 0), iPg); assert_lists_are_ok(pFS); assert( iPg>=fsFirstPageOnBlock(pFS, 1) ); @@ -1689,8 +1689,8 @@ int lsmFsReadSyncedId(lsm_db *db, int iMeta, i64 *piVal){ static int fsRunEndsBetween( Segment *pRun, Segment *pIgnore, - Pgno iFirst, - Pgno iLast + LsmPgno iFirst, + LsmPgno iLast ){ return (pRun!=pIgnore && ( (pRun->iFirst>=iFirst && pRun->iFirst<=iLast) @@ -1705,8 +1705,8 @@ static int fsRunEndsBetween( static int fsLevelEndsBetween( Level *pLevel, Segment *pIgnore, - Pgno iFirst, - Pgno iLast + LsmPgno iFirst, + LsmPgno iLast ){ int i; @@ -1733,13 +1733,13 @@ static int fsFreeBlock( int iBlk /* Block number of block to free */ ){ int rc = LSM_OK; /* Return code */ - Pgno iFirst; /* First page on block iBlk */ - Pgno iLast; /* Last page on block iBlk */ + LsmPgno iFirst; /* First page on block iBlk */ + LsmPgno iLast; /* Last page on block iBlk */ Level *pLevel; /* Used to iterate through levels */ int iIn; /* Used to iterate through append points */ int iOut = 0; /* Used to output append points */ - Pgno *aApp = pSnapshot->aiAppend; + LsmPgno *aApp = pSnapshot->aiAppend; iFirst = fsFirstPageOnBlock(pFS, iBlk); iLast = fsLastPageOnBlock(pFS, iBlk); @@ -1811,11 +1811,16 @@ int lsmFsSortedDelete( ** number from the array that falls on block iBlk. Or, if none of the pages ** in aPgno[] fall on block iBlk, return 0. */ -static Pgno firstOnBlock(FileSystem *pFS, int iBlk, Pgno *aPgno, int nPgno){ - Pgno iRet = 0; +static LsmPgno firstOnBlock( + FileSystem *pFS, + int iBlk, + LsmPgno *aPgno, + int nPgno +){ + LsmPgno iRet = 0; int i; for(i=0; ipRedirect, iPg)); } @@ -1854,7 +1859,7 @@ static int fsSegmentRedirects(FileSystem *pFS, Segment *p){ void lsmFsGobble( lsm_db *pDb, Segment *pRun, - Pgno *aPgno, + LsmPgno *aPgno, int nPgno ){ int rc = LSM_OK; @@ -1871,7 +1876,7 @@ void lsmFsGobble( while( rc==LSM_OK ){ int iNext = 0; - Pgno iFirst = firstOnBlock(pFS, iBlk, aPgno, nPgno); + LsmPgno iFirst = firstOnBlock(pFS, iBlk, aPgno, nPgno); if( iFirst ){ pRun->iFirst = iFirst; break; @@ -1905,11 +1910,11 @@ void lsmFsGobble( static int fsNextPageOffset( FileSystem *pFS, /* File system object */ Segment *pSeg, /* Segment to move within */ - Pgno iPg, /* Offset of current page */ + LsmPgno iPg, /* Offset of current page */ int nByte, /* Size of current page including headers */ - Pgno *piNext /* OUT: Offset of next page. Or zero (EOF) */ + LsmPgno *piNext /* OUT: Offset of next page. Or zero (EOF) */ ){ - Pgno iNext; + LsmPgno iNext; int rc; assert( pFS->pCompress ); @@ -1939,8 +1944,8 @@ static int fsNextPageOffset( static int fsGetPageBefore( FileSystem *pFS, Segment *pSeg, - Pgno iPg, - Pgno *piPrev + LsmPgno iPg, + LsmPgno *piPrev ){ u8 aSz[3]; int rc; @@ -1990,7 +1995,7 @@ static int fsGetPageBefore( int lsmFsDbPageNext(Segment *pRun, Page *pPg, int eDir, Page **ppNext){ int rc = LSM_OK; FileSystem *pFS = pPg->pFS; - Pgno iPg = pPg->iPg; + LsmPgno iPg = pPg->iPg; assert( 0==fsSegmentRedirects(pFS, pRun) ); if( pFS->pCompress ){ @@ -2062,10 +2067,10 @@ int lsmFsDbPageNext(Segment *pRun, Page *pPg, int eDir, Page **ppNext){ ** start the new segment immediately following any segment that is part ** of the right-hand-side of pLvl. */ -static Pgno findAppendPoint(FileSystem *pFS, Level *pLvl){ +static LsmPgno findAppendPoint(FileSystem *pFS, Level *pLvl){ int i; - Pgno *aiAppend = pFS->pDb->pWorker->aiAppend; - Pgno iRet = 0; + LsmPgno *aiAppend = pFS->pDb->pWorker->aiAppend; + LsmPgno iRet = 0; for(i=LSM_APPLIST_SZ-1; iRet==0 && i>=0; i--){ if( (iRet = aiAppend[i]) ){ @@ -2098,10 +2103,10 @@ int lsmFsSortedAppend( ){ int rc = LSM_OK; Page *pPg = 0; - Pgno iApp = 0; - Pgno iNext = 0; + LsmPgno iApp = 0; + LsmPgno iNext = 0; Segment *p = &pLvl->lhs; - Pgno iPrev = p->iLastPg; + LsmPgno iPrev = p->iLastPg; *ppOut = 0; assert( p->pRedirect==0 ); @@ -2195,7 +2200,7 @@ int lsmFsSortedFinish(FileSystem *pFS, Segment *p){ */ if( fsLastPageOnPagesBlock(pFS, p->iLastPg)!=p->iLastPg ){ int i; - Pgno *aiAppend = pFS->pDb->pWorker->aiAppend; + LsmPgno *aiAppend = pFS->pDb->pWorker->aiAppend; for(i=0; iiLastPg+1; @@ -2226,7 +2231,7 @@ int lsmFsSortedFinish(FileSystem *pFS, Segment *p){ ** ** Return LSM_OK if successful, or an lsm error code if an error occurs. */ -int lsmFsDbPageGet(FileSystem *pFS, Segment *pSeg, Pgno iPg, Page **ppPg){ +int lsmFsDbPageGet(FileSystem *pFS, Segment *pSeg, LsmPgno iPg, Page **ppPg){ return fsPageGet(pFS, pSeg, iPg, 0, ppPg, 0); } @@ -2238,7 +2243,7 @@ int lsmFsDbPageGet(FileSystem *pFS, Segment *pSeg, Pgno iPg, Page **ppPg){ */ int lsmFsDbPageLast(FileSystem *pFS, Segment *pSeg, Page **ppPg){ int rc; - Pgno iPg = pSeg->iLastPg; + LsmPgno iPg = pSeg->iLastPg; if( pFS->pCompress ){ int nSpace; iPg++; @@ -2366,14 +2371,14 @@ static void fsMovePage( FileSystem *pFS, /* File system object */ int iTo, /* Destination block */ int iFrom, /* Source block */ - Pgno *piPg /* IN/OUT: Page number */ + LsmPgno *piPg /* IN/OUT: Page number */ ){ - Pgno iPg = *piPg; + LsmPgno iPg = *piPg; if( iFrom==fsPageToBlock(pFS, iPg) ){ const int nPagePerBlock = ( pFS->pCompress ? pFS ->nBlocksize : (pFS->nBlocksize / pFS->nPagesize) ); - *piPg = iPg - (Pgno)(iFrom - iTo) * nPagePerBlock; + *piPg = iPg - (LsmPgno)(iFrom - iTo) * nPagePerBlock; } } @@ -2457,21 +2462,21 @@ int lsmFsMoveBlock(FileSystem *pFS, Segment *pSeg, int iTo, int iFrom){ ** ** This function is only used in compressed database mode. */ -static Pgno fsAppendData( +static LsmPgno fsAppendData( FileSystem *pFS, /* File-system handle */ Segment *pSeg, /* Segment to append to */ const u8 *aData, /* Buffer containing data to write */ int nData, /* Size of buffer aData[] in bytes */ int *pRc /* IN/OUT: Error code */ ){ - Pgno iRet = 0; + LsmPgno iRet = 0; int rc = *pRc; assert( pFS->pCompress ); if( rc==LSM_OK ){ int nRem = 0; int nWrite = 0; - Pgno iLastOnBlock; - Pgno iApp = pSeg->iLastPg+1; + LsmPgno iLastOnBlock; + LsmPgno iApp = pSeg->iLastPg+1; /* If this is the first data written into the segment, find an append-point ** or allocate a new block. */ @@ -2519,7 +2524,7 @@ static Pgno fsAppendData( /* Set the "prev" pointer on the new block */ if( rc==LSM_OK ){ - Pgno iWrite; + LsmPgno iWrite; lsmPutU32(aPtr, fsPageToBlock(pFS, iApp)); iWrite = fsFirstPageOnBlock(pFS, iBlk); rc = lsmEnvWrite(pFS->pEnv, pFS->fdDb, iWrite-4, aPtr, sizeof(aPtr)); @@ -2588,11 +2593,11 @@ static int fsCompressIntoBuffer(FileSystem *pFS, Page *pPg){ static int fsAppendPage( FileSystem *pFS, Segment *pSeg, - Pgno *piNew, + LsmPgno *piNew, int *piPrev, int *piNext ){ - Pgno iPrev = pSeg->iLastPg; + LsmPgno iPrev = pSeg->iLastPg; int rc; assert( iPrev!=0 ); @@ -2650,7 +2655,7 @@ void lsmFsFlushWaiting(FileSystem *pFS, int *pRc){ /* ** If there exists a hash-table entry associated with page iPg, remove it. */ -static void fsRemoveHashEntry(FileSystem *pFS, Pgno iPg){ +static void fsRemoveHashEntry(FileSystem *pFS, LsmPgno iPg){ Page *p; int iHash = fsHashKey(pFS->nHash, iPg); @@ -2804,8 +2809,8 @@ int lsmFsSortedPadding( ){ int rc = LSM_OK; if( pFS->pCompress && pSeg->iFirst ){ - Pgno iLast2; - Pgno iLast = pSeg->iLastPg; /* Current last page of segment */ + LsmPgno iLast2; + LsmPgno iLast = pSeg->iLastPg; /* Current last page of segment */ int nPad; /* Bytes of padding required */ u8 aSz[3]; @@ -2935,7 +2940,7 @@ int lsmFsSectorSize(FileSystem *pFS){ /* ** Helper function for lsmInfoArrayStructure(). */ -static Segment *startsWith(Segment *pRun, Pgno iFirst){ +static Segment *startsWith(Segment *pRun, LsmPgno iFirst){ return (iFirst==pRun->iFirst) ? pRun : 0; } @@ -2943,7 +2948,7 @@ static Segment *startsWith(Segment *pRun, Pgno iFirst){ ** Return the segment that starts with page iFirst, if any. If no such segment ** can be found, return NULL. */ -static Segment *findSegment(Snapshot *pWorker, Pgno iFirst){ +static Segment *findSegment(Snapshot *pWorker, LsmPgno iFirst){ Level *pLvl; /* Used to iterate through db levels */ Segment *pSeg = 0; /* Pointer to segment to return */ @@ -2970,7 +2975,7 @@ static Segment *findSegment(Snapshot *pWorker, Pgno iFirst){ int lsmInfoArrayStructure( lsm_db *pDb, int bBlock, /* True for block numbers only */ - Pgno iFirst, + LsmPgno iFirst, char **pzOut ){ int rc = LSM_OK; @@ -3035,7 +3040,7 @@ int lsmInfoArrayStructure( int lsmFsSegmentContainsPg( FileSystem *pFS, Segment *pSeg, - Pgno iPg, + LsmPgno iPg, int *pbRes ){ Redirect *pRedir = pSeg->pRedirect; @@ -3064,7 +3069,7 @@ int lsmFsSegmentContainsPg( ** ** If an error occurs, *pzOut is set to NULL and an LSM error code returned. */ -int lsmInfoArrayPages(lsm_db *pDb, Pgno iFirst, char **pzOut){ +int lsmInfoArrayPages(lsm_db *pDb, LsmPgno iFirst, char **pzOut){ int rc = LSM_OK; Snapshot *pWorker; /* Worker snapshot */ Segment *pSeg = 0; /* Array to report on */ @@ -3297,7 +3302,7 @@ int lsmFsIntegrityCheck(lsm_db *pDb){ */ int lsmFsDbPageIsLast(Segment *pSeg, Page *pPg){ if( pPg->pFS->pCompress ){ - Pgno iNext = 0; + LsmPgno iNext = 0; int rc; rc = fsNextPageOffset(pPg->pFS, pSeg, pPg->iPg, pPg->nCompress+6, &iNext); return (rc!=LSM_OK || iNext==0); diff --git a/ext/lsm1/lsm_main.c b/ext/lsm1/lsm_main.c index 8a324a3efe..a9c48e004e 100644 --- a/ext/lsm1/lsm_main.c +++ b/ext/lsm1/lsm_main.c @@ -583,14 +583,14 @@ int lsm_info(lsm_db *pDb, int eParam, ...){ } case LSM_INFO_ARRAY_STRUCTURE: { - Pgno pgno = va_arg(ap, Pgno); + LsmPgno pgno = va_arg(ap, LsmPgno); char **pzVal = va_arg(ap, char **); rc = lsmInfoArrayStructure(pDb, 0, pgno, pzVal); break; } case LSM_INFO_ARRAY_PAGES: { - Pgno pgno = va_arg(ap, Pgno); + LsmPgno pgno = va_arg(ap, LsmPgno); char **pzVal = va_arg(ap, char **); rc = lsmInfoArrayPages(pDb, pgno, pzVal); break; @@ -598,7 +598,7 @@ int lsm_info(lsm_db *pDb, int eParam, ...){ case LSM_INFO_PAGE_HEX_DUMP: case LSM_INFO_PAGE_ASCII_DUMP: { - Pgno pgno = va_arg(ap, Pgno); + LsmPgno pgno = va_arg(ap, LsmPgno); char **pzVal = va_arg(ap, char **); int bUnlock = 0; rc = infoGetWorker(pDb, 0, &bUnlock); @@ -683,7 +683,7 @@ static int doWriteOp( int nDiff; if( nQuant>pDb->nTreeLimit ){ - nQuant = pDb->nTreeLimit; + nQuant = LSM_MAX(pDb->nTreeLimit, pgsz); } nBefore = lsmTreeSize(pDb); diff --git a/ext/lsm1/lsm_sorted.c b/ext/lsm1/lsm_sorted.c index f479f4ce8c..de962b8808 100644 --- a/ext/lsm1/lsm_sorted.c +++ b/ext/lsm1/lsm_sorted.c @@ -92,7 +92,7 @@ #define SEGMENT_POINTER_OFFSET(pgsz) ((pgsz) - 2 - 2 - 8) #define SEGMENT_CELLPTR_OFFSET(pgsz, iCell) ((pgsz) - 2 - 2 - 8 - 2 - (iCell)*2) -#define SEGMENT_EOF(pgsz, nEntry) SEGMENT_CELLPTR_OFFSET(pgsz, nEntry) +#define SEGMENT_EOF(pgsz, nEntry) SEGMENT_CELLPTR_OFFSET(pgsz, nEntry-1) #define SEGMENT_BTREE_FLAG 0x0001 #define PGFTR_SKIP_NEXT_FLAG 0x0002 @@ -104,9 +104,9 @@ #endif typedef struct SegmentPtr SegmentPtr; -typedef struct Blob Blob; +typedef struct LsmBlob LsmBlob; -struct Blob { +struct LsmBlob { lsm_env *pEnv; void *pData; int nData; @@ -129,18 +129,18 @@ struct SegmentPtr { Page *pPg; /* Current page */ u16 flags; /* Copy of page flags field */ int nCell; /* Number of cells on pPg */ - Pgno iPtr; /* Base cascade pointer */ + LsmPgno iPtr; /* Base cascade pointer */ /* Current cell. See segmentPtrLoadCell() */ int iCell; /* Current record within page pPg */ int eType; /* Type of current record */ - Pgno iPgPtr; /* Cascade pointer offset */ + LsmPgno iPgPtr; /* Cascade pointer offset */ void *pKey; int nKey; /* Key associated with current record */ void *pVal; int nVal; /* Current record value (eType==WRITE only) */ /* Blobs used to allocate buffers for pKey and pVal as required */ - Blob blob1; - Blob blob2; + LsmBlob blob1; + LsmBlob blob2; }; /* @@ -171,10 +171,10 @@ struct BtreeCursor { void *pKey; int nKey; int eType; - Pgno iPtr; + LsmPgno iPtr; /* Storage for key, if not local */ - Blob blob; + LsmBlob blob; }; @@ -203,8 +203,8 @@ struct MultiCursor { int flags; /* Mask of CURSOR_XXX flags */ int eType; /* Cache of current key type */ - Blob key; /* Cache of current key (or NULL) */ - Blob val; /* Cache of current value */ + LsmBlob key; /* Cache of current key (or NULL) */ + LsmBlob val; /* Cache of current value */ /* All the component cursors: */ TreeCursor *apTreeCsr[2]; /* Up to two tree cursors */ @@ -221,7 +221,7 @@ struct MultiCursor { void *pSystemVal; /* Pointer to buffer to free */ /* Used by worker cursors only */ - Pgno *pPrevMergePtr; + LsmPgno *pPrevMergePtr; }; /* @@ -295,11 +295,11 @@ struct MergeWorker { Hierarchy hier; /* B-tree hierarchy under construction */ Page *pPage; /* Current output page */ int nWork; /* Number of calls to mergeWorkerNextPage() */ - Pgno *aGobble; /* Gobble point for each input segment */ + LsmPgno *aGobble; /* Gobble point for each input segment */ - Pgno iIndirect; + LsmPgno iIndirect; struct SavedPgno { - Pgno iPgno; + LsmPgno iPgno; int bStore; } aSave[2]; }; @@ -371,7 +371,7 @@ void lsmPutU64(u8 *aOut, u64 nVal){ aOut[7] = (u8)((nVal ) & 0xFF); } -static int sortedBlobGrow(lsm_env *pEnv, Blob *pBlob, int nData){ +static int sortedBlobGrow(lsm_env *pEnv, LsmBlob *pBlob, int nData){ assert( pBlob->pEnv==pEnv || (pBlob->pEnv==0 && pBlob->pData==0) ); if( pBlob->nAllocpData = lsmReallocOrFree(pEnv, pBlob->pData, nData); @@ -382,7 +382,7 @@ static int sortedBlobGrow(lsm_env *pEnv, Blob *pBlob, int nData){ return LSM_OK; } -static int sortedBlobSet(lsm_env *pEnv, Blob *pBlob, void *pData, int nData){ +static int sortedBlobSet(lsm_env *pEnv, LsmBlob *pBlob, void *pData, int nData){ if( sortedBlobGrow(pEnv, pBlob, nData) ) return LSM_NOMEM; memcpy(pBlob->pData, pData, nData); pBlob->nData = nData; @@ -390,15 +390,15 @@ static int sortedBlobSet(lsm_env *pEnv, Blob *pBlob, void *pData, int nData){ } #if 0 -static int sortedBlobCopy(Blob *pDest, Blob *pSrc){ +static int sortedBlobCopy(LsmBlob *pDest, LsmBlob *pSrc){ return sortedBlobSet(pDest, pSrc->pData, pSrc->nData); } #endif -static void sortedBlobFree(Blob *pBlob){ +static void sortedBlobFree(LsmBlob *pBlob){ assert( pBlob->pEnv || pBlob->pData==0 ); if( pBlob->pData ) lsmFree(pBlob->pEnv, pBlob->pData); - memset(pBlob, 0, sizeof(Blob)); + memset(pBlob, 0, sizeof(LsmBlob)); } static int sortedReadData( @@ -407,7 +407,7 @@ static int sortedReadData( int iOff, int nByte, void **ppData, - Blob *pBlob + LsmBlob *pBlob ){ int rc = LSM_OK; int iEnd; @@ -481,8 +481,8 @@ static int pageGetNRec(u8 *aData, int nData){ return (int)lsmGetU16(&aData[SEGMENT_NRECORD_OFFSET(nData)]); } -static Pgno pageGetPtr(u8 *aData, int nData){ - return (Pgno)lsmGetU64(&aData[SEGMENT_POINTER_OFFSET(nData)]); +static LsmPgno pageGetPtr(u8 *aData, int nData){ + return (LsmPgno)lsmGetU64(&aData[SEGMENT_POINTER_OFFSET(nData)]); } static int pageGetFlags(u8 *aData, int nData){ @@ -506,8 +506,8 @@ static int pageObjGetNRec(Page *pPg){ ** Return the decoded (possibly relative) pointer value stored in cell ** iCell from page aData/nData. */ -static Pgno pageGetRecordPtr(u8 *aData, int nData, int iCell){ - Pgno iRet; /* Return value */ +static LsmPgno pageGetRecordPtr(u8 *aData, int nData, int iCell){ + LsmPgno iRet; /* Return value */ u8 *aCell; /* Pointer to cell iCell */ assert( iCell=0 ); @@ -522,7 +522,7 @@ static u8 *pageGetKey( int iCell, /* Index of cell on page to read */ int *piTopic, /* OUT: Topic associated with this key */ int *pnKey, /* OUT: Size of key in bytes */ - Blob *pBlob /* If required, use this for dynamic memory */ + LsmBlob *pBlob /* If required, use this for dynamic memory */ ){ u8 *pKey; int nDummy; @@ -554,7 +554,7 @@ static int pageGetKeyCopy( Page *pPg, /* Page to read from */ int iCell, /* Index of cell on page to read */ int *piTopic, /* OUT: Topic associated with this key */ - Blob *pBlob /* If required, use this for dynamic memory */ + LsmBlob *pBlob /* If required, use this for dynamic memory */ ){ int rc = LSM_OK; int nKey; @@ -569,8 +569,8 @@ static int pageGetKeyCopy( return rc; } -static Pgno pageGetBtreeRef(Page *pPg, int iKey){ - Pgno iRef; +static LsmPgno pageGetBtreeRef(Page *pPg, int iKey){ + LsmPgno iRef; u8 *aData; int nData; u8 *aCell; @@ -592,11 +592,11 @@ static int pageGetBtreeKey( Segment *pSeg, /* Segment page pPg belongs to */ Page *pPg, int iKey, - Pgno *piPtr, + LsmPgno *piPtr, int *piTopic, void **ppKey, int *pnKey, - Blob *pBlob + LsmBlob *pBlob ){ u8 *aData; int nData; @@ -613,7 +613,7 @@ static int pageGetBtreeKey( if( eType==0 ){ int rc; - Pgno iRef; /* Page number of referenced page */ + LsmPgno iRef; /* Page number of referenced page */ Page *pRef; aCell += GETVARINT64(aCell, iRef); rc = lsmFsDbPageGet(lsmPageFS(pPg), pSeg, iRef, &pRef); @@ -638,7 +638,7 @@ static int btreeCursorLoadKey(BtreeCursor *pCsr){ pCsr->nKey = 0; pCsr->eType = 0; }else{ - Pgno dummy; + LsmPgno dummy; int iPg = pCsr->iPg; int iCell = pCsr->aPg[iPg].iCell; while( iCell<0 && (--iPg)>=0 ){ @@ -683,7 +683,7 @@ static int btreeCursorNext(BtreeCursor *pCsr){ assert( pPg->iCell<=nCell ); pPg->iCell++; if( pPg->iCell==nCell ){ - Pgno iLoad; + LsmPgno iLoad; /* Up to parent. */ lsmFsPageRelease(pPg->pPage); @@ -842,7 +842,7 @@ static int btreeCursorRestore( if( p->iPg ){ lsm_env *pEnv = lsmFsEnv(pCsr->pFS); int iCell; /* Current cell number on leaf page */ - Pgno iLeaf; /* Page number of current leaf page */ + LsmPgno iLeaf; /* Page number of current leaf page */ int nDepth; /* Depth of b-tree structure */ Segment *pSeg = pCsr->pSeg; @@ -866,7 +866,7 @@ static int btreeCursorRestore( /* Populate any other aPg[] array entries */ if( rc==LSM_OK && nDepth>1 ){ - Blob blob = {0,0,0}; + LsmBlob blob = {0,0,0}; void *pSeek; int nSeek; int iTopicSeek; @@ -883,7 +883,7 @@ static int btreeCursorRestore( pSeek = 0; nSeek = 0; }else{ - Pgno dummy; + LsmPgno dummy; rc = pageGetBtreeKey(pSeg, pPg, 0, &dummy, &iTopicSeek, &pSeek, &nSeek, &pCsr->blob ); @@ -912,7 +912,7 @@ static int btreeCursorRestore( int iTry = (iMin+iMax)/2; void *pKey; int nKey; /* Key for cell iTry */ int iTopic; /* Topic for key pKeyT/nKeyT */ - Pgno iPtr; /* Pointer for cell iTry */ + LsmPgno iPtr; /* Pointer for cell iTry */ int res; /* (pSeek - pKeyT) */ rc = pageGetBtreeKey( @@ -955,7 +955,7 @@ static int btreeCursorRestore( aData = fsPageData(pBtreePg->pPage, &nData); pCsr->iPtr = btreeCursorPtr(aData, nData, pBtreePg->iCell+1); if( pBtreePg->iCell<0 ){ - Pgno dummy; + LsmPgno dummy; int i; for(i=pCsr->iPg-1; i>=0; i--){ if( pCsr->aPg[i].iCell>0 ) break; @@ -1030,7 +1030,7 @@ static int segmentPtrReadData( int iOff, int nByte, void **ppData, - Blob *pBlob + LsmBlob *pBlob ){ return sortedReadData(pPtr->pSeg, pPtr->pPg, iOff, nByte, ppData, pBlob); } @@ -1123,7 +1123,7 @@ static void sortedSplitkey(lsm_db *pDb, Level *pLevel, int *pRc){ } if( rc==LSM_OK ){ int iTopic; - Blob blob = {0, 0, 0, 0}; + LsmBlob blob = {0, 0, 0, 0}; u8 *aData; int nData; @@ -1131,7 +1131,7 @@ static void sortedSplitkey(lsm_db *pDb, Level *pLevel, int *pRc){ if( pageGetFlags(aData, nData) & SEGMENT_BTREE_FLAG ){ void *pKey; int nKey; - Pgno dummy; + LsmPgno dummy; rc = pageGetBtreeKey(pSeg, pPg, pMerge->splitkey.iCell, &dummy, &iTopic, &pKey, &nKey, &blob ); @@ -1342,7 +1342,7 @@ static int assertKeyLocation( void *pKey, int nKey ){ lsm_env *pEnv = lsmFsEnv(pCsr->pDb->pFS); - Blob blob = {0, 0, 0}; + LsmBlob blob = {0, 0, 0}; int eDir; int iTopic = 0; /* TODO: Fix me */ @@ -1488,7 +1488,7 @@ static int ptrFwdPointer( Page *pPage, int iCell, Segment *pSeg, - Pgno *piPtr, + LsmPgno *piPtr, int *pbFound ){ Page *pPg = pPage; @@ -1573,14 +1573,14 @@ static int sortedRhsFirst(MultiCursor *pCsr, Level *pLvl, SegmentPtr *pPtr){ static int segmentPtrFwdPointer( MultiCursor *pCsr, /* Multi-cursor pPtr belongs to */ SegmentPtr *pPtr, /* Segment-pointer to extract FC ptr from */ - Pgno *piPtr /* OUT: FC pointer value */ + LsmPgno *piPtr /* OUT: FC pointer value */ ){ Level *pLvl = pPtr->pLevel; Level *pNext = pLvl->pNext; Page *pPg = pPtr->pPg; int rc; int bFound; - Pgno iOut = 0; + LsmPgno iOut = 0; if( pPtr->pSeg==&pLvl->lhs || pPtr->pSeg==&pLvl->aRhs[pLvl->nRight-1] ){ if( pNext==0 @@ -1641,7 +1641,7 @@ static int segmentPtrSeek( int rc = LSM_OK; int iMin; int iMax; - Pgno iPtrOut = 0; + LsmPgno iPtrOut = 0; /* If the current page contains an oversized entry, then there are no ** pointers to one or more of the subsequent pages in the sorted run. @@ -1768,18 +1768,18 @@ static int seekInBtree( Segment *pSeg, /* Seek within this segment */ int iTopic, void *pKey, int nKey, /* Key to seek to */ - Pgno *aPg, /* OUT: Page numbers */ + LsmPgno *aPg, /* OUT: Page numbers */ Page **ppPg /* OUT: Leaf (sorted-run) page reference */ ){ int i = 0; int rc; int iPg; Page *pPg = 0; - Blob blob = {0, 0, 0}; + LsmBlob blob = {0, 0, 0}; iPg = (int)pSeg->iRoot; do { - Pgno *piFirst = 0; + LsmPgno *piFirst = 0; if( aPg ){ aPg[i++] = iPg; piFirst = &aPg[i]; @@ -1808,7 +1808,7 @@ static int seekInBtree( int iTry = (iMin+iMax)/2; void *pKeyT; int nKeyT; /* Key for cell iTry */ int iTopicT; /* Topic for key pKeyT/nKeyT */ - Pgno iPtr; /* Pointer associated with cell iTry */ + LsmPgno iPtr; /* Pointer associated with cell iTry */ int res; /* (pKey - pKeyT) */ rc = pageGetBtreeKey( @@ -1899,7 +1899,7 @@ static int seekInLevel( int eSeek, /* Search bias - see above */ int iTopic, /* Key topic to search for */ void *pKey, int nKey, /* Key to search for */ - Pgno *piPgno, /* IN/OUT: fraction cascade pointer (or 0) */ + LsmPgno *piPgno, /* IN/OUT: fraction cascade pointer (or 0) */ int *pbStop /* OUT: See above */ ){ Level *pLvl = aPtr[0].pLevel; /* Level to seek within */ @@ -3055,7 +3055,7 @@ int lsmMCursorSeek( int bStop = 0; /* Set to true to halt search operation */ int rc = LSM_OK; /* Return code */ int iPtr = 0; /* Used to iterate through pCsr->aPtr[] */ - Pgno iPgno = 0; /* FC pointer value */ + LsmPgno iPgno = 0; /* FC pointer value */ assert( pCsr->apTreeCsr[0]==0 || iTopic==0 ); assert( pCsr->apTreeCsr[1]==0 || iTopic==0 ); @@ -3537,7 +3537,7 @@ static int mergeWorkerLoadHierarchy(MergeWorker *pMW){ ** + Type byte (always SORTED_SEPARATOR or SORTED_SYSTEM_SEPARATOR), ** + Absolute pointer value (varint), ** + Number of bytes in key (varint), -** + Blob containing key data. +** + LsmBlob containing key data. ** ** 2. All pointer values are stored as absolute values (not offsets ** relative to the footer pointer value). @@ -3571,8 +3571,8 @@ static int mergeWorkerLoadHierarchy(MergeWorker *pMW){ static int mergeWorkerBtreeWrite( MergeWorker *pMW, u8 eType, - Pgno iPtr, - Pgno iKeyPg, + LsmPgno iPtr, + LsmPgno iKeyPg, void *pKey, int nKey ){ @@ -3682,7 +3682,7 @@ static int mergeWorkerBtreeWrite( static int mergeWorkerBtreeIndirect(MergeWorker *pMW){ int rc = LSM_OK; if( pMW->iIndirect ){ - Pgno iKeyPg = pMW->aSave[1].iPgno; + LsmPgno iKeyPg = pMW->aSave[1].iPgno; rc = mergeWorkerBtreeWrite(pMW, 0, pMW->iIndirect, iKeyPg, 0, 0); pMW->iIndirect = 0; } @@ -3703,7 +3703,7 @@ static int mergeWorkerPushHierarchy( int nKey /* Size of pKey buffer in bytes */ ){ int rc = LSM_OK; /* Return Code */ - Pgno iPtr; /* Pointer value to accompany pKey/nKey */ + LsmPgno iPtr; /* Pointer value to accompany pKey/nKey */ assert( pMW->aSave[0].bStore==0 ); assert( pMW->aSave[1].bStore==0 ); @@ -3734,7 +3734,7 @@ static int mergeWorkerFinishHierarchy( ){ int i; /* Used to loop through apHier[] */ int rc = LSM_OK; /* Return code */ - Pgno iPtr; /* New right-hand-child pointer value */ + LsmPgno iPtr; /* New right-hand-child pointer value */ iPtr = pMW->aSave[0].iPgno; for(i=0; ihier.nHier && rc==LSM_OK; i++){ @@ -3830,7 +3830,7 @@ static int mergeWorkerPersistAndRelease(MergeWorker *pMW){ */ static int mergeWorkerNextPage( MergeWorker *pMW, /* Merge worker object to append page to */ - Pgno iFPtr /* Pointer value for footer of new page */ + LsmPgno iFPtr /* Pointer value for footer of new page */ ){ int rc = LSM_OK; /* Return code */ Page *pNext = 0; /* New page appended to run */ @@ -3999,6 +3999,11 @@ static int mergeWorkerWrite( ** marked read-only, advance to the next page of the output run. */ iOff = pMerge->iOutputOff; if( iOff<0 || pPg==0 || iOff+nHdr > SEGMENT_EOF(nData, nRec+1) ){ + if( iOff>=0 && pPg ){ + /* Zero any free space on the page */ + assert( aData ); + memset(&aData[iOff], 0, SEGMENT_EOF(nData, nRec)-iOff); + } iFPtr = (int)*pMW->pCsr->pPrevMergePtr; iRPtr = iPtr - iFPtr; iOff = 0; @@ -4069,36 +4074,49 @@ static void mergeWorkerShutdown(MergeWorker *pMW, int *pRc){ /* Unless the merge has finished, save the cursor position in the ** Merge.aInput[] array. See function mergeWorkerInit() for the ** code to restore a cursor position based on aInput[]. */ - if( rc==LSM_OK && pCsr && lsmMCursorValid(pCsr) ){ + if( rc==LSM_OK && pCsr ){ Merge *pMerge = pMW->pLevel->pMerge; - int bBtree = (pCsr->pBtCsr!=0); - int iPtr; + if( lsmMCursorValid(pCsr) ){ + int bBtree = (pCsr->pBtCsr!=0); + int iPtr; - /* pMerge->nInput==0 indicates that this is a FlushTree() operation. */ - assert( pMerge->nInput==0 || pMW->pLevel->nRight>0 ); - assert( pMerge->nInput==0 || pMerge->nInput==(pCsr->nPtr+bBtree) ); + /* pMerge->nInput==0 indicates that this is a FlushTree() operation. */ + assert( pMerge->nInput==0 || pMW->pLevel->nRight>0 ); + assert( pMerge->nInput==0 || pMerge->nInput==(pCsr->nPtr+bBtree) ); - for(i=0; i<(pMerge->nInput-bBtree); i++){ - SegmentPtr *pPtr = &pCsr->aPtr[i]; - if( pPtr->pPg ){ - pMerge->aInput[i].iPg = lsmFsPageNumber(pPtr->pPg); - pMerge->aInput[i].iCell = pPtr->iCell; + for(i=0; i<(pMerge->nInput-bBtree); i++){ + SegmentPtr *pPtr = &pCsr->aPtr[i]; + if( pPtr->pPg ){ + pMerge->aInput[i].iPg = lsmFsPageNumber(pPtr->pPg); + pMerge->aInput[i].iCell = pPtr->iCell; + }else{ + pMerge->aInput[i].iPg = 0; + pMerge->aInput[i].iCell = 0; + } + } + if( bBtree && pMerge->nInput ){ + assert( i==pCsr->nPtr ); + btreeCursorPosition(pCsr->pBtCsr, &pMerge->aInput[i]); + } + + /* Store the location of the split-key */ + iPtr = pCsr->aTree[1] - CURSOR_DATA_SEGMENT; + if( iPtrnPtr ){ + pMerge->splitkey = pMerge->aInput[iPtr]; }else{ - pMerge->aInput[i].iPg = 0; - pMerge->aInput[i].iCell = 0; + btreeCursorSplitkey(pCsr->pBtCsr, &pMerge->splitkey); } } - if( bBtree && pMerge->nInput ){ - assert( i==pCsr->nPtr ); - btreeCursorPosition(pCsr->pBtCsr, &pMerge->aInput[i]); - } - /* Store the location of the split-key */ - iPtr = pCsr->aTree[1] - CURSOR_DATA_SEGMENT; - if( iPtrnPtr ){ - pMerge->splitkey = pMerge->aInput[iPtr]; - }else{ - btreeCursorSplitkey(pCsr->pBtCsr, &pMerge->splitkey); + /* Zero any free space left on the final page. This helps with + ** compression if using a compression hook. And prevents valgrind + ** from complaining about uninitialized byte passed to write(). */ + if( pMW->pPage ){ + int nData; + u8 *aData = fsPageData(pMW->pPage, &nData); + int iOff = pMerge->iOutputOff; + int iEof = SEGMENT_EOF(nData, pageGetNRec(aData, nData)); + memset(&aData[iOff], 0, iEof - iOff); } pMerge->iOutputOff = -1; @@ -4200,7 +4218,7 @@ static int mergeWorkerStep(MergeWorker *pMW){ int rc = LSM_OK; /* Return code */ int eType; /* SORTED_SEPARATOR, WRITE or DELETE */ void *pKey; int nKey; /* Key */ - Pgno iPtr; + LsmPgno iPtr; int iVal; pCsr = pMW->pCsr; @@ -4353,7 +4371,7 @@ static int sortedNewToplevel( if( rc!=LSM_OK ){ lsmMCursorClose(pCsr, 0); }else{ - Pgno iLeftPtr = 0; + LsmPgno iLeftPtr = 0; Merge merge; /* Merge object used to create new level */ MergeWorker mergeworker; /* MergeWorker object for the same purpose */ @@ -4530,7 +4548,7 @@ static int mergeWorkerInit( memset(pMW, 0, sizeof(MergeWorker)); pMW->pDb = pDb; pMW->pLevel = pLevel; - pMW->aGobble = lsmMallocZeroRc(pDb->pEnv, sizeof(Pgno) * pLevel->nRight, &rc); + pMW->aGobble = lsmMallocZeroRc(pDb->pEnv, sizeof(LsmPgno)*pLevel->nRight,&rc); /* Create a multi-cursor to read the data to write to the new ** segment. The new segment contains: @@ -4612,7 +4630,7 @@ static int sortedBtreeGobble( int rc = LSM_OK; if( rtTopic(pCsr->eType)==0 ){ Segment *pSeg = pCsr->aPtr[iGobble].pSeg; - Pgno *aPg; + LsmPgno *aPg; int nPg; /* Seek from the root of the b-tree to the segment leaf that may contain @@ -4621,7 +4639,7 @@ static int sortedBtreeGobble( ** gobbled up to (but not including) the first of these page numbers. */ assert( pSeg->iRoot>0 ); - aPg = lsmMallocZeroRc(pDb->pEnv, sizeof(Pgno)*32, &rc); + aPg = lsmMallocZeroRc(pDb->pEnv, sizeof(LsmPgno)*32, &rc); if( rc==LSM_OK ){ rc = seekInBtree(pCsr, pSeg, rtTopic(pCsr->eType), pCsr->key.pData, pCsr->key.nData, aPg, 0 @@ -5234,16 +5252,15 @@ static int doLsmSingleWork( /* If the in-memory part of the free-list is too large, write a new ** top-level containing just the in-memory free-list entries to disk. */ if( rc==LSM_OK && pDb->pWorker->freelist.nEntry > pDb->nMaxFreelist ){ - int nPg = 0; while( rc==LSM_OK && lsmDatabaseFull(pDb) ){ + int nPg = 0; rc = sortedWork(pDb, 16, nMerge, 1, &nPg); nRem -= nPg; } if( rc==LSM_OK ){ rc = sortedNewFreelistOnly(pDb); } - nRem -= nPg; - if( nPg ) bDirty = 1; + bDirty = 1; } if( rc==LSM_OK ){ @@ -5448,9 +5465,9 @@ int lsmFlushTreeToDisk(lsm_db *pDb){ */ static char *segToString(lsm_env *pEnv, Segment *pSeg, int nMin){ int nSize = pSeg->nSize; - Pgno iRoot = pSeg->iRoot; - Pgno iFirst = pSeg->iFirst; - Pgno iLast = pSeg->iLastPg; + LsmPgno iRoot = pSeg->iRoot; + LsmPgno iFirst = pSeg->iFirst; + LsmPgno iLast = pSeg->iLastPg; char *z; char *z1; @@ -5509,7 +5526,7 @@ static int fileToString( } void sortedDumpPage(lsm_db *pDb, Segment *pRun, Page *pPg, int bVals){ - Blob blob = {0, 0, 0}; /* Blob used for keys */ + LsmBlob blob = {0, 0, 0}; /* LsmBlob used for keys */ LsmString s; int i; @@ -5545,7 +5562,7 @@ void sortedDumpPage(lsm_db *pDb, Segment *pRun, Page *pPg, int bVals){ aCell += lsmVarintGet32(aCell, &iPgPtr); if( eType==0 ){ - Pgno iRef; /* Page number of referenced page */ + LsmPgno iRef; /* Page number of referenced page */ aCell += lsmVarintGet64(aCell, &iRef); lsmFsDbPageGet(pDb->pFS, pRun, iRef, &pRef); aKey = pageGetKey(pRun, pRef, 0, &iTopic, &nKey, &blob); @@ -5589,7 +5606,7 @@ static void infoCellDump( int *piPgPtr, u8 **paKey, int *pnKey, u8 **paVal, int *pnVal, - Blob *pBlob + LsmBlob *pBlob ){ u8 *aData; int nData; /* Page data */ u8 *aKey; int nKey = 0; /* Key */ @@ -5607,7 +5624,7 @@ static void infoCellDump( if( eType==0 ){ int dummy; - Pgno iRef; /* Page number of referenced page */ + LsmPgno iRef; /* Page number of referenced page */ aCell += lsmVarintGet64(aCell, &iRef); if( bIndirect ){ lsmFsDbPageGet(pDb->pFS, pSeg, iRef, &pRef); @@ -5653,7 +5670,7 @@ static int infoAppendBlob(LsmString *pStr, int bHex, u8 *z, int n){ static int infoPageDump( lsm_db *pDb, /* Database handle */ - Pgno iPg, /* Page number of page to dump */ + LsmPgno iPg, /* Page number of page to dump */ int flags, char **pzOut /* OUT: lsmMalloc'd string */ ){ @@ -5694,7 +5711,7 @@ static int infoPageDump( } if( rc==LSM_OK ){ - Blob blob = {0, 0, 0, 0}; + LsmBlob blob = {0, 0, 0, 0}; int nKeyWidth = 0; LsmString str; int nRec; @@ -5729,7 +5746,7 @@ static int infoPageDump( u8 *aVal; int nVal = 0; /* Value */ int iPgPtr; int eType; - Pgno iAbsPtr; + LsmPgno iAbsPtr; char zFlags[8]; infoCellDump(pDb, pSeg, bIndirect, pPg, iCell, &eType, &iPgPtr, @@ -5795,7 +5812,7 @@ static int infoPageDump( int lsmInfoPageDump( lsm_db *pDb, /* Database handle */ - Pgno iPg, /* Page number of page to dump */ + LsmPgno iPg, /* Page number of page to dump */ int bHex, /* True to output key/value in hex form */ char **pzOut /* OUT: lsmMalloc'd string */ ){ @@ -5971,8 +5988,8 @@ void lsmSortedExpandBtreePage(Page *pPg, int nOrig){ #ifdef LSM_DEBUG_EXPENSIVE static void assertRunInOrder(lsm_db *pDb, Segment *pSeg){ Page *pPg = 0; - Blob blob1 = {0, 0, 0, 0}; - Blob blob2 = {0, 0, 0, 0}; + LsmBlob blob1 = {0, 0, 0, 0}; + LsmBlob blob2 = {0, 0, 0, 0}; lsmFsDbPageGet(pDb->pFS, pSeg, pSeg->iFirst, &pPg); while( pPg ){ @@ -6034,7 +6051,7 @@ static int assertPointersOk( int rc = LSM_OK; /* Error code */ SegmentPtr ptr1; /* Iterates through pOne */ SegmentPtr ptr2; /* Iterates through pTwo */ - Pgno iPrev; + LsmPgno iPrev; assert( pOne && pTwo ); @@ -6057,7 +6074,7 @@ static int assertPointersOk( } while( rc==LSM_OK && ptr2.pPg ){ - Pgno iThis; + LsmPgno iThis; /* Advance to the next page of segment pTwo that contains at least ** one cell. Break out of the loop if the iterator reaches EOF. */ @@ -6119,7 +6136,7 @@ static int assertBtreeOk( ){ int rc = LSM_OK; /* Return code */ if( pSeg->iRoot ){ - Blob blob = {0, 0, 0}; /* Buffer used to cache overflow keys */ + LsmBlob blob = {0, 0, 0}; /* Buffer used to cache overflow keys */ FileSystem *pFS = pDb->pFS; /* File system to read from */ Page *pPg = 0; /* Main run page */ BtreeCursor *pCsr = 0; /* Btree cursor */ diff --git a/ext/lsm1/tool/mklsm1c.tcl b/ext/lsm1/tool/mklsm1c.tcl new file mode 100644 index 0000000000..d4a317b700 --- /dev/null +++ b/ext/lsm1/tool/mklsm1c.tcl @@ -0,0 +1,88 @@ +#!/bin/sh +# restart with tclsh \ +exec tclsh "$0" "$@" + +set srcdir [file dirname [file dirname [info script]]] +set G(src) [string map [list %dir% $srcdir] { + %dir%/lsm.h + %dir%/lsmInt.h + %dir%/lsm_vtab.c + %dir%/lsm_ckpt.c + %dir%/lsm_file.c + %dir%/lsm_log.c + %dir%/lsm_main.c + %dir%/lsm_mem.c + %dir%/lsm_mutex.c + %dir%/lsm_shared.c + %dir%/lsm_sorted.c + %dir%/lsm_str.c + %dir%/lsm_tree.c + %dir%/lsm_unix.c + %dir%/lsm_varint.c + %dir%/lsm_win32.c +}] + +set G(hdr) { + +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_LSM1) + +#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) +# define NDEBUG 1 +#endif +#if defined(NDEBUG) && defined(SQLITE_DEBUG) +# undef NDEBUG +#endif + +} + +set G(footer) { + +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_LSM1) */ +} + +#------------------------------------------------------------------------- +# Read and return the entire contents of text file $zFile from disk. +# +proc readfile {zFile} { + set fd [open $zFile] + set data [read $fd] + close $fd + return $data +} + +proc lsm1c_init {zOut} { + global G + set G(fd) stdout + set G(fd) [open $zOut w] + + puts -nonewline $G(fd) $G(hdr) +} + +proc lsm1c_printfile {zIn} { + global G + set data [readfile $zIn] + set zTail [file tail $zIn] + puts $G(fd) "#line 1 \"$zTail\"" + + foreach line [split $data "\n"] { + if {[regexp {^# *include.*lsm} $line]} { + set line "/* $line */" + } elseif { [regexp {^(const )?[a-zA-Z][a-zA-Z0-9]* [*]?lsm[^_]} $line] } { + set line "static $line" + } + puts $G(fd) $line + } +} + +proc lsm1c_close {} { + global G + puts -nonewline $G(fd) $G(footer) + if {$G(fd)!="stdout"} { + close $G(fd) + } +} + + +lsm1c_init lsm1.c +foreach f $G(src) { lsm1c_printfile $f } +lsm1c_close diff --git a/ext/misc/README.md b/ext/misc/README.md index 970a09c2e9..69cb230255 100644 --- a/ext/misc/README.md +++ b/ext/misc/README.md @@ -14,11 +14,20 @@ as follows: It is a good example of how to go about implementing a custom [table-valued function](https://www.sqlite.org/vtab.html#tabfunc2). + * **csv.c** — A [virtual table](https://sqlite.org/vtab.html) + for reading + [Comma-Separated-Value (CSV) files](https://en.wikipedia.org/wiki/Comma-separated_values). + * **dbdump.c** — This is not actually a loadable extension, but rather a library that implements an approximate equivalent to the ".dump" command of the [command-line shell](https://www.sqlite.org/cli.html). + * **json1.c** — Various SQL functions and table-valued functions + for processing JSON. This extension is already built into the + [SQLite amalgamation](https://sqlite.org/amalgamation.html). See + for additional information. + * **memvfs.c** — This file implements a custom [VFS](https://www.sqlite.org/vfs.html) that stores an entire database file in a single block of RAM. It serves as a good example of how @@ -38,3 +47,14 @@ as follows: on the source filename with digits removed, so if we used the name "sha3.c" then the entry point would conflict with the prior "sha1.c" extension. + + * **unionvtab.c** — Implementation of the unionvtab and + [swarmvtab](https://sqlite.org/swarmvtab.html) virtual tables. + These virtual tables allow a single + large table to be spread out across multiple database files. In the + case of swarmvtab, the individual database files can be attached on + demand. + + * **zipfile.c** — A [virtual table](https://sqlite.org/vtab.html) + that can read and write a + [ZIP archive](https://en.wikipedia.org/wiki/Zip_%28file_format%29). diff --git a/ext/misc/appendvfs.c b/ext/misc/appendvfs.c new file mode 100644 index 0000000000..b224245f3d --- /dev/null +++ b/ext/misc/appendvfs.c @@ -0,0 +1,565 @@ +/* +** 2017-10-20 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file implements a VFS shim that allows an SQLite database to be +** appended onto the end of some other file, such as an executable. +** +** A special record must appear at the end of the file that identifies the +** file as an appended database and provides an offset to page 1. For +** best performance page 1 should be located at a disk page boundary, though +** that is not required. +** +** When opening a database using this VFS, the connection might treat +** the file as an ordinary SQLite database, or it might treat is as a +** database appended onto some other file. Here are the rules: +** +** (1) When opening a new empty file, that file is treated as an ordinary +** database. +** +** (2) When opening a file that begins with the standard SQLite prefix +** string "SQLite format 3", that file is treated as an ordinary +** database. +** +** (3) When opening a file that ends with the appendvfs trailer string +** "Start-Of-SQLite3-NNNNNNNN" that file is treated as an appended +** database. +** +** (4) If none of the above apply and the SQLITE_OPEN_CREATE flag is +** set, then a new database is appended to the already existing file. +** +** (5) Otherwise, SQLITE_CANTOPEN is returned. +** +** To avoid unnecessary complications with the PENDING_BYTE, the size of +** the file containing the database is limited to 1GB. This VFS will refuse +** to read or write past the 1GB mark. This restriction might be lifted in +** future versions. For now, if you need a large database, then keep the +** database in a separate file. +** +** If the file being opened is not an appended database, then this shim is +** a pass-through into the default underlying VFS. +**/ +#include "sqlite3ext.h" +SQLITE_EXTENSION_INIT1 +#include +#include + +/* The append mark at the end of the database is: +** +** Start-Of-SQLite3-NNNNNNNN +** 123456789 123456789 12345 +** +** The NNNNNNNN represents a 64-bit big-endian unsigned integer which is +** the offset to page 1. +*/ +#define APND_MARK_PREFIX "Start-Of-SQLite3-" +#define APND_MARK_PREFIX_SZ 17 +#define APND_MARK_SIZE 25 + +/* +** Maximum size of the combined prefix + database + append-mark. This +** must be less than 0x40000000 to avoid locking issues on Windows. +*/ +#define APND_MAX_SIZE (65536*15259) + +/* +** Forward declaration of objects used by this utility +*/ +typedef struct sqlite3_vfs ApndVfs; +typedef struct ApndFile ApndFile; + +/* Access to a lower-level VFS that (might) implement dynamic loading, +** access to randomness, etc. +*/ +#define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData)) +#define ORIGFILE(p) ((sqlite3_file*)(((ApndFile*)(p))+1)) + +/* An open file */ +struct ApndFile { + sqlite3_file base; /* IO methods */ + sqlite3_int64 iPgOne; /* File offset to page 1 */ + sqlite3_int64 iMark; /* Start of the append-mark */ +}; + +/* +** Methods for ApndFile +*/ +static int apndClose(sqlite3_file*); +static int apndRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); +static int apndWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst); +static int apndTruncate(sqlite3_file*, sqlite3_int64 size); +static int apndSync(sqlite3_file*, int flags); +static int apndFileSize(sqlite3_file*, sqlite3_int64 *pSize); +static int apndLock(sqlite3_file*, int); +static int apndUnlock(sqlite3_file*, int); +static int apndCheckReservedLock(sqlite3_file*, int *pResOut); +static int apndFileControl(sqlite3_file*, int op, void *pArg); +static int apndSectorSize(sqlite3_file*); +static int apndDeviceCharacteristics(sqlite3_file*); +static int apndShmMap(sqlite3_file*, int iPg, int pgsz, int, void volatile**); +static int apndShmLock(sqlite3_file*, int offset, int n, int flags); +static void apndShmBarrier(sqlite3_file*); +static int apndShmUnmap(sqlite3_file*, int deleteFlag); +static int apndFetch(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp); +static int apndUnfetch(sqlite3_file*, sqlite3_int64 iOfst, void *p); + +/* +** Methods for ApndVfs +*/ +static int apndOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *); +static int apndDelete(sqlite3_vfs*, const char *zName, int syncDir); +static int apndAccess(sqlite3_vfs*, const char *zName, int flags, int *); +static int apndFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut); +static void *apndDlOpen(sqlite3_vfs*, const char *zFilename); +static void apndDlError(sqlite3_vfs*, int nByte, char *zErrMsg); +static void (*apndDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void); +static void apndDlClose(sqlite3_vfs*, void*); +static int apndRandomness(sqlite3_vfs*, int nByte, char *zOut); +static int apndSleep(sqlite3_vfs*, int microseconds); +static int apndCurrentTime(sqlite3_vfs*, double*); +static int apndGetLastError(sqlite3_vfs*, int, char *); +static int apndCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*); +static int apndSetSystemCall(sqlite3_vfs*, const char*,sqlite3_syscall_ptr); +static sqlite3_syscall_ptr apndGetSystemCall(sqlite3_vfs*, const char *z); +static const char *apndNextSystemCall(sqlite3_vfs*, const char *zName); + +static sqlite3_vfs apnd_vfs = { + 3, /* iVersion (set when registered) */ + 0, /* szOsFile (set when registered) */ + 1024, /* mxPathname */ + 0, /* pNext */ + "apndvfs", /* zName */ + 0, /* pAppData (set when registered) */ + apndOpen, /* xOpen */ + apndDelete, /* xDelete */ + apndAccess, /* xAccess */ + apndFullPathname, /* xFullPathname */ + apndDlOpen, /* xDlOpen */ + apndDlError, /* xDlError */ + apndDlSym, /* xDlSym */ + apndDlClose, /* xDlClose */ + apndRandomness, /* xRandomness */ + apndSleep, /* xSleep */ + apndCurrentTime, /* xCurrentTime */ + apndGetLastError, /* xGetLastError */ + apndCurrentTimeInt64, /* xCurrentTimeInt64 */ + apndSetSystemCall, /* xSetSystemCall */ + apndGetSystemCall, /* xGetSystemCall */ + apndNextSystemCall /* xNextSystemCall */ +}; + +static const sqlite3_io_methods apnd_io_methods = { + 3, /* iVersion */ + apndClose, /* xClose */ + apndRead, /* xRead */ + apndWrite, /* xWrite */ + apndTruncate, /* xTruncate */ + apndSync, /* xSync */ + apndFileSize, /* xFileSize */ + apndLock, /* xLock */ + apndUnlock, /* xUnlock */ + apndCheckReservedLock, /* xCheckReservedLock */ + apndFileControl, /* xFileControl */ + apndSectorSize, /* xSectorSize */ + apndDeviceCharacteristics, /* xDeviceCharacteristics */ + apndShmMap, /* xShmMap */ + apndShmLock, /* xShmLock */ + apndShmBarrier, /* xShmBarrier */ + apndShmUnmap, /* xShmUnmap */ + apndFetch, /* xFetch */ + apndUnfetch /* xUnfetch */ +}; + + + +/* +** Close an apnd-file. +*/ +static int apndClose(sqlite3_file *pFile){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xClose(pFile); +} + +/* +** Read data from an apnd-file. +*/ +static int apndRead( + sqlite3_file *pFile, + void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + ApndFile *p = (ApndFile *)pFile; + pFile = ORIGFILE(pFile); + return pFile->pMethods->xRead(pFile, zBuf, iAmt, iOfst+p->iPgOne); +} + +/* +** Add the append-mark onto the end of the file. +*/ +static int apndWriteMark(ApndFile *p, sqlite3_file *pFile){ + int i; + unsigned char a[APND_MARK_SIZE]; + memcpy(a, APND_MARK_PREFIX, APND_MARK_PREFIX_SZ); + for(i=0; i<8; i++){ + a[APND_MARK_PREFIX_SZ+i] = (p->iPgOne >> (56 - i*8)) & 0xff; + } + return pFile->pMethods->xWrite(pFile, a, APND_MARK_SIZE, p->iMark); +} + +/* +** Write data to an apnd-file. +*/ +static int apndWrite( + sqlite3_file *pFile, + const void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + int rc; + ApndFile *p = (ApndFile *)pFile; + pFile = ORIGFILE(pFile); + if( iOfst+iAmt>=APND_MAX_SIZE ) return SQLITE_FULL; + rc = pFile->pMethods->xWrite(pFile, zBuf, iAmt, iOfst+p->iPgOne); + if( rc==SQLITE_OK && iOfst + iAmt + p->iPgOne > p->iMark ){ + sqlite3_int64 sz = 0; + rc = pFile->pMethods->xFileSize(pFile, &sz); + if( rc==SQLITE_OK ){ + p->iMark = sz - APND_MARK_SIZE; + if( iOfst + iAmt + p->iPgOne > p->iMark ){ + p->iMark = p->iPgOne + iOfst + iAmt; + rc = apndWriteMark(p, pFile); + } + } + } + return rc; +} + +/* +** Truncate an apnd-file. +*/ +static int apndTruncate(sqlite3_file *pFile, sqlite_int64 size){ + int rc; + ApndFile *p = (ApndFile *)pFile; + pFile = ORIGFILE(pFile); + rc = pFile->pMethods->xTruncate(pFile, size+p->iPgOne+APND_MARK_SIZE); + if( rc==SQLITE_OK ){ + p->iMark = p->iPgOne+size; + rc = apndWriteMark(p, pFile); + } + return rc; +} + +/* +** Sync an apnd-file. +*/ +static int apndSync(sqlite3_file *pFile, int flags){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xSync(pFile, flags); +} + +/* +** Return the current file-size of an apnd-file. +*/ +static int apndFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ + ApndFile *p = (ApndFile *)pFile; + int rc; + pFile = ORIGFILE(p); + rc = pFile->pMethods->xFileSize(pFile, pSize); + if( rc==SQLITE_OK && p->iPgOne ){ + *pSize -= p->iPgOne + APND_MARK_SIZE; + } + return rc; +} + +/* +** Lock an apnd-file. +*/ +static int apndLock(sqlite3_file *pFile, int eLock){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xLock(pFile, eLock); +} + +/* +** Unlock an apnd-file. +*/ +static int apndUnlock(sqlite3_file *pFile, int eLock){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xUnlock(pFile, eLock); +} + +/* +** Check if another file-handle holds a RESERVED lock on an apnd-file. +*/ +static int apndCheckReservedLock(sqlite3_file *pFile, int *pResOut){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xCheckReservedLock(pFile, pResOut); +} + +/* +** File control method. For custom operations on an apnd-file. +*/ +static int apndFileControl(sqlite3_file *pFile, int op, void *pArg){ + ApndFile *p = (ApndFile *)pFile; + int rc; + pFile = ORIGFILE(pFile); + rc = pFile->pMethods->xFileControl(pFile, op, pArg); + if( rc==SQLITE_OK && op==SQLITE_FCNTL_VFSNAME ){ + *(char**)pArg = sqlite3_mprintf("apnd(%lld)/%z", p->iPgOne, *(char**)pArg); + } + return rc; +} + +/* +** Return the sector-size in bytes for an apnd-file. +*/ +static int apndSectorSize(sqlite3_file *pFile){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xSectorSize(pFile); +} + +/* +** Return the device characteristic flags supported by an apnd-file. +*/ +static int apndDeviceCharacteristics(sqlite3_file *pFile){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xDeviceCharacteristics(pFile); +} + +/* Create a shared memory file mapping */ +static int apndShmMap( + sqlite3_file *pFile, + int iPg, + int pgsz, + int bExtend, + void volatile **pp +){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xShmMap(pFile,iPg,pgsz,bExtend,pp); +} + +/* Perform locking on a shared-memory segment */ +static int apndShmLock(sqlite3_file *pFile, int offset, int n, int flags){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xShmLock(pFile,offset,n,flags); +} + +/* Memory barrier operation on shared memory */ +static void apndShmBarrier(sqlite3_file *pFile){ + pFile = ORIGFILE(pFile); + pFile->pMethods->xShmBarrier(pFile); +} + +/* Unmap a shared memory segment */ +static int apndShmUnmap(sqlite3_file *pFile, int deleteFlag){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xShmUnmap(pFile,deleteFlag); +} + +/* Fetch a page of a memory-mapped file */ +static int apndFetch( + sqlite3_file *pFile, + sqlite3_int64 iOfst, + int iAmt, + void **pp +){ + ApndFile *p = (ApndFile *)pFile; + pFile = ORIGFILE(pFile); + return pFile->pMethods->xFetch(pFile, iOfst+p->iPgOne, iAmt, pp); +} + +/* Release a memory-mapped page */ +static int apndUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){ + ApndFile *p = (ApndFile *)pFile; + pFile = ORIGFILE(pFile); + return pFile->pMethods->xUnfetch(pFile, iOfst+p->iPgOne, pPage); +} + +/* +** Check to see if the file is an ordinary SQLite database file. +*/ +static int apndIsOrdinaryDatabaseFile(sqlite3_int64 sz, sqlite3_file *pFile){ + int rc; + char zHdr[16]; + static const char aSqliteHdr[] = "SQLite format 3"; + if( sz<512 ) return 0; + rc = pFile->pMethods->xRead(pFile, zHdr, sizeof(zHdr), 0); + if( rc ) return 0; + return memcmp(zHdr, aSqliteHdr, sizeof(zHdr))==0; +} + +/* +** Try to read the append-mark off the end of a file. Return the +** start of the appended database if the append-mark is present. If +** there is no append-mark, return -1; +*/ +static sqlite3_int64 apndReadMark(sqlite3_int64 sz, sqlite3_file *pFile){ + int rc, i; + sqlite3_int64 iMark; + unsigned char a[APND_MARK_SIZE]; + + if( sz<=APND_MARK_SIZE ) return -1; + rc = pFile->pMethods->xRead(pFile, a, APND_MARK_SIZE, sz-APND_MARK_SIZE); + if( rc ) return -1; + if( memcmp(a, APND_MARK_PREFIX, APND_MARK_PREFIX_SZ)!=0 ) return -1; + iMark = ((sqlite3_int64)(a[APND_MARK_PREFIX_SZ]&0x7f))<<56; + for(i=1; i<8; i++){ + iMark += (sqlite3_int64)a[APND_MARK_PREFIX_SZ+i]<<(56-8*i); + } + return iMark; +} + +/* +** Open an apnd file handle. +*/ +static int apndOpen( + sqlite3_vfs *pVfs, + const char *zName, + sqlite3_file *pFile, + int flags, + int *pOutFlags +){ + ApndFile *p; + sqlite3_file *pSubFile; + sqlite3_vfs *pSubVfs; + int rc; + sqlite3_int64 sz; + pSubVfs = ORIGVFS(pVfs); + if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){ + return pSubVfs->xOpen(pSubVfs, zName, pFile, flags, pOutFlags); + } + p = (ApndFile*)pFile; + memset(p, 0, sizeof(*p)); + pSubFile = ORIGFILE(pFile); + p->base.pMethods = &apnd_io_methods; + rc = pSubVfs->xOpen(pSubVfs, zName, pSubFile, flags, pOutFlags); + if( rc ) goto apnd_open_done; + rc = pSubFile->pMethods->xFileSize(pSubFile, &sz); + if( rc ){ + pSubFile->pMethods->xClose(pSubFile); + goto apnd_open_done; + } + if( apndIsOrdinaryDatabaseFile(sz, pSubFile) ){ + memmove(pFile, pSubFile, pSubVfs->szOsFile); + return SQLITE_OK; + } + p->iMark = 0; + p->iPgOne = apndReadMark(sz, pFile); + if( p->iPgOne>0 ){ + return SQLITE_OK; + } + if( (flags & SQLITE_OPEN_CREATE)==0 ){ + pSubFile->pMethods->xClose(pSubFile); + rc = SQLITE_CANTOPEN; + } + p->iPgOne = (sz+0xfff) & ~(sqlite3_int64)0xfff; +apnd_open_done: + if( rc ) pFile->pMethods = 0; + return rc; +} + +/* +** All other VFS methods are pass-thrus. +*/ +static int apndDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ + return ORIGVFS(pVfs)->xDelete(ORIGVFS(pVfs), zPath, dirSync); +} +static int apndAccess( + sqlite3_vfs *pVfs, + const char *zPath, + int flags, + int *pResOut +){ + return ORIGVFS(pVfs)->xAccess(ORIGVFS(pVfs), zPath, flags, pResOut); +} +static int apndFullPathname( + sqlite3_vfs *pVfs, + const char *zPath, + int nOut, + char *zOut +){ + return ORIGVFS(pVfs)->xFullPathname(ORIGVFS(pVfs),zPath,nOut,zOut); +} +static void *apndDlOpen(sqlite3_vfs *pVfs, const char *zPath){ + return ORIGVFS(pVfs)->xDlOpen(ORIGVFS(pVfs), zPath); +} +static void apndDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){ + ORIGVFS(pVfs)->xDlError(ORIGVFS(pVfs), nByte, zErrMsg); +} +static void (*apndDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){ + return ORIGVFS(pVfs)->xDlSym(ORIGVFS(pVfs), p, zSym); +} +static void apndDlClose(sqlite3_vfs *pVfs, void *pHandle){ + ORIGVFS(pVfs)->xDlClose(ORIGVFS(pVfs), pHandle); +} +static int apndRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ + return ORIGVFS(pVfs)->xRandomness(ORIGVFS(pVfs), nByte, zBufOut); +} +static int apndSleep(sqlite3_vfs *pVfs, int nMicro){ + return ORIGVFS(pVfs)->xSleep(ORIGVFS(pVfs), nMicro); +} +static int apndCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){ + return ORIGVFS(pVfs)->xCurrentTime(ORIGVFS(pVfs), pTimeOut); +} +static int apndGetLastError(sqlite3_vfs *pVfs, int a, char *b){ + return ORIGVFS(pVfs)->xGetLastError(ORIGVFS(pVfs), a, b); +} +static int apndCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){ + return ORIGVFS(pVfs)->xCurrentTimeInt64(ORIGVFS(pVfs), p); +} +static int apndSetSystemCall( + sqlite3_vfs *pVfs, + const char *zName, + sqlite3_syscall_ptr pCall +){ + return ORIGVFS(pVfs)->xSetSystemCall(ORIGVFS(pVfs),zName,pCall); +} +static sqlite3_syscall_ptr apndGetSystemCall( + sqlite3_vfs *pVfs, + const char *zName +){ + return ORIGVFS(pVfs)->xGetSystemCall(ORIGVFS(pVfs),zName); +} +static const char *apndNextSystemCall(sqlite3_vfs *pVfs, const char *zName){ + return ORIGVFS(pVfs)->xNextSystemCall(ORIGVFS(pVfs), zName); +} + + +#ifdef _WIN32 +__declspec(dllexport) +#endif +/* +** This routine is called when the extension is loaded. +** Register the new VFS. +*/ +int sqlite3_appendvfs_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + sqlite3_vfs *pOrig; + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; + (void)db; + pOrig = sqlite3_vfs_find(0); + apnd_vfs.iVersion = pOrig->iVersion; + apnd_vfs.pAppData = pOrig; + apnd_vfs.szOsFile = pOrig->szOsFile + sizeof(ApndFile); + rc = sqlite3_vfs_register(&apnd_vfs, 0); +#ifdef APPENDVFS_TEST + if( rc==SQLITE_OK ){ + rc = sqlite3_auto_extension((void(*)(void))apndvfsRegister); + } +#endif + if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY; + return rc; +} diff --git a/ext/misc/btreeinfo.c b/ext/misc/btreeinfo.c index d75f06277f..131b210a79 100644 --- a/ext/misc/btreeinfo.c +++ b/ext/misc/btreeinfo.c @@ -339,7 +339,8 @@ static int binfoColumn( sqlite3 *db = sqlite3_context_db_handle(ctx); int rc = binfoCompute(db, pgno, pCsr); if( rc ){ - return rc; + pCursor->pVtab->zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(db)); + return SQLITE_ERROR; } } switch( i ){ diff --git a/ext/misc/compress.c b/ext/misc/compress.c index bf38d4c93c..6e7d8b6148 100644 --- a/ext/misc/compress.c +++ b/ext/misc/compress.c @@ -27,6 +27,21 @@ SQLITE_EXTENSION_INIT1 ** seven bits per integer stored in the lower seven bits of each byte. ** More significant bits occur first. The most significant bit (0x80) ** is a flag to indicate the end of the integer. +** +** This function, SQLAR, and ZIP all use the same "deflate" compression +** algorithm, but each is subtly different: +** +** * ZIP uses raw deflate. +** +** * SQLAR uses the "zlib format" which is raw deflate with a two-byte +** algorithm-identification header and a four-byte checksum at the end. +** +** * This utility uses the "zlib format" like SQLAR, but adds the variable- +** length integer uncompressed size value at the beginning. +** +** This function might be extended in the future to support compression +** formats other than deflate, by providing a different algorithm-id +** mark following the variable-length integer size parameter. */ static void compressFunc( sqlite3_context *context, diff --git a/ext/misc/fileio.c b/ext/misc/fileio.c index 2c00ad971d..7226ff18b3 100644 --- a/ext/misc/fileio.c +++ b/ext/misc/fileio.c @@ -11,11 +11,122 @@ ****************************************************************************** ** ** This SQLite extension implements SQL functions readfile() and -** writefile(). +** writefile(), and eponymous virtual type "fsdir". +** +** WRITEFILE(FILE, DATA [, MODE [, MTIME]]): +** +** If neither of the optional arguments is present, then this UDF +** function writes blob DATA to file FILE. If successful, the number +** of bytes written is returned. If an error occurs, NULL is returned. +** +** If the first option argument - MODE - is present, then it must +** be passed an integer value that corresponds to a POSIX mode +** value (file type + permissions, as returned in the stat.st_mode +** field by the stat() system call). Three types of files may +** be written/created: +** +** regular files: (mode & 0170000)==0100000 +** symbolic links: (mode & 0170000)==0120000 +** directories: (mode & 0170000)==0040000 +** +** For a directory, the DATA is ignored. For a symbolic link, it is +** interpreted as text and used as the target of the link. For a +** regular file, it is interpreted as a blob and written into the +** named file. Regardless of the type of file, its permissions are +** set to (mode & 0777) before returning. +** +** If the optional MTIME argument is present, then it is interpreted +** as an integer - the number of seconds since the unix epoch. The +** modification-time of the target file is set to this value before +** returning. +** +** If three or more arguments are passed to this function and an +** error is encountered, an exception is raised. +** +** READFILE(FILE): +** +** Read and return the contents of file FILE (type blob) from disk. +** +** FSDIR: +** +** Used as follows: +** +** SELECT * FROM fsdir($path [, $dir]); +** +** Parameter $path is an absolute or relative pathname. If the file that it +** refers to does not exist, it is an error. If the path refers to a regular +** file or symbolic link, it returns a single row. Or, if the path refers +** to a directory, it returns one row for the directory, and one row for each +** file within the hierarchy rooted at $path. +** +** Each row has the following columns: +** +** name: Path to file or directory (text value). +** mode: Value of stat.st_mode for directory entry (an integer). +** mtime: Value of stat.st_mtime for directory entry (an integer). +** data: For a regular file, a blob containing the file data. For a +** symlink, a text value containing the text of the link. For a +** directory, NULL. +** +** If a non-NULL value is specified for the optional $dir parameter and +** $path is a relative path, then $path is interpreted relative to $dir. +** And the paths returned in the "name" column of the table are also +** relative to directory $dir. */ #include "sqlite3ext.h" SQLITE_EXTENSION_INIT1 #include +#include +#include + +#include +#include +#include +#if !defined(_WIN32) && !defined(WIN32) +# include +# include +# include +# include +#else +# include "windows.h" +# include +# include +# include "test_windirent.h" +# define dirent DIRENT +# ifndef stat +# define stat _stat +# endif +# define mkdir(path,mode) _mkdir(path) +# define lstat(path,buf) stat(path,buf) +#endif +#include +#include + + +#define FSDIR_SCHEMA "(name,mode,mtime,data,path HIDDEN,dir HIDDEN)" + +/* +** Set the result stored by context ctx to a blob containing the +** contents of file zName. +*/ +static void readFileContents(sqlite3_context *ctx, const char *zName){ + FILE *in; + long nIn; + void *pBuf; + + in = fopen(zName, "rb"); + if( in==0 ) return; + fseek(in, 0, SEEK_END); + nIn = ftell(in); + rewind(in); + pBuf = sqlite3_malloc( nIn ); + if( pBuf && 1==fread(pBuf, nIn, 1, in) ){ + sqlite3_result_blob(ctx, pBuf, nIn, sqlite3_free); + }else{ + sqlite3_free(pBuf); + } + fclose(in); +} /* ** Implementation of the "readfile(X)" SQL function. The entire content @@ -28,58 +139,666 @@ static void readfileFunc( sqlite3_value **argv ){ const char *zName; - FILE *in; - long nIn; - void *pBuf; - (void)(argc); /* Unused parameter */ 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_malloc( nIn ); - if( pBuf && 1==fread(pBuf, nIn, 1, in) ){ - sqlite3_result_blob(context, pBuf, nIn, sqlite3_free); - }else{ - sqlite3_free(pBuf); - } - fclose(in); + readFileContents(context, zName); } /* -** 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. +** Set the error message contained in context ctx to the results of +** vprintf(zFmt, ...). +*/ +static void ctxErrorMsg(sqlite3_context *ctx, const char *zFmt, ...){ + char *zMsg = 0; + va_list ap; + va_start(ap, zFmt); + zMsg = sqlite3_vmprintf(zFmt, ap); + sqlite3_result_error(ctx, zMsg, -1); + sqlite3_free(zMsg); + va_end(ap); +} + +/* +** Argument zFile is the name of a file that will be created and/or written +** by SQL function writefile(). This function ensures that the directory +** zFile will be written to exists, creating it if required. The permissions +** for any path components created by this function are set to (mode&0777). +** +** If an OOM condition is encountered, SQLITE_NOMEM is returned. Otherwise, +** SQLITE_OK is returned if the directory is successfully created, or +** SQLITE_ERROR otherwise. +*/ +static int makeDirectory( + const char *zFile, + mode_t mode +){ + char *zCopy = sqlite3_mprintf("%s", zFile); + int rc = SQLITE_OK; + + if( zCopy==0 ){ + rc = SQLITE_NOMEM; + }else{ + int nCopy = (int)strlen(zCopy); + int i = 1; + + while( rc==SQLITE_OK ){ + struct stat sStat; + int rc2; + + for(; zCopy[i]!='/' && i=0 ){ +#if defined(_WIN32) + /* Windows */ + FILETIME lastAccess; + FILETIME lastWrite; + SYSTEMTIME currentTime; + LONGLONG intervals; + HANDLE hFile; + LPWSTR zUnicodeName; + extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*); + + GetSystemTime(¤tTime); + SystemTimeToFileTime(¤tTime, &lastAccess); + intervals = Int32x32To64(mtime, 10000000) + 116444736000000000; + lastWrite.dwLowDateTime = (DWORD)intervals; + lastWrite.dwHighDateTime = intervals >> 32; + zUnicodeName = sqlite3_win32_utf8_to_unicode(zFile); + hFile = CreateFileW( + zUnicodeName, FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, NULL + ); + sqlite3_free(zUnicodeName); + if( hFile!=INVALID_HANDLE_VALUE ){ + BOOL bResult = SetFileTime(hFile, NULL, &lastAccess, &lastWrite); + CloseHandle(hFile); + return !bResult; + }else{ + return 1; + } +#elif defined(AT_FDCWD) && 0 /* utimensat() is not univerally available */ + /* Recent unix */ + struct timespec times[2]; + times[0].tv_nsec = times[1].tv_nsec = 0; + times[0].tv_sec = time(0); + times[1].tv_sec = mtime; + if( utimensat(AT_FDCWD, zFile, times, AT_SYMLINK_NOFOLLOW) ){ + return 1; + } +#else + /* Legacy unix */ + struct timeval times[2]; + times[0].tv_usec = times[1].tv_usec = 0; + times[0].tv_sec = time(0); + times[1].tv_sec = mtime; + if( utimes(zFile, times) ){ + return 1; + } +#endif + } + + return 0; +} + +/* +** Implementation of the "writefile(W,X[,Y[,Z]]])" SQL function. +** Refer to header comments at the top of this file for details. */ static void writefileFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ - FILE *out; - const char *z; - sqlite3_int64 rc; const char *zFile; + mode_t mode = 0; + int res; + sqlite3_int64 mtime = -1; + + if( argc<2 || argc>4 ){ + sqlite3_result_error(context, + "wrong number of arguments to function writefile()", -1 + ); + return; + } - (void)(argc); /* Unused parameter */ 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); + if( argc>=3 ){ + mode = (mode_t)sqlite3_value_int(argv[2]); + } + if( argc==4 ){ + mtime = sqlite3_value_int64(argv[3]); + } + + res = writeFile(context, zFile, argv[1], mode, mtime); + if( res==1 && errno==ENOENT ){ + if( makeDirectory(zFile, mode)==SQLITE_OK ){ + res = writeFile(context, zFile, argv[1], mode, mtime); + } + } + + if( argc>2 && res!=0 ){ + if( S_ISLNK(mode) ){ + ctxErrorMsg(context, "failed to create symlink: %s", zFile); + }else if( S_ISDIR(mode) ){ + ctxErrorMsg(context, "failed to create directory: %s", zFile); + }else{ + ctxErrorMsg(context, "failed to write file: %s", zFile); + } } - fclose(out); - sqlite3_result_int64(context, rc); } +/* +** SQL function: lsmode(MODE) +** +** Given a numberic st_mode from stat(), convert it into a human-readable +** text string in the style of "ls -l". +*/ +static void lsModeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int i; + int iMode = sqlite3_value_int(argv[0]); + char z[16]; + (void)argc; + if( S_ISLNK(iMode) ){ + z[0] = 'l'; + }else if( S_ISREG(iMode) ){ + z[0] = '-'; + }else if( S_ISDIR(iMode) ){ + z[0] = 'd'; + }else{ + z[0] = '?'; + } + for(i=0; i<3; i++){ + int m = (iMode >> ((2-i)*3)); + char *a = &z[1 + i*3]; + a[0] = (m & 0x4) ? 'r' : '-'; + a[1] = (m & 0x2) ? 'w' : '-'; + a[2] = (m & 0x1) ? 'x' : '-'; + } + z[10] = '\0'; + sqlite3_result_text(context, z, -1, SQLITE_TRANSIENT); +} + +#ifndef SQLITE_OMIT_VIRTUALTABLE + +/* +** Cursor type for recursively iterating through a directory structure. +*/ +typedef struct fsdir_cursor fsdir_cursor; +typedef struct FsdirLevel FsdirLevel; + +struct FsdirLevel { + DIR *pDir; /* From opendir() */ + char *zDir; /* Name of directory (nul-terminated) */ +}; + +struct fsdir_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + + int nLvl; /* Number of entries in aLvl[] array */ + int iLvl; /* Index of current entry */ + FsdirLevel *aLvl; /* Hierarchy of directories being traversed */ + + const char *zBase; + int nBase; + + struct stat sStat; /* Current lstat() results */ + char *zPath; /* Path to current entry */ + sqlite3_int64 iRowid; /* Current rowid */ +}; + +typedef struct fsdir_tab fsdir_tab; +struct fsdir_tab { + sqlite3_vtab base; /* Base class - must be first */ +}; + +/* +** Construct a new fsdir virtual table object. +*/ +static int fsdirConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + fsdir_tab *pNew = 0; + int rc; + (void)pAux; + (void)argc; + (void)argv; + (void)pzErr; + rc = sqlite3_declare_vtab(db, "CREATE TABLE x" FSDIR_SCHEMA); + if( rc==SQLITE_OK ){ + pNew = (fsdir_tab*)sqlite3_malloc( sizeof(*pNew) ); + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + } + *ppVtab = (sqlite3_vtab*)pNew; + return rc; +} + +/* +** This method is the destructor for fsdir vtab objects. +*/ +static int fsdirDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Constructor for a new fsdir_cursor object. +*/ +static int fsdirOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + fsdir_cursor *pCur; + (void)p; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + pCur->iLvl = -1; + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* +** Reset a cursor back to the state it was in when first returned +** by fsdirOpen(). +*/ +static void fsdirResetCursor(fsdir_cursor *pCur){ + int i; + for(i=0; i<=pCur->iLvl; i++){ + FsdirLevel *pLvl = &pCur->aLvl[i]; + if( pLvl->pDir ) closedir(pLvl->pDir); + sqlite3_free(pLvl->zDir); + } + sqlite3_free(pCur->zPath); + sqlite3_free(pCur->aLvl); + pCur->aLvl = 0; + pCur->zPath = 0; + pCur->zBase = 0; + pCur->nBase = 0; + pCur->iLvl = -1; + pCur->iRowid = 1; +} + +/* +** Destructor for an fsdir_cursor. +*/ +static int fsdirClose(sqlite3_vtab_cursor *cur){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + + fsdirResetCursor(pCur); + sqlite3_free(pCur); + return SQLITE_OK; +} + +/* +** Set the error message for the virtual table associated with cursor +** pCur to the results of vprintf(zFmt, ...). +*/ +static void fsdirSetErrmsg(fsdir_cursor *pCur, const char *zFmt, ...){ + va_list ap; + va_start(ap, zFmt); + pCur->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap); + va_end(ap); +} + + +/* +** Advance an fsdir_cursor to its next row of output. +*/ +static int fsdirNext(sqlite3_vtab_cursor *cur){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + mode_t m = pCur->sStat.st_mode; + + pCur->iRowid++; + if( S_ISDIR(m) ){ + /* Descend into this directory */ + int iNew = pCur->iLvl + 1; + FsdirLevel *pLvl; + if( iNew>=pCur->nLvl ){ + int nNew = iNew+1; + int nByte = nNew*sizeof(FsdirLevel); + FsdirLevel *aNew = (FsdirLevel*)sqlite3_realloc(pCur->aLvl, nByte); + if( aNew==0 ) return SQLITE_NOMEM; + memset(&aNew[pCur->nLvl], 0, sizeof(FsdirLevel)*(nNew-pCur->nLvl)); + pCur->aLvl = aNew; + pCur->nLvl = nNew; + } + pCur->iLvl = iNew; + pLvl = &pCur->aLvl[iNew]; + + pLvl->zDir = pCur->zPath; + pCur->zPath = 0; + pLvl->pDir = opendir(pLvl->zDir); + if( pLvl->pDir==0 ){ + fsdirSetErrmsg(pCur, "cannot read directory: %s", pCur->zPath); + return SQLITE_ERROR; + } + } + + while( pCur->iLvl>=0 ){ + FsdirLevel *pLvl = &pCur->aLvl[pCur->iLvl]; + struct dirent *pEntry = readdir(pLvl->pDir); + if( pEntry ){ + if( pEntry->d_name[0]=='.' ){ + if( pEntry->d_name[1]=='.' && pEntry->d_name[2]=='\0' ) continue; + if( pEntry->d_name[1]=='\0' ) continue; + } + sqlite3_free(pCur->zPath); + pCur->zPath = sqlite3_mprintf("%s/%s", pLvl->zDir, pEntry->d_name); + if( pCur->zPath==0 ) return SQLITE_NOMEM; + if( lstat(pCur->zPath, &pCur->sStat) ){ + fsdirSetErrmsg(pCur, "cannot stat file: %s", pCur->zPath); + return SQLITE_ERROR; + } + return SQLITE_OK; + } + closedir(pLvl->pDir); + sqlite3_free(pLvl->zDir); + pLvl->pDir = 0; + pLvl->zDir = 0; + pCur->iLvl--; + } + + /* EOF */ + sqlite3_free(pCur->zPath); + pCur->zPath = 0; + return SQLITE_OK; +} + +/* +** Return values of columns for the row at which the series_cursor +** is currently pointing. +*/ +static int fsdirColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + switch( i ){ + case 0: { /* name */ + sqlite3_result_text(ctx, &pCur->zPath[pCur->nBase], -1, SQLITE_TRANSIENT); + break; + } + + case 1: /* mode */ + sqlite3_result_int64(ctx, pCur->sStat.st_mode); + break; + + case 2: /* mtime */ + sqlite3_result_int64(ctx, pCur->sStat.st_mtime); + break; + + case 3: { /* data */ + mode_t m = pCur->sStat.st_mode; + if( S_ISDIR(m) ){ + sqlite3_result_null(ctx); +#if !defined(_WIN32) && !defined(WIN32) + }else if( S_ISLNK(m) ){ + char aStatic[64]; + char *aBuf = aStatic; + int nBuf = 64; + int n; + + while( 1 ){ + n = readlink(pCur->zPath, aBuf, nBuf); + if( nzPath); + } + } + } + return SQLITE_OK; +} + +/* +** Return the rowid for the current row. In this implementation, the +** first row returned is assigned rowid value 1, and each subsequent +** row a value 1 more than that of the previous. +*/ +static int fsdirRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + fsdir_cursor *pCur = (fsdir_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 fsdirEof(sqlite3_vtab_cursor *cur){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + return (pCur->zPath==0); +} + +/* +** xFilter callback. +*/ +static int fsdirFilter( + sqlite3_vtab_cursor *cur, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + const char *zDir = 0; + fsdir_cursor *pCur = (fsdir_cursor*)cur; + (void)idxStr; + fsdirResetCursor(pCur); + + if( idxNum==0 ){ + fsdirSetErrmsg(pCur, "table function fsdir requires an argument"); + return SQLITE_ERROR; + } + + assert( argc==idxNum && (argc==1 || argc==2) ); + zDir = (const char*)sqlite3_value_text(argv[0]); + if( zDir==0 ){ + fsdirSetErrmsg(pCur, "table function fsdir requires a non-NULL argument"); + return SQLITE_ERROR; + } + if( argc==2 ){ + pCur->zBase = (const char*)sqlite3_value_text(argv[1]); + } + if( pCur->zBase ){ + pCur->nBase = (int)strlen(pCur->zBase)+1; + pCur->zPath = sqlite3_mprintf("%s/%s", pCur->zBase, zDir); + }else{ + pCur->zPath = sqlite3_mprintf("%s", zDir); + } + + if( pCur->zPath==0 ){ + return SQLITE_NOMEM; + } + if( lstat(pCur->zPath, &pCur->sStat) ){ + fsdirSetErrmsg(pCur, "cannot stat file: %s", pCur->zPath); + return SQLITE_ERROR; + } + + 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 fsdirBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; /* Loop over constraints */ + int idx4 = -1; + int idx5 = -1; + const struct sqlite3_index_constraint *pConstraint; + + (void)tab; + pConstraint = pIdxInfo->aConstraint; + for(i=0; inConstraint; i++, pConstraint++){ + if( pConstraint->usable==0 ) continue; + if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + if( pConstraint->iColumn==4 ) idx4 = i; + if( pConstraint->iColumn==5 ) idx5 = i; + } + + if( idx4<0 ){ + pIdxInfo->idxNum = 0; + pIdxInfo->estimatedCost = (double)(((sqlite3_int64)1) << 50); + }else{ + pIdxInfo->aConstraintUsage[idx4].omit = 1; + pIdxInfo->aConstraintUsage[idx4].argvIndex = 1; + if( idx5>=0 ){ + pIdxInfo->aConstraintUsage[idx5].omit = 1; + pIdxInfo->aConstraintUsage[idx5].argvIndex = 2; + pIdxInfo->idxNum = 2; + pIdxInfo->estimatedCost = 10.0; + }else{ + pIdxInfo->idxNum = 1; + pIdxInfo->estimatedCost = 100.0; + } + } + + return SQLITE_OK; +} + +/* +** Register the "fsdir" virtual table. +*/ +static int fsdirRegister(sqlite3 *db){ + static sqlite3_module fsdirModule = { + 0, /* iVersion */ + 0, /* xCreate */ + fsdirConnect, /* xConnect */ + fsdirBestIndex, /* xBestIndex */ + fsdirDisconnect, /* xDisconnect */ + 0, /* xDestroy */ + fsdirOpen, /* xOpen - open a cursor */ + fsdirClose, /* xClose - close a cursor */ + fsdirFilter, /* xFilter - configure scan constraints */ + fsdirNext, /* xNext - advance a cursor */ + fsdirEof, /* xEof - check for end of scan */ + fsdirColumn, /* xColumn - read data */ + fsdirRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0 /* xRollbackTo */ + }; + + int rc = sqlite3_create_module(db, "fsdir", &fsdirModule, 0); + return rc; +} +#else /* SQLITE_OMIT_VIRTUALTABLE */ +# define fsdirRegister(x) SQLITE_OK +#endif #ifdef _WIN32 __declspec(dllexport) @@ -95,8 +814,15 @@ int sqlite3_fileio_init( rc = sqlite3_create_function(db, "readfile", 1, SQLITE_UTF8, 0, readfileFunc, 0, 0); if( rc==SQLITE_OK ){ - rc = sqlite3_create_function(db, "writefile", 2, SQLITE_UTF8, 0, + rc = sqlite3_create_function(db, "writefile", -1, SQLITE_UTF8, 0, writefileFunc, 0, 0); } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "lsmode", 1, SQLITE_UTF8, 0, + lsModeFunc, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = fsdirRegister(db); + } return rc; } diff --git a/ext/misc/memvfs.c b/ext/misc/memvfs.c index 62a8a033d7..27a61c35e4 100644 --- a/ext/misc/memvfs.c +++ b/ext/misc/memvfs.c @@ -10,23 +10,33 @@ ** ****************************************************************************** ** -** 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. +** This is an in-memory VFS implementation. The application supplies +** a chunk of memory to hold the database file. ** -** Because there is no place to store journals and no good way to lock -** the "file", this VFS is read-only. +** Because there is place to store a rollback or wal journal, the database +** must use one of journal_mode=MEMORY or journal_mode=NONE. ** ** USAGE: ** -** sqlite3_open_v2("file:/whatever?ptr=0xf05538&sz=14336", &db, -** SQLITE_OPEN_READONLY | SQLITE_OPEN_URI, +** sqlite3_open_v2("file:/whatever?ptr=0xf05538&sz=14336&max=65536", &db, +** SQLITE_OPEN_READWRITE | 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. +** These are the query parameters: +** +** ptr= The address of the memory buffer that holds the database. +** +** sz= The current size the database file +** +** maxsz= The maximum size of the database. In other words, the +** amount of space allocated for the ptr= buffer. +** +** freeonclose= If true, then sqlite3_free() is called on the ptr= +** value when the connection closes. +** +** The ptr= and sz= query parameters are required. If maxsz= is omitted, +** then it defaults to the sz= value. Parameter values can be in either +** decimal or hexadecimal. The filename in the URI is ignored. */ #include SQLITE_EXTENSION_INIT1 @@ -49,7 +59,9 @@ typedef struct MemFile MemFile; struct MemFile { sqlite3_file base; /* IO methods */ sqlite3_int64 sz; /* Size of the file */ + sqlite3_int64 szMax; /* Space allocated to aData */ unsigned char *aData; /* content of the file */ + int bFreeOnClose; /* Invoke sqlite3_free() on aData at close */ }; /* @@ -144,6 +156,8 @@ static const sqlite3_io_methods mem_io_methods = { ** to free. */ static int memClose(sqlite3_file *pFile){ + MemFile *p = (MemFile *)pFile; + if( p->bFreeOnClose ) sqlite3_free(p->aData); return SQLITE_OK; } @@ -170,21 +184,34 @@ static int memWrite( int iAmt, sqlite_int64 iOfst ){ - return SQLITE_READONLY; + MemFile *p = (MemFile *)pFile; + if( iOfst+iAmt>p->sz ){ + if( iOfst+iAmt>p->szMax ) return SQLITE_FULL; + if( iOfst>p->sz ) memset(p->aData+p->sz, 0, iOfst-p->sz); + p->sz = iOfst+iAmt; + } + memcpy(p->aData+iOfst, z, iAmt); + return SQLITE_OK; } /* ** Truncate an mem-file. */ static int memTruncate(sqlite3_file *pFile, sqlite_int64 size){ - return SQLITE_READONLY; + MemFile *p = (MemFile *)pFile; + if( size>p->sz ){ + if( size>p->szMax ) return SQLITE_FULL; + memset(p->aData+p->sz, 0, size-p->sz); + } + p->sz = size; + return SQLITE_OK; } /* ** Sync an mem-file. */ static int memSync(sqlite3_file *pFile, int flags){ - return SQLITE_READONLY; + return SQLITE_OK; } /* @@ -200,7 +227,7 @@ static int memFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ ** Lock an mem-file. */ static int memLock(sqlite3_file *pFile, int eLock){ - return SQLITE_READONLY; + return SQLITE_OK; } /* @@ -242,7 +269,10 @@ static int memSectorSize(sqlite3_file *pFile){ ** Return the device characteristic flags supported by an mem-file. */ static int memDeviceCharacteristics(sqlite3_file *pFile){ - return SQLITE_IOCAP_IMMUTABLE; + return SQLITE_IOCAP_ATOMIC | + SQLITE_IOCAP_POWERSAFE_OVERWRITE | + SQLITE_IOCAP_SAFE_APPEND | + SQLITE_IOCAP_SEQUENTIAL; } /* Create a shared memory file mapping */ @@ -253,12 +283,12 @@ static int memShmMap( int bExtend, void volatile **pp ){ - return SQLITE_READONLY; + return SQLITE_IOERR_SHMMAP; } /* Perform locking on a shared-memory segment */ static int memShmLock(sqlite3_file *pFile, int offset, int n, int flags){ - return SQLITE_READONLY; + return SQLITE_IOERR_SHMLOCK; } /* Memory barrier operation on shared memory */ @@ -305,6 +335,9 @@ static int memOpen( if( p->aData==0 ) return SQLITE_CANTOPEN; p->sz = sqlite3_uri_int64(zName,"sz",0); if( p->sz<0 ) return SQLITE_CANTOPEN; + p->szMax = sqlite3_uri_int64(zName,"max",p->sz); + if( p->szMaxsz ) return SQLITE_CANTOPEN; + p->bFreeOnClose = sqlite3_uri_boolean(zName,"freeonclose",0); pFile->pMethods = &mem_io_methods; return SQLITE_OK; } @@ -315,7 +348,7 @@ static int memOpen( ** returning. */ static int memDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ - return SQLITE_READONLY; + return SQLITE_IOERR_DELETE; } /* @@ -328,14 +361,7 @@ static int memAccess( 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; - } + *pResOut = 0; return SQLITE_OK; } @@ -416,31 +442,43 @@ static int memCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){ #ifdef MEMVFS_TEST /* -** memload(FILENAME) +** memvfs_from_file(FILENAME, MAXSIZE) ** ** 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. +** a URI that can be handed to ATTACH to attach the memory buffer as +** a database. Example: +** +** ATTACH memvfs_from_file('test.db',1048576) AS inmem; +** +** The optional MAXSIZE argument gives the size of the memory allocation +** used to hold the database. If omitted, it defaults to the size of the +** file on disk. */ #include -static void memvfsMemloadFunc( +static void memvfsFromFileFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ unsigned char *p; sqlite3_int64 sz; + sqlite3_int64 szMax; FILE *in; const char *zFilename = (const char*)sqlite3_value_text(argv[0]); - char zReturn[100]; + char *zUri; if( zFilename==0 ) return; in = fopen(zFilename, "rb"); if( in==0 ) return; fseek(in, 0, SEEK_END); - sz = ftell(in); + szMax = sz = ftell(in); rewind(in); - p = sqlite3_malloc( sz ); + if( argc>=2 ){ + szMax = sqlite3_value_int64(argv[1]); + if( szMaxzName,"memvfs")!=0 ) return; + rc = sqlite3_file_control(db, zSchema, SQLITE_FCNTL_FILE_POINTER, &p); + if( rc ) return; + fwrite(p->aData, 1, (size_t)p->sz, out); + fclose(out); +} +#endif /* MEMVFS_TEST */ + +#ifdef MEMVFS_TEST /* Called for each new database connection */ static int memvfsRegister( sqlite3 *db, - const char **pzErrMsg, + char **pzErrMsg, const struct sqlite3_api_routines *pThunk ){ - return sqlite3_create_function(db, "memload", 1, SQLITE_UTF8, 0, - memvfsMemloadFunc, 0, 0); + sqlite3_create_function(db, "memvfs_from_file", 1, SQLITE_UTF8, 0, + memvfsFromFileFunc, 0, 0); + sqlite3_create_function(db, "memvfs_from_file", 2, SQLITE_UTF8, 0, + memvfsFromFileFunc, 0, 0); + sqlite3_create_function(db, "memvfs_to_file", 2, SQLITE_UTF8, 0, + memvfsToFileFunc, 0, 0); + return SQLITE_OK; } #endif /* MEMVFS_TEST */ @@ -485,6 +565,9 @@ int sqlite3_memvfs_init( if( rc==SQLITE_OK ){ rc = sqlite3_auto_extension((void(*)(void))memvfsRegister); } + if( rc==SQLITE_OK ){ + rc = memvfsRegister(db, pzErrMsg, pApi); + } #endif if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY; return rc; diff --git a/ext/misc/normalize.c b/ext/misc/normalize.c new file mode 100644 index 0000000000..fd656f1303 --- /dev/null +++ b/ext/misc/normalize.c @@ -0,0 +1,707 @@ +/* +** 2018-01-08 +** +** 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 code to implement the sqlite3_normalize() function. +** +** char *sqlite3_normalize(const char *zSql); +** +** This function takes an SQL string as input and returns a "normalized" +** version of that string in memory obtained from sqlite3_malloc64(). The +** caller is responsible for ensuring that the returned memory is freed. +** +** If a memory allocation error occurs, this routine returns NULL. +** +** The normalization consists of the following transformations: +** +** (1) Convert every literal (string, blob literal, numeric constant, +** or "NULL" constant) into a ? +** +** (2) Remove all superfluous whitespace, including comments. Change +** all required whitespace to a single space character. +** +** (3) Lowercase all ASCII characters. +** +** (4) If an IN or NOT IN operator is followed by a list of 1 or more +** values, convert that list into "(?,?,?)". +** +** The purpose of normalization is two-fold: +** +** (1) Sanitize queries by removing potentially private or sensitive +** information contained in literals. +** +** (2) Identify structurally identical queries by comparing their +** normalized forms. +** +** Command-Line Utility +** -------------------- +** +** This file also contains code for a command-line utility that converts +** SQL queries in text files into their normalized forms. To build the +** command-line program, compile this file with -DSQLITE_NORMALIZE_CLI +** and link it against the SQLite library. +*/ +#include +#include + +/* +** Implementation note: +** +** Much of the tokenizer logic is copied out of the tokenize.c source file +** of SQLite. That logic could be simplified for this particular application, +** but that would impose a risk of introducing subtle errors. It is best to +** keep the code as close to the original as possible. +** +** The tokenize code is in sync with the SQLite core as of 2018-01-08. +** Any future changes to the core tokenizer might require corresponding +** adjustments to the tokenizer logic in this module. +*/ + + +/* Character classes for tokenizing +** +** In the sqlite3GetToken() function, a switch() on aiClass[c] is implemented +** using a lookup table, whereas a switch() directly on c uses a binary search. +** The lookup table is much faster. To maximize speed, and to ensure that +** a lookup table is used, all of the classes need to be small integers and +** all of them need to be used within the switch. +*/ +#define CC_X 0 /* The letter 'x', or start of BLOB literal */ +#define CC_KYWD 1 /* Alphabetics or '_'. Usable in a keyword */ +#define CC_ID 2 /* unicode characters usable in IDs */ +#define CC_DIGIT 3 /* Digits */ +#define CC_DOLLAR 4 /* '$' */ +#define CC_VARALPHA 5 /* '@', '#', ':'. Alphabetic SQL variables */ +#define CC_VARNUM 6 /* '?'. Numeric SQL variables */ +#define CC_SPACE 7 /* Space characters */ +#define CC_QUOTE 8 /* '"', '\'', or '`'. String literals, quoted ids */ +#define CC_QUOTE2 9 /* '['. [...] style quoted ids */ +#define CC_PIPE 10 /* '|'. Bitwise OR or concatenate */ +#define CC_MINUS 11 /* '-'. Minus or SQL-style comment */ +#define CC_LT 12 /* '<'. Part of < or <= or <> */ +#define CC_GT 13 /* '>'. Part of > or >= */ +#define CC_EQ 14 /* '='. Part of = or == */ +#define CC_BANG 15 /* '!'. Part of != */ +#define CC_SLASH 16 /* '/'. / or c-style comment */ +#define CC_LP 17 /* '(' */ +#define CC_RP 18 /* ')' */ +#define CC_SEMI 19 /* ';' */ +#define CC_PLUS 20 /* '+' */ +#define CC_STAR 21 /* '*' */ +#define CC_PERCENT 22 /* '%' */ +#define CC_COMMA 23 /* ',' */ +#define CC_AND 24 /* '&' */ +#define CC_TILDA 25 /* '~' */ +#define CC_DOT 26 /* '.' */ +#define CC_ILLEGAL 27 /* Illegal character */ + +static const unsigned char aiClass[] = { +/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */ +/* 0x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 7, 7, 27, 7, 7, 27, 27, +/* 1x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* 2x */ 7, 15, 8, 5, 4, 22, 24, 8, 17, 18, 21, 20, 23, 11, 26, 16, +/* 3x */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 19, 12, 14, 13, 6, +/* 4x */ 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +/* 5x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 9, 27, 27, 27, 1, +/* 6x */ 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +/* 7x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 27, 10, 27, 25, 27, +/* 8x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, +/* 9x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, +/* Ax */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, +/* Bx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, +/* Cx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, +/* Dx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, +/* Ex */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, +/* Fx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 +}; + +/* An array to map all upper-case characters into their corresponding +** lower-case character. +** +** SQLite only considers US-ASCII (or EBCDIC) characters. We do not +** handle case conversions for the UTF character set since the tables +** involved are nearly as big or bigger than SQLite itself. +*/ +static const unsigned char sqlite3UpperToLower[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99,100,101,102,103, + 104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121, + 122, 91, 92, 93, 94, 95, 96, 97, 98, 99,100,101,102,103,104,105,106,107, + 108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125, + 126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161, + 162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179, + 180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197, + 198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215, + 216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233, + 234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251, + 252,253,254,255 +}; + +/* +** The following 256 byte lookup table is used to support SQLites built-in +** equivalents to the following standard library functions: +** +** isspace() 0x01 +** isalpha() 0x02 +** isdigit() 0x04 +** isalnum() 0x06 +** isxdigit() 0x08 +** toupper() 0x20 +** SQLite identifier character 0x40 +** Quote character 0x80 +** +** Bit 0x20 is set if the mapped character requires translation to upper +** case. i.e. if the character is a lower-case ASCII character. +** If x is a lower-case ASCII character, then its upper-case equivalent +** is (x - 0x20). Therefore toupper() can be implemented as: +** +** (x & ~(map[x]&0x20)) +** +** 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 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. +*/ +static const unsigned char sqlite3CtypeMap[256] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 00..07 ........ */ + 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, /* 08..0f ........ */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 10..17 ........ */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 18..1f ........ */ + 0x01, 0x00, 0x80, 0x00, 0x40, 0x00, 0x00, 0x80, /* 20..27 !"#$%&' */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 28..2f ()*+,-./ */ + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, /* 30..37 01234567 */ + 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 38..3f 89:;<=>? */ + + 0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x02, /* 40..47 @ABCDEFG */ + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, /* 48..4f HIJKLMNO */ + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, /* 50..57 PQRSTUVW */ + 0x02, 0x02, 0x02, 0x80, 0x00, 0x00, 0x00, 0x40, /* 58..5f XYZ[\]^_ */ + 0x80, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x22, /* 60..67 `abcdefg */ + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, /* 68..6f hijklmno */ + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, /* 70..77 pqrstuvw */ + 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, /* 78..7f xyz{|}~. */ + + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 80..87 ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 88..8f ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 90..97 ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 98..9f ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* a0..a7 ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* a8..af ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* b0..b7 ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* b8..bf ........ */ + + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* c0..c7 ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* c8..cf ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* d0..d7 ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* d8..df ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* e0..e7 ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* e8..ef ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* f0..f7 ........ */ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40 /* f8..ff ........ */ +}; +#define sqlite3Toupper(x) ((x)&~(sqlite3CtypeMap[(unsigned char)(x)]&0x20)) +#define sqlite3Isspace(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x01) +#define sqlite3Isalnum(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x06) +#define sqlite3Isalpha(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x02) +#define sqlite3Isdigit(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x04) +#define sqlite3Isxdigit(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x08) +#define sqlite3Tolower(x) (sqlite3UpperToLower[(unsigned char)(x)]) +#define sqlite3Isquote(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x80) + + +/* +** If X is a character that can be used in an identifier then +** IdChar(X) will be true. Otherwise it is false. +** +** For ASCII, any character with the high-order bit set is +** allowed in an identifier. For 7-bit characters, +** sqlite3IsIdChar[X] must be 1. +** +** For EBCDIC, the rules are more complex but have the same +** end result. +** +** Ticket #1066. the SQL standard does not allow '$' in the +** middle of identifiers. But many SQL implementations do. +** SQLite will allow '$' in identifiers for compatibility. +** But the feature is undocumented. +*/ +#define IdChar(C) ((sqlite3CtypeMap[(unsigned char)C]&0x46)!=0) + +/* +** Ignore testcase() macros +*/ +#define testcase(X) + +/* +** Token values +*/ +#define TK_SPACE 0 +#define TK_NAME 1 +#define TK_LITERAL 2 +#define TK_PUNCT 3 +#define TK_ERROR 4 + +#define TK_MINUS TK_PUNCT +#define TK_LP TK_PUNCT +#define TK_RP TK_PUNCT +#define TK_SEMI TK_PUNCT +#define TK_PLUS TK_PUNCT +#define TK_STAR TK_PUNCT +#define TK_SLASH TK_PUNCT +#define TK_REM TK_PUNCT +#define TK_EQ TK_PUNCT +#define TK_LE TK_PUNCT +#define TK_NE TK_PUNCT +#define TK_LSHIFT TK_PUNCT +#define TK_LT TK_PUNCT +#define TK_GE TK_PUNCT +#define TK_RSHIFT TK_PUNCT +#define TK_GT TK_PUNCT +#define TK_GE TK_PUNCT +#define TK_BITOR TK_PUNCT +#define TK_CONCAT TK_PUNCT +#define TK_COMMA TK_PUNCT +#define TK_BITAND TK_PUNCT +#define TK_BITNOT TK_PUNCT +#define TK_STRING TK_LITERAL +#define TK_ID TK_NAME +#define TK_ILLEGAL TK_ERROR +#define TK_DOT TK_PUNCT +#define TK_INTEGER TK_LITERAL +#define TK_FLOAT TK_LITERAL +#define TK_VARIABLE TK_LITERAL +#define TK_BLOB TK_LITERAL + +/* +** Return the length (in bytes) of the token that begins at z[0]. +** Store the token type in *tokenType before returning. +*/ +static int sqlite3GetToken(const unsigned char *z, int *tokenType){ + int i, c; + switch( aiClass[*z] ){ /* Switch on the character-class of the first byte + ** of the token. See the comment on the CC_ defines + ** above. */ + case CC_SPACE: { + for(i=1; sqlite3Isspace(z[i]); i++){} + *tokenType = TK_SPACE; + return i; + } + case CC_MINUS: { + if( z[1]=='-' ){ + for(i=2; (c=z[i])!=0 && c!='\n'; i++){} + *tokenType = TK_SPACE; + return i; + } + *tokenType = TK_MINUS; + return 1; + } + case CC_LP: { + *tokenType = TK_LP; + return 1; + } + case CC_RP: { + *tokenType = TK_RP; + return 1; + } + case CC_SEMI: { + *tokenType = TK_SEMI; + return 1; + } + case CC_PLUS: { + *tokenType = TK_PLUS; + return 1; + } + case CC_STAR: { + *tokenType = TK_STAR; + return 1; + } + case CC_SLASH: { + if( z[1]!='*' || z[2]==0 ){ + *tokenType = TK_SLASH; + return 1; + } + for(i=3, c=z[2]; (c!='*' || z[i]!='/') && (c=z[i])!=0; i++){} + if( c ) i++; + *tokenType = TK_SPACE; + return i; + } + case CC_PERCENT: { + *tokenType = TK_REM; + return 1; + } + case CC_EQ: { + *tokenType = TK_EQ; + return 1 + (z[1]=='='); + } + case CC_LT: { + if( (c=z[1])=='=' ){ + *tokenType = TK_LE; + return 2; + }else if( c=='>' ){ + *tokenType = TK_NE; + return 2; + }else if( c=='<' ){ + *tokenType = TK_LSHIFT; + return 2; + }else{ + *tokenType = TK_LT; + return 1; + } + } + case CC_GT: { + if( (c=z[1])=='=' ){ + *tokenType = TK_GE; + return 2; + }else if( c=='>' ){ + *tokenType = TK_RSHIFT; + return 2; + }else{ + *tokenType = TK_GT; + return 1; + } + } + case CC_BANG: { + if( z[1]!='=' ){ + *tokenType = TK_ILLEGAL; + return 1; + }else{ + *tokenType = TK_NE; + return 2; + } + } + case CC_PIPE: { + if( z[1]!='|' ){ + *tokenType = TK_BITOR; + return 1; + }else{ + *tokenType = TK_CONCAT; + return 2; + } + } + case CC_COMMA: { + *tokenType = TK_COMMA; + return 1; + } + case CC_AND: { + *tokenType = TK_BITAND; + return 1; + } + case CC_TILDA: { + *tokenType = TK_BITNOT; + return 1; + } + case CC_QUOTE: { + int delim = z[0]; + testcase( delim=='`' ); + testcase( delim=='\'' ); + testcase( delim=='"' ); + for(i=1; (c=z[i])!=0; i++){ + if( c==delim ){ + if( z[i+1]==delim ){ + i++; + }else{ + break; + } + } + } + if( c=='\'' ){ + *tokenType = TK_STRING; + return i+1; + }else if( c!=0 ){ + *tokenType = TK_ID; + return i+1; + }else{ + *tokenType = TK_ILLEGAL; + return i; + } + } + case CC_DOT: { + if( !sqlite3Isdigit(z[1]) ){ + *tokenType = TK_DOT; + return 1; + } + /* If the next character is a digit, this is a floating point + ** number that begins with ".". Fall thru into the next case */ + } + case CC_DIGIT: { + *tokenType = TK_INTEGER; + if( z[0]=='0' && (z[1]=='x' || z[1]=='X') && sqlite3Isxdigit(z[2]) ){ + for(i=3; sqlite3Isxdigit(z[i]); i++){} + return i; + } + for(i=0; sqlite3Isdigit(z[i]); i++){} + if( z[i]=='.' ){ + i++; + while( sqlite3Isdigit(z[i]) ){ i++; } + *tokenType = TK_FLOAT; + } + if( (z[i]=='e' || z[i]=='E') && + ( sqlite3Isdigit(z[i+1]) + || ((z[i+1]=='+' || z[i+1]=='-') && sqlite3Isdigit(z[i+2])) + ) + ){ + i += 2; + while( sqlite3Isdigit(z[i]) ){ i++; } + *tokenType = TK_FLOAT; + } + while( IdChar(z[i]) ){ + *tokenType = TK_ILLEGAL; + i++; + } + return i; + } + case CC_QUOTE2: { + for(i=1, c=z[0]; c!=']' && (c=z[i])!=0; i++){} + *tokenType = c==']' ? TK_ID : TK_ILLEGAL; + return i; + } + case CC_VARNUM: { + *tokenType = TK_VARIABLE; + for(i=1; sqlite3Isdigit(z[i]); i++){} + return i; + } + case CC_DOLLAR: + case CC_VARALPHA: { + int n = 0; + testcase( z[0]=='$' ); testcase( z[0]=='@' ); + testcase( z[0]==':' ); testcase( z[0]=='#' ); + *tokenType = TK_VARIABLE; + for(i=1; (c=z[i])!=0; i++){ + if( IdChar(c) ){ + n++; + }else if( c=='(' && n>0 ){ + do{ + i++; + }while( (c=z[i])!=0 && !sqlite3Isspace(c) && c!=')' ); + if( c==')' ){ + i++; + }else{ + *tokenType = TK_ILLEGAL; + } + break; + }else if( c==':' && z[i+1]==':' ){ + i++; + }else{ + break; + } + } + if( n==0 ) *tokenType = TK_ILLEGAL; + return i; + } + case CC_KYWD: { + for(i=1; aiClass[z[i]]<=CC_KYWD; i++){} + if( IdChar(z[i]) ){ + /* This token started out using characters that can appear in keywords, + ** but z[i] is a character not allowed within keywords, so this must + ** be an identifier instead */ + i++; + break; + } + *tokenType = TK_ID; + return i; + } + case CC_X: { + testcase( z[0]=='x' ); testcase( z[0]=='X' ); + if( z[1]=='\'' ){ + *tokenType = TK_BLOB; + for(i=2; sqlite3Isxdigit(z[i]); i++){} + if( z[i]!='\'' || i%2 ){ + *tokenType = TK_ILLEGAL; + while( z[i] && z[i]!='\'' ){ i++; } + } + if( z[i] ) i++; + return i; + } + /* If it is not a BLOB literal, then it must be an ID, since no + ** SQL keywords start with the letter 'x'. Fall through */ + } + case CC_ID: { + i = 1; + break; + } + default: { + *tokenType = TK_ILLEGAL; + return 1; + } + } + while( IdChar(z[i]) ){ i++; } + *tokenType = TK_ID; + return i; +} + +char *sqlite3_normalize(const char *zSql){ + char *z; /* The output string */ + sqlite3_int64 nZ; /* Size of the output string in bytes */ + sqlite3_int64 nSql; /* Size of the input string in bytes */ + int i; /* Next character to read from zSql[] */ + int j; /* Next slot to fill in on z[] */ + int tokenType; /* Type of the next token */ + int n; /* Size of the next token */ + int k; /* Loop counter */ + + nSql = strlen(zSql); + nZ = nSql; + z = sqlite3_malloc64( nZ+2 ); + if( z==0 ) return 0; + for(i=j=0; zSql[i]; i += n){ + n = sqlite3GetToken((unsigned char*)zSql+i, &tokenType); + switch( tokenType ){ + case TK_SPACE: { + break; + } + case TK_ERROR: { + sqlite3_free(z); + return 0; + } + case TK_LITERAL: { + z[j++] = '?'; + break; + } + case TK_PUNCT: + case TK_NAME: { + if( n==4 && sqlite3_strnicmp(zSql+i,"NULL",4)==0 ){ + if( (j>=3 && strncmp(z+j-2,"is",2)==0 && !IdChar(z[j-3])) + || (j>=4 && strncmp(z+j-3,"not",3)==0 && !IdChar(z[j-4])) + ){ + /* NULL is a keyword in this case, not a literal value */ + }else{ + /* Here the NULL is a literal value */ + z[j++] = '?'; + break; + } + } + if( j>0 && IdChar(z[j-1]) && IdChar(zSql[i]) ) z[j++] = ' '; + for(k=0; k0 && z[j-1]==' ' ){ j--; } + if( i>0 && z[j-1]!=';' ){ z[j++] = ';'; } + z[j] = 0; + + /* Make a second pass converting "in(...)" where the "..." is not a + ** SELECT statement into "in(?,?,?)" */ + for(i=0; i5 ){ + memmove(z+n+5, z+n+k, j-(n+k)); + } + j = j-k+5; + z[j] = 0; + memcpy(z+n, "?,?,?", 5); + } + return z; +} + +/* +** For testing purposes, or to build a stand-alone SQL normalizer program, +** compile this one source file with the -DSQLITE_NORMALIZE_CLI and link +** it against any SQLite library. The resulting command-line program will +** run sqlite3_normalize() over the text of all files named on the command- +** line and show the result on standard output. +*/ +#ifdef SQLITE_NORMALIZE_CLI +#include +#include + +/* +** Break zIn up into separate SQL statements and run sqlite3_normalize() +** on each one. Print the result of each run. +*/ +static void normalizeFile(char *zIn){ + int i; + if( zIn==0 ) return; + for(i=0; zIn[i]; i++){ + char cSaved; + if( zIn[i]!=';' ) continue; + cSaved = zIn[i+1]; + zIn[i+1] = 0; + if( sqlite3_complete(zIn) ){ + char *zOut = sqlite3_normalize(zIn); + if( zOut ){ + printf("%s\n", zOut); + sqlite3_free(zOut); + }else{ + fprintf(stderr, "ERROR: %s\n", zIn); + } + zIn[i+1] = cSaved; + zIn += i+1; + i = -1; + }else{ + zIn[i+1] = cSaved; + } + } +} + +/* +** The main routine for "sql_normalize". Read files named on the +** command-line and run the text of each through sqlite3_normalize(). +*/ +int main(int argc, char **argv){ + int i; + FILE *in; + char *zBuf = 0; + sqlite3_int64 sz, got; + + for(i=1; iu.s[0]) -# define A01 (p->u.s[1]) -# define A02 (p->u.s[2]) -# define A03 (p->u.s[3]) -# define A04 (p->u.s[4]) -# define A10 (p->u.s[5]) -# define A11 (p->u.s[6]) -# define A12 (p->u.s[7]) -# define A13 (p->u.s[8]) -# define A14 (p->u.s[9]) -# define A20 (p->u.s[10]) -# define A21 (p->u.s[11]) -# define A22 (p->u.s[12]) -# define A23 (p->u.s[13]) -# define A24 (p->u.s[14]) -# define A30 (p->u.s[15]) -# define A31 (p->u.s[16]) -# define A32 (p->u.s[17]) -# define A33 (p->u.s[18]) -# define A34 (p->u.s[19]) -# define A40 (p->u.s[20]) -# define A41 (p->u.s[21]) -# define A42 (p->u.s[22]) -# define A43 (p->u.s[23]) -# define A44 (p->u.s[24]) +# define a00 (p->u.s[0]) +# define a01 (p->u.s[1]) +# define a02 (p->u.s[2]) +# define a03 (p->u.s[3]) +# define a04 (p->u.s[4]) +# define a10 (p->u.s[5]) +# define a11 (p->u.s[6]) +# define a12 (p->u.s[7]) +# define a13 (p->u.s[8]) +# define a14 (p->u.s[9]) +# define a20 (p->u.s[10]) +# define a21 (p->u.s[11]) +# define a22 (p->u.s[12]) +# define a23 (p->u.s[13]) +# define a24 (p->u.s[14]) +# define a30 (p->u.s[15]) +# define a31 (p->u.s[16]) +# define a32 (p->u.s[17]) +# define a33 (p->u.s[18]) +# define a34 (p->u.s[19]) +# define a40 (p->u.s[20]) +# define a41 (p->u.s[21]) +# define a42 (p->u.s[22]) +# define a43 (p->u.s[23]) +# define a44 (p->u.s[24]) # define ROL64(a,x) ((a<>(64-x))) for(i=0; i<24; i+=4){ - C0 = A00^A10^A20^A30^A40; - C1 = A01^A11^A21^A31^A41; - C2 = A02^A12^A22^A32^A42; - C3 = A03^A13^A23^A33^A43; - C4 = A04^A14^A24^A34^A44; - D0 = C4^ROL64(C1, 1); - D1 = C0^ROL64(C2, 1); - D2 = C1^ROL64(C3, 1); - D3 = C2^ROL64(C4, 1); - D4 = C3^ROL64(C0, 1); + c0 = a00^a10^a20^a30^a40; + c1 = a01^a11^a21^a31^a41; + c2 = a02^a12^a22^a32^a42; + c3 = a03^a13^a23^a33^a43; + c4 = a04^a14^a24^a34^a44; + d0 = c4^ROL64(c1, 1); + d1 = c0^ROL64(c2, 1); + d2 = c1^ROL64(c3, 1); + d3 = c2^ROL64(c4, 1); + d4 = c3^ROL64(c0, 1); - B0 = (A00^D0); - B1 = ROL64((A11^D1), 44); - B2 = ROL64((A22^D2), 43); - B3 = ROL64((A33^D3), 21); - B4 = ROL64((A44^D4), 14); - A00 = B0 ^((~B1)& B2 ); - A00 ^= RC[i]; - A11 = B1 ^((~B2)& B3 ); - A22 = B2 ^((~B3)& B4 ); - A33 = B3 ^((~B4)& B0 ); - A44 = B4 ^((~B0)& B1 ); + b0 = (a00^d0); + b1 = ROL64((a11^d1), 44); + b2 = ROL64((a22^d2), 43); + b3 = ROL64((a33^d3), 21); + b4 = ROL64((a44^d4), 14); + a00 = b0 ^((~b1)& b2 ); + a00 ^= RC[i]; + a11 = b1 ^((~b2)& b3 ); + a22 = b2 ^((~b3)& b4 ); + a33 = b3 ^((~b4)& b0 ); + a44 = b4 ^((~b0)& b1 ); - B2 = ROL64((A20^D0), 3); - B3 = ROL64((A31^D1), 45); - B4 = ROL64((A42^D2), 61); - B0 = ROL64((A03^D3), 28); - B1 = ROL64((A14^D4), 20); - A20 = B0 ^((~B1)& B2 ); - A31 = B1 ^((~B2)& B3 ); - A42 = B2 ^((~B3)& B4 ); - A03 = B3 ^((~B4)& B0 ); - A14 = B4 ^((~B0)& B1 ); + b2 = ROL64((a20^d0), 3); + b3 = ROL64((a31^d1), 45); + b4 = ROL64((a42^d2), 61); + b0 = ROL64((a03^d3), 28); + b1 = ROL64((a14^d4), 20); + a20 = b0 ^((~b1)& b2 ); + a31 = b1 ^((~b2)& b3 ); + a42 = b2 ^((~b3)& b4 ); + a03 = b3 ^((~b4)& b0 ); + a14 = b4 ^((~b0)& b1 ); - B4 = ROL64((A40^D0), 18); - B0 = ROL64((A01^D1), 1); - B1 = ROL64((A12^D2), 6); - B2 = ROL64((A23^D3), 25); - B3 = ROL64((A34^D4), 8); - A40 = B0 ^((~B1)& B2 ); - A01 = B1 ^((~B2)& B3 ); - A12 = B2 ^((~B3)& B4 ); - A23 = B3 ^((~B4)& B0 ); - A34 = B4 ^((~B0)& B1 ); + b4 = ROL64((a40^d0), 18); + b0 = ROL64((a01^d1), 1); + b1 = ROL64((a12^d2), 6); + b2 = ROL64((a23^d3), 25); + b3 = ROL64((a34^d4), 8); + a40 = b0 ^((~b1)& b2 ); + a01 = b1 ^((~b2)& b3 ); + a12 = b2 ^((~b3)& b4 ); + a23 = b3 ^((~b4)& b0 ); + a34 = b4 ^((~b0)& b1 ); - B1 = ROL64((A10^D0), 36); - B2 = ROL64((A21^D1), 10); - B3 = ROL64((A32^D2), 15); - B4 = ROL64((A43^D3), 56); - B0 = ROL64((A04^D4), 27); - A10 = B0 ^((~B1)& B2 ); - A21 = B1 ^((~B2)& B3 ); - A32 = B2 ^((~B3)& B4 ); - A43 = B3 ^((~B4)& B0 ); - A04 = B4 ^((~B0)& B1 ); + b1 = ROL64((a10^d0), 36); + b2 = ROL64((a21^d1), 10); + b3 = ROL64((a32^d2), 15); + b4 = ROL64((a43^d3), 56); + b0 = ROL64((a04^d4), 27); + a10 = b0 ^((~b1)& b2 ); + a21 = b1 ^((~b2)& b3 ); + a32 = b2 ^((~b3)& b4 ); + a43 = b3 ^((~b4)& b0 ); + a04 = b4 ^((~b0)& b1 ); - B3 = ROL64((A30^D0), 41); - B4 = ROL64((A41^D1), 2); - B0 = ROL64((A02^D2), 62); - B1 = ROL64((A13^D3), 55); - B2 = ROL64((A24^D4), 39); - A30 = B0 ^((~B1)& B2 ); - A41 = B1 ^((~B2)& B3 ); - A02 = B2 ^((~B3)& B4 ); - A13 = B3 ^((~B4)& B0 ); - A24 = B4 ^((~B0)& B1 ); + b3 = ROL64((a30^d0), 41); + b4 = ROL64((a41^d1), 2); + b0 = ROL64((a02^d2), 62); + b1 = ROL64((a13^d3), 55); + b2 = ROL64((a24^d4), 39); + a30 = b0 ^((~b1)& b2 ); + a41 = b1 ^((~b2)& b3 ); + a02 = b2 ^((~b3)& b4 ); + a13 = b3 ^((~b4)& b0 ); + a24 = b4 ^((~b0)& b1 ); - C0 = A00^A20^A40^A10^A30; - C1 = A11^A31^A01^A21^A41; - C2 = A22^A42^A12^A32^A02; - C3 = A33^A03^A23^A43^A13; - C4 = A44^A14^A34^A04^A24; - D0 = C4^ROL64(C1, 1); - D1 = C0^ROL64(C2, 1); - D2 = C1^ROL64(C3, 1); - D3 = C2^ROL64(C4, 1); - D4 = C3^ROL64(C0, 1); + c0 = a00^a20^a40^a10^a30; + c1 = a11^a31^a01^a21^a41; + c2 = a22^a42^a12^a32^a02; + c3 = a33^a03^a23^a43^a13; + c4 = a44^a14^a34^a04^a24; + d0 = c4^ROL64(c1, 1); + d1 = c0^ROL64(c2, 1); + d2 = c1^ROL64(c3, 1); + d3 = c2^ROL64(c4, 1); + d4 = c3^ROL64(c0, 1); - B0 = (A00^D0); - B1 = ROL64((A31^D1), 44); - B2 = ROL64((A12^D2), 43); - B3 = ROL64((A43^D3), 21); - B4 = ROL64((A24^D4), 14); - A00 = B0 ^((~B1)& B2 ); - A00 ^= RC[i+1]; - A31 = B1 ^((~B2)& B3 ); - A12 = B2 ^((~B3)& B4 ); - A43 = B3 ^((~B4)& B0 ); - A24 = B4 ^((~B0)& B1 ); + b0 = (a00^d0); + b1 = ROL64((a31^d1), 44); + b2 = ROL64((a12^d2), 43); + b3 = ROL64((a43^d3), 21); + b4 = ROL64((a24^d4), 14); + a00 = b0 ^((~b1)& b2 ); + a00 ^= RC[i+1]; + a31 = b1 ^((~b2)& b3 ); + a12 = b2 ^((~b3)& b4 ); + a43 = b3 ^((~b4)& b0 ); + a24 = b4 ^((~b0)& b1 ); - B2 = ROL64((A40^D0), 3); - B3 = ROL64((A21^D1), 45); - B4 = ROL64((A02^D2), 61); - B0 = ROL64((A33^D3), 28); - B1 = ROL64((A14^D4), 20); - A40 = B0 ^((~B1)& B2 ); - A21 = B1 ^((~B2)& B3 ); - A02 = B2 ^((~B3)& B4 ); - A33 = B3 ^((~B4)& B0 ); - A14 = B4 ^((~B0)& B1 ); + b2 = ROL64((a40^d0), 3); + b3 = ROL64((a21^d1), 45); + b4 = ROL64((a02^d2), 61); + b0 = ROL64((a33^d3), 28); + b1 = ROL64((a14^d4), 20); + a40 = b0 ^((~b1)& b2 ); + a21 = b1 ^((~b2)& b3 ); + a02 = b2 ^((~b3)& b4 ); + a33 = b3 ^((~b4)& b0 ); + a14 = b4 ^((~b0)& b1 ); - B4 = ROL64((A30^D0), 18); - B0 = ROL64((A11^D1), 1); - B1 = ROL64((A42^D2), 6); - B2 = ROL64((A23^D3), 25); - B3 = ROL64((A04^D4), 8); - A30 = B0 ^((~B1)& B2 ); - A11 = B1 ^((~B2)& B3 ); - A42 = B2 ^((~B3)& B4 ); - A23 = B3 ^((~B4)& B0 ); - A04 = B4 ^((~B0)& B1 ); + b4 = ROL64((a30^d0), 18); + b0 = ROL64((a11^d1), 1); + b1 = ROL64((a42^d2), 6); + b2 = ROL64((a23^d3), 25); + b3 = ROL64((a04^d4), 8); + a30 = b0 ^((~b1)& b2 ); + a11 = b1 ^((~b2)& b3 ); + a42 = b2 ^((~b3)& b4 ); + a23 = b3 ^((~b4)& b0 ); + a04 = b4 ^((~b0)& b1 ); - B1 = ROL64((A20^D0), 36); - B2 = ROL64((A01^D1), 10); - B3 = ROL64((A32^D2), 15); - B4 = ROL64((A13^D3), 56); - B0 = ROL64((A44^D4), 27); - A20 = B0 ^((~B1)& B2 ); - A01 = B1 ^((~B2)& B3 ); - A32 = B2 ^((~B3)& B4 ); - A13 = B3 ^((~B4)& B0 ); - A44 = B4 ^((~B0)& B1 ); + b1 = ROL64((a20^d0), 36); + b2 = ROL64((a01^d1), 10); + b3 = ROL64((a32^d2), 15); + b4 = ROL64((a13^d3), 56); + b0 = ROL64((a44^d4), 27); + a20 = b0 ^((~b1)& b2 ); + a01 = b1 ^((~b2)& b3 ); + a32 = b2 ^((~b3)& b4 ); + a13 = b3 ^((~b4)& b0 ); + a44 = b4 ^((~b0)& b1 ); - B3 = ROL64((A10^D0), 41); - B4 = ROL64((A41^D1), 2); - B0 = ROL64((A22^D2), 62); - B1 = ROL64((A03^D3), 55); - B2 = ROL64((A34^D4), 39); - A10 = B0 ^((~B1)& B2 ); - A41 = B1 ^((~B2)& B3 ); - A22 = B2 ^((~B3)& B4 ); - A03 = B3 ^((~B4)& B0 ); - A34 = B4 ^((~B0)& B1 ); + b3 = ROL64((a10^d0), 41); + b4 = ROL64((a41^d1), 2); + b0 = ROL64((a22^d2), 62); + b1 = ROL64((a03^d3), 55); + b2 = ROL64((a34^d4), 39); + a10 = b0 ^((~b1)& b2 ); + a41 = b1 ^((~b2)& b3 ); + a22 = b2 ^((~b3)& b4 ); + a03 = b3 ^((~b4)& b0 ); + a34 = b4 ^((~b0)& b1 ); - C0 = A00^A40^A30^A20^A10; - C1 = A31^A21^A11^A01^A41; - C2 = A12^A02^A42^A32^A22; - C3 = A43^A33^A23^A13^A03; - C4 = A24^A14^A04^A44^A34; - D0 = C4^ROL64(C1, 1); - D1 = C0^ROL64(C2, 1); - D2 = C1^ROL64(C3, 1); - D3 = C2^ROL64(C4, 1); - D4 = C3^ROL64(C0, 1); + c0 = a00^a40^a30^a20^a10; + c1 = a31^a21^a11^a01^a41; + c2 = a12^a02^a42^a32^a22; + c3 = a43^a33^a23^a13^a03; + c4 = a24^a14^a04^a44^a34; + d0 = c4^ROL64(c1, 1); + d1 = c0^ROL64(c2, 1); + d2 = c1^ROL64(c3, 1); + d3 = c2^ROL64(c4, 1); + d4 = c3^ROL64(c0, 1); - B0 = (A00^D0); - B1 = ROL64((A21^D1), 44); - B2 = ROL64((A42^D2), 43); - B3 = ROL64((A13^D3), 21); - B4 = ROL64((A34^D4), 14); - A00 = B0 ^((~B1)& B2 ); - A00 ^= RC[i+2]; - A21 = B1 ^((~B2)& B3 ); - A42 = B2 ^((~B3)& B4 ); - A13 = B3 ^((~B4)& B0 ); - A34 = B4 ^((~B0)& B1 ); + b0 = (a00^d0); + b1 = ROL64((a21^d1), 44); + b2 = ROL64((a42^d2), 43); + b3 = ROL64((a13^d3), 21); + b4 = ROL64((a34^d4), 14); + a00 = b0 ^((~b1)& b2 ); + a00 ^= RC[i+2]; + a21 = b1 ^((~b2)& b3 ); + a42 = b2 ^((~b3)& b4 ); + a13 = b3 ^((~b4)& b0 ); + a34 = b4 ^((~b0)& b1 ); - B2 = ROL64((A30^D0), 3); - B3 = ROL64((A01^D1), 45); - B4 = ROL64((A22^D2), 61); - B0 = ROL64((A43^D3), 28); - B1 = ROL64((A14^D4), 20); - A30 = B0 ^((~B1)& B2 ); - A01 = B1 ^((~B2)& B3 ); - A22 = B2 ^((~B3)& B4 ); - A43 = B3 ^((~B4)& B0 ); - A14 = B4 ^((~B0)& B1 ); + b2 = ROL64((a30^d0), 3); + b3 = ROL64((a01^d1), 45); + b4 = ROL64((a22^d2), 61); + b0 = ROL64((a43^d3), 28); + b1 = ROL64((a14^d4), 20); + a30 = b0 ^((~b1)& b2 ); + a01 = b1 ^((~b2)& b3 ); + a22 = b2 ^((~b3)& b4 ); + a43 = b3 ^((~b4)& b0 ); + a14 = b4 ^((~b0)& b1 ); - B4 = ROL64((A10^D0), 18); - B0 = ROL64((A31^D1), 1); - B1 = ROL64((A02^D2), 6); - B2 = ROL64((A23^D3), 25); - B3 = ROL64((A44^D4), 8); - A10 = B0 ^((~B1)& B2 ); - A31 = B1 ^((~B2)& B3 ); - A02 = B2 ^((~B3)& B4 ); - A23 = B3 ^((~B4)& B0 ); - A44 = B4 ^((~B0)& B1 ); + b4 = ROL64((a10^d0), 18); + b0 = ROL64((a31^d1), 1); + b1 = ROL64((a02^d2), 6); + b2 = ROL64((a23^d3), 25); + b3 = ROL64((a44^d4), 8); + a10 = b0 ^((~b1)& b2 ); + a31 = b1 ^((~b2)& b3 ); + a02 = b2 ^((~b3)& b4 ); + a23 = b3 ^((~b4)& b0 ); + a44 = b4 ^((~b0)& b1 ); - B1 = ROL64((A40^D0), 36); - B2 = ROL64((A11^D1), 10); - B3 = ROL64((A32^D2), 15); - B4 = ROL64((A03^D3), 56); - B0 = ROL64((A24^D4), 27); - A40 = B0 ^((~B1)& B2 ); - A11 = B1 ^((~B2)& B3 ); - A32 = B2 ^((~B3)& B4 ); - A03 = B3 ^((~B4)& B0 ); - A24 = B4 ^((~B0)& B1 ); + b1 = ROL64((a40^d0), 36); + b2 = ROL64((a11^d1), 10); + b3 = ROL64((a32^d2), 15); + b4 = ROL64((a03^d3), 56); + b0 = ROL64((a24^d4), 27); + a40 = b0 ^((~b1)& b2 ); + a11 = b1 ^((~b2)& b3 ); + a32 = b2 ^((~b3)& b4 ); + a03 = b3 ^((~b4)& b0 ); + a24 = b4 ^((~b0)& b1 ); - B3 = ROL64((A20^D0), 41); - B4 = ROL64((A41^D1), 2); - B0 = ROL64((A12^D2), 62); - B1 = ROL64((A33^D3), 55); - B2 = ROL64((A04^D4), 39); - A20 = B0 ^((~B1)& B2 ); - A41 = B1 ^((~B2)& B3 ); - A12 = B2 ^((~B3)& B4 ); - A33 = B3 ^((~B4)& B0 ); - A04 = B4 ^((~B0)& B1 ); + b3 = ROL64((a20^d0), 41); + b4 = ROL64((a41^d1), 2); + b0 = ROL64((a12^d2), 62); + b1 = ROL64((a33^d3), 55); + b2 = ROL64((a04^d4), 39); + a20 = b0 ^((~b1)& b2 ); + a41 = b1 ^((~b2)& b3 ); + a12 = b2 ^((~b3)& b4 ); + a33 = b3 ^((~b4)& b0 ); + a04 = b4 ^((~b0)& b1 ); - C0 = A00^A30^A10^A40^A20; - C1 = A21^A01^A31^A11^A41; - C2 = A42^A22^A02^A32^A12; - C3 = A13^A43^A23^A03^A33; - C4 = A34^A14^A44^A24^A04; - D0 = C4^ROL64(C1, 1); - D1 = C0^ROL64(C2, 1); - D2 = C1^ROL64(C3, 1); - D3 = C2^ROL64(C4, 1); - D4 = C3^ROL64(C0, 1); + c0 = a00^a30^a10^a40^a20; + c1 = a21^a01^a31^a11^a41; + c2 = a42^a22^a02^a32^a12; + c3 = a13^a43^a23^a03^a33; + c4 = a34^a14^a44^a24^a04; + d0 = c4^ROL64(c1, 1); + d1 = c0^ROL64(c2, 1); + d2 = c1^ROL64(c3, 1); + d3 = c2^ROL64(c4, 1); + d4 = c3^ROL64(c0, 1); - B0 = (A00^D0); - B1 = ROL64((A01^D1), 44); - B2 = ROL64((A02^D2), 43); - B3 = ROL64((A03^D3), 21); - B4 = ROL64((A04^D4), 14); - A00 = B0 ^((~B1)& B2 ); - A00 ^= RC[i+3]; - A01 = B1 ^((~B2)& B3 ); - A02 = B2 ^((~B3)& B4 ); - A03 = B3 ^((~B4)& B0 ); - A04 = B4 ^((~B0)& B1 ); + b0 = (a00^d0); + b1 = ROL64((a01^d1), 44); + b2 = ROL64((a02^d2), 43); + b3 = ROL64((a03^d3), 21); + b4 = ROL64((a04^d4), 14); + a00 = b0 ^((~b1)& b2 ); + a00 ^= RC[i+3]; + a01 = b1 ^((~b2)& b3 ); + a02 = b2 ^((~b3)& b4 ); + a03 = b3 ^((~b4)& b0 ); + a04 = b4 ^((~b0)& b1 ); - B2 = ROL64((A10^D0), 3); - B3 = ROL64((A11^D1), 45); - B4 = ROL64((A12^D2), 61); - B0 = ROL64((A13^D3), 28); - B1 = ROL64((A14^D4), 20); - A10 = B0 ^((~B1)& B2 ); - A11 = B1 ^((~B2)& B3 ); - A12 = B2 ^((~B3)& B4 ); - A13 = B3 ^((~B4)& B0 ); - A14 = B4 ^((~B0)& B1 ); + b2 = ROL64((a10^d0), 3); + b3 = ROL64((a11^d1), 45); + b4 = ROL64((a12^d2), 61); + b0 = ROL64((a13^d3), 28); + b1 = ROL64((a14^d4), 20); + a10 = b0 ^((~b1)& b2 ); + a11 = b1 ^((~b2)& b3 ); + a12 = b2 ^((~b3)& b4 ); + a13 = b3 ^((~b4)& b0 ); + a14 = b4 ^((~b0)& b1 ); - B4 = ROL64((A20^D0), 18); - B0 = ROL64((A21^D1), 1); - B1 = ROL64((A22^D2), 6); - B2 = ROL64((A23^D3), 25); - B3 = ROL64((A24^D4), 8); - A20 = B0 ^((~B1)& B2 ); - A21 = B1 ^((~B2)& B3 ); - A22 = B2 ^((~B3)& B4 ); - A23 = B3 ^((~B4)& B0 ); - A24 = B4 ^((~B0)& B1 ); + b4 = ROL64((a20^d0), 18); + b0 = ROL64((a21^d1), 1); + b1 = ROL64((a22^d2), 6); + b2 = ROL64((a23^d3), 25); + b3 = ROL64((a24^d4), 8); + a20 = b0 ^((~b1)& b2 ); + a21 = b1 ^((~b2)& b3 ); + a22 = b2 ^((~b3)& b4 ); + a23 = b3 ^((~b4)& b0 ); + a24 = b4 ^((~b0)& b1 ); - B1 = ROL64((A30^D0), 36); - B2 = ROL64((A31^D1), 10); - B3 = ROL64((A32^D2), 15); - B4 = ROL64((A33^D3), 56); - B0 = ROL64((A34^D4), 27); - A30 = B0 ^((~B1)& B2 ); - A31 = B1 ^((~B2)& B3 ); - A32 = B2 ^((~B3)& B4 ); - A33 = B3 ^((~B4)& B0 ); - A34 = B4 ^((~B0)& B1 ); + b1 = ROL64((a30^d0), 36); + b2 = ROL64((a31^d1), 10); + b3 = ROL64((a32^d2), 15); + b4 = ROL64((a33^d3), 56); + b0 = ROL64((a34^d4), 27); + a30 = b0 ^((~b1)& b2 ); + a31 = b1 ^((~b2)& b3 ); + a32 = b2 ^((~b3)& b4 ); + a33 = b3 ^((~b4)& b0 ); + a34 = b4 ^((~b0)& b1 ); - B3 = ROL64((A40^D0), 41); - B4 = ROL64((A41^D1), 2); - B0 = ROL64((A42^D2), 62); - B1 = ROL64((A43^D3), 55); - B2 = ROL64((A44^D4), 39); - A40 = B0 ^((~B1)& B2 ); - A41 = B1 ^((~B2)& B3 ); - A42 = B2 ^((~B3)& B4 ); - A43 = B3 ^((~B4)& B0 ); - A44 = B4 ^((~B0)& B1 ); + b3 = ROL64((a40^d0), 41); + b4 = ROL64((a41^d1), 2); + b0 = ROL64((a42^d2), 62); + b1 = ROL64((a43^d3), 55); + b2 = ROL64((a44^d4), 39); + a40 = b0 ^((~b1)& b2 ); + a41 = b1 ^((~b2)& b3 ); + a42 = b2 ^((~b3)& b4 ); + a43 = b3 ^((~b4)& b0 ); + a44 = b4 ^((~b0)& b1 ); } } diff --git a/ext/misc/spellfix.c b/ext/misc/spellfix.c index 4f17b88e1d..8651bb7a9d 100644 --- a/ext/misc/spellfix.c +++ b/ext/misc/spellfix.c @@ -18,6 +18,12 @@ SQLITE_EXTENSION_INIT1 #ifndef SQLITE_AMALGAMATION +# if !defined(NDEBUG) && !defined(SQLITE_DEBUG) +# define NDEBUG 1 +# endif +# if defined(NDEBUG) && defined(SQLITE_DEBUG) +# undef NDEBUG +# endif # include # include # include @@ -652,6 +658,79 @@ static void editDist3ConfigDelete(void *pIn){ sqlite3_free(p); } +/* Compare the FROM values of two EditDist3Cost objects, for sorting. +** Return negative, zero, or positive if the A is less than, equal to, +** or greater than B. +*/ +static int editDist3CostCompare(EditDist3Cost *pA, EditDist3Cost *pB){ + int n = pA->nFrom; + int rc; + if( n>pB->nFrom ) n = pB->nFrom; + rc = strncmp(pA->a, pB->a, n); + if( rc==0 ) rc = pA->nFrom - pB->nFrom; + return rc; +} + +/* +** Merge together two sorted lists of EditDist3Cost objects, in order +** of increasing FROM. +*/ +static EditDist3Cost *editDist3CostMerge( + EditDist3Cost *pA, + EditDist3Cost *pB +){ + EditDist3Cost *pHead = 0; + EditDist3Cost **ppTail = &pHead; + EditDist3Cost *p; + while( pA && pB ){ + if( editDist3CostCompare(pA,pB)<=0 ){ + p = pA; + pA = pA->pNext; + }else{ + p = pB; + pB = pB->pNext; + } + *ppTail = p; + ppTail = &p->pNext; + } + if( pA ){ + *ppTail = pA; + }else{ + *ppTail = pB; + } + return pHead; +} + +/* +** Sort a list of EditDist3Cost objects into order of increasing FROM +*/ +static EditDist3Cost *editDist3CostSort(EditDist3Cost *pList){ + EditDist3Cost *ap[60], *p; + int i; + int mx = 0; + ap[0] = 0; + ap[1] = 0; + while( pList ){ + p = pList; + pList = p->pNext; + p->pNext = 0; + for(i=0; ap[i]; i++){ + p = editDist3CostMerge(ap[i],p); + ap[i] = 0; + } + ap[i] = p; + if( i>mx ){ + mx = i; + ap[i+1] = 0; + } + } + p = 0; + for(i=0; i<=mx; i++){ + if( ap[i] ) p = editDist3CostMerge(p,ap[i]); + } + return p; +} + /* ** Load all edit-distance weights from a table. */ @@ -685,6 +764,7 @@ static int editDist3ConfigLoad( assert( zTo!=0 || nTo==0 ); if( nFrom>100 || nTo>100 ) continue; if( iCost<0 ) continue; + if( iCost>10000 ) continue; /* Costs above 10K are considered infinite */ if( pLang==0 || iLang!=iLangPrev ){ EditDist3Lang *pNew; pNew = sqlite3_realloc64(p->a, (p->nLang+1)*sizeof(p->a[0])); @@ -722,6 +802,12 @@ static int editDist3ConfigLoad( } rc2 = sqlite3_finalize(pStmt); if( rc==SQLITE_OK ) rc = rc2; + if( rc==SQLITE_OK ){ + int iLang; + for(iLang=0; iLangnLang; iLang++){ + p->a[iLang].pCost = editDist3CostSort(p->a[iLang].pCost); + } + } return rc; } @@ -749,6 +835,7 @@ static int utf8Len(unsigned char c, int N){ ** the given string. */ static int matchTo(EditDist3Cost *p, const char *z, int n){ + if( p->a[p->nFrom]!=z[0] ) return 0; if( p->nTo>n ) return 0; if( strncmp(p->a+p->nFrom, z, p->nTo)!=0 ) return 0; return 1; @@ -760,6 +847,7 @@ static int matchTo(EditDist3Cost *p, const char *z, int n){ */ static int matchFrom(EditDist3Cost *p, const char *z, int n){ assert( p->nFrom<=n ); + if( p->a[0]!=z[0] ) return 0; if( strncmp(p->a, z, p->nFrom)!=0 ) return 0; return 1; } @@ -776,7 +864,8 @@ static int matchFromTo( ){ int b1 = pStr->a[n1].nByte; if( b1>n2 ) return 0; - if( memcmp(pStr->z+n1, z2, b1)!=0 ) return 0; + if( pStr->z[n1]!=z2[0] ) return 0; + if( strncmp(pStr->z+n1, z2, b1)!=0 ) return 0; return 1; } @@ -858,9 +947,6 @@ static EditDist3FromString *editDist3FromStringNew( /* ** Update entry m[i] such that it is the minimum of its current value ** and m[j]+iCost. -** -** If the iCost is 1,000,000 or greater, then consider the cost to be -** infinite and skip the update. */ static void updateCost( unsigned int *m, @@ -868,11 +954,11 @@ static void updateCost( int j, int iCost ){ + unsigned int b; assert( iCost>=0 ); - if( iCost<10000 ){ - unsigned int b = m[j] + iCost; - if( bpCost; p; p=p->pNext){ EditDist3Cost **apNew; - if( p->nFrom>0 ) continue; + if( p->nFrom>0 ) break; if( i2+p->nTo>n2 ) continue; + if( p->a[0]>z2[i2] ) break; if( matchTo(p, z2+i2, n2-i2)==0 ) continue; a2[i2].nIns++; apNew = sqlite3_realloc64(a2[i2].apIns, sizeof(*apNew)*a2[i2].nIns); @@ -1200,404 +1287,412 @@ static int utf8Charlen(const char *zIn, int nIn){ return nChar; } +typedef struct Transliteration Transliteration; +struct Transliteration { + unsigned short int cFrom; + unsigned char cTo0, cTo1, cTo2, cTo3; +}; + /* ** Table of translations from unicode characters into ASCII. */ -static const struct { - unsigned short int cFrom; - unsigned char cTo0, cTo1; -} translit[] = { - { 0x00A0, 0x20, 0x00 }, /*   to */ - { 0x00B5, 0x75, 0x00 }, /* µ to u */ - { 0x00C0, 0x41, 0x00 }, /* À to A */ - { 0x00C1, 0x41, 0x00 }, /* Á to A */ - { 0x00C2, 0x41, 0x00 }, /*  to A */ - { 0x00C3, 0x41, 0x00 }, /* à to A */ - { 0x00C4, 0x41, 0x65 }, /* Ä to Ae */ - { 0x00C5, 0x41, 0x61 }, /* Å to Aa */ - { 0x00C6, 0x41, 0x45 }, /* Æ to AE */ - { 0x00C7, 0x43, 0x00 }, /* Ç to C */ - { 0x00C8, 0x45, 0x00 }, /* È to E */ - { 0x00C9, 0x45, 0x00 }, /* É to E */ - { 0x00CA, 0x45, 0x00 }, /* Ê to E */ - { 0x00CB, 0x45, 0x00 }, /* Ë to E */ - { 0x00CC, 0x49, 0x00 }, /* Ì to I */ - { 0x00CD, 0x49, 0x00 }, /* Í to I */ - { 0x00CE, 0x49, 0x00 }, /* Î to I */ - { 0x00CF, 0x49, 0x00 }, /* Ï to I */ - { 0x00D0, 0x44, 0x00 }, /* Ð to D */ - { 0x00D1, 0x4E, 0x00 }, /* Ñ to N */ - { 0x00D2, 0x4F, 0x00 }, /* Ò to O */ - { 0x00D3, 0x4F, 0x00 }, /* Ó to O */ - { 0x00D4, 0x4F, 0x00 }, /* Ô to O */ - { 0x00D5, 0x4F, 0x00 }, /* Õ to O */ - { 0x00D6, 0x4F, 0x65 }, /* Ö to Oe */ - { 0x00D7, 0x78, 0x00 }, /* × to x */ - { 0x00D8, 0x4F, 0x00 }, /* Ø to O */ - { 0x00D9, 0x55, 0x00 }, /* Ù to U */ - { 0x00DA, 0x55, 0x00 }, /* Ú to U */ - { 0x00DB, 0x55, 0x00 }, /* Û to U */ - { 0x00DC, 0x55, 0x65 }, /* Ü to Ue */ - { 0x00DD, 0x59, 0x00 }, /* Ý to Y */ - { 0x00DE, 0x54, 0x68 }, /* Þ to Th */ - { 0x00DF, 0x73, 0x73 }, /* ß to ss */ - { 0x00E0, 0x61, 0x00 }, /* à to a */ - { 0x00E1, 0x61, 0x00 }, /* á to a */ - { 0x00E2, 0x61, 0x00 }, /* â to a */ - { 0x00E3, 0x61, 0x00 }, /* ã to a */ - { 0x00E4, 0x61, 0x65 }, /* ä to ae */ - { 0x00E5, 0x61, 0x61 }, /* å to aa */ - { 0x00E6, 0x61, 0x65 }, /* æ to ae */ - { 0x00E7, 0x63, 0x00 }, /* ç to c */ - { 0x00E8, 0x65, 0x00 }, /* è to e */ - { 0x00E9, 0x65, 0x00 }, /* é to e */ - { 0x00EA, 0x65, 0x00 }, /* ê to e */ - { 0x00EB, 0x65, 0x00 }, /* ë to e */ - { 0x00EC, 0x69, 0x00 }, /* ì to i */ - { 0x00ED, 0x69, 0x00 }, /* í to i */ - { 0x00EE, 0x69, 0x00 }, /* î to i */ - { 0x00EF, 0x69, 0x00 }, /* ï to i */ - { 0x00F0, 0x64, 0x00 }, /* ð to d */ - { 0x00F1, 0x6E, 0x00 }, /* ñ to n */ - { 0x00F2, 0x6F, 0x00 }, /* ò to o */ - { 0x00F3, 0x6F, 0x00 }, /* ó to o */ - { 0x00F4, 0x6F, 0x00 }, /* ô to o */ - { 0x00F5, 0x6F, 0x00 }, /* õ to o */ - { 0x00F6, 0x6F, 0x65 }, /* ö to oe */ - { 0x00F7, 0x3A, 0x00 }, /* ÷ to : */ - { 0x00F8, 0x6F, 0x00 }, /* ø to o */ - { 0x00F9, 0x75, 0x00 }, /* ù to u */ - { 0x00FA, 0x75, 0x00 }, /* ú to u */ - { 0x00FB, 0x75, 0x00 }, /* û to u */ - { 0x00FC, 0x75, 0x65 }, /* ü to ue */ - { 0x00FD, 0x79, 0x00 }, /* ý to y */ - { 0x00FE, 0x74, 0x68 }, /* þ to th */ - { 0x00FF, 0x79, 0x00 }, /* ÿ to y */ - { 0x0100, 0x41, 0x00 }, /* Ā to A */ - { 0x0101, 0x61, 0x00 }, /* ā to a */ - { 0x0102, 0x41, 0x00 }, /* Ă to A */ - { 0x0103, 0x61, 0x00 }, /* ă to a */ - { 0x0104, 0x41, 0x00 }, /* Ą to A */ - { 0x0105, 0x61, 0x00 }, /* ą to a */ - { 0x0106, 0x43, 0x00 }, /* Ć to C */ - { 0x0107, 0x63, 0x00 }, /* ć to c */ - { 0x0108, 0x43, 0x68 }, /* Ĉ to Ch */ - { 0x0109, 0x63, 0x68 }, /* ĉ to ch */ - { 0x010A, 0x43, 0x00 }, /* Ċ to C */ - { 0x010B, 0x63, 0x00 }, /* ċ to c */ - { 0x010C, 0x43, 0x00 }, /* Č to C */ - { 0x010D, 0x63, 0x00 }, /* č to c */ - { 0x010E, 0x44, 0x00 }, /* Ď to D */ - { 0x010F, 0x64, 0x00 }, /* ď to d */ - { 0x0110, 0x44, 0x00 }, /* Đ to D */ - { 0x0111, 0x64, 0x00 }, /* đ to d */ - { 0x0112, 0x45, 0x00 }, /* Ē to E */ - { 0x0113, 0x65, 0x00 }, /* ē to e */ - { 0x0114, 0x45, 0x00 }, /* Ĕ to E */ - { 0x0115, 0x65, 0x00 }, /* ĕ to e */ - { 0x0116, 0x45, 0x00 }, /* Ė to E */ - { 0x0117, 0x65, 0x00 }, /* ė to e */ - { 0x0118, 0x45, 0x00 }, /* Ę to E */ - { 0x0119, 0x65, 0x00 }, /* ę to e */ - { 0x011A, 0x45, 0x00 }, /* Ě to E */ - { 0x011B, 0x65, 0x00 }, /* ě to e */ - { 0x011C, 0x47, 0x68 }, /* Ĝ to Gh */ - { 0x011D, 0x67, 0x68 }, /* ĝ to gh */ - { 0x011E, 0x47, 0x00 }, /* Ğ to G */ - { 0x011F, 0x67, 0x00 }, /* ğ to g */ - { 0x0120, 0x47, 0x00 }, /* Ġ to G */ - { 0x0121, 0x67, 0x00 }, /* ġ to g */ - { 0x0122, 0x47, 0x00 }, /* Ģ to G */ - { 0x0123, 0x67, 0x00 }, /* ģ to g */ - { 0x0124, 0x48, 0x68 }, /* Ĥ to Hh */ - { 0x0125, 0x68, 0x68 }, /* ĥ to hh */ - { 0x0126, 0x48, 0x00 }, /* Ħ to H */ - { 0x0127, 0x68, 0x00 }, /* ħ to h */ - { 0x0128, 0x49, 0x00 }, /* Ĩ to I */ - { 0x0129, 0x69, 0x00 }, /* ĩ to i */ - { 0x012A, 0x49, 0x00 }, /* Ī to I */ - { 0x012B, 0x69, 0x00 }, /* ī to i */ - { 0x012C, 0x49, 0x00 }, /* Ĭ to I */ - { 0x012D, 0x69, 0x00 }, /* ĭ to i */ - { 0x012E, 0x49, 0x00 }, /* Į to I */ - { 0x012F, 0x69, 0x00 }, /* į to i */ - { 0x0130, 0x49, 0x00 }, /* İ to I */ - { 0x0131, 0x69, 0x00 }, /* ı to i */ - { 0x0132, 0x49, 0x4A }, /* IJ to IJ */ - { 0x0133, 0x69, 0x6A }, /* ij to ij */ - { 0x0134, 0x4A, 0x68 }, /* Ĵ to Jh */ - { 0x0135, 0x6A, 0x68 }, /* ĵ to jh */ - { 0x0136, 0x4B, 0x00 }, /* Ķ to K */ - { 0x0137, 0x6B, 0x00 }, /* ķ to k */ - { 0x0138, 0x6B, 0x00 }, /* ĸ to k */ - { 0x0139, 0x4C, 0x00 }, /* Ĺ to L */ - { 0x013A, 0x6C, 0x00 }, /* ĺ to l */ - { 0x013B, 0x4C, 0x00 }, /* Ļ to L */ - { 0x013C, 0x6C, 0x00 }, /* ļ to l */ - { 0x013D, 0x4C, 0x00 }, /* Ľ to L */ - { 0x013E, 0x6C, 0x00 }, /* ľ to l */ - { 0x013F, 0x4C, 0x2E }, /* Ŀ to L. */ - { 0x0140, 0x6C, 0x2E }, /* ŀ to l. */ - { 0x0141, 0x4C, 0x00 }, /* Ł to L */ - { 0x0142, 0x6C, 0x00 }, /* ł to l */ - { 0x0143, 0x4E, 0x00 }, /* Ń to N */ - { 0x0144, 0x6E, 0x00 }, /* ń to n */ - { 0x0145, 0x4E, 0x00 }, /* Ņ to N */ - { 0x0146, 0x6E, 0x00 }, /* ņ to n */ - { 0x0147, 0x4E, 0x00 }, /* Ň to N */ - { 0x0148, 0x6E, 0x00 }, /* ň to n */ - { 0x0149, 0x27, 0x6E }, /* ʼn to 'n */ - { 0x014A, 0x4E, 0x47 }, /* Ŋ to NG */ - { 0x014B, 0x6E, 0x67 }, /* ŋ to ng */ - { 0x014C, 0x4F, 0x00 }, /* Ō to O */ - { 0x014D, 0x6F, 0x00 }, /* ō to o */ - { 0x014E, 0x4F, 0x00 }, /* Ŏ to O */ - { 0x014F, 0x6F, 0x00 }, /* ŏ to o */ - { 0x0150, 0x4F, 0x00 }, /* Ő to O */ - { 0x0151, 0x6F, 0x00 }, /* ő to o */ - { 0x0152, 0x4F, 0x45 }, /* Œ to OE */ - { 0x0153, 0x6F, 0x65 }, /* œ to oe */ - { 0x0154, 0x52, 0x00 }, /* Ŕ to R */ - { 0x0155, 0x72, 0x00 }, /* ŕ to r */ - { 0x0156, 0x52, 0x00 }, /* Ŗ to R */ - { 0x0157, 0x72, 0x00 }, /* ŗ to r */ - { 0x0158, 0x52, 0x00 }, /* Ř to R */ - { 0x0159, 0x72, 0x00 }, /* ř to r */ - { 0x015A, 0x53, 0x00 }, /* Ś to S */ - { 0x015B, 0x73, 0x00 }, /* ś to s */ - { 0x015C, 0x53, 0x68 }, /* Ŝ to Sh */ - { 0x015D, 0x73, 0x68 }, /* ŝ to sh */ - { 0x015E, 0x53, 0x00 }, /* Ş to S */ - { 0x015F, 0x73, 0x00 }, /* ş to s */ - { 0x0160, 0x53, 0x00 }, /* Š to S */ - { 0x0161, 0x73, 0x00 }, /* š to s */ - { 0x0162, 0x54, 0x00 }, /* Ţ to T */ - { 0x0163, 0x74, 0x00 }, /* ţ to t */ - { 0x0164, 0x54, 0x00 }, /* Ť to T */ - { 0x0165, 0x74, 0x00 }, /* ť to t */ - { 0x0166, 0x54, 0x00 }, /* Ŧ to T */ - { 0x0167, 0x74, 0x00 }, /* ŧ to t */ - { 0x0168, 0x55, 0x00 }, /* Ũ to U */ - { 0x0169, 0x75, 0x00 }, /* ũ to u */ - { 0x016A, 0x55, 0x00 }, /* Ū to U */ - { 0x016B, 0x75, 0x00 }, /* ū to u */ - { 0x016C, 0x55, 0x00 }, /* Ŭ to U */ - { 0x016D, 0x75, 0x00 }, /* ŭ to u */ - { 0x016E, 0x55, 0x00 }, /* Ů to U */ - { 0x016F, 0x75, 0x00 }, /* ů to u */ - { 0x0170, 0x55, 0x00 }, /* Ű to U */ - { 0x0171, 0x75, 0x00 }, /* ű to u */ - { 0x0172, 0x55, 0x00 }, /* Ų to U */ - { 0x0173, 0x75, 0x00 }, /* ų to u */ - { 0x0174, 0x57, 0x00 }, /* Ŵ to W */ - { 0x0175, 0x77, 0x00 }, /* ŵ to w */ - { 0x0176, 0x59, 0x00 }, /* Ŷ to Y */ - { 0x0177, 0x79, 0x00 }, /* ŷ to y */ - { 0x0178, 0x59, 0x00 }, /* Ÿ to Y */ - { 0x0179, 0x5A, 0x00 }, /* Ź to Z */ - { 0x017A, 0x7A, 0x00 }, /* ź to z */ - { 0x017B, 0x5A, 0x00 }, /* Ż to Z */ - { 0x017C, 0x7A, 0x00 }, /* ż to z */ - { 0x017D, 0x5A, 0x00 }, /* Ž to Z */ - { 0x017E, 0x7A, 0x00 }, /* ž to z */ - { 0x017F, 0x73, 0x00 }, /* ſ to s */ - { 0x0192, 0x66, 0x00 }, /* ƒ to f */ - { 0x0218, 0x53, 0x00 }, /* Ș to S */ - { 0x0219, 0x73, 0x00 }, /* ș to s */ - { 0x021A, 0x54, 0x00 }, /* Ț to T */ - { 0x021B, 0x74, 0x00 }, /* ț to t */ - { 0x0386, 0x41, 0x00 }, /* Ά to A */ - { 0x0388, 0x45, 0x00 }, /* Έ to E */ - { 0x0389, 0x49, 0x00 }, /* Ή to I */ - { 0x038A, 0x49, 0x00 }, /* Ί to I */ - { 0x038C, 0x4f, 0x00 }, /* Ό to O */ - { 0x038E, 0x59, 0x00 }, /* Ύ to Y */ - { 0x038F, 0x4f, 0x00 }, /* Ώ to O */ - { 0x0390, 0x69, 0x00 }, /* ΐ to i */ - { 0x0391, 0x41, 0x00 }, /* Α to A */ - { 0x0392, 0x42, 0x00 }, /* Β to B */ - { 0x0393, 0x47, 0x00 }, /* Γ to G */ - { 0x0394, 0x44, 0x00 }, /* Δ to D */ - { 0x0395, 0x45, 0x00 }, /* Ε to E */ - { 0x0396, 0x5a, 0x00 }, /* Ζ to Z */ - { 0x0397, 0x49, 0x00 }, /* Η to I */ - { 0x0398, 0x54, 0x68 }, /* Θ to Th */ - { 0x0399, 0x49, 0x00 }, /* Ι to I */ - { 0x039A, 0x4b, 0x00 }, /* Κ to K */ - { 0x039B, 0x4c, 0x00 }, /* Λ to L */ - { 0x039C, 0x4d, 0x00 }, /* Μ to M */ - { 0x039D, 0x4e, 0x00 }, /* Ν to N */ - { 0x039E, 0x58, 0x00 }, /* Ξ to X */ - { 0x039F, 0x4f, 0x00 }, /* Ο to O */ - { 0x03A0, 0x50, 0x00 }, /* Π to P */ - { 0x03A1, 0x52, 0x00 }, /* Ρ to R */ - { 0x03A3, 0x53, 0x00 }, /* Σ to S */ - { 0x03A4, 0x54, 0x00 }, /* Τ to T */ - { 0x03A5, 0x59, 0x00 }, /* Υ to Y */ - { 0x03A6, 0x46, 0x00 }, /* Φ to F */ - { 0x03A7, 0x43, 0x68 }, /* Χ to Ch */ - { 0x03A8, 0x50, 0x73 }, /* Ψ to Ps */ - { 0x03A9, 0x4f, 0x00 }, /* Ω to O */ - { 0x03AA, 0x49, 0x00 }, /* Ϊ to I */ - { 0x03AB, 0x59, 0x00 }, /* Ϋ to Y */ - { 0x03AC, 0x61, 0x00 }, /* ά to a */ - { 0x03AD, 0x65, 0x00 }, /* έ to e */ - { 0x03AE, 0x69, 0x00 }, /* ή to i */ - { 0x03AF, 0x69, 0x00 }, /* ί to i */ - { 0x03B1, 0x61, 0x00 }, /* α to a */ - { 0x03B2, 0x62, 0x00 }, /* β to b */ - { 0x03B3, 0x67, 0x00 }, /* γ to g */ - { 0x03B4, 0x64, 0x00 }, /* δ to d */ - { 0x03B5, 0x65, 0x00 }, /* ε to e */ - { 0x03B6, 0x7a, 0x00 }, /* ζ to z */ - { 0x03B7, 0x69, 0x00 }, /* η to i */ - { 0x03B8, 0x74, 0x68 }, /* θ to th */ - { 0x03B9, 0x69, 0x00 }, /* ι to i */ - { 0x03BA, 0x6b, 0x00 }, /* κ to k */ - { 0x03BB, 0x6c, 0x00 }, /* λ to l */ - { 0x03BC, 0x6d, 0x00 }, /* μ to m */ - { 0x03BD, 0x6e, 0x00 }, /* ν to n */ - { 0x03BE, 0x78, 0x00 }, /* ξ to x */ - { 0x03BF, 0x6f, 0x00 }, /* ο to o */ - { 0x03C0, 0x70, 0x00 }, /* π to p */ - { 0x03C1, 0x72, 0x00 }, /* ρ to r */ - { 0x03C3, 0x73, 0x00 }, /* σ to s */ - { 0x03C4, 0x74, 0x00 }, /* τ to t */ - { 0x03C5, 0x79, 0x00 }, /* υ to y */ - { 0x03C6, 0x66, 0x00 }, /* φ to f */ - { 0x03C7, 0x63, 0x68 }, /* χ to ch */ - { 0x03C8, 0x70, 0x73 }, /* ψ to ps */ - { 0x03C9, 0x6f, 0x00 }, /* ω to o */ - { 0x03CA, 0x69, 0x00 }, /* ϊ to i */ - { 0x03CB, 0x79, 0x00 }, /* ϋ to y */ - { 0x03CC, 0x6f, 0x00 }, /* ό to o */ - { 0x03CD, 0x79, 0x00 }, /* ύ to y */ - { 0x03CE, 0x69, 0x00 }, /* ώ to i */ - { 0x0400, 0x45, 0x00 }, /* Ѐ to E */ - { 0x0401, 0x45, 0x00 }, /* Ё to E */ - { 0x0402, 0x44, 0x00 }, /* Ђ to D */ - { 0x0403, 0x47, 0x00 }, /* Ѓ to G */ - { 0x0404, 0x45, 0x00 }, /* Є to E */ - { 0x0405, 0x5a, 0x00 }, /* Ѕ to Z */ - { 0x0406, 0x49, 0x00 }, /* І to I */ - { 0x0407, 0x49, 0x00 }, /* Ї to I */ - { 0x0408, 0x4a, 0x00 }, /* Ј to J */ - { 0x0409, 0x49, 0x00 }, /* Љ to I */ - { 0x040A, 0x4e, 0x00 }, /* Њ to N */ - { 0x040B, 0x44, 0x00 }, /* Ћ to D */ - { 0x040C, 0x4b, 0x00 }, /* Ќ to K */ - { 0x040D, 0x49, 0x00 }, /* Ѝ to I */ - { 0x040E, 0x55, 0x00 }, /* Ў to U */ - { 0x040F, 0x44, 0x00 }, /* Џ to D */ - { 0x0410, 0x41, 0x00 }, /* А to A */ - { 0x0411, 0x42, 0x00 }, /* Б to B */ - { 0x0412, 0x56, 0x00 }, /* В to V */ - { 0x0413, 0x47, 0x00 }, /* Г to G */ - { 0x0414, 0x44, 0x00 }, /* Д to D */ - { 0x0415, 0x45, 0x00 }, /* Е to E */ - { 0x0416, 0x5a, 0x68 }, /* Ж to Zh */ - { 0x0417, 0x5a, 0x00 }, /* З to Z */ - { 0x0418, 0x49, 0x00 }, /* И to I */ - { 0x0419, 0x49, 0x00 }, /* Й to I */ - { 0x041A, 0x4b, 0x00 }, /* К to K */ - { 0x041B, 0x4c, 0x00 }, /* Л to L */ - { 0x041C, 0x4d, 0x00 }, /* М to M */ - { 0x041D, 0x4e, 0x00 }, /* Н to N */ - { 0x041E, 0x4f, 0x00 }, /* О to O */ - { 0x041F, 0x50, 0x00 }, /* П to P */ - { 0x0420, 0x52, 0x00 }, /* Р to R */ - { 0x0421, 0x53, 0x00 }, /* С to S */ - { 0x0422, 0x54, 0x00 }, /* Т to T */ - { 0x0423, 0x55, 0x00 }, /* У to U */ - { 0x0424, 0x46, 0x00 }, /* Ф to F */ - { 0x0425, 0x4b, 0x68 }, /* Х to Kh */ - { 0x0426, 0x54, 0x63 }, /* Ц to Tc */ - { 0x0427, 0x43, 0x68 }, /* Ч to Ch */ - { 0x0428, 0x53, 0x68 }, /* Ш to Sh */ - { 0x0429, 0x53, 0x68 }, /* Щ to Shch */ - { 0x042A, 0x61, 0x00 }, /* to A */ - { 0x042B, 0x59, 0x00 }, /* Ы to Y */ - { 0x042C, 0x59, 0x00 }, /* to Y */ - { 0x042D, 0x45, 0x00 }, /* Э to E */ - { 0x042E, 0x49, 0x75 }, /* Ю to Iu */ - { 0x042F, 0x49, 0x61 }, /* Я to Ia */ - { 0x0430, 0x61, 0x00 }, /* а to a */ - { 0x0431, 0x62, 0x00 }, /* б to b */ - { 0x0432, 0x76, 0x00 }, /* в to v */ - { 0x0433, 0x67, 0x00 }, /* г to g */ - { 0x0434, 0x64, 0x00 }, /* д to d */ - { 0x0435, 0x65, 0x00 }, /* е to e */ - { 0x0436, 0x7a, 0x68 }, /* ж to zh */ - { 0x0437, 0x7a, 0x00 }, /* з to z */ - { 0x0438, 0x69, 0x00 }, /* и to i */ - { 0x0439, 0x69, 0x00 }, /* й to i */ - { 0x043A, 0x6b, 0x00 }, /* к to k */ - { 0x043B, 0x6c, 0x00 }, /* л to l */ - { 0x043C, 0x6d, 0x00 }, /* м to m */ - { 0x043D, 0x6e, 0x00 }, /* н to n */ - { 0x043E, 0x6f, 0x00 }, /* о to o */ - { 0x043F, 0x70, 0x00 }, /* п to p */ - { 0x0440, 0x72, 0x00 }, /* р to r */ - { 0x0441, 0x73, 0x00 }, /* с to s */ - { 0x0442, 0x74, 0x00 }, /* т to t */ - { 0x0443, 0x75, 0x00 }, /* у to u */ - { 0x0444, 0x66, 0x00 }, /* ф to f */ - { 0x0445, 0x6b, 0x68 }, /* х to kh */ - { 0x0446, 0x74, 0x63 }, /* ц to tc */ - { 0x0447, 0x63, 0x68 }, /* ч to ch */ - { 0x0448, 0x73, 0x68 }, /* ш to sh */ - { 0x0449, 0x73, 0x68 }, /* щ to shch */ - { 0x044A, 0x61, 0x00 }, /* to a */ - { 0x044B, 0x79, 0x00 }, /* ы to y */ - { 0x044C, 0x79, 0x00 }, /* to y */ - { 0x044D, 0x65, 0x00 }, /* э to e */ - { 0x044E, 0x69, 0x75 }, /* ю to iu */ - { 0x044F, 0x69, 0x61 }, /* я to ia */ - { 0x0450, 0x65, 0x00 }, /* ѐ to e */ - { 0x0451, 0x65, 0x00 }, /* ё to e */ - { 0x0452, 0x64, 0x00 }, /* ђ to d */ - { 0x0453, 0x67, 0x00 }, /* ѓ to g */ - { 0x0454, 0x65, 0x00 }, /* є to e */ - { 0x0455, 0x7a, 0x00 }, /* ѕ to z */ - { 0x0456, 0x69, 0x00 }, /* і to i */ - { 0x0457, 0x69, 0x00 }, /* ї to i */ - { 0x0458, 0x6a, 0x00 }, /* ј to j */ - { 0x0459, 0x69, 0x00 }, /* љ to i */ - { 0x045A, 0x6e, 0x00 }, /* њ to n */ - { 0x045B, 0x64, 0x00 }, /* ћ to d */ - { 0x045C, 0x6b, 0x00 }, /* ќ to k */ - { 0x045D, 0x69, 0x00 }, /* ѝ to i */ - { 0x045E, 0x75, 0x00 }, /* ў to u */ - { 0x045F, 0x64, 0x00 }, /* џ to d */ - { 0x1E02, 0x42, 0x00 }, /* Ḃ to B */ - { 0x1E03, 0x62, 0x00 }, /* ḃ to b */ - { 0x1E0A, 0x44, 0x00 }, /* Ḋ to D */ - { 0x1E0B, 0x64, 0x00 }, /* ḋ to d */ - { 0x1E1E, 0x46, 0x00 }, /* Ḟ to F */ - { 0x1E1F, 0x66, 0x00 }, /* ḟ to f */ - { 0x1E40, 0x4D, 0x00 }, /* Ṁ to M */ - { 0x1E41, 0x6D, 0x00 }, /* ṁ to m */ - { 0x1E56, 0x50, 0x00 }, /* Ṗ to P */ - { 0x1E57, 0x70, 0x00 }, /* ṗ to p */ - { 0x1E60, 0x53, 0x00 }, /* Ṡ to S */ - { 0x1E61, 0x73, 0x00 }, /* ṡ to s */ - { 0x1E6A, 0x54, 0x00 }, /* Ṫ to T */ - { 0x1E6B, 0x74, 0x00 }, /* ṫ to t */ - { 0x1E80, 0x57, 0x00 }, /* Ẁ to W */ - { 0x1E81, 0x77, 0x00 }, /* ẁ to w */ - { 0x1E82, 0x57, 0x00 }, /* Ẃ to W */ - { 0x1E83, 0x77, 0x00 }, /* ẃ to w */ - { 0x1E84, 0x57, 0x00 }, /* Ẅ to W */ - { 0x1E85, 0x77, 0x00 }, /* ẅ to w */ - { 0x1EF2, 0x59, 0x00 }, /* Ỳ to Y */ - { 0x1EF3, 0x79, 0x00 }, /* ỳ to y */ - { 0xFB00, 0x66, 0x66 }, /* ff to ff */ - { 0xFB01, 0x66, 0x69 }, /* fi to fi */ - { 0xFB02, 0x66, 0x6C }, /* fl to fl */ - { 0xFB05, 0x73, 0x74 }, /* ſt to st */ - { 0xFB06, 0x73, 0x74 }, /* st to st */ +static const Transliteration translit[] = { + { 0x00A0, 0x20, 0x00, 0x00, 0x00 }, /*   to */ + { 0x00B5, 0x75, 0x00, 0x00, 0x00 }, /* µ to u */ + { 0x00C0, 0x41, 0x00, 0x00, 0x00 }, /* À to A */ + { 0x00C1, 0x41, 0x00, 0x00, 0x00 }, /* Á to A */ + { 0x00C2, 0x41, 0x00, 0x00, 0x00 }, /*  to A */ + { 0x00C3, 0x41, 0x00, 0x00, 0x00 }, /* à to A */ + { 0x00C4, 0x41, 0x65, 0x00, 0x00 }, /* Ä to Ae */ + { 0x00C5, 0x41, 0x61, 0x00, 0x00 }, /* Å to Aa */ + { 0x00C6, 0x41, 0x45, 0x00, 0x00 }, /* Æ to AE */ + { 0x00C7, 0x43, 0x00, 0x00, 0x00 }, /* Ç to C */ + { 0x00C8, 0x45, 0x00, 0x00, 0x00 }, /* È to E */ + { 0x00C9, 0x45, 0x00, 0x00, 0x00 }, /* É to E */ + { 0x00CA, 0x45, 0x00, 0x00, 0x00 }, /* Ê to E */ + { 0x00CB, 0x45, 0x00, 0x00, 0x00 }, /* Ë to E */ + { 0x00CC, 0x49, 0x00, 0x00, 0x00 }, /* Ì to I */ + { 0x00CD, 0x49, 0x00, 0x00, 0x00 }, /* Í to I */ + { 0x00CE, 0x49, 0x00, 0x00, 0x00 }, /* Î to I */ + { 0x00CF, 0x49, 0x00, 0x00, 0x00 }, /* Ï to I */ + { 0x00D0, 0x44, 0x00, 0x00, 0x00 }, /* Ð to D */ + { 0x00D1, 0x4E, 0x00, 0x00, 0x00 }, /* Ñ to N */ + { 0x00D2, 0x4F, 0x00, 0x00, 0x00 }, /* Ò to O */ + { 0x00D3, 0x4F, 0x00, 0x00, 0x00 }, /* Ó to O */ + { 0x00D4, 0x4F, 0x00, 0x00, 0x00 }, /* Ô to O */ + { 0x00D5, 0x4F, 0x00, 0x00, 0x00 }, /* Õ to O */ + { 0x00D6, 0x4F, 0x65, 0x00, 0x00 }, /* Ö to Oe */ + { 0x00D7, 0x78, 0x00, 0x00, 0x00 }, /* × to x */ + { 0x00D8, 0x4F, 0x00, 0x00, 0x00 }, /* Ø to O */ + { 0x00D9, 0x55, 0x00, 0x00, 0x00 }, /* Ù to U */ + { 0x00DA, 0x55, 0x00, 0x00, 0x00 }, /* Ú to U */ + { 0x00DB, 0x55, 0x00, 0x00, 0x00 }, /* Û to U */ + { 0x00DC, 0x55, 0x65, 0x00, 0x00 }, /* Ü to Ue */ + { 0x00DD, 0x59, 0x00, 0x00, 0x00 }, /* Ý to Y */ + { 0x00DE, 0x54, 0x68, 0x00, 0x00 }, /* Þ to Th */ + { 0x00DF, 0x73, 0x73, 0x00, 0x00 }, /* ß to ss */ + { 0x00E0, 0x61, 0x00, 0x00, 0x00 }, /* à to a */ + { 0x00E1, 0x61, 0x00, 0x00, 0x00 }, /* á to a */ + { 0x00E2, 0x61, 0x00, 0x00, 0x00 }, /* â to a */ + { 0x00E3, 0x61, 0x00, 0x00, 0x00 }, /* ã to a */ + { 0x00E4, 0x61, 0x65, 0x00, 0x00 }, /* ä to ae */ + { 0x00E5, 0x61, 0x61, 0x00, 0x00 }, /* å to aa */ + { 0x00E6, 0x61, 0x65, 0x00, 0x00 }, /* æ to ae */ + { 0x00E7, 0x63, 0x00, 0x00, 0x00 }, /* ç to c */ + { 0x00E8, 0x65, 0x00, 0x00, 0x00 }, /* è to e */ + { 0x00E9, 0x65, 0x00, 0x00, 0x00 }, /* é to e */ + { 0x00EA, 0x65, 0x00, 0x00, 0x00 }, /* ê to e */ + { 0x00EB, 0x65, 0x00, 0x00, 0x00 }, /* ë to e */ + { 0x00EC, 0x69, 0x00, 0x00, 0x00 }, /* ì to i */ + { 0x00ED, 0x69, 0x00, 0x00, 0x00 }, /* í to i */ + { 0x00EE, 0x69, 0x00, 0x00, 0x00 }, /* î to i */ + { 0x00EF, 0x69, 0x00, 0x00, 0x00 }, /* ï to i */ + { 0x00F0, 0x64, 0x00, 0x00, 0x00 }, /* ð to d */ + { 0x00F1, 0x6E, 0x00, 0x00, 0x00 }, /* ñ to n */ + { 0x00F2, 0x6F, 0x00, 0x00, 0x00 }, /* ò to o */ + { 0x00F3, 0x6F, 0x00, 0x00, 0x00 }, /* ó to o */ + { 0x00F4, 0x6F, 0x00, 0x00, 0x00 }, /* ô to o */ + { 0x00F5, 0x6F, 0x00, 0x00, 0x00 }, /* õ to o */ + { 0x00F6, 0x6F, 0x65, 0x00, 0x00 }, /* ö to oe */ + { 0x00F7, 0x3A, 0x00, 0x00, 0x00 }, /* ÷ to : */ + { 0x00F8, 0x6F, 0x00, 0x00, 0x00 }, /* ø to o */ + { 0x00F9, 0x75, 0x00, 0x00, 0x00 }, /* ù to u */ + { 0x00FA, 0x75, 0x00, 0x00, 0x00 }, /* ú to u */ + { 0x00FB, 0x75, 0x00, 0x00, 0x00 }, /* û to u */ + { 0x00FC, 0x75, 0x65, 0x00, 0x00 }, /* ü to ue */ + { 0x00FD, 0x79, 0x00, 0x00, 0x00 }, /* ý to y */ + { 0x00FE, 0x74, 0x68, 0x00, 0x00 }, /* þ to th */ + { 0x00FF, 0x79, 0x00, 0x00, 0x00 }, /* ÿ to y */ + { 0x0100, 0x41, 0x00, 0x00, 0x00 }, /* Ā to A */ + { 0x0101, 0x61, 0x00, 0x00, 0x00 }, /* ā to a */ + { 0x0102, 0x41, 0x00, 0x00, 0x00 }, /* Ă to A */ + { 0x0103, 0x61, 0x00, 0x00, 0x00 }, /* ă to a */ + { 0x0104, 0x41, 0x00, 0x00, 0x00 }, /* Ą to A */ + { 0x0105, 0x61, 0x00, 0x00, 0x00 }, /* ą to a */ + { 0x0106, 0x43, 0x00, 0x00, 0x00 }, /* Ć to C */ + { 0x0107, 0x63, 0x00, 0x00, 0x00 }, /* ć to c */ + { 0x0108, 0x43, 0x68, 0x00, 0x00 }, /* Ĉ to Ch */ + { 0x0109, 0x63, 0x68, 0x00, 0x00 }, /* ĉ to ch */ + { 0x010A, 0x43, 0x00, 0x00, 0x00 }, /* Ċ to C */ + { 0x010B, 0x63, 0x00, 0x00, 0x00 }, /* ċ to c */ + { 0x010C, 0x43, 0x00, 0x00, 0x00 }, /* Č to C */ + { 0x010D, 0x63, 0x00, 0x00, 0x00 }, /* č to c */ + { 0x010E, 0x44, 0x00, 0x00, 0x00 }, /* Ď to D */ + { 0x010F, 0x64, 0x00, 0x00, 0x00 }, /* ď to d */ + { 0x0110, 0x44, 0x00, 0x00, 0x00 }, /* Đ to D */ + { 0x0111, 0x64, 0x00, 0x00, 0x00 }, /* đ to d */ + { 0x0112, 0x45, 0x00, 0x00, 0x00 }, /* Ē to E */ + { 0x0113, 0x65, 0x00, 0x00, 0x00 }, /* ē to e */ + { 0x0114, 0x45, 0x00, 0x00, 0x00 }, /* Ĕ to E */ + { 0x0115, 0x65, 0x00, 0x00, 0x00 }, /* ĕ to e */ + { 0x0116, 0x45, 0x00, 0x00, 0x00 }, /* Ė to E */ + { 0x0117, 0x65, 0x00, 0x00, 0x00 }, /* ė to e */ + { 0x0118, 0x45, 0x00, 0x00, 0x00 }, /* Ę to E */ + { 0x0119, 0x65, 0x00, 0x00, 0x00 }, /* ę to e */ + { 0x011A, 0x45, 0x00, 0x00, 0x00 }, /* Ě to E */ + { 0x011B, 0x65, 0x00, 0x00, 0x00 }, /* ě to e */ + { 0x011C, 0x47, 0x68, 0x00, 0x00 }, /* Ĝ to Gh */ + { 0x011D, 0x67, 0x68, 0x00, 0x00 }, /* ĝ to gh */ + { 0x011E, 0x47, 0x00, 0x00, 0x00 }, /* Ğ to G */ + { 0x011F, 0x67, 0x00, 0x00, 0x00 }, /* ğ to g */ + { 0x0120, 0x47, 0x00, 0x00, 0x00 }, /* Ġ to G */ + { 0x0121, 0x67, 0x00, 0x00, 0x00 }, /* ġ to g */ + { 0x0122, 0x47, 0x00, 0x00, 0x00 }, /* Ģ to G */ + { 0x0123, 0x67, 0x00, 0x00, 0x00 }, /* ģ to g */ + { 0x0124, 0x48, 0x68, 0x00, 0x00 }, /* Ĥ to Hh */ + { 0x0125, 0x68, 0x68, 0x00, 0x00 }, /* ĥ to hh */ + { 0x0126, 0x48, 0x00, 0x00, 0x00 }, /* Ħ to H */ + { 0x0127, 0x68, 0x00, 0x00, 0x00 }, /* ħ to h */ + { 0x0128, 0x49, 0x00, 0x00, 0x00 }, /* Ĩ to I */ + { 0x0129, 0x69, 0x00, 0x00, 0x00 }, /* ĩ to i */ + { 0x012A, 0x49, 0x00, 0x00, 0x00 }, /* Ī to I */ + { 0x012B, 0x69, 0x00, 0x00, 0x00 }, /* ī to i */ + { 0x012C, 0x49, 0x00, 0x00, 0x00 }, /* Ĭ to I */ + { 0x012D, 0x69, 0x00, 0x00, 0x00 }, /* ĭ to i */ + { 0x012E, 0x49, 0x00, 0x00, 0x00 }, /* Į to I */ + { 0x012F, 0x69, 0x00, 0x00, 0x00 }, /* į to i */ + { 0x0130, 0x49, 0x00, 0x00, 0x00 }, /* İ to I */ + { 0x0131, 0x69, 0x00, 0x00, 0x00 }, /* ı to i */ + { 0x0132, 0x49, 0x4A, 0x00, 0x00 }, /* IJ to IJ */ + { 0x0133, 0x69, 0x6A, 0x00, 0x00 }, /* ij to ij */ + { 0x0134, 0x4A, 0x68, 0x00, 0x00 }, /* Ĵ to Jh */ + { 0x0135, 0x6A, 0x68, 0x00, 0x00 }, /* ĵ to jh */ + { 0x0136, 0x4B, 0x00, 0x00, 0x00 }, /* Ķ to K */ + { 0x0137, 0x6B, 0x00, 0x00, 0x00 }, /* ķ to k */ + { 0x0138, 0x6B, 0x00, 0x00, 0x00 }, /* ĸ to k */ + { 0x0139, 0x4C, 0x00, 0x00, 0x00 }, /* Ĺ to L */ + { 0x013A, 0x6C, 0x00, 0x00, 0x00 }, /* ĺ to l */ + { 0x013B, 0x4C, 0x00, 0x00, 0x00 }, /* Ļ to L */ + { 0x013C, 0x6C, 0x00, 0x00, 0x00 }, /* ļ to l */ + { 0x013D, 0x4C, 0x00, 0x00, 0x00 }, /* Ľ to L */ + { 0x013E, 0x6C, 0x00, 0x00, 0x00 }, /* ľ to l */ + { 0x013F, 0x4C, 0x2E, 0x00, 0x00 }, /* Ŀ to L. */ + { 0x0140, 0x6C, 0x2E, 0x00, 0x00 }, /* ŀ to l. */ + { 0x0141, 0x4C, 0x00, 0x00, 0x00 }, /* Ł to L */ + { 0x0142, 0x6C, 0x00, 0x00, 0x00 }, /* ł to l */ + { 0x0143, 0x4E, 0x00, 0x00, 0x00 }, /* Ń to N */ + { 0x0144, 0x6E, 0x00, 0x00, 0x00 }, /* ń to n */ + { 0x0145, 0x4E, 0x00, 0x00, 0x00 }, /* Ņ to N */ + { 0x0146, 0x6E, 0x00, 0x00, 0x00 }, /* ņ to n */ + { 0x0147, 0x4E, 0x00, 0x00, 0x00 }, /* Ň to N */ + { 0x0148, 0x6E, 0x00, 0x00, 0x00 }, /* ň to n */ + { 0x0149, 0x27, 0x6E, 0x00, 0x00 }, /* ʼn to 'n */ + { 0x014A, 0x4E, 0x47, 0x00, 0x00 }, /* Ŋ to NG */ + { 0x014B, 0x6E, 0x67, 0x00, 0x00 }, /* ŋ to ng */ + { 0x014C, 0x4F, 0x00, 0x00, 0x00 }, /* Ō to O */ + { 0x014D, 0x6F, 0x00, 0x00, 0x00 }, /* ō to o */ + { 0x014E, 0x4F, 0x00, 0x00, 0x00 }, /* Ŏ to O */ + { 0x014F, 0x6F, 0x00, 0x00, 0x00 }, /* ŏ to o */ + { 0x0150, 0x4F, 0x00, 0x00, 0x00 }, /* Ő to O */ + { 0x0151, 0x6F, 0x00, 0x00, 0x00 }, /* ő to o */ + { 0x0152, 0x4F, 0x45, 0x00, 0x00 }, /* Œ to OE */ + { 0x0153, 0x6F, 0x65, 0x00, 0x00 }, /* œ to oe */ + { 0x0154, 0x52, 0x00, 0x00, 0x00 }, /* Ŕ to R */ + { 0x0155, 0x72, 0x00, 0x00, 0x00 }, /* ŕ to r */ + { 0x0156, 0x52, 0x00, 0x00, 0x00 }, /* Ŗ to R */ + { 0x0157, 0x72, 0x00, 0x00, 0x00 }, /* ŗ to r */ + { 0x0158, 0x52, 0x00, 0x00, 0x00 }, /* Ř to R */ + { 0x0159, 0x72, 0x00, 0x00, 0x00 }, /* ř to r */ + { 0x015A, 0x53, 0x00, 0x00, 0x00 }, /* Ś to S */ + { 0x015B, 0x73, 0x00, 0x00, 0x00 }, /* ś to s */ + { 0x015C, 0x53, 0x68, 0x00, 0x00 }, /* Ŝ to Sh */ + { 0x015D, 0x73, 0x68, 0x00, 0x00 }, /* ŝ to sh */ + { 0x015E, 0x53, 0x00, 0x00, 0x00 }, /* Ş to S */ + { 0x015F, 0x73, 0x00, 0x00, 0x00 }, /* ş to s */ + { 0x0160, 0x53, 0x00, 0x00, 0x00 }, /* Š to S */ + { 0x0161, 0x73, 0x00, 0x00, 0x00 }, /* š to s */ + { 0x0162, 0x54, 0x00, 0x00, 0x00 }, /* Ţ to T */ + { 0x0163, 0x74, 0x00, 0x00, 0x00 }, /* ţ to t */ + { 0x0164, 0x54, 0x00, 0x00, 0x00 }, /* Ť to T */ + { 0x0165, 0x74, 0x00, 0x00, 0x00 }, /* ť to t */ + { 0x0166, 0x54, 0x00, 0x00, 0x00 }, /* Ŧ to T */ + { 0x0167, 0x74, 0x00, 0x00, 0x00 }, /* ŧ to t */ + { 0x0168, 0x55, 0x00, 0x00, 0x00 }, /* Ũ to U */ + { 0x0169, 0x75, 0x00, 0x00, 0x00 }, /* ũ to u */ + { 0x016A, 0x55, 0x00, 0x00, 0x00 }, /* Ū to U */ + { 0x016B, 0x75, 0x00, 0x00, 0x00 }, /* ū to u */ + { 0x016C, 0x55, 0x00, 0x00, 0x00 }, /* Ŭ to U */ + { 0x016D, 0x75, 0x00, 0x00, 0x00 }, /* ŭ to u */ + { 0x016E, 0x55, 0x00, 0x00, 0x00 }, /* Ů to U */ + { 0x016F, 0x75, 0x00, 0x00, 0x00 }, /* ů to u */ + { 0x0170, 0x55, 0x00, 0x00, 0x00 }, /* Ű to U */ + { 0x0171, 0x75, 0x00, 0x00, 0x00 }, /* ű to u */ + { 0x0172, 0x55, 0x00, 0x00, 0x00 }, /* Ų to U */ + { 0x0173, 0x75, 0x00, 0x00, 0x00 }, /* ų to u */ + { 0x0174, 0x57, 0x00, 0x00, 0x00 }, /* Ŵ to W */ + { 0x0175, 0x77, 0x00, 0x00, 0x00 }, /* ŵ to w */ + { 0x0176, 0x59, 0x00, 0x00, 0x00 }, /* Ŷ to Y */ + { 0x0177, 0x79, 0x00, 0x00, 0x00 }, /* ŷ to y */ + { 0x0178, 0x59, 0x00, 0x00, 0x00 }, /* Ÿ to Y */ + { 0x0179, 0x5A, 0x00, 0x00, 0x00 }, /* Ź to Z */ + { 0x017A, 0x7A, 0x00, 0x00, 0x00 }, /* ź to z */ + { 0x017B, 0x5A, 0x00, 0x00, 0x00 }, /* Ż to Z */ + { 0x017C, 0x7A, 0x00, 0x00, 0x00 }, /* ż to z */ + { 0x017D, 0x5A, 0x00, 0x00, 0x00 }, /* Ž to Z */ + { 0x017E, 0x7A, 0x00, 0x00, 0x00 }, /* ž to z */ + { 0x017F, 0x73, 0x00, 0x00, 0x00 }, /* ſ to s */ + { 0x0192, 0x66, 0x00, 0x00, 0x00 }, /* ƒ to f */ + { 0x0218, 0x53, 0x00, 0x00, 0x00 }, /* Ș to S */ + { 0x0219, 0x73, 0x00, 0x00, 0x00 }, /* ș to s */ + { 0x021A, 0x54, 0x00, 0x00, 0x00 }, /* Ț to T */ + { 0x021B, 0x74, 0x00, 0x00, 0x00 }, /* ț to t */ + { 0x0386, 0x41, 0x00, 0x00, 0x00 }, /* Ά to A */ + { 0x0388, 0x45, 0x00, 0x00, 0x00 }, /* Έ to E */ + { 0x0389, 0x49, 0x00, 0x00, 0x00 }, /* Ή to I */ + { 0x038A, 0x49, 0x00, 0x00, 0x00 }, /* Ί to I */ + { 0x038C, 0x4f, 0x00, 0x00, 0x00 }, /* Ό to O */ + { 0x038E, 0x59, 0x00, 0x00, 0x00 }, /* Ύ to Y */ + { 0x038F, 0x4f, 0x00, 0x00, 0x00 }, /* Ώ to O */ + { 0x0390, 0x69, 0x00, 0x00, 0x00 }, /* ΐ to i */ + { 0x0391, 0x41, 0x00, 0x00, 0x00 }, /* Α to A */ + { 0x0392, 0x42, 0x00, 0x00, 0x00 }, /* Β to B */ + { 0x0393, 0x47, 0x00, 0x00, 0x00 }, /* Γ to G */ + { 0x0394, 0x44, 0x00, 0x00, 0x00 }, /* Δ to D */ + { 0x0395, 0x45, 0x00, 0x00, 0x00 }, /* Ε to E */ + { 0x0396, 0x5a, 0x00, 0x00, 0x00 }, /* Ζ to Z */ + { 0x0397, 0x49, 0x00, 0x00, 0x00 }, /* Η to I */ + { 0x0398, 0x54, 0x68, 0x00, 0x00 }, /* Θ to Th */ + { 0x0399, 0x49, 0x00, 0x00, 0x00 }, /* Ι to I */ + { 0x039A, 0x4b, 0x00, 0x00, 0x00 }, /* Κ to K */ + { 0x039B, 0x4c, 0x00, 0x00, 0x00 }, /* Λ to L */ + { 0x039C, 0x4d, 0x00, 0x00, 0x00 }, /* Μ to M */ + { 0x039D, 0x4e, 0x00, 0x00, 0x00 }, /* Ν to N */ + { 0x039E, 0x58, 0x00, 0x00, 0x00 }, /* Ξ to X */ + { 0x039F, 0x4f, 0x00, 0x00, 0x00 }, /* Ο to O */ + { 0x03A0, 0x50, 0x00, 0x00, 0x00 }, /* Π to P */ + { 0x03A1, 0x52, 0x00, 0x00, 0x00 }, /* Ρ to R */ + { 0x03A3, 0x53, 0x00, 0x00, 0x00 }, /* Σ to S */ + { 0x03A4, 0x54, 0x00, 0x00, 0x00 }, /* Τ to T */ + { 0x03A5, 0x59, 0x00, 0x00, 0x00 }, /* Υ to Y */ + { 0x03A6, 0x46, 0x00, 0x00, 0x00 }, /* Φ to F */ + { 0x03A7, 0x43, 0x68, 0x00, 0x00 }, /* Χ to Ch */ + { 0x03A8, 0x50, 0x73, 0x00, 0x00 }, /* Ψ to Ps */ + { 0x03A9, 0x4f, 0x00, 0x00, 0x00 }, /* Ω to O */ + { 0x03AA, 0x49, 0x00, 0x00, 0x00 }, /* Ϊ to I */ + { 0x03AB, 0x59, 0x00, 0x00, 0x00 }, /* Ϋ to Y */ + { 0x03AC, 0x61, 0x00, 0x00, 0x00 }, /* ά to a */ + { 0x03AD, 0x65, 0x00, 0x00, 0x00 }, /* έ to e */ + { 0x03AE, 0x69, 0x00, 0x00, 0x00 }, /* ή to i */ + { 0x03AF, 0x69, 0x00, 0x00, 0x00 }, /* ί to i */ + { 0x03B1, 0x61, 0x00, 0x00, 0x00 }, /* α to a */ + { 0x03B2, 0x62, 0x00, 0x00, 0x00 }, /* β to b */ + { 0x03B3, 0x67, 0x00, 0x00, 0x00 }, /* γ to g */ + { 0x03B4, 0x64, 0x00, 0x00, 0x00 }, /* δ to d */ + { 0x03B5, 0x65, 0x00, 0x00, 0x00 }, /* ε to e */ + { 0x03B6, 0x7a, 0x00, 0x00, 0x00 }, /* ζ to z */ + { 0x03B7, 0x69, 0x00, 0x00, 0x00 }, /* η to i */ + { 0x03B8, 0x74, 0x68, 0x00, 0x00 }, /* θ to th */ + { 0x03B9, 0x69, 0x00, 0x00, 0x00 }, /* ι to i */ + { 0x03BA, 0x6b, 0x00, 0x00, 0x00 }, /* κ to k */ + { 0x03BB, 0x6c, 0x00, 0x00, 0x00 }, /* λ to l */ + { 0x03BC, 0x6d, 0x00, 0x00, 0x00 }, /* μ to m */ + { 0x03BD, 0x6e, 0x00, 0x00, 0x00 }, /* ν to n */ + { 0x03BE, 0x78, 0x00, 0x00, 0x00 }, /* ξ to x */ + { 0x03BF, 0x6f, 0x00, 0x00, 0x00 }, /* ο to o */ + { 0x03C0, 0x70, 0x00, 0x00, 0x00 }, /* π to p */ + { 0x03C1, 0x72, 0x00, 0x00, 0x00 }, /* ρ to r */ + { 0x03C3, 0x73, 0x00, 0x00, 0x00 }, /* σ to s */ + { 0x03C4, 0x74, 0x00, 0x00, 0x00 }, /* τ to t */ + { 0x03C5, 0x79, 0x00, 0x00, 0x00 }, /* υ to y */ + { 0x03C6, 0x66, 0x00, 0x00, 0x00 }, /* φ to f */ + { 0x03C7, 0x63, 0x68, 0x00, 0x00 }, /* χ to ch */ + { 0x03C8, 0x70, 0x73, 0x00, 0x00 }, /* ψ to ps */ + { 0x03C9, 0x6f, 0x00, 0x00, 0x00 }, /* ω to o */ + { 0x03CA, 0x69, 0x00, 0x00, 0x00 }, /* ϊ to i */ + { 0x03CB, 0x79, 0x00, 0x00, 0x00 }, /* ϋ to y */ + { 0x03CC, 0x6f, 0x00, 0x00, 0x00 }, /* ό to o */ + { 0x03CD, 0x79, 0x00, 0x00, 0x00 }, /* ύ to y */ + { 0x03CE, 0x69, 0x00, 0x00, 0x00 }, /* ώ to i */ + { 0x0400, 0x45, 0x00, 0x00, 0x00 }, /* Ѐ to E */ + { 0x0401, 0x45, 0x00, 0x00, 0x00 }, /* Ё to E */ + { 0x0402, 0x44, 0x00, 0x00, 0x00 }, /* Ђ to D */ + { 0x0403, 0x47, 0x00, 0x00, 0x00 }, /* Ѓ to G */ + { 0x0404, 0x45, 0x00, 0x00, 0x00 }, /* Є to E */ + { 0x0405, 0x5a, 0x00, 0x00, 0x00 }, /* Ѕ to Z */ + { 0x0406, 0x49, 0x00, 0x00, 0x00 }, /* І to I */ + { 0x0407, 0x49, 0x00, 0x00, 0x00 }, /* Ї to I */ + { 0x0408, 0x4a, 0x00, 0x00, 0x00 }, /* Ј to J */ + { 0x0409, 0x49, 0x00, 0x00, 0x00 }, /* Љ to I */ + { 0x040A, 0x4e, 0x00, 0x00, 0x00 }, /* Њ to N */ + { 0x040B, 0x44, 0x00, 0x00, 0x00 }, /* Ћ to D */ + { 0x040C, 0x4b, 0x00, 0x00, 0x00 }, /* Ќ to K */ + { 0x040D, 0x49, 0x00, 0x00, 0x00 }, /* Ѝ to I */ + { 0x040E, 0x55, 0x00, 0x00, 0x00 }, /* Ў to U */ + { 0x040F, 0x44, 0x00, 0x00, 0x00 }, /* Џ to D */ + { 0x0410, 0x41, 0x00, 0x00, 0x00 }, /* А to A */ + { 0x0411, 0x42, 0x00, 0x00, 0x00 }, /* Б to B */ + { 0x0412, 0x56, 0x00, 0x00, 0x00 }, /* В to V */ + { 0x0413, 0x47, 0x00, 0x00, 0x00 }, /* Г to G */ + { 0x0414, 0x44, 0x00, 0x00, 0x00 }, /* Д to D */ + { 0x0415, 0x45, 0x00, 0x00, 0x00 }, /* Е to E */ + { 0x0416, 0x5a, 0x68, 0x00, 0x00 }, /* Ж to Zh */ + { 0x0417, 0x5a, 0x00, 0x00, 0x00 }, /* З to Z */ + { 0x0418, 0x49, 0x00, 0x00, 0x00 }, /* И to I */ + { 0x0419, 0x49, 0x00, 0x00, 0x00 }, /* Й to I */ + { 0x041A, 0x4b, 0x00, 0x00, 0x00 }, /* К to K */ + { 0x041B, 0x4c, 0x00, 0x00, 0x00 }, /* Л to L */ + { 0x041C, 0x4d, 0x00, 0x00, 0x00 }, /* М to M */ + { 0x041D, 0x4e, 0x00, 0x00, 0x00 }, /* Н to N */ + { 0x041E, 0x4f, 0x00, 0x00, 0x00 }, /* О to O */ + { 0x041F, 0x50, 0x00, 0x00, 0x00 }, /* П to P */ + { 0x0420, 0x52, 0x00, 0x00, 0x00 }, /* Р to R */ + { 0x0421, 0x53, 0x00, 0x00, 0x00 }, /* С to S */ + { 0x0422, 0x54, 0x00, 0x00, 0x00 }, /* Т to T */ + { 0x0423, 0x55, 0x00, 0x00, 0x00 }, /* У to U */ + { 0x0424, 0x46, 0x00, 0x00, 0x00 }, /* Ф to F */ + { 0x0425, 0x4b, 0x68, 0x00, 0x00 }, /* Х to Kh */ + { 0x0426, 0x54, 0x63, 0x00, 0x00 }, /* Ц to Tc */ + { 0x0427, 0x43, 0x68, 0x00, 0x00 }, /* Ч to Ch */ + { 0x0428, 0x53, 0x68, 0x00, 0x00 }, /* Ш to Sh */ + { 0x0429, 0x53, 0x68, 0x63, 0x68 }, /* Щ to Shch */ + { 0x042A, 0x61, 0x00, 0x00, 0x00 }, /* to A */ + { 0x042B, 0x59, 0x00, 0x00, 0x00 }, /* Ы to Y */ + { 0x042C, 0x59, 0x00, 0x00, 0x00 }, /* to Y */ + { 0x042D, 0x45, 0x00, 0x00, 0x00 }, /* Э to E */ + { 0x042E, 0x49, 0x75, 0x00, 0x00 }, /* Ю to Iu */ + { 0x042F, 0x49, 0x61, 0x00, 0x00 }, /* Я to Ia */ + { 0x0430, 0x61, 0x00, 0x00, 0x00 }, /* а to a */ + { 0x0431, 0x62, 0x00, 0x00, 0x00 }, /* б to b */ + { 0x0432, 0x76, 0x00, 0x00, 0x00 }, /* в to v */ + { 0x0433, 0x67, 0x00, 0x00, 0x00 }, /* г to g */ + { 0x0434, 0x64, 0x00, 0x00, 0x00 }, /* д to d */ + { 0x0435, 0x65, 0x00, 0x00, 0x00 }, /* е to e */ + { 0x0436, 0x7a, 0x68, 0x00, 0x00 }, /* ж to zh */ + { 0x0437, 0x7a, 0x00, 0x00, 0x00 }, /* з to z */ + { 0x0438, 0x69, 0x00, 0x00, 0x00 }, /* и to i */ + { 0x0439, 0x69, 0x00, 0x00, 0x00 }, /* й to i */ + { 0x043A, 0x6b, 0x00, 0x00, 0x00 }, /* к to k */ + { 0x043B, 0x6c, 0x00, 0x00, 0x00 }, /* л to l */ + { 0x043C, 0x6d, 0x00, 0x00, 0x00 }, /* м to m */ + { 0x043D, 0x6e, 0x00, 0x00, 0x00 }, /* н to n */ + { 0x043E, 0x6f, 0x00, 0x00, 0x00 }, /* о to o */ + { 0x043F, 0x70, 0x00, 0x00, 0x00 }, /* п to p */ + { 0x0440, 0x72, 0x00, 0x00, 0x00 }, /* р to r */ + { 0x0441, 0x73, 0x00, 0x00, 0x00 }, /* с to s */ + { 0x0442, 0x74, 0x00, 0x00, 0x00 }, /* т to t */ + { 0x0443, 0x75, 0x00, 0x00, 0x00 }, /* у to u */ + { 0x0444, 0x66, 0x00, 0x00, 0x00 }, /* ф to f */ + { 0x0445, 0x6b, 0x68, 0x00, 0x00 }, /* х to kh */ + { 0x0446, 0x74, 0x63, 0x00, 0x00 }, /* ц to tc */ + { 0x0447, 0x63, 0x68, 0x00, 0x00 }, /* ч to ch */ + { 0x0448, 0x73, 0x68, 0x00, 0x00 }, /* ш to sh */ + { 0x0449, 0x73, 0x68, 0x63, 0x68 }, /* щ to shch */ + { 0x044A, 0x61, 0x00, 0x00, 0x00 }, /* to a */ + { 0x044B, 0x79, 0x00, 0x00, 0x00 }, /* ы to y */ + { 0x044C, 0x79, 0x00, 0x00, 0x00 }, /* to y */ + { 0x044D, 0x65, 0x00, 0x00, 0x00 }, /* э to e */ + { 0x044E, 0x69, 0x75, 0x00, 0x00 }, /* ю to iu */ + { 0x044F, 0x69, 0x61, 0x00, 0x00 }, /* я to ia */ + { 0x0450, 0x65, 0x00, 0x00, 0x00 }, /* ѐ to e */ + { 0x0451, 0x65, 0x00, 0x00, 0x00 }, /* ё to e */ + { 0x0452, 0x64, 0x00, 0x00, 0x00 }, /* ђ to d */ + { 0x0453, 0x67, 0x00, 0x00, 0x00 }, /* ѓ to g */ + { 0x0454, 0x65, 0x00, 0x00, 0x00 }, /* є to e */ + { 0x0455, 0x7a, 0x00, 0x00, 0x00 }, /* ѕ to z */ + { 0x0456, 0x69, 0x00, 0x00, 0x00 }, /* і to i */ + { 0x0457, 0x69, 0x00, 0x00, 0x00 }, /* ї to i */ + { 0x0458, 0x6a, 0x00, 0x00, 0x00 }, /* ј to j */ + { 0x0459, 0x69, 0x00, 0x00, 0x00 }, /* љ to i */ + { 0x045A, 0x6e, 0x00, 0x00, 0x00 }, /* њ to n */ + { 0x045B, 0x64, 0x00, 0x00, 0x00 }, /* ћ to d */ + { 0x045C, 0x6b, 0x00, 0x00, 0x00 }, /* ќ to k */ + { 0x045D, 0x69, 0x00, 0x00, 0x00 }, /* ѝ to i */ + { 0x045E, 0x75, 0x00, 0x00, 0x00 }, /* ў to u */ + { 0x045F, 0x64, 0x00, 0x00, 0x00 }, /* џ to d */ + { 0x1E02, 0x42, 0x00, 0x00, 0x00 }, /* Ḃ to B */ + { 0x1E03, 0x62, 0x00, 0x00, 0x00 }, /* ḃ to b */ + { 0x1E0A, 0x44, 0x00, 0x00, 0x00 }, /* Ḋ to D */ + { 0x1E0B, 0x64, 0x00, 0x00, 0x00 }, /* ḋ to d */ + { 0x1E1E, 0x46, 0x00, 0x00, 0x00 }, /* Ḟ to F */ + { 0x1E1F, 0x66, 0x00, 0x00, 0x00 }, /* ḟ to f */ + { 0x1E40, 0x4D, 0x00, 0x00, 0x00 }, /* Ṁ to M */ + { 0x1E41, 0x6D, 0x00, 0x00, 0x00 }, /* ṁ to m */ + { 0x1E56, 0x50, 0x00, 0x00, 0x00 }, /* Ṗ to P */ + { 0x1E57, 0x70, 0x00, 0x00, 0x00 }, /* ṗ to p */ + { 0x1E60, 0x53, 0x00, 0x00, 0x00 }, /* Ṡ to S */ + { 0x1E61, 0x73, 0x00, 0x00, 0x00 }, /* ṡ to s */ + { 0x1E6A, 0x54, 0x00, 0x00, 0x00 }, /* Ṫ to T */ + { 0x1E6B, 0x74, 0x00, 0x00, 0x00 }, /* ṫ to t */ + { 0x1E80, 0x57, 0x00, 0x00, 0x00 }, /* Ẁ to W */ + { 0x1E81, 0x77, 0x00, 0x00, 0x00 }, /* ẁ to w */ + { 0x1E82, 0x57, 0x00, 0x00, 0x00 }, /* Ẃ to W */ + { 0x1E83, 0x77, 0x00, 0x00, 0x00 }, /* ẃ to w */ + { 0x1E84, 0x57, 0x00, 0x00, 0x00 }, /* Ẅ to W */ + { 0x1E85, 0x77, 0x00, 0x00, 0x00 }, /* ẅ to w */ + { 0x1EF2, 0x59, 0x00, 0x00, 0x00 }, /* Ỳ to Y */ + { 0x1EF3, 0x79, 0x00, 0x00, 0x00 }, /* ỳ to y */ + { 0xFB00, 0x66, 0x66, 0x00, 0x00 }, /* ff to ff */ + { 0xFB01, 0x66, 0x69, 0x00, 0x00 }, /* fi to fi */ + { 0xFB02, 0x66, 0x6C, 0x00, 0x00 }, /* fl to fl */ + { 0xFB05, 0x73, 0x74, 0x00, 0x00 }, /* ſt to st */ + { 0xFB06, 0x73, 0x74, 0x00, 0x00 }, /* st to st */ }; +static const Transliteration *spellfixFindTranslit(int c, int *pxTop){ + *pxTop = (sizeof(translit)/sizeof(translit[0])) - 1; + return translit; +} + /* ** Convert the input string from UTF-8 into pure ASCII by converting ** all non-ASCII characters to some combination of characters in the @@ -1621,23 +1716,24 @@ static unsigned char *transliterate(const unsigned char *zIn, int nIn){ zOut[nOut++] = (unsigned char)c; }else{ int xTop, xBtm, x; - xTop = sizeof(translit)/sizeof(translit[0]) - 1; + const Transliteration *tbl = spellfixFindTranslit(c, &xTop); xBtm = 0; while( xTop>=xBtm ){ x = (xTop + xBtm)/2; - if( translit[x].cFrom==c ){ - zOut[nOut++] = translit[x].cTo0; - if( translit[x].cTo1 ){ - zOut[nOut++] = translit[x].cTo1; - /* Add an extra "ch" after the "sh" for Щ and щ */ - if( c==0x0429 || c== 0x0449 ){ - zOut[nOut++] = 'c'; - zOut[nOut++] = 'h'; + if( tbl[x].cFrom==c ){ + zOut[nOut++] = tbl[x].cTo0; + if( tbl[x].cTo1 ){ + zOut[nOut++] = tbl[x].cTo1; + if( tbl[x].cTo2 ){ + zOut[nOut++] = tbl[x].cTo2; + if( tbl[x].cTo3 ){ + zOut[nOut++] = tbl[x].cTo3; + } } } c = 0; break; - }else if( translit[x].cFrom>c ){ + }else if( tbl[x].cFrom>c ){ xTop = x-1; }else{ xBtm = x+1; @@ -1668,15 +1764,22 @@ static int translen_to_charlen(const char *zIn, int nIn, int nTrans){ nOut++; if( c>=128 ){ int xTop, xBtm, x; - xTop = sizeof(translit)/sizeof(translit[0]) - 1; + const Transliteration *tbl = spellfixFindTranslit(c, &xTop); xBtm = 0; while( xTop>=xBtm ){ x = (xTop + xBtm)/2; - if( translit[x].cFrom==c ){ - if( translit[x].cTo1 ) nOut++; - if( c==0x0429 || c== 0x0449 ) nOut += 2; + if( tbl[x].cFrom==c ){ + if( tbl[x].cTo1 ){ + nOut++; + if( tbl[x].cTo2 ){ + nOut++; + if( tbl[x].cTo3 ){ + nOut++; + } + } + } break; - }else if( translit[x].cFrom>c ){ + }else if( tbl[x].cFrom>c ){ xTop = x-1; }else{ xBtm = x+1; @@ -2476,7 +2579,7 @@ static int spellfix1FilterForMatch( nPattern = (int)strlen(zPattern); if( zPattern[nPattern-1]=='*' ) nPattern--; zSql = sqlite3_mprintf( - "SELECT id, word, rank, k1" + "SELECT id, word, rank, coalesce(k1,word)" " FROM \"%w\".\"%w_vocab\"" " WHERE langid=%d AND k2>=?1 AND k2zDbName, p->zTableName, iLang @@ -2810,17 +2913,17 @@ static int spellfix1Update( if( sqlite3_value_type(argv[1])==SQLITE_NULL ){ spellfix1DbExec(&rc, db, "INSERT INTO \"%w\".\"%w_vocab\"(rank,langid,word,k1,k2) " - "VALUES(%d,%d,%Q,%Q,%Q)", + "VALUES(%d,%d,%Q,nullif(%Q,%Q),%Q)", p->zDbName, p->zTableName, - iRank, iLang, zWord, zK1, zK2 + iRank, iLang, zWord, zK1, zWord, zK2 ); }else{ newRowid = sqlite3_value_int64(argv[1]); spellfix1DbExec(&rc, db, "INSERT OR %s INTO \"%w\".\"%w_vocab\"(id,rank,langid,word,k1,k2) " - "VALUES(%lld,%d,%d,%Q,%Q,%Q)", + "VALUES(%lld,%d,%d,%Q,nullif(%Q,%Q),%Q)", zConflict, p->zDbName, p->zTableName, - newRowid, iRank, iLang, zWord, zK1, zK2 + newRowid, iRank, iLang, zWord, zK1, zWord, zK2 ); } *pRowid = sqlite3_last_insert_rowid(db); @@ -2829,9 +2932,9 @@ static int spellfix1Update( newRowid = *pRowid = sqlite3_value_int64(argv[1]); spellfix1DbExec(&rc, db, "UPDATE OR %s \"%w\".\"%w_vocab\" SET id=%lld, rank=%d, langid=%d," - " word=%Q, k1=%Q, k2=%Q WHERE id=%lld", + " word=%Q, k1=nullif(%Q,%Q), k2=%Q WHERE id=%lld", zConflict, p->zDbName, p->zTableName, newRowid, iRank, iLang, - zWord, zK1, zK2, rowid + zWord, zK1, zWord, zK2, rowid ); } sqlite3_free(zK1); diff --git a/ext/misc/sqlar.c b/ext/misc/sqlar.c new file mode 100644 index 0000000000..e812d70c99 --- /dev/null +++ b/ext/misc/sqlar.c @@ -0,0 +1,121 @@ +/* +** 2017-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. +** +****************************************************************************** +** +** Utility functions sqlar_compress() and sqlar_uncompress(). Useful +** for working with sqlar archives and used by the shell tool's built-in +** sqlar support. +*/ +#include "sqlite3ext.h" +SQLITE_EXTENSION_INIT1 +#include + +/* +** Implementation of the "sqlar_compress(X)" SQL function. +** +** If the type of X is SQLITE_BLOB, and compressing that blob using +** zlib utility function compress() yields a smaller blob, return the +** compressed blob. Otherwise, return a copy of X. +** +** SQLar uses the "zlib format" for compressed content. The zlib format +** contains a two-byte identification header and a four-byte checksum at +** the end. This is different from ZIP which uses the raw deflate format. +** +** Future enhancements to SQLar might add support for new compression formats. +** If so, those new formats will be identified by alternative headers in the +** compressed data. +*/ +static void sqlarCompressFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + assert( argc==1 ); + if( sqlite3_value_type(argv[0])==SQLITE_BLOB ){ + const Bytef *pData = sqlite3_value_blob(argv[0]); + uLong nData = sqlite3_value_bytes(argv[0]); + uLongf nOut = compressBound(nData); + Bytef *pOut; + + pOut = (Bytef*)sqlite3_malloc(nOut); + if( pOut==0 ){ + sqlite3_result_error_nomem(context); + return; + }else{ + if( Z_OK!=compress(pOut, &nOut, pData, nData) ){ + sqlite3_result_error(context, "error in compress()", -1); + }else if( nOut @@ -66,13 +68,78 @@ ** the database file containing the source table. The option ** is optional. If included, it is the name of an application-defined ** SQL function that is invoked with the URI of the file, if the file -** does not already exist on disk. +** does not already exist on disk when required by swarmvtab. +** +** NEW SYNTAX: +** +** Using the new syntax, a swarmvtab table is created with: +** +** CREATE VIRTUAL TABLE USING swarmvtab( +** [, ] +** ); +** +** where valid are: +** +** missing= +** openclose= +** maxopen= +** = +** +** The must return the same 4 columns as for a swarmvtab +** table in legacy mode. However, it may also return a 5th column - the +** "context" column. The text value returned in this column is not used +** at all by the swarmvtab implementation, except that it is passed as +** an additional argument to the two UDF functions that may be invoked +** (see below). +** +** The "missing" option, if present, specifies the name of an SQL UDF +** function to be invoked if a database file is not already present on +** disk when required by swarmvtab. If the did not provide +** a context column, it is invoked as: +** +** SELECT (); +** +** Or, if there was a context column: +** +** SELECT (, ); +** +** The "openclose" option may also specify a UDF function. This function +** is invoked right before swarmvtab opens a database, and right after +** it closes one. The first argument - or first two arguments, if +** supplied the context column - is the same as for +** the "missing" UDF. Following this, the UDF is passed integer value +** 0 before a db is opened, and 1 right after it is closed. If both +** a missing and openclose UDF is supplied, the application should expect +** the following sequence of calls (for a single database): +** +** SELECT (, , 0); +** if( db not already on disk ){ +** SELECT (, ); +** } +** ... swarmvtab uses database ... +** SELECT (, , 1); +** +** The "maxopen" option is used to configure the maximum number of +** database files swarmvtab will hold open simultaneously (default 9). +** +** If an option name begins with a ":" character, then it is assumed +** to be an SQL parameter. In this case, the specified text value is +** bound to the same variable of the before it is +** executed. It is an error of the named SQL parameter does not exist. +** For example: +** +** CREATE VIRTUAL TABLE swarm USING swarmvtab( +** 'SELECT :path || localfile, tbl, min, max FROM swarmdir', +** :path='/home/user/databases/' +** missing='missing_func' +** ); */ #include "sqlite3ext.h" SQLITE_EXTENSION_INIT1 #include #include +#include #ifndef SQLITE_OMIT_VIRTUALTABLE @@ -128,6 +195,7 @@ struct UnionSrc { /* Fields used by swarmvtab only */ char *zFile; /* Database file containing table zTab */ + char *zContext; /* Context string, if any */ int nUser; /* Current number of users */ sqlite3 *db; /* Database handle */ UnionSrc *pNextClosable; /* Next in list of closable sources */ @@ -145,8 +213,11 @@ struct UnionTab { UnionSrc *aSrc; /* Array of source tables, sorted by rowid */ /* Used by swarmvtab only */ + int bHasContext; /* Has context strings */ char *zSourceStr; /* Expected unionSourceToStr() value */ - char *zNotFoundCallback; /* UDF to invoke if file not found on open */ + sqlite3_stmt *pNotFound; /* UDF to invoke if file not found on open */ + sqlite3_stmt *pOpenClose; /* UDF to invoke on open and close */ + UnionSrc *pClosable; /* First in list of closable sources */ int nOpen; /* Current number of open sources */ int nMaxOpen; /* Maximum number of open sources */ @@ -351,6 +422,39 @@ static void unionFinalize(int *pRc, sqlite3_stmt *pStmt, char **pzErr){ } } +/* +** If an "openclose" UDF was supplied when this virtual table was created, +** invoke it now. The first argument passed is the name of the database +** file for source pSrc. The second is integer value bClose. +** +** If successful, return SQLITE_OK. Otherwise an SQLite error code. In this +** case if argument pzErr is not NULL, also set (*pzErr) to an English +** language error message. The caller is responsible for eventually freeing +** any error message using sqlite3_free(). +*/ +static int unionInvokeOpenClose( + UnionTab *pTab, + UnionSrc *pSrc, + int bClose, + char **pzErr +){ + int rc = SQLITE_OK; + if( pTab->pOpenClose ){ + sqlite3_bind_text(pTab->pOpenClose, 1, pSrc->zFile, -1, SQLITE_STATIC); + if( pTab->bHasContext ){ + sqlite3_bind_text(pTab->pOpenClose, 2, pSrc->zContext, -1, SQLITE_STATIC); + } + sqlite3_bind_int(pTab->pOpenClose, 2+pTab->bHasContext, bClose); + sqlite3_step(pTab->pOpenClose); + if( SQLITE_OK!=(rc = sqlite3_reset(pTab->pOpenClose)) ){ + if( pzErr ){ + *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(pTab->db)); + } + } + } + return rc; +} + /* ** This function is a no-op for unionvtab. For swarmvtab, it attempts to ** close open database files until at most nMax are open. An SQLite error @@ -358,13 +462,16 @@ static void unionFinalize(int *pRc, sqlite3_stmt *pStmt, char **pzErr){ */ static void unionCloseSources(UnionTab *pTab, int nMax){ while( pTab->pClosable && pTab->nOpen>nMax ){ + UnionSrc *p; UnionSrc **pp; for(pp=&pTab->pClosable; (*pp)->pNextClosable; pp=&(*pp)->pNextClosable); - assert( (*pp)->db ); - sqlite3_close((*pp)->db); - (*pp)->db = 0; + p = *pp; + assert( p->db ); + sqlite3_close(p->db); + p->db = 0; *pp = 0; pTab->nOpen--; + unionInvokeOpenClose(pTab, p, 1, 0); } } @@ -377,13 +484,19 @@ static int unionDisconnect(sqlite3_vtab *pVtab){ int i; for(i=0; inSrc; i++){ UnionSrc *pSrc = &pTab->aSrc[i]; + int bHaveSrcDb = (pSrc->db!=0); + sqlite3_close(pSrc->db); + if( bHaveSrcDb ){ + unionInvokeOpenClose(pTab, pSrc, 1, 0); + } sqlite3_free(pSrc->zDb); sqlite3_free(pSrc->zTab); sqlite3_free(pSrc->zFile); - sqlite3_close(pSrc->db); + sqlite3_free(pSrc->zContext); } + sqlite3_finalize(pTab->pNotFound); + sqlite3_finalize(pTab->pOpenClose); sqlite3_free(pTab->zSourceStr); - sqlite3_free(pTab->zNotFoundCallback); sqlite3_free(pTab->aSrc); sqlite3_free(pTab); } @@ -496,29 +609,31 @@ static int unionSourceCheck(UnionTab *pTab, char **pzErr){ return rc; } - /* ** Try to open the swarmvtab database. If initially unable, invoke the ** not-found callback UDF and then try again. */ static int unionOpenDatabaseInner(UnionTab *pTab, UnionSrc *pSrc, char **pzErr){ - int rc = SQLITE_OK; - static const int openFlags = - SQLITE_OPEN_READONLY | SQLITE_OPEN_URI; + static const int openFlags = SQLITE_OPEN_READONLY | SQLITE_OPEN_URI; + int rc; + + rc = unionInvokeOpenClose(pTab, pSrc, 0, pzErr); + if( rc!=SQLITE_OK ) return rc; + rc = sqlite3_open_v2(pSrc->zFile, &pSrc->db, openFlags, 0); if( rc==SQLITE_OK ) return rc; - if( pTab->zNotFoundCallback ){ - char *zSql = sqlite3_mprintf("SELECT \"%w\"(%Q);", - pTab->zNotFoundCallback, pSrc->zFile); + if( pTab->pNotFound ){ sqlite3_close(pSrc->db); pSrc->db = 0; - if( zSql==0 ){ - *pzErr = sqlite3_mprintf("out of memory"); - return SQLITE_NOMEM; + sqlite3_bind_text(pTab->pNotFound, 1, pSrc->zFile, -1, SQLITE_STATIC); + if( pTab->bHasContext ){ + sqlite3_bind_text(pTab->pNotFound, 2, pSrc->zContext, -1, SQLITE_STATIC); + } + sqlite3_step(pTab->pNotFound); + if( SQLITE_OK!=(rc = sqlite3_reset(pTab->pNotFound)) ){ + *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(pTab->db)); + return rc; } - rc = sqlite3_exec(pTab->db, zSql, 0, 0, pzErr); - sqlite3_free(zSql); - if( rc ) return rc; rc = sqlite3_open_v2(pSrc->zFile, &pSrc->db, openFlags, 0); } if( rc!=SQLITE_OK ){ @@ -572,6 +687,7 @@ static int unionOpenDatabase(UnionTab *pTab, int iSrc, char **pzErr){ }else{ sqlite3_close(pSrc->db); pSrc->db = 0; + unionInvokeOpenClose(pTab, pSrc, 1, 0); } } @@ -627,6 +743,132 @@ static int unionFinalizeCsrStmt(UnionCsr *pCsr){ return rc; } +/* +** Return true if the argument is a space, tab, CR or LF character. +*/ +static int union_isspace(char c){ + return (c==' ' || c=='\n' || c=='\r' || c=='\t'); +} + +/* +** Return true if the argument is an alphanumeric character in the +** ASCII range. +*/ +static int union_isidchar(char c){ + return ((c>='a' && c<='z') || (c>='A' && c<'Z') || (c>='0' && c<='9')); +} + +/* +** This function is called to handle all arguments following the first +** (the SQL statement) passed to a swarmvtab (not unionvtab) CREATE +** VIRTUAL TABLE statement. It may bind parameters to the SQL statement +** or configure members of the UnionTab object passed as the second +** argument. +** +** Refer to header comments at the top of this file for a description +** of the arguments parsed. +** +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwise, if an error occurs, *pRc is set to an SQLite error +** code. In this case *pzErr may be set to point to a buffer containing +** an English language error message. It is the responsibility of the +** caller to eventually free the buffer using sqlite3_free(). +*/ +static void unionConfigureVtab( + int *pRc, /* IN/OUT: Error code */ + UnionTab *pTab, /* Table to configure */ + sqlite3_stmt *pStmt, /* SQL statement to find sources */ + int nArg, /* Number of entries in azArg[] array */ + const char * const *azArg, /* Array of arguments to consider */ + char **pzErr /* OUT: Error message */ +){ + int rc = *pRc; + int i; + if( rc==SQLITE_OK ){ + pTab->bHasContext = (sqlite3_column_count(pStmt)>4); + } + for(i=0; rc==SQLITE_OK && inMaxOpen = atoi(zVal); + if( pTab->nMaxOpen<=0 ){ + *pzErr = sqlite3_mprintf("swarmvtab: illegal maxopen value"); + rc = SQLITE_ERROR; + } + }else if( nOpt==7 && 0==sqlite3_strnicmp(zOpt, "missing", 7) ){ + if( pTab->pNotFound ){ + *pzErr = sqlite3_mprintf( + "swarmvtab: duplicate \"missing\" option"); + rc = SQLITE_ERROR; + }else{ + pTab->pNotFound = unionPreparePrintf(&rc, pzErr, pTab->db, + "SELECT \"%w\"(?%s)", zVal, pTab->bHasContext ? ",?" : "" + ); + } + }else if( nOpt==9 && 0==sqlite3_strnicmp(zOpt, "openclose", 9) ){ + if( pTab->pOpenClose ){ + *pzErr = sqlite3_mprintf( + "swarmvtab: duplicate \"openclose\" option"); + rc = SQLITE_ERROR; + }else{ + pTab->pOpenClose = unionPreparePrintf(&rc, pzErr, pTab->db, + "SELECT \"%w\"(?,?%s)", zVal, pTab->bHasContext ? ",?" : "" + ); + } + }else{ + *pzErr = sqlite3_mprintf("swarmvtab: unrecognized option: %s",zOpt); + rc = SQLITE_ERROR; + } + sqlite3_free(zVal); + } + }else{ + if( i==0 && nArg==1 ){ + pTab->pNotFound = unionPreparePrintf(&rc, pzErr, pTab->db, + "SELECT \"%w\"(?)", zArg + ); + }else{ + *pzErr = sqlite3_mprintf( "swarmvtab: parse error: %s", azArg[i]); + rc = SQLITE_ERROR; + } + } + sqlite3_free(zArg); + } + } + *pRc = rc; +} + /* ** xConnect/xCreate method. ** @@ -654,7 +896,7 @@ static int unionConnect( /* unionvtab tables may only be created in the temp schema */ *pzErr = sqlite3_mprintf("%s tables must be created in TEMP schema", zVtab); rc = SQLITE_ERROR; - }else if( argc!=4 && argc!=5 ){ + }else if( argc<4 || (argc>4 && bSwarm==0) ){ *pzErr = sqlite3_mprintf("wrong number of arguments for %s", zVtab); rc = SQLITE_ERROR; }else{ @@ -673,6 +915,17 @@ static int unionConnect( /* Allocate the UnionTab structure */ pTab = unionMalloc(&rc, sizeof(UnionTab)); + if( pTab ){ + assert( rc==SQLITE_OK ); + pTab->db = db; + pTab->bSwarm = bSwarm; + pTab->nMaxOpen = SWARMVTAB_MAX_OPEN; + } + + /* Parse other CVT arguments, if any */ + if( bSwarm ){ + unionConfigureVtab(&rc, pTab, pStmt, argc-4, &argv[4], pzErr); + } /* Iterate through the rows returned by the SQL statement specified ** as an argument to the CREATE VIRTUAL TABLE statement. */ @@ -715,17 +968,15 @@ static int unionConnect( }else{ pSrc->zDb = unionStrdup(&rc, zDb); } + if( pTab->bHasContext ){ + const char *zContext = (const char*)sqlite3_column_text(pStmt, 4); + pSrc->zContext = unionStrdup(&rc, zContext); + } } } unionFinalize(&rc, pStmt, pzErr); pStmt = 0; - /* Capture the not-found callback UDF name */ - if( rc==SQLITE_OK && argc>=5 ){ - pTab->zNotFoundCallback = unionStrdup(&rc, argv[4]); - unionDequote(pTab->zNotFoundCallback); - } - /* It is an error if the SELECT statement returned zero rows. If only ** because there is no way to determine the schema of the virtual ** table in this case. */ @@ -738,9 +989,6 @@ static int unionConnect( ** compatible schemas. For swarmvtab, attach the first database and ** check that the first table is a rowid table only. */ if( rc==SQLITE_OK ){ - pTab->db = db; - pTab->bSwarm = bSwarm; - pTab->nMaxOpen = SWARMVTAB_MAX_OPEN; if( bSwarm ){ rc = unionOpenDatabase(pTab, 0, pzErr); }else{ diff --git a/ext/misc/zipfile.c b/ext/misc/zipfile.c new file mode 100644 index 0000000000..a400ae30e1 --- /dev/null +++ b/ext/misc/zipfile.c @@ -0,0 +1,2123 @@ +/* +** 2017-12-26 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file implements a virtual table for reading and writing ZIP archive +** files. +** +** Usage example: +** +** SELECT name, sz, datetime(mtime,'unixepoch') FROM zipfile($filename); +** +** Current limitations: +** +** * No support for encryption +** * No support for ZIP archives spanning multiple files +** * No support for zip64 extensions +** * Only the "inflate/deflate" (zlib) compression method is supported +*/ +#include "sqlite3ext.h" +SQLITE_EXTENSION_INIT1 +#include +#include +#include + +#include + +#ifndef SQLITE_OMIT_VIRTUALTABLE + +#ifndef SQLITE_AMALGAMATION + +typedef sqlite3_int64 i64; +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned long u32; +#define MIN(a,b) ((a)<(b) ? (a) : (b)) + +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +#elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) +#else +# define ALWAYS(X) (X) +# define NEVER(X) (X) +#endif + +#endif /* SQLITE_AMALGAMATION */ + +/* +** Definitions for mode bitmasks S_IFDIR, S_IFREG and S_IFLNK. +** +** In some ways it would be better to obtain these values from system +** header files. But, the dependency is undesirable and (a) these +** have been stable for decades, (b) the values are part of POSIX and +** are also made explicit in [man stat], and (c) are part of the +** file format for zip archives. +*/ +#ifndef S_IFDIR +# define S_IFDIR 0040000 +#endif +#ifndef S_IFREG +# define S_IFREG 0100000 +#endif +#ifndef S_IFLNK +# define S_IFLNK 0120000 +#endif + +static const char ZIPFILE_SCHEMA[] = + "CREATE TABLE y(" + "name PRIMARY KEY," /* 0: Name of file in zip archive */ + "mode," /* 1: POSIX mode for file */ + "mtime," /* 2: Last modification time (secs since 1970)*/ + "sz," /* 3: Size of object */ + "rawdata," /* 4: Raw data */ + "data," /* 5: Uncompressed data */ + "method," /* 6: Compression method (integer) */ + "z HIDDEN" /* 7: Name of zip file */ + ") WITHOUT ROWID;"; + +#define ZIPFILE_F_COLUMN_IDX 7 /* Index of column "file" in the above */ +#define ZIPFILE_BUFFER_SIZE (64*1024) + + +/* +** Magic numbers used to read and write zip files. +** +** ZIPFILE_NEWENTRY_MADEBY: +** Use this value for the "version-made-by" field in new zip file +** entries. The upper byte indicates "unix", and the lower byte +** indicates that the zip file matches pkzip specification 3.0. +** This is what info-zip seems to do. +** +** ZIPFILE_NEWENTRY_REQUIRED: +** Value for "version-required-to-extract" field of new entries. +** Version 2.0 is required to support folders and deflate compression. +** +** ZIPFILE_NEWENTRY_FLAGS: +** Value for "general-purpose-bit-flags" field of new entries. Bit +** 11 means "utf-8 filename and comment". +** +** ZIPFILE_SIGNATURE_CDS: +** First 4 bytes of a valid CDS record. +** +** ZIPFILE_SIGNATURE_LFH: +** First 4 bytes of a valid LFH record. +** +** ZIPFILE_SIGNATURE_EOCD +** First 4 bytes of a valid EOCD record. +*/ +#define ZIPFILE_EXTRA_TIMESTAMP 0x5455 +#define ZIPFILE_NEWENTRY_MADEBY ((3<<8) + 30) +#define ZIPFILE_NEWENTRY_REQUIRED 20 +#define ZIPFILE_NEWENTRY_FLAGS 0x800 +#define ZIPFILE_SIGNATURE_CDS 0x02014b50 +#define ZIPFILE_SIGNATURE_LFH 0x04034b50 +#define ZIPFILE_SIGNATURE_EOCD 0x06054b50 + +/* +** The sizes of the fixed-size part of each of the three main data +** structures in a zip archive. +*/ +#define ZIPFILE_LFH_FIXED_SZ 30 +#define ZIPFILE_EOCD_FIXED_SZ 22 +#define ZIPFILE_CDS_FIXED_SZ 46 + +/* +*** 4.3.16 End of central directory record: +*** +*** end of central dir signature 4 bytes (0x06054b50) +*** number of this disk 2 bytes +*** number of the disk with the +*** start of the central directory 2 bytes +*** total number of entries in the +*** central directory on this disk 2 bytes +*** total number of entries in +*** the central directory 2 bytes +*** size of the central directory 4 bytes +*** offset of start of central +*** directory with respect to +*** the starting disk number 4 bytes +*** .ZIP file comment length 2 bytes +*** .ZIP file comment (variable size) +*/ +typedef struct ZipfileEOCD ZipfileEOCD; +struct ZipfileEOCD { + u16 iDisk; + u16 iFirstDisk; + u16 nEntry; + u16 nEntryTotal; + u32 nSize; + u32 iOffset; +}; + +/* +*** 4.3.12 Central directory structure: +*** +*** ... +*** +*** central file header signature 4 bytes (0x02014b50) +*** version made by 2 bytes +*** version needed to extract 2 bytes +*** general purpose bit flag 2 bytes +*** compression method 2 bytes +*** last mod file time 2 bytes +*** last mod file date 2 bytes +*** crc-32 4 bytes +*** compressed size 4 bytes +*** uncompressed size 4 bytes +*** file name length 2 bytes +*** extra field length 2 bytes +*** file comment length 2 bytes +*** disk number start 2 bytes +*** internal file attributes 2 bytes +*** external file attributes 4 bytes +*** relative offset of local header 4 bytes +*/ +typedef struct ZipfileCDS ZipfileCDS; +struct ZipfileCDS { + u16 iVersionMadeBy; + u16 iVersionExtract; + u16 flags; + u16 iCompression; + u16 mTime; + u16 mDate; + u32 crc32; + u32 szCompressed; + u32 szUncompressed; + u16 nFile; + u16 nExtra; + u16 nComment; + u16 iDiskStart; + u16 iInternalAttr; + u32 iExternalAttr; + u32 iOffset; + char *zFile; /* Filename (sqlite3_malloc()) */ +}; + +/* +*** 4.3.7 Local file header: +*** +*** local file header signature 4 bytes (0x04034b50) +*** version needed to extract 2 bytes +*** general purpose bit flag 2 bytes +*** compression method 2 bytes +*** last mod file time 2 bytes +*** last mod file date 2 bytes +*** crc-32 4 bytes +*** compressed size 4 bytes +*** uncompressed size 4 bytes +*** file name length 2 bytes +*** extra field length 2 bytes +*** +*/ +typedef struct ZipfileLFH ZipfileLFH; +struct ZipfileLFH { + u16 iVersionExtract; + u16 flags; + u16 iCompression; + u16 mTime; + u16 mDate; + u32 crc32; + u32 szCompressed; + u32 szUncompressed; + u16 nFile; + u16 nExtra; +}; + +typedef struct ZipfileEntry ZipfileEntry; +struct ZipfileEntry { + ZipfileCDS cds; /* Parsed CDS record */ + u32 mUnixTime; /* Modification time, in UNIX format */ + u8 *aExtra; /* cds.nExtra+cds.nComment bytes of extra data */ + i64 iDataOff; /* Offset to data in file (if aData==0) */ + u8 *aData; /* cds.szCompressed bytes of compressed data */ + ZipfileEntry *pNext; /* Next element in in-memory CDS */ +}; + +/* +** Cursor type for zipfile tables. +*/ +typedef struct ZipfileCsr ZipfileCsr; +struct ZipfileCsr { + sqlite3_vtab_cursor base; /* Base class - must be first */ + i64 iId; /* Cursor ID */ + u8 bEof; /* True when at EOF */ + u8 bNoop; /* If next xNext() call is no-op */ + + /* Used outside of write transactions */ + FILE *pFile; /* Zip file */ + i64 iNextOff; /* Offset of next record in central directory */ + ZipfileEOCD eocd; /* Parse of central directory record */ + + ZipfileEntry *pFreeEntry; /* Free this list when cursor is closed or reset */ + ZipfileEntry *pCurrent; /* Current entry */ + ZipfileCsr *pCsrNext; /* Next cursor on same virtual table */ +}; + +typedef struct ZipfileTab ZipfileTab; +struct ZipfileTab { + sqlite3_vtab base; /* Base class - must be first */ + char *zFile; /* Zip file this table accesses (may be NULL) */ + u8 *aBuffer; /* Temporary buffer used for various tasks */ + + ZipfileCsr *pCsrList; /* List of cursors */ + i64 iNextCsrid; + + /* The following are used by write transactions only */ + ZipfileEntry *pFirstEntry; /* Linked list of all files (if pWriteFd!=0) */ + ZipfileEntry *pLastEntry; /* Last element in pFirstEntry list */ + FILE *pWriteFd; /* File handle open on zip archive */ + i64 szCurrent; /* Current size of zip archive */ + i64 szOrig; /* Size of archive at start of transaction */ +}; + +/* +** Set the error message contained in context ctx to the results of +** vprintf(zFmt, ...). +*/ +static void zipfileCtxErrorMsg(sqlite3_context *ctx, const char *zFmt, ...){ + char *zMsg = 0; + va_list ap; + va_start(ap, zFmt); + zMsg = sqlite3_vmprintf(zFmt, ap); + sqlite3_result_error(ctx, zMsg, -1); + sqlite3_free(zMsg); + va_end(ap); +} + +/* +** If string zIn is quoted, dequote it in place. Otherwise, if the string +** is not quoted, do nothing. +*/ +static void zipfileDequote(char *zIn){ + char q = zIn[0]; + if( q=='"' || q=='\'' || q=='`' || q=='[' ){ + int iIn = 1; + int iOut = 0; + if( q=='[' ) q = ']'; + while( ALWAYS(zIn[iIn]) ){ + char c = zIn[iIn++]; + if( c==q && zIn[iIn++]!=q ) break; + zIn[iOut++] = c; + } + zIn[iOut] = '\0'; + } +} + +/* +** Construct a new ZipfileTab virtual table object. +** +** argv[0] -> module name ("zipfile") +** argv[1] -> database name +** argv[2] -> table name +** argv[...] -> "column name" and other module argument fields. +*/ +static int zipfileConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + int nByte = sizeof(ZipfileTab) + ZIPFILE_BUFFER_SIZE; + int nFile = 0; + const char *zFile = 0; + ZipfileTab *pNew = 0; + int rc; + + /* If the table name is not "zipfile", require that the argument be + ** specified. This stops zipfile tables from being created as: + ** + ** CREATE VIRTUAL TABLE zzz USING zipfile(); + ** + ** It does not prevent: + ** + ** CREATE VIRTUAL TABLE zipfile USING zipfile(); + */ + assert( 0==sqlite3_stricmp(argv[0], "zipfile") ); + if( (0!=sqlite3_stricmp(argv[2], "zipfile") && argc<4) || argc>4 ){ + *pzErr = sqlite3_mprintf("zipfile constructor requires one argument"); + return SQLITE_ERROR; + } + + if( argc>3 ){ + zFile = argv[3]; + nFile = (int)strlen(zFile)+1; + } + + rc = sqlite3_declare_vtab(db, ZIPFILE_SCHEMA); + if( rc==SQLITE_OK ){ + pNew = (ZipfileTab*)sqlite3_malloc(nByte+nFile); + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, nByte+nFile); + pNew->aBuffer = (u8*)&pNew[1]; + if( zFile ){ + pNew->zFile = (char*)&pNew->aBuffer[ZIPFILE_BUFFER_SIZE]; + memcpy(pNew->zFile, zFile, nFile); + zipfileDequote(pNew->zFile); + } + } + *ppVtab = (sqlite3_vtab*)pNew; + return rc; +} + +/* +** Free the ZipfileEntry structure indicated by the only argument. +*/ +static void zipfileEntryFree(ZipfileEntry *p){ + if( p ){ + sqlite3_free(p->cds.zFile); + sqlite3_free(p); + } +} + +/* +** Release resources that should be freed at the end of a write +** transaction. +*/ +static void zipfileCleanupTransaction(ZipfileTab *pTab){ + ZipfileEntry *pEntry; + ZipfileEntry *pNext; + + if( pTab->pWriteFd ){ + fclose(pTab->pWriteFd); + pTab->pWriteFd = 0; + } + for(pEntry=pTab->pFirstEntry; pEntry; pEntry=pNext){ + pNext = pEntry->pNext; + zipfileEntryFree(pEntry); + } + pTab->pFirstEntry = 0; + pTab->pLastEntry = 0; + pTab->szCurrent = 0; + pTab->szOrig = 0; +} + +/* +** This method is the destructor for zipfile vtab objects. +*/ +static int zipfileDisconnect(sqlite3_vtab *pVtab){ + zipfileCleanupTransaction((ZipfileTab*)pVtab); + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Constructor for a new ZipfileCsr object. +*/ +static int zipfileOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCsr){ + ZipfileTab *pTab = (ZipfileTab*)p; + ZipfileCsr *pCsr; + pCsr = sqlite3_malloc(sizeof(*pCsr)); + *ppCsr = (sqlite3_vtab_cursor*)pCsr; + if( pCsr==0 ){ + return SQLITE_NOMEM; + } + memset(pCsr, 0, sizeof(*pCsr)); + pCsr->iId = ++pTab->iNextCsrid; + pCsr->pCsrNext = pTab->pCsrList; + pTab->pCsrList = pCsr; + return SQLITE_OK; +} + +/* +** Reset a cursor back to the state it was in when first returned +** by zipfileOpen(). +*/ +static void zipfileResetCursor(ZipfileCsr *pCsr){ + ZipfileEntry *p; + ZipfileEntry *pNext; + + pCsr->bEof = 0; + if( pCsr->pFile ){ + fclose(pCsr->pFile); + pCsr->pFile = 0; + zipfileEntryFree(pCsr->pCurrent); + pCsr->pCurrent = 0; + } + + for(p=pCsr->pFreeEntry; p; p=pNext){ + pNext = p->pNext; + zipfileEntryFree(p); + } +} + +/* +** Destructor for an ZipfileCsr. +*/ +static int zipfileClose(sqlite3_vtab_cursor *cur){ + ZipfileCsr *pCsr = (ZipfileCsr*)cur; + ZipfileTab *pTab = (ZipfileTab*)(pCsr->base.pVtab); + ZipfileCsr **pp; + zipfileResetCursor(pCsr); + + /* Remove this cursor from the ZipfileTab.pCsrList list. */ + for(pp=&pTab->pCsrList; *pp!=pCsr; pp=&((*pp)->pCsrNext)); + *pp = pCsr->pCsrNext; + + sqlite3_free(pCsr); + return SQLITE_OK; +} + +/* +** Set the error message for the virtual table associated with cursor +** pCsr to the results of vprintf(zFmt, ...). +*/ +static void zipfileSetErrmsg(ZipfileCsr *pCsr, const char *zFmt, ...){ + va_list ap; + va_start(ap, zFmt); + pCsr->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap); + va_end(ap); +} + +/* +** Read nRead bytes of data from offset iOff of file pFile into buffer +** aRead[]. Return SQLITE_OK if successful, or an SQLite error code +** otherwise. +** +** If an error does occur, output variable (*pzErrmsg) may be set to point +** to an English language error message. It is the responsibility of the +** caller to eventually free this buffer using +** sqlite3_free(). +*/ +static int zipfileReadData( + FILE *pFile, /* Read from this file */ + u8 *aRead, /* Read into this buffer */ + int nRead, /* Number of bytes to read */ + i64 iOff, /* Offset to read from */ + char **pzErrmsg /* OUT: Error message (from sqlite3_malloc) */ +){ + size_t n; + fseek(pFile, (long)iOff, SEEK_SET); + n = fread(aRead, 1, nRead, pFile); + if( (int)n!=nRead ){ + *pzErrmsg = sqlite3_mprintf("error in fread()"); + return SQLITE_ERROR; + } + return SQLITE_OK; +} + +static int zipfileAppendData( + ZipfileTab *pTab, + const u8 *aWrite, + int nWrite +){ + size_t n; + fseek(pTab->pWriteFd, (long)pTab->szCurrent, SEEK_SET); + n = fwrite(aWrite, 1, nWrite, pTab->pWriteFd); + if( (int)n!=nWrite ){ + pTab->base.zErrMsg = sqlite3_mprintf("error in fwrite()"); + return SQLITE_ERROR; + } + pTab->szCurrent += nWrite; + return SQLITE_OK; +} + +/* +** Read and return a 16-bit little-endian unsigned integer from buffer aBuf. +*/ +static u16 zipfileGetU16(const u8 *aBuf){ + return (aBuf[1] << 8) + aBuf[0]; +} + +/* +** Read and return a 32-bit little-endian unsigned integer from buffer aBuf. +*/ +static u32 zipfileGetU32(const u8 *aBuf){ + return ((u32)(aBuf[3]) << 24) + + ((u32)(aBuf[2]) << 16) + + ((u32)(aBuf[1]) << 8) + + ((u32)(aBuf[0]) << 0); +} + +/* +** Write a 16-bit little endiate integer into buffer aBuf. +*/ +static void zipfilePutU16(u8 *aBuf, u16 val){ + aBuf[0] = val & 0xFF; + aBuf[1] = (val>>8) & 0xFF; +} + +/* +** Write a 32-bit little endiate integer into buffer aBuf. +*/ +static void zipfilePutU32(u8 *aBuf, u32 val){ + aBuf[0] = val & 0xFF; + aBuf[1] = (val>>8) & 0xFF; + aBuf[2] = (val>>16) & 0xFF; + aBuf[3] = (val>>24) & 0xFF; +} + +#define zipfileRead32(aBuf) ( aBuf+=4, zipfileGetU32(aBuf-4) ) +#define zipfileRead16(aBuf) ( aBuf+=2, zipfileGetU16(aBuf-2) ) + +#define zipfileWrite32(aBuf,val) { zipfilePutU32(aBuf,val); aBuf+=4; } +#define zipfileWrite16(aBuf,val) { zipfilePutU16(aBuf,val); aBuf+=2; } + +/* +** Magic numbers used to read CDS records. +*/ +#define ZIPFILE_CDS_NFILE_OFF 28 +#define ZIPFILE_CDS_SZCOMPRESSED_OFF 20 + +/* +** Decode the CDS record in buffer aBuf into (*pCDS). Return SQLITE_ERROR +** if the record is not well-formed, or SQLITE_OK otherwise. +*/ +static int zipfileReadCDS(u8 *aBuf, ZipfileCDS *pCDS){ + u8 *aRead = aBuf; + u32 sig = zipfileRead32(aRead); + int rc = SQLITE_OK; + if( sig!=ZIPFILE_SIGNATURE_CDS ){ + rc = SQLITE_ERROR; + }else{ + pCDS->iVersionMadeBy = zipfileRead16(aRead); + pCDS->iVersionExtract = zipfileRead16(aRead); + pCDS->flags = zipfileRead16(aRead); + pCDS->iCompression = zipfileRead16(aRead); + pCDS->mTime = zipfileRead16(aRead); + pCDS->mDate = zipfileRead16(aRead); + pCDS->crc32 = zipfileRead32(aRead); + pCDS->szCompressed = zipfileRead32(aRead); + pCDS->szUncompressed = zipfileRead32(aRead); + assert( aRead==&aBuf[ZIPFILE_CDS_NFILE_OFF] ); + pCDS->nFile = zipfileRead16(aRead); + pCDS->nExtra = zipfileRead16(aRead); + pCDS->nComment = zipfileRead16(aRead); + pCDS->iDiskStart = zipfileRead16(aRead); + pCDS->iInternalAttr = zipfileRead16(aRead); + pCDS->iExternalAttr = zipfileRead32(aRead); + pCDS->iOffset = zipfileRead32(aRead); + assert( aRead==&aBuf[ZIPFILE_CDS_FIXED_SZ] ); + } + + return rc; +} + +/* +** Decode the LFH record in buffer aBuf into (*pLFH). Return SQLITE_ERROR +** if the record is not well-formed, or SQLITE_OK otherwise. +*/ +static int zipfileReadLFH( + u8 *aBuffer, + ZipfileLFH *pLFH +){ + u8 *aRead = aBuffer; + int rc = SQLITE_OK; + + u32 sig = zipfileRead32(aRead); + if( sig!=ZIPFILE_SIGNATURE_LFH ){ + rc = SQLITE_ERROR; + }else{ + pLFH->iVersionExtract = zipfileRead16(aRead); + pLFH->flags = zipfileRead16(aRead); + pLFH->iCompression = zipfileRead16(aRead); + pLFH->mTime = zipfileRead16(aRead); + pLFH->mDate = zipfileRead16(aRead); + pLFH->crc32 = zipfileRead32(aRead); + pLFH->szCompressed = zipfileRead32(aRead); + pLFH->szUncompressed = zipfileRead32(aRead); + pLFH->nFile = zipfileRead16(aRead); + pLFH->nExtra = zipfileRead16(aRead); + } + return rc; +} + + +/* +** Buffer aExtra (size nExtra bytes) contains zip archive "extra" fields. +** Scan through this buffer to find an "extra-timestamp" field. If one +** exists, extract the 32-bit modification-timestamp from it and store +** the value in output parameter *pmTime. +** +** Zero is returned if no extra-timestamp record could be found (and so +** *pmTime is left unchanged), or non-zero otherwise. +** +** The general format of an extra field is: +** +** Header ID 2 bytes +** Data Size 2 bytes +** Data N bytes +*/ +static int zipfileScanExtra(u8 *aExtra, int nExtra, u32 *pmTime){ + int ret = 0; + u8 *p = aExtra; + u8 *pEnd = &aExtra[nExtra]; + + while( p modtime is present */ + *pmTime = zipfileGetU32(&p[1]); + ret = 1; + } + break; + } + } + + p += nByte; + } + return ret; +} + +/* +** Convert the standard MS-DOS timestamp stored in the mTime and mDate +** fields of the CDS structure passed as the only argument to a 32-bit +** UNIX seconds-since-the-epoch timestamp. Return the result. +** +** "Standard" MS-DOS time format: +** +** File modification time: +** Bits 00-04: seconds divided by 2 +** Bits 05-10: minute +** Bits 11-15: hour +** File modification date: +** Bits 00-04: day +** Bits 05-08: month (1-12) +** Bits 09-15: years from 1980 +** +** https://msdn.microsoft.com/en-us/library/9kkf9tah.aspx +*/ +static u32 zipfileMtime(ZipfileCDS *pCDS){ + int Y = (1980 + ((pCDS->mDate >> 9) & 0x7F)); + int M = ((pCDS->mDate >> 5) & 0x0F); + int D = (pCDS->mDate & 0x1F); + int B = -13; + + int sec = (pCDS->mTime & 0x1F)*2; + int min = (pCDS->mTime >> 5) & 0x3F; + int hr = (pCDS->mTime >> 11) & 0x1F; + i64 JD; + + /* JD = INT(365.25 * (Y+4716)) + INT(30.6001 * (M+1)) + D + B - 1524.5 */ + + /* Calculate the JD in seconds for noon on the day in question */ + if( M<3 ){ + Y = Y-1; + M = M+12; + } + JD = (i64)(24*60*60) * ( + (int)(365.25 * (Y + 4716)) + + (int)(30.6001 * (M + 1)) + + D + B - 1524 + ); + + /* Correct the JD for the time within the day */ + JD += (hr-12) * 3600 + min * 60 + sec; + + /* Convert JD to unix timestamp (the JD epoch is 2440587.5) */ + return (u32)(JD - (i64)(24405875) * 24*60*6); +} + +/* +** The opposite of zipfileMtime(). This function populates the mTime and +** mDate fields of the CDS structure passed as the first argument according +** to the UNIX timestamp value passed as the second. +*/ +static void zipfileMtimeToDos(ZipfileCDS *pCds, u32 mUnixTime){ + /* Convert unix timestamp to JD (2440588 is noon on 1/1/1970) */ + i64 JD = (i64)2440588 + mUnixTime / (24*60*60); + + int A, B, C, D, E; + int yr, mon, day; + int hr, min, sec; + + A = (int)((JD - 1867216.25)/36524.25); + A = (int)(JD + 1 + A - (A/4)); + B = A + 1524; + C = (int)((B - 122.1)/365.25); + D = (36525*(C&32767))/100; + E = (int)((B-D)/30.6001); + + day = B - D - (int)(30.6001*E); + mon = (E<14 ? E-1 : E-13); + yr = mon>2 ? C-4716 : C-4715; + + hr = (mUnixTime % (24*60*60)) / (60*60); + min = (mUnixTime % (60*60)) / 60; + sec = (mUnixTime % 60); + + pCds->mDate = (u16)(day + (mon << 5) + ((yr-1980) << 9)); + pCds->mTime = (u16)(sec/2 + (min<<5) + (hr<<11)); + + assert( mUnixTime<315507600 + || mUnixTime==zipfileMtime(pCds) + || ((mUnixTime % 2) && mUnixTime-1==zipfileMtime(pCds)) + /* || (mUnixTime % 2) */ + ); +} + +/* +** If aBlob is not NULL, then it is a pointer to a buffer (nBlob bytes in +** size) containing an entire zip archive image. Or, if aBlob is NULL, +** then pFile is a file-handle open on a zip file. In either case, this +** function creates a ZipfileEntry object based on the zip archive entry +** for which the CDS record is at offset iOff. +** +** If successful, SQLITE_OK is returned and (*ppEntry) set to point to +** the new object. Otherwise, an SQLite error code is returned and the +** final value of (*ppEntry) undefined. +*/ +static int zipfileGetEntry( + ZipfileTab *pTab, /* Store any error message here */ + const u8 *aBlob, /* Pointer to in-memory file image */ + int nBlob, /* Size of aBlob[] in bytes */ + FILE *pFile, /* If aBlob==0, read from this file */ + i64 iOff, /* Offset of CDS record */ + ZipfileEntry **ppEntry /* OUT: Pointer to new object */ +){ + u8 *aRead; + char **pzErr = &pTab->base.zErrMsg; + int rc = SQLITE_OK; + + if( aBlob==0 ){ + aRead = pTab->aBuffer; + rc = zipfileReadData(pFile, aRead, ZIPFILE_CDS_FIXED_SZ, iOff, pzErr); + }else{ + aRead = (u8*)&aBlob[iOff]; + } + + if( rc==SQLITE_OK ){ + int nAlloc; + ZipfileEntry *pNew; + + int nFile = zipfileGetU16(&aRead[ZIPFILE_CDS_NFILE_OFF]); + int nExtra = zipfileGetU16(&aRead[ZIPFILE_CDS_NFILE_OFF+2]); + nExtra += zipfileGetU16(&aRead[ZIPFILE_CDS_NFILE_OFF+4]); + + nAlloc = sizeof(ZipfileEntry) + nExtra; + if( aBlob ){ + nAlloc += zipfileGetU32(&aRead[ZIPFILE_CDS_SZCOMPRESSED_OFF]); + } + + pNew = (ZipfileEntry*)sqlite3_malloc(nAlloc); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(pNew, 0, sizeof(ZipfileEntry)); + rc = zipfileReadCDS(aRead, &pNew->cds); + if( rc!=SQLITE_OK ){ + *pzErr = sqlite3_mprintf("failed to read CDS at offset %lld", iOff); + }else if( aBlob==0 ){ + rc = zipfileReadData( + pFile, aRead, nExtra+nFile, iOff+ZIPFILE_CDS_FIXED_SZ, pzErr + ); + }else{ + aRead = (u8*)&aBlob[iOff + ZIPFILE_CDS_FIXED_SZ]; + } + } + + if( rc==SQLITE_OK ){ + u32 *pt = &pNew->mUnixTime; + pNew->cds.zFile = sqlite3_mprintf("%.*s", nFile, aRead); + pNew->aExtra = (u8*)&pNew[1]; + memcpy(pNew->aExtra, &aRead[nFile], nExtra); + if( pNew->cds.zFile==0 ){ + rc = SQLITE_NOMEM; + }else if( 0==zipfileScanExtra(&aRead[nFile], pNew->cds.nExtra, pt) ){ + pNew->mUnixTime = zipfileMtime(&pNew->cds); + } + } + + if( rc==SQLITE_OK ){ + static const int szFix = ZIPFILE_LFH_FIXED_SZ; + ZipfileLFH lfh; + if( pFile ){ + rc = zipfileReadData(pFile, aRead, szFix, pNew->cds.iOffset, pzErr); + }else{ + aRead = (u8*)&aBlob[pNew->cds.iOffset]; + } + + rc = zipfileReadLFH(aRead, &lfh); + if( rc==SQLITE_OK ){ + pNew->iDataOff = pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ; + pNew->iDataOff += lfh.nFile + lfh.nExtra; + if( aBlob && pNew->cds.szCompressed ){ + pNew->aData = &pNew->aExtra[nExtra]; + memcpy(pNew->aData, &aBlob[pNew->iDataOff], pNew->cds.szCompressed); + } + }else{ + *pzErr = sqlite3_mprintf("failed to read LFH at offset %d", + (int)pNew->cds.iOffset + ); + } + } + + if( rc!=SQLITE_OK ){ + zipfileEntryFree(pNew); + }else{ + *ppEntry = pNew; + } + } + + return rc; +} + +/* +** Advance an ZipfileCsr to its next row of output. +*/ +static int zipfileNext(sqlite3_vtab_cursor *cur){ + ZipfileCsr *pCsr = (ZipfileCsr*)cur; + int rc = SQLITE_OK; + + if( pCsr->pFile ){ + i64 iEof = pCsr->eocd.iOffset + pCsr->eocd.nSize; + zipfileEntryFree(pCsr->pCurrent); + pCsr->pCurrent = 0; + if( pCsr->iNextOff>=iEof ){ + pCsr->bEof = 1; + }else{ + ZipfileEntry *p = 0; + ZipfileTab *pTab = (ZipfileTab*)(cur->pVtab); + rc = zipfileGetEntry(pTab, 0, 0, pCsr->pFile, pCsr->iNextOff, &p); + if( rc==SQLITE_OK ){ + pCsr->iNextOff += ZIPFILE_CDS_FIXED_SZ; + pCsr->iNextOff += (int)p->cds.nExtra + p->cds.nFile + p->cds.nComment; + } + pCsr->pCurrent = p; + } + }else{ + if( !pCsr->bNoop ){ + pCsr->pCurrent = pCsr->pCurrent->pNext; + } + if( pCsr->pCurrent==0 ){ + pCsr->bEof = 1; + } + } + + pCsr->bNoop = 0; + return rc; +} + +static void zipfileFree(void *p) { + sqlite3_free(p); +} + +/* +** Buffer aIn (size nIn bytes) contains compressed data. Uncompressed, the +** size is nOut bytes. This function uncompresses the data and sets the +** return value in context pCtx to the result (a blob). +** +** If an error occurs, an error code is left in pCtx instead. +*/ +static void zipfileInflate( + sqlite3_context *pCtx, /* Store result here */ + const u8 *aIn, /* Compressed data */ + int nIn, /* Size of buffer aIn[] in bytes */ + int nOut /* Expected output size */ +){ + u8 *aRes = sqlite3_malloc(nOut); + if( aRes==0 ){ + sqlite3_result_error_nomem(pCtx); + }else{ + int err; + z_stream str; + memset(&str, 0, sizeof(str)); + + str.next_in = (Byte*)aIn; + str.avail_in = nIn; + str.next_out = (Byte*)aRes; + str.avail_out = nOut; + + err = inflateInit2(&str, -15); + if( err!=Z_OK ){ + zipfileCtxErrorMsg(pCtx, "inflateInit2() failed (%d)", err); + }else{ + err = inflate(&str, Z_NO_FLUSH); + if( err!=Z_STREAM_END ){ + zipfileCtxErrorMsg(pCtx, "inflate() failed (%d)", err); + }else{ + sqlite3_result_blob(pCtx, aRes, nOut, zipfileFree); + aRes = 0; + } + } + sqlite3_free(aRes); + inflateEnd(&str); + } +} + +/* +** Buffer aIn (size nIn bytes) contains uncompressed data. This function +** compresses it and sets (*ppOut) to point to a buffer containing the +** compressed data. The caller is responsible for eventually calling +** sqlite3_free() to release buffer (*ppOut). Before returning, (*pnOut) +** is set to the size of buffer (*ppOut) in bytes. +** +** If no error occurs, SQLITE_OK is returned. Otherwise, an SQLite error +** code is returned and an error message left in virtual-table handle +** pTab. The values of (*ppOut) and (*pnOut) are left unchanged in this +** case. +*/ +static int zipfileDeflate( + const u8 *aIn, int nIn, /* Input */ + u8 **ppOut, int *pnOut, /* Output */ + char **pzErr /* OUT: Error message */ +){ + int nAlloc = (int)compressBound(nIn); + u8 *aOut; + int rc = SQLITE_OK; + + aOut = (u8*)sqlite3_malloc(nAlloc); + if( aOut==0 ){ + rc = SQLITE_NOMEM; + }else{ + int res; + z_stream str; + memset(&str, 0, sizeof(str)); + str.next_in = (Bytef*)aIn; + str.avail_in = nIn; + str.next_out = aOut; + str.avail_out = nAlloc; + + deflateInit2(&str, 9, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); + res = deflate(&str, Z_FINISH); + + if( res==Z_STREAM_END ){ + *ppOut = aOut; + *pnOut = (int)str.total_out; + }else{ + sqlite3_free(aOut); + *pzErr = sqlite3_mprintf("zipfile: deflate() error"); + rc = SQLITE_ERROR; + } + deflateEnd(&str); + } + + return rc; +} + + +/* +** Return values of columns for the row at which the series_cursor +** is currently pointing. +*/ +static int zipfileColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + ZipfileCsr *pCsr = (ZipfileCsr*)cur; + ZipfileCDS *pCDS = &pCsr->pCurrent->cds; + int rc = SQLITE_OK; + switch( i ){ + case 0: /* name */ + sqlite3_result_text(ctx, pCDS->zFile, -1, SQLITE_TRANSIENT); + break; + case 1: /* mode */ + /* TODO: Whether or not the following is correct surely depends on + ** the platform on which the archive was created. */ + sqlite3_result_int(ctx, pCDS->iExternalAttr >> 16); + break; + case 2: { /* mtime */ + sqlite3_result_int64(ctx, pCsr->pCurrent->mUnixTime); + break; + } + case 3: { /* sz */ + if( sqlite3_vtab_nochange(ctx)==0 ){ + sqlite3_result_int64(ctx, pCDS->szUncompressed); + } + break; + } + case 4: /* rawdata */ + if( sqlite3_vtab_nochange(ctx) ) break; + case 5: { /* data */ + if( i==4 || pCDS->iCompression==0 || pCDS->iCompression==8 ){ + int sz = pCDS->szCompressed; + int szFinal = pCDS->szUncompressed; + if( szFinal>0 ){ + u8 *aBuf; + u8 *aFree = 0; + if( pCsr->pCurrent->aData ){ + aBuf = pCsr->pCurrent->aData; + }else{ + aBuf = aFree = sqlite3_malloc(sz); + if( aBuf==0 ){ + rc = SQLITE_NOMEM; + }else{ + FILE *pFile = pCsr->pFile; + if( pFile==0 ){ + pFile = ((ZipfileTab*)(pCsr->base.pVtab))->pWriteFd; + } + rc = zipfileReadData(pFile, aBuf, sz, pCsr->pCurrent->iDataOff, + &pCsr->base.pVtab->zErrMsg + ); + } + } + if( rc==SQLITE_OK ){ + if( i==5 && pCDS->iCompression ){ + zipfileInflate(ctx, aBuf, sz, szFinal); + }else{ + sqlite3_result_blob(ctx, aBuf, sz, SQLITE_TRANSIENT); + } + } + sqlite3_free(aFree); + }else{ + /* Figure out if this is a directory or a zero-sized file. Consider + ** it to be a directory either if the mode suggests so, or if + ** the final character in the name is '/'. */ + u32 mode = pCDS->iExternalAttr >> 16; + if( !(mode & S_IFDIR) && pCDS->zFile[pCDS->nFile-1]!='/' ){ + sqlite3_result_blob(ctx, "", 0, SQLITE_STATIC); + } + } + } + break; + } + case 6: /* method */ + sqlite3_result_int(ctx, pCDS->iCompression); + break; + default: /* z */ + assert( i==7 ); + sqlite3_result_int64(ctx, pCsr->iId); + break; + } + + return rc; +} + +/* +** Return TRUE if the cursor is at EOF. +*/ +static int zipfileEof(sqlite3_vtab_cursor *cur){ + ZipfileCsr *pCsr = (ZipfileCsr*)cur; + return pCsr->bEof; +} + +/* +** If aBlob is not NULL, then it points to a buffer nBlob bytes in size +** containing an entire zip archive image. Or, if aBlob is NULL, then pFile +** is guaranteed to be a file-handle open on a zip file. +** +** This function attempts to locate the EOCD record within the zip archive +** and populate *pEOCD with the results of decoding it. SQLITE_OK is +** returned if successful. Otherwise, an SQLite error code is returned and +** an English language error message may be left in virtual-table pTab. +*/ +static int zipfileReadEOCD( + ZipfileTab *pTab, /* Return errors here */ + const u8 *aBlob, /* Pointer to in-memory file image */ + int nBlob, /* Size of aBlob[] in bytes */ + FILE *pFile, /* Read from this file if aBlob==0 */ + ZipfileEOCD *pEOCD /* Object to populate */ +){ + u8 *aRead = pTab->aBuffer; /* Temporary buffer */ + int nRead; /* Bytes to read from file */ + int rc = SQLITE_OK; + + if( aBlob==0 ){ + i64 iOff; /* Offset to read from */ + i64 szFile; /* Total size of file in bytes */ + fseek(pFile, 0, SEEK_END); + szFile = (i64)ftell(pFile); + if( szFile==0 ){ + memset(pEOCD, 0, sizeof(ZipfileEOCD)); + return SQLITE_OK; + } + nRead = (int)(MIN(szFile, ZIPFILE_BUFFER_SIZE)); + iOff = szFile - nRead; + rc = zipfileReadData(pFile, aRead, nRead, iOff, &pTab->base.zErrMsg); + }else{ + nRead = (int)(MIN(nBlob, ZIPFILE_BUFFER_SIZE)); + aRead = (u8*)&aBlob[nBlob-nRead]; + } + + if( rc==SQLITE_OK ){ + int i; + + /* Scan backwards looking for the signature bytes */ + for(i=nRead-20; i>=0; i--){ + if( aRead[i]==0x50 && aRead[i+1]==0x4b + && aRead[i+2]==0x05 && aRead[i+3]==0x06 + ){ + break; + } + } + if( i<0 ){ + pTab->base.zErrMsg = sqlite3_mprintf( + "cannot find end of central directory record" + ); + return SQLITE_ERROR; + } + + aRead += i+4; + pEOCD->iDisk = zipfileRead16(aRead); + pEOCD->iFirstDisk = zipfileRead16(aRead); + pEOCD->nEntry = zipfileRead16(aRead); + pEOCD->nEntryTotal = zipfileRead16(aRead); + pEOCD->nSize = zipfileRead32(aRead); + pEOCD->iOffset = zipfileRead32(aRead); + } + + return rc; +} + +/* +** Add object pNew to the linked list that begins at ZipfileTab.pFirstEntry +** and ends with pLastEntry. If argument pBefore is NULL, then pNew is added +** to the end of the list. Otherwise, it is added to the list immediately +** before pBefore (which is guaranteed to be a part of said list). +*/ +static void zipfileAddEntry( + ZipfileTab *pTab, + ZipfileEntry *pBefore, + ZipfileEntry *pNew +){ + assert( (pTab->pFirstEntry==0)==(pTab->pLastEntry==0) ); + assert( pNew->pNext==0 ); + if( pBefore==0 ){ + if( pTab->pFirstEntry==0 ){ + pTab->pFirstEntry = pTab->pLastEntry = pNew; + }else{ + assert( pTab->pLastEntry->pNext==0 ); + pTab->pLastEntry->pNext = pNew; + pTab->pLastEntry = pNew; + } + }else{ + ZipfileEntry **pp; + for(pp=&pTab->pFirstEntry; *pp!=pBefore; pp=&((*pp)->pNext)); + pNew->pNext = pBefore; + *pp = pNew; + } +} + +static int zipfileLoadDirectory(ZipfileTab *pTab, const u8 *aBlob, int nBlob){ + ZipfileEOCD eocd; + int rc; + int i; + i64 iOff; + + rc = zipfileReadEOCD(pTab, aBlob, nBlob, pTab->pWriteFd, &eocd); + iOff = eocd.iOffset; + for(i=0; rc==SQLITE_OK && ipWriteFd, iOff, &pNew); + + if( rc==SQLITE_OK ){ + zipfileAddEntry(pTab, 0, pNew); + iOff += ZIPFILE_CDS_FIXED_SZ; + iOff += (int)pNew->cds.nExtra + pNew->cds.nFile + pNew->cds.nComment; + } + } + return rc; +} + +/* +** xFilter callback. +*/ +static int zipfileFilter( + sqlite3_vtab_cursor *cur, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + ZipfileTab *pTab = (ZipfileTab*)cur->pVtab; + ZipfileCsr *pCsr = (ZipfileCsr*)cur; + const char *zFile = 0; /* Zip file to scan */ + int rc = SQLITE_OK; /* Return Code */ + int bInMemory = 0; /* True for an in-memory zipfile */ + + zipfileResetCursor(pCsr); + + if( pTab->zFile ){ + zFile = pTab->zFile; + }else if( idxNum==0 ){ + zipfileSetErrmsg(pCsr, "zipfile() function requires an argument"); + return SQLITE_ERROR; + }else if( sqlite3_value_type(argv[0])==SQLITE_BLOB ){ + const u8 *aBlob = (const u8*)sqlite3_value_blob(argv[0]); + int nBlob = sqlite3_value_bytes(argv[0]); + assert( pTab->pFirstEntry==0 ); + rc = zipfileLoadDirectory(pTab, aBlob, nBlob); + pCsr->pFreeEntry = pTab->pFirstEntry; + pTab->pFirstEntry = pTab->pLastEntry = 0; + if( rc!=SQLITE_OK ) return rc; + bInMemory = 1; + }else{ + zFile = (const char*)sqlite3_value_text(argv[0]); + } + + if( 0==pTab->pWriteFd && 0==bInMemory ){ + pCsr->pFile = fopen(zFile, "rb"); + if( pCsr->pFile==0 ){ + zipfileSetErrmsg(pCsr, "cannot open file: %s", zFile); + rc = SQLITE_ERROR; + }else{ + rc = zipfileReadEOCD(pTab, 0, 0, pCsr->pFile, &pCsr->eocd); + if( rc==SQLITE_OK ){ + if( pCsr->eocd.nEntry==0 ){ + pCsr->bEof = 1; + }else{ + pCsr->iNextOff = pCsr->eocd.iOffset; + rc = zipfileNext(cur); + } + } + } + }else{ + pCsr->bNoop = 1; + pCsr->pCurrent = pCsr->pFreeEntry ? pCsr->pFreeEntry : pTab->pFirstEntry; + rc = zipfileNext(cur); + } + + return rc; +} + +/* +** xBestIndex callback. +*/ +static int zipfileBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; + + for(i=0; inConstraint; i++){ + const struct sqlite3_index_constraint *pCons = &pIdxInfo->aConstraint[i]; + if( pCons->usable==0 ) continue; + if( pCons->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + if( pCons->iColumn!=ZIPFILE_F_COLUMN_IDX ) continue; + break; + } + + if( inConstraint ){ + pIdxInfo->aConstraintUsage[i].argvIndex = 1; + pIdxInfo->aConstraintUsage[i].omit = 1; + pIdxInfo->estimatedCost = 1000.0; + pIdxInfo->idxNum = 1; + }else{ + pIdxInfo->estimatedCost = (double)(((sqlite3_int64)1) << 50); + pIdxInfo->idxNum = 0; + } + + return SQLITE_OK; +} + +static ZipfileEntry *zipfileNewEntry(const char *zPath){ + ZipfileEntry *pNew; + pNew = sqlite3_malloc(sizeof(ZipfileEntry)); + if( pNew ){ + memset(pNew, 0, sizeof(ZipfileEntry)); + pNew->cds.zFile = sqlite3_mprintf("%s", zPath); + if( pNew->cds.zFile==0 ){ + sqlite3_free(pNew); + pNew = 0; + } + } + return pNew; +} + +static int zipfileSerializeLFH(ZipfileEntry *pEntry, u8 *aBuf){ + ZipfileCDS *pCds = &pEntry->cds; + u8 *a = aBuf; + + pCds->nExtra = 9; + + /* Write the LFH itself */ + zipfileWrite32(a, ZIPFILE_SIGNATURE_LFH); + zipfileWrite16(a, pCds->iVersionExtract); + zipfileWrite16(a, pCds->flags); + zipfileWrite16(a, pCds->iCompression); + zipfileWrite16(a, pCds->mTime); + zipfileWrite16(a, pCds->mDate); + zipfileWrite32(a, pCds->crc32); + zipfileWrite32(a, pCds->szCompressed); + zipfileWrite32(a, pCds->szUncompressed); + zipfileWrite16(a, (u16)pCds->nFile); + zipfileWrite16(a, pCds->nExtra); + assert( a==&aBuf[ZIPFILE_LFH_FIXED_SZ] ); + + /* Add the file name */ + memcpy(a, pCds->zFile, (int)pCds->nFile); + a += (int)pCds->nFile; + + /* The "extra" data */ + zipfileWrite16(a, ZIPFILE_EXTRA_TIMESTAMP); + zipfileWrite16(a, 5); + *a++ = 0x01; + zipfileWrite32(a, pEntry->mUnixTime); + + return a-aBuf; +} + +static int zipfileAppendEntry( + ZipfileTab *pTab, + ZipfileEntry *pEntry, + const u8 *pData, + int nData +){ + u8 *aBuf = pTab->aBuffer; + int nBuf; + int rc; + + nBuf = zipfileSerializeLFH(pEntry, aBuf); + rc = zipfileAppendData(pTab, aBuf, nBuf); + if( rc==SQLITE_OK ){ + pEntry->iDataOff = pTab->szCurrent; + rc = zipfileAppendData(pTab, pData, nData); + } + + return rc; +} + +static int zipfileGetMode( + sqlite3_value *pVal, + int bIsDir, /* If true, default to directory */ + u32 *pMode, /* OUT: Mode value */ + char **pzErr /* OUT: Error message */ +){ + const char *z = (const char*)sqlite3_value_text(pVal); + u32 mode = 0; + if( z==0 ){ + mode = (bIsDir ? (S_IFDIR + 0755) : (S_IFREG + 0644)); + }else if( z[0]>='0' && z[0]<='9' ){ + mode = (unsigned int)sqlite3_value_int(pVal); + }else{ + const char zTemplate[11] = "-rwxrwxrwx"; + int i; + if( strlen(z)!=10 ) goto parse_error; + switch( z[0] ){ + case '-': mode |= S_IFREG; break; + case 'd': mode |= S_IFDIR; break; +#if !defined(_WIN32) && !defined(WIN32) + case 'l': mode |= S_IFLNK; break; +#endif + default: goto parse_error; + } + for(i=1; i<10; i++){ + if( z[i]==zTemplate[i] ) mode |= 1 << (9-i); + else if( z[i]!='-' ) goto parse_error; + } + } + if( (bIsDir == ((mode & S_IFDIR)==0)) ){ + /* The "mode" attribute is a directory, but data has been specified. + ** Or vice-versa - no data but "mode" is a file or symlink. */ + return SQLITE_CONSTRAINT; + } + *pMode = mode; + return SQLITE_OK; + + parse_error: + *pzErr = sqlite3_mprintf("zipfile: parse error in mode: %s", z); + return SQLITE_ERROR; +} + +/* +** Both (const char*) arguments point to nul-terminated strings. Argument +** nB is the value of strlen(zB). This function returns 0 if the strings are +** identical, ignoring any trailing '/' character in either path. */ +static int zipfileComparePath(const char *zA, const char *zB, int nB){ + int nA = (int)strlen(zA); + if( zA[nA-1]=='/' ) nA--; + if( zB[nB-1]=='/' ) nB--; + if( nA==nB && memcmp(zA, zB, nA)==0 ) return 0; + return 1; +} + +static int zipfileBegin(sqlite3_vtab *pVtab){ + ZipfileTab *pTab = (ZipfileTab*)pVtab; + int rc = SQLITE_OK; + + assert( pTab->pWriteFd==0 ); + + /* Open a write fd on the file. Also load the entire central directory + ** structure into memory. During the transaction any new file data is + ** appended to the archive file, but the central directory is accumulated + ** in main-memory until the transaction is committed. */ + pTab->pWriteFd = fopen(pTab->zFile, "ab+"); + if( pTab->pWriteFd==0 ){ + pTab->base.zErrMsg = sqlite3_mprintf( + "zipfile: failed to open file %s for writing", pTab->zFile + ); + rc = SQLITE_ERROR; + }else{ + fseek(pTab->pWriteFd, 0, SEEK_END); + pTab->szCurrent = pTab->szOrig = (i64)ftell(pTab->pWriteFd); + rc = zipfileLoadDirectory(pTab, 0, 0); + } + + if( rc!=SQLITE_OK ){ + zipfileCleanupTransaction(pTab); + } + + return rc; +} + +/* +** Return the current time as a 32-bit timestamp in UNIX epoch format (like +** time(2)). +*/ +static u32 zipfileTime(void){ + sqlite3_vfs *pVfs = sqlite3_vfs_find(0); + u32 ret; + if( pVfs->iVersion>=2 && pVfs->xCurrentTimeInt64 ){ + i64 ms; + pVfs->xCurrentTimeInt64(pVfs, &ms); + ret = (u32)((ms/1000) - ((i64)24405875 * 8640)); + }else{ + double day; + pVfs->xCurrentTime(pVfs, &day); + ret = (u32)((day - 2440587.5) * 86400); + } + return ret; +} + +/* +** Return a 32-bit timestamp in UNIX epoch format. +** +** If the value passed as the only argument is either NULL or an SQL NULL, +** return the current time. Otherwise, return the value stored in (*pVal) +** cast to a 32-bit unsigned integer. +*/ +static u32 zipfileGetTime(sqlite3_value *pVal){ + if( pVal==0 || sqlite3_value_type(pVal)==SQLITE_NULL ){ + return zipfileTime(); + } + return (u32)sqlite3_value_int64(pVal); +} + +/* +** xUpdate method. +*/ +static int zipfileUpdate( + sqlite3_vtab *pVtab, + int nVal, + sqlite3_value **apVal, + sqlite_int64 *pRowid +){ + ZipfileTab *pTab = (ZipfileTab*)pVtab; + int rc = SQLITE_OK; /* Return Code */ + ZipfileEntry *pNew = 0; /* New in-memory CDS entry */ + + u32 mode = 0; /* Mode for new entry */ + u32 mTime = 0; /* Modification time for new entry */ + i64 sz = 0; /* Uncompressed size */ + const char *zPath = 0; /* Path for new entry */ + int nPath = 0; /* strlen(zPath) */ + const u8 *pData = 0; /* Pointer to buffer containing content */ + int nData = 0; /* Size of pData buffer in bytes */ + int iMethod = 0; /* Compression method for new entry */ + u8 *pFree = 0; /* Free this */ + char *zFree = 0; /* Also free this */ + ZipfileEntry *pOld = 0; + int bIsDir = 0; + u32 iCrc32 = 0; + + if( pTab->pWriteFd==0 ){ + rc = zipfileBegin(pVtab); + if( rc!=SQLITE_OK ) return rc; + } + + /* If this is a DELETE or UPDATE, find the archive entry to delete. */ + if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){ + const char *zDelete = (const char*)sqlite3_value_text(apVal[0]); + int nDelete = (int)strlen(zDelete); + for(pOld=pTab->pFirstEntry; 1; pOld=pOld->pNext){ + if( zipfileComparePath(pOld->cds.zFile, zDelete, nDelete)==0 ){ + break; + } + assert( pOld->pNext ); + } + } + + if( nVal>1 ){ + /* Check that "sz" and "rawdata" are both NULL: */ + if( sqlite3_value_type(apVal[5])!=SQLITE_NULL + || sqlite3_value_type(apVal[6])!=SQLITE_NULL + ){ + rc = SQLITE_CONSTRAINT; + } + + if( rc==SQLITE_OK ){ + if( sqlite3_value_type(apVal[7])==SQLITE_NULL ){ + /* data=NULL. A directory */ + bIsDir = 1; + }else{ + /* Value specified for "data", and possibly "method". This must be + ** a regular file or a symlink. */ + const u8 *aIn = sqlite3_value_blob(apVal[7]); + int nIn = sqlite3_value_bytes(apVal[7]); + int bAuto = sqlite3_value_type(apVal[8])==SQLITE_NULL; + + iMethod = sqlite3_value_int(apVal[8]); + sz = nIn; + pData = aIn; + nData = nIn; + if( iMethod!=0 && iMethod!=8 ){ + rc = SQLITE_CONSTRAINT; + }else{ + if( bAuto || iMethod ){ + int nCmp; + rc = zipfileDeflate(aIn, nIn, &pFree, &nCmp, &pTab->base.zErrMsg); + if( rc==SQLITE_OK ){ + if( iMethod || nCmpbase.zErrMsg); + } + + if( rc==SQLITE_OK ){ + zPath = (const char*)sqlite3_value_text(apVal[2]); + nPath = (int)strlen(zPath); + mTime = zipfileGetTime(apVal[4]); + } + + if( rc==SQLITE_OK && bIsDir ){ + /* For a directory, check that the last character in the path is a + ** '/'. This appears to be required for compatibility with info-zip + ** (the unzip command on unix). It does not create directories + ** otherwise. */ + if( zPath[nPath-1]!='/' ){ + zFree = sqlite3_mprintf("%s/", zPath); + if( zFree==0 ){ rc = SQLITE_NOMEM; } + zPath = (const char*)zFree; + nPath++; + } + } + + /* Check that we're not inserting a duplicate entry */ + if( pOld==0 && rc==SQLITE_OK ){ + ZipfileEntry *p; + for(p=pTab->pFirstEntry; p; p=p->pNext){ + if( zipfileComparePath(p->cds.zFile, zPath, nPath)==0 ){ + rc = SQLITE_CONSTRAINT; + break; + } + } + } + + if( rc==SQLITE_OK ){ + /* Create the new CDS record. */ + pNew = zipfileNewEntry(zPath); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + pNew->cds.iVersionMadeBy = ZIPFILE_NEWENTRY_MADEBY; + pNew->cds.iVersionExtract = ZIPFILE_NEWENTRY_REQUIRED; + pNew->cds.flags = ZIPFILE_NEWENTRY_FLAGS; + pNew->cds.iCompression = (u16)iMethod; + zipfileMtimeToDos(&pNew->cds, mTime); + pNew->cds.crc32 = iCrc32; + pNew->cds.szCompressed = nData; + pNew->cds.szUncompressed = (u32)sz; + pNew->cds.iExternalAttr = (mode<<16); + pNew->cds.iOffset = (u32)pTab->szCurrent; + pNew->cds.nFile = (u16)nPath; + pNew->mUnixTime = (u32)mTime; + rc = zipfileAppendEntry(pTab, pNew, pData, nData); + zipfileAddEntry(pTab, pOld, pNew); + } + } + } + + if( rc==SQLITE_OK && pOld ){ + ZipfileEntry **pp; + ZipfileCsr *pCsr; + for(pCsr=pTab->pCsrList; pCsr; pCsr=pCsr->pCsrNext){ + if( pCsr->pCurrent==pOld ){ + pCsr->pCurrent = pOld->pNext; + pCsr->bNoop = 1; + } + } + for(pp=&pTab->pFirstEntry; (*pp)!=pOld; pp=&((*pp)->pNext)); + *pp = (*pp)->pNext; + zipfileEntryFree(pOld); + } + + sqlite3_free(pFree); + sqlite3_free(zFree); + return rc; +} + +static int zipfileSerializeEOCD(ZipfileEOCD *p, u8 *aBuf){ + u8 *a = aBuf; + zipfileWrite32(a, ZIPFILE_SIGNATURE_EOCD); + zipfileWrite16(a, p->iDisk); + zipfileWrite16(a, p->iFirstDisk); + zipfileWrite16(a, p->nEntry); + zipfileWrite16(a, p->nEntryTotal); + zipfileWrite32(a, p->nSize); + zipfileWrite32(a, p->iOffset); + zipfileWrite16(a, 0); /* Size of trailing comment in bytes*/ + + return a-aBuf; +} + +static int zipfileAppendEOCD(ZipfileTab *pTab, ZipfileEOCD *p){ + int nBuf = zipfileSerializeEOCD(p, pTab->aBuffer); + assert( nBuf==ZIPFILE_EOCD_FIXED_SZ ); + return zipfileAppendData(pTab, pTab->aBuffer, nBuf); +} + +/* +** Serialize the CDS structure into buffer aBuf[]. Return the number +** of bytes written. +*/ +static int zipfileSerializeCDS(ZipfileEntry *pEntry, u8 *aBuf){ + u8 *a = aBuf; + ZipfileCDS *pCDS = &pEntry->cds; + + if( pEntry->aExtra==0 ){ + pCDS->nExtra = 9; + } + + zipfileWrite32(a, ZIPFILE_SIGNATURE_CDS); + zipfileWrite16(a, pCDS->iVersionMadeBy); + zipfileWrite16(a, pCDS->iVersionExtract); + zipfileWrite16(a, pCDS->flags); + zipfileWrite16(a, pCDS->iCompression); + zipfileWrite16(a, pCDS->mTime); + zipfileWrite16(a, pCDS->mDate); + zipfileWrite32(a, pCDS->crc32); + zipfileWrite32(a, pCDS->szCompressed); + zipfileWrite32(a, pCDS->szUncompressed); + assert( a==&aBuf[ZIPFILE_CDS_NFILE_OFF] ); + zipfileWrite16(a, pCDS->nFile); + zipfileWrite16(a, pCDS->nExtra); + zipfileWrite16(a, pCDS->nComment); + zipfileWrite16(a, pCDS->iDiskStart); + zipfileWrite16(a, pCDS->iInternalAttr); + zipfileWrite32(a, pCDS->iExternalAttr); + zipfileWrite32(a, pCDS->iOffset); + + memcpy(a, pCDS->zFile, pCDS->nFile); + a += pCDS->nFile; + + if( pEntry->aExtra ){ + int n = (int)pCDS->nExtra + (int)pCDS->nComment; + memcpy(a, pEntry->aExtra, n); + a += n; + }else{ + assert( pCDS->nExtra==9 ); + zipfileWrite16(a, ZIPFILE_EXTRA_TIMESTAMP); + zipfileWrite16(a, 5); + *a++ = 0x01; + zipfileWrite32(a, pEntry->mUnixTime); + } + + return a-aBuf; +} + +static int zipfileCommit(sqlite3_vtab *pVtab){ + ZipfileTab *pTab = (ZipfileTab*)pVtab; + int rc = SQLITE_OK; + if( pTab->pWriteFd ){ + i64 iOffset = pTab->szCurrent; + ZipfileEntry *p; + ZipfileEOCD eocd; + int nEntry = 0; + + /* Write out all entries */ + for(p=pTab->pFirstEntry; rc==SQLITE_OK && p; p=p->pNext){ + int n = zipfileSerializeCDS(p, pTab->aBuffer); + rc = zipfileAppendData(pTab, pTab->aBuffer, n); + nEntry++; + } + + /* Write out the EOCD record */ + eocd.iDisk = 0; + eocd.iFirstDisk = 0; + eocd.nEntry = (u16)nEntry; + eocd.nEntryTotal = (u16)nEntry; + eocd.nSize = (u32)(pTab->szCurrent - iOffset); + eocd.iOffset = (u32)iOffset; + rc = zipfileAppendEOCD(pTab, &eocd); + + zipfileCleanupTransaction(pTab); + } + return rc; +} + +static int zipfileRollback(sqlite3_vtab *pVtab){ + return zipfileCommit(pVtab); +} + +static ZipfileCsr *zipfileFindCursor(ZipfileTab *pTab, i64 iId){ + ZipfileCsr *pCsr; + for(pCsr=pTab->pCsrList; pCsr; pCsr=pCsr->pCsrNext){ + if( iId==pCsr->iId ) break; + } + return pCsr; +} + +static void zipfileFunctionCds( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + ZipfileCsr *pCsr; + ZipfileTab *pTab = (ZipfileTab*)sqlite3_user_data(context); + assert( argc>0 ); + + pCsr = zipfileFindCursor(pTab, sqlite3_value_int64(argv[0])); + if( pCsr ){ + ZipfileCDS *p = &pCsr->pCurrent->cds; + char *zRes = sqlite3_mprintf("{" + "\"version-made-by\" : %u, " + "\"version-to-extract\" : %u, " + "\"flags\" : %u, " + "\"compression\" : %u, " + "\"time\" : %u, " + "\"date\" : %u, " + "\"crc32\" : %u, " + "\"compressed-size\" : %u, " + "\"uncompressed-size\" : %u, " + "\"file-name-length\" : %u, " + "\"extra-field-length\" : %u, " + "\"file-comment-length\" : %u, " + "\"disk-number-start\" : %u, " + "\"internal-attr\" : %u, " + "\"external-attr\" : %u, " + "\"offset\" : %u }", + (u32)p->iVersionMadeBy, (u32)p->iVersionExtract, + (u32)p->flags, (u32)p->iCompression, + (u32)p->mTime, (u32)p->mDate, + (u32)p->crc32, (u32)p->szCompressed, + (u32)p->szUncompressed, (u32)p->nFile, + (u32)p->nExtra, (u32)p->nComment, + (u32)p->iDiskStart, (u32)p->iInternalAttr, + (u32)p->iExternalAttr, (u32)p->iOffset + ); + + if( zRes==0 ){ + sqlite3_result_error_nomem(context); + }else{ + sqlite3_result_text(context, zRes, -1, SQLITE_TRANSIENT); + sqlite3_free(zRes); + } + } +} + +/* +** xFindFunction method. +*/ +static int zipfileFindFunction( + sqlite3_vtab *pVtab, /* Virtual table handle */ + int nArg, /* Number of SQL function arguments */ + const char *zName, /* Name of SQL function */ + void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), /* OUT: Result */ + void **ppArg /* OUT: User data for *pxFunc */ +){ + if( sqlite3_stricmp("zipfile_cds", zName)==0 ){ + *pxFunc = zipfileFunctionCds; + *ppArg = (void*)pVtab; + return 1; + } + return 0; +} + +typedef struct ZipfileBuffer ZipfileBuffer; +struct ZipfileBuffer { + u8 *a; /* Pointer to buffer */ + int n; /* Size of buffer in bytes */ + int nAlloc; /* Byte allocated at a[] */ +}; + +typedef struct ZipfileCtx ZipfileCtx; +struct ZipfileCtx { + int nEntry; + ZipfileBuffer body; + ZipfileBuffer cds; +}; + +static int zipfileBufferGrow(ZipfileBuffer *pBuf, int nByte){ + if( pBuf->n+nByte>pBuf->nAlloc ){ + u8 *aNew; + int nNew = pBuf->n ? pBuf->n*2 : 512; + int nReq = pBuf->n + nByte; + + while( nNewa, nNew); + if( aNew==0 ) return SQLITE_NOMEM; + pBuf->a = aNew; + pBuf->nAlloc = nNew; + } + return SQLITE_OK; +} + +/* +** xStep() callback for the zipfile() aggregate. This can be called in +** any of the following ways: +** +** SELECT zipfile(name,data) ... +** SELECT zipfile(name,mode,mtime,data) ... +** SELECT zipfile(name,mode,mtime,data,method) ... +*/ +void zipfileStep(sqlite3_context *pCtx, int nVal, sqlite3_value **apVal){ + ZipfileCtx *p; /* Aggregate function context */ + ZipfileEntry e; /* New entry to add to zip archive */ + + sqlite3_value *pName = 0; + sqlite3_value *pMode = 0; + sqlite3_value *pMtime = 0; + sqlite3_value *pData = 0; + sqlite3_value *pMethod = 0; + + int bIsDir = 0; + u32 mode; + int rc = SQLITE_OK; + char *zErr = 0; + + int iMethod = -1; /* Compression method to use (0 or 8) */ + + const u8 *aData = 0; /* Possibly compressed data for new entry */ + int nData = 0; /* Size of aData[] in bytes */ + int szUncompressed = 0; /* Size of data before compression */ + u8 *aFree = 0; /* Free this before returning */ + u32 iCrc32 = 0; /* crc32 of uncompressed data */ + + char *zName = 0; /* Path (name) of new entry */ + int nName = 0; /* Size of zName in bytes */ + char *zFree = 0; /* Free this before returning */ + int nByte; + + memset(&e, 0, sizeof(e)); + p = (ZipfileCtx*)sqlite3_aggregate_context(pCtx, sizeof(ZipfileCtx)); + if( p==0 ) return; + + /* Martial the arguments into stack variables */ + if( nVal!=2 && nVal!=4 && nVal!=5 ){ + zErr = sqlite3_mprintf("wrong number of arguments to function zipfile()"); + rc = SQLITE_ERROR; + goto zipfile_step_out; + } + pName = apVal[0]; + if( nVal==2 ){ + pData = apVal[1]; + }else{ + pMode = apVal[1]; + pMtime = apVal[2]; + pData = apVal[3]; + if( nVal==5 ){ + pMethod = apVal[4]; + } + } + + /* Check that the 'name' parameter looks ok. */ + zName = (char*)sqlite3_value_text(pName); + nName = sqlite3_value_bytes(pName); + if( zName==0 ){ + zErr = sqlite3_mprintf("first argument to zipfile() must be non-NULL"); + rc = SQLITE_ERROR; + goto zipfile_step_out; + } + + /* Inspect the 'method' parameter. This must be either 0 (store), 8 (use + ** deflate compression) or NULL (choose automatically). */ + if( pMethod && SQLITE_NULL!=sqlite3_value_type(pMethod) ){ + iMethod = (int)sqlite3_value_int64(pMethod); + if( iMethod!=0 && iMethod!=8 ){ + zErr = sqlite3_mprintf("illegal method value: %d", iMethod); + rc = SQLITE_ERROR; + goto zipfile_step_out; + } + } + + /* Now inspect the data. If this is NULL, then the new entry must be a + ** directory. Otherwise, figure out whether or not the data should + ** be deflated or simply stored in the zip archive. */ + if( sqlite3_value_type(pData)==SQLITE_NULL ){ + bIsDir = 1; + iMethod = 0; + }else{ + aData = sqlite3_value_blob(pData); + szUncompressed = nData = sqlite3_value_bytes(pData); + iCrc32 = crc32(0, aData, nData); + if( iMethod<0 || iMethod==8 ){ + int nOut = 0; + rc = zipfileDeflate(aData, nData, &aFree, &nOut, &zErr); + if( rc!=SQLITE_OK ){ + goto zipfile_step_out; + } + if( iMethod==8 || nOut1 && zName[nName-2]=='/' ) nName--; + } + } + + /* Assemble the ZipfileEntry object for the new zip archive entry */ + e.cds.iVersionMadeBy = ZIPFILE_NEWENTRY_MADEBY; + e.cds.iVersionExtract = ZIPFILE_NEWENTRY_REQUIRED; + e.cds.flags = ZIPFILE_NEWENTRY_FLAGS; + e.cds.iCompression = (u16)iMethod; + zipfileMtimeToDos(&e.cds, (u32)e.mUnixTime); + e.cds.crc32 = iCrc32; + e.cds.szCompressed = nData; + e.cds.szUncompressed = szUncompressed; + e.cds.iExternalAttr = (mode<<16); + e.cds.iOffset = p->body.n; + e.cds.nFile = (u16)nName; + e.cds.zFile = zName; + + /* Append the LFH to the body of the new archive */ + nByte = ZIPFILE_LFH_FIXED_SZ + e.cds.nFile + 9; + if( (rc = zipfileBufferGrow(&p->body, nByte)) ) goto zipfile_step_out; + p->body.n += zipfileSerializeLFH(&e, &p->body.a[p->body.n]); + + /* Append the data to the body of the new archive */ + if( (rc = zipfileBufferGrow(&p->body, nData)) ) goto zipfile_step_out; + memcpy(&p->body.a[p->body.n], aData, nData); + p->body.n += nData; + + /* Append the CDS record to the directory of the new archive */ + nByte = ZIPFILE_CDS_FIXED_SZ + e.cds.nFile + 9; + if( (rc = zipfileBufferGrow(&p->cds, nByte)) ) goto zipfile_step_out; + p->cds.n += zipfileSerializeCDS(&e, &p->cds.a[p->cds.n]); + + /* Increment the count of entries in the archive */ + p->nEntry++; + + zipfile_step_out: + sqlite3_free(aFree); + sqlite3_free(zFree); + if( rc ){ + if( zErr ){ + sqlite3_result_error(pCtx, zErr, -1); + }else{ + sqlite3_result_error_code(pCtx, rc); + } + } + sqlite3_free(zErr); +} + +/* +** xFinalize() callback for zipfile aggregate function. +*/ +void zipfileFinal(sqlite3_context *pCtx){ + ZipfileCtx *p; + ZipfileEOCD eocd; + int nZip; + u8 *aZip; + + p = (ZipfileCtx*)sqlite3_aggregate_context(pCtx, sizeof(ZipfileCtx)); + if( p==0 ) return; + if( p->nEntry>0 ){ + memset(&eocd, 0, sizeof(eocd)); + eocd.nEntry = (u16)p->nEntry; + eocd.nEntryTotal = (u16)p->nEntry; + eocd.nSize = p->cds.n; + eocd.iOffset = p->body.n; + + nZip = p->body.n + p->cds.n + ZIPFILE_EOCD_FIXED_SZ; + aZip = (u8*)sqlite3_malloc(nZip); + if( aZip==0 ){ + sqlite3_result_error_nomem(pCtx); + }else{ + memcpy(aZip, p->body.a, p->body.n); + memcpy(&aZip[p->body.n], p->cds.a, p->cds.n); + zipfileSerializeEOCD(&eocd, &aZip[p->body.n + p->cds.n]); + sqlite3_result_blob(pCtx, aZip, nZip, zipfileFree); + } + } + + sqlite3_free(p->body.a); + sqlite3_free(p->cds.a); +} + + +/* +** Register the "zipfile" virtual table. +*/ +static int zipfileRegister(sqlite3 *db){ + static sqlite3_module zipfileModule = { + 1, /* iVersion */ + zipfileConnect, /* xCreate */ + zipfileConnect, /* xConnect */ + zipfileBestIndex, /* xBestIndex */ + zipfileDisconnect, /* xDisconnect */ + zipfileDisconnect, /* xDestroy */ + zipfileOpen, /* xOpen - open a cursor */ + zipfileClose, /* xClose - close a cursor */ + zipfileFilter, /* xFilter - configure scan constraints */ + zipfileNext, /* xNext - advance a cursor */ + zipfileEof, /* xEof - check for end of scan */ + zipfileColumn, /* xColumn - read data */ + 0, /* xRowid - read data */ + zipfileUpdate, /* xUpdate */ + zipfileBegin, /* xBegin */ + 0, /* xSync */ + zipfileCommit, /* xCommit */ + zipfileRollback, /* xRollback */ + zipfileFindFunction, /* xFindMethod */ + 0, /* xRename */ + }; + + int rc = sqlite3_create_module(db, "zipfile" , &zipfileModule, 0); + if( rc==SQLITE_OK ) rc = sqlite3_overload_function(db, "zipfile_cds", -1); + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "zipfile", -1, SQLITE_UTF8, 0, 0, + zipfileStep, zipfileFinal + ); + } + return rc; +} +#else /* SQLITE_OMIT_VIRTUALTABLE */ +# define zipfileRegister(x) SQLITE_OK +#endif + +#ifdef _WIN32 +__declspec(dllexport) +#endif +int sqlite3_zipfile_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused parameter */ + return zipfileRegister(db); +} diff --git a/ext/misc/zorder.c b/ext/misc/zorder.c new file mode 100644 index 0000000000..c385d3c3c3 --- /dev/null +++ b/ext/misc/zorder.c @@ -0,0 +1,102 @@ +/* +** 2018-02-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. +** +****************************************************************************** +** +** SQL functions for z-order (Morton code) transformations. +** +** zorder(X0,X0,..,xN) Generate an N+1 dimension Morton code +** +** unzorder(Z,N,I) Extract the I-th dimension from N-dimensional +** Morton code Z. +*/ +#include "sqlite3ext.h" +SQLITE_EXTENSION_INIT1 +#include +#include + +/* +** Functions: zorder(X0,X1,....) +** +** Convert integers X0, X1, ... into morton code. +** +** The output is a signed 64-bit integer. If any argument is too large, +** an error is thrown. +*/ +static void zorderFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + sqlite3_int64 z, x[63]; + int i, j; + z = 0; + for(i=0; i0 ){ + for(i=0; i<63; i++){ + j = i%argc; + z |= (x[j]&1)<>= 1; + } + } + sqlite3_result_int64(context, z); + for(i=0; i>j)&1)<>1] + + forcedelete test.db + forcedelete test.db2 + forcedelete rbu.db + forcedelete rbu.db2 + + do_test 1.$i.0 { + sqlite3 db test.db + sqlite3 db2 test.db2 + build_db db + build_db db2 + + sqlite3 rbu1 rbu.db + sqlite3 rbu2 rbu.db2 + + build_rbu rbu1 + build_rbu rbu2 + + rbu1 close + rbu2 close + } {} + + set m1 [db eval {SELECT md5sum(a, b, c) FROM t1}] + set m2 [db2 eval {SELECT md5sum(a, b, c) FROM t1}] + + do_test 1.$i.1 { + step_rbu2 $bOpenClose { + sqlite3rbu r1 test.db rbu.db + } { + sqlite3rbu r2 test.db2 rbu.db2 + } + } {SQLITE_DONE SQLITE_DONE} + + do_execsql_test -db db 1.$i.2.1 { PRAGMA integrity_check } ok + do_execsql_test -db db2 1.$i.2.2 { PRAGMA integrity_check } ok + + do_execsql_test -db db 1.$i.3.1 { SELECT md5sum(a, b, c)==$m1 FROM t1 } 0 + do_execsql_test -db db2 1.$i.3.2 { SELECT md5sum(a, b, c)==$m2 FROM t1 } 0 + + catch { db close } + catch { db2 close } + #----------------------------------------------------------------------- + forcedelete test.db2 + forcedelete test.db + forcedelete rbu.db2 + + do_test 1.$i.4 { + sqlite3 db test.db + sqlite3 db2 test.db2 + build_db db + build_db db2 + sqlite3 rbu2 rbu.db2 + build_rbu rbu2 + rbu2 close + } {} + + set m1 [db eval {SELECT md5sum(a, b, c) FROM t1}] + set m2 [db2 eval {SELECT md5sum(a, b, c) FROM t1}] + + do_test 1.$i.5 { + step_rbu2 $bOpenClose { + sqlite3rbu_vacuum r1 test.db + } { + sqlite3rbu r2 test.db2 rbu.db2 + } + } {SQLITE_DONE SQLITE_DONE} + + do_execsql_test -db db 1.$i.6.1 { SELECT md5sum(a, b, c)==$m1 FROM t1 } 1 + do_execsql_test -db db2 1.$i.6.2 { SELECT md5sum(a, b, c)==$m2 FROM t1 } 0 + + do_execsql_test -db db 1.$i.7.1 { PRAGMA integrity_check } ok + do_execsql_test -db db2 1.$i.7.2 { PRAGMA integrity_check } ok + + catch { db close } + catch { db2 close } + if {$i & 0x01} { + sqlite3rbu_destroy_vfs myrbu + } + +} + + +finish_test + diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c index ff15a192a1..00513d4005 100644 --- a/ext/rtree/rtree.c +++ b/ext/rtree/rtree.c @@ -52,7 +52,8 @@ ** child page. */ -#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_RTREE) +#if !defined(SQLITE_CORE) \ + || (defined(SQLITE_ENABLE_RTREE) && !defined(SQLITE_OMIT_VIRTUALTABLE)) #ifndef SQLITE_CORE #include "sqlite3ext.h" @@ -784,6 +785,7 @@ static int nodeWrite(Rtree *pRtree, RtreeNode *pNode){ sqlite3_step(p); pNode->isDirty = 0; rc = sqlite3_reset(p); + sqlite3_bind_null(p, 2); if( pNode->iNode==0 && rc==SQLITE_OK ){ pNode->iNode = sqlite3_last_insert_rowid(pRtree->db); nodeHashInsert(pRtree, pNode); @@ -2021,7 +2023,7 @@ static int ChooseLeaf( ){ int rc; int ii; - RtreeNode *pNode; + RtreeNode *pNode = 0; rc = nodeAcquire(pRtree, 1, 0, &pNode); for(ii=0; rc==SQLITE_OK && ii<(pRtree->iDepth-iHeight); ii++){ @@ -2896,7 +2898,7 @@ static int rtreeDeleteRowid(Rtree *pRtree, sqlite3_int64 iDelete){ */ if( rc==SQLITE_OK && pRtree->iDepth>0 && NCELL(pRoot)==1 ){ int rc2; - RtreeNode *pChild; + RtreeNode *pChild = 0; i64 iChild = nodeGetRowid(pRtree, pRoot, 0); rc = nodeAcquire(pRtree, iChild, pRoot, &pChild); if( rc==SQLITE_OK ){ diff --git a/ext/rtree/rtree.h b/ext/rtree/rtree.h index 1fdbcccc5e..8f41500db1 100644 --- a/ext/rtree/rtree.h +++ b/ext/rtree/rtree.h @@ -15,6 +15,10 @@ */ #include "sqlite3.h" +#ifdef SQLITE_OMIT_VIRTUALTABLE +# undef SQLITE_ENABLE_RTREE +#endif + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ diff --git a/ext/rtree/rtree1.test b/ext/rtree/rtree1.test index 0deee6635b..ac6e8d9d96 100644 --- a/ext/rtree/rtree1.test +++ b/ext/rtree/rtree1.test @@ -609,4 +609,5 @@ do_execsql_test 15.2 { COMMIT; } +expand_all_sql db finish_test diff --git a/ext/rtree/rtree4.test b/ext/rtree/rtree4.test index af3f8d3995..a73921d8d5 100644 --- a/ext/rtree/rtree4.test +++ b/ext/rtree/rtree4.test @@ -250,4 +250,5 @@ for {set nDim 1} {$nDim<=5} {incr nDim} { do_rtree_integrity_test rtree4-$nDim.3 rx } +expand_all_sql db finish_test diff --git a/ext/rtree/rtree5.test b/ext/rtree/rtree5.test index 749385e882..92bb6905c7 100644 --- a/ext/rtree/rtree5.test +++ b/ext/rtree/rtree5.test @@ -79,4 +79,5 @@ do_test rtree5-1.13 { } {2 2147483643 2147483647 -2147483648 -2147483643} do_rtree_integrity_test rtree5-1.14 t1 +expand_all_sql db finish_test diff --git a/ext/rtree/rtree6.test b/ext/rtree/rtree6.test index c9c87e8ad9..406604810b 100644 --- a/ext/rtree/rtree6.test +++ b/ext/rtree/rtree6.test @@ -158,5 +158,5 @@ do_execsql_test rtree6-3.5 { x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>1.1 } {} - +expand_all_sql db finish_test diff --git a/ext/rtree/rtreeG.test b/ext/rtree/rtreeG.test index 3bef89c8e7..12225d5832 100644 --- a/ext/rtree/rtreeG.test +++ b/ext/rtree/rtreeG.test @@ -59,6 +59,7 @@ do_test rtreeG-1.4log { set ::log } {} +expand_all_sql db db close sqlite3_shutdown test_sqlite3_log diff --git a/ext/session/session4.test b/ext/session/session4.test index 120a230b57..bf55e362c6 100644 --- a/ext/session/session4.test +++ b/ext/session/session4.test @@ -64,4 +64,80 @@ do_test 1.3 { list [catch { sqlite3changeset_apply db $x xConflict } msg] $msg } {1 SQLITE_CORRUPT} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 2.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY,b,c,d); + CREATE TABLE t2(e TEXT PRIMARY KEY NOT NULL,f,g); + CREATE TABLE t3(w REAL PRIMARY KEY NOT NULL,x,y); + CREATE TABLE t4(z PRIMARY KEY) WITHOUT ROWID; +} + +foreach {tn blob} { + 1 54010174340012000000 + 2 54fefe8bcb0012000300 + 3 5480809280808001017434001200fb + 4 50af9c939c9c9cb09c9c6400b09c9c6400 + 5 12000300 + 6 09847304 + 7 5401017434001208 + 8 54010174340012fc0386868600 + 9 54010174340012FC0386868600 + 10 548894FEFE + 11 54010171340012E703ABFA7433FD1200 + 12 540101743400120003FFED00010000000000000002120002400C00000000000054040100000074310017000100000000000000050100000000000000030100000000000000040000010000000000000004010000000000000003001700010000000000000007030378797A01000000000000000F000001000000000000000F030378797A005403010000743200090003037838790100000000800000000200000000000000000900030378327902400C0000000000000304666F7572 + 13 540101743400120003001200010000000000000002120002400C0000000000005404010000007431001700010000000000000005010000000000000003010000000000000004000001000000000000000401000000000000000300170001000000000000000703FC87797A01000000000000000F000001000000000000000F030378797A005403010000743200090003037838790100000000800000000200000000000000000900030378327902400C0000000000000304666F7572 + 14 540101743400120003001200010000000000000002120002400C00000000000054040100000074310017000100000000000000050100000000000000030100000000000000040000010000000000000004010000000000000003001700010000000000000007030378797A01000000000000000F000001000000000000000F03FC87797A005403010000743200090003037838790100000000800000000200000000000000000900030378327902400C0000000000000304666F7572 + 15 540101743400120003001200010000000000000002120002400C00000000000054040100000074310017000100000000000000050100000000000000030100000000000000040000010000000000000004010000000000000003001700010000000000000007030378797A01000000000000000F000001000000000000000F030378797A005403010000743200090003FC8738790100000000800000000200000000000000000900030378327902400C0000000000000304666F7572 + 16 540101743400120003001200010000000000000002120002400C00000000000054040100000074310017000100000000000000050100000000000000030100000000000000040000010000000000000004010000000000000003001700010000000000000007030378797A01000000000000000F000001000000000000000F030378797A00540301000074320009000303783879010000000080000000020000000000000000090003FC87327902400C0000000000000304666F7572 + 17 540101743400120003FFE3000412F7010000E600000000021202120002400C0000000000005B0401000000743100171C0304646F750002400C000000000000540401000000D3310017000100000000000000050100000000000378797A405403000002F10100000100000000000004090001000100000007030378797A0100000000000D0007000001000000002300000F1B0378797A405403013900743200090003038C3879010000000000000000000002120002400C0000000000005B0401000000743117170003047C5E00FF + 18 54010174340012000300120001000000E6FF100000120002401E00000000000054040100000074310017000100040000010000000000000004FFFF7FFF0000000000010000010000001000000007030378797A01000000000000000F000000000000FA0304666F7572 + 19 540101743400120003001200010000000000000002121B02400C00000000000054040000000074310017000100000000000000050100000000000000030100000000000000040000010000000000000004010000000000000003001700010000000000000007030378817A01000000000000000F000001000000000100000F030378797A005403010000743200090003FFE809000303780000000000000304666F7572 + 20 5401017D3400120003001200010000000000000002120002400CFC00000000005404010000007431001700010000000000000005010000000000000003010000000000000004000001000000000000000401000000000000000300170001000000000000000703FFFF797A01000000000000000F000001000000000000000F030378797A005403010000743200090003037838790100000000800000000200000000000000000900030378326C02400C0000000000000304666F7572 + 21 5401017434001200030012000100FFE20000000002120002400C00000000000054040100E0007431001700010000E99D000000020000000003FFE70009000303783279020004000001030000000000002117000003001700012701000100000000743100000100000000008000090003037F387901000000008000000002000000000400000009005303010A00FF7FFFFF00000000000304664F6572 + 22 540101743400120003FFFF7FFF0000000000000002120002400C00000000000054040100000074310017000100000000000000050100000000000000030100010000000000000000040000010000000000000004010000000000000003001700010000000000000007030378797A01000000000000000F000001000000000000000F030378797A005403010000743200090003037838790100000000800000000200000000000000000900030378327902400C0000000000000304666F7572 + 23 540101742700120100120003F5FF0300 + 24 5401017434E312540101743400120003FFFC00 + 25 540101743400540101743D3D3D3D3D3D3D3D3D3D3D3D3D3400120003FFED000300 + 26 5401017446EA5301743D1D3D3D01743D1D3D3DCF3D3D3D1A3D3D3D3D3400120003FFFF000000 + 27 540101743400540101743D3D3D3D3D3D3D3D3D3D251000120003FF81000000000000 + 28 540101340012000397FF3D7F3D3400120003001200540101743D3D3D3D3D3D393D3D3D12000300 + 29 500174340050010F74340012000300120003FFE5 + 30 5004007233E900177FEF0054257F0002EF001200031E12000300 + 31 5001015001015252525250010174340012EF039A9A0100E351525D52525252525252525252525252525252525250010174340012EF039A0100009A9A9A9A9A9BA3B200120003010040743400 + 32 5401017400123400120003FFFC00 + 33 540101743400120003001200010000000000004002120002400C0000000000005404010000007431001700010000000000000005010000000000000003010000000000000004000001000000000000000401000000000000000300170001000000000000000703FC87797A01000000000000000F000001000000000000000F030378797A005403010000743200090003037838790100000000800000000200000000000000000900030378327902400C0000000000000304666F7572 + 34 54040100000074310017000100000002000015050100000000000000030100000000140000040000010000000000000004010000000000000003001700010000000000000007030378797A01000000000000000F000001000000000000000F030378797A0054030100007432000900030378387901000000008E000000020000000000000000090003FFFF000002400C0000000000000304666F7572 + 35 540101743400120003001200010000000000000002120002400C00000000000050060100000074310017000100000000000000050100000000000000030100000003001700010000666F7572 + 36 540101743400120003001200010000000000000002120002400C00000000000050050100000074310017000100000000000000050100000000000000030100000003001700010000666F7572 + 37 540101743400120003001200010000000000000002120002400C00000000000050040100008074310017000100000000000000050100000000000000030100000003001700010000666F7572 + 38 540101743400120003001200010000000000000002120002400C00000000000050040100000074310017000000000000000000050100000000000000030100000003001700010000666F7572 + 39 540101743400120003001200010000000000000002120002400C00000000000050040100018074310017000100000000000000050100000000000000030100000003001700010000666F7572 + 40 540101743400120003001200010000000000000002120002400C0000000000005004FEFFFFFF74310017000100000000000000050100000000000000030100000003001700010000666F7572 + 41 540101743400120003001200010000000000000002120002400C00000000000050040100000074310017000004000000000000050100000000000000030100000003001700010000666F7572 + 42 540101743400120003001200010000000000000002120002400C0000000000005005FFFF050074310017000100000000000000050100000000000000030100000003001700010000666F7572 + 43 540101743400120003001200010000000000000002120002400C000000000000500401006E0074310017000300000000001221050100000000000000030100000003001700010000666F7572 + 44 540101743400120003001200010000000000020000120002400C00000000000050050100000074310017000100000000000000050100004000000000030100000025001700010000666F7572 + 45 540101743400120003001200010000000000ECFF02120002400C000000000000500401F9FF00743100170001000000000000000500E1000000000000030100000003000000000000666F7572 + 46 54010174340B0B0B0B0B0B0B0B0B0B0B0B0B0B0B00120003001200010000000000000002120002400C00000000000050040100000074310017010000000000000000050100FFE900000000030100000003007F00000000666F7572 + 47 54010103001200010000000000020002120002400C0000000000005004010000F374310017000100000000000000050100000000000000030100000003001700010000666F8E72 + 48 540101743400120003001200010000000000000002120002400C00000000000050030012000174310017000700000000000000050100002000000001000000000003001700010000666F7572 + 49 540101743400120004001200010000000000000002120002400C0000000000005004010000FC733100170001000000000000000501000000000000000301000000F6FF17000100007C6F7572 + 50 54010174FFDDFF8003001200010000100000000002120002400C000000000000500401000000743100170000000005010000000000000000000003010072 + 51 540101743200120003001200010000000000000002120002400C00000000000050040100001074310017000000000003010000120300170100000000000000050100000000000000030100000003001700010000666F7572 + 52 540101745401017434001200010000000000001702120002400C00000000000050040100001A74310017000100000000000100000100000000000000030100000003001700010000666F7572 + 53 540101743400120003001200010000000000000002120002400C000000000000500401000000743100170001000002400C00000000000050040110000074310017000000000000050100000000000000030100000003001700010000666F7572 + 54 540101743400120003001200010000000000000002120002400C000000000002120002400C00000000000050040100000074310017FF0050040100000074310017FF7F00000000000000050100000000000000030100000003001700010000666F7572 + 55 540101743400120003001200010000000000000002120002400C00000000000050040100000074310017000100010080000001000000020003010100000300170100000003001700010000666F7572 +} { + do_test 2.$tn { + set changeset [binary decode hex $blob] +#set fd [open x.change w+] +#fconfigure $fd -encoding binary -translation binary +#puts -nonewline $fd $changeset +#close $fd + list [catch { sqlite3changeset_apply db $changeset xConflict } msg] $msg + } {1 SQLITE_CORRUPT} +} + finish_test diff --git a/ext/session/sessionD.test b/ext/session/sessionD.test index b8572782e2..84c31cbc2f 100644 --- a/ext/session/sessionD.test +++ b/ext/session/sessionD.test @@ -221,5 +221,38 @@ do_test 4.2.2 { } {1 {SQLITE_SCHEMA - table schemas do not match}} S delete +do_test 4.3.1 { + sqlite3session S db main + S attach t4 + execsql { CREATE TABLE t4(i PRIMARY KEY, b) } + list [catch { S diff ixua t4 } msg] $msg +} {1 {SQLITE_SCHEMA - table schemas do not match}} +S delete +do_catchsql_test 4.3.2 { + SELECT * FROM ixua.t4; +} {1 {no such table: ixua.t4}} + +do_test 4.4.1 { + sqlite3session S db main + S attach sqlite_stat1 + execsql { ANALYZE } + execsql { DROP TABLE ixua.sqlite_stat1 } + list [catch { S diff ixua sqlite_stat1 } msg] $msg +} {1 {SQLITE_SCHEMA - table schemas do not match}} +S delete +do_catchsql_test 4.4.2 { + SELECT * FROM ixua.sqlite_stat1; +} {1 {no such table: ixua.sqlite_stat1}} + +do_test 4.5.1 { + sqlite3session S db main + S attach t8 + list [catch { S diff ixua t8 } msg] $msg +} {0 {}} +S delete +do_catchsql_test 4.5.2 { + SELECT * FROM ixua.i8; +} {1 {no such table: ixua.i8}} + finish_test diff --git a/ext/session/sessionG.test b/ext/session/sessionG.test index 5c057350e4..2b6c0410a7 100644 --- a/ext/session/sessionG.test +++ b/ext/session/sessionG.test @@ -173,5 +173,39 @@ do_test 4.3 { compare_db db db2 } {} +#------------------------------------------------------------------------- +reset_db +catch { db2 close } +forcedelete test.db2 +sqlite3 db2 test.db2 + +do_execsql_test 5.0.1 { + CREATE TABLE t1(a PRIMARY KEY, b, c); + CREATE TABLE t2(a, b, c PRIMARY KEY); + CREATE TABLE t3(a, b PRIMARY KEY, c); +} +do_execsql_test -db db2 5.0.2 { + CREATE TABLE t1(a PRIMARY KEY, b, c); + CREATE TABLE t2(a, b, c); + CREATE TABLE t3(a, b PRIMARY KEY, c); +} + +do_test 5.1 { + do_then_apply_sql { + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t2 VALUES(4, 5, 6); + INSERT INTO t3 VALUES(7, 8, 9); + } + + db2 eval { + SELECT * FROM t1; + SELECT * FROM t2; + SELECT * FROM t3; + } +} {1 2 3 7 8 9} + + + + finish_test diff --git a/ext/session/sessionH.test b/ext/session/sessionH.test new file mode 100644 index 0000000000..643fdb3fbe --- /dev/null +++ b/ext/session/sessionH.test @@ -0,0 +1,39 @@ +# 2018 January 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. +# +#*********************************************************************** +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] session_common.tcl] +source $testdir/tester.tcl +ifcapable !session {finish_test; return} +set testprefix sessionH + +forcedelete test.db2 +sqlite3 db2 test.db2 + +do_test 1.0 { + do_common_sql { + CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b)); + } + do_then_apply_sql { + WITH s(i) AS ( + VALUES(1) UNION ALL SELECT i+1 FROM s WHERe i<10000 + ) + INSERT INTO t1 SELECT 'abcde', randomblob(16), i FROM s; + } + compare_db db db2 +} {} + + +finish_test + diff --git a/ext/session/sessionat.test b/ext/session/sessionat.test index f482d01520..4a3f59a441 100644 --- a/ext/session/sessionat.test +++ b/ext/session/sessionat.test @@ -241,5 +241,9 @@ eval [string map [list %WR% $trailing] { }] } +catch { db close } +catch { db2 close } +sqlite3_shutdown +test_sqlite3_log finish_test diff --git a/ext/session/sessionstat1.test b/ext/session/sessionstat1.test new file mode 100644 index 0000000000..082c02bcf1 --- /dev/null +++ b/ext/session/sessionstat1.test @@ -0,0 +1,311 @@ +# 2018 January 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. +# +#*********************************************************************** +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] session_common.tcl] +source $testdir/tester.tcl +ifcapable !session {finish_test; return} + +set testprefix sessionstat1 + +do_execsql_test 1.0 { + CREATE TABLE t1(a PRIMARY KEY, b, c); + CREATE INDEX t1b ON t1(b); + CREATE INDEX t1c ON t1(c); + + WITH s(i) AS ( + SELECT 0 UNION ALL SELECT i+1 FROM s WHERE (i+1)<32 + ) + INSERT INTO t1 SELECT i, i%8, i%2 FROM s; +} + +do_iterator_test 1.1 {} { + ANALYZE +} { + {INSERT sqlite_stat1 0 XX. {} {t t1 t sqlite_autoindex_t1_1 t {32 1}}} + {INSERT sqlite_stat1 0 XX. {} {t t1 t t1b t {32 4}}} + {INSERT sqlite_stat1 0 XX. {} {t t1 t t1c t {32 16}}} +} + +do_execsql_test 1.2 { + WITH s(i) AS ( + SELECT 32 UNION ALL SELECT i+1 FROM s WHERE (i+1)<64 + ) + INSERT INTO t1 SELECT i, i%8, i%2 FROM s; +} + +do_iterator_test 1.3 {} { + ANALYZE +} { + {UPDATE sqlite_stat1 0 XX. {t t1 t sqlite_autoindex_t1_1 t {32 1}} {{} {} {} {} t {64 1}}} + {UPDATE sqlite_stat1 0 XX. {t t1 t t1b t {32 4}} {{} {} {} {} t {64 8}}} + {UPDATE sqlite_stat1 0 XX. {t t1 t t1c t {32 16}} {{} {} {} {} t {64 32}}} +} + +do_iterator_test 1.5 {} { + DROP INDEX t1b; +} { + {DELETE sqlite_stat1 0 XX. {t t1 t t1b t {64 8}} {}} +} + +do_iterator_test 1.6 {} { + DROP TABLE t1; +} { + {DELETE sqlite_stat1 0 XX. {t t1 t sqlite_autoindex_t1_1 t {64 1}} {}} + {DELETE sqlite_stat1 0 XX. {t t1 t t1c t {64 32}} {}} +} + +#------------------------------------------------------------------------- +# +catch { db2 close } +forcedelete test.db2 +sqlite3 db2 test.db2 + +do_test 2.0 { + do_common_sql { + CREATE TABLE t1(a PRIMARY KEY, b, c); + CREATE INDEX t1b ON t1(b); + CREATE INDEX t1c ON t1(c); + ANALYZE; + } +} {} + +do_test 2.1 { + do_then_apply_sql { + WITH s(i) AS ( + SELECT 0 UNION ALL SELECT i+1 FROM s WHERE (i+1)<32 + ) + INSERT INTO t1 SELECT i, i%8, i%2 FROM s; + ANALYZE; + } +} {} + +do_execsql_test -db db2 2.2 { + SELECT * FROM sqlite_stat1 +} { + t1 sqlite_autoindex_t1_1 {32 1} + t1 t1b {32 4} + t1 t1c {32 16} +} + +do_test 2.3 { + do_then_apply_sql { DROP INDEX t1c } +} {} + +do_execsql_test -db db2 2.4 { + SELECT * FROM sqlite_stat1 +} { + t1 sqlite_autoindex_t1_1 {32 1} + t1 t1b {32 4} +} + +do_test 2.3 { + do_then_apply_sql { DROP TABLE t1 } +} {} + +do_execsql_test -db db2 2.4 { + SELECT * FROM sqlite_stat1 +} { +} + +do_execsql_test -db db2 2.5 { SELECT count(*) FROM t1 } 32 + +#------------------------------------------------------------------------- +db2 close +forcedelete test.db2 +reset_db +sqlite3 db2 test.db2 + +do_test 3.0 { + do_common_sql { + CREATE TABLE t1(a, b, c); + ANALYZE; + DELETE FROM sqlite_stat1; + } + execsql { + INSERT INTO t1 VALUES(1, 1, 1); + INSERT INTO t1 VALUES(2, 2, 2); + INSERT INTO t1 VALUES(3, 3, 3); + INSERT INTO t1 VALUES(4, 4, 4); + } +} {} + +do_iterator_test 3.1 {} { + ANALYZE +} { + {INSERT sqlite_stat1 0 XX. {} {t t1 b {} t 4}} +} +db null null +db2 null null +do_execsql_test 3.2 { + SELECT * FROM sqlite_stat1; +} {t1 null 4} +do_test 3.3 { + execsql { DELETE FROM sqlite_stat1 } + do_then_apply_sql { ANALYZE } + execsql { SELECT * FROM sqlite_stat1 } db2 +} {t1 null 4} +do_test 3.4 { + execsql { INSERT INTO t1 VALUES(5,5,5) } + do_then_apply_sql { ANALYZE } + execsql { SELECT * FROM sqlite_stat1 } db2 +} {t1 null 5} +do_test 3.5 { + do_then_apply_sql { DROP TABLE t1 } + execsql { SELECT * FROM sqlite_stat1 } db2 +} {} + +do_test 3.6.1 { + execsql { + CREATE TABLE t1(a, b, c); + CREATE TABLE t2(x, y, z); + INSERT INTO t1 VALUES(1,1,1), (2,2,2), (3,3,3), (4,4,4), (5,5,5); + INSERT INTO t2 SELECT * FROM t1; + DELETE FROM sqlite_stat1; + } + sqlite3session S db main + S attach sqlite_stat1 + execsql { ANALYZE } +} {} +do_changeset_test 3.6.2 S { + {INSERT sqlite_stat1 0 XX. {} {t t2 b {} t 5}} + {INSERT sqlite_stat1 0 XX. {} {t t1 b {} t 5}} +} +do_changeset_invert_test 3.6.3 S { + {DELETE sqlite_stat1 0 XX. {t t2 b {} t 5} {}} + {DELETE sqlite_stat1 0 XX. {t t1 b {} t 5} {}} +} +do_test 3.6.4 { S delete } {} + +proc sql_changeset_concat {args} { + foreach sql $args { + sqlite3session S db main + S attach sqlite_stat1 + execsql $sql + set change [S changeset] + S delete + + if {[info vars ret]!=""} { + set ret [sqlite3changeset_concat $ret $change] + } else { + set ret $change + } + } + + changeset_to_list $ret +} + +proc do_scc_test {tn args} { + uplevel [list \ + do_test $tn [concat sql_changeset_concat [lrange $args 0 end-1]] \ + [list {*}[ lindex $args end ]] + ] +} + +do_execsql_test 3.7.0 { + DELETE FROM sqlite_stat1; +} +do_scc_test 3.7.1 { + ANALYZE; +} { + INSERT INTO t2 VALUES(6,6,6); + ANALYZE; +} { + {INSERT sqlite_stat1 0 XX. {} {t t1 b {} t 5}} + {INSERT sqlite_stat1 0 XX. {} {t t2 b {} t 6}} +} + +#------------------------------------------------------------------------- +catch { db2 close } +reset_db +forcedelete test.db2 +sqlite3 db2 test.db2 + +do_test 4.1.0 { + do_common_sql { + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a); + CREATE INDEX i2 ON t1(b); + INSERT INTO t1 VALUES(1,1), (2,2); + ANALYZE; + } + execsql { DELETE FROM sqlite_stat1 } +} {} + +do_test 4.1.1 { + execsql { INSERT INTO t1 VALUES(3,3); } + set C [changeset_from_sql {ANALYZE}] + set ::c [list] + proc xConflict {args} { + lappend ::c $args + return "OMIT" + } + sqlite3changeset_apply db2 $C xConflict + set ::c +} [list {*}{ + {INSERT sqlite_stat1 CONFLICT {t t1 t i1 t {3 1}} {t t1 t i1 t {2 1}}} + {INSERT sqlite_stat1 CONFLICT {t t1 t i2 t {3 1}} {t t1 t i2 t {2 1}}} +}] + +do_execsql_test -db db2 4.1.2 { + SELECT * FROM sqlite_stat1 ORDER BY 1,2; +} {t1 i1 {2 1} t1 i2 {2 1}} + +do_test 4.1.3 { + proc xConflict {args} { + return "REPLACE" + } + sqlite3changeset_apply db2 $C xConflict + execsql { SELECT * FROM sqlite_stat1 ORDER BY 1,2 } db2 +} {t1 i1 {3 1} t1 i2 {3 1}} + +do_test 4.2.0 { + do_common_sql { + DROP TABLE t1; + CREATE TABLE t3(x,y); + INSERT INTO t3 VALUES('a','a'); + INSERT INTO t3 VALUES('b','b'); + ANALYZE; + } + execsql { DELETE FROM sqlite_stat1 } +} {} +do_test 4.2.1 { + execsql { INSERT INTO t3 VALUES('c','c'); } + set C [changeset_from_sql {ANALYZE}] + set ::c [list] + proc xConflict {args} { + lappend ::c $args + return "OMIT" + } + sqlite3changeset_apply db2 $C xConflict + set ::c +} [list {*}{ + {INSERT sqlite_stat1 CONFLICT {t t3 b {} t 3} {t t3 b {} t 2}} +}] + +db2 null null +do_execsql_test -db db2 4.2.2 { + SELECT * FROM sqlite_stat1 ORDER BY 1,2; +} {t3 null 2} + +do_test 4.2.3 { + proc xConflict {args} { + return "REPLACE" + } + sqlite3changeset_apply db2 $C xConflict + execsql { SELECT * FROM sqlite_stat1 ORDER BY 1,2 } db2 +} {t3 null 3} + +finish_test + diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 3976ee4e19..29f19719e1 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -53,6 +53,7 @@ struct sqlite3_session { int rc; /* Non-zero if an error has occurred */ void *pFilterCtx; /* First argument to pass to xTableFilter */ int (*xTableFilter)(void *pCtx, const char *zTab); + sqlite3_value *pZeroBlob; /* Value containing X'' */ sqlite3_session *pNext; /* Next session object on same db. */ SessionTable *pTable; /* List of attached tables */ SessionHook hook; /* APIs to grab new and old data with */ @@ -120,6 +121,7 @@ struct SessionTable { SessionTable *pNext; char *zName; /* Local name of table */ int nCol; /* Number of columns in table zName */ + int bStat1; /* True if this is sqlite_stat1 */ const char **azCol; /* Column names */ u8 *abPK; /* Array of primary key flags */ int nEntry; /* Total number of entries in hash table */ @@ -503,6 +505,7 @@ static int sessionPreupdateHash( h = sessionHashAppendBlob(h, n, z); }else{ assert( eType==SQLITE_NULL ); + assert( pTab->bStat1==0 || i!=1 ); *pbNullPK = 1; } } @@ -845,7 +848,6 @@ static int sessionPreupdateEqual( } if( memcmp(a, z, n) ) return 0; a += n; - break; } } } @@ -901,9 +903,7 @@ static int sessionGrowHash(int bPatchset, SessionTable *pTab){ /* ** This function queries the database for the names of the columns of table -** zThis, in schema zDb. It is expected that the table has nCol columns. If -** not, SQLITE_SCHEMA is returned and none of the output variables are -** populated. +** zThis, in schema zDb. ** ** Otherwise, if they are not NULL, variable *pnCol is set to the number ** of columns in the database table and variable *pzTab is set to point to a @@ -924,9 +924,7 @@ static int sessionGrowHash(int bPatchset, SessionTable *pTab){ ** *pabPK = {1, 0, 0, 1} ** ** All returned buffers are part of the same single allocation, which must -** be freed using sqlite3_free() by the caller. If pazCol was not NULL, then -** pointer *pazCol should be freed to release all memory. Otherwise, pointer -** *pabPK. It is illegal for both pazCol and pabPK to be NULL. +** be freed using sqlite3_free() by the caller */ static int sessionTableInfo( sqlite3 *db, /* Database connection */ @@ -951,7 +949,23 @@ static int sessionTableInfo( assert( pazCol && pabPK ); nThis = sqlite3Strlen30(zThis); - zPragma = sqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, zThis); + if( nThis==12 && 0==sqlite3_stricmp("sqlite_stat1", zThis) ){ + rc = sqlite3_table_column_metadata(db, zDb, zThis, 0, 0, 0, 0, 0, 0); + if( rc==SQLITE_OK ){ + /* For sqlite_stat1, pretend that (tbl,idx) is the PRIMARY KEY. */ + zPragma = sqlite3_mprintf( + "SELECT 0, 'tbl', '', 0, '', 1 UNION ALL " + "SELECT 1, 'idx', '', 0, '', 2 UNION ALL " + "SELECT 2, 'stat', '', 0, '', 0" + ); + }else if( rc==SQLITE_ERROR ){ + zPragma = sqlite3_mprintf(""); + }else{ + return rc; + } + }else{ + zPragma = sqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, zThis); + } if( !zPragma ) return SQLITE_NOMEM; rc = sqlite3_prepare_v2(db, zPragma, -1, &pStmt, 0); @@ -1043,11 +1057,55 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){ break; } } + if( 0==sqlite3_stricmp("sqlite_stat1", pTab->zName) ){ + pTab->bStat1 = 1; + } } } return (pSession->rc || pTab->abPK==0); } +/* +** Versions of the four methods in object SessionHook for use with the +** sqlite_stat1 table. The purpose of this is to substitute a zero-length +** blob each time a NULL value is read from the "idx" column of the +** sqlite_stat1 table. +*/ +typedef struct SessionStat1Ctx SessionStat1Ctx; +struct SessionStat1Ctx { + SessionHook hook; + sqlite3_session *pSession; +}; +static int sessionStat1Old(void *pCtx, int iCol, sqlite3_value **ppVal){ + SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx; + sqlite3_value *pVal = 0; + int rc = p->hook.xOld(p->hook.pCtx, iCol, &pVal); + if( rc==SQLITE_OK && iCol==1 && sqlite3_value_type(pVal)==SQLITE_NULL ){ + pVal = p->pSession->pZeroBlob; + } + *ppVal = pVal; + return rc; +} +static int sessionStat1New(void *pCtx, int iCol, sqlite3_value **ppVal){ + SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx; + sqlite3_value *pVal = 0; + int rc = p->hook.xNew(p->hook.pCtx, iCol, &pVal); + if( rc==SQLITE_OK && iCol==1 && sqlite3_value_type(pVal)==SQLITE_NULL ){ + pVal = p->pSession->pZeroBlob; + } + *ppVal = pVal; + return rc; +} +static int sessionStat1Count(void *pCtx){ + SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx; + return p->hook.xCount(p->hook.pCtx); +} +static int sessionStat1Depth(void *pCtx){ + SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx; + return p->hook.xDepth(p->hook.pCtx); +} + + /* ** This function is only called from with a pre-update-hook reporting a ** change on table pTab (attached to session pSession). The type of change @@ -1064,6 +1122,7 @@ static void sessionPreupdateOneChange( int iHash; int bNull = 0; int rc = SQLITE_OK; + SessionStat1Ctx stat1 = {0}; if( pSession->rc ) return; @@ -1083,6 +1142,25 @@ static void sessionPreupdateOneChange( return; } + if( pTab->bStat1 ){ + stat1.hook = pSession->hook; + stat1.pSession = pSession; + pSession->hook.pCtx = (void*)&stat1; + pSession->hook.xNew = sessionStat1New; + pSession->hook.xOld = sessionStat1Old; + pSession->hook.xCount = sessionStat1Count; + pSession->hook.xDepth = sessionStat1Depth; + if( pSession->pZeroBlob==0 ){ + sqlite3_value *p = sqlite3ValueNew(0); + if( p==0 ){ + rc = SQLITE_NOMEM; + goto error_out; + } + sqlite3ValueSetStr(p, 0, "", 0, SQLITE_STATIC); + pSession->pZeroBlob = p; + } + } + /* Calculate the hash-key for this change. If the primary key of the row ** includes a NULL value, exit early. Such changes are ignored by the ** session module. */ @@ -1172,6 +1250,9 @@ static void sessionPreupdateOneChange( /* If an error has occurred, mark the session object as failed. */ error_out: + if( pTab->bStat1 ){ + pSession->hook = stat1.hook; + } if( rc!=SQLITE_OK ){ pSession->rc = rc; } @@ -1508,7 +1589,6 @@ int sqlite3session_diff( if( abPK[i] ) bHasPk = 1; } } - } sqlite3_free((char*)azCol); if( bMismatch ){ @@ -1634,6 +1714,7 @@ void sqlite3session_delete(sqlite3_session *pSession){ } } sqlite3_mutex_leave(sqlite3_db_mutex(db)); + sqlite3ValueFree(pSession->pZeroBlob); /* Delete all attached table objects. And the contents of their ** associated hash-tables. */ @@ -2101,28 +2182,41 @@ static int sessionSelectStmt( sqlite3_stmt **ppStmt /* OUT: Prepared SELECT statement */ ){ int rc = SQLITE_OK; - int i; - const char *zSep = ""; - SessionBuffer buf = {0, 0, 0}; + char *zSql = 0; + int nSql = -1; - sessionAppendStr(&buf, "SELECT * FROM ", &rc); - sessionAppendIdent(&buf, zDb, &rc); - sessionAppendStr(&buf, ".", &rc); - sessionAppendIdent(&buf, zTab, &rc); - sessionAppendStr(&buf, " WHERE ", &rc); - for(i=0; iaData[pIn->iNext++]; - } - - assert( apOut[i]==0 ); - if( eType ){ - apOut[i] = sqlite3ValueNew(0); - if( !apOut[i] ) rc = SQLITE_NOMEM; + if( pIn->iNext>=pIn->nData ){ + rc = SQLITE_CORRUPT_BKPT; + }else{ + eType = pIn->aData[pIn->iNext++]; + assert( apOut[i]==0 ); + if( eType ){ + apOut[i] = sqlite3ValueNew(0); + if( !apOut[i] ) rc = SQLITE_NOMEM; + } + } } if( rc==SQLITE_OK ){ @@ -2663,10 +2760,14 @@ static int sessionReadRecord( pIn->iNext += sessionVarintGet(aVal, &nByte); rc = sessionInputBuffer(pIn, nByte); if( rc==SQLITE_OK ){ - u8 enc = (eType==SQLITE_TEXT ? SQLITE_UTF8 : 0); - rc = sessionValueSetStr(apOut[i],&pIn->aData[pIn->iNext],nByte,enc); + if( nByte<0 || nByte>pIn->nData-pIn->iNext ){ + rc = SQLITE_CORRUPT_BKPT; + }else{ + u8 enc = (eType==SQLITE_TEXT ? SQLITE_UTF8 : 0); + rc = sessionValueSetStr(apOut[i],&pIn->aData[pIn->iNext],nByte,enc); + pIn->iNext += nByte; + } } - pIn->iNext += nByte; } if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ sqlite3_int64 v = sessionGetI64(aVal); @@ -2706,8 +2807,12 @@ static int sessionChangesetBufferTblhdr(SessionInput *pIn, int *pnByte){ rc = sessionInputBuffer(pIn, 9); if( rc==SQLITE_OK ){ nRead += sessionVarintGet(&pIn->aData[pIn->iNext + nRead], &nCol); - rc = sessionInputBuffer(pIn, nRead+nCol+100); - nRead += nCol; + if( nCol<0 ){ + rc = SQLITE_CORRUPT_BKPT; + }else{ + rc = sessionInputBuffer(pIn, nRead+nCol+100); + nRead += nCol; + } } while( rc==SQLITE_OK ){ @@ -2784,11 +2889,15 @@ static int sessionChangesetReadTblhdr(sqlite3_changeset_iter *p){ int nByte; int nVarint; nVarint = sessionVarintGet(&p->in.aData[p->in.iNext], &p->nCol); - nCopy -= nVarint; - p->in.iNext += nVarint; - nByte = p->nCol * sizeof(sqlite3_value*) * 2 + nCopy; - p->tblhdr.nBuf = 0; - sessionBufferGrow(&p->tblhdr, nByte, &rc); + if( p->nCol>0 ){ + nCopy -= nVarint; + p->in.iNext += nVarint; + nByte = p->nCol * sizeof(sqlite3_value*) * 2 + nCopy; + p->tblhdr.nBuf = 0; + sessionBufferGrow(&p->tblhdr, nByte, &rc); + }else{ + rc = SQLITE_CORRUPT_BKPT; + } } if( rc==SQLITE_OK ){ @@ -2866,6 +2975,13 @@ static int sessionChangesetNext( op = p->in.aData[p->in.iNext++]; } + if( p->zTab==0 ){ + /* The first record in the changeset is not a table header. Must be a + ** corrupt changeset. */ + assert( p->in.iNext==1 ); + return (p->rc = SQLITE_CORRUPT_BKPT); + } + p->op = op; p->bIndirect = p->in.aData[p->in.iNext++]; if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){ @@ -2908,9 +3024,9 @@ static int sessionChangesetNext( ** new.* to old.*, to accommodate the code that reads these arrays. */ for(i=0; inCol; i++){ assert( p->apValue[i]==0 ); - assert( p->abPK[i]==0 || p->apValue[i+p->nCol] ); if( p->abPK[i] ){ p->apValue[i] = p->apValue[i+p->nCol]; + if( p->apValue[i]==0 ) return (p->rc = SQLITE_CORRUPT_BKPT); p->apValue[i+p->nCol] = 0; } } @@ -3305,7 +3421,7 @@ struct SessionApplyCtx { int nCol; /* Size of azCol[] and abPK[] arrays */ const char **azCol; /* Array of column names */ u8 *abPK; /* Boolean array - true if column is in PK */ - + int bStat1; /* True if table is sqlite_stat1 */ int bDeferConstraints; /* True to defer constraints */ SessionBuffer constraints; /* Deferred constraints are stored here */ }; @@ -3475,6 +3591,7 @@ static int sessionUpdateRow( return rc; } + /* ** Formulate and prepare an SQL statement to query table zTab by primary ** key. Assuming the following table structure: @@ -3536,6 +3653,47 @@ static int sessionInsertRow( return rc; } +static int sessionPrepare(sqlite3 *db, sqlite3_stmt **pp, const char *zSql){ + return sqlite3_prepare_v2(db, zSql, -1, pp, 0); +} + +/* +** Prepare statements for applying changes to the sqlite_stat1 table. +** These are similar to those created by sessionSelectRow(), +** sessionInsertRow(), sessionUpdateRow() and sessionDeleteRow() for +** other tables. +*/ +static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){ + int rc = sessionSelectRow(db, "sqlite_stat1", p); + if( rc==SQLITE_OK ){ + rc = sessionPrepare(db, &p->pInsert, + "INSERT INTO main.sqlite_stat1 VALUES(?1, " + "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END, " + "?3)" + ); + } + if( rc==SQLITE_OK ){ + rc = sessionPrepare(db, &p->pUpdate, + "UPDATE main.sqlite_stat1 SET " + "tbl = CASE WHEN ?2 THEN ?3 ELSE tbl END, " + "idx = CASE WHEN ?5 THEN ?6 ELSE idx END, " + "stat = CASE WHEN ?8 THEN ?9 ELSE stat END " + "WHERE tbl=?1 AND idx IS " + "CASE WHEN length(?4)=0 AND typeof(?4)='blob' THEN NULL ELSE ?4 END " + "AND (?10 OR ?8=0 OR stat IS ?7)" + ); + } + if( rc==SQLITE_OK ){ + rc = sessionPrepare(db, &p->pDelete, + "DELETE FROM main.sqlite_stat1 WHERE tbl=?1 AND idx IS " + "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END " + "AND (?4 OR stat IS ?3)" + ); + } + assert( rc==SQLITE_OK ); + return rc; +} + /* ** A wrapper around sqlite3_bind_value() that detects an extra problem. ** See comments in the body of this function for details. @@ -3593,7 +3751,13 @@ static int sessionBindRow( if( !abPK || abPK[i] ){ sqlite3_value *pVal; (void)xValue(pIter, i, &pVal); - rc = sessionBindValue(pStmt, i+1, pVal); + if( pVal==0 ){ + /* The value in the changeset was "undefined". This indicates a + ** corrupt changeset blob. */ + rc = SQLITE_CORRUPT_BKPT; + }else{ + rc = sessionBindValue(pStmt, i+1, pVal); + } } } return rc; @@ -3866,11 +4030,25 @@ static int sessionApplyOneOp( }else{ assert( op==SQLITE_INSERT ); - rc = sessionBindRow(pIter, sqlite3changeset_new, nCol, 0, p->pInsert); - if( rc!=SQLITE_OK ) return rc; + if( p->bStat1 ){ + /* Check if there is a conflicting row. For sqlite_stat1, this needs + ** to be done using a SELECT, as there is no PRIMARY KEY in the + ** database schema to throw an exception if a duplicate is inserted. */ + rc = sessionSeekToRow(p->db, pIter, p->abPK, p->pSelect); + if( rc==SQLITE_ROW ){ + rc = SQLITE_CONSTRAINT; + sqlite3_reset(p->pSelect); + } + } + + if( rc==SQLITE_OK ){ + rc = sessionBindRow(pIter, sqlite3changeset_new, nCol, 0, p->pInsert); + if( rc!=SQLITE_OK ) return rc; + + sqlite3_step(p->pInsert); + rc = sqlite3_reset(p->pInsert); + } - sqlite3_step(p->pInsert); - rc = sqlite3_reset(p->pInsert); if( (rc&0xff)==SQLITE_CONSTRAINT ){ rc = sessionConflictHandler( SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, pbReplace @@ -4103,12 +4281,20 @@ static int sessionChangesetApply( } else{ sApply.nCol = nCol; - if((rc = sessionSelectRow(db, zTab, &sApply)) - || (rc = sessionUpdateRow(db, zTab, &sApply)) - || (rc = sessionDeleteRow(db, zTab, &sApply)) - || (rc = sessionInsertRow(db, zTab, &sApply)) - ){ - break; + if( 0==sqlite3_stricmp(zTab, "sqlite_stat1") ){ + if( (rc = sessionStat1Sql(db, &sApply) ) ){ + break; + } + sApply.bStat1 = 1; + }else{ + if((rc = sessionSelectRow(db, zTab, &sApply)) + || (rc = sessionUpdateRow(db, zTab, &sApply)) + || (rc = sessionDeleteRow(db, zTab, &sApply)) + || (rc = sessionInsertRow(db, zTab, &sApply)) + ){ + break; + } + sApply.bStat1 = 0; } } nTab = sqlite3Strlen30(zTab); diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h index 74e0693106..53f1ba2c69 100644 --- a/ext/session/sqlite3session.h +++ b/ext/session/sqlite3session.h @@ -13,16 +13,23 @@ extern "C" { /* ** CAPI3REF: Session Object Handle +** +** An instance of this object is a [session] that can be used to +** record changes to a database. */ typedef struct sqlite3_session sqlite3_session; /* ** CAPI3REF: Changeset Iterator Handle +** +** An instance of this object acts as a cursor for iterating +** over the elements of a [changeset] or [patchset]. */ typedef struct sqlite3_changeset_iter sqlite3_changeset_iter; /* ** CAPI3REF: Create A New Session Object +** CONSTRUCTOR: sqlite3_session ** ** Create a new session object attached to database handle db. If successful, ** a pointer to the new object is written to *ppSession and SQLITE_OK is @@ -59,6 +66,7 @@ int sqlite3session_create( /* ** CAPI3REF: Delete A Session Object +** DESTRUCTOR: sqlite3_session ** ** Delete a session object previously allocated using ** [sqlite3session_create()]. Once a session object has been deleted, the @@ -74,6 +82,7 @@ void sqlite3session_delete(sqlite3_session *pSession); /* ** CAPI3REF: Enable Or Disable A Session Object +** METHOD: sqlite3_session ** ** Enable or disable the recording of changes by a session object. When ** enabled, a session object records changes made to the database. When @@ -93,6 +102,7 @@ int sqlite3session_enable(sqlite3_session *pSession, int bEnable); /* ** CAPI3REF: Set Or Clear the Indirect Change Flag +** METHOD: sqlite3_session ** ** Each change recorded by a session object is marked as either direct or ** indirect. A change is marked as indirect if either: @@ -122,6 +132,7 @@ int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect); /* ** CAPI3REF: Attach A Table To A Session Object +** METHOD: sqlite3_session ** ** If argument zTab is not NULL, then it is the name of a table to attach ** to the session object passed as the first argument. All subsequent changes @@ -147,6 +158,35 @@ int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect); ** ** SQLITE_OK is returned if the call completes without error. Or, if an error ** occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned. +** +**

Special sqlite_stat1 Handling

+** +** As of SQLite version 3.22.0, the "sqlite_stat1" table is an exception to +** some of the rules above. In SQLite, the schema of sqlite_stat1 is: +**
+**        CREATE TABLE sqlite_stat1(tbl,idx,stat)  
+**  
+** +** Even though sqlite_stat1 does not have a PRIMARY KEY, changes are +** recorded for it as if the PRIMARY KEY is (tbl,idx). Additionally, changes +** are recorded for rows for which (idx IS NULL) is true. However, for such +** rows a zero-length blob (SQL value X'') is stored in the changeset or +** patchset instead of a NULL value. This allows such changesets to be +** manipulated by legacy implementations of sqlite3changeset_invert(), +** concat() and similar. +** +** The sqlite3changeset_apply() function automatically converts the +** zero-length blob back to a NULL value when updating the sqlite_stat1 +** table. However, if the application calls sqlite3changeset_new(), +** sqlite3changeset_old() or sqlite3changeset_conflict on a changeset +** iterator directly (including on a changeset iterator passed to a +** conflict-handler callback) then the X'' value is returned. The application +** must translate X'' to NULL itself if required. +** +** Legacy (older than 3.22.0) versions of the sessions module cannot capture +** changes made to the sqlite_stat1 table. Legacy versions of the +** sqlite3changeset_apply() function silently ignore any modifications to the +** sqlite_stat1 table that are part of a changeset or patchset. */ int sqlite3session_attach( sqlite3_session *pSession, /* Session object */ @@ -155,6 +195,7 @@ int sqlite3session_attach( /* ** CAPI3REF: Set a table filter on a Session Object. +** METHOD: sqlite3_session ** ** The second argument (xFilter) is the "filter callback". For changes to rows ** in tables that are not attached to the Session object, the filter is called @@ -173,6 +214,7 @@ void sqlite3session_table_filter( /* ** CAPI3REF: Generate A Changeset From A Session Object +** METHOD: sqlite3_session ** ** Obtain a changeset containing changes to the tables attached to the ** session object passed as the first argument. If successful, @@ -295,7 +337,8 @@ int sqlite3session_fullchangeset( ); /* -** CAPI3REF: Load The Difference Between Tables Into A Session +** CAPI3REF: Load The Difference Between Tables Into A Session +** METHOD: sqlite3_session ** ** If it is not already attached to the session object passed as the first ** argument, this function attaches table zTbl in the same manner as the @@ -360,6 +403,7 @@ int sqlite3session_diff( /* ** CAPI3REF: Generate A Patchset From A Session Object +** METHOD: sqlite3_session ** ** The differences between a patchset and a changeset are that: ** @@ -411,6 +455,7 @@ int sqlite3session_isempty(sqlite3_session *pSession); /* ** CAPI3REF: Create An Iterator To Traverse A Changeset +** CONSTRUCTOR: sqlite3_changeset_iter ** ** Create an iterator used to iterate through the contents of a changeset. ** If successful, *pp is set to point to the iterator handle and SQLITE_OK @@ -451,6 +496,7 @@ int sqlite3changeset_start( /* ** CAPI3REF: Advance A Changeset Iterator +** METHOD: sqlite3_changeset_iter ** ** This function may only be used with iterators created by function ** [sqlite3changeset_start()]. If it is called on an iterator passed to @@ -475,6 +521,7 @@ int sqlite3changeset_next(sqlite3_changeset_iter *pIter); /* ** CAPI3REF: Obtain The Current Operation From A Changeset Iterator +** METHOD: sqlite3_changeset_iter ** ** The pIter argument passed to this function may either be an iterator ** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator @@ -509,6 +556,7 @@ int sqlite3changeset_op( /* ** CAPI3REF: Obtain The Primary Key Definition Of A Table +** METHOD: sqlite3_changeset_iter ** ** For each modified table, a changeset includes the following: ** @@ -540,6 +588,7 @@ int sqlite3changeset_pk( /* ** CAPI3REF: Obtain old.* Values From A Changeset Iterator +** METHOD: sqlite3_changeset_iter ** ** The pIter argument passed to this function may either be an iterator ** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator @@ -570,6 +619,7 @@ int sqlite3changeset_old( /* ** CAPI3REF: Obtain new.* Values From A Changeset Iterator +** METHOD: sqlite3_changeset_iter ** ** The pIter argument passed to this function may either be an iterator ** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator @@ -603,6 +653,7 @@ int sqlite3changeset_new( /* ** CAPI3REF: Obtain Conflicting Row Values From A Changeset Iterator +** METHOD: sqlite3_changeset_iter ** ** This function should only be used with iterator objects passed to a ** conflict-handler callback by [sqlite3changeset_apply()] with either @@ -630,6 +681,7 @@ int sqlite3changeset_conflict( /* ** CAPI3REF: Determine The Number Of Foreign Key Constraint Violations +** METHOD: sqlite3_changeset_iter ** ** This function may only be called with an iterator passed to an ** SQLITE_CHANGESET_FOREIGN_KEY conflict handler callback. In this case @@ -646,6 +698,7 @@ int sqlite3changeset_fk_conflicts( /* ** CAPI3REF: Finalize A Changeset Iterator +** METHOD: sqlite3_changeset_iter ** ** This function is used to finalize an iterator allocated with ** [sqlite3changeset_start()]. @@ -662,6 +715,7 @@ int sqlite3changeset_fk_conflicts( ** to that error is returned by this function. Otherwise, SQLITE_OK is ** returned. This is to allow the following pattern (pseudo-code): ** +**
 **   sqlite3changeset_start();
 **   while( SQLITE_ROW==sqlite3changeset_next() ){
 **     // Do something with change.
@@ -670,6 +724,7 @@ int sqlite3changeset_fk_conflicts(
 **   if( rc!=SQLITE_OK ){
 **     // An error has occurred 
 **   }
+** 
*/ int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter); @@ -717,6 +772,7 @@ int sqlite3changeset_invert( ** sqlite3_changegroup object. Calling it produces similar results as the ** following code fragment: ** +**
 **   sqlite3_changegroup *pGrp;
 **   rc = sqlite3_changegroup_new(&pGrp);
 **   if( rc==SQLITE_OK ) rc = sqlite3changegroup_add(pGrp, nA, pA);
@@ -727,6 +783,7 @@ int sqlite3changeset_invert(
 **     *ppOut = 0;
 **     *pnOut = 0;
 **   }
+** 
** ** Refer to the sqlite3_changegroup documentation below for details. */ @@ -742,11 +799,15 @@ int sqlite3changeset_concat( /* ** CAPI3REF: Changegroup Handle +** +** A changegroup is an object used to combine two or more +** [changesets] or [patchsets] */ typedef struct sqlite3_changegroup sqlite3_changegroup; /* ** CAPI3REF: Create A New Changegroup Object +** CONSTRUCTOR: sqlite3_changegroup ** ** An sqlite3_changegroup object is used to combine two or more changesets ** (or patchsets) into a single changeset (or patchset). A single changegroup @@ -784,6 +845,7 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp); /* ** CAPI3REF: Add A Changeset To A Changegroup +** METHOD: sqlite3_changegroup ** ** Add all changes within the changeset (or patchset) in buffer pData (size ** nData bytes) to the changegroup. @@ -861,6 +923,7 @@ int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); /* ** CAPI3REF: Obtain A Composite Changeset From A Changegroup +** METHOD: sqlite3_changegroup ** ** Obtain a buffer containing a changeset (or patchset) representing the ** current contents of the changegroup. If the inputs to the changegroup @@ -891,6 +954,7 @@ int sqlite3changegroup_output( /* ** CAPI3REF: Delete A Changegroup Object +** DESTRUCTOR: sqlite3_changegroup */ void sqlite3changegroup_delete(sqlite3_changegroup*); diff --git a/main.mk b/main.mk index 9c5ac57b95..d833d2fa93 100644 --- a/main.mk +++ b/main.mk @@ -263,6 +263,24 @@ FTS5_SRC = \ $(TOP)/ext/fts5/fts5_varint.c \ $(TOP)/ext/fts5/fts5_vocab.c \ +LSM1_SRC = \ + $(TOP)/ext/lsm1/lsm.h \ + $(TOP)/ext/lsm1/lsmInt.h \ + $(TOP)/ext/lsm1/lsm_ckpt.c \ + $(TOP)/ext/lsm1/lsm_file.c \ + $(TOP)/ext/lsm1/lsm_log.c \ + $(TOP)/ext/lsm1/lsm_main.c \ + $(TOP)/ext/lsm1/lsm_mem.c \ + $(TOP)/ext/lsm1/lsm_mutex.c \ + $(TOP)/ext/lsm1/lsm_shared.c \ + $(TOP)/ext/lsm1/lsm_sorted.c \ + $(TOP)/ext/lsm1/lsm_str.c \ + $(TOP)/ext/lsm1/lsm_tree.c \ + $(TOP)/ext/lsm1/lsm_unix.c \ + $(TOP)/ext/lsm1/lsm_varint.c \ + $(TOP)/ext/lsm1/lsm_vtab.c \ + $(TOP)/ext/lsm1/lsm_win32.c + # Generated source code files # @@ -279,6 +297,8 @@ SRC += \ # Source code to the test files. # TESTSRC = \ + $(TOP)/ext/expert/sqlite3expert.c \ + $(TOP)/ext/expert/test_expert.c \ $(TOP)/ext/fts3/fts3_term.c \ $(TOP)/ext/fts3/fts3_test.c \ $(TOP)/ext/rbu/test_rbu.c \ @@ -341,6 +361,7 @@ TESTSRC += \ $(TOP)/ext/misc/ieee754.c \ $(TOP)/ext/misc/mmapwarm.c \ $(TOP)/ext/misc/nextchar.c \ + $(TOP)/ext/misc/normalize.c \ $(TOP)/ext/misc/percentile.c \ $(TOP)/ext/misc/regexp.c \ $(TOP)/ext/misc/remember.c \ @@ -350,9 +371,10 @@ TESTSRC += \ $(TOP)/ext/misc/unionvtab.c \ $(TOP)/ext/misc/wholenumber.c \ $(TOP)/ext/misc/vfslog.c \ + $(TOP)/ext/misc/zipfile.c \ $(TOP)/ext/fts5/fts5_tcl.c \ $(TOP)/ext/fts5/fts5_test_mi.c \ - $(TOP)/ext/fts5/fts5_test_tok.c + $(TOP)/ext/fts5/fts5_test_tok.c #TESTSRC += $(TOP)/ext/fts2/fts2_tokenizer.c @@ -401,6 +423,7 @@ TESTSRC2 = \ $(TOP)/ext/fts3/fts3_tokenizer.c \ $(TOP)/ext/fts3/fts3_write.c \ $(TOP)/ext/async/sqlite3async.c \ + $(TOP)/ext/misc/stmt.c \ $(TOP)/ext/session/sqlite3session.c \ $(TOP)/ext/session/sqlite3changebatch.c \ $(TOP)/ext/session/test_session.c @@ -467,7 +490,8 @@ TESTPROGS = \ sqlite3_analyzer$(EXE) \ sqlite3_checker$(EXE) \ sqldiff$(EXE) \ - dbhash$(EXE) + dbhash$(EXE) \ + sqltclsh$(EXE) # Databases containing fuzzer test cases # @@ -485,14 +509,18 @@ TESTOPTS = --verbose=file --output=test-out.txt # Extra compiler options for various shell tools # SHELL_OPT += -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS5 +SHELL_OPT += -DSQLITE_ENABLE_RTREE SHELL_OPT += -DSQLITE_ENABLE_EXPLAIN_COMMENTS SHELL_OPT += -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION SHELL_OPT += -DSQLITE_ENABLE_STMTVTAB SHELL_OPT += -DSQLITE_ENABLE_DBPAGE_VTAB SHELL_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB +SHELL_OPT += -DSQLITE_ENABLE_OFFSET_SQL_FUNC +SHELL_OPT += -DSQLITE_INTROSPECTION_PRAGMAS FUZZERSHELL_OPT = -DSQLITE_ENABLE_JSON1 FUZZCHECK_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5 FUZZCHECK_OPT += -DSQLITE_MAX_MEMORY=50000000 +FUZZCHECK_OPT += -DSQLITE_PRINTF_PRECISION_LIMIT=1000 DBFUZZ_OPT = KV_OPT = -DSQLITE_THREADSAFE=0 -DSQLITE_DIRECT_OVERFLOW_READ ST_OPT = -DSQLITE_THREADSAFE=0 @@ -671,9 +699,15 @@ keywordhash.h: $(TOP)/tool/mkkeywordhash.c # Source files that go into making shell.c SHELL_SRC = \ $(TOP)/src/shell.c.in \ + $(TOP)/ext/misc/appendvfs.c \ $(TOP)/ext/misc/shathree.c \ $(TOP)/ext/misc/fileio.c \ - $(TOP)/ext/misc/completion.c + $(TOP)/ext/misc/completion.c \ + $(TOP)/ext/misc/sqlar.c \ + $(TOP)/ext/expert/sqlite3expert.c \ + $(TOP)/ext/expert/sqlite3expert.h \ + $(TOP)/ext/misc/zipfile.c \ + $(TOP)/src/test_windirent.c shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl tclsh $(TOP)/tool/mkshellc.tcl >shell.c @@ -767,6 +801,10 @@ fts5.c: $(FTS5_SRC) $(FTS5_HDR) tclsh $(TOP)/ext/fts5/tool/mkfts5c.tcl cp $(TOP)/ext/fts5/fts5.h . +lsm1.c: $(LSM1_SRC) + tclsh $(TOP)/ext/lsm1/tool/mklsm1c.tcl + cp $(TOP)/ext/lsm1/lsm.h . + userauth.o: $(TOP)/ext/userauth/userauth.c $(HDR) $(EXTHDR) $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/userauth/userauth.c @@ -788,6 +826,15 @@ sqlite3_analyzer.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/spaceanal.tcl $ sqlite3_analyzer$(EXE): sqlite3_analyzer.c $(TCCX) $(TCL_FLAGS) sqlite3_analyzer.c -o $@ $(LIBTCL) $(THREADLIB) +sqltclsh.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/sqltclsh.tcl $(TOP)/ext/misc/appendvfs.c $(TOP)/tool/mkccode.tcl + tclsh $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqltclsh.c.in >sqltclsh.c + +sqltclsh$(EXE): sqltclsh.c + $(TCCX) $(TCL_FLAGS) sqltclsh.c -o $@ $(LIBTCL) $(THREADLIB) + +sqlite3_expert$(EXE): $(TOP)/ext/expert/sqlite3expert.h $(TOP)/ext/expert/sqlite3expert.c $(TOP)/ext/expert/expert.c sqlite3.c + $(TCCX) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION $(TOP)/ext/expert/sqlite3expert.c $(TOP)/ext/expert/expert.c sqlite3.c -o sqlite3_expert$(EXE) $(THREADLIB) + CHECKER_DEPS =\ $(TOP)/tool/mkccode.tcl \ sqlite3.c \ @@ -1017,9 +1064,11 @@ clean: rm -f sqlite3rc.h rm -f shell.c sqlite3ext.h rm -f sqlite3_analyzer sqlite3_analyzer.exe sqlite3_analyzer.c + rm -f sqlite3_expert sqlite3_expert.exe rm -f sqlite-*-output.vsix rm -f mptester mptester.exe rm -f fuzzershell fuzzershell.exe rm -f fuzzcheck fuzzcheck.exe rm -f sqldiff sqldiff.exe rm -f fts5.* fts5parse.* + rm -f lsm.h lsm1.c diff --git a/manifest b/manifest index dce0a2ddfa..69e9a19f7f 100644 --- a/manifest +++ b/manifest @@ -1,20 +1,22 @@ -C Abort\son\san\sinvalid\sparamater\sto\ssqlite3BitvecSet(). -D 2017-12-11T14:02:10.998 -F Makefile.in b142eb20482922153ebc77b261cdfd0a560ed05a81e9f6d9a2b0e8192922a1d2 +C Merge\slatest\strunk\schanges\sinto\sthis\sbranch. +D 2018-03-02T17:40:23.911 +F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 +F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea +F Makefile.in a2d2fb8d17c39ab5ec52beb27850b903949080848236923f436156b72a958737 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 -F Makefile.msc a55372a22454e742ba7c8f6edf05b83213ec01125166ad7dcee0567e2f7fc81b -F README.md f5c87359573c4d255425e588a56554b50fdcc2afba4e017a2e02a43701456afd -F VERSION 0c10cdfed866fdd2d80434f64f042c3330f1daaed12e54287beb104f04b3faaf +F Makefile.msc bf19d3a0eb849bd3b114653b0e455aa5b2799a96f413287a5866013db0e47f30 +F README.md 1d5342ebda97420f114283e604e5fe99b0da939d63b76d492eabbaae23488276 +F VERSION cdf91ac446255ecf3d8f6d8c3ee40d64123235ae5b3cef29d344e61b45ec3759 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 66c0befa511f0d95ba229e180067cf0357a9ebf8b3201b06d683c5ba6220fb39 -F autoconf/Makefile.msc 6143fe5b571cfeb0159702931d3ade664a00edc0c03814c7f6d825ae73eeffac +F autoconf/Makefile.am 2c274948734e03c51790ff51468f91db8d570bcca864284d9c6d6e777264cd7e +F autoconf/Makefile.msc 2c50a59319af7da4eaca8c13e3240881b1bc245fd175845a055faab7d03d6e67 F autoconf/README.first 6c4f34fe115ff55d4e8dbfa3cecf04a0188292f7 F autoconf/README.txt 4f04b0819303aabaa35fff5f7b257fb0c1ef95f1 -F autoconf/configure.ac 8dd08ca564279fff091c9bfdd2599d8f992c9f1f70c5396de2126ad2bd1b3bed +F autoconf/configure.ac 18fca06f884213be062dd5e07c5297079cc45893d9cd3f522ce426e715033e3d F autoconf/tea/Makefile.in b438a7020446c8a8156e8d97c8914a04833da6fd F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873 F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43 @@ -30,8 +32,8 @@ F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 226d9a188c6196f3033ffc651cbc9dcee1a42977 F config.h.in 6376abec766e9a0785178b1823b5a587e9f1ccbc F config.sub 9ebe4c3b3dab6431ece34f16828b594fb420da55 -F configure bdc49e9f0b0ced903ebdb2850362dd3391eeb88585e0429d12b94928d2873b6b x -F configure.ac 369ebae6c04d9d2de5064e21d300f2f42f2fbf13235cabff9d1a54f2b2c4d05d +F configure 2c71f331b463e987567a2dd942f728534f1aa7a174551e08a7b31b328e9da4ff x +F configure.ac d4529ebb26ae046269334f1dac65f2b1d6927c2efe22b2ec24dce24dfe4f83dd F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/lemon.html 278113807f49d12d04179a93fab92b5b917a08771152ca7949d34e928efa3941 F doc/pager-invariants.txt 27fed9a70ddad2088750c4a2b493b63853da2710 @@ -40,6 +42,12 @@ F ext/README.md fd5f78013b0a2bc6f0067afb19e6ad040e89a10179b4f6f03eee58fac5f169bd F ext/async/README.txt e12275968f6fde133a80e04387d0e839b0c51f91 F ext/async/sqlite3async.c 0f3070cc3f5ede78f2b9361fb3b629ce200d7d74 F ext/async/sqlite3async.h f489b080af7e72aec0e1ee6f1d98ab6cf2e4dcef +F ext/expert/README.md b321c2762bb93c18ea102d5a5f7753a4b8bac646cb392b3b437f633caf2020c3 +F ext/expert/expert.c 4791c5e064aea81b2b829fa95228b22283380ee370ea88a1e580103b75516ebf +F ext/expert/expert1.test fd21496d8e52c817a7741f467f42b0502c0ac7e07dcdd1d6e15a3e8154ed4e41 +F ext/expert/sqlite3expert.c 1dfa561e64dc0f89d56b96e6afda87468c34b43604c2df50c47e3f4362778fb2 +F ext/expert/sqlite3expert.h af6354f8ee5c9e025024e63fec3bd640a802afcc3099a44d804752cf0791d811 +F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72 F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e F ext/fts1/ft_hash.c 3927bd880e65329bdc6f506555b228b28924921b F ext/fts1/ft_hash.h 06df7bba40dadd19597aa400a875dbc2fed705ea @@ -88,7 +96,7 @@ F ext/fts3/fts3_tokenizer.h 64c6ef6c5272c51ebe60fc607a896e84288fcbc3 F ext/fts3/fts3_tokenizer1.c 5c98225a53705e5ee34824087478cf477bdb7004 F ext/fts3/fts3_unicode.c 525a3bd9a7564603c5c061b7de55403a565307758a94600e8a2f6b00d1c40d9d F ext/fts3/fts3_unicode2.c cc04fc672bfd42b1e650398cb0bf71f64f9aae032cfe75bbcfe75b9cf966029c -F ext/fts3/fts3_write.c a3f7bf869622d1d0aa66661ba71d88e6f9646d69a2c335f40a0addf25974db47 +F ext/fts3/fts3_write.c b583dede85eb0c3c3026f8d7ccb781ea4e845ae583754fecb2ca425b5907d87d F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9 F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100 F ext/fts3/tool/fts3cov.sh c331d006359456cf6f8f953e37f2b9c7d568f3863f00bb5f7eb87fea4ac01b73 @@ -99,31 +107,31 @@ F ext/fts3/unicode/mkunicode.tcl ab0543a3b2399092ea2dd75df1bef333405b0d7f6b8c495 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 15e7514b46a845937d7c62e5c69e935091f0dbb72eb61aa4c8bcfbd39fdea158 -F ext/fts5/fts5_aux.c 67acf8d51723cf28ffc3828210ba662df4b8d267 +F ext/fts5/fts5Int.h eda28e3a0a5d87c412e8355fe35da875b04cb389908c8eb0d867ad662adbc491 +F ext/fts5/fts5_aux.c ca666a3bbe07c5a3bbe9fffaea19c935a1efaf337333e28bad7bdd1971ffd093 F ext/fts5/fts5_buffer.c 1dd1ec0446b3acfc2d7d407eb894762a461613e2695273f48e449bfd13e973ff F ext/fts5/fts5_config.c 5af9c360e99669d29f06492c370892394aba0857 -F ext/fts5/fts5_expr.c f2825f714d91bbe62ab5820aee9ad12e0c94205b2a01725eaa9072415ae9ff1c +F ext/fts5/fts5_expr.c 01048018d21524e2c302b063ff5c3cdcf546e03297215e577205d85b47499deb F ext/fts5/fts5_hash.c 32be400cf761868c9db33efe81a06eb19a17c5402ad477ee9efb51301546dd55 -F ext/fts5/fts5_index.c 2ce9d50ec5508b8205615aad69e1c9b2c77f017f21d4479e1fb2079c01fdd017 +F ext/fts5/fts5_index.c 22b71d0e9e4b3ddd123a39ae27174e0012da2806f91b64087a68584f13f189de F ext/fts5/fts5_main.c 24868f88ab2a865defbba7a92eebeb726cc991eb092b71b5f5508f180c72605b -F ext/fts5/fts5_storage.c fb5ef3c27073f67ade2e1bea08405f9e43f68f5f3676ed0ab7013bce5ba10be6 -F ext/fts5/fts5_tcl.c a7df39442ae674dde877cf06fe02ebb7658e69c179a4d223241c90df4f14b54e +F ext/fts5/fts5_storage.c 4bec8a1b3905978b22a67bca5f4a3cfdb94af234cf51efb36f4f2d733d278634 +F ext/fts5/fts5_tcl.c 39bcbae507f594aad778172fa914cad0f585bf92fd3b078c686e249282db0d95 F ext/fts5/fts5_test_mi.c 65864ba1e5c34a61d409c4c587e0bbe0466eb4f8f478d85dc42a92caad1338e6 F ext/fts5/fts5_test_tok.c ffd657dd67e7fcdb31bf63fb60b6d867299a581d0f46e97086abacd66c2a9b26 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 1cd79854cb21543e66507b25b0578bc1b20aa6a1349b7feceb8e8fed0e7a77a6 -F ext/fts5/fts5parse.y a070b538e08ae9e2177d15c337ed2a3464408f0f886e746307098f746efd94ca +F ext/fts5/fts5parse.y eb526940f892ade5693f22ffd6c4f2702543a9059942772526eac1fde256bb05 F ext/fts5/mkportersteps.tcl 5acf962d2e0074f701620bb5308155fa1e4a63ba F ext/fts5/test/fts5_common.tcl b01c584144b5064f30e6c648145a2dd6bc440841 -F ext/fts5/test/fts5aa.test cba3fae6466446980caf1b9f5f26df77f95a999d35db7d932d6e82ae7ba0ede9 +F ext/fts5/test/fts5aa.test 6e2fdb0ee667c05f41921e7ec345cae874be651670900918e9ccc539514b9356 F ext/fts5/test/fts5ab.test 9205c839332c908aaad2b01ab8670ece8b161e8f2ec8a9fabf18ca9385880bb7 F ext/fts5/test/fts5ac.test a7aa7e1fefc6e1918aa4d3111d5c44a09177168e962c5fd2cca9620de8a7ed6d F ext/fts5/test/fts5ad.test e8cf959dfcd57c8e46d6f5f25665686f3b6627130a9a981371dafdf6482790de F ext/fts5/test/fts5ae.test 1142d16d9cc193894dc13cc8f9c7a8a21411ac61b5567a878514df6f9f0d7bb7 -F ext/fts5/test/fts5af.test aa635947bda31ac87fbe99483eef4d9a8571f58ad89c75dfb63312a35688eceb +F ext/fts5/test/fts5af.test 724247405b13f8f06cc6ce464dc4f152dc5dd4e86b12c2099685d8f19747bf7b F ext/fts5/test/fts5ag.test 7816f25a0707578f08145ab539fc0ca025f8951e788b28a6a18a06b2099469dd F ext/fts5/test/fts5ah.test 27b5a33bfd0363ca8a4dc659e6e2a5df3dea1c3c5b04bc51ca6aeb1277bd9b21 F ext/fts5/test/fts5ai.test d837c42249c0d8ad1a2912270e22cf2f303790a611f85c0be3a58e42a3696e3d @@ -157,13 +165,14 @@ F ext/fts5/test/fts5fault2.test 69c8fdbef830cd0d450908d4504d5bb86609e255af99c421 F ext/fts5/test/fts5fault3.test da2f9e3e56ff5740d68ebdd6877c97089e7ed28ddff28a0da87a6afea27e5522 F ext/fts5/test/fts5fault4.test 1c1db5fcfe59401e7833146100f1d8de284a0a686fac31ddac9fb56c459f725b F ext/fts5/test/fts5fault5.test a336e4e11847de24c9497f80cce18e00bb3fab7fb11f97d04eb9af898900a762 -F ext/fts5/test/fts5fault6.test 8a3c61402e36960ba46a419e73121fcefdc9160e0c04b6f5318c7fb0e3180dbc +F ext/fts5/test/fts5fault6.test a0fc0a8f99e4b16500c31dfc7e38e1defe0f1693ac47650517ac7b723b1956f8 F ext/fts5/test/fts5fault7.test 0acbec416edb24b8881f154e99c31e9ccf73f539cfcd164090be139e9e97ed4c F ext/fts5/test/fts5fault8.test 318238659d35f82ad215ecb57ca4c87486ea85d45dbeedaee42f148ff5105ee2 -F ext/fts5/test/fts5fault9.test 0111b229388bdf251b91cfead68580227801dd30960a19aa8fe9021a1e73cb6d +F ext/fts5/test/fts5fault9.test 098e6b894bbdf9b2192f994a30f4043673fb3f338b6b8ab1624c704422f39119 F ext/fts5/test/fts5faultA.test be4487576bff8c22cee6597d1893b312f306504a8c6ccd3c53ca85af12290c8c -F ext/fts5/test/fts5faultB.test 28810d93d37b59ebd5cf9502897f4dc9e6adb8ea6a5f64e125d3088597199d0d +F ext/fts5/test/fts5faultB.test e6d04f9ea7b21be1d89abb8df2cb4baf65b0453b744d5a805fcd3ef45ff86a7e F ext/fts5/test/fts5faultD.test cc5d1225556e356615e719c612e845d41bff7d5a +F ext/fts5/test/fts5first.test 707a591b1b7d893fcfcb2366cbfe56aefab5d9c7cfa58bef35eba73a1dbf3b29 F ext/fts5/test/fts5full.test 49b565da02918c06e58f51f0b953b0302b96f155aa68baba24782b81570685e2 F ext/fts5/test/fts5fuzz1.test 238d8c45f3b81342aa384de3e581ff2fa330bf922a7b69e484bbc06051a1080e F ext/fts5/test/fts5hash.test a4cf51acad99bfc43c16fb74f9d22495dc221ae0701fc5e908ca963a9b26a02b @@ -182,7 +191,7 @@ F ext/fts5/test/fts5plan.test e30e8378441114ef6977a3dc24ecd203caa670d782124dfc9a F ext/fts5/test/fts5porter.test 8d08010c28527db66bc3feebd2b8767504aaeb9b101a986342fa7833d49d0d15 F ext/fts5/test/fts5porter2.test 0d251a673f02fa13ca7f011654873b3add20745f7402f108600a23e52d8c7457 F ext/fts5/test/fts5prefix.test a0fa67b06650f2deaa7bf27745899d94e0fb547ad9ecbd08bfad98c04912c056 -F ext/fts5/test/fts5query.test bdb6fd9e73268cfc07f789f1448cd71ea78acb02e481c619f286289ea18ca518 +F ext/fts5/test/fts5query.test ac363b17a442620bb0780e93c24f16a5f963dfe2f23dc85647b869efcfada728 F ext/fts5/test/fts5rank.test 6e149da77a269923a8439aaa52366e49b85be4721902662da39a5ded16ed85d9 F ext/fts5/test/fts5rebuild.test 6d09fd54b1170a1e54fe17b808bbf17fba3154956cc2f065dd94bf1e3d254f63 F ext/fts5/test/fts5restart.test 835ecc8f449e3919f72509ab58056d0cedca40d1fe04108ccf8ac4c2ba41f415 @@ -209,12 +218,12 @@ 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 635775226d07c743c770888a9dd5175afc6e67d3e28a4032b7fedc3bcaa92e65 +F ext/icu/icu.c c2c7592574c08cd1270d909b8fb8797f6ea1f49e931e71dbcc25506b9b224580 F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37 F ext/lsm1/Makefile 98b0a24b45e248283d6bea4b6cb3e58d7b394edd8e96a0ac28c5fa5104813bad F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 F ext/lsm1/lsm-test/README 87ea529d2abe615e856d4714bfe8bb185e6c2771b8612aa6298588b7b43e6f86 -F ext/lsm1/lsm-test/lsmtest.h 5847594d4b43ec3412e1fd97104f7eb5fd770be55e691e6cb2e80929f86bebe3 +F ext/lsm1/lsm-test/lsmtest.h cf58528ffe0cfe535e91b44584e2ec5fb1caacdabecef0d8dcf83bf83168bf28 F ext/lsm1/lsm-test/lsmtest1.c 33158978327f800e82b6a47c09b86ace809f56a9ff10b0162273ec1186cc3153 F ext/lsm1/lsm-test/lsmtest2.c 188b09aec776516aeedcfd13b9c6faf85ba16b3671a0897a2c740ee00a5dc4f8 F ext/lsm1/lsm-test/lsmtest3.c 9ab87528a36dbf4a61d7c8ad954f5ee368c0878c127b84b942b2e2abe522de26 @@ -230,23 +239,23 @@ F ext/lsm1/lsm-test/lsmtest_func.c 159aa401bc8032bfa3d8cf2977bd687abebab88025589 F ext/lsm1/lsm-test/lsmtest_io.c cf11b27b129c6bd5818fa1d440176502dc27229f0db892b4479118d61993ea20 F ext/lsm1/lsm-test/lsmtest_main.c a9bc647738c0dcaebf205d6d194b3ce4a6ef3925801cd2d919f0a4ea33a15aeb F ext/lsm1/lsm-test/lsmtest_mem.c 4e63c764345ab1df59d4f13a77980c6f3643798210b10d6cdbd785b4b888fda5 -F ext/lsm1/lsm-test/lsmtest_tdb.c 555fb101d2fe638abdd133e9294536c857fb38e0f227e049c00a67f51eaece06 +F ext/lsm1/lsm-test/lsmtest_tdb.c 618a8619183fda4f5540fcde15f9068293c5e3180e1a246e34409b0c148758b3 F ext/lsm1/lsm-test/lsmtest_tdb.h 8733eee249b12956a9df8322994b43d19bd8c02ad2e8b0bb5164db4d6ccc1735 F ext/lsm1/lsm-test/lsmtest_tdb2.cc 99ea7f2dd9c7536c8fb9bdd329e4cfeb76899f3ddf6f48bdd3926e016922b715 -F ext/lsm1/lsm-test/lsmtest_tdb3.c e44bf94e8bd724cd6ac161fd2cd44ffe43193932ad3a6bee1b07d80bb74012bb +F ext/lsm1/lsm-test/lsmtest_tdb3.c b4e46b1d2fec553fe4efb44e341b43abd20556fde610db0cfffdc2300b72defe F ext/lsm1/lsm-test/lsmtest_tdb4.c 47e8bb5eba266472d690fb8264f1855ebdba0ae5a0e541e35fcda61ebf1d277f F ext/lsm1/lsm-test/lsmtest_util.c 241622db5a332a09c8e6e7606b617d288a37b557f7d3bce0bb97809f67cc2806 F ext/lsm1/lsm-test/lsmtest_win32.c 0e0a224674c4d3170631c41b026b56c7e1672b151f5261e1b4cc19068641da2d F ext/lsm1/lsm.h 0f6f64ff071471cb87bf98beb8386566f30ea001 -F ext/lsm1/lsmInt.h e9e5c5f08e35a104086102b3def94ee69cbc0d39002f6596f5c80a640439628e -F ext/lsm1/lsm_ckpt.c ac6fb4581983291c2e0be6fbb68f12b26f0c08d606835c05417be1323d0fdd03 -F ext/lsm1/lsm_file.c 4b3fb56336fbc9d941e1b2042e809d986feebdc41e73dc7fc4fdc0dd1bd4274d +F ext/lsm1/lsmInt.h 5983690e05e83653cc01ba9d8fbf8455e534ddf8349ed9adedbf46a7549760b0 +F ext/lsm1/lsm_ckpt.c 0eabfaf812ddb4ea43add38f05e430694cd054eb622c3e35af4c43118a2d5321 +F ext/lsm1/lsm_file.c 3c51841d5b3e7da162693cbac9a9f47eeedf6bcbbe2969a4d25e30c428c9fe36 F ext/lsm1/lsm_log.c a8bf334532109bba05b09a504ee45fc393828b0d034ca61ab45e3940709d9a7c -F ext/lsm1/lsm_main.c 15e73ccdafdd44ddeefc29e332079d88ba8f00c12c797b3c2b63d3171b5afce8 +F ext/lsm1/lsm_main.c b5703f8042e71d3a2d65e671f6832e077e79e89e9975818f67f969922618db63 F ext/lsm1/lsm_mem.c 4c51ea9fa285ee6e35301b33491642d071740a0a F ext/lsm1/lsm_mutex.c 378edf0a2b142b4f7640ee982df06d50b98788ea F ext/lsm1/lsm_shared.c 76adfc1ed9ffebaf92746dde4b370ccc48143ca8b05b563816eadd2aadf1c525 -F ext/lsm1/lsm_sorted.c a04518dfbfff0171fafb152a46e9fe9f45e1edbf3570e4533dd58ddb6567f0c9 +F ext/lsm1/lsm_sorted.c df7b393d9e4b85e6ad07181c6434d58a69a9145ee925d89c3ee1d2a210adaf9a F ext/lsm1/lsm_str.c 65e361b488c87b10bf3e5c0070b14ffc602cf84f094880bece77bbf6678bca82 F ext/lsm1/lsm_tree.c 682679d7ef2b8b6f2fe77aeb532c8d29695bca671c220b0abac77069de5fb9fb F ext/lsm1/lsm_unix.c 57361bcf5b1a1a028f5d66571ee490e9064d2cfb145a2cc9e5ddade467bb551b @@ -255,42 +264,48 @@ F ext/lsm1/lsm_vtab.c 529255dc704289001b225d97e57e0cfa14b29c3f281c7349cfa8fdb655 F ext/lsm1/lsm_win32.c 0a4acbd7e8d136dd3a5753f0a9e7a9802263a9d96cef3278cf120bcaa724db7c F ext/lsm1/test/lsm1_common.tcl 5ed4bab07c93be2e4f300ebe46007ecf4b3e20bc5fbe1dedaf04a8774a6d8d82 F ext/lsm1/test/lsm1_simple.test ca949efefa102f4644231dcd9291d8cda7699a4ce1006b26e0e3fcb72233f422 -F ext/misc/README.md 8e008c8d2b02e09096b31dfba033253ac27c6c06a18aa5826e299fa7601d90b2 +F ext/lsm1/tool/mklsm1c.tcl f31561bbee5349f0a554d1ad7236ac1991fc09176626f529f6078e07335398b0 +F ext/misc/README.md d6dd0fe1d8af77040216798a6a2b0c46c73054d2f0ea544fbbcdccf6f238c240 F ext/misc/amatch.c 6db4607cb17c54b853a2d7c7c36046d004853f65b9b733e6f019d543d5dfae87 F ext/misc/anycollseq.c 5ffdfde9829eeac52219136ad6aa7cd9a4edb3b15f4f2532de52f4a22525eddb -F ext/misc/btreeinfo.c d7fd9a2fe2fa33ba28488e2fce703ebecc759219ea9e0bb3b254784866c0a676 +F ext/misc/appendvfs.c 3777f22ec1057dc4e5fd89f2fbddcc7a29fbeef1ad038c736c54411bb1967af7 +F ext/misc/btreeinfo.c 78c8c57d325185ccc04b7679e5b020e34a4d9c87453e6b7ac943d0a26cee3256 F ext/misc/carray.c ed96c218ea940b85c9a274c4d9c59fe9491c299147a38a8bba537687bd6c6005 F ext/misc/closure.c 0d2a038df8fbae7f19de42e7c7d71f2e4dc88704 F ext/misc/completion.c 52c3f01523e3e387eb321b4739a89d1fe47cbe6025aa1f2d8d3685e9e365df0f -F ext/misc/compress.c 122faa92d25033d6c3f07c39231de074ab3d2e83 +F ext/misc/compress.c dd4f8a6d0baccff3c694757db5b430f3bbd821d8686d1fc24df55cf9f035b189 F ext/misc/csv.c 1a009b93650732e22334edc92459c4630b9fa703397cbb3c8ca279921a36ca11 F ext/misc/dbdump.c 3509fa6b8932d04e932d6b6b827b6a82ca362781b8e8f3c77336f416793e215e F ext/misc/eval.c f971962e92ebb8b0a4e6b62949463ee454d88fa2 -F ext/misc/fileio.c b1aa06c0f1dac277695d4529e5e976c65ab5678dcbb53a0304deaa8adc44b332 +F ext/misc/fileio.c 4cad3a78bfbbea9b1414b4405fd704ee57fbe2621254b011888dae7b0ba8c513 F ext/misc/fuzzer.c 7c64b8197bb77b7d64eff7cac7848870235d4c25 F ext/misc/ieee754.c f190d0cc5182529acb15babd177781be1ac1718c F ext/misc/json1.c dbe086615b9546c156bf32b9378fc09383b58bd17513b866cfd24c1e15281984 -F ext/misc/memvfs.c e5225bc22e79dde6b28380f3a068ddf600683a33 +F ext/misc/memvfs.c ab36f49e02ebcdf85a1e08dc4d8599ea8f343e073ac9e0bca18a98b7e1ec9567 F ext/misc/mmapwarm.c 70b618f2d0bde43fae288ad0b7498a629f2b6f61b50a27e06fae3cd23c83af29 F ext/misc/nextchar.c 35c8b8baacb96d92abbb34a83a997b797075b342 +F ext/misc/normalize.c 19262ef3ef29d4de2f281b423326865c8916c63d0cb09f1dc98d24d5c1e8ba64 F ext/misc/percentile.c 92699c8cd7d517ff610e6037e56506f8904dae2e F ext/misc/regexp.c a68d25c659bd2d893cd1215667bbf75ecb9dc7d4 F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c -F ext/misc/rot13.c 1ac6f95f99b575907b9b09c81a349114cf9be45a +F ext/misc/rot13.c 540a169cb0d74f15522a8930b0cccdcb37a4fd071d219a5a083a319fc6e8db77 F ext/misc/scrub.c 1c5bfb8b0cd18b602fcb55755e84abf0023ac2fb F ext/misc/series.c f3c0dba5c5c749ce1782b53076108f87cf0b71041eb6023f727a9c50681da564 F ext/misc/sha1.c 0b9e9b855354910d3ca467bf39099d570e73db56 -F ext/misc/shathree.c fa185d7aee0ad0aca5e091b4a2db7baff11796170e5793b5de99e511a13af448 +F ext/misc/shathree.c 9e960ba50483214c6a7a4b1517f8d8cef799e9db381195178c3fd3ad207e10c0 F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52 -F ext/misc/spellfix.c 41cf26c6b89fcaa8798ae10ae64d39c1f1d9d6995152e545bd491c13058b8fac +F ext/misc/spellfix.c b3a644285cb008f3c10ed4cf04e17adcbc7d62c3911c79d786dfc91c177534f0 +F ext/misc/sqlar.c 57d5bc45cd5492208e451f697404be88f8612527d64c9d42f96b325b64983d74 F ext/misc/stmt.c 6f16443abb3551e3f5813bb13ba19a30e7032830015b0f92fe0c0453045c0a11 F ext/misc/totype.c 4a167594e791abeed95e0a8db028822b5e8fe512 -F ext/misc/unionvtab.c 1e0ebc5078e1a916db191bcd88f87e94ea7ba4aa563ee30ff706261cb4b39461 +F ext/misc/unionvtab.c 0b3173f69b8899da640a13a345dc5ef1400199405f738abe6145b2454195b8ff F ext/misc/vfslog.c fe40fab5c077a40477f7e5eba994309ecac6cc95 F ext/misc/vfsstat.c bf10ef0bc51e1ad6756629e1edb142f7a8db1178 F ext/misc/vtablog.c 31d0d8f4406795679dcd3a67917c213d3a2a5fb3ea5de35f6e773491ed7e13c9 F ext/misc/vtshim.c 1976e6dd68dd0d64508c91a6dfab8e75f8aaf6cd F ext/misc/wholenumber.c 784b12543d60702ebdd47da936e278aa03076212 +F ext/misc/zipfile.c bc88dbf9aa26c24595d158c1398208786ef34e7aee19ea29e8f0110c752fca03 +F ext/misc/zorder.c b0ff58fa643afa1d846786d51ea8d5c4b6b35aa0254ab5a82617db92f3adda64 F ext/rbu/rbu.c ea7d1b7eb44c123a2a619332e19fe5313500705c4a58aaa1887905c0d83ffc2e F ext/rbu/rbu1.test 43836fac8c7179a358eaf38a8a1ef3d6e6285842 F ext/rbu/rbu10.test 1846519a438697f45e9dcb246908af81b551c29e1078d0304fae83f1fed7e9ee @@ -317,6 +332,7 @@ F ext/rbu/rbufault2.test 9a7f19edd6ea35c4c9f807d8a3db0a03a5670c06 F ext/rbu/rbufault3.test 0913c1aeaee266d9c36c33179341a5a504aad7d423d1979cfec43c8346a29899 F ext/rbu/rbufault4.test 34e70701cbec51571ffbd9fbf9d4e0f2ec495ca7 F ext/rbu/rbufts.test a2bbd202c9321fba15fb4a62a90add7d70e07bd8404e1e598135adbfff8a0508 +F ext/rbu/rbumulti.test 2cf153ab3d5861ff26517dc6cbaec430787a59f1d50e8771fe7a7529a0551cf1 F ext/rbu/rbuprogress.test 1849d4e0e50616edf5ce75ce7db86622e656b5cf F ext/rbu/rburesume.test 8acb77f4a422ff55acfcfc9cc15a5cb210b1de83 F ext/rbu/rbusave.test 0f43b6686084f426ddd040b878426452fd2c2f48 @@ -336,14 +352,14 @@ F ext/repair/test/checkfreelist01.test 3e8aa6aeb4007680c94a8d07b41c339aa635cc782 F ext/repair/test/checkindex01.test 6945d0ffc0c1dc993b2ce88036b26e0f5d6fcc65da70fc9df27c2647bb358b0f F ext/repair/test/test.tcl 686d76d888dffd021f64260abf29a55c57b2cedfa7fc69150b42b1d6119aac3c F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 -F ext/rtree/rtree.c cc91b6905bf55512c6ebc7dfdd37ac81c86f1753db8cfa6d62f0ee864464044f -F ext/rtree/rtree.h 834dbcb82dc85b2481cde6a07cdadfddc99e9b9e -F ext/rtree/rtree1.test 82a353747fcab1083d114b2ac84723dfefdbf86c1a6e1df57bf588c7d4285436 +F ext/rtree/rtree.c bc61010e978b5b8ae6dbb90274a2fbb5db5ff5e2880b5c6e8abd48eea77264db +F ext/rtree/rtree.h 4a690463901cb5e6127cf05eb8e642f127012fd5003830dbc974eca5802d9412 +F ext/rtree/rtree1.test 47e2095bebea6813754fd7afa6a20e2b7b4ebcd5cb7dbcb6932b6c9f86bbf972 F ext/rtree/rtree2.test 5f25b01acd03470067a2d52783b2eb0a50bf836803d4342d20ca39e541220fe2 F ext/rtree/rtree3.test 2cafe8265d1ff28f206fce88d114f208349df482 -F ext/rtree/rtree4.test 67b021858ba4334c8d49b3449476942c2ce0e5ef7123538f2e9dd508ed03a12d -F ext/rtree/rtree5.test 8aaa4bcdc42f718fe165572f5623e4732831aca95a2bc32482d33d4d2cf1325d -F ext/rtree/rtree6.test 773a90db2dce6a8353dd0d5b64bca69b29761196 +F ext/rtree/rtree4.test 304de65d484540111b896827e4261815e5dca4ce28eeecd58be648cd73452c4b +F ext/rtree/rtree5.test 49c9041d713d54560b315c2c7ef7207ee287eba1b20f8266968a06f2e55d3142 +F ext/rtree/rtree6.test 916a641d2beac01b9880871ff07612d56c1e466190a27c82ab36ffd58be03b9f F ext/rtree/rtree7.test c8fb2e555b128dd0f0bdb520c61380014f497f8a23c40f2e820acc9f9e4fdce5 F ext/rtree/rtree8.test 649f5a37ec656028a4a32674b9b1183104285a7625a09d2a8f52a1cef72c93f2 F ext/rtree/rtree9.test c646f12c8c1c68ef015c6c043d86a0c42488e2e68ed1bb1b0771a7ca246cbabf @@ -353,7 +369,7 @@ F ext/rtree/rtreeC.test d9d06dda1aee68b4dc227dfcc899f335f8b621e9d1920ee3d4e5dab8 F ext/rtree/rtreeD.test fe46aa7f012e137bd58294409b16c0d43976c3bb92c8f710481e577c4a1100dc F ext/rtree/rtreeE.test e65d3fc625da1800b412fc8785817327d43ccfec5f5973912d8c9e471928caa9 F ext/rtree/rtreeF.test 81ffa7ef51c4e4618d497a57328c265bf576990c7070633b623b23cd450ed331 -F ext/rtree/rtreeG.test fd3af1ca944a0bdb0cbb5455a4905c9f012e2fffcab6b791f07afa0dcbbcae0e +F ext/rtree/rtreeG.test 1b9ca6e3effb48f4161edaa463ddeaa8fca4b2526d084f9cbf5dbe4e0184939c F ext/rtree/rtree_perf.tcl 6c18c1f23cd48e0f948930c98dfdd37dfccb5195 F ext/rtree/rtree_util.tcl db734b4c5e75fed6acc56d9701f2235345acfdec750b5fc7b587936f5f6bceed F ext/rtree/rtreecheck.test 4d29103d1e16fcbf90135d1c637b833688492b063b2971dfb5dc6ba76555cfee @@ -367,7 +383,7 @@ F ext/session/changeset.c 4ccbaa4531944c24584bf6a61ba3a39c62b6267a F ext/session/session1.test 736d7ff178662f0b717c37f46531b84a5ce0210ccb0c4edf629c55dbcbbc3ea1 F ext/session/session2.test 284de45abae4cc1082bc52012ee81521d5ac58e0 F ext/session/session3.test ce9ce3dfa489473987f899e9f6a0f2db9bde3479 -F ext/session/session4.test 457b02bdc349eb01151e54de014df77abd3c08c8 +F ext/session/session4.test efd7a46ed6a954d51ab00bdc4d656d2bc31e46be64393224cf6acf1319fbd32c F ext/session/session5.test 716bc6fafd625ce60dfa62ae128971628c1a1169 F ext/session/session6.test 443789bc2fca12e4f7075cf692c60b8a2bea1a26 F ext/session/session8.test 8e194b3f655d861ca36de5d4de53f702751bab3b @@ -375,21 +391,23 @@ F ext/session/session9.test 5409d90d8141881d08285ed1c2c0d8d10fb92069 F ext/session/sessionA.test 1feeab0b8e03527f08f2f1defb442da25480138f F ext/session/sessionB.test 886252dcb7e692e62ef7e357456200912e367823 F ext/session/sessionC.test 97556f5164ac29f2344b24bd7de6a3a35a95c390 -F ext/session/sessionD.test d4744c78334162851d2a2f285c7e603e31b49aa2 +F ext/session/sessionD.test d3617e29aa15c9413aee5286d99587633245d58d2ad28f3f331c822735418a22 F ext/session/sessionE.test 0a616c4ad8fd2c05f23217ebb6212ef80b7fef30f5f086a6633a081f93e84637 F ext/session/sessionF.test c2f178d4dfd723a5fd94a730ea2ccb44c669e3ce -F ext/session/sessionG.test 01ef705096a9d3984eebdcca79807a211dee1b60 +F ext/session/sessionG.test 63f9a744341d670775af29e4f19c1ef09a4810798400f28cd76704803a2e56ff +F ext/session/sessionH.test 332b60e4c2e0a680105e11936201cabe378216f307e2747803cea56fa7d9ebae F ext/session/session_common.tcl 7776eda579773113b30c7abfd4545c445228cb73 F ext/session/session_speed_test.c edc1f96fd5e0e4b16eb03e2a73041013d59e8723 -F ext/session/sessionat.test feb7d22b3124882064b9d9df69f5484a9bb8c123dc9ddc6ffcd357521848139f +F ext/session/sessionat.test efe88965e74ff1bc2af9c310b28358c02d420c1fb2705cc7a28f0c1cc142c3ec F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7 F ext/session/sessionfault2.test 04aa0bc9aa70ea43d8de82c4f648db4de1e990b0 +F ext/session/sessionstat1.test 41cd97c2e48619a41cdf8ae749e1b25f34719de638689221aa43971be693bf4e F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc F ext/session/sqlite3changebatch.c d5553b79e012ee2cb06c0a96bdf9dfe19e66354390ea0036cc46c4953142d517 F ext/session/sqlite3changebatch.h e72016998c9a22d439ddfd547b69e1ebac810c24 -F ext/session/sqlite3session.c 97e478b348bcf22bc39e91430bd077e1030c6529009c8d7fb3d6731e40b02436 -F ext/session/sqlite3session.h 50f6aacf6439684185cb168000363ade106076162a3a3951afaff990321a6f64 +F ext/session/sqlite3session.c 21810bc8a5571ebd4263053a2e4bc4e534444336a62248d60a08bae43ea62f20 +F ext/session/sqlite3session.h 1eb6bc132e1e23eba7290b8cb85141fa4ef6cb5c8104a6851a4c4e88512996b6 F ext/session/test_session.c badd5da3cb561564b093745f7d843430d1d76347 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 @@ -397,7 +415,7 @@ F ext/userauth/userauth.c 3410be31283abba70255d71fd24734e017a4497f F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60 -F main.mk 709d0c6fb5af0c8de9f72cd4b1e28c386dddd936a01695949eadec02f728ab32 +F main.mk 2263d738b707d420c24535a85aad10ab35be5d90a14660b325b2bc99e8b91085 F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 @@ -410,37 +428,37 @@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786 F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a F src/alter.c cf7a8af45cb0ace672f47a1b29ab24092a9e8cd8d945a9974e3b5d925f548594 -F src/analyze.c 0d0ccf7520a201d8747ea2f02c92c26e26f801bc161f714f27b9f7630dde0421 -F src/attach.c 07b706e336fd3cedbd855e1f8266d10e82fecae07daf86717b5760cd7784c584 +F src/analyze.c 6b42e36a5dcc2703a771f2411bd5e99524bd62c7ecde209bb88dfb04c72f046e +F src/attach.c 79cb6b365d79b96b15dd429aa3e4c3b99335039e059baca8df28f1708fbff261 F src/auth.c 6277d63837357549fe14e723490d6dc1a38768d71c795c5eb5c0f8a99f918f73 F src/backup.c faf17e60b43233c214aae6a8179d24503a61e83b F src/bitvec.c 8433d9e98dd6f2ea3286e0d2fe5d65de1bfc18a706486eb2026b01be066b5806 F src/btmutex.c 0e9ce2d56159b89b9bc8e197e023ee11e39ff8ca -F src/btree.c a41d580524a4cd9b00443b7196d298e05463f6bfcb712853db3abcddc93cf3ab -F src/btree.h feafd0647331366f4ef17f7e68597e9029f001e7ab16a125e2f176c598a7ef4a -F src/btreeInt.h 0e0abe97427b4139092ec8782d396a4ad18566964e992c60043e370d4c86fd99 -F src/build.c f890a66f2b78cd820b21b580f37605f8dd77f19d0b35f5850a675c88a815adca -F src/callback.c 28a8ede982fde4129b828350f78f2c01fe7d12c74d1a0a05d7108ab36f308688 +F src/btree.c d070d79e81d76e521992a1f2e6b55892da04c35bbe493a07a3904a404e4416cc +F src/btree.h 0cd745755efd1f3df4c70544c54253920ea32fe6b179b97e9daeb786ba0de4ba +F src/btreeInt.h 6c65e6c96f561596f6870c79a64d4706af81613881d7947e3f063e923f14115f +F src/build.c 4584d3b8bf05dc3c6b58a3869d66707adf1a2d0c0dd278cbdf6698a3bfc95cc6 +F src/callback.c fe677cb5f5abb02f7a772a62a98c2f516426081df68856e8f2d5f950929b966a F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e F src/ctime.c ff1be3eed7bdd75aaca61ca8dc848f7c9f850ef2fb9cb56f2734e922a098f9c0 -F src/date.c 48f743d88bbe88f848532d333cca84f26e52a4f217e86f86be7fc1b919c33d74 +F src/date.c ebe1dc7c8a347117bb02570f1a931c62dd78f4a2b1b516f4837d45b7d6426957 F src/dbpage.c 8db4c97f630e7d83f884ea75caf1ffd0988c160e9d530194d93721c80821e0f6 F src/dbstat.c 7a4ba8518b6369ef3600c49cf9c918ad979acba610b2aebef1b656d649b96720 -F src/delete.c f97cc2a9a30b4119ec5ed0da4e41335060cbb8510a5f1a756be48c065cb824f0 -F src/expr.c 0016b95aed1df47ebc0eb7415bd621841d72dbffd3bfb62210e50e71d83b4691 +F src/delete.c 20c8788451dc737a967c87ea53ad43544d617f5b57d32ccce8bd52a0daf9e89b +F src/expr.c 904f85e5f5fa232488cbb662fcf3306ae203728751468670fb9840a9b1886abc F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 -F src/fkey.c 821f70b6c43ba4542ffb5fdf11d3ac5f79e4cbae4750ee3de379afbc5e1f7781 -F src/func.c 0fb9a2d678d3c8aba89b46468b309cd7e8fa9806a369a30aa89024660845bb13 -F src/global.c ac3094f1dc59fbeb919aef7cc0cc827a8459d1fb1adb7972ef75bd9e0c10b75b +F src/fkey.c d617daf66b5515e2b42c1405b2b4984c30ca50fb705ab164271a9bf66c69e331 +F src/func.c 94f42cba2cc1c34aeaa441022ba0170ec3fec4bba54db4e0ded085c6dc0fdc51 +F src/global.c 01506976bd75e5e7b977207a6a05062e2dd0050012f8071be06bbea22ec6d69a F src/hash.c a12580e143f10301ed5166ea4964ae2853d3905a511d4e0c44497245c7ce1f7a F src/hash.h ab34c5c54a9e9de2e790b24349ba5aab3dbb4fd4 F src/hwtime.h 747c1bbe9df21a92e9c50f3bbec1de841dc5e5da F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 -F src/insert.c c7f333547211b8efbac8a72f71adad736b91e655d7bcdfacc737351ecf3c8df2 +F src/insert.c 14686083cedc198540b15a79586cdd4be2acf6d5fa97627e355f817ab07e9fee F src/legacy.c 134ab3e3fae00a0f67a5187981d6935b24b337bcf0f4b3e5c9fa5763da95bf4e -F src/loadext.c 20865b183bb8a3723d59cf1efffc3c50217eb452c1021d077b908c94da26b0b2 -F src/main.c ec2a53690e2d10e101a2917347b59a0d46ba91a5916021eb56bd3e43692ff9e2 -F src/malloc.c a02c9e69bc76bee0f639416b947a946412890b606301454727feadcb313536d6 +F src/loadext.c f6e4e416a736369f9e80eba609f0acda97148a8b0453784d670c78d3eed2f302 +F src/main.c 64a93806e98950ee872ee0952b43b62bb5f6cb10452b99ae5e5f77502d6b622d +F src/malloc.c 07295435093ce354c6d9063ac05a2eeae28bd251d2e63c48b3d67c12c76f7e18 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de F src/mem2.c f1940d9e91948dd6a908fbb9ce3835c36b5d83c3 @@ -448,43 +466,43 @@ F src/mem3.c 8768ac94694f31ffaf8b4d0ea5dc08af7010a35a F src/mem5.c 9bf955937b07f8c32541c8a9991f33ce3173d944 F src/memjournal.c 6f3d36a0a8f72f48f6c3c722f04301ac64f2515435fa42924293e46fc7994661 F src/msvc.h 4942752b6a253116baaa8de75256c51a459a5e81 -F src/mutex.c 8e45800ee78e0cd1f1f3fe8e398853307f4a085c +F src/mutex.c b021263554c8a3995e9d53193b8194b96d1ed28e06c3b532dd7f7d29cf0c7d53 F src/mutex.h 779d588e3b7756ec3ecf7d78cde1d84aba414f85 F src/mutex_noop.c 9d4309c075ba9cc7249e19412d3d62f7f94839c4 -F src/mutex_unix.c 27bb6cc49485ee46711a6580ab7b3f1402211d23 -F src/mutex_w32.c a898fa969823b100c0f5fdc57e54c9a1e419ab4d +F src/mutex_unix.c aaf9ebc3f89df28483c52208497a99a02cc3650011422fc9d4c57e4392f7fe58 +F src/mutex_w32.c 7670d770c94bbfe8289bec9d7f1394c5a00a57c37f892aab6b6612d085255235 F src/notify.c 9711a7575036f0d3040ba61bc6e217f13a9888e7 F src/os.c 22d31db3ca5a96a408fbf1ceeaaebcaf64c87024d2ff9fe1cf2ddbec3e75c104 F src/os.h 48388821692e87da174ea198bf96b1b2d9d83be5dfc908f673ee21fafbe0d432 F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85 F src/os_setup.h 0dbaea40a7d36bf311613d31342e0b99e2536586 -F src/os_unix.c 1f10b35801b6107cbfe10dd093d19e93410e05dcc7e011dcb19847feec15fa01 -F src/os_win.c 6892c3ff23b7886577e47f13d827ca220c0831bae3ce00eea8c258352692f8c6 +F src/os_unix.c da5704d0c54eec4b8e02095782c0360af4edfbf7832191f5ff697fddfbb4f9ef +F src/os_win.c eb03c6d52f893bcd7fdd4c6006674c13c1b5e49543fec98d605201af2997171c F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a -F src/pager.c a98e9760c8750e25d2ae6f6dc78b20f192408b85047e8811fcc5918af464b887 +F src/pager.c fa0c6d8cc7483bcf5a3c02bc80a5162b81bf948e567113fda8bf548edc187131 F src/pager.h 72e1a29470f2c2ad482ce944f61c7818098a842740d94c2bd167f348da4fdbac -F src/parse.y 839e06142f53b51dc443beb4e2f3c21f501531edf9bd385490020242b9587490 +F src/parse.y 678b3afe0d9e79534295d8be4d4f4eebae5d6df2b0c402ac73650b240caedc30 F src/pcache.c 7ae91a4557a43d77d449accbfdc68846e6516f8e2eda46e8bbe4536fb669b201 F src/pcache.h 072f94d29281cffd99e46c1539849f248c4b56ae7684c1f36626797fee375170 F src/pcache1.c 716975564c15eb6679e97f734cec1bfd6c16ac3d4010f05f1f8e509fc7d19880 -F src/pragma.c d04725ac25387d9638919e197fb009f378e13af7bf899516979e54b3164e3602 +F src/pragma.c bea56df3ae0637768c0da4fbbb8f2492f780980d95000034a105ff291bf7ca69 F src/pragma.h bb83728944b42f6d409c77f5838a8edbdb0fe83046c5496ffc9602b40340a324 -F src/prepare.c 7cf451f903ad92a14e22de415a13e7a7d30f1bd23b3d21eeb0dc7264723244c5 -F src/printf.c 40aee47ae9be4bd3dbdc8968bd07fddc027be8edec8daddf24d3391d36698a1c +F src/prepare.c 259f4e7960c47082c9653f3d5f0c294abd68bb9c3aab86de7630700cba1c20fb +F src/printf.c d3b7844ddeb11fbbdd38dd84d09c9c1ac171d21fb038473c3aa97981201cc660 F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384 -F src/resolve.c 5b1e89ba279f4a4ab2f0975a7100d75be71e1a43a2df75a9c909d45bdd18c6ed +F src/resolve.c 66c73fcb7719b8ff0e841b58338f13604ff3e2b50a723f9b8f383595735262f6 F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac -F src/select.c 660ef7977841fb462f24c8561e4212615bb6e5c9835fd3556257ce8316c50fee -F src/shell.c.in 08cbffc31900359fea85896342a46147e9772c370d8a5079b7be26e3a1f50e8a -F src/sqlite.h.in 68bfa4942af031d16055ab4d37d26fe44b11b0756a89f309cd98ea17080db1cc +F src/select.c f02352ac5cbb6fad3804add825743b924cfb6c742ba2e8666d726828a9985d73 +F src/shell.c.in 47dac081e15544f4d4373a837215391459971b312c8bed15903e7e9045c3e652 +F src/sqlite.h.in 5576a6e50560b23fb072d5824e5b60084275897eaec8235d98a81fcaa316db6d F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 -F src/sqlite3ext.h c02d628cca67f3889c689d82d25c3eb45e2c155db08e4c6089b5840d64687d34 -F src/sqliteInt.h 83bee2a797beedf3edddb29fca39bf724b2e77972a9440398638674d4877673b +F src/sqlite3ext.h 83a3c4ce93d650bedfd1aa558cb85a516bd6d094445ee989740827d0d944368d +F src/sqliteInt.h 51c1a7062421433bd7b352e51b4fbce9aad1d74f10fdf2597760df24dfb87024 F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6a76b F src/status.c 9737ed017279a9e0c5da748701c3c7bf1e8ae0dae459aad20dd64fcff97a7e35 F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34 -F src/tclsqlite.c 1833388c01e3b77f4c712185ee7250b9423ee0981ce6ae7e401e47db0319a696 -F src/test1.c a1b238ce48cafc2d93acf8c062161b235f4a5d4a57ebc867355445002c4b2799 +F src/tclsqlite.c 11a2618c227fd13ccad73ee02d1199f9880c59db2b3144fd7432db1980a2577d +F src/test1.c c2b5de91195cf2911760cfe543eb1bf315a01e1db32a95d2ba5d8fe6be06f41c F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5 F src/test3.c b8434949dfb8aff8dfa082c8b592109e77844c2135ed3c492113839b6956255b F src/test4.c 18ec393bb4d0ad1de729f0b94da7267270f3d8e6 @@ -499,7 +517,7 @@ F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0 F src/test_bestindex.c 78809f11026f18a93fcfd798d9479cba37e1201c830260bf1edc674b2fa9b857 F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31ce F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274 -F src/test_config.c 1880e08f2702cb4e24b287a3b1c897098e574a3033d66e877ceaa5669398e78e +F src/test_config.c 621e3330989efdacce8e7b1e2ea4c0864bada1e7ea746ce3ba01e7466409580c F src/test_delete.c e2fe07646dff6300b48d49b2fee2fe192ed389e834dd635e3b3bac0ce0bf9f8f F src/test_demovfs.c a0c3bdd45ed044115c2c9f7779e56eafff18741e F src/test_devsym.c 1960abbb234b97e9b920f07e99503fc04b443f62bbc3c6ff2c2cea2133e3b8a2 @@ -527,40 +545,40 @@ F src/test_server.c a2615049954cbb9cfb4a62e18e2f0616e4dc38fe F src/test_sqllog.c 11e6ce7575f489155c604ac4b439f2ac1d3d5aef F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e F src/test_syscall.c 1073306ba2e9bfc886771871a13d3de281ed3939 -F src/test_tclsh.c 74fcfb7f3b0ff1f871e62263dd84ffba46a8e9d477439115e0fb2035e4bf69e1 +F src/test_tclsh.c 58052fe48efe8f579834f4648d239569f2efc6285f5019ebdf0040f58d16238d F src/test_tclvar.c 33ff42149494a39c5fbb0df3d25d6fafb2f668888e41c0688d07273dcb268dfc F src/test_thread.c 911d15fb14e19c0c542bdc8aabf981c2f10a4858 F src/test_vfs.c f0186261a24de2671d080bcd8050732f0cb64f6e F src/test_vfstrace.c bab9594adc976cbe696ff3970728830b4c5ed698 -F src/test_windirent.c 17f91f5f2aa1bb7328abb49414c363b5d2a9d3ff -F src/test_windirent.h 5d67483a55442e31e1bde0f4a230e6e932ad5906 +F src/test_windirent.c a895e2c068a06644eef91a7f0a32182445a893b9a0f33d0cdb4283dca2486ac1 +F src/test_windirent.h 90dfbe95442c9762357fe128dc7ae3dc199d006de93eb33ba3972e0a90484215 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c -F src/tokenize.c 1003d6d90c6783206c711f0a9397656fa5b055209f4d092caa43bb3bf5215db5 -F src/treeview.c 2ee4a5dada213d5ab08a742af5c876cee6f1aaae65f10a61923f3fb63846afef -F src/trigger.c 929b12bb5f9ab3b10c589f71fa176effe6ea8bddc9eb1bbd2439755fa0acdfdf -F src/update.c 2e7ef3839eb620e9140a41bbfd05e29d7d216441c5d97221b75635dea4e49672 +F src/tokenize.c 5b0c661a85f783d35b9883830736eeb63be4aefc4f6b7d9cd081d48782c041e2 +F src/treeview.c e0d62678314abf0e1e6d09ea405f4de3f404a17b4641c2169c304b5edf509320 +F src/trigger.c a34539c69433276d37b0da9a89c117726ff2d292c0902895af1f393a983cd3a1 +F src/update.c a90a32ffc0100265b0693dbbdbe490756447af181f5ea2c138cce515b08c8795 F src/utf.c 810fbfebe12359f10bc2a011520a6e10879ab2a163bcb26c74768eab82ea62a5 -F src/util.c d01fa6f45bfad3b65fb2490513aa2e0676412c61b4b094340b513cf72c3704a4 -F src/vacuum.c 07eec96f821c7dcbdca2fadffc6e38ea2c24bf409fcb15fe9fb3ac444d632dfe -F src/vdbe.c 1834769e38fa8e2cd28651159773be1585242ab7ab8098c55d39c1b58e5f3240 -F src/vdbe.h d50cadf12bcf9fb99117ef392ce1ea283aa429270481426b6e8b0280c101fd97 -F src/vdbeInt.h 1fe00770144c12c4913128f35262d11527ef3284561baaab59b947a41c08d0d9 -F src/vdbeapi.c 9c670ca0dcc1cd86373aa353b747b26fe531ca5cd4331690c611d1f03842e2a1 -F src/vdbeaux.c e9aa37f7bc19415218bc9d5a83f3fa86fb3c504ecb0d67d159f6d138df5b837a +F src/util.c d9eb0a6c4aae1b00a7369eadd7ca0bbe946cb4c953b6751aa20d357c2f482157 +F src/vacuum.c f0b8302219b00461ac0edbb790b5ef52d3d454a7ef9b78030a44b32bbdc39e8a +F src/vdbe.c 213097212a5e1836a57cd4faa396996234481d8351c29a417d9b648f0f53a10b +F src/vdbe.h 134beb7a12a6213c00eba58febaede33447cc4441bc568a0d9c144b33fc3720a +F src/vdbeInt.h 95f7adfdc5c8f1353321f55a6c5ec00a90877e3b85af5159e393afb41ff54110 +F src/vdbeapi.c fea41171884a4de119f8b10ab514c788674eeeb7f27218bb6d008e1310bfd07f +F src/vdbeaux.c db9d38ca41466d5cc845bedbf88f470eefb5c4edaff2070249fa08a40273d1a1 F src/vdbeblob.c f5c70f973ea3a9e915d1693278a5f890dc78594300cf4d54e64f2b0917c94191 -F src/vdbemem.c 2ef9e66b301a1e575e32966c4c0fd4844e8eea37a2f02bae78c4f68f50a6ab30 +F src/vdbemem.c 414e28d3a7e2a8bee2bb247de115dcbc68e3cbac284d5862d077002f7a93bce1 F src/vdbesort.c 731a09e5cb9e96b70c394c1b7cf3860fbe84acca7682e178615eb941a3a0ef2f F src/vdbetrace.c 48e11ebe040c6b41d146abed2602e3d00d621d7ebe4eb29b0a0f1617fd3c2f6c F src/vtab.c 0e4885495172e1bdf54b12cce23b395ac74ef5729031f15e1bc1e3e6b360ed1a F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 -F src/wal.c a0f3b4c92fc2a4d581d75f3a722a8269e697a9a5f4a6e21057c14f7566072fc1 +F src/wal.c 44e8d9f9b42dba0dd6253d67574307ff1892ee56f3ae99cabfcec5a2676763b4 F src/wal.h 1713fefe4587678c295bbeb91c4e7442998ad74f19249869ce49dda9e8ce5d53 -F src/walker.c d591e8a9ccf60abb010966b354fcea4aa08eba4d83675c2b281a8764c76cc22f -F src/where.c 031a80bcafe93934fd7052f3031c9e7eb36b61754c6c84d6bf0833184abad3db +F src/walker.c da987a20d40145c0a03c07d8fefcb2ed363becc7680d0500d9c79915591f5b1f +F src/where.c 7cae47e813393d70c6d327fdf000fcb30f76b1b0b5a5b52ff6402e0c658de32c F src/whereInt.h 82c04c5075308abbac59180c8bad5ecb45b07453981f60a53f3c7dee21e1e971 -F src/wherecode.c 4a117dd5886616d074f7b6589c23bf742f5a9858d6ffdaf8b9d1f76ab06245d2 -F src/whereexpr.c 427ea8e96ec24f2a7814c67b8024ad664a9c7656264c4566c34743cb23186e46 +F src/wherecode.c e1aaadd8fec650037cfbf27d1b3470338fb3b58fec34d11082df16fe9a08fbd7 +F src/whereexpr.c 53532be687e12f3cd314f1e204cd4fbdac7ad250e918a182b048121e16e828ae F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test a6d901b436328bd67a79b41bb0ac2663918fe3bd F test/affinity3.test 6a101af2fc945ce2912f6fe54dd646018551710d @@ -655,13 +673,13 @@ F test/busy.test 510dc6daaad18bcbbc085bcc6217d6dc418def5e73f72ce1475eea0cb783472 F test/cache.test 13bc046b26210471ca6f2889aceb1ea52dc717de F test/cacheflush.test af25bb1509df04c1da10e38d8f322d66eceedf61 F test/cachespill.test 895997f84a25b323b166aecb69baab2d6380ea98f9e0bcc688c4493c535cfab9 -F test/capi2.test 011c16da245fdc0106a2785035de6b242c05e738 -F test/capi3.test 986e57cea8ab423b3fc8c2e3b69330394252d3d2a4496122ff3749e258305695 +F test/capi2.test 34a1a9a96d543a2ec2c209696b11b164444f57253b1f2cba1c2e53fadede6c7b +F test/capi3.test 36f5c859c91a9bb0075d6ddcfcf2476cad629b09f7bfd135776fb94b06c04706 F test/capi3b.test efb2b9cfd127efa84433cd7a2d72ce0454ae0dc4 F test/capi3c.test 7ebed1d8fa2f3190149d556fe8cff5a006be62af437c5c4640db614470126098 F test/capi3d.test 485048dc5cd07bc68011e4917ad035ad6047ab82 F test/capi3e.test 3d49c01ef2a1a55f41d73cba2b23b5059ec460fe -F test/cast.test 4c275cbdc8202d6f9c54a3596701719868ac7dc3 +F test/cast.test 5ceb920718d280b61163500a7d29e0e0a86458b1cbd92d96f962c9d970aa3857 F test/cffault.test 9d6b20606afe712374952eec4f8fd74b1a8097ef F test/check.test 33a698e8c63613449d85d624a38ef669bf20331daabebe3891c9405dd6df463a F test/close.test 799ea4599d2f5704b0a30f477d17c2c760d8523fa5d0c8be4a7df2a8cad787d8 @@ -679,7 +697,7 @@ F test/collate9.test 3adcc799229545940df2f25308dd1ad65869145a F test/collateA.test b8218ab90d1fa5c59dcf156efabb1b2599c580d6 F test/collateB.test 1e68906951b846570f29f20102ed91d29e634854ee47454d725f2151ecac0b95 F test/colmeta.test 2c765ea61ee37bc43bbe6d6047f89004e6508eb1 -F test/colname.test c47639d26cbeba6977457e5ef2c2c55c5b6c889478dd7eb0ed858ba894e7fa93 +F test/colname.test fb28b3687e03625425bc216edf8b186ce974aa71008e2aa1f426a7dcb75a601d F test/concfault.test 500f17c3fcfe7705114422bcc6ddd3c740001a43 F test/concurrent.test 86661967a680670127a62a819e60dc93c2d3d49043ac95b26dfa70d3e60dbde5 F test/concurrent2.test 9dfbeb0a323733fe1d13443371734bb94a674dbf777f464365475903873111f8 @@ -720,7 +738,7 @@ F test/crash4.test fe2821baf37168dc59dd733dcf7dba2a401487bc F test/crash5.test 05dd3aa9dbb751a22d5cdaf22a9c49b6667aa219 F test/crash6.test 4c56f1e40d0291e1110790a99807aa875b1647ba F test/crash7.test 1a194c4900a255258cf94b7fcbfd29536db572df -F test/crash8.test a63907617d8e74fb54b4bff23eca8a4435625245 +F test/crash8.test 63cd5aea313222d7a69637cf7174c34d151676cc187d57193b66d4c89dedede3 F test/crashM.test d95f59046fa749b0d0822edf18a717788c8f318d F test/crashtest1.c 09c1c7d728ccf4feb9e481671e29dda5669bbcc2 F test/createtab.test b5de160630b209c4b8925bdcbbaf48cc90b67fe8 @@ -740,13 +758,13 @@ 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/delete_db.test 096d828493c7907f9ea11a7098ea6a0f73edba89406487d5d6cc2228dc4ab8b0 F test/descidx1.test 6d03b44c8538fe0eb4924e19fba10cdd8f3c9240 F test/descidx2.test 9f1a0c83fd57f8667c82310ca21b30a350888b5d F test/descidx3.test 09ddbe3f5295f482d2f8b687cf6db8bad7acd9a2 F test/diskfull.test 106391384780753ea6896b7b4f005d10e9866b6e F test/distinct.test a1783b960ad8c15a77cd9f207be072898db1026c -F test/distinct2.test faef8a3f27424e2cfbe19b2a40752294de3b5d957e049e3336be53ec0476cb58 +F test/distinct2.test df0bb52b754661ea84ec9ff488d48913c97bd31d83ca17ce0bf1334645e660cf F test/distinctagg.test 1a6ef9c87a58669438fc771450d7a72577417376 F test/e_blobbytes.test 439a945953b35cb6948a552edaec4dc31fd70a05 F test/e_blobclose.test 4b3c8c60c2171164d472059c73e9f3c1844bb66d @@ -781,23 +799,23 @@ F test/enc4.test c8f1ce3618508fd0909945beb8b8831feef2c020 F test/eqp.test 3fe051af50921284189d1970eb653f9fcf5117d2 F test/errmsg.test eae9f091eb39ce7e20305de45d8e5d115b68fa856fba4ea6757b6ca3705ff7f9 F test/eval.test a64c9105d6ff163df7cf09d6ac29cdad5922078c -F test/exclusive.test 9a57bd66e39144b888ca75c309914fcdefb4e3f9 +F test/exclusive.test 1206b87e192497d78c7f35552e86a9d05421498da300fb1cce5ca5351ccde3c3 F test/exclusive2.test 984090e8e9d1b331d2e8111daf6e5d61dda0bef7 F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7 F test/exists.test 79a75323c78f02bbe9c251ea502a092f9ef63dac -F test/expr.test 66a2c9ac34f74f036faa4092f5402c7d3162fc93 +F test/expr.test 7cb55e80aeb41d65fec968c08212505123063fea60bdc355d764d747670e9eea F test/extension01.test 00d13cec817f331a687a243e0e5a2d87b0e358c9 F test/extraquick.test cb254400bd42bfb777ff675356aabf3287978f79 -F test/fallocate.test 87b5e43c872b7e69cd80b7b8813eb102b571a75d45dda24e38b65537bcc85733 +F test/fallocate.test 07416bd593a116d5893cb244f45a94d5c6fe030561df3bd972e6135f8106e509 F test/filectrl.test 6e871c2d35dead1d9a88e176e8d2ca094fec6bb3 F test/filefmt.test f393e80c4b8d493b7a7f8f3809a8425bbf4292af1f5140f01cb1427798a2bbd4 -F test/fkey1.test ba64806ff9a04eecab2679caad377ae99a5e94e4 +F test/fkey1.test d11dbb8a93ead9b5c46ae5d02da016d61245d47662fb2d844c99214f6163f768 F test/fkey2.test 155809016fad6b2a1491facf2ac53a551bc57c2c F test/fkey3.test 76d475c80b84ee7a5d062e56ccb6ea68882e2b49 F test/fkey4.test 86446017011273aad8f9a99c1a65019e7bd9ca9d F test/fkey5.test 24dd28eb3d9f1b5a174f47e9899ace5facb08373a4223593c8c631e6cf9f7d5a F test/fkey6.test d078a1e323a740062bed38df32b8a736fd320dc0 -F test/fkey7.test 72e915890ee4a005daaf3002cb208e8fe973ac13 +F test/fkey7.test 24076d43d3449f12f25503909ca4bfb5fc5fefd5af1f930723a496343eb28454 F test/fkey8.test e5372e32cdb4481f121ec3550703eeb7b4e0762c F test/fkey_malloc.test 594a7ea1fbab553c036c70813cd8bd9407d63749 F test/fordelete.test eb93a2f34137bb87bdab88fcab06c0bd92719aff @@ -839,7 +857,7 @@ F test/fts2r.test b154c30b63061d8725e320fba1a39e2201cadd5e F test/fts2token.test d8070b241a15ff13592a9ae4a8b7c171af6f445a F test/fts3.test 672a040ea57036fb4b6fdc09027c18d7d24ab654 F test/fts3_common.tcl 99cf6659b87c0f74f55963c2aea03b3a7d66ceb0 -F test/fts3aa.test 39b65c11913d277c91d7426c62cfc1d147d1b4e9a48fecd9e38f60d0b5a5f505 +F test/fts3aa.test f267fcd6aca30fc70b81e5d82b68b34b38f581896020b57ed49e9777c7ebd85f F test/fts3ab.test 7f6cf260ae80dda064023df8e8e503e9a412b91f F test/fts3ac.test 636ed7486043055d4f126a0e385f2d5a82ebbf63 F test/fts3ad.test e40570cb6f74f059129ad48bcef3d7cbc20dda49 @@ -889,7 +907,7 @@ F test/fts3offsets.test b85fd382abdc78ebce721d8117bd552dfb75094c F test/fts3prefix.test fa794eaab0bdae466494947b0b153d7844478ab2 F test/fts3prefix2.test e1f0a822ca661dced7f12ce392e14eaf65609dce F test/fts3query.test f33eb71a1fe1084ea585eeb7ee76b390729f5170 -F test/fts3rank.test e4d2e16a28c98cae95001a75e2b4b05b19b051ffd6aaab15491c5e0595127b9b +F test/fts3rank.test cd99bc83a3c923c8d52afd90d86979cf05fc41849f892faeac3988055ef37b99 F test/fts3rnd.test 1320d8826a845e38a96e769562bf83d7a92a15d0 F test/fts3shared.test 57e26a801f21027b7530da77db54286a6fe4997e F test/fts3snippet.test 01a4231816e03a0660ae53ba2404fe69012fe0db @@ -911,15 +929,16 @@ F test/fts4merge2.test 5faa558d1b672f82b847d2a337465fa745e46891 F test/fts4merge3.test 8d9ccb4a3d41c4c617a149d6c4b13ad02de797d0 F test/fts4merge4.test d895b1057a7798b67e03455d0fa50e9ea836c47b F test/fts4noti.test 5553d7bb2e20bf4a06b23e849352efc022ce6309 -F test/fts4onepass.test 7319d61a2ed1325fc54afd0c060a0513b462303a +F test/fts4onepass.test d69ddc4ee3415e40b0c5d1d0408488a87614d4f63ba9c44f3e52db541d6b7cc7 F test/fts4opt.test fd6a11684b965e1999564ae763797b7fb9e34c96 F test/fts4unicode.test ceca76422abc251818cb25dabe33d3c3970da5f7c90e1540f190824e6b3a7c95 F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d -F test/func.test ae97561957aba6ca9e3a7b8a13aac41830d701ef +F test/func.test 09dda479bcfc568f99f3070413e9672a8eeedc1be9c5d819bf55d4788c2583b7 F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f F test/func3.test d202a7606d23f90988a664e88e268aed1087c11c F test/func4.test 6beacdfcb0e18c358e6c2dcacf1b65d1fa80955f F test/func5.test cdd224400bc3e48d891827cc913a57051a426fa4 +F test/func6.test 90e42b64c4f9fb6f04f44cb8a1da586c8542502e926b19c76504fe74ff2a9b7c F test/fuzz-oss1.test e58330d01cbbd8215ee636b17a03fe220b37dbfa F test/fuzz.test 96083052bf5765e4518c1ba686ce2bab785670d1 F test/fuzz2.test 76dc35b32b6d6f965259508508abce75a6c4d7e1 @@ -940,9 +959,9 @@ F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98 F test/having.test e4098a4b8962f9596035c3b87a8928a10648acc509f1bb8d6f96413bbf79a1b3 F test/hexlit.test 4a6a5f46e3c65c4bf1fa06f5dd5a9507a5627751 F test/hidden.test 23c1393a79e846d68fd902d72c85d5e5dcf98711 -F test/hook.test dbc0b87756e1e20e7497b56889c9e9cd2f8cc2b5 +F test/hook.test 1604b3b2f5931430087540404555c1b6be3618600b81558657c66b533ed70b13 F test/hook2.test b9ff3b8c6519fb67f33192f1afe86e7782ee4ac8 -F test/icu.test 73956798bace8982909c00476b216714a6d0559a +F test/icu.test 41aa8847745a879b897a7febea0f8f9efc8e67fe8bf680589b6e07c7b0a1569a F test/ieee754.test 806fc0ce7f305f57e3331eaceeddcfec9339e607 F test/imposter1.test c3f1db2d3db2c24611a6596a3fc0ffc14f1466c8 F test/in.test 2fa2dfba1afe30eb830f327e7131dfadaa7a701d677de0eb65f9303d99e18fe0 @@ -971,7 +990,7 @@ F test/index7.test 7feababe16f2091b229c22aff2bcc1d4d6b9d2bb F test/index8.test bc2e3db70e8e62459aaa1bd7e4a9b39664f8f9d7 F test/index9.test 0aa3e509dddf81f93380396e40e9bb386904c1054924ba8fa9bcdfe85a8e7721 F test/indexedby.test faa585e315e868f09bce0eb39c41d6134649b13d2801638294d3ae616edf1609 -F test/indexexpr1.test 84100e880154a4b645db9f4fc7642756d9a2b6011b68f73c8efda4d244816de9 +F test/indexexpr1.test 635261197bcdc19b9b2c59bbfa7227d525c00e9587faddb2d293c44d287ce60e F test/indexexpr2.test 13247bac49143196556eb3f65e97ef301bd3e993f4511558b5db322ddc370ea6 F test/indexfault.test 31d4ab9a7d2f6e9616933eb079722362a883eb1d F test/init.test 15c823093fdabbf7b531fe22cf037134d09587a7 @@ -987,25 +1006,26 @@ 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 +F test/ioerr.test 470fcc78e9cd352d162baf782fe301ea807d764241f58a48fc58109c2dfcdb6b F test/ioerr2.test 2593563599e2cc6b6b4fcf5878b177bdd5d8df26 F test/ioerr3.test d3cec5e1a11ad6d27527d0d38573fbff14c71bdd F test/ioerr4.test f130fe9e71008577b342b8874d52984bd04ede2c F test/ioerr5.test 2edfa4fb0f896f733071303b42224df8bedd9da4 F test/ioerr6.test a395a6ab144b26a9e3e21059a1ab6a7149cca65b +F test/istrue.test 2d89873d72248b7a0ef7b5457120100cce75a19181bcf4b30e90a3f96a5c5558 F test/join.test 442c462eea85cf065d70a663c626b780a95af6e11585d909bb63b87598afe678 -F test/join2.test a48f723c5692e2cbb23a9297ac2720cb77d51a70 +F test/join2.test f1b5ba94ba70a791df2d99e61adcebf62edacad1eca57881c97eb3e2ca718595 F test/join3.test 6f0c774ff1ba0489e6c88a3e77b9d3528fb4fda0 F test/join4.test 1a352e4e267114444c29266ce79e941af5885916 F test/join5.test bc98ea4b4e5003f5b1453701ebb8cd7d1c01a550 F test/join6.test cfe6503791ceb0cbb509966740286ec423cbf10b -F test/journal1.test 69abc726c51b4a0409189f9a85191205297c0577 +F test/journal1.test c7b768041b7f494471531e17abc2f4f5ebf9e5096984f43ed17c4eb80ba34497 F test/journal2.test 9dac6b4ba0ca79c3b21446bbae993a462c2397c4 -F test/journal3.test ff8af941f9e06161d3db1b46bb9f965ff0e7f307 -F test/jrnlmode.test 7864d59cf7f6e552b9b99ba0f38acd167edc10fa -F test/jrnlmode2.test 81610545a4e6ed239ea8fa661891893385e23a1d +F test/journal3.test c9c29883f5bf535ae82ae21c472df6263806a22e467b6db7cd0d6d545305b4f6 +F test/jrnlmode.test a6693f2bed4541a21e703aaa37bb3e10de154130645952933b82b2dec0a8b539 +F test/jrnlmode2.test 8759a1d4657c064637f8b079592651530db738419e1d649c6df7048cd724363d F test/jrnlmode3.test 556b447a05be0e0963f4311e95ab1632b11c9eaa -F test/json101.test d7cdf3e6731d41e0c4bde1c88806abd17f1f478486a1409933c1d8eac9120095 +F test/json101.test 24e97954e3bd6404f3715888c7f8f835e36e19c7ae6513b5d9ab2d381498962d F test/json102.test eeb54efa221e50b74a2d6fb9259963b48d7414dca3ce2fdfdeed45cb28487bc1 F test/json103.test c5f6b85e69de05f6b3195f9f9d5ce9cd179099a0 F test/json104.test 877d5845f6303899b7889ea5dd1bea99076e3100574d5c536082245c5805dcaa @@ -1017,22 +1037,22 @@ F test/like.test 11cfd7d4ef8625389df9efc46735ff0b0b41d5e62047ef0f3bc24c380d28a7a F test/like2.test 3b2ee13149ba4a8a60b59756f4e5d345573852da F test/like3.test 3608a2042b6f922f900fbfd5d3ce4e7eca57f7c4 F test/limit.test 0c99a27a87b14c646a9d583c7c89fd06c352663e -F test/limit2.test e35f57bd3a62d7c5dcb5ac4306e675c75f974809 +F test/limit2.test 360982809e03211636d2b18ddbc97d5da06826941370607e4b00e113f827cb5a F test/loadext.test d077450695ddb5c1ea3ad7d48e5f5850fe732ad9 F test/loadext2.test 0408380b57adca04004247179837a18e866a74f7 F test/lock.test be4fe08118fb988fed741f429b7dd5d65e1c90db F test/lock2.test 5242d8ac4e2d59c403aebff606af449b455aceff F test/lock3.test f271375930711ae044080f4fe6d6eda930870d00 -F test/lock4.test e175ae13865bc87680607563bafba21f31a26f12 +F test/lock4.test 27143363eda1622f03c133efc8db808fc331afd973486cb571ea71cd717d37b8 F test/lock5.test c6c5e0ebcb21c61a572870cc86c0cb9f14cede38 F test/lock6.test ad5b387a3a8096afd3c68a55b9535056431b0cf5 F test/lock7.test 49f1eaff1cdc491cc5dee3669f3c671d9f172431 F test/lock_common.tcl 7ffb45accf6ee91c736df9bafe0806a44358f035 F test/lookaside.test b17c99ae3aef96a8c9fa6f6be33cc75b93d657cb791d3827302b6835b71941f7 -F test/main.test bb75e406c9b64931f3dc7e7f04626633365bb22f +F test/main.test 6bbb3999fd461eb8fb335cbab97409a3d7f91bbb8da60635e8be3e4a04a77772 F test/make-where7.tcl 05c16b5d4f5d6512881dfec560cb793915932ef9 -F test/malloc.test 21c213365f2cca95ab2d7dc078dc8525f96065f8 -F test/malloc3.test e3b32c724b5a124b57cb0ed177f675249ad0c66a +F test/malloc.test 18dd1c4188c81ca79cf123527c71b19ee0c31feb9947fdffb0dc6ceb1436816a +F test/malloc3.test 6e88bae6312854a4adb4ecc2a6a5ea8c59b4db778b724ba718e1c43fc8c3c136 F test/malloc4.test 957337613002b7058a85116493a262f679f3a261 F test/malloc5.test f6eb6eca07a4c75f2897bf43a404689b6295bb95ab2e07d4b52eda743f925a27 F test/malloc6.test 2f039d9821927eacae43e1831f815e157659a151 @@ -1064,16 +1084,16 @@ F test/minmax.test 6751e87b409fe11b02e70a306d846fa544e25a41 F test/minmax2.test dae92964ac87c1d2ef978c582e81a95e11c00f1cbef68980bfb2abaf10315063 F test/minmax3.test cc1e8b010136db0d01a6f2a29ba5a9f321034354 F test/minmax4.test 936941484ebdceb8adec7c86b6cd9b6e5e897c1f -F test/misc1.test 76737c259537586355f45e2a1e121b6e91b5476c4604ad5c53d1abfcb3acf786 +F test/misc1.test 704ea2cc7e7b9deb622b37953f0e77d0879826e8c3bfc1d7a691528035405061 F test/misc2.test 00d7de54eda90e237fc9a38b9e5ccc769ebf6d4d F test/misc3.test cf3dda47d5dda3e53fc5804a100d3c82be736c9d F test/misc4.test 0d8be3466adf123a7791a66ba2bc8e8d229e87f3 F test/misc5.test 60e1fc758a93cacd19eb2fafcd1d40d150a05047546c7a92389c98047d621901 F test/misc6.test 953cc693924d88e6117aeba16f46f0bf5abede91 -F test/misc7.test edd0b63e2ee29a256900b0514f6fff27e19e9bb2 +F test/misc7.test 567e223b6497da2226a0340befaf2d663c91ad57a48aede21a35a984a2882d41 F test/misc8.test ba03aaa08f02d62fbb8d3b2f5595c1b33aa9bbc5 F test/misuse.test 9e7f78402005e833af71dcab32d048003869eca5abcaccc985d4f8dc1d86bcc7 -F test/mjournal.test 68b749956f9a179e7e633a3958b48a5a905d28d30c7ec88f3f26dc6f220129db +F test/mjournal.test 9d86e697dcbc5da2c4e8caba9b176b5765fe65e80c88c278b8c09a917e436795 F test/mmap1.test d2cfc1635171c434dcff0ece2f1c8e0a658807ce F test/mmap2.test 9d6dd9ddb4ad2379f29cc78f38ce1e63ed418022 F test/mmap3.test b3c297e78e6a8520aafcc1a8f140535594c9086e @@ -1087,12 +1107,13 @@ F test/multiplex4.test e8ae4c4bd70606a5727743241f13b5701990abe4 F test/mutex1.test ea2cc74d97f077b9e74c84cbd024f14d79a8126f F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660 F test/nan.test 437d40e6d0778b050d7750726c0cbd2c9936b81962926e8f8c48ca698f00f4d1 -F test/nockpt.test 9a436a7213ba5ef7a32304998d386d3ea3f76c9d +F test/nockpt.test fd5473e30a84848b25ab06524750c05095383e191be83ccb4e6951c8beddfb5b F test/nolock.test f196cf8b8fbea4e2ca345140a2b3f3b0da45c76e +F test/normalize.test 501630ab49b0b26b65c74124bf03e3374c1b57fa97aae750f84803609141d167 F test/notify1.test 669b2b743618efdc18ca4b02f45423d5d2304abf F test/notify2.test 2ecabaa1305083856b7c39cf32816b612740c161 F test/notify3.test 10ff25cde502e72a92053a2f215d64bece4ef934 -F test/notnull.test f8fcf58669ddba79274daa2770d61dfad8274f62 +F test/notnull.test b6999231221df3534827e45e2005dd7a815fdd5f2c2e1afb9be21ead410816f8 F test/null.test 0dcce4f04284ec66108c503327ad6d224c0752b3 F test/numcast.test 5d126f7f581432e86a90d1e35cac625164aec4a1 F test/numindex1.test 20a5450d4b056e48cd5db30e659f13347a099823 @@ -1108,14 +1129,14 @@ F test/orderby7.test 3d1383d52ade5b9eb3a173b3147fdd296f0202da F test/orderby8.test 23ef1a5d72bd3adcc2f65561c654295d1b8047bd F test/orderby9.test 87fb9548debcc2cd141c5299002dd94672fa76a3 F test/oserror.test b32dc34f2363ef18532e3a0a7358e3e7e321974f -F test/ossfuzz.c 7f5cc87a0280a5854c1bfa7d5c4d07d34731f08ec34dc9c916aa35ed292b1468 +F test/ossfuzz.c 3613bc516386234cf2e513fb94dc677ab3862eb7ebc5b3671c319a80f86839fb F test/ossshell.c 296ab63067841bd1b1e97b46a0b2af48ee7f69d50d1a723008bee12dd7122622 F test/ovfl.test 199c482696defceacee8c8e0e0ef36da62726b2f -F test/pager1.test 8149b2a8986fee667ab6a8171ab310be19e77ae215bebad0e90c857b0df1935c +F test/pager1.test f596d3bd53ce96e1d87d44d223d2ae6c8867dd782c425e5eb28b5721fa6aaa97 F test/pager2.test 67b8f40ae98112bcdba1f2b2d03ea83266418c71 -F test/pager3.test 3856d9c80839be0668efee1b74811b1b7f7fc95f +F test/pager3.test 4e9a83d6ca0838d7c602c9eb93d1357562d9059c1e02ffb138a8271020838370 F test/pager4.test a122e9e6925d5b23b31e3dfef8c6a44bbf19590e -F test/pagerfault.test 263c5442c06caf0b9b9e3fe42acdeb11f254dcebe533f69f401aaef9111eaf20 +F test/pagerfault.test 63c5da625562c66345ab4528790327ca63db2f6f9cbae2aba8cb7c51de3d1628 F test/pagerfault2.test caf4c7facb914fd3b03a17b31ae2b180c8d6ca1f F test/pagerfault3.test 1003fcda009bf48a8e22a516e193b6ef0dd1bbd8 F test/pageropt.test 84e4cc5cbca285357f7906e99b21be4f2bf5abc0 @@ -1123,15 +1144,15 @@ F test/pagesize.test 5769fc62d8c890a83a503f67d47508dfdc543305 F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442 F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff -F test/permutations.test 490e3333b9b1aefb7ebc6e9ab2ae0e382b7dd8713ccc4a2786b0f75467c2ab6b +F test/permutations.test 8ada8c1dee071e0fc275bc8bc2db7de537d625cad949d2200664b99a0a89eac5 F test/pragma.test 7c8cfc328a1717a95663cf8edb06c52ddfeaf97bb0aee69ae7457132e8d39e7d F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f F test/pragma3.test 14c12bc5352b1e100e0b6b44f371053a81ccf8ed F test/pragma4.test 3046501bee2f652dc2a4f9c87781e2741361d6864439c8381aba6c3b774b335c -F test/pragma5.test fd517f42ee847e126afbbbd9fd0fb9e5a4a61a962496a350adb8a22583fbdc37 +F test/pragma5.test 824ce6ced5d6b7ec71abe37fc6005ff836fe39d638273dc5192b39864b9ee983 F test/pragmafault.test 275edaf3161771d37de60e5c2b412627ac94cef11739236bec12ed1258b240f8 F test/printf.test b3ff34e73d59124140eaf89f7672e21bc2ca5fcc -F test/printf2.test 9e6db85f81c63f2367c34a9d7db384088bd374ad +F test/printf2.test 30b5dd0b4b992dc5626496846ecce17ff592cacbcb11c3e589f3ac4d7e129dae F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc F test/pushdown.test 5e72c51c5e33253ed639ccee1e01ce62d62b6eee5ca893cd82334e4ee7b1d7fc @@ -1148,15 +1169,15 @@ F test/rdonly.test 64e2696c322e3538df0b1ed624e21f9a23ed9ff8 F test/regexp1.test 497ea812f264d12b6198d6e50a76be4a1973a9d8 F test/regexp2.test 40e894223b3d6672655481493f1be12012f2b33c F test/reindex.test 44edd3966b474468b823d481eafef0c305022254 -F test/releasetest.tcl 0b0b3d926e36822ff63b405d683544ce1014303b029f2678bbcf40c162b5f246 x +F test/releasetest.tcl 6aaa853f7a7bbdc458d4cb42c0425228729b0f3e5769e9b41088c08eee999a49 x F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb -F test/rollback.test f580934279800d480a19176c6b44909df31ce7ad45267ea475a541daa522f3d3 +F test/rollback.test 06680159bc6746d0f26276e339e3ae2f951c64812468308838e0a3362d911eaa F test/rollback2.test 8435d6ff0f13f51d2a4181c232e706005fa90fc5 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/rowvalue.test 32861d6a933ded868035f2ec79aeb993a2a46eb7a6d282ae13415a4c2e369463 F test/rowvalue2.test 060d238b7e5639a7c5630cb5e63e311b44efef2b F test/rowvalue3.test 3068f508753af69884b12125995f023da0dbb256 F test/rowvalue4.test 4b556d7de161a0dd8cff095c336e913986398bea @@ -1182,9 +1203,9 @@ F test/schema3.test 1bc1008e1f8cb5654b248c55f27249366eb7ed38 F test/schema4.test 3b26c9fa916abb6dadf894137adcf41b7796f7b9 F test/schema5.test 29699b4421f183c8f0e88bd28ce7d75d13ea653e F test/schema6.test e4bd1f23d368695eb9e7b51ef6e02ca0642ea2ab4a52579959826b5e7dce1f9b -F test/securedel.test 5f997cb6bd38727b81e0985f53ec386c99db6441b2b9e6357240649d29017239 +F test/securedel.test 2f70b2449186a1921bd01ec9da407fbfa98c3a7a5521854c300c194b2ff09384 F test/securedel2.test 2d54c28e46eb1fd6902089958b20b1b056c6f1c5 -F test/select1.test 460a5824df01575b18f7fa4bd8e40d09de20c542e90c1543e164bc7d3b0a0bb7 +F test/select1.test 2e760bab8f3658b3b97debcf52860d0d2e20aa6cbe8b40e678ddb99871a15491 F test/select2.test 352480e0e9c66eda9c3044e412abdf5be0215b56 F test/select3.test 2ce595f8fb8e2ac10071d3b4e424cadd4634a054 F test/select4.test 5389d9895968d1196c457d59b3ee6515d771d328 @@ -1199,7 +1220,7 @@ F test/selectC.test e25243f8ca503e06f252eb0218976d07cfeceac3 F test/selectD.test b0f02a04ef7737decb24e08be2c39b9664b43394 F test/selectE.test a8730ca330fcf40ace158f134f4fe0eb00c7edbf F test/selectF.test 21c94e6438f76537b72532fa9fd4710cdd455fc3 -F test/selectG.test e8600e379589e85e9fefd2fe4d44a4cdd63f6982 +F test/selectG.test 089f7d3d7e6db91566f00b036cb353107a2cca6220eb1cb264085a836dae8840 F test/server1.test 46803bd3fe8b99b30dbc5ff38ffc756f5c13a118 F test/session.test 78fa2365e93d3663a6e933f86e7afc395adf18be F test/shared.test 1da9dbad400cee0d93f252ccf76e1ae007a63746 @@ -1210,17 +1231,18 @@ F test/shared6.test 866bb4982c45ce216c61ded5e8fde4e7e2f3ffa9 F test/shared7.test a81e99f83e6c51b02ac99c96fb3a2a7b5978c956 F test/shared8.test 00a07bf5e1337ecf72e94542bdefdc330d7a2538 F test/shared9.test 5f2a8f79b4d6c7d107a01ffa1ed05ae7e6333e21 -F test/sharedA.test 0cdf1a76dfa00e6beee66af5b534b1e8df2720f5 +F test/sharedA.test 49d87ec54ab640fbbc3786ee3c01de94aaa482a3a9f834ad3fe92770eb69e281 F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 -F test/shell1.test f78ea0e2637ae9f497cb41206f6346f983052ffc7a359e664aaf6847fc704ea7 +F test/shell1.test 9f8b8da05a79b134e252a5e1d8d411245ad83ac7126c262900b9f42b43108ffd F test/shell2.test e242a9912f44f4c23c3d1d802a83e934e84c853b -F test/shell3.test 9b95ba643eaa228376f06a898fb410ee9b6e57c1 +F test/shell3.test ac8c2b744014c3e9a0e26bfd829ab65f00923dc1a91ffd044863e9423cc91494 F test/shell4.test 89ad573879a745974ff2df20ff97c5d6ffffbd5d F test/shell5.test 23939a4c51f0421330ea61dbd3c74f9c215f5f8d3d1a94846da6ffc777a35458 -F test/shell6.test ab1592ebe881371f651f18ee6a0df21cbfb5310f88cb832ab642d4038f679772 +F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3 F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f +F test/shell8.test 96be02ea0c21f05b24c1883d7b711a1fa8525a68ab7b636aacf6057876941013 F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5 F test/shrink.test 1b4330b1fd9e818c04726d45cb28db73087535ce @@ -1232,6 +1254,7 @@ F test/skipscan5.test 67817a4b6857c47e0e33ba3e506da6f23ef68de2 F test/skipscan6.test 5866039d03a56f5bd0b3d172a012074a1d90a15b F test/snapshot.test 85735bd997a4f6d710140c28fd860519a299649f F test/snapshot2.test 925e42427e923262db63c9d7155183f889e3e99feaedec4075f659e51608344f +F test/snapshot3.test 9719443594a04778861bd20d12596c5f880af177d6cd62f111da3198cafc6096 F test/snapshot_fault.test 52c5e97ebd218846a8ae2da4d147d3e77d71f963 F test/soak.test 0b5b6375c9f4110c828070b826b3b4b0bb65cd5f F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087 @@ -1249,17 +1272,18 @@ F test/speed3.test 694affeb9100526007436334cf7d08f3d74b85ef F test/speed4.test abc0ad3399dcf9703abed2fff8705e4f8e416715 F test/speed4p.explain 6b5f104ebeb34a038b2f714150f51d01143e59aa F test/speed4p.test 0e51908951677de5a969b723e03a27a1c45db38b -F test/speedtest1.c e44c5fccddcfe916c3bf7fe2f87dcc4b4fd66a0d923eb83515f311212670f267 -F test/spellfix.test f9c1f431e2c096c8775fec032952320c0e4700db +F test/speedtest1.c a5faf4cbe5769eee4b721b3875cb3f12520a9b99d9026b1063b47c39603375b8 +F test/spellfix.test 951a6405d49d1a23d6b78027d3877b4a33eeb8221dcab5704b499755bb4f552e F test/spellfix2.test dfc8f519a3fc204cb2dfa8b4f29821ae90f6f8c3 F test/spellfix3.test 0f9efaaa502a0e0a09848028518a6fb096c8ad33 +F test/spellfix4.test 51c7c26514ade169855c66bcf130bd5acfb4d7fd090cc624645ab275ae6a41fb F test/sqldiff1.test 28cd737cf1b0078b1ec1bbf425e674c47785835e F test/sqllimits1.test a74ee2a3740b9f9c2437c246d8fb77354862a142 F test/sqllog.test 6af6cb0b09f4e44e1917e06ce85be7670302517a F test/stat.test f8f1279ffffabe6df825723af18cc6e0ae70a893 F test/statfault.test f525a7bf633e50afd027700e9a486090684b1ac1 -F test/stmt.test 64844332db69cf1a735fcb3e11548557fc95392f -F test/stmtvtab1.test acc3c40f484f2c4922e270724383d715abb9d69676da907a9c64f31c54f2ef9f +F test/stmt.test 54ed2cc0764bf3e48a058331813c3dbd19fc1d0827c3d8369914a5d8f564ec75 +F test/stmtvtab1.test 6873dfb24f8e79cbb5b799b95c2e4349060eb7a3b811982749a84b359468e2d5 F test/subjournal.test 8d4e2572c0ee9a15549f0d8e40863161295107e52f07a3e8012a2e1fdd093c49 F test/subquery.test d7268d193dd33d5505df965399d3a594e76ae13f F test/subquery2.test 438f8a7da1457277b22e4176510f7659b286995f @@ -1267,12 +1291,13 @@ F test/subselect.test 0966aa8e720224dbd6a5e769a3ec2a723e332303 F test/substr.test 18f57c4ca8a598805c4d64e304c418734d843c1a F test/subtype1.test 7fe09496352f97053af1437150751be2d0a0cae8 F test/superlock.test ec94f0556b6488d97f71c79f9061ae08d9ab8f12 -F test/swarmvtab.test c2279311b44de032f86a8295a9b06818d864856f9428b4c99eee91a0d419cf25 -F test/swarmvtab2.test 9a3a68a1e58d00f4ed6c68d12d52f2df971b9e22a80a41f6f8c1409abba8e5b4 +F test/swarmvtab.test 9a3fd5ab3e9b3c976ad1b3d7646aab725114f2ac26b59395d0778b33bab6cdaf +F test/swarmvtab2.test c948cb2fdfc5b01d85e8f6d6504854202dc1a0782ab2a0ed61538f27cbd0aa5c +F test/swarmvtab3.test 6cb664669630fcec4102a09333e52068734858fd2761eee3b0465c14cdbcee29 F test/swarmvtabfault.test 00aec54665909490f5c383f3cae3b5d18bd97c12490b429ff8752a3027acfa42 -F test/symlink.test c9ebe7330d228249e447038276bfc8a7b22f4849 -F test/sync.test 2f84bdbc2b2df1fcb0220575b4b9f8cea94b7529 -F test/sync2.test 6be8ed007fa063b147773c1982b5bdba97a32badc536bdc6077eff5cf8710ece +F test/symlink.test 0d816670325536b8973ec08d32b45136baddb80bd45fd178e0ce7a9e8153f3e7 +F test/sync.test 89539f4973c010eda5638407e71ca7fddbcd8e0594f4c9980229f804d4333092 +F test/sync2.test 8f9f7d4f6d5be8ca8941a8dadcc4299e558cb6a1ff653a9469146c7a76ef2039 F test/syscall.test a39d9a36f852ae6e4800f861bc2f2e83f68bbc2112d9399931ecfadeabd2d69d F test/sysfault.test c9f2b0d8d677558f74de750c75e12a5454719d04 F test/tabfunc01.test c47171c36b3d411df2bd49719dcaa5d034f8d277477fd41d253940723b969a51 @@ -1280,14 +1305,14 @@ F test/table.test b708f3e5fa2542fa51dfab21fc07b36ea445cb2f F test/tableapi.test 2674633fa95d80da917571ebdd759a14d9819126 F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930 F test/tclsqlite.test c3d7ac9449634b9f17fd048a3c0212e88a7448be810a9c5bd051acc1ffa00d2f -F test/tempdb.test bd92eba8f20e16a9136e434e20b280794de3cdb6 +F test/tempdb.test 4cdaa23ddd8acb4d79cbb1b68ccdfd09b0537aaba909ca69a876157c2a2cbd08 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 38f0ca479b1822d3117069e014daabcaacefffcc -F test/tester.tcl 9948bd856ce8a1c127f2f7900365387a42a917ce0dc87185bdd128fa5b11aff2 +F test/tester.tcl 94901a4625d9a2229666dd5c44120ddf7f0fb639470710ef74a4cefc7b039e07 F test/thread001.test 9f22fd3525a307ff42a326b6bc7b0465be1745a5 F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -1302,7 +1327,7 @@ F test/threadtest3.c 4b413718ab3ef5e4f9f1ab0502b89707c5a9ccdc F test/threadtest4.c c1e67136ceb6c7ec8184e56ac61db28f96bd2925 F test/time-wordcount.sh 8e0b0f8109367827ad5d58f5cc849705731e4b90 F test/tkt-02a8e81d44.test 6c80d9c7514e2a42d4918bf87bf6bc54f379110c -F test/tkt-26ff0c2d1e.test 888324e751512972c6e0d1a09df740d8f5aaf660 +F test/tkt-26ff0c2d1e.test c15bec890c4d226c0da2f35ff30f9e84c169cfef90e73a8cb5cec11d723dfa96 F test/tkt-2a5629202f.test 0521bd25658428baa26665aa53ffed9367d33af2 F test/tkt-2d1a5c67d.test be1326f3061caec85085f4c9ee4490561ca037c0 F test/tkt-2ea2425d34.test 1cf13e6f75d149b3209a0cb32927a82d3d79fb28 @@ -1324,7 +1349,7 @@ F test/tkt-5ee23731f.test 9db6e1d7209dc0794948b260d6f82b2b1de83a9f F test/tkt-6bfb98dfc0.test 24780633627b5cfc0635a5500c2389ebfb563336 F test/tkt-752e1646fc.test ea78d88d14fe9866bdd991c634483334639e13bf F test/tkt-78e04e52ea.test 1b2e6bf4f1d9887b216b6da774e5f25915ec8118 -F test/tkt-7a31705a7e6.test e75a2bba4eec801b92c8040eb22096ac6d35e844 +F test/tkt-7a31705a7e6.test 9e9c057b6a9497c8f7ba7b16871029414ccf6550e7345d9085d6d71c9a56bb6f F test/tkt-7bbfb7d442.test 7b2cd79c7a17ae6750e75ec1a7846712a69c9d18 F test/tkt-80ba201079.test 105a721e6aad0ae3c5946d7615d1e4d03f6145b8 F test/tkt-80e031a00f.test 9ee36348b761bf7c14261e002b75a4c0d5a04d4c @@ -1337,7 +1362,7 @@ F test/tkt-9a8b09f8e6.test b2ef151d0984b2ebf237760dbeaa50724e5a0667 F test/tkt-9d68c883.test 16f7cb96781ba579bc2e19bb14b4ad609d9774b6 F test/tkt-9f2eb3abac.test cb6123ac695a08b4454c3792fbe85108f67fabf8 F test/tkt-a7b7803e.test 159ef554234fa1f9fb318c751b284bd1cf858da4 -F test/tkt-a8a0d2996a.test eb597379dbcefa24765763d7f682c00cb5924fa9 +F test/tkt-a8a0d2996a.test 76662ff0622c90e7ce7bbcb4d9e1129acddf877d17c3489f2da7f17ddfaad1f4 F test/tkt-b1d3a2e531.test 8f7576e41ca179289ee1a8fee28386fd8e4b0550 F test/tkt-b351d95f9.test d14a503c414c5c58fdde3e80f9a3cfef986498c0 F test/tkt-b72787b1.test a95e8cdad0b98af1853ac7f0afd4ab27b77bf5f3 @@ -1401,13 +1426,13 @@ F test/tkt3121.test 536df66a02838c26a12fe98639354ca1290ca68b F test/tkt3201.test f1500ccecc0d578dc4cde7d3242008297c4d59b3 F test/tkt3292.test 962465a0984a3b8c757efe59c2c59144871ee1dd F test/tkt3298.test 20fd8773b825cb602e033aa04f8602e1ebdcd93c -F test/tkt3334.test ea13a53cb176e90571a76c86605b14a09efe366d +F test/tkt3334.test 9756631e3c4aa3c416362c279e3c0953a83b7ca8274cb81a13264bb56296d8b0 F test/tkt3346.test 6f67c3ed7db94dfc5df4f5f0b63809a1f611e01a F test/tkt3357.test 77c37c6482b526fe89941ce951c22d011f5922ed F test/tkt3419.test 1bbf36d7ea03b638c15804251287c2391f5c1f6b F test/tkt3424.test 61f831bd2b071bd128fa5d00fbda57e656ca5812 F test/tkt3442.test 53840ec5325bb94544792aad4c20476f81dc26b1 -F test/tkt3457.test 44e980fe5334753dcc27b94fa4deabc485a92f74 +F test/tkt3457.test 5651e2cbb94645b677ec663160b9e192b87b7d365aecdfb24e19f749575a6fc2 F test/tkt3461.test 228ea328a5a21e8663f80ee3d212a6ad92549a19 F test/tkt3493.test 1686cbde85f8721fc1bdc0ee72f2ef2f63139218 F test/tkt3508.test d75704db9501625c7f7deec119fcaf1696aefb7d @@ -1443,7 +1468,7 @@ F test/tkt3997.test a335fa41ca3985660a139df7b734a26ef53284bd F test/tkt4018.test 18dbc6617f7a4b90e938d1bd6d26ad18daafaf08 F test/tokenize.test ce430a7aed48fc98301611429595883fdfcab5d7 F test/tpch01.test 04adbf8d8300fa60a222f28d901abd76e7be6dd4 -F test/trace.test 6f676313e3ebd2a50585036d2f212a3319dd5836 +F test/trace.test a659a9862957f4789e37a92b3bf6d2caf5c86b02cdeefc41e850ae53acf6992a F test/trace2.test f5cb67ad3bc09e0c58e8cca78dfd0b5639259983 F test/trace3.test 56ab944fddacf628b118cc298503fc45c2e50ab0 F test/trans.test 6e1b4c6a42dba31bd65f8fa5e61a2708e08ddde6 @@ -1465,7 +1490,7 @@ F test/triggerC.test 302d8995f5ffe63bbc15053abb3ef7a39cf5a092 F test/triggerD.test 8e7f3921a92a5797d472732108109e44575fa650 F test/triggerE.test 15fa63f1097db1f83dd62d121616006978063d1f F test/triggerF.test 6a8c22bd058cf467f0c7d112afe87f7a8c579c0c4681b914b8f19020f48528a4 -F test/triggerG.test 175cafdc6399d85231a09e82e051b0e45a2fd1f23dd08ae715bc359716149ab6 +F test/triggerG.test d5caeef6144ede2426dd13211fd72248241ff2ebc68e12a4c0bf30f5faa21499 F test/tt3_checkpoint.c 9e75cf7c1c364f52e1c47fd0f14c4340a9db0fe1 F test/tt3_core.c 8cd89ead95410f70e7fb02c79f1e040f9c5ad5cf F test/tt3_index.c 39eec10a35f57672225be4d182862152896dee4a @@ -1482,7 +1507,7 @@ F test/unique2.test 3674e9f2a3f1fbbfd4772ac74b7a97090d0f77d2 F test/unixexcl.test d936ba2b06794018e136418addd59a2354eeae97 F test/unordered.test ca7adce0419e4ca0c50f039885e76ed2c531eda8 F test/update.test 6c68446b8a0a33d522a7c72b320934596a2d7d32 -F test/update2.test fffc92e72ae568fe048588762e650cd8ccbd8c8b6e4fe9099231766bfe4b51de +F test/update2.test 5e67667e1c54017d964e626db765cf8bedcf87483c184f4c575bdb8c1dd2313e F test/uri.test 3481026f00ade6dfe8adb7acb6e1e47b04369568 F test/uri2.test 9d3ba7a53ee167572d53a298ee4a5d38ec4a8fb7 F test/userauth01.test e740a2697a7b40d7c5003a7d7edaee16acd349a9 @@ -1490,10 +1515,10 @@ 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/vacuum5.test c87234e8ca4107f349da4edbeda3e4ea5adc93f3 +F test/vacuum4.test 7ea76b769fffeb41f925303b04cbcf5a5bbeabe55e4c60ae754ff24eeeb7c010 +F test/vacuum5.test 263b144d537e92ad8e9ca8a73cc6e1583f41cfd0dda9432b87f7806174a2f48c F test/vacuummem.test 7b42abb3208bd82dd23a7536588396f295a314f2 -F test/varint.test ab7b110089a08b9926ed7390e7e97bdefeb74102 +F test/varint.test bbce22cda8fc4d135bcc2b589574be8410614e62 F test/veryquick.test 57ab846bacf7b90cf4e9a672721ea5c5b669b661 F test/view.test 765802c7a66d37fabd5ac8e2f2dbe572b43eb9ab F test/vtab1.test 8f91b9538d1404c3932293a588c4344218a0c94792d4289bb55e41020e7b3fff @@ -1518,7 +1543,7 @@ F test/vtab_alter.test 736e66fb5ec7b4fee58229aa3ada2f27ec58bc58c00edae4836890c37 F test/vtab_err.test 0d4d8eb4def1d053ac7c5050df3024fd47a3fbd8 F test/vtab_shared.test 5253bff2355a9a3f014c15337da7e177ab0ef8ad F test/wal.test 613efec03e517e1775d86b993a54877d2e29a477 -F test/wal2.test 3055879e7bf83c8d1b6130a4bdccdb3c230bc023a696850912ef6207d68dd24a +F test/wal2.test a225bafac35a47765b890bacdeb57e5e81039f21cc18a1e8ce88eb76e56b843c F test/wal3.test 2a93004bc0fb2b5c29888964024695bade278ab2 F test/wal4.test 4744e155cd6299c6bd99d3eab1c82f77db9cdb3c F test/wal5.test 9c11da7aeccd83a46d79a556ad11a18d3cb15aa9 @@ -1536,17 +1561,19 @@ F test/walcrash.test 21038858cc552077b0522f50b0fa87e38139306a F test/walcrash2.test a0edab4e5390f03b99a790de89aad15d6ec70b36 F test/walcrash3.test e426aa58122d20f2b9fbe9a507f9eb8cab85b8af F test/walcrash4.test e7b6e7639a950a0cca8e210e248c8dad4d63bf20 -F test/walfault.test 1f8389f7709877e9b4cc679033d71d6fe529056b +F test/walfault.test 09b8ad7e52d2f54bce50e31aa7ea51412bb9f70ac13c74e669ddcd8b48b0d98d F test/walhook.test ed00a40ba7255da22d6b66433ab61fab16a63483 -F test/walmode.test 4022fe03ae6e830583672caa101f046438a0473c +F test/walmode.test cd6e7cff618eaaa5910ce57c3657aa50110397f86213886a2400afb9bfec7b7b F test/walnoshm.test 84ca10c544632a756467336b7c3b864d493ee496 F test/waloverwrite.test dad2f26567f1b45174e54fbf9a8dc1cb876a7f03 F test/walpersist.test 8c6b7e3ec1ba91b5e4dc4e0921d6d3f87cd356a6 -F test/walprotocol.test 0b92feb132ccebd855494d917d3f6c2d717ace20 -F test/walro.test 4ab7ac01b77c2f894235c699d59e3e3c7f15a160 +F test/walprotocol.test a112aba0b79e3adeaa485fed09484b32c654e97df58e454aa8489ac2cd57bf84 +F test/walro.test cb438d05ba0d191f10b688e39c4f0cd5b71569a1d1f4440e5bdf3c6880e08c20 +F test/walro2.test 0e79dd15cbdb4f482c01ea248373669c732414a726b357d04846a816afafb768 +F test/walrofault.test c70cb6e308c443867701856cce92ad8288cd99488fa52afab77cca6cfd51af68 F test/walshared.test 0befc811dcf0b287efae21612304d15576e35417 F test/walslow.test c05c68d4dc2700a982f89133ce103a1a84cc285f -F test/walthread.test de8dbaf6d9e41481c460ba31ca61e163d7348f8e +F test/walthread.test 14b20fcfa6ae152f5d8e12f5dc8a8a724b7ef189f5d8ef1e2ceab79f2af51747 F test/where.test f0c325563acde44f2c4ea6ba348e9e29f7121757 F test/where2.test 478d2170637b9211f593120648858593bf2445a1 F test/where3.test 54cdeb02157acc979de41530b804ae7b09552bf1 @@ -1561,7 +1588,7 @@ F test/whereB.test 0def95db3bdec220a731c7e4bec5930327c1d8c5 F test/whereC.test cae295158703cb3fc23bf1a108a9ab730efff0f6 F test/whereD.test 711d4df58d6d4fb9b3f5ce040b818564198be002 F test/whereE.test b3a055eef928c992b0a33198a7b8dc10eea5ad2f -F test/whereF.test d44b58338fe5ddd7286023e9bedb255aa264a6c4d2168b49591b167371c675c7 +F test/whereF.test 3d9412b1199d3e2bed34fcb76b4c48d0bf4df95d27e3f8dd27b6f8b4716d0d89 F test/whereG.test dde4c52a97385a55be6a7cd46be8373f0cf35501 F test/whereH.test e4b07f7a3c2f5d31195cd33710054c78667573b2 F test/whereI.test eab5b226bbc344ac70d7dc09b963a064860ae6d7 @@ -1577,8 +1604,9 @@ F test/win32lock.test fbf107c91d8f5512be5a5b87c4c42ab9fdd54972 F test/win32longpath.test 169c75a3b2e43481f4a62122510210c67b08f26d F test/win32nolock.test ac4f08811a562e45a5755e661f45ca85892bdbbc F test/with1.test ca08e291249a810a2ec9b72ceef5575e07d5925b360fcf6652ae6fe06ac4dced -F test/with2.test 2b40da883658eb74ad8ad06afabe11a408e7fb87 +F test/with2.test e0030e2f0267a910d6c0e4f46f2dfe941c1cc0d4f659ba69b3597728e7e8f1ab F test/with3.test e71604a0e53cba82bc04c703987cb1d6751ec0b6 +F test/with4.test 257be66c0c67fee1defbbac0f685c3465e2cad037f21ce65f23f86084f198205 F test/withM.test 693b61765f2b387b5e3e24a4536e2e82de15ff64 F test/without_rowid1.test 06b7215130882d6a072233820dd364c874c4fd69221e8fc756ec471009192874 F test/without_rowid2.test af260339f79d13cb220288b67cd287fbcf81ad99 @@ -1589,11 +1617,14 @@ F test/without_rowid6.test 1f99644e6508447fb050f73697350c7ceca3392e F test/wordcount.c cb589cec469a1d90add05b1f8cee75c7210338d87a5afd65260ed5c0f4bbf8ac F test/writecrash.test f1da7f7adfe8d7f09ea79b42e5ca6dcc41102f27f8e334ad71539501ddd910cc F test/zeroblob.test 3857870fe681b8185654414a9bccfde80b62a0fa -F test/zerodamage.test e59a56443d6298ecf7435f618f0b27654f0c849e +F test/zerodamage.test 9c41628db7e8d9e8a0181e59ea5f189df311a9f6ce99cc376dc461f66db6f8dc +F test/zipfile.test 44aa8af115cc3e8c905468768dc761260650a8fdfca57e10f9818f5f8008d340 +F test/zipfile2.test 67d5f08a202796d4b7a71dfa4b8dcb74aa7a9d1f42c5f17bedff9855c1ba7aa5 +F test/zipfilefault.test 050be76778d2ec83566a542800fc3967f65ce719671a819016ada8b654bc14f7 F tool/GetFile.cs a15e08acb5dd7539b75ba23501581d7c2b462cb5 F tool/GetTclKit.bat 8995df40c4209808b31f24de0b58f90930239a234f7591e3675d45bfbb990c5d F tool/Replace.cs 02c67258801c2fb5f63231e0ac0f220b4b36ba91 -F tool/addopcodes.tcl c3e58098a42ce1525a9ff4d6d73bcbea667deea978428d2d59d90b9831479e5f +F tool/addopcodes.tcl 251331be53533f11d9ba36c730b9baaa911df89a08da3043344d0d634dc4a48e F tool/build-all-msvc.bat c12328d06c45fec8baada5949e3d5af54bf8c887 x F tool/build-shell.sh 950f47c6174f1eea171319438b93ba67ff5bf367 F tool/cg_anno.tcl f95b0006c52cf7f0496b506343415b6ee3cdcdd3 x @@ -1607,8 +1638,8 @@ F tool/genfkey.README cf68fddd4643bbe3ff8e31b8b6d8b0a1b85e20f4 F tool/genfkey.test 4196a8928b78f51d54ef58e99e99401ab2f0a7e5 F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce F tool/kvtest-speed.sh 4761a9c4b3530907562314d7757995787f7aef8f -F tool/lemon.c e6056373044d55296d21f81467dba7632bbb81dc49af072b3f0e76338771497e -F tool/lempar.c 105d0d9cbe5a25d24d4769241ffbfc63ac7c09e6ccee0dc43dcc8a4c4ae4e426 +F tool/lemon.c 7f7735326ca9c3b48327b241063cee52d35d44e20ebe1b3624a81658052a4d39 +F tool/lempar.c 468a155e8729cfbccfe1d85bf60d064f1dab76167a51149ec5c7928a2de63953 F tool/libvers.c caafc3b689638a1d88d44bc5f526c2278760d9b9 F tool/loadfts.c c3c64e4d5e90e8ba41159232c2189dba4be7b862 F tool/logest.c 11346aa019e2e77a00902aa7d0cabd27bd2e8cca @@ -1622,7 +1653,7 @@ F tool/mkopcodec.tcl d1b6362bd3aa80d5520d4d6f3765badf01f6c43c F tool/mkopcodeh.tcl 4ee2a30ccbd900dc4d5cdb61bdab87cd2166cd2affcc78c9cc0b8d22a65b2eee F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e F tool/mkpragmatab.tcl 2144bc8550a6471a029db262a132d2df4b9e0db61b90398bf64f5b7b3f8d92cd -F tool/mkshellc.tcl 574307265b49d813301fba91ccd74e6a26d33f65f74b6891c320a0ffbee07895 +F tool/mkshellc.tcl 1f45770aea226ac093a9c72f718efbb88a2a2833409ec2e1c4cecae4202626f5 F tool/mksourceid.c d458f9004c837bee87a6382228ac20d3eae3c49ea3b0a5aace936f8b60748d3b F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 F tool/mksqlite3c-noext.tcl fef88397668ae83166735c41af99d79f56afaabb @@ -1646,7 +1677,7 @@ F tool/showstat4.c 0682ebea7abf4d3657f53c4a243f2e7eab48eab344ed36a94bb75dcd19a5c F tool/showwal.c ad9d768f96ca6199ad3a8c9562d679680bd032dd01204ea3e5ea6fb931d81847 F tool/soak1.tcl 8d407956e1a45b485a8e072470a3e629a27037fe F tool/spaceanal.tcl 4bfd19aad7eb3ce0372ef0255f58035e0bba4ff5e9acfd763a10c6fb365c8dec -F tool/speed-check.sh a97ae367e9172a706101901e7caef48f1a14fc8a49053b25e79f6a67296b3412 +F tool/speed-check.sh 4ff9b095cf1a7643f0264e7fb7d23f0b12b7cce587a9de315877c378e90eeaf4 F tool/speedtest.tcl 06c76698485ccf597b9e7dbb1ac70706eb873355 F tool/speedtest16.c ecb6542862151c3e6509bbc00509b234562ae81e F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff @@ -1655,6 +1686,8 @@ F tool/speedtest8inst1.c 7ce07da76b5e745783e703a834417d725b7d45fd F tool/split-sqlite3c.tcl 3efcd4240b738f6bb2b5af0aea7e1e0ef9bc1c61654f645076cec883030b710c F tool/sqldiff.c 30879bbc8de686df4624e86adce2d8981f500904c1cfb55b5d1eea2ffd9341eb F tool/sqlite3_analyzer.c.in 7eeaae8b0d7577662acaabbb11107af0659d1b41bc1dfdd4d91422de27127968 +F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaaef898 +F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848 F tool/srcck1.c 371de5363b70154012955544f86fdee8f6e5326f F tool/stack_usage.tcl f8e71b92cdb099a147dad572375595eae55eca43 F tool/symbols-mingw.sh 4dbcea7e74768305384c9fd2ed2b41bbf9f0414d @@ -1686,7 +1719,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 d90e5f346bcf7adab26ca8dad9dfbd0fbb86604a15f2fe827f11b3faab036750 -R e43dfcde6e63fd81287681f03e602a2f -U drh -Z 3fc04e05119313efe5c6f01b2d450caa +P 163c870950f386f6b0bb1ff9b3886cf95ba0deed414cae75baf87621ed3528c2 1293d4f64f322ee57510bdff497c54e0be2c9205217e1fd96887bba1f36bec40 +R d95135aa69ebdf39b1d9aaa7d36a2c84 +U dan +Z 069d19cd10e378f6784afc839a1c9bac diff --git a/manifest.uuid b/manifest.uuid index af787c32f4..5638c6d4d4 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -163c870950f386f6b0bb1ff9b3886cf95ba0deed414cae75baf87621ed3528c2 \ No newline at end of file +36801effa9ec67b551f58972e21794466420f10cd0420701fcd87695e6cd11ee \ No newline at end of file diff --git a/src/analyze.c b/src/analyze.c index 495cc954ac..0d13d77790 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -234,6 +234,10 @@ static void openStatTable( "DELETE FROM %Q.%s WHERE %s=%Q", pDb->zDbSName, zTab, zWhereType, zWhere ); +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + }else if( db->xPreUpdateCallback ){ + sqlite3NestedParse(pParse, "DELETE FROM %Q.%s", pDb->zDbSName, zTab); +#endif }else{ /* The sqlite_stat[134] table already exists. Delete all rows. */ sqlite3VdbeAddOp2(v, OP_Clear, aRoot[i], iDb); @@ -998,6 +1002,9 @@ static void analyzeOneTable( int regIdxname = iMem++; /* Register containing index name */ int regStat1 = iMem++; /* Value for the stat column of sqlite_stat1 */ int regPrev = iMem; /* MUST BE LAST (see below) */ +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + Table *pStat1 = 0; +#endif pParse->nMem = MAX(pParse->nMem, iMem); v = sqlite3GetVdbe(pParse); @@ -1023,6 +1030,18 @@ static void analyzeOneTable( } #endif +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + if( db->xPreUpdateCallback ){ + pStat1 = (Table*)sqlite3DbMallocZero(db, sizeof(Table) + 13); + if( pStat1==0 ) return; + pStat1->zName = (char*)&pStat1[1]; + memcpy(pStat1->zName, "sqlite_stat1", 13); + pStat1->nCol = 3; + pStat1->iPKey = -1; + sqlite3VdbeAddOp4(pParse->pVdbe, OP_Noop, 0, 0, 0,(char*)pStat1,P4_DYNBLOB); + } +#endif + /* Establish a read-lock on the table at the shared-cache level. ** Open a read-only cursor on the table. Also allocate a cursor number ** to use for scanning indexes (iIdxCur). No index cursor is opened at @@ -1224,6 +1243,9 @@ static void analyzeOneTable( sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "BBB", 0); sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid); sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regTemp, regNewRowid); +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + sqlite3VdbeChangeP4(v, -1, (char*)pStat1, P4_TABLE); +#endif sqlite3VdbeChangeP5(v, OPFLAG_APPEND); /* Add the entries to the stat3 or stat4 table. */ @@ -1287,6 +1309,9 @@ static void analyzeOneTable( sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid); sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regTemp, regNewRowid); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + sqlite3VdbeChangeP4(v, -1, (char*)pStat1, P4_TABLE); +#endif sqlite3VdbeJumpHere(v, jZeroRows); } } diff --git a/src/attach.c b/src/attach.c index 68a9a77c66..f3d68553b6 100644 --- a/src/attach.c +++ b/src/attach.c @@ -504,8 +504,13 @@ int sqlite3FixSelect( if( sqlite3FixExpr(pFix, pSelect->pLimit) ){ return 1; } - if( sqlite3FixExpr(pFix, pSelect->pOffset) ){ - return 1; + if( pSelect->pWith ){ + int i; + for(i=0; ipWith->nCte; i++){ + if( sqlite3FixSelect(pFix, pSelect->pWith->a[i].pSelect) ){ + return 1; + } + } } pSelect = pSelect->pPrior; } diff --git a/src/btree.c b/src/btree.c index e46a3f2c34..50c5f6999a 100644 --- a/src/btree.c +++ b/src/btree.c @@ -112,6 +112,34 @@ int sqlite3_enable_shared_cache(int enable){ #define hasReadConflicts(a, b) 0 #endif +/* +** Implementation of the SQLITE_CORRUPT_PAGE() macro. Takes a single +** (MemPage*) as an argument. The (MemPage*) must not be NULL. +** +** If SQLITE_DEBUG is not defined, then this macro is equivalent to +** SQLITE_CORRUPT_BKPT. Or, if SQLITE_DEBUG is set, then the log message +** normally produced as a side-effect of SQLITE_CORRUPT_BKPT is augmented +** with the page number and filename associated with the (MemPage*). +*/ +#ifdef SQLITE_DEBUG +int corruptPageError(int lineno, MemPage *p){ + char *zMsg; + sqlite3BeginBenignMalloc(); + zMsg = sqlite3_mprintf("database corruption page %d of %s", + (int)p->pgno, sqlite3PagerFilename(p->pBt->pPager, 0) + ); + sqlite3EndBenignMalloc(); + if( zMsg ){ + sqlite3ReportError(SQLITE_CORRUPT, lineno, zMsg); + } + sqlite3_free(zMsg); + return SQLITE_CORRUPT_BKPT; +} +# define SQLITE_CORRUPT_PAGE(pMemPage) corruptPageError(__LINE__, pMemPage) +#else +# define SQLITE_CORRUPT_PAGE(pMemPage) SQLITE_CORRUPT_PGNO(pMemPage->pgno) +#endif + #ifndef SQLITE_OMIT_SHARED_CACHE #ifdef SQLITE_DEBUG @@ -1603,7 +1631,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ int sz = get2byte(&data[iFree+2]); int top = get2byte(&data[hdr+5]); if( top>=iFree ){ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } if( iFree2 ){ assert( iFree+sz<=iFree2 ); /* Verified by pageFindSlot() */ @@ -1637,13 +1665,13 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ ** if PRAGMA cell_size_check=ON. */ if( pciCellLast ){ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } assert( pc>=iCellFirst && pc<=iCellLast ); size = pPage->xCellSize(pPage, &src[pc]); cbrk -= size; if( cbrkusableSize ){ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } assert( cbrk+size<=usableSize && cbrk>=iCellFirst ); testcase( cbrk+size==usableSize ); @@ -1663,7 +1691,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ defragment_out: if( data[hdr+7]+cbrk-iCellFirst!=pPage->nFree ){ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } assert( cbrk>=iCellFirst ); put2byte(&data[hdr+5], cbrk); @@ -1707,7 +1735,7 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ testcase( x==4 ); testcase( x==3 ); if( size+pc > usableSize ){ - *pRc = SQLITE_CORRUPT_PGNO(pPg->pgno); + *pRc = SQLITE_CORRUPT_PAGE(pPg); return 0; }else if( x<4 ){ /* EVIDENCE-OF: R-11498-58022 In a well-formed b-tree page, the total @@ -1730,7 +1758,7 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ if( pcpgno); + *pRc = SQLITE_CORRUPT_PAGE(pPg); } return 0; @@ -1778,7 +1806,7 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ if( top==0 && pPage->pBt->usableSize==65536 ){ top = 65536; }else{ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } } @@ -1868,12 +1896,12 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ while( (iFreeBlk = get2byte(&data[iPtr]))pgno); + return SQLITE_CORRUPT_PAGE(pPage); } iPtr = iFreeBlk; } if( iFreeBlk>pPage->pBt->usableSize-4 ){ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } assert( iFreeBlk>iPtr || iFreeBlk==0 ); @@ -1885,10 +1913,10 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ */ if( iFreeBlk && iEnd+3>=iFreeBlk ){ nFrag = iFreeBlk - iEnd; - if( iEnd>iFreeBlk ) return SQLITE_CORRUPT_PGNO(pPage->pgno); + if( iEnd>iFreeBlk ) return SQLITE_CORRUPT_PAGE(pPage); iEnd = iFreeBlk + get2byte(&data[iFreeBlk+2]); if( iEnd > pPage->pBt->usableSize ){ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } iSize = iEnd - iStart; iFreeBlk = get2byte(&data[iFreeBlk]); @@ -1901,13 +1929,13 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ if( iPtr>hdr+1 ){ int iPtrEnd = iPtr + get2byte(&data[iPtr+2]); if( iPtrEnd+3>=iStart ){ - if( iPtrEnd>iStart ) return SQLITE_CORRUPT_PGNO(pPage->pgno); + if( iPtrEnd>iStart ) return SQLITE_CORRUPT_PAGE(pPage); nFrag += iStart - iPtrEnd; iSize = iEnd - iPtr; iStart = iPtr; } } - if( nFrag>data[hdr+7] ) return SQLITE_CORRUPT_PGNO(pPage->pgno); + if( nFrag>data[hdr+7] ) return SQLITE_CORRUPT_PAGE(pPage); data[hdr+7] -= nFrag; } x = get2byte(&data[hdr+5]); @@ -1915,7 +1943,7 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ /* The new freeblock is at the beginning of the cell content area, ** so just extend the cell content area rather than create another ** freelist entry */ - if( iStartpgno); + if( iStartpgno); + return SQLITE_CORRUPT_PAGE(pPage); } pPage->max1bytePayload = pBt->max1bytePayload; return SQLITE_OK; @@ -2029,7 +2057,7 @@ static int btreeInitPage(MemPage *pPage){ /* EVIDENCE-OF: R-28594-02890 The one-byte flag at offset 0 indicating ** the b-tree page type. */ if( decodeFlags(pPage, data[hdr]) ){ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } assert( pBt->pageSize>=512 && pBt->pageSize<=65536 ); pPage->maskPage = (u16)(pBt->pageSize - 1); @@ -2048,7 +2076,7 @@ static int btreeInitPage(MemPage *pPage){ pPage->nCell = get2byte(&data[hdr+3]); if( pPage->nCell>MX_CELL(pBt) ){ /* To many cells for a single page. The page must be corrupt */ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } testcase( pPage->nCell==MX_CELL(pBt) ); /* EVIDENCE-OF: R-24089-57979 If a page contains no cells (which is only @@ -2076,12 +2104,12 @@ static int btreeInitPage(MemPage *pPage){ testcase( pc==iCellFirst ); testcase( pc==iCellLast ); if( pciCellLast ){ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } sz = pPage->xCellSize(pPage, &data[pc]); testcase( pc+sz==usableSize ); if( pc+sz>usableSize ){ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } } if( !pPage->leaf ) iCellLast++; @@ -2099,12 +2127,12 @@ static int btreeInitPage(MemPage *pPage){ /* EVIDENCE-OF: R-55530-52930 In a well-formed b-tree page, there will ** always be at least one cell before the first freeblock. */ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } while( 1 ){ if( pc>iCellLast ){ /* Freeblock off the end of the page */ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } next = get2byte(&data[pc]); size = get2byte(&data[pc+2]); @@ -2114,11 +2142,11 @@ static int btreeInitPage(MemPage *pPage){ } if( next>0 ){ /* Freeblock not in ascending order */ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } if( pc+size>(unsigned int)usableSize ){ /* Last freeblock extends past page end */ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } } @@ -2130,7 +2158,7 @@ static int btreeInitPage(MemPage *pPage){ ** area, according to the page header, lies within the page. */ if( nFree>usableSize ){ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } pPage->nFree = (u16)(nFree - iCellFirst); pPage->isInit = 1; @@ -3687,7 +3715,7 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){ if( eType==PTRMAP_OVERFLOW2 ){ /* The pointer is always the first 4 bytes of the page in this case. */ if( get4byte(pPage->aData)!=iFrom ){ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } put4byte(pPage->aData, iTo); }else{ @@ -3706,7 +3734,7 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){ pPage->xParseCell(pPage, pCell, &info); if( info.nLocal pPage->aData+pPage->pBt->usableSize ){ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } if( iFrom==get4byte(pCell+info.nSize-4) ){ put4byte(pCell+info.nSize-4, iTo); @@ -3724,7 +3752,7 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){ if( i==nCell ){ if( eType!=PTRMAP_BTREE || get4byte(&pPage->aData[pPage->hdrOffset+8])!=iFrom ){ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } put4byte(&pPage->aData[pPage->hdrOffset+8], iTo); } @@ -4724,7 +4752,7 @@ int sqlite3BtreeCursorSize(void){ ** of run-time by skipping the initialization of those elements. */ void sqlite3BtreeCursorZero(BtCursor *p){ - memset(p, 0, offsetof(BtCursor, iPage)); + memset(p, 0, offsetof(BtCursor, BTCURSOR_FIRST_UNINIT)); } /* @@ -4767,11 +4795,19 @@ int sqlite3BtreeCloseCursor(BtCursor *pCur){ ** Using this cache reduces the number of calls to btreeParseCell(). */ #ifndef NDEBUG + static int cellInfoEqual(CellInfo *a, CellInfo *b){ + if( a->nKey!=b->nKey ) return 0; + if( a->pPayload!=b->pPayload ) return 0; + if( a->nPayload!=b->nPayload ) return 0; + if( a->nLocal!=b->nLocal ) return 0; + if( a->nSize!=b->nSize ) return 0; + return 1; + } static void assertCellInfo(BtCursor *pCur){ CellInfo info; memset(&info, 0, sizeof(info)); btreeParseCell(pCur->pPage, pCur->ix, &info); - assert( CORRUPT_DB || memcmp(&info, &pCur->info, sizeof(info))==0 ); + assert( CORRUPT_DB || cellInfoEqual(&info, &pCur->info) ); } #else #define assertCellInfo(x) @@ -4814,6 +4850,20 @@ i64 sqlite3BtreeIntegerKey(BtCursor *pCur){ return pCur->info.nKey; } +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC +/* +** Return the offset into the database file for the start of the +** payload to which the cursor is pointing. +*/ +i64 sqlite3BtreeOffset(BtCursor *pCur){ + assert( cursorHoldsMutex(pCur) ); + assert( pCur->eState==CURSOR_VALID ); + getCellInfo(pCur); + return (i64)pCur->pBt->pageSize*((i64)pCur->pPage->pgno - 1) + + (i64)(pCur->info.pPayload - pCur->pPage->aData); +} +#endif /* SQLITE_ENABLE_OFFSET_SQL_FUNC */ + /* ** Return the number of bytes of payload for the entry that pCur is ** currently pointing to. For table btrees, this will be the amount @@ -5000,7 +5050,7 @@ static int accessPayload( ** &aPayload[pCur->info.nLocal] > &pPage->aData[pBt->usableSize] ** but is recast into its current form to avoid integer overflow problems */ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } /* Check if data must be read/written to/from the btree page itself. */ @@ -5033,14 +5083,15 @@ static int accessPayload( */ if( (pCur->curFlags & BTCF_ValidOvfl)==0 ){ int nOvfl = (pCur->info.nPayload-pCur->info.nLocal+ovflSize-1)/ovflSize; - if( nOvfl>pCur->nOvflAlloc ){ + if( pCur->aOverflow==0 + || nOvfl*(int)sizeof(Pgno) > sqlite3MallocSize(pCur->aOverflow) + ){ Pgno *aNew = (Pgno*)sqlite3Realloc( pCur->aOverflow, nOvfl*2*sizeof(Pgno) ); if( aNew==0 ){ return SQLITE_NOMEM_BKPT; }else{ - pCur->nOvflAlloc = nOvfl*2; pCur->aOverflow = aNew; } } @@ -5148,7 +5199,7 @@ static int accessPayload( if( rc==SQLITE_OK && amt>0 ){ /* Overflow chain ends prematurely */ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } return rc; } @@ -5433,7 +5484,7 @@ static int moveToRoot(BtCursor *pCur){ ** (or the freelist). */ assert( pRoot->intKey==1 || pRoot->intKey==0 ); if( pRoot->isInit==0 || (pCur->pKeyInfo==0)!=pRoot->intKey ){ - return SQLITE_CORRUPT_PGNO(pCur->pPage->pgno); + return SQLITE_CORRUPT_PAGE(pCur->pPage); } skip_init: @@ -5706,7 +5757,7 @@ int sqlite3BtreeMovetoUnpacked( if( pPage->intKeyLeaf ){ while( 0x80 <= *(pCell++) ){ if( pCell>=pPage->aDataEnd ){ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } } } @@ -5780,7 +5831,7 @@ int sqlite3BtreeMovetoUnpacked( testcase( nCell==1 ); /* Invalid key size: 0x80 0x80 0x01 */ testcase( nCell==2 ); /* Minimum legal index key size */ if( nCell<2 ){ - rc = SQLITE_CORRUPT_PGNO(pPage->pgno); + rc = SQLITE_CORRUPT_PAGE(pPage); goto moveto_finish; } pCellKey = sqlite3Malloc( nCell+18 ); @@ -6571,9 +6622,8 @@ static void freePage(MemPage *pPage, int *pRC){ } /* -** Free any overflow pages associated with the given Cell. Write the -** local Cell size (the number of bytes on the original page, omitting -** overflow) into *pnSize. +** Free any overflow pages associated with the given Cell. Store +** size information about the cell in pInfo. */ static int clearCell( MemPage *pPage, /* The page that contains the Cell */ @@ -6593,7 +6643,7 @@ static int clearCell( } if( pCell+pInfo->nSize-1 > pPage->aData+pPage->maskPage ){ /* Cell extends past end of page */ - return SQLITE_CORRUPT_PGNO(pPage->pgno); + return SQLITE_CORRUPT_PAGE(pPage); } ovflPgno = get4byte(pCell + pInfo->nSize - 4); pBt = pPage->pBt; @@ -7778,7 +7828,7 @@ static int balance_nonroot( } /* Load b.apCell[] with pointers to all cells in pOld. If pOld - ** constains overflow cells, include them in the b.apCell[] array + ** contains overflow cells, include them in the b.apCell[] array ** in the correct spot. ** ** Note that when there are multiple overflow cells, it is always the diff --git a/src/btree.h b/src/btree.h index 8663dd5434..ea5c106d0b 100644 --- a/src/btree.h +++ b/src/btree.h @@ -291,6 +291,9 @@ int sqlite3BtreeNext(BtCursor*, int flags); int sqlite3BtreeEof(BtCursor*); int sqlite3BtreePrevious(BtCursor*, int flags); i64 sqlite3BtreeIntegerKey(BtCursor*); +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC +i64 sqlite3BtreeOffset(BtCursor*); +#endif int sqlite3BtreePayload(BtCursor*, u32 offset, u32 amt, void*); const void *sqlite3BtreePayloadFetch(BtCursor*, u32 *pAmt); u32 sqlite3BtreePayloadSize(BtCursor*); diff --git a/src/btreeInt.h b/src/btreeInt.h index 081a073dd0..9946a12576 100644 --- a/src/btreeInt.h +++ b/src/btreeInt.h @@ -510,20 +510,20 @@ struct BtCursor { u8 curFlags; /* zero or more BTCF_* flags defined below */ u8 curPagerFlags; /* Flags to send to sqlite3PagerGet() */ u8 hints; /* As configured by CursorSetHints() */ - int nOvflAlloc; /* Allocated size of aOverflow[] array */ - Btree *pBtree; /* The Btree to which this cursor belongs */ - BtShared *pBt; /* The BtShared this cursor points to */ - BtCursor *pNext; /* Forms a linked list of all cursors */ - Pgno *aOverflow; /* Cache of overflow page locations */ - CellInfo info; /* A parse of the cell we are pointing at */ - i64 nKey; /* Size of pKey, or last integer key */ - void *pKey; /* Saved key that was cursor last known position */ - Pgno pgnoRoot; /* The root page of this tree */ int skipNext; /* Prev() is noop if negative. Next() is noop if positive. ** Error code if eState==CURSOR_FAULT */ + Btree *pBtree; /* The Btree to which this cursor belongs */ + Pgno *aOverflow; /* Cache of overflow page locations */ + void *pKey; /* Saved key that was cursor last known position */ /* All fields above are zeroed when the cursor is allocated. See ** sqlite3BtreeCursorZero(). Fields that follow must be manually ** initialized. */ +#define BTCURSOR_FIRST_UNINIT pBt /* Name of first uninitialized field */ + BtShared *pBt; /* The BtShared this cursor points to */ + BtCursor *pNext; /* Forms a linked list of all cursors */ + CellInfo info; /* A parse of the cell we are pointing at */ + i64 nKey; /* Size of pKey, or last integer key */ + Pgno pgnoRoot; /* The root page of this tree */ i8 iPage; /* Index of current page in apPage */ u8 curIntKey; /* Value of apPage[0]->intKey */ u16 ix; /* Current index for apPage[iPage] */ @@ -573,8 +573,8 @@ struct BtCursor { ** Do nothing else with this cursor. Any attempt to use the cursor ** should return the error code stored in BtCursor.skipNext */ -#define CURSOR_INVALID 0 -#define CURSOR_VALID 1 +#define CURSOR_VALID 0 +#define CURSOR_INVALID 1 #define CURSOR_SKIPNEXT 2 #define CURSOR_REQUIRESEEK 3 #define CURSOR_FAULT 4 diff --git a/src/build.c b/src/build.c index 289efa8c25..8e69363b0a 100644 --- a/src/build.c +++ b/src/build.c @@ -1120,10 +1120,24 @@ void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){ */ void sqlite3AddNotNull(Parse *pParse, int onError){ Table *p; + Column *pCol; p = pParse->pNewTable; if( p==0 || NEVER(p->nCol<1) ) return; - p->aCol[p->nCol-1].notNull = (u8)onError; + pCol = &p->aCol[p->nCol-1]; + pCol->notNull = (u8)onError; p->tabFlags |= TF_HasNotNull; + + /* Set the uniqNotNull flag on any UNIQUE or PK indexes already created + ** on this column. */ + if( pCol->colFlags & COLFLAG_UNIQUE ){ + Index *pIdx; + for(pIdx=p->pIndex; pIdx; pIdx=pIdx->pNext){ + assert( pIdx->nKeyCol==1 && pIdx->onError!=OE_None ); + if( pIdx->aiColumn[0]==p->nCol-1 ){ + pIdx->uniqNotNull = 1; + } + } + } } /* @@ -1223,34 +1237,37 @@ char sqlite3AffinityType(const char *zIn, u8 *pszEst){ ** This routine is called by the parser while in the middle of ** parsing a CREATE TABLE statement. */ -void sqlite3AddDefaultValue(Parse *pParse, ExprSpan *pSpan){ +void sqlite3AddDefaultValue( + Parse *pParse, /* Parsing context */ + Expr *pExpr, /* The parsed expression of the default value */ + const char *zStart, /* Start of the default value text */ + const char *zEnd /* First character past end of defaut value text */ +){ Table *p; Column *pCol; sqlite3 *db = pParse->db; p = pParse->pNewTable; if( p!=0 ){ pCol = &(p->aCol[p->nCol-1]); - if( !sqlite3ExprIsConstantOrFunction(pSpan->pExpr, db->init.busy) ){ + if( !sqlite3ExprIsConstantOrFunction(pExpr, db->init.busy) ){ sqlite3ErrorMsg(pParse, "default value of column [%s] is not constant", pCol->zName); }else{ /* A copy of pExpr is used instead of the original, as pExpr contains - ** tokens that point to volatile memory. The 'span' of the expression - ** is required by pragma table_info. + ** tokens that point to volatile memory. */ Expr x; sqlite3ExprDelete(db, pCol->pDflt); memset(&x, 0, sizeof(x)); x.op = TK_SPAN; - x.u.zToken = sqlite3DbStrNDup(db, (char*)pSpan->zStart, - (int)(pSpan->zEnd - pSpan->zStart)); - x.pLeft = pSpan->pExpr; + x.u.zToken = sqlite3DbSpanDup(db, zStart, zEnd); + x.pLeft = pExpr; x.flags = EP_Skip; pCol->pDflt = sqlite3ExprDup(db, &x, EXPRDUP_REDUCE); sqlite3DbFree(db, x.u.zToken); } } - sqlite3ExprDelete(db, pSpan->pExpr); + sqlite3ExprDelete(db, pExpr); } /* @@ -1967,10 +1984,6 @@ void sqlite3EndTable( pParse->nTab = 2; addrTop = sqlite3VdbeCurrentAddr(v) + 1; sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop); - sqlite3SelectDestInit(&dest, SRT_Coroutine, regYield); - sqlite3Select(pParse, pSelect, &dest); - sqlite3VdbeEndCoroutine(v, regYield); - sqlite3VdbeJumpHere(v, addrTop - 1); if( pParse->nErr ) return; pSelTab = sqlite3ResultSetOfSelect(pParse, pSelect); if( pSelTab==0 ) return; @@ -1980,6 +1993,11 @@ void sqlite3EndTable( pSelTab->nCol = 0; pSelTab->aCol = 0; sqlite3DeleteTable(db, pSelTab); + sqlite3SelectDestInit(&dest, SRT_Coroutine, regYield); + sqlite3Select(pParse, pSelect, &dest); + if( pParse->nErr ) return; + sqlite3VdbeEndCoroutine(v, regYield); + sqlite3VdbeJumpHere(v, addrTop - 1); addrInsLoop = sqlite3VdbeAddOp1(v, OP_Yield, dest.iSDParm); VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_MakeRecord, dest.iSdst, dest.nSdst, regRec); @@ -2122,7 +2140,7 @@ void sqlite3CreateView( ** the end. */ sEnd = pParse->sLastToken; - assert( sEnd.z[0]!=0 ); + assert( sEnd.z[0]!=0 || sEnd.n==0 ); if( sEnd.z[0]!=';' ){ sEnd.z += sEnd.n; } @@ -3083,7 +3101,9 @@ void sqlite3CreateIndex( */ if( pList==0 ){ Token prevCol; - sqlite3TokenInit(&prevCol, pTab->aCol[pTab->nCol-1].zName); + Column *pCol = &pTab->aCol[pTab->nCol-1]; + pCol->colFlags |= COLFLAG_UNIQUE; + sqlite3TokenInit(&prevCol, pCol->zName); pList = sqlite3ExprListAppend(pParse, 0, sqlite3ExprAlloc(db, TK_ID, &prevCol, 0)); if( pList==0 ) goto exit_create_index; @@ -4366,6 +4386,18 @@ KeyInfo *sqlite3KeyInfoOfIndex(Parse *pParse, Index *pIdx){ pKey->aSortOrder[i] = pIdx->aSortOrder[i]; } if( pParse->nErr ){ + assert( pParse->rc==SQLITE_ERROR_MISSING_COLLSEQ ); + if( pIdx->bNoQuery==0 ){ + /* Deactivate the index because it contains an unknown collating + ** sequence. The only way to reactive the index is to reload the + ** schema. Adding the missing collating sequence later does not + ** reactive the index. The application had the chance to register + ** the missing index using the collation-needed callback. For + ** simplicity, SQLite will not give the application a second chance. + */ + pIdx->bNoQuery = 1; + pParse->rc = SQLITE_ERROR_RETRY; + } sqlite3KeyInfoUnref(pKey); pKey = 0; } diff --git a/src/callback.c b/src/callback.c index e08924b2c2..0396df7a02 100644 --- a/src/callback.c +++ b/src/callback.c @@ -105,6 +105,7 @@ CollSeq *sqlite3GetCollSeq( assert( !p || p->xCmp ); if( p==0 ){ sqlite3ErrorMsg(pParse, "no such collation sequence: %s", zName); + pParse->rc = SQLITE_ERROR_MISSING_COLLSEQ; } return p; } diff --git a/src/date.c b/src/date.c index c0ca4c9904..313c7f9137 100644 --- a/src/date.c +++ b/src/date.c @@ -39,7 +39,7 @@ ** ** Jean Meeus ** Astronomical Algorithms, 2nd Edition, 1998 -** ISBM 0-943396-61-1 +** ISBN 0-943396-61-1 ** Willmann-Bell, Inc ** Richmond, Virginia (USA) */ diff --git a/src/delete.c b/src/delete.c index 5c96fdfb8d..2b69e4763a 100644 --- a/src/delete.c +++ b/src/delete.c @@ -92,7 +92,6 @@ void sqlite3MaterializeView( Expr *pWhere, /* Optional WHERE clause to be added */ ExprList *pOrderBy, /* Optional ORDER BY clause */ Expr *pLimit, /* Optional LIMIT clause */ - Expr *pOffset, /* Optional OFFSET clause */ int iCur /* Cursor number for ephemeral table */ ){ SelectDest dest; @@ -110,7 +109,7 @@ void sqlite3MaterializeView( assert( pFrom->a[0].pUsing==0 ); } pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, pOrderBy, - SF_IncludeHidden, pLimit, pOffset); + SF_IncludeHidden, pLimit); sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur); sqlite3Select(pParse, pSel, &dest); sqlite3SelectDelete(db, pSel); @@ -132,7 +131,6 @@ Expr *sqlite3LimitWhere( Expr *pWhere, /* The WHERE clause. May be null */ ExprList *pOrderBy, /* The ORDER BY clause. May be null */ Expr *pLimit, /* The LIMIT clause. May be null */ - Expr *pOffset, /* The OFFSET clause. May be null */ char *zStmtType /* Either DELETE or UPDATE. For err msgs. */ ){ sqlite3 *db = pParse->db; @@ -149,8 +147,6 @@ Expr *sqlite3LimitWhere( sqlite3ErrorMsg(pParse, "ORDER BY without LIMIT on %s", zStmtType); sqlite3ExprDelete(pParse->db, pWhere); sqlite3ExprListDelete(pParse->db, pOrderBy); - sqlite3ExprDelete(pParse->db, pLimit); - sqlite3ExprDelete(pParse->db, pOffset); return 0; } @@ -158,8 +154,6 @@ Expr *sqlite3LimitWhere( ** is a limit/offset term to enforce. */ if( pLimit == 0 ) { - /* if pLimit is null, pOffset will always be null as well. */ - assert( pOffset == 0 ); return pWhere; } @@ -206,7 +200,7 @@ Expr *sqlite3LimitWhere( /* generate the SELECT expression tree. */ pSelect = sqlite3SelectNew(pParse, pEList, pSelectSrc, pWhere, 0 ,0, - pOrderBy,0,pLimit,pOffset + pOrderBy,0,pLimit ); /* now generate the new WHERE rowid IN clause for the DELETE/UDPATE */ @@ -229,8 +223,7 @@ void sqlite3DeleteFrom( SrcList *pTabList, /* The table from which we should delete things */ Expr *pWhere, /* The WHERE clause. May be null */ ExprList *pOrderBy, /* ORDER BY clause. May be null */ - Expr *pLimit, /* LIMIT clause. May be null */ - Expr *pOffset /* OFFSET clause. May be null */ + Expr *pLimit /* LIMIT clause. May be null */ ){ Vdbe *v; /* The virtual database engine */ Table *pTab; /* The table from which records will be deleted */ @@ -290,11 +283,11 @@ void sqlite3DeleteFrom( #ifndef SQLITE_OMIT_TRIGGER pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0); isView = pTab->pSelect!=0; - bComplex = pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0); #else # define pTrigger 0 # define isView 0 #endif + bComplex = pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0); #ifdef SQLITE_OMIT_VIEW # undef isView # define isView 0 @@ -303,10 +296,10 @@ void sqlite3DeleteFrom( #ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT if( !isView ){ pWhere = sqlite3LimitWhere( - pParse, pTabList, pWhere, pOrderBy, pLimit, pOffset, "DELETE" + pParse, pTabList, pWhere, pOrderBy, pLimit, "DELETE" ); pOrderBy = 0; - pLimit = pOffset = 0; + pLimit = 0; } #endif @@ -358,11 +351,11 @@ void sqlite3DeleteFrom( #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) if( isView ){ sqlite3MaterializeView(pParse, pTab, - pWhere, pOrderBy, pLimit, pOffset, iTabCur + pWhere, pOrderBy, pLimit, iTabCur ); iDataCur = iIdxCur = iTabCur; pOrderBy = 0; - pLimit = pOffset = 0; + pLimit = 0; } #endif @@ -609,7 +602,6 @@ delete_from_cleanup: #if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) sqlite3ExprListDelete(db, pOrderBy); sqlite3ExprDelete(db, pLimit); - sqlite3ExprDelete(db, pOffset); #endif sqlite3DbFree(db, aToOpen); return; @@ -768,7 +760,7 @@ void sqlite3GenerateRowDelete( u8 p5 = 0; sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek); sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, (count?OPFLAG_NCHANGE:0)); - if( pParse->nested==0 ){ + if( pParse->nested==0 || 0==sqlite3_stricmp(pTab->zName, "sqlite_stat1") ){ sqlite3VdbeAppendP4(v, (char*)pTab, P4_TABLE); } if( eMode!=ONEPASS_OFF ){ diff --git a/src/expr.c b/src/expr.c index 0a5dc913f6..d2fad0ffc3 100644 --- a/src/expr.c +++ b/src/expr.c @@ -658,16 +658,15 @@ static void heightOfExprList(ExprList *p, int *pnHeight){ } } } -static void heightOfSelect(Select *p, int *pnHeight){ - if( p ){ +static void heightOfSelect(Select *pSelect, int *pnHeight){ + Select *p; + for(p=pSelect; p; p=p->pPrior){ heightOfExpr(p->pWhere, pnHeight); heightOfExpr(p->pHaving, pnHeight); heightOfExpr(p->pLimit, pnHeight); - heightOfExpr(p->pOffset, pnHeight); heightOfExprList(p->pEList, pnHeight); heightOfExprList(p->pGroupBy, pnHeight); heightOfExprList(p->pOrderBy, pnHeight); - heightOfSelect(p->pPrior, pnHeight); } } @@ -1462,7 +1461,6 @@ Select *sqlite3SelectDup(sqlite3 *db, Select *pDup, int flags){ pNew->pNext = pNext; pNew->pPrior = 0; pNew->pLimit = sqlite3ExprDup(db, p->pLimit, flags); - pNew->pOffset = sqlite3ExprDup(db, p->pOffset, flags); pNew->iLimit = 0; pNew->iOffset = 0; pNew->selFlags = p->selFlags & ~SF_UsesEphemeral; @@ -1656,17 +1654,16 @@ void sqlite3ExprListSetName( void sqlite3ExprListSetSpan( Parse *pParse, /* Parsing context */ ExprList *pList, /* List to which to add the span. */ - ExprSpan *pSpan /* The span to be added */ + const char *zStart, /* Start of the span */ + const char *zEnd /* End of the span */ ){ sqlite3 *db = pParse->db; assert( pList!=0 || db->mallocFailed!=0 ); if( pList ){ struct ExprList_item *pItem = &pList->a[pList->nExpr-1]; assert( pList->nExpr>0 ); - assert( db->mallocFailed || pItem->pExpr==pSpan->pExpr ); sqlite3DbFree(db, pItem->zSpan); - pItem->zSpan = sqlite3DbStrNDup(db, (char*)pSpan->zStart, - (int)(pSpan->zEnd - pSpan->zStart)); + pItem->zSpan = sqlite3DbSpanDup(db, zStart, zEnd); } } @@ -1735,6 +1732,34 @@ int sqlite3SelectWalkFail(Walker *pWalker, Select *NotUsed){ return WRC_Abort; } +/* +** If the input expression is an ID with the name "true" or "false" +** then convert it into an TK_TRUEFALSE term. Return non-zero if +** the conversion happened, and zero if the expression is unaltered. +*/ +int sqlite3ExprIdToTrueFalse(Expr *pExpr){ + assert( pExpr->op==TK_ID || pExpr->op==TK_STRING ); + if( sqlite3StrICmp(pExpr->u.zToken, "true")==0 + || sqlite3StrICmp(pExpr->u.zToken, "false")==0 + ){ + pExpr->op = TK_TRUEFALSE; + return 1; + } + return 0; +} + +/* +** The argument must be a TK_TRUEFALSE Expr node. Return 1 if it is TRUE +** and 0 if it is FALSE. +*/ +int sqlite3ExprTruthValue(const Expr *pExpr){ + assert( pExpr->op==TK_TRUEFALSE ); + assert( sqlite3StrICmp(pExpr->u.zToken,"true")==0 + || sqlite3StrICmp(pExpr->u.zToken,"false")==0 ); + return pExpr->u.zToken[4]==0; +} + + /* ** These routines are Walker callbacks used to check expressions to ** see if they are "constant" for some definition of constant. The @@ -1782,6 +1807,12 @@ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){ return WRC_Abort; } case TK_ID: + /* Convert "true" or "false" in a DEFAULT clause into the + ** appropriate TK_TRUEFALSE operator */ + if( sqlite3ExprIdToTrueFalse(pExpr) ){ + return WRC_Prune; + } + /* Fall thru */ case TK_COLUMN: case TK_AGG_FUNCTION: case TK_AGG_COLUMN: @@ -2099,7 +2130,6 @@ static Select *isCandidateForInOpt(Expr *pX){ } assert( p->pGroupBy==0 ); /* Has no GROUP BY clause */ if( p->pLimit ) return 0; /* Has no LIMIT clause */ - assert( p->pOffset==0 ); /* No LIMIT means no OFFSET */ if( p->pWhere ) return 0; /* Has no WHERE clause */ pSrc = p->pSrc; assert( pSrc!=0 ); @@ -2189,16 +2219,15 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** pX->iTable made to point to the ephemeral table instead of an ** existing table. ** -** The inFlags parameter must contain exactly one of the bits -** IN_INDEX_MEMBERSHIP or IN_INDEX_LOOP. If inFlags contains -** IN_INDEX_MEMBERSHIP, then the generated table will be used for a -** fast membership test. When the IN_INDEX_LOOP bit is set, the -** IN index will be used to loop over all values of the RHS of the -** IN operator. +** The inFlags parameter must contain, at a minimum, one of the bits +** IN_INDEX_MEMBERSHIP or IN_INDEX_LOOP but not both. If inFlags contains +** IN_INDEX_MEMBERSHIP, then the generated table will be used for a fast +** membership test. When the IN_INDEX_LOOP bit is set, the IN index will +** be used to loop over all values of the RHS of the IN operator. ** ** 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 columns are guaranteed +** An epheremal table will be created 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. ** @@ -2739,6 +2768,7 @@ int sqlite3CodeSubselect( Select *pSel; /* SELECT statement to encode */ SelectDest dest; /* How to deal with SELECT result */ int nReg; /* Registers to allocate */ + Expr *pLimit; /* New limit expression */ testcase( pExpr->op==TK_EXISTS ); testcase( pExpr->op==TK_SELECT ); @@ -2760,11 +2790,14 @@ int sqlite3CodeSubselect( sqlite3VdbeAddOp2(v, OP_Integer, 0, dest.iSDParm); VdbeComment((v, "Init EXISTS result")); } - sqlite3ExprDelete(pParse->db, pSel->pLimit); - pSel->pLimit = sqlite3ExprAlloc(pParse->db, TK_INTEGER, - &sqlite3IntTokens[1], 0); + pLimit = sqlite3ExprAlloc(pParse->db, TK_INTEGER,&sqlite3IntTokens[1], 0); + if( pSel->pLimit ){ + sqlite3ExprDelete(pParse->db, pSel->pLimit->pLeft); + pSel->pLimit->pLeft = pLimit; + }else{ + pSel->pLimit = sqlite3PExpr(pParse, TK_LIMIT, pLimit, 0); + } pSel->iLimit = 0; - pSel->selFlags &= ~SF_MultiValue; if( sqlite3Select(pParse, pSel, &dest) ){ return 0; } @@ -3544,6 +3577,10 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ codeInteger(pParse, pExpr, 0, target); return target; } + case TK_TRUEFALSE: { + sqlite3VdbeAddOp2(v, OP_Integer, sqlite3ExprTruthValue(pExpr), target); + return target; + } #ifndef SQLITE_OMIT_FLOATING_POINT case TK_FLOAT: { assert( !ExprHasProperty(pExpr, EP_IntValue) ); @@ -3699,6 +3736,18 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ sqlite3VdbeAddOp2(v, op, r1, inReg); break; } + case TK_TRUTH: { + int isTrue; /* IS TRUE or IS NOT TRUE */ + int bNormal; /* IS TRUE or IS FALSE */ + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); + testcase( regFree1==0 ); + isTrue = sqlite3ExprTruthValue(pExpr->pRight); + bNormal = pExpr->op2==TK_IS; + testcase( isTrue && bNormal); + testcase( !isTrue && bNormal); + sqlite3VdbeAddOp4Int(v, OP_IsTrue, r1, inReg, !isTrue, isTrue ^ bNormal); + break; + } case TK_ISNULL: case TK_NOTNULL: { int addr; @@ -3870,9 +3919,21 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ if( !pColl ) pColl = db->pDfltColl; sqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0, (char *)pColl, P4_COLLSEQ); } - sqlite3VdbeAddOp4(v, pParse->iSelfTab ? OP_PureFunc0 : OP_Function0, - constMask, r1, target, (char*)pDef, P4_FUNCDEF); - sqlite3VdbeChangeP5(v, (u8)nFarg); +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + if( pDef->funcFlags & SQLITE_FUNC_OFFSET ){ + Expr *pArg = pFarg->a[0].pExpr; + if( pArg->op==TK_COLUMN ){ + sqlite3VdbeAddOp3(v, OP_Offset, pArg->iTable, pArg->iColumn, target); + }else{ + sqlite3VdbeAddOp2(v, OP_Null, 0, target); + } + }else +#endif + { + sqlite3VdbeAddOp4(v, pParse->iSelfTab ? OP_PureFunc0 : OP_Function0, + constMask, r1, target, (char*)pDef, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, (u8)nFarg); + } if( nFarg && constMask==0 ){ sqlite3ReleaseTempRange(pParse, r1, nFarg); } @@ -4462,6 +4523,23 @@ void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull); break; } + case TK_TRUTH: { + int isNot; /* IS NOT TRUE or IS NOT FALSE */ + int isTrue; /* IS TRUE or IS NOT TRUE */ + testcase( jumpIfNull==0 ); + isNot = pExpr->op2==TK_ISNOT; + isTrue = sqlite3ExprTruthValue(pExpr->pRight); + testcase( isTrue && isNot ); + testcase( !isTrue && isNot ); + if( isTrue ^ isNot ){ + sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, + isNot ? SQLITE_JUMPIFNULL : 0); + }else{ + sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, + isNot ? SQLITE_JUMPIFNULL : 0); + } + break; + } case TK_IS: case TK_ISNOT: testcase( op==TK_IS ); @@ -4616,6 +4694,26 @@ void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull); break; } + case TK_TRUTH: { + int isNot; /* IS NOT TRUE or IS NOT FALSE */ + int isTrue; /* IS TRUE or IS NOT TRUE */ + testcase( jumpIfNull==0 ); + isNot = pExpr->op2==TK_ISNOT; + isTrue = sqlite3ExprTruthValue(pExpr->pRight); + testcase( isTrue && isNot ); + testcase( !isTrue && isNot ); + if( isTrue ^ isNot ){ + /* IS TRUE and IS NOT FALSE */ + sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, + isNot ? 0 : SQLITE_JUMPIFNULL); + + }else{ + /* IS FALSE and IS NOT TRUE */ + sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, + isNot ? 0 : SQLITE_JUMPIFNULL); + } + break; + } case TK_IS: case TK_ISNOT: testcase( pExpr->op==TK_IS ); diff --git a/src/fkey.c b/src/fkey.c index 44ef3c7c7b..c366c1b3aa 100644 --- a/src/fkey.c +++ b/src/fkey.c @@ -725,7 +725,7 @@ void sqlite3FkDropTable(Parse *pParse, SrcList *pName, Table *pTab){ } pParse->disableTriggers = 1; - sqlite3DeleteFrom(pParse, sqlite3SrcListDup(db, pName, 0), 0, 0, 0, 0); + sqlite3DeleteFrom(pParse, sqlite3SrcListDup(db, pName, 0), 0, 0, 0); pParse->disableTriggers = 0; /* If the DELETE has generated immediate foreign key constraint @@ -1283,7 +1283,7 @@ static Trigger *fkActionTrigger( sqlite3ExprListAppend(pParse, 0, pRaise), sqlite3SrcListAppend(db, 0, &tFrom, 0), pWhere, - 0, 0, 0, 0, 0, 0 + 0, 0, 0, 0, 0 ); pWhere = 0; } diff --git a/src/func.c b/src/func.c index 7528fa8b4d..deb7e74928 100644 --- a/src/func.c +++ b/src/func.c @@ -35,6 +35,8 @@ static CollSeq *sqlite3GetFuncCollSeq(sqlite3_context *context){ ** iteration of the aggregate loop. */ static void sqlite3SkipAccumulatorLoad(sqlite3_context *context){ + assert( context->isError<=0 ); + context->isError = -1; context->skipFlag = 1; } @@ -101,8 +103,6 @@ static void lengthFunc( int argc, sqlite3_value **argv ){ - int len; - assert( argc==1 ); UNUSED_PARAMETER(argc); switch( sqlite3_value_type(argv[0]) ){ @@ -114,13 +114,17 @@ static void lengthFunc( } case SQLITE_TEXT: { const unsigned char *z = sqlite3_value_text(argv[0]); + const unsigned char *z0; + unsigned char c; if( z==0 ) return; - len = 0; - while( *z ){ - len++; - SQLITE_SKIP_UTF8(z); + z0 = z; + while( (c = *z)!=0 ){ + z++; + if( c>=0xc0 ){ + while( (*z & 0xc0)==0x80 ){ z++; z0++; } + } } - sqlite3_result_int(context, len); + sqlite3_result_int(context, (int)(z-z0)); break; } default: { @@ -1195,6 +1199,8 @@ static void replaceFunc( i64 nOut; /* Maximum size of zOut */ int loopLimit; /* Last zStr[] that might match zPattern[] */ int i, j; /* Loop counters */ + unsigned cntExpand; /* Number zOut expansions */ + sqlite3 *db = sqlite3_context_db_handle(context); assert( argc==3 ); UNUSED_PARAMETER(argc); @@ -1226,33 +1232,40 @@ static void replaceFunc( return; } loopLimit = nStr - nPattern; + cntExpand = 0; for(i=j=0; i<=loopLimit; i++){ if( zStr[i]!=zPattern[0] || memcmp(&zStr[i], zPattern, nPattern) ){ zOut[j++] = zStr[i]; }else{ - u8 *zOld; - sqlite3 *db = sqlite3_context_db_handle(context); - nOut += nRep - nPattern; - testcase( nOut-1==db->aLimit[SQLITE_LIMIT_LENGTH] ); - testcase( nOut-2==db->aLimit[SQLITE_LIMIT_LENGTH] ); - if( nOut-1>db->aLimit[SQLITE_LIMIT_LENGTH] ){ - sqlite3_result_error_toobig(context); - sqlite3_free(zOut); - return; - } - zOld = zOut; - zOut = sqlite3_realloc64(zOut, (int)nOut); - if( zOut==0 ){ - sqlite3_result_error_nomem(context); - sqlite3_free(zOld); - return; + if( nRep>nPattern ){ + nOut += nRep - nPattern; + testcase( nOut-1==db->aLimit[SQLITE_LIMIT_LENGTH] ); + testcase( nOut-2==db->aLimit[SQLITE_LIMIT_LENGTH] ); + if( nOut-1>db->aLimit[SQLITE_LIMIT_LENGTH] ){ + sqlite3_result_error_toobig(context); + sqlite3_free(zOut); + return; + } + cntExpand++; + if( (cntExpand&(cntExpand-1))==0 ){ + /* Grow the size of the output buffer only on substitutions + ** whose index is a power of two: 1, 2, 4, 8, 16, 32, ... */ + u8 *zOld; + zOld = zOut; + zOut = sqlite3_realloc64(zOut, (int)nOut + (nOut - nStr - 1)); + if( zOut==0 ){ + sqlite3_result_error_nomem(context); + sqlite3_free(zOld); + return; + } + } } memcpy(&zOut[j], zRep, nRep); j += nRep; i += nPattern-1; } } - assert( j+nStr-i+1==nOut ); + assert( j+nStr-i+1<=nOut ); memcpy(&zOut[j], &zStr[i], nStr-i); j += nStr - i; assert( j<=nOut ); @@ -1798,6 +1811,10 @@ void sqlite3RegisterBuiltinFunctions(void){ FUNCTION2(likely, 1, 0, 0, noopFunc, SQLITE_FUNC_UNLIKELY), #ifdef SQLITE_DEBUG FUNCTION2(affinity, 1, 0, 0, noopFunc, SQLITE_FUNC_AFFINITY), +#endif +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + FUNCTION2(sqlite_offset, 1, 0, 0, noopFunc, SQLITE_FUNC_OFFSET| + SQLITE_FUNC_TYPEOF), #endif FUNCTION(ltrim, 1, 1, 0, trimFunc ), FUNCTION(ltrim, 2, 1, 0, trimFunc ), diff --git a/src/global.c b/src/global.c index e66be81495..04a3d185a8 100644 --- a/src/global.c +++ b/src/global.c @@ -258,6 +258,13 @@ const Token sqlite3IntTokens[] = { { "1", 1 } }; +#ifdef VDBE_PROFILE +/* +** The following performance counter can be used in place of +** sqlite3Hwtime() for profiling. This is a no-op on standard builds. +*/ +sqlite3_uint64 sqlite3NProfileCnt = 0; +#endif /* ** The value of the "pending" byte must be 0x40000000 (1 byte past the diff --git a/src/insert.c b/src/insert.c index 3a531495cf..e1514692cc 100644 --- a/src/insert.c +++ b/src/insert.c @@ -1571,6 +1571,7 @@ void sqlite3GenerateConstraintChecks( } /* Check to see if the new index entry will be unique */ + sqlite3ExprCachePush(pParse); sqlite3VdbeAddOp4Int(v, OP_NoConflict, iThisCur, addrUniqueOk, regIdx, pIdx->nKeyCol); VdbeCoverage(v); @@ -1659,6 +1660,7 @@ void sqlite3GenerateConstraintChecks( } } sqlite3VdbeResolveLabel(v, addrUniqueOk); + sqlite3ExprCachePop(pParse); if( regR!=regIdx ) sqlite3ReleaseTempRange(pParse, regR, nPkField); } if( ipkTop ){ @@ -2007,7 +2009,6 @@ static int xferOptimization( if( pSelect->pLimit ){ return 0; /* SELECT may not have a LIMIT clause */ } - assert( pSelect->pOffset==0 ); /* Must be so if pLimit==0 */ if( pSelect->pPrior ){ return 0; /* SELECT may not be a compound query */ } diff --git a/src/loadext.c b/src/loadext.c index 6751425936..cf58237bf9 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -430,7 +430,11 @@ static const sqlite3_api_routines sqlite3Apis = { sqlite3_prepare16_v3, sqlite3_bind_pointer, sqlite3_result_pointer, - sqlite3_value_pointer + sqlite3_value_pointer, + /* Version 3.22.0 and later */ + sqlite3_vtab_nochange, + sqlite3_value_nochange, + sqlite3_vtab_collation }; /* @@ -496,8 +500,10 @@ static int sqlite3LoadExtension( #if SQLITE_OS_UNIX || SQLITE_OS_WIN for(ii=0; ii=0) && rcmutex = sqlite3MutexAlloc(SQLITE_MUTEX_RECURSIVE); if( db->mutex==0 ){ sqlite3_free(db); db = 0; goto opendb_out; } + if( isThreadsafe==0 ){ + sqlite3MutexWarnOnContention(db->mutex); + } } sqlite3_mutex_enter(db->mutex); db->errMask = 0xff; @@ -3042,7 +3062,7 @@ static int openDatabase( } #endif -#ifdef SQLITE_ENABLE_ICU +#if defined(SQLITE_ENABLE_ICU) || defined(SQLITE_ENABLE_ICU_COLLATIONS) if( !db->mallocFailed && rc==SQLITE_OK ){ rc = sqlite3IcuInit(db); } @@ -3344,37 +3364,37 @@ int sqlite3_get_autocommit(sqlite3 *db){ ** 2. Invoke sqlite3_log() to provide the source code location where ** a low-level error is first detected. */ -static int reportError(int iErr, int lineno, const char *zType){ +int sqlite3ReportError(int iErr, int lineno, const char *zType){ sqlite3_log(iErr, "%s at line %d of [%.10s]", zType, lineno, 20+sqlite3_sourceid()); return iErr; } int sqlite3CorruptError(int lineno){ testcase( sqlite3GlobalConfig.xLog!=0 ); - return reportError(SQLITE_CORRUPT, lineno, "database corruption"); + return sqlite3ReportError(SQLITE_CORRUPT, lineno, "database corruption"); } int sqlite3MisuseError(int lineno){ testcase( sqlite3GlobalConfig.xLog!=0 ); - return reportError(SQLITE_MISUSE, lineno, "misuse"); + return sqlite3ReportError(SQLITE_MISUSE, lineno, "misuse"); } int sqlite3CantopenError(int lineno){ testcase( sqlite3GlobalConfig.xLog!=0 ); - return reportError(SQLITE_CANTOPEN, lineno, "cannot open file"); + return sqlite3ReportError(SQLITE_CANTOPEN, lineno, "cannot open file"); } #ifdef SQLITE_DEBUG int sqlite3CorruptPgnoError(int lineno, Pgno pgno){ char zMsg[100]; sqlite3_snprintf(sizeof(zMsg), zMsg, "database corruption page %d", pgno); testcase( sqlite3GlobalConfig.xLog!=0 ); - return reportError(SQLITE_CORRUPT, lineno, zMsg); + return sqlite3ReportError(SQLITE_CORRUPT, lineno, zMsg); } int sqlite3NomemError(int lineno){ testcase( sqlite3GlobalConfig.xLog!=0 ); - return reportError(SQLITE_NOMEM, lineno, "OOM"); + return sqlite3ReportError(SQLITE_NOMEM, lineno, "OOM"); } int sqlite3IoerrnomemError(int lineno){ testcase( sqlite3GlobalConfig.xLog!=0 ); - return reportError(SQLITE_IOERR_NOMEM, lineno, "I/O OOM error"); + return sqlite3ReportError(SQLITE_IOERR_NOMEM, lineno, "I/O OOM error"); } #endif @@ -3902,6 +3922,22 @@ int sqlite3_test_control(int op, ...){ sqlite3_mutex_leave(db->mutex); break; } + +#if defined(YYCOVERAGE) + /* sqlite3_test_control(SQLITE_TESTCTRL_PARSER_COVERAGE, FILE *out) + ** + ** This test control (only available when SQLite is compiled with + ** -DYYCOVERAGE) writes a report onto "out" that shows all + ** state/lookahead combinations in the parser state machine + ** which are never exercised. If any state is missed, make the + ** return code SQLITE_ERROR. + */ + case SQLITE_TESTCTRL_PARSER_COVERAGE: { + FILE *out = va_arg(ap, FILE*); + if( sqlite3ParserCoverage(out) ) rc = SQLITE_ERROR; + break; + } +#endif /* defined(YYCOVERAGE) */ } va_end(ap); #endif /* SQLITE_UNTESTABLE */ diff --git a/src/malloc.c b/src/malloc.c index b750f6e72f..d7f9df5efc 100644 --- a/src/malloc.c +++ b/src/malloc.c @@ -627,6 +627,19 @@ char *sqlite3DbStrNDup(sqlite3 *db, const char *z, u64 n){ return zNew; } +/* +** The text between zStart and zEnd represents a phrase within a larger +** SQL statement. Make a copy of this phrase in space obtained form +** sqlite3DbMalloc(). Omit leading and trailing whitespace. +*/ +char *sqlite3DbSpanDup(sqlite3 *db, const char *zStart, const char *zEnd){ + int n; + while( sqlite3Isspace(zStart[0]) ) zStart++; + n = (int)(zEnd - zStart); + while( ALWAYS(n>0) && sqlite3Isspace(zStart[n-1]) ) n--; + return sqlite3DbStrNDup(db, zStart, n); +} + /* ** Free any prior content in *pz and replace it with a copy of zNew. */ diff --git a/src/mutex.c b/src/mutex.c index 6f1bc9767d..3699f91e38 100644 --- a/src/mutex.c +++ b/src/mutex.c @@ -26,6 +26,193 @@ static SQLITE_WSD int mutexIsInit = 0; #ifndef SQLITE_MUTEX_OMIT + +#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS +/* +** This block (enclosed by SQLITE_ENABLE_MULTITHREADED_CHECKS) contains +** the implementation of a wrapper around the system default mutex +** implementation (sqlite3DefaultMutex()). +** +** Most calls are passed directly through to the underlying default +** mutex implementation. Except, if a mutex is configured by calling +** sqlite3MutexWarnOnContention() on it, then if contention is ever +** encountered within xMutexEnter() a warning is emitted via sqlite3_log(). +** +** This type of mutex is used as the database handle mutex when testing +** apps that usually use SQLITE_CONFIG_MULTITHREAD mode. +*/ + +/* +** Type for all mutexes used when SQLITE_ENABLE_MULTITHREADED_CHECKS +** is defined. Variable CheckMutex.mutex is a pointer to the real mutex +** allocated by the system mutex implementation. Variable iType is usually set +** to the type of mutex requested - SQLITE_MUTEX_RECURSIVE, SQLITE_MUTEX_FAST +** or one of the static mutex identifiers. Or, if this is a recursive mutex +** that has been configured using sqlite3MutexWarnOnContention(), it is +** set to SQLITE_MUTEX_WARNONCONTENTION. +*/ +typedef struct CheckMutex CheckMutex; +struct CheckMutex { + int iType; + sqlite3_mutex *mutex; +}; + +#define SQLITE_MUTEX_WARNONCONTENTION (-1) + +/* +** Pointer to real mutex methods object used by the CheckMutex +** implementation. Set by checkMutexInit(). +*/ +static SQLITE_WSD const sqlite3_mutex_methods *pGlobalMutexMethods; + +#ifdef SQLITE_DEBUG +static int checkMutexHeld(sqlite3_mutex *p){ + return pGlobalMutexMethods->xMutexHeld(((CheckMutex*)p)->mutex); +} +static int checkMutexNotheld(sqlite3_mutex *p){ + return pGlobalMutexMethods->xMutexNotheld(((CheckMutex*)p)->mutex); +} +#endif + +/* +** Initialize and deinitialize the mutex subsystem. +*/ +static int checkMutexInit(void){ + pGlobalMutexMethods = sqlite3DefaultMutex(); + return SQLITE_OK; +} +static int checkMutexEnd(void){ + pGlobalMutexMethods = 0; + return SQLITE_OK; +} + +/* +** Allocate a mutex. +*/ +static sqlite3_mutex *checkMutexAlloc(int iType){ + static CheckMutex staticMutexes[] = { + {2, 0}, {3, 0}, {4, 0}, {5, 0}, + {6, 0}, {7, 0}, {8, 0}, {9, 0}, + {10, 0}, {11, 0}, {12, 0}, {13, 0} + }; + CheckMutex *p = 0; + + assert( SQLITE_MUTEX_RECURSIVE==1 && SQLITE_MUTEX_FAST==0 ); + if( iType<2 ){ + p = sqlite3MallocZero(sizeof(CheckMutex)); + if( p==0 ) return 0; + p->iType = iType; + }else{ +#ifdef SQLITE_ENABLE_API_ARMOR + if( iType-2>=ArraySize(staticMutexes) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + p = &staticMutexes[iType-2]; + } + + if( p->mutex==0 ){ + p->mutex = pGlobalMutexMethods->xMutexAlloc(iType); + if( p->mutex==0 ){ + if( iType<2 ){ + sqlite3_free(p); + } + p = 0; + } + } + + return (sqlite3_mutex*)p; +} + +/* +** Free a mutex. +*/ +static void checkMutexFree(sqlite3_mutex *p){ + assert( SQLITE_MUTEX_RECURSIVE<2 ); + assert( SQLITE_MUTEX_FAST<2 ); + assert( SQLITE_MUTEX_WARNONCONTENTION<2 ); + +#if SQLITE_ENABLE_API_ARMOR + if( ((CheckMutex*)p)->iType<2 ) +#endif + { + CheckMutex *pCheck = (CheckMutex*)p; + pGlobalMutexMethods->xMutexFree(pCheck->mutex); + sqlite3_free(pCheck); + } +#ifdef SQLITE_ENABLE_API_ARMOR + else{ + (void)SQLITE_MISUSE_BKPT; + } +#endif +} + +/* +** Enter the mutex. +*/ +static void checkMutexEnter(sqlite3_mutex *p){ + CheckMutex *pCheck = (CheckMutex*)p; + if( pCheck->iType==SQLITE_MUTEX_WARNONCONTENTION ){ + if( SQLITE_OK==pGlobalMutexMethods->xMutexTry(pCheck->mutex) ){ + return; + } + sqlite3_log(SQLITE_MISUSE, + "illegal multi-threaded access to database connection" + ); + } + pGlobalMutexMethods->xMutexEnter(pCheck->mutex); +} + +/* +** Enter the mutex (do not block). +*/ +static int checkMutexTry(sqlite3_mutex *p){ + CheckMutex *pCheck = (CheckMutex*)p; + return pGlobalMutexMethods->xMutexTry(pCheck->mutex); +} + +/* +** Leave the mutex. +*/ +static void checkMutexLeave(sqlite3_mutex *p){ + CheckMutex *pCheck = (CheckMutex*)p; + pGlobalMutexMethods->xMutexLeave(pCheck->mutex); +} + +sqlite3_mutex_methods const *multiThreadedCheckMutex(void){ + static const sqlite3_mutex_methods sMutex = { + checkMutexInit, + checkMutexEnd, + checkMutexAlloc, + checkMutexFree, + checkMutexEnter, + checkMutexTry, + checkMutexLeave, +#ifdef SQLITE_DEBUG + checkMutexHeld, + checkMutexNotheld +#else + 0, + 0 +#endif + }; + return &sMutex; +} + +/* +** Mark the SQLITE_MUTEX_RECURSIVE mutex passed as the only argument as +** one on which there should be no contention. +*/ +void sqlite3MutexWarnOnContention(sqlite3_mutex *p){ + if( sqlite3GlobalConfig.mutex.xMutexAlloc==checkMutexAlloc ){ + CheckMutex *pCheck = (CheckMutex*)p; + assert( pCheck->iType==SQLITE_MUTEX_RECURSIVE ); + pCheck->iType = SQLITE_MUTEX_WARNONCONTENTION; + } +} +#endif /* ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS */ + /* ** Initialize the mutex system. */ @@ -41,7 +228,11 @@ int sqlite3MutexInit(void){ sqlite3_mutex_methods *pTo = &sqlite3GlobalConfig.mutex; if( sqlite3GlobalConfig.bCoreMutex ){ +#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS + pFrom = multiThreadedCheckMutex(); +#else pFrom = sqlite3DefaultMutex(); +#endif }else{ pFrom = sqlite3NoopMutex(); } @@ -167,3 +358,4 @@ int sqlite3_mutex_notheld(sqlite3_mutex *p){ #endif #endif /* !defined(SQLITE_MUTEX_OMIT) */ + diff --git a/src/mutex_unix.c b/src/mutex_unix.c index 55d08c8052..9282d28016 100644 --- a/src/mutex_unix.c +++ b/src/mutex_unix.c @@ -50,11 +50,12 @@ struct sqlite3_mutex { #endif }; #if SQLITE_MUTEX_NREF -#define SQLITE3_MUTEX_INITIALIZER {PTHREAD_MUTEX_INITIALIZER,0,0,(pthread_t)0,0} +# define SQLITE3_MUTEX_INITIALIZER(id) \ + {PTHREAD_MUTEX_INITIALIZER,id,0,(pthread_t)0,0} #elif defined(SQLITE_ENABLE_API_ARMOR) -#define SQLITE3_MUTEX_INITIALIZER { PTHREAD_MUTEX_INITIALIZER, 0 } +# define SQLITE3_MUTEX_INITIALIZER(id) { PTHREAD_MUTEX_INITIALIZER, id } #else -#define SQLITE3_MUTEX_INITIALIZER { PTHREAD_MUTEX_INITIALIZER } +#define SQLITE3_MUTEX_INITIALIZER(id) { PTHREAD_MUTEX_INITIALIZER } #endif /* @@ -151,18 +152,18 @@ static int pthreadMutexEnd(void){ return SQLITE_OK; } */ static sqlite3_mutex *pthreadMutexAlloc(int iType){ static sqlite3_mutex staticMutexes[] = { - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER + SQLITE3_MUTEX_INITIALIZER(2), + SQLITE3_MUTEX_INITIALIZER(3), + SQLITE3_MUTEX_INITIALIZER(4), + SQLITE3_MUTEX_INITIALIZER(5), + SQLITE3_MUTEX_INITIALIZER(6), + SQLITE3_MUTEX_INITIALIZER(7), + SQLITE3_MUTEX_INITIALIZER(8), + SQLITE3_MUTEX_INITIALIZER(9), + SQLITE3_MUTEX_INITIALIZER(10), + SQLITE3_MUTEX_INITIALIZER(11), + SQLITE3_MUTEX_INITIALIZER(12), + SQLITE3_MUTEX_INITIALIZER(13) }; sqlite3_mutex *p; switch( iType ){ @@ -180,6 +181,9 @@ static sqlite3_mutex *pthreadMutexAlloc(int iType){ pthread_mutexattr_settype(&recursiveAttr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&p->mutex, &recursiveAttr); pthread_mutexattr_destroy(&recursiveAttr); +#endif +#if SQLITE_MUTEX_NREF || defined(SQLITE_ENABLE_API_ARMOR) + p->id = SQLITE_MUTEX_RECURSIVE; #endif } break; @@ -188,6 +192,9 @@ static sqlite3_mutex *pthreadMutexAlloc(int iType){ p = sqlite3MallocZero( sizeof(*p) ); if( p ){ pthread_mutex_init(&p->mutex, 0); +#if SQLITE_MUTEX_NREF || defined(SQLITE_ENABLE_API_ARMOR) + p->id = SQLITE_MUTEX_FAST; +#endif } break; } @@ -203,7 +210,7 @@ static sqlite3_mutex *pthreadMutexAlloc(int iType){ } } #if SQLITE_MUTEX_NREF || defined(SQLITE_ENABLE_API_ARMOR) - if( p ) p->id = iType; + assert( p==0 || p->id==iType ); #endif return p; } diff --git a/src/mutex_w32.c b/src/mutex_w32.c index 9da93cf319..8a8ae289ba 100644 --- a/src/mutex_w32.c +++ b/src/mutex_w32.c @@ -40,7 +40,7 @@ struct sqlite3_mutex { #ifdef SQLITE_DEBUG volatile int nRef; /* Number of enterances */ volatile DWORD owner; /* Thread holding this mutex */ - volatile int trace; /* True to trace changes */ + volatile LONG trace; /* True to trace changes */ #endif }; @@ -52,10 +52,10 @@ struct sqlite3_mutex { #define SQLITE_W32_MUTEX_INITIALIZER { 0 } #ifdef SQLITE_DEBUG -#define SQLITE3_MUTEX_INITIALIZER { SQLITE_W32_MUTEX_INITIALIZER, 0, \ +#define SQLITE3_MUTEX_INITIALIZER(id) { SQLITE_W32_MUTEX_INITIALIZER, id, \ 0L, (DWORD)0, 0 } #else -#define SQLITE3_MUTEX_INITIALIZER { SQLITE_W32_MUTEX_INITIALIZER, 0 } +#define SQLITE3_MUTEX_INITIALIZER(id) { SQLITE_W32_MUTEX_INITIALIZER, id } #endif #ifdef SQLITE_DEBUG @@ -98,18 +98,18 @@ void sqlite3MemoryBarrier(void){ ** Initialize and deinitialize the mutex subsystem. */ static sqlite3_mutex winMutex_staticMutexes[] = { - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER, - SQLITE3_MUTEX_INITIALIZER + SQLITE3_MUTEX_INITIALIZER(2), + SQLITE3_MUTEX_INITIALIZER(3), + SQLITE3_MUTEX_INITIALIZER(4), + SQLITE3_MUTEX_INITIALIZER(5), + SQLITE3_MUTEX_INITIALIZER(6), + SQLITE3_MUTEX_INITIALIZER(7), + SQLITE3_MUTEX_INITIALIZER(8), + SQLITE3_MUTEX_INITIALIZER(9), + SQLITE3_MUTEX_INITIALIZER(10), + SQLITE3_MUTEX_INITIALIZER(11), + SQLITE3_MUTEX_INITIALIZER(12), + SQLITE3_MUTEX_INITIALIZER(13) }; static int winMutex_isInit = 0; @@ -239,15 +239,15 @@ static sqlite3_mutex *winMutexAlloc(int iType){ } #endif p = &winMutex_staticMutexes[iType-2]; - p->id = iType; #ifdef SQLITE_DEBUG #ifdef SQLITE_WIN32_MUTEX_TRACE_STATIC - p->trace = 1; + InterlockedCompareExchange(&p->trace, 1, 0); #endif #endif break; } } + assert( p==0 || p->id==iType ); return p; } diff --git a/src/os_unix.c b/src/os_unix.c index 11442c1b11..71c57ff0f6 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -468,7 +468,11 @@ static struct unix_syscall { #endif #define osFchown ((int(*)(int,uid_t,gid_t))aSyscall[20].pCurrent) +#if defined(HAVE_FCHOWN) { "geteuid", (sqlite3_syscall_ptr)geteuid, 0 }, +#else + { "geteuid", (sqlite3_syscall_ptr)0, 0 }, +#endif #define osGeteuid ((uid_t(*)(void))aSyscall[21].pCurrent) #if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 @@ -483,7 +487,7 @@ static struct unix_syscall { #else { "munmap", (sqlite3_syscall_ptr)0, 0 }, #endif -#define osMunmap ((void*(*)(void*,size_t))aSyscall[23].pCurrent) +#define osMunmap ((int(*)(void*,size_t))aSyscall[23].pCurrent) #if HAVE_MREMAP && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) { "mremap", (sqlite3_syscall_ptr)mremap, 0 }, @@ -696,15 +700,16 @@ static int robust_open(const char *z, int f, mode_t m){ ** assert( unixMutexHeld() ); ** unixEnterLeave() */ +static sqlite3_mutex *unixBigLock = 0; static void unixEnterMutex(void){ - sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1)); + sqlite3_mutex_enter(unixBigLock); } static void unixLeaveMutex(void){ - sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1)); + sqlite3_mutex_leave(unixBigLock); } #ifdef SQLITE_DEBUG static int unixMutexHeld(void) { - return sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1)); + return sqlite3_mutex_held(unixBigLock); } #endif @@ -3972,7 +3977,7 @@ static void setDeviceCharacteristics(unixFile *pFile){ pFile->sectorSize = SQLITE_DEFAULT_SECTOR_SIZE; pFile->deviceCharacteristics = 0; if( fstatvfs(pFile->h, &fsInfo) == -1 ) { - return pFile->sectorSize; + return; } if( !strcmp(fsInfo.f_basetype, "tmp") ) { @@ -4130,6 +4135,7 @@ struct unixShmNode { int szRegion; /* Size of shared-memory regions */ u16 nRegion; /* Size of array apRegion */ u8 isReadonly; /* True if read-only */ + u8 isUnlocked; /* True if no DMS lock held */ char **apRegion; /* Array of mapped shared-memory regions */ int nRef; /* Number of unixShm objects pointing to this */ unixShm *pFirst; /* All unixShm objects pointing to this */ @@ -4186,7 +4192,7 @@ static int unixShmSystemLock( /* Access to the unixShmNode object is serialized by the caller */ pShmNode = pFile->pInode->pShmNode; - assert( sqlite3_mutex_held(pShmNode->mutex) || pShmNode->nRef==0 ); + assert( pShmNode->nRef==0 || sqlite3_mutex_held(pShmNode->mutex) ); /* Shared locks never span more than one byte */ assert( n==1 || lockType!=F_RDLCK ); @@ -4196,12 +4202,10 @@ static int unixShmSystemLock( if( pShmNode->h>=0 ){ /* Initialize the locking parameters */ - memset(&f, 0, sizeof(f)); f.l_type = lockType; f.l_whence = SEEK_SET; f.l_start = ofst; f.l_len = n; - rc = osFcntl(pShmNode->h, F_SETLK, &f); rc = (rc!=(-1)) ? SQLITE_OK : SQLITE_BUSY; } @@ -4292,6 +4296,64 @@ static void unixShmPurge(unixFile *pFd){ } } +/* +** The DMS lock has not yet been taken on shm file pShmNode. Attempt to +** take it now. Return SQLITE_OK if successful, or an SQLite error +** code otherwise. +** +** If the DMS cannot be locked because this is a readonly_shm=1 +** connection and no other process already holds a lock, return +** SQLITE_READONLY_CANTINIT and set pShmNode->isUnlocked=1. +*/ +static int unixLockSharedMemory(unixFile *pDbFd, unixShmNode *pShmNode){ + struct flock lock; + int rc = SQLITE_OK; + + /* Use F_GETLK to determine the locks other processes are holding + ** on the DMS byte. If it indicates that another process is holding + ** a SHARED lock, then this process may also take a SHARED lock + ** and proceed with opening the *-shm file. + ** + ** Or, if no other process is holding any lock, then this process + ** is the first to open it. In this case take an EXCLUSIVE lock on the + ** DMS byte and truncate the *-shm file to zero bytes in size. Then + ** downgrade to a SHARED lock on the DMS byte. + ** + ** If another process is holding an EXCLUSIVE lock on the DMS byte, + ** return SQLITE_BUSY to the caller (it will try again). An earlier + ** version of this code attempted the SHARED lock at this point. But + ** this introduced a subtle race condition: if the process holding + ** EXCLUSIVE failed just before truncating the *-shm file, then this + ** process might open and use the *-shm file without truncating it. + ** And if the *-shm file has been corrupted by a power failure or + ** system crash, the database itself may also become corrupt. */ + lock.l_whence = SEEK_SET; + lock.l_start = UNIX_SHM_DMS; + lock.l_len = 1; + lock.l_type = F_WRLCK; + if( osFcntl(pShmNode->h, F_GETLK, &lock)!=0 ) { + rc = SQLITE_IOERR_LOCK; + }else if( lock.l_type==F_UNLCK ){ + if( pShmNode->isReadonly ){ + pShmNode->isUnlocked = 1; + rc = SQLITE_READONLY_CANTINIT; + }else{ + rc = unixShmSystemLock(pDbFd, F_WRLCK, UNIX_SHM_DMS, 1); + if( rc==SQLITE_OK && robust_ftruncate(pShmNode->h, 0) ){ + rc = unixLogError(SQLITE_IOERR_SHMOPEN,"ftruncate",pShmNode->zFilename); + } + } + }else if( lock.l_type==F_WRLCK ){ + rc = SQLITE_BUSY; + } + + if( rc==SQLITE_OK ){ + assert( lock.l_type==F_UNLCK || lock.l_type==F_RDLCK ); + rc = unixShmSystemLock(pDbFd, F_RDLCK, UNIX_SHM_DMS, 1); + } + return rc; +} + /* ** Open a shared-memory area associated with open database file pDbFd. ** This particular implementation uses mmapped files. @@ -4330,9 +4392,9 @@ static void unixShmPurge(unixFile *pFd){ static int unixOpenSharedMemory(unixFile *pDbFd){ struct unixShm *p = 0; /* The connection to be opened */ struct unixShmNode *pShmNode; /* The underlying mmapped file */ - int rc; /* Result code */ + int rc = SQLITE_OK; /* Result code */ unixInodeInfo *pInode; /* The inode of fd */ - char *zShmFilename; /* Name of the file used for SHM */ + char *zShm; /* Name of the file used for SHM */ int nShmFilename; /* Size of the SHM filename in bytes */ /* Allocate space for the new unixShm object. */ @@ -4373,14 +4435,14 @@ static int unixOpenSharedMemory(unixFile *pDbFd){ goto shm_open_err; } memset(pShmNode, 0, sizeof(*pShmNode)+nShmFilename); - zShmFilename = pShmNode->zFilename = (char*)&pShmNode[1]; + zShm = pShmNode->zFilename = (char*)&pShmNode[1]; #ifdef SQLITE_SHM_DIRECTORY - sqlite3_snprintf(nShmFilename, zShmFilename, + sqlite3_snprintf(nShmFilename, zShm, SQLITE_SHM_DIRECTORY "/sqlite-shm-%x-%x", (u32)sStat.st_ino, (u32)sStat.st_dev); #else - sqlite3_snprintf(nShmFilename, zShmFilename, "%s-shm", zBasePath); - sqlite3FileSuffix3(pDbFd->zPath, zShmFilename); + sqlite3_snprintf(nShmFilename, zShm, "%s-shm", zBasePath); + sqlite3FileSuffix3(pDbFd->zPath, zShm); #endif pShmNode->h = -1; pDbFd->pInode->pShmNode = pShmNode; @@ -4394,15 +4456,16 @@ static int unixOpenSharedMemory(unixFile *pDbFd){ } if( pInode->bProcessLock==0 ){ - int openFlags = O_RDWR | O_CREAT; - if( sqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0) ){ - openFlags = O_RDONLY; - pShmNode->isReadonly = 1; + if( 0==sqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0) ){ + pShmNode->h = robust_open(zShm, O_RDWR|O_CREAT, (sStat.st_mode&0777)); } - pShmNode->h = robust_open(zShmFilename, openFlags, (sStat.st_mode&0777)); if( pShmNode->h<0 ){ - rc = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zShmFilename); - goto shm_open_err; + pShmNode->h = robust_open(zShm, O_RDONLY, (sStat.st_mode&0777)); + if( pShmNode->h<0 ){ + rc = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zShm); + goto shm_open_err; + } + pShmNode->isReadonly = 1; } /* If this process is running as root, make sure that the SHM file @@ -4410,20 +4473,9 @@ static int unixOpenSharedMemory(unixFile *pDbFd){ ** the original owner will not be able to connect. */ robustFchown(pShmNode->h, sStat.st_uid, sStat.st_gid); - - /* Check to see if another process is holding the dead-man switch. - ** If not, truncate the file to zero length. - */ - rc = SQLITE_OK; - if( unixShmSystemLock(pDbFd, F_WRLCK, UNIX_SHM_DMS, 1)==SQLITE_OK ){ - if( robust_ftruncate(pShmNode->h, 0) ){ - rc = unixLogError(SQLITE_IOERR_SHMOPEN, "ftruncate", zShmFilename); - } - } - if( rc==SQLITE_OK ){ - rc = unixShmSystemLock(pDbFd, F_RDLCK, UNIX_SHM_DMS, 1); - } - if( rc ) goto shm_open_err; + + rc = unixLockSharedMemory(pDbFd, pShmNode); + if( rc!=SQLITE_OK && rc!=SQLITE_READONLY_CANTINIT ) goto shm_open_err; } } @@ -4447,7 +4499,7 @@ static int unixOpenSharedMemory(unixFile *pDbFd){ p->pNext = pShmNode->pFirst; pShmNode->pFirst = p; sqlite3_mutex_leave(pShmNode->mutex); - return SQLITE_OK; + return rc; /* Jump here on any error */ shm_open_err: @@ -4499,6 +4551,11 @@ static int unixShmMap( p = pDbFd->pShm; pShmNode = p->pShmNode; sqlite3_mutex_enter(pShmNode->mutex); + if( pShmNode->isUnlocked ){ + rc = unixLockSharedMemory(pDbFd, pShmNode); + if( rc!=SQLITE_OK ) goto shmpage_out; + pShmNode->isUnlocked = 0; + } assert( szRegion==pShmNode->szRegion || pShmNode->nRegion==0 ); assert( pShmNode->pInode==pDbFd->pInode ); assert( pShmNode->h>=0 || pDbFd->pInode->bProcessLock==1 ); @@ -5795,7 +5852,7 @@ static int unixOpen( ** a file-descriptor on the directory too. The first time unixSync() ** is called the directory file descriptor will be fsync()ed and close()d. */ - int syncDir = (isCreate && ( + int isNewJrnl = (isCreate && ( eType==SQLITE_OPEN_MASTER_JOURNAL || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_WAL @@ -5842,7 +5899,6 @@ static int unixOpen( randomnessPid = osGetpid(0); sqlite3_randomness(0,0); } - memset(p, 0, sizeof(unixFile)); if( eType==SQLITE_OPEN_MAIN_DB ){ @@ -5865,7 +5921,7 @@ static int unixOpen( }else if( !zName ){ /* If zName is NULL, the upper layer is requesting a temp file. */ - assert(isDelete && !syncDir); + assert(isDelete && !isNewJrnl); rc = unixGetTempname(pVfs->mxPathname, zTmpname); if( rc!=SQLITE_OK ){ return rc; @@ -5900,17 +5956,24 @@ static int unixOpen( fd = robust_open(zName, openFlags, openMode); OSTRACE(("OPENX %-3d %s 0%o\n", fd, zName, openFlags)); assert( !isExclusive || (openFlags & O_CREAT)!=0 ); - if( fd<0 && errno!=EISDIR && isReadWrite ){ - /* Failed to open the file for read/write access. Try read-only. */ - flags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); - openFlags &= ~(O_RDWR|O_CREAT); - flags |= SQLITE_OPEN_READONLY; - openFlags |= O_RDONLY; - isReadonly = 1; - fd = robust_open(zName, openFlags, openMode); + if( fd<0 ){ + if( isNewJrnl && errno==EACCES && osAccess(zName, F_OK) ){ + /* If unable to create a journal because the directory is not + ** writable, change the error code to indicate that. */ + rc = SQLITE_READONLY_DIRECTORY; + }else if( errno!=EISDIR && isReadWrite ){ + /* Failed to open the file for read/write access. Try read-only. */ + flags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); + openFlags &= ~(O_RDWR|O_CREAT); + flags |= SQLITE_OPEN_READONLY; + openFlags |= O_RDONLY; + isReadonly = 1; + fd = robust_open(zName, openFlags, openMode); + } } if( fd<0 ){ - rc = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zName); + int rc2 = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zName); + if( rc==SQLITE_OK ) rc = rc2; goto open_finished; } @@ -5970,7 +6033,7 @@ static int unixOpen( if( isReadonly ) ctrlFlags |= UNIXFILE_RDONLY; noLock = eType!=SQLITE_OPEN_MAIN_DB; if( noLock ) ctrlFlags |= UNIXFILE_NOLOCK; - if( syncDir ) ctrlFlags |= UNIXFILE_DIRSYNC; + if( isNewJrnl ) ctrlFlags |= UNIXFILE_DIRSYNC; if( flags & SQLITE_OPEN_URI ) ctrlFlags |= UNIXFILE_URI; #if SQLITE_ENABLE_LOCKING_STYLE @@ -7710,6 +7773,7 @@ int sqlite3_os_init(void){ for(i=0; i<(sizeof(aVfs)/sizeof(sqlite3_vfs)); i++){ sqlite3_vfs_register(&aVfs[i], i==0); } + unixBigLock = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); return SQLITE_OK; } @@ -7721,6 +7785,7 @@ int sqlite3_os_init(void){ ** This routine is a no-op for unix. */ int sqlite3_os_end(void){ + unixBigLock = 0; return SQLITE_OK; } diff --git a/src/os_win.c b/src/os_win.c index 245a86045b..534426977f 100644 --- a/src/os_win.c +++ b/src/os_win.c @@ -3631,15 +3631,16 @@ static SYSTEM_INFO winSysInfo; ** assert( winShmMutexHeld() ); ** winShmLeaveMutex() */ +static sqlite3_mutex *winBigLock = 0; static void winShmEnterMutex(void){ - sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1)); + sqlite3_mutex_enter(winBigLock); } static void winShmLeaveMutex(void){ - sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1)); + sqlite3_mutex_leave(winBigLock); } #ifndef NDEBUG static int winShmMutexHeld(void) { - return sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1)); + return sqlite3_mutex_held(winBigLock); } #endif @@ -3673,6 +3674,9 @@ struct winShmNode { int szRegion; /* Size of shared-memory regions */ int nRegion; /* Size of array apRegion */ + u8 isReadonly; /* True if read-only */ + u8 isUnlocked; /* True if no DMS lock held */ + struct ShmRegion { HANDLE hMap; /* File handle from CreateFileMapping */ void *pMap; @@ -3739,7 +3743,7 @@ static int winShmSystemLock( int rc = 0; /* Result code form Lock/UnlockFileEx() */ /* Access to the winShmNode object is serialized by the caller */ - assert( sqlite3_mutex_held(pFile->mutex) || pFile->nRef==0 ); + assert( pFile->nRef==0 || sqlite3_mutex_held(pFile->mutex) ); OSTRACE(("SHM-LOCK file=%p, lock=%d, offset=%d, size=%d\n", pFile->hFile.h, lockType, ofst, nByte)); @@ -3820,6 +3824,37 @@ static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){ } } +/* +** The DMS lock has not yet been taken on shm file pShmNode. Attempt to +** take it now. Return SQLITE_OK if successful, or an SQLite error +** code otherwise. +** +** If the DMS cannot be locked because this is a readonly_shm=1 +** connection and no other process already holds a lock, return +** SQLITE_READONLY_CANTINIT and set pShmNode->isUnlocked=1. +*/ +static int winLockSharedMemory(winShmNode *pShmNode){ + int rc = winShmSystemLock(pShmNode, WINSHM_WRLCK, WIN_SHM_DMS, 1); + + if( rc==SQLITE_OK ){ + if( pShmNode->isReadonly ){ + pShmNode->isUnlocked = 1; + winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1); + return SQLITE_READONLY_CANTINIT; + }else if( winTruncate((sqlite3_file*)&pShmNode->hFile, 0) ){ + winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1); + return winLogError(SQLITE_IOERR_SHMOPEN, osGetLastError(), + "winLockSharedMemory", pShmNode->zFilename); + } + } + + if( rc==SQLITE_OK ){ + winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1); + } + + return winShmSystemLock(pShmNode, WINSHM_RDLCK, WIN_SHM_DMS, 1); +} + /* ** Open the shared-memory area associated with database file pDbFd. ** @@ -3829,9 +3864,9 @@ static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){ */ static int winOpenSharedMemory(winFile *pDbFd){ struct winShm *p; /* The connection to be opened */ - struct winShmNode *pShmNode = 0; /* The underlying mmapped file */ - int rc; /* Result code */ - struct winShmNode *pNew; /* Newly allocated winShmNode */ + winShmNode *pShmNode = 0; /* The underlying mmapped file */ + int rc = SQLITE_OK; /* Result code */ + winShmNode *pNew; /* Newly allocated winShmNode */ int nName; /* Size of zName in bytes */ assert( pDbFd->pShm==0 ); /* Not previously opened */ @@ -3864,6 +3899,9 @@ static int winOpenSharedMemory(winFile *pDbFd){ if( pShmNode ){ sqlite3_free(pNew); }else{ + int inFlags = SQLITE_OPEN_WAL; + int outFlags = 0; + pShmNode = pNew; pNew = 0; ((winFile*)(&pShmNode->hFile))->h = INVALID_HANDLE_VALUE; @@ -3878,30 +3916,23 @@ static int winOpenSharedMemory(winFile *pDbFd){ } } - rc = winOpen(pDbFd->pVfs, - pShmNode->zFilename, /* Name of the file (UTF-8) */ - (sqlite3_file*)&pShmNode->hFile, /* File handle here */ - SQLITE_OPEN_WAL | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, - 0); - if( SQLITE_OK!=rc ){ + if( 0==sqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0) ){ + inFlags |= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; + }else{ + inFlags |= SQLITE_OPEN_READONLY; + } + rc = winOpen(pDbFd->pVfs, pShmNode->zFilename, + (sqlite3_file*)&pShmNode->hFile, + inFlags, &outFlags); + if( rc!=SQLITE_OK ){ + rc = winLogError(rc, osGetLastError(), "winOpenShm", + pShmNode->zFilename); goto shm_open_err; } + if( outFlags==SQLITE_OPEN_READONLY ) pShmNode->isReadonly = 1; - /* Check to see if another process is holding the dead-man switch. - ** If not, truncate the file to zero length. - */ - if( winShmSystemLock(pShmNode, WINSHM_WRLCK, WIN_SHM_DMS, 1)==SQLITE_OK ){ - rc = winTruncate((sqlite3_file *)&pShmNode->hFile, 0); - if( rc!=SQLITE_OK ){ - rc = winLogError(SQLITE_IOERR_SHMOPEN, osGetLastError(), - "winOpenShm", pDbFd->zPath); - } - } - if( rc==SQLITE_OK ){ - winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1); - rc = winShmSystemLock(pShmNode, WINSHM_RDLCK, WIN_SHM_DMS, 1); - } - if( rc ) goto shm_open_err; + rc = winLockSharedMemory(pShmNode); + if( rc!=SQLITE_OK && rc!=SQLITE_READONLY_CANTINIT ) goto shm_open_err; } /* Make the new connection a child of the winShmNode */ @@ -3924,7 +3955,7 @@ static int winOpenSharedMemory(winFile *pDbFd){ p->pNext = pShmNode->pFirst; pShmNode->pFirst = p; sqlite3_mutex_leave(pShmNode->mutex); - return SQLITE_OK; + return rc; /* Jump here on any error */ shm_open_err: @@ -4128,6 +4159,8 @@ static int winShmMap( winFile *pDbFd = (winFile*)fd; winShm *pShm = pDbFd->pShm; winShmNode *pShmNode; + DWORD protect = PAGE_READWRITE; + DWORD flags = FILE_MAP_WRITE | FILE_MAP_READ; int rc = SQLITE_OK; if( !pShm ){ @@ -4138,6 +4171,11 @@ static int winShmMap( pShmNode = pShm->pShmNode; sqlite3_mutex_enter(pShmNode->mutex); + if( pShmNode->isUnlocked ){ + rc = winLockSharedMemory(pShmNode); + if( rc!=SQLITE_OK ) goto shmpage_out; + pShmNode->isUnlocked = 0; + } assert( szRegion==pShmNode->szRegion || pShmNode->nRegion==0 ); if( pShmNode->nRegion<=iRegion ){ @@ -4184,21 +4222,26 @@ static int winShmMap( } pShmNode->aRegion = apNew; + if( pShmNode->isReadonly ){ + protect = PAGE_READONLY; + flags = FILE_MAP_READ; + } + while( pShmNode->nRegion<=iRegion ){ HANDLE hMap = NULL; /* file-mapping handle */ void *pMap = 0; /* Mapped memory region */ #if SQLITE_OS_WINRT hMap = osCreateFileMappingFromApp(pShmNode->hFile.h, - NULL, PAGE_READWRITE, nByte, NULL + NULL, protect, nByte, NULL ); #elif defined(SQLITE_WIN32_HAS_WIDE) hMap = osCreateFileMappingW(pShmNode->hFile.h, - NULL, PAGE_READWRITE, 0, nByte, NULL + NULL, protect, 0, nByte, NULL ); #elif defined(SQLITE_WIN32_HAS_ANSI) && SQLITE_WIN32_CREATEFILEMAPPINGA hMap = osCreateFileMappingA(pShmNode->hFile.h, - NULL, PAGE_READWRITE, 0, nByte, NULL + NULL, protect, 0, nByte, NULL ); #endif OSTRACE(("SHM-MAP-CREATE pid=%lu, region=%d, size=%d, rc=%s\n", @@ -4208,11 +4251,11 @@ static int winShmMap( int iOffset = pShmNode->nRegion*szRegion; int iOffsetShift = iOffset % winSysInfo.dwAllocationGranularity; #if SQLITE_OS_WINRT - pMap = osMapViewOfFileFromApp(hMap, FILE_MAP_WRITE | FILE_MAP_READ, + pMap = osMapViewOfFileFromApp(hMap, flags, iOffset - iOffsetShift, szRegion + iOffsetShift ); #else - pMap = osMapViewOfFile(hMap, FILE_MAP_WRITE | FILE_MAP_READ, + pMap = osMapViewOfFile(hMap, flags, 0, iOffset - iOffsetShift, szRegion + iOffsetShift ); #endif @@ -4243,6 +4286,7 @@ shmpage_out: }else{ *pp = 0; } + if( pShmNode->isReadonly && rc==SQLITE_OK ) rc = SQLITE_READONLY; sqlite3_mutex_leave(pShmNode->mutex); return rc; } @@ -5071,8 +5115,10 @@ static int winOpen( &extendedParameters); if( h!=INVALID_HANDLE_VALUE ) break; if( isReadWrite ){ - int isRO = 0; - int rc2 = winAccess(pVfs, zName, SQLITE_ACCESS_READ, &isRO); + int rc2, isRO = 0; + sqlite3BeginBenignMalloc(); + rc2 = winAccess(pVfs, zName, SQLITE_ACCESS_READ, &isRO); + sqlite3EndBenignMalloc(); if( rc2==SQLITE_OK && isRO ) break; } }while( winRetryIoerr(&cnt, &lastErrno) ); @@ -5086,8 +5132,10 @@ static int winOpen( NULL); if( h!=INVALID_HANDLE_VALUE ) break; if( isReadWrite ){ - int isRO = 0; - int rc2 = winAccess(pVfs, zName, SQLITE_ACCESS_READ, &isRO); + int rc2, isRO = 0; + sqlite3BeginBenignMalloc(); + rc2 = winAccess(pVfs, zName, SQLITE_ACCESS_READ, &isRO); + sqlite3EndBenignMalloc(); if( rc2==SQLITE_OK && isRO ) break; } }while( winRetryIoerr(&cnt, &lastErrno) ); @@ -5104,8 +5152,10 @@ static int winOpen( NULL); if( h!=INVALID_HANDLE_VALUE ) break; if( isReadWrite ){ - int isRO = 0; - int rc2 = winAccess(pVfs, zName, SQLITE_ACCESS_READ, &isRO); + int rc2, isRO = 0; + sqlite3BeginBenignMalloc(); + rc2 = winAccess(pVfs, zName, SQLITE_ACCESS_READ, &isRO); + sqlite3EndBenignMalloc(); if( rc2==SQLITE_OK && isRO ) break; } }while( winRetryIoerr(&cnt, &lastErrno) ); @@ -6013,6 +6063,10 @@ int sqlite3_os_init(void){ sqlite3_vfs_register(&winLongPathNolockVfs, 0); #endif +#ifndef SQLITE_OMIT_WAL + winBigLock = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); +#endif + return SQLITE_OK; } @@ -6023,6 +6077,11 @@ int sqlite3_os_end(void){ sleepObj = NULL; } #endif + +#ifndef SQLITE_OMIT_WAL + winBigLock = 0; +#endif + return SQLITE_OK; } diff --git a/src/pager.c b/src/pager.c index 2af9139d4e..9e1a32f1b8 100644 --- a/src/pager.c +++ b/src/pager.c @@ -1218,7 +1218,7 @@ static int jrnlBufferSize(Pager *pPager){ #endif #ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE - if( dc&SQLITE_IOCAP_BATCH_ATOMIC ){ + if( pPager->dbSize>0 && (dc&SQLITE_IOCAP_BATCH_ATOMIC) ){ return -1; } #endif @@ -4164,6 +4164,30 @@ static void pagerFreeMapHdrs(Pager *pPager){ } } +/* Verify that the database file has not be deleted or renamed out from +** under the pager. Return SQLITE_OK if the database is still where it ought +** to be on disk. Return non-zero (SQLITE_READONLY_DBMOVED or some other error +** code from sqlite3OsAccess()) if the database has gone missing. +*/ +static int databaseIsUnmoved(Pager *pPager){ + int bHasMoved = 0; + int rc; + + if( pPager->tempFile ) return SQLITE_OK; + if( pPager->dbSize==0 ) return SQLITE_OK; + assert( pPager->zFilename && pPager->zFilename[0] ); + rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_HAS_MOVED, &bHasMoved); + if( rc==SQLITE_NOTFOUND ){ + /* If the HAS_MOVED file-control is unimplemented, assume that the file + ** has not been moved. That is the historical behavior of SQLite: prior to + ** version 3.8.3, it never checked */ + rc = SQLITE_OK; + }else if( rc==SQLITE_OK && bHasMoved ){ + rc = SQLITE_READONLY_DBMOVED; + } + return rc; +} + /* ** Shutdown the page cache. Free all memory and close all files. @@ -4180,8 +4204,7 @@ static void pagerFreeMapHdrs(Pager *pPager){ ** to the caller. */ int sqlite3PagerClose(Pager *pPager, sqlite3 *db){ - u8 *pTmp = (u8 *)pPager->pTmpSpace; - + u8 *pTmp = (u8*)pPager->pTmpSpace; assert( db || pagerUseWal(pPager)==0 ); assert( assert_pager_state(pPager) ); disable_simulated_io_errors(); @@ -4190,11 +4213,17 @@ int sqlite3PagerClose(Pager *pPager, sqlite3 *db){ /* pPager->errCode = 0; */ pPager->exclusiveMode = 0; #ifndef SQLITE_OMIT_WAL - assert( db || pPager->pWal==0 ); - sqlite3WalClose(pPager->pWal, db, pPager->walSyncFlags, pPager->pageSize, - (db && (db->flags & SQLITE_NoCkptOnClose) ? 0 : pTmp) - ); - pPager->pWal = 0; + { + u8 *a = 0; + assert( db || pPager->pWal==0 ); + if( db && 0==(db->flags & SQLITE_NoCkptOnClose) + && SQLITE_OK==databaseIsUnmoved(pPager) + ){ + a = pTmp; + } + sqlite3WalClose(pPager->pWal, db, pPager->walSyncFlags, pPager->pageSize,a); + pPager->pWal = 0; + } #endif pager_reset(pPager); if( MEMDB ){ @@ -5035,30 +5064,6 @@ act_like_temp_file: } -/* Verify that the database file has not be deleted or renamed out from -** under the pager. Return SQLITE_OK if the database is still were it ought -** to be on disk. Return non-zero (SQLITE_READONLY_DBMOVED or some other error -** code from sqlite3OsAccess()) if the database has gone missing. -*/ -static int databaseIsUnmoved(Pager *pPager){ - int bHasMoved = 0; - int rc; - - if( pPager->tempFile ) return SQLITE_OK; - if( pPager->dbSize==0 ) return SQLITE_OK; - assert( pPager->zFilename && pPager->zFilename[0] ); - rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_HAS_MOVED, &bHasMoved); - if( rc==SQLITE_NOTFOUND ){ - /* If the HAS_MOVED file-control is unimplemented, assume that the file - ** has not been moved. That is the historical behavior of SQLite: prior to - ** version 3.8.3, it never checked */ - rc = SQLITE_OK; - }else if( rc==SQLITE_OK && bHasMoved ){ - rc = SQLITE_READONLY_DBMOVED; - } - return rc; -} - /* ** This function is called after transitioning from PAGER_UNLOCK to @@ -5659,7 +5664,7 @@ static int getPageMMap( } if( pPg==0 ){ rc = pagerAcquireMapPage(pPager, pgno, pData, &pPg); - }else{ + }else{ sqlite3OsUnfetch(pPager->fd, (i64)(pgno-1)*pPager->pageSize, pData); } if( pPg ){ @@ -6654,8 +6659,9 @@ int sqlite3PagerCommitPhaseOne( if( bBatch ){ if( rc==SQLITE_OK ){ rc = sqlite3OsFileControl(fd, SQLITE_FCNTL_COMMIT_ATOMIC_WRITE, 0); - }else{ - sqlite3OsFileControl(fd, SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE, 0); + } + if( rc!=SQLITE_OK ){ + sqlite3OsFileControlHint(fd, SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE, 0); } } diff --git a/src/parse.y b/src/parse.y index b4f26a5f55..1665346048 100644 --- a/src/parse.y +++ b/src/parse.y @@ -31,8 +31,11 @@ // %syntax_error { UNUSED_PARAMETER(yymajor); /* Silence some compiler warnings */ - assert( TOKEN.z[0] ); /* The tokenizer always gives us a token */ - parserSyntaxError(pParse, &TOKEN); + if( TOKEN.z[0] ){ + sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &TOKEN); + }else{ + sqlite3ErrorMsg(pParse, "incomplete input"); + } } %stack_overflow { sqlite3ErrorMsg(pParse, "parser stack overflow"); @@ -84,15 +87,6 @@ */ #define YYMALLOCARGTYPE u64 -/* -** An instance of this structure holds information about the -** LIMIT clause of a SELECT statement. -*/ -struct LimitVal { - Expr *pLimit; /* The LIMIT expression. NULL if there is no limit */ - Expr *pOffset; /* The OFFSET expression. NULL if there is none */ -}; - /* ** An instance of the following structure describes the event of a ** TRIGGER. "a" is the event type, one of TK_UPDATE, TK_INSERT, @@ -297,26 +291,48 @@ typename(A) ::= typename(A) ids(Y). {A.n=Y.n+(int)(Y.z-A.z);} signed ::= plus_num. signed ::= minus_num. +// The scanpt non-terminal takes a value which is a pointer to the +// input text just past the last token that has been shifted into +// the parser. By surrounding some phrase in the grammar with two +// scanpt non-terminals, we can capture the input text for that phrase. +// For example: +// +// something ::= .... scanpt(A) phrase scanpt(Z). +// +// The text that is parsed as "phrase" is a string starting at A +// and containing (int)(Z-A) characters. There might be some extra +// whitespace on either end of the text, but that can be removed in +// post-processing, if needed. +// +%type scanpt {const char*} +scanpt(A) ::= . { + assert( yyLookahead!=YYNOCODE ); + A = yyLookaheadToken.z; +} + // "carglist" is a list of additional constraints that come after the // column name and column type in a CREATE TABLE statement. // carglist ::= carglist ccons. carglist ::= . ccons ::= CONSTRAINT nm(X). {pParse->constraintName = X;} -ccons ::= DEFAULT term(X). {sqlite3AddDefaultValue(pParse,&X);} -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); - v.zStart = A.z; - v.zEnd = X.zEnd; - sqlite3AddDefaultValue(pParse,&v); +ccons ::= DEFAULT scanpt(A) term(X) scanpt(Z). + {sqlite3AddDefaultValue(pParse,X,A,Z);} +ccons ::= DEFAULT LP(A) expr(X) RP(Z). + {sqlite3AddDefaultValue(pParse,X,A.z+1,Z.z);} +ccons ::= DEFAULT PLUS(A) term(X) scanpt(Z). + {sqlite3AddDefaultValue(pParse,X,A.z,Z);} +ccons ::= DEFAULT MINUS(A) term(X) scanpt(Z). { + Expr *p = sqlite3PExpr(pParse, TK_UMINUS, X, 0); + sqlite3AddDefaultValue(pParse,p,A.z,Z); } -ccons ::= DEFAULT id(X). { - ExprSpan v; - spanExpr(&v, pParse, TK_STRING, X); - sqlite3AddDefaultValue(pParse,&v); +ccons ::= DEFAULT scanpt id(X). { + Expr *p = tokenExpr(pParse, TK_STRING, X); + if( p ){ + sqlite3ExprIdToTrueFalse(p); + testcase( p->op==TK_TRUEFALSE && sqlite3ExprTruthValue(p) ); + } + sqlite3AddDefaultValue(pParse,p,X.z,X.z+X.n); } // In addition to the type name, we also care about the primary key and @@ -328,7 +344,7 @@ ccons ::= PRIMARY KEY sortorder(Z) onconf(R) autoinc(I). {sqlite3AddPrimaryKey(pParse,0,R,I,Z);} ccons ::= UNIQUE onconf(R). {sqlite3CreateIndex(pParse,0,0,0,0,R,0,0,0,0, SQLITE_IDXTYPE_UNIQUE);} -ccons ::= CHECK LP expr(X) RP. {sqlite3AddCheckConstraint(pParse,X.pExpr);} +ccons ::= CHECK LP expr(X) RP. {sqlite3AddCheckConstraint(pParse,X);} ccons ::= REFERENCES nm(T) eidlist_opt(TA) refargs(R). {sqlite3CreateForeignKey(pParse,0,&T,TA,R);} ccons ::= defer_subclause(D). {sqlite3DeferForeignKey(pParse,D);} @@ -379,7 +395,7 @@ tcons ::= UNIQUE LP sortlist(X) RP onconf(R). {sqlite3CreateIndex(pParse,0,0,0,X,R,0,0,0,0, SQLITE_IDXTYPE_UNIQUE);} tcons ::= CHECK LP expr(E) RP onconf. - {sqlite3AddCheckConstraint(pParse,E.pExpr);} + {sqlite3AddCheckConstraint(pParse,E);} tcons ::= FOREIGN KEY LP eidlist(FA) RP REFERENCES nm(T) eidlist_opt(TA) refargs(R) defer_subclause_opt(D). { sqlite3CreateForeignKey(pParse, FA, &T, TA, R); @@ -485,7 +501,7 @@ selectnowith(A) ::= selectnowith(A) multiselect_op(Y) oneselect(Z). { x.n = 0; parserDoubleLinkSelect(pParse, pRhs); pFrom = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&x,pRhs,0,0); - pRhs = sqlite3SelectNew(pParse,0,pFrom,0,0,0,0,0,0,0); + pRhs = sqlite3SelectNew(pParse,0,pFrom,0,0,0,0,0,0); } if( pRhs ){ pRhs->op = (u8)Y; @@ -508,7 +524,7 @@ oneselect(A) ::= SELECT(S) distinct(D) selcollist(W) from(X) where_opt(Y) #if SELECTTRACE_ENABLED Token s = S; /*A-overwrites-S*/ #endif - A = sqlite3SelectNew(pParse,W,X,Y,P,Q,Z,D,L.pLimit,L.pOffset); + A = sqlite3SelectNew(pParse,W,X,Y,P,Q,Z,D,L); #if SELECTTRACE_ENABLED /* Populate the Select.zSelName[] string that is used to help with ** query planner debugging, to differentiate between multiple Select @@ -539,11 +555,11 @@ oneselect(A) ::= values(A). %type values {Select*} %destructor values {sqlite3SelectDelete(pParse->db, $$);} values(A) ::= VALUES LP nexprlist(X) RP. { - A = sqlite3SelectNew(pParse,X,0,0,0,0,0,SF_Values,0,0); + A = sqlite3SelectNew(pParse,X,0,0,0,0,0,SF_Values,0); } values(A) ::= values(A) COMMA LP exprlist(Y) RP. { Select *pRight, *pLeft = A; - pRight = sqlite3SelectNew(pParse,Y,0,0,0,0,0,SF_Values|SF_MultiValue,0,0); + pRight = sqlite3SelectNew(pParse,Y,0,0,0,0,0,SF_Values|SF_MultiValue,0); if( ALWAYS(pLeft) ) pLeft->selFlags &= ~SF_MultiValue; if( pRight ){ pRight->op = TK_ALL; @@ -573,16 +589,16 @@ distinct(A) ::= . {A = 0;} %destructor sclp {sqlite3ExprListDelete(pParse->db, $$);} sclp(A) ::= selcollist(A) COMMA. sclp(A) ::= . {A = 0;} -selcollist(A) ::= sclp(A) expr(X) as(Y). { - A = sqlite3ExprListAppend(pParse, A, X.pExpr); +selcollist(A) ::= sclp(A) scanpt(B) expr(X) scanpt(Z) as(Y). { + A = sqlite3ExprListAppend(pParse, A, X); if( Y.n>0 ) sqlite3ExprListSetName(pParse, A, &Y, 1); - sqlite3ExprListSetSpan(pParse,A,&X); + sqlite3ExprListSetSpan(pParse,A,B,Z); } -selcollist(A) ::= sclp(A) STAR. { +selcollist(A) ::= sclp(A) scanpt STAR. { Expr *p = sqlite3Expr(pParse->db, TK_ASTERISK, 0); A = sqlite3ExprListAppend(pParse, A, p); } -selcollist(A) ::= sclp(A) nm(X) DOT STAR. { +selcollist(A) ::= sclp(A) scanpt 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); @@ -654,7 +670,7 @@ seltablist(A) ::= stl_prefix(A) nm(Y) dbnm(D) LP exprlist(E) RP as(Z) }else{ Select *pSubquery; sqlite3SrcListShiftJoinType(F); - pSubquery = sqlite3SelectNew(pParse,0,F,0,0,0,0,SF_NestedFrom,0,0); + pSubquery = sqlite3SelectNew(pParse,0,F,0,0,0,0,SF_NestedFrom,0); A = sqlite3SrcListAppendFromTerm(pParse,A,0,0,&Z,pSubquery,N,U); } } @@ -680,7 +696,7 @@ joinop(X) ::= JOIN_KW(A) nm(B) nm(C) JOIN. %type on_opt {Expr*} %destructor on_opt {sqlite3ExprDelete(pParse->db, $$);} -on_opt(N) ::= ON expr(E). {N = E.pExpr;} +on_opt(N) ::= ON expr(E). {N = E;} on_opt(N) ::= . {N = 0;} // Note that this block abuses the Token type just a little. If there is @@ -717,11 +733,11 @@ using_opt(U) ::= . {U = 0;} orderby_opt(A) ::= . {A = 0;} orderby_opt(A) ::= ORDER BY sortlist(X). {A = X;} sortlist(A) ::= sortlist(A) COMMA expr(Y) sortorder(Z). { - A = sqlite3ExprListAppend(pParse,A,Y.pExpr); + A = sqlite3ExprListAppend(pParse,A,Y); sqlite3ExprListSetSortOrder(A,Z); } sortlist(A) ::= expr(Y) sortorder(Z). { - A = sqlite3ExprListAppend(pParse,0,Y.pExpr); /*A-overwrites-Y*/ + A = sqlite3ExprListAppend(pParse,0,Y); /*A-overwrites-Y*/ sqlite3ExprListSetSortOrder(A,Z); } @@ -739,9 +755,9 @@ groupby_opt(A) ::= GROUP BY nexprlist(X). {A = X;} %type having_opt {Expr*} %destructor having_opt {sqlite3ExprDelete(pParse->db, $$);} having_opt(A) ::= . {A = 0;} -having_opt(A) ::= HAVING expr(X). {A = X.pExpr;} +having_opt(A) ::= HAVING expr(X). {A = X;} -%type limit_opt {struct LimitVal} +%type limit_opt {Expr*} // The destructor for limit_opt will never fire in the current grammar. // The limit_opt non-terminal only occurs at the end of a single production @@ -750,16 +766,14 @@ having_opt(A) ::= HAVING expr(X). {A = X.pExpr;} // reduce. So there is never a limit_opt non-terminal on the stack // except as a transient. So there is never anything to destroy. // -//%destructor limit_opt { -// sqlite3ExprDelete(pParse->db, $$.pLimit); -// sqlite3ExprDelete(pParse->db, $$.pOffset); -//} -limit_opt(A) ::= . {A.pLimit = 0; A.pOffset = 0;} -limit_opt(A) ::= LIMIT expr(X). {A.pLimit = X.pExpr; A.pOffset = 0;} +//%destructor limit_opt {sqlite3ExprDelete(pParse->db, $$);} +limit_opt(A) ::= . {A = 0;} +limit_opt(A) ::= LIMIT expr(X). + {A = sqlite3PExpr(pParse,TK_LIMIT,X,0);} limit_opt(A) ::= LIMIT expr(X) OFFSET expr(Y). - {A.pLimit = X.pExpr; A.pOffset = Y.pExpr;} + {A = sqlite3PExpr(pParse,TK_LIMIT,X,Y);} limit_opt(A) ::= LIMIT expr(X) COMMA expr(Y). - {A.pOffset = X.pExpr; A.pLimit = Y.pExpr;} + {A = sqlite3PExpr(pParse,TK_LIMIT,Y,X);} /////////////////////////// The DELETE statement ///////////////////////////// // @@ -768,14 +782,14 @@ cmd ::= with(C) DELETE FROM fullname(X) indexed_opt(I) where_opt(W) orderby_opt(O) limit_opt(L). { sqlite3WithPush(pParse, C, 1); sqlite3SrcListIndexedBy(pParse, X, &I); - sqlite3DeleteFrom(pParse,X,W,O,L.pLimit,L.pOffset); + sqlite3DeleteFrom(pParse,X,W,O,L); } %endif %ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT cmd ::= with(C) DELETE FROM fullname(X) indexed_opt(I) where_opt(W). { sqlite3WithPush(pParse, C, 1); sqlite3SrcListIndexedBy(pParse, X, &I); - sqlite3DeleteFrom(pParse,X,W,0,0,0); + sqlite3DeleteFrom(pParse,X,W,0,0); } %endif @@ -783,7 +797,7 @@ cmd ::= with(C) DELETE FROM fullname(X) indexed_opt(I) where_opt(W). { %destructor where_opt {sqlite3ExprDelete(pParse->db, $$);} where_opt(A) ::= . {A = 0;} -where_opt(A) ::= WHERE expr(X). {A = X.pExpr;} +where_opt(A) ::= WHERE expr(X). {A = X;} ////////////////////////// The UPDATE command //////////////////////////////// // @@ -793,7 +807,7 @@ cmd ::= with(C) UPDATE orconf(R) fullname(X) indexed_opt(I) SET setlist(Y) sqlite3WithPush(pParse, C, 1); sqlite3SrcListIndexedBy(pParse, X, &I); sqlite3ExprListCheckLength(pParse,Y,"set list"); - sqlite3Update(pParse,X,Y,W,R,O,L.pLimit,L.pOffset); + sqlite3Update(pParse,X,Y,W,R,O,L); } %endif %ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT @@ -802,7 +816,7 @@ cmd ::= with(C) UPDATE orconf(R) fullname(X) indexed_opt(I) SET setlist(Y) sqlite3WithPush(pParse, C, 1); sqlite3SrcListIndexedBy(pParse, X, &I); sqlite3ExprListCheckLength(pParse,Y,"set list"); - sqlite3Update(pParse,X,Y,W,R,0,0,0); + sqlite3Update(pParse,X,Y,W,R,0,0); } %endif @@ -810,18 +824,18 @@ cmd ::= with(C) UPDATE orconf(R) fullname(X) indexed_opt(I) SET setlist(Y) %destructor setlist {sqlite3ExprListDelete(pParse->db, $$);} setlist(A) ::= setlist(A) COMMA nm(X) EQ expr(Y). { - A = sqlite3ExprListAppend(pParse, A, Y.pExpr); + A = sqlite3ExprListAppend(pParse, A, Y); sqlite3ExprListSetName(pParse, A, &X, 1); } setlist(A) ::= setlist(A) COMMA LP idlist(X) RP EQ expr(Y). { - A = sqlite3ExprListAppendVector(pParse, A, X, Y.pExpr); + A = sqlite3ExprListAppendVector(pParse, A, X, Y); } setlist(A) ::= nm(X) EQ expr(Y). { - A = sqlite3ExprListAppend(pParse, 0, Y.pExpr); + A = sqlite3ExprListAppend(pParse, 0, Y); sqlite3ExprListSetName(pParse, A, &X, 1); } setlist(A) ::= LP idlist(X) RP EQ expr(Y). { - A = sqlite3ExprListAppendVector(pParse, 0, X, Y.pExpr); + A = sqlite3ExprListAppendVector(pParse, 0, X, Y); } ////////////////////////// The INSERT command ///////////////////////////////// @@ -855,26 +869,18 @@ idlist(A) ::= nm(Y). /////////////////////////// Expression Processing ///////////////////////////// // -%type expr {ExprSpan} -%destructor expr {sqlite3ExprDelete(pParse->db, $$.pExpr);} -%type term {ExprSpan} -%destructor term {sqlite3ExprDelete(pParse->db, $$.pExpr);} +%type expr {Expr*} +%destructor expr {sqlite3ExprDelete(pParse->db, $$);} +%type term {Expr*} +%destructor term {sqlite3ExprDelete(pParse->db, $$);} %include { - /* This is a utility routine used to set the ExprSpan.zStart and - ** ExprSpan.zEnd values of pOut so that the span covers the complete - ** range of text beginning with pStart and going to the end of pEnd. - */ - static void spanSet(ExprSpan *pOut, Token *pStart, Token *pEnd){ - pOut->zStart = pStart->z; - pOut->zEnd = &pEnd->z[pEnd->n]; - } /* Construct a new Expr object from a single identifier. Use the ** new Expr to populate pOut. Set the span of pOut to be the identifier ** that created the expression. */ - static void spanExpr(ExprSpan *pOut, Parse *pParse, int op, Token t){ + static Expr *tokenExpr(Parse *pParse, int op, Token t){ Expr *p = sqlite3DbMallocRawNN(pParse->db, sizeof(Expr)+t.n+1); if( p ){ memset(p, 0, sizeof(Expr)); @@ -892,136 +898,98 @@ idlist(A) ::= nm(Y). p->nHeight = 1; #endif } - pOut->pExpr = p; - pOut->zStart = t.z; - pOut->zEnd = &t.z[t.n]; + return p; } } expr(A) ::= term(A). -expr(A) ::= LP(B) expr(X) RP(E). - {spanSet(&A,&B,&E); /*A-overwrites-B*/ A.pExpr = X.pExpr;} -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) ::= LP expr(X) RP. {A = X;} +expr(A) ::= id(X). {A=tokenExpr(pParse,TK_ID,X); /*A-overwrites-X*/} +expr(A) ::= JOIN_KW(X). {A=tokenExpr(pParse,TK_ID,X); /*A-overwrites-X*/} expr(A) ::= nm(X) DOT nm(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); + A = sqlite3PExpr(pParse, TK_DOT, temp1, temp2); } expr(A) ::= nm(X) DOT nm(Y) DOT nm(Z). { 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); + A = sqlite3PExpr(pParse, TK_DOT, temp1, temp4); } -term(A) ::= NULL|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) ::= NULL|FLOAT|BLOB(X). {A=tokenExpr(pParse,@X,X); /*A-overwrites-X*/} +term(A) ::= STRING(X). {A=tokenExpr(pParse,@X,X); /*A-overwrites-X*/} term(A) ::= INTEGER(X). { - A.pExpr = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &X, 1); - A.zStart = X.z; - A.zEnd = X.z + X.n; + A = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &X, 1); } 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, n); + A = tokenExpr(pParse, TK_VARIABLE, X); + sqlite3ExprAssignVarNumber(pParse, A, 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 ** in the virtual machine. #N is the N-th register. */ Token t = X; /*A-overwrites-X*/ assert( t.n>=2 ); - spanSet(&A, &t, &t); if( pParse->nested==0 ){ parserSyntaxError(pParse, &t); - A.pExpr = 0; + A = 0; }else{ - A.pExpr = sqlite3PExpr(pParse, TK_REGISTER, 0, 0); - if( A.pExpr ) sqlite3GetInt32(&t.z[1], &A.pExpr->iTable); + A = sqlite3PExpr(pParse, TK_REGISTER, 0, 0); + if( A ) sqlite3GetInt32(&t.z[1], &A->iTable); } } } expr(A) ::= expr(A) COLLATE ids(C). { - A.pExpr = sqlite3ExprAddCollateToken(pParse, A.pExpr, &C, 1); - A.zEnd = &C.z[C.n]; + A = sqlite3ExprAddCollateToken(pParse, A, &C, 1); } %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 = sqlite3ExprAlloc(pParse->db, TK_CAST, &T, 1); - sqlite3ExprAttachSubtrees(pParse->db, A.pExpr, E.pExpr, 0); +expr(A) ::= CAST LP expr(E) AS typetoken(T) RP. { + A = sqlite3ExprAlloc(pParse->db, TK_CAST, &T, 1); + sqlite3ExprAttachSubtrees(pParse->db, A, E, 0); } %endif SQLITE_OMIT_CAST -expr(A) ::= id(X) LP distinct(D) exprlist(Y) RP(E). { +expr(A) ::= id(X) LP distinct(D) exprlist(Y) RP. { if( Y && Y->nExpr>pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] ){ sqlite3ErrorMsg(pParse, "too many arguments on function %T", &X); } - A.pExpr = sqlite3ExprFunction(pParse, Y, &X); - spanSet(&A,&X,&E); - if( D==SF_Distinct && A.pExpr ){ - A.pExpr->flags |= EP_Distinct; + A = sqlite3ExprFunction(pParse, Y, &X); + if( D==SF_Distinct && A ){ + A->flags |= EP_Distinct; } } -expr(A) ::= id(X) LP STAR RP(E). { - A.pExpr = sqlite3ExprFunction(pParse, 0, &X); - spanSet(&A,&X,&E); +expr(A) ::= id(X) LP STAR RP. { + A = sqlite3ExprFunction(pParse, 0, &X); } term(A) ::= CTIME_KW(OP). { - A.pExpr = sqlite3ExprFunction(pParse, 0, &OP); - spanSet(&A, &OP, &OP); + A = sqlite3ExprFunction(pParse, 0, &OP); } -%include { - /* This routine constructs a binary expression node out of two ExprSpan - ** objects and uses the result to populate a new ExprSpan object. - */ - static void spanBinaryExpr( - Parse *pParse, /* The parsing context. Errors accumulate here */ - int op, /* The binary operation */ - ExprSpan *pLeft, /* The left operand, and output */ - ExprSpan *pRight /* The right operand */ - ){ - pLeft->pExpr = sqlite3PExpr(pParse, op, pLeft->pExpr, pRight->pExpr); - pLeft->zEnd = pRight->zEnd; - } - - /* If doNot is true, then add a TK_NOT Expr-node wrapper around the - ** outside of *ppExpr. - */ - static void exprNot(Parse *pParse, int doNot, ExprSpan *pSpan){ - if( doNot ){ - 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); +expr(A) ::= LP nexprlist(X) COMMA expr(Y) RP. { + ExprList *pList = sqlite3ExprListAppend(pParse, X, Y); + A = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); + if( A ){ + A->x.pList = pList; }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) AND(OP) expr(Y). {A=sqlite3PExpr(pParse,@OP,A,Y);} +expr(A) ::= expr(A) OR(OP) expr(Y). {A=sqlite3PExpr(pParse,@OP,A,Y);} expr(A) ::= expr(A) LT|GT|GE|LE(OP) expr(Y). - {spanBinaryExpr(pParse,@OP,&A,&Y);} -expr(A) ::= expr(A) EQ|NE(OP) expr(Y). {spanBinaryExpr(pParse,@OP,&A,&Y);} + {A=sqlite3PExpr(pParse,@OP,A,Y);} +expr(A) ::= expr(A) EQ|NE(OP) expr(Y). {A=sqlite3PExpr(pParse,@OP,A,Y);} expr(A) ::= expr(A) BITAND|BITOR|LSHIFT|RSHIFT(OP) expr(Y). - {spanBinaryExpr(pParse,@OP,&A,&Y);} + {A=sqlite3PExpr(pParse,@OP,A,Y);} expr(A) ::= expr(A) PLUS|MINUS(OP) expr(Y). - {spanBinaryExpr(pParse,@OP,&A,&Y);} + {A=sqlite3PExpr(pParse,@OP,A,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);} + {A=sqlite3PExpr(pParse,@OP,A,Y);} +expr(A) ::= expr(A) CONCAT(OP) expr(Y). {A=sqlite3PExpr(pParse,@OP,A,Y);} %type likeop {Token} likeop(A) ::= LIKE_KW|MATCH(A). likeop(A) ::= NOT LIKE_KW|MATCH(X). {A=X; A.n|=0x80000000; /*A-overwrite-X*/} @@ -1029,42 +997,26 @@ 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); - exprNot(pParse, bNot, &A); - A.zEnd = Y.zEnd; - if( A.pExpr ) A.pExpr->flags |= EP_InfixFunc; + pList = sqlite3ExprListAppend(pParse,0, Y); + pList = sqlite3ExprListAppend(pParse,pList, A); + A = sqlite3ExprFunction(pParse, pList, &OP); + if( bNot ) A = sqlite3PExpr(pParse, TK_NOT, A, 0); + if( A ) A->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); - exprNot(pParse, bNot, &A); - A.zEnd = E.zEnd; - if( A.pExpr ) A.pExpr->flags |= EP_InfixFunc; + pList = sqlite3ExprListAppend(pParse,0, Y); + pList = sqlite3ExprListAppend(pParse,pList, A); + pList = sqlite3ExprListAppend(pParse,pList, E); + A = sqlite3ExprFunction(pParse, pList, &OP); + if( bNot ) A = sqlite3PExpr(pParse, TK_NOT, A, 0); + if( A ) A->flags |= EP_InfixFunc; } -%include { - /* Construct an expression node for a unary postfix operator - */ - static void spanUnaryPostfix( - Parse *pParse, /* Parsing context to record errors */ - int op, /* The operator */ - ExprSpan *pOperand, /* The operand, and output */ - Token *pPostOp /* The operand token for setting the span */ - ){ - pOperand->pExpr = sqlite3PExpr(pParse, op, pOperand->pExpr, 0); - pOperand->zEnd = &pPostOp->z[pPostOp->n]; - } -} - -expr(A) ::= expr(A) ISNULL|NOTNULL(E). {spanUnaryPostfix(pParse,@E,&A,&E);} -expr(A) ::= expr(A) NOT NULL(E). {spanUnaryPostfix(pParse,TK_NOTNULL,&A,&E);} +expr(A) ::= expr(A) ISNULL|NOTNULL(E). {A = sqlite3PExpr(pParse,@E,A,0);} +expr(A) ::= expr(A) NOT NULL. {A = sqlite3PExpr(pParse,TK_NOTNULL,A,0);} %include { /* A routine to convert a binary TK_IS or TK_ISNOT expression into a @@ -1086,61 +1038,42 @@ expr(A) ::= expr(A) NOT NULL(E). {spanUnaryPostfix(pParse,TK_NOTNULL,&A,&E);} // is any other expression, code as TK_IS or TK_ISNOT. // expr(A) ::= expr(A) IS expr(Y). { - spanBinaryExpr(pParse,TK_IS,&A,&Y); - binaryToUnaryIfNull(pParse, Y.pExpr, A.pExpr, TK_ISNULL); + A = sqlite3PExpr(pParse,TK_IS,A,Y); + binaryToUnaryIfNull(pParse, Y, A, TK_ISNULL); } expr(A) ::= expr(A) IS NOT expr(Y). { - spanBinaryExpr(pParse,TK_ISNOT,&A,&Y); - binaryToUnaryIfNull(pParse, Y.pExpr, A.pExpr, TK_NOTNULL); + A = sqlite3PExpr(pParse,TK_ISNOT,A,Y); + binaryToUnaryIfNull(pParse, Y, A, TK_NOTNULL); } -%include { - /* Construct an expression node for a unary prefix operator - */ - static void spanUnaryPrefix( - ExprSpan *pOut, /* Write the new expression node here */ - Parse *pParse, /* Parsing context to record errors */ - int op, /* The operator */ - ExprSpan *pOperand, /* The operand */ - Token *pPreOp /* The operand token for setting the span */ - ){ - pOut->zStart = pPreOp->z; - pOut->pExpr = sqlite3PExpr(pParse, op, pOperand->pExpr, 0); - pOut->zEnd = pOperand->zEnd; - } -} - - - expr(A) ::= NOT(B) expr(X). - {spanUnaryPrefix(&A,pParse,@B,&X,&B);/*A-overwrites-B*/} + {A = sqlite3PExpr(pParse, @B, X, 0);/*A-overwrites-B*/} expr(A) ::= BITNOT(B) expr(X). - {spanUnaryPrefix(&A,pParse,@B,&X,&B);/*A-overwrites-B*/} -expr(A) ::= MINUS(B) expr(X). [BITNOT] - {spanUnaryPrefix(&A,pParse,TK_UMINUS,&X,&B);/*A-overwrites-B*/} -expr(A) ::= PLUS(B) expr(X). [BITNOT] - {spanUnaryPrefix(&A,pParse,TK_UPLUS,&X,&B);/*A-overwrites-B*/} + {A = sqlite3PExpr(pParse, @B, X, 0);/*A-overwrites-B*/} +expr(A) ::= MINUS expr(X). [BITNOT] + {A = sqlite3PExpr(pParse, TK_UMINUS, X, 0);} +expr(A) ::= PLUS expr(X). [BITNOT] + {A = sqlite3PExpr(pParse, TK_UPLUS, X, 0);} %type between_op {int} between_op(A) ::= BETWEEN. {A = 0;} 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); - if( A.pExpr ){ - A.pExpr->x.pList = pList; + ExprList *pList = sqlite3ExprListAppend(pParse,0, X); + pList = sqlite3ExprListAppend(pParse,pList, Y); + A = sqlite3PExpr(pParse, TK_BETWEEN, A, 0); + if( A ){ + A->x.pList = pList; }else{ sqlite3ExprListDelete(pParse->db, pList); } - exprNot(pParse, N, &A); - A.zEnd = Y.zEnd; + if( N ) A = sqlite3PExpr(pParse, TK_NOT, A, 0); } %ifndef SQLITE_OMIT_SUBQUERY %type in_op {int} in_op(A) ::= IN. {A = 0;} in_op(A) ::= NOT IN. {A = 1;} - expr(A) ::= expr(A) in_op(N) LP exprlist(Y) RP(E). [IN] { + expr(A) ::= expr(A) in_op(N) LP exprlist(Y) RP. [IN] { if( Y==0 ){ /* Expressions of the form ** @@ -1150,8 +1083,8 @@ expr(A) ::= expr(A) between_op(N) expr(X) AND expr(Y). [BETWEEN] { ** simplify to constants 0 (false) and 1 (true), respectively, ** regardless of the value of expr1. */ - sqlite3ExprDelete(pParse->db, A.pExpr); - A.pExpr = sqlite3ExprAlloc(pParse->db, TK_INTEGER,&sqlite3IntTokens[N],1); + sqlite3ExprDelete(pParse->db, A); + A = sqlite3ExprAlloc(pParse->db, TK_INTEGER,&sqlite3IntTokens[N],1); }else if( Y->nExpr==1 ){ /* Expressions of the form: ** @@ -1178,54 +1111,48 @@ 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); + A = sqlite3PExpr(pParse, N ? TK_NE : TK_EQ, A, pRHS); }else{ - A.pExpr = sqlite3PExpr(pParse, TK_IN, A.pExpr, 0); - if( A.pExpr ){ - A.pExpr->x.pList = Y; - sqlite3ExprSetHeightAndFlags(pParse, A.pExpr); + A = sqlite3PExpr(pParse, TK_IN, A, 0); + if( A ){ + A->x.pList = Y; + sqlite3ExprSetHeightAndFlags(pParse, A); }else{ sqlite3ExprListDelete(pParse->db, Y); } - exprNot(pParse, N, &A); + if( N ) A = sqlite3PExpr(pParse, TK_NOT, A, 0); } - A.zEnd = &E.z[E.n]; } - expr(A) ::= LP(B) select(X) RP(E). { - spanSet(&A,&B,&E); /*A-overwrites-B*/ - A.pExpr = sqlite3PExpr(pParse, TK_SELECT, 0, 0); - sqlite3PExprAddSelect(pParse, A.pExpr, X); + expr(A) ::= LP select(X) RP. { + A = sqlite3PExpr(pParse, TK_SELECT, 0, 0); + sqlite3PExprAddSelect(pParse, A, X); } - expr(A) ::= expr(A) in_op(N) LP select(Y) RP(E). [IN] { - A.pExpr = sqlite3PExpr(pParse, TK_IN, A.pExpr, 0); - sqlite3PExprAddSelect(pParse, A.pExpr, Y); - exprNot(pParse, N, &A); - A.zEnd = &E.z[E.n]; + expr(A) ::= expr(A) in_op(N) LP select(Y) RP. [IN] { + A = sqlite3PExpr(pParse, TK_IN, A, 0); + sqlite3PExprAddSelect(pParse, A, Y); + if( N ) A = sqlite3PExpr(pParse, TK_NOT, A, 0); } expr(A) ::= expr(A) in_op(N) nm(Y) dbnm(Z) paren_exprlist(E). [IN] { SrcList *pSrc = sqlite3SrcListAppend(pParse->db, 0,&Y,&Z); - Select *pSelect = sqlite3SelectNew(pParse, 0,pSrc,0,0,0,0,0,0,0); + Select *pSelect = sqlite3SelectNew(pParse, 0,pSrc,0,0,0,0,0,0); if( E ) sqlite3SrcListFuncArgs(pParse, pSelect ? pSrc : 0, E); - 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]; + A = sqlite3PExpr(pParse, TK_IN, A, 0); + sqlite3PExprAddSelect(pParse, A, pSelect); + if( N ) A = sqlite3PExpr(pParse, TK_NOT, A, 0); } - expr(A) ::= EXISTS(B) LP select(Y) RP(E). { + expr(A) ::= EXISTS LP select(Y) RP. { Expr *p; - spanSet(&A,&B,&E); /*A-overwrites-B*/ - p = A.pExpr = sqlite3PExpr(pParse, TK_EXISTS, 0, 0); + p = A = sqlite3PExpr(pParse, TK_EXISTS, 0, 0); sqlite3PExprAddSelect(pParse, p, Y); } %endif SQLITE_OMIT_SUBQUERY /* 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); - if( A.pExpr ){ - A.pExpr->x.pList = Z ? sqlite3ExprListAppend(pParse,Y,Z) : Y; - sqlite3ExprSetHeightAndFlags(pParse, A.pExpr); +expr(A) ::= CASE case_operand(X) case_exprlist(Y) case_else(Z) END. { + A = sqlite3PExpr(pParse, TK_CASE, X, 0); + if( A ){ + A->x.pList = Z ? sqlite3ExprListAppend(pParse,Y,Z) : Y; + sqlite3ExprSetHeightAndFlags(pParse, A); }else{ sqlite3ExprListDelete(pParse->db, Y); sqlite3ExprDelete(pParse->db, Z); @@ -1234,20 +1161,20 @@ expr(A) ::= CASE(C) case_operand(X) case_exprlist(Y) case_else(Z) END(E). { %type case_exprlist {ExprList*} %destructor case_exprlist {sqlite3ExprListDelete(pParse->db, $$);} case_exprlist(A) ::= case_exprlist(A) WHEN expr(Y) THEN expr(Z). { - A = sqlite3ExprListAppend(pParse,A, Y.pExpr); - A = sqlite3ExprListAppend(pParse,A, Z.pExpr); + A = sqlite3ExprListAppend(pParse,A, Y); + A = sqlite3ExprListAppend(pParse,A, Z); } case_exprlist(A) ::= WHEN expr(Y) THEN expr(Z). { - A = sqlite3ExprListAppend(pParse,0, Y.pExpr); - A = sqlite3ExprListAppend(pParse,A, Z.pExpr); + A = sqlite3ExprListAppend(pParse,0, Y); + A = sqlite3ExprListAppend(pParse,A, Z); } %type case_else {Expr*} %destructor case_else {sqlite3ExprDelete(pParse->db, $$);} -case_else(A) ::= ELSE expr(X). {A = X.pExpr;} +case_else(A) ::= ELSE expr(X). {A = X;} case_else(A) ::= . {A = 0;} %type case_operand {Expr*} %destructor case_operand {sqlite3ExprDelete(pParse->db, $$);} -case_operand(A) ::= expr(X). {A = X.pExpr; /*A-overwrites-X*/} +case_operand(A) ::= expr(X). {A = X; /*A-overwrites-X*/} case_operand(A) ::= . {A = 0;} %type exprlist {ExprList*} @@ -1258,9 +1185,9 @@ case_operand(A) ::= . {A = 0;} exprlist(A) ::= nexprlist(A). exprlist(A) ::= . {A = 0;} nexprlist(A) ::= nexprlist(A) COMMA expr(Y). - {A = sqlite3ExprListAppend(pParse,A,Y.pExpr);} + {A = sqlite3ExprListAppend(pParse,A,Y);} nexprlist(A) ::= expr(Y). - {A = sqlite3ExprListAppend(pParse,0,Y.pExpr); /*A-overwrites-Y*/} + {A = sqlite3ExprListAppend(pParse,0,Y); /*A-overwrites-Y*/} %ifndef SQLITE_OMIT_SUBQUERY /* A paren_exprlist is an optional expression list contained inside @@ -1414,7 +1341,7 @@ foreach_clause ::= FOR EACH ROW. %type when_clause {Expr*} %destructor when_clause {sqlite3ExprDelete(pParse->db, $$);} when_clause(A) ::= . { A = 0; } -when_clause(A) ::= WHEN expr(X). { A = X.pExpr; } +when_clause(A) ::= WHEN expr(X). { A = X; } %type trigger_cmd_list {TriggerStep*} %destructor trigger_cmd_list {sqlite3DeleteTriggerStep(pParse->db, $$);} @@ -1463,34 +1390,33 @@ tridxby ::= NOT INDEXED. { %destructor trigger_cmd {sqlite3DeleteTriggerStep(pParse->db, $$);} // UPDATE trigger_cmd(A) ::= - UPDATE orconf(R) trnm(X) tridxby SET setlist(Y) where_opt(Z). - {A = sqlite3TriggerUpdateStep(pParse->db, &X, Y, Z, R);} + UPDATE(B) orconf(R) trnm(X) tridxby SET setlist(Y) where_opt(Z) scanpt(E). + {A = sqlite3TriggerUpdateStep(pParse->db, &X, Y, Z, R, B.z, E);} // INSERT -trigger_cmd(A) ::= insert_cmd(R) INTO trnm(X) idlist_opt(F) select(S). - {A = sqlite3TriggerInsertStep(pParse->db, &X, F, S, R);/*A-overwrites-R*/} +trigger_cmd(A) ::= scanpt(B) insert_cmd(R) INTO + trnm(X) idlist_opt(F) select(S) scanpt(Z). + {A = sqlite3TriggerInsertStep(pParse->db,&X,F,S,R,B,Z);/*A-overwrites-R*/} // DELETE -trigger_cmd(A) ::= DELETE FROM trnm(X) tridxby where_opt(Y). - {A = sqlite3TriggerDeleteStep(pParse->db, &X, Y);} +trigger_cmd(A) ::= DELETE(B) FROM trnm(X) tridxby where_opt(Y) scanpt(E). + {A = sqlite3TriggerDeleteStep(pParse->db, &X, Y, B.z, E);} // SELECT -trigger_cmd(A) ::= select(X). - {A = sqlite3TriggerSelectStep(pParse->db, X); /*A-overwrites-X*/} +trigger_cmd(A) ::= scanpt(B) select(X) scanpt(E). + {A = sqlite3TriggerSelectStep(pParse->db, X, B, E); /*A-overwrites-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); - if( A.pExpr ){ - A.pExpr->affinity = OE_Ignore; +expr(A) ::= RAISE LP IGNORE RP. { + A = sqlite3PExpr(pParse, TK_RAISE, 0, 0); + if( A ){ + A->affinity = OE_Ignore; } } -expr(A) ::= RAISE(X) LP raisetype(T) COMMA nm(Z) RP(Y). { - spanSet(&A,&X,&Y); /*A-overwrites-X*/ - A.pExpr = sqlite3ExprAlloc(pParse->db, TK_RAISE, &Z, 1); - if( A.pExpr ) { - A.pExpr->affinity = (char)T; +expr(A) ::= RAISE LP raisetype(T) COMMA nm(Z) RP. { + A = sqlite3ExprAlloc(pParse->db, TK_RAISE, &Z, 1); + if( A ) { + A->affinity = (char)T; } } %endif !SQLITE_OMIT_TRIGGER @@ -1511,16 +1437,16 @@ cmd ::= DROP TRIGGER ifexists(NOERR) fullname(X). { //////////////////////// ATTACH DATABASE file AS name ///////////////////////// %ifndef SQLITE_OMIT_ATTACH cmd ::= ATTACH database_kw_opt expr(F) AS expr(D) key_opt(K). { - sqlite3Attach(pParse, F.pExpr, D.pExpr, K); + sqlite3Attach(pParse, F, D, K); } cmd ::= DETACH database_kw_opt expr(D). { - sqlite3Detach(pParse, D.pExpr); + sqlite3Detach(pParse, D); } %type key_opt {Expr*} %destructor key_opt {sqlite3ExprDelete(pParse->db, $$);} key_opt(A) ::= . { A = 0; } -key_opt(A) ::= KEY expr(X). { A = X.pExpr; } +key_opt(A) ::= KEY expr(X). { A = X; } database_kw_opt ::= DATABASE. database_kw_opt ::= . diff --git a/src/pragma.c b/src/pragma.c index 918b1d8131..cdebcabae0 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -1080,6 +1080,7 @@ void sqlite3Pragma( ** type: Column declaration type. ** notnull: True if 'NOT NULL' is part of column declaration ** dflt_value: The default value for the column, if any. + ** pk: Non-zero for PK fields. */ case PragTyp_TABLE_INFO: if( zRight ){ Table *pTab; diff --git a/src/prepare.c b/src/prepare.c index 9141cb8a78..65a4afcbbd 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -655,8 +655,6 @@ static int sqlite3Prepare( end_prepare: sqlite3ParserReset(&sParse); - rc = sqlite3ApiExit(db, rc); - assert( (rc&db->errMask)==rc ); return rc; } static int sqlite3LockAndPrepare( @@ -669,6 +667,7 @@ static int sqlite3LockAndPrepare( const char **pzTail /* OUT: End of parsed string */ ){ int rc; + int cnt = 0; #ifdef SQLITE_ENABLE_API_ARMOR if( ppStmt==0 ) return SQLITE_MISUSE_BKPT; @@ -679,15 +678,18 @@ static int sqlite3LockAndPrepare( } sqlite3_mutex_enter(db->mutex); sqlite3BtreeEnterAll(db); - rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail); - if( rc==SQLITE_SCHEMA ){ - sqlite3ResetOneSchema(db, -1); - sqlite3_finalize(*ppStmt); + do{ + /* Make multiple attempts to compile the SQL, until it either succeeds + ** or encounters a permanent error. A schema problem after one schema + ** reset is considered a permanent error. */ rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail); - } + assert( rc==SQLITE_OK || *ppStmt==0 ); + }while( rc==SQLITE_ERROR_RETRY + || (rc==SQLITE_SCHEMA && (sqlite3ResetOneSchema(db,-1), cnt++)==0) ); sqlite3BtreeLeaveAll(db); + rc = sqlite3ApiExit(db, rc); + assert( (rc&db->errMask)==rc ); sqlite3_mutex_leave(db->mutex); - assert( rc==SQLITE_OK || *ppStmt==0 ); return rc; } diff --git a/src/printf.c b/src/printf.c index 9427844e09..fcbd7fdbe0 100644 --- a/src/printf.c +++ b/src/printf.c @@ -206,6 +206,11 @@ void sqlite3VXPrintf( PrintfArguments *pArgList = 0; /* Arguments for SQLITE_PRINTF_SQLFUNC */ char buf[etBUFSIZE]; /* Conversion buffer */ + /* pAccum never starts out with an empty buffer that was obtained from + ** malloc(). This precondition is required by the mprintf("%z...") + ** optimization. */ + assert( pAccum->nChar>0 || (pAccum->printfFlags&SQLITE_PRINTF_MALLOCED)==0 ); + bufpt = 0; if( (pAccum->printfFlags & SQLITE_PRINTF_SQLFUNC)!=0 ){ pArgList = va_arg(ap, PrintfArguments*); @@ -624,9 +629,38 @@ void sqlite3VXPrintf( case etCHARX: if( bArgList ){ bufpt = getTextArg(pArgList); - c = bufpt ? bufpt[0] : 0; + length = 1; + if( bufpt ){ + buf[0] = c = *(bufpt++); + if( (c&0xc0)==0xc0 ){ + while( length<4 && (bufpt[0]&0xc0)==0x80 ){ + buf[length++] = *(bufpt++); + } + } + }else{ + buf[0] = 0; + } }else{ - c = va_arg(ap,int); + unsigned int ch = va_arg(ap,unsigned int); + if( ch<0x00080 ){ + buf[0] = ch & 0xff; + length = 1; + }else if( ch<0x00800 ){ + buf[0] = 0xc0 + (u8)((ch>>6)&0x1f); + buf[1] = 0x80 + (u8)(ch & 0x3f); + length = 2; + }else if( ch<0x10000 ){ + buf[0] = 0xe0 + (u8)((ch>>12)&0x0f); + buf[1] = 0x80 + (u8)((ch>>6) & 0x3f); + buf[2] = 0x80 + (u8)(ch & 0x3f); + length = 3; + }else{ + buf[0] = 0xf0 + (u8)((ch>>18) & 0x07); + buf[1] = 0x80 + (u8)((ch>>12) & 0x3f); + buf[2] = 0x80 + (u8)((ch>>6) & 0x3f); + buf[3] = 0x80 + (u8)(ch & 0x3f); + length = 4; + } } if( precision>1 ){ width -= precision-1; @@ -634,12 +668,13 @@ void sqlite3VXPrintf( sqlite3AppendChar(pAccum, width-1, ' '); width = 0; } - sqlite3AppendChar(pAccum, precision-1, c); + while( precision-- > 1 ){ + sqlite3StrAccumAppend(pAccum, buf, length); + } } - length = 1; - buf[0] = c; bufpt = buf; - break; + flag_altform2 = 1; + goto adjust_width_for_utf8; case etSTRING: case etDYNSTRING: if( bArgList ){ @@ -651,17 +686,45 @@ void sqlite3VXPrintf( if( bufpt==0 ){ bufpt = ""; }else if( xtype==etDYNSTRING ){ + if( pAccum->nChar==0 && pAccum->mxAlloc && width==0 && precision<0 ){ + /* Special optimization for sqlite3_mprintf("%z..."): + ** Extend an existing memory allocation rather than creating + ** a new one. */ + assert( (pAccum->printfFlags&SQLITE_PRINTF_MALLOCED)==0 ); + pAccum->zText = bufpt; + pAccum->nAlloc = sqlite3DbMallocSize(pAccum->db, bufpt); + pAccum->nChar = 0x7fffffff & (int)strlen(bufpt); + pAccum->printfFlags |= SQLITE_PRINTF_MALLOCED; + length = 0; + break; + } zExtra = bufpt; } if( precision>=0 ){ - for(length=0; length 0 && z[0] ){ + SQLITE_SKIP_UTF8(z); + } + length = (int)(z - (unsigned char*)bufpt); + }else{ + for(length=0; length0 ){ + /* Adjust width to account for extra bytes in UTF-8 characters */ + int ii = length - 1; + while( ii>=0 ) if( (bufpt[ii--] & 0xc0)==0x80 ) width++; + } break; - case etSQLESCAPE: /* Escape ' characters */ - case etSQLESCAPE2: /* Escape ' and enclose in '...' */ - case etSQLESCAPE3: { /* Escape " characters */ + case etSQLESCAPE: /* %q: Escape ' characters */ + case etSQLESCAPE2: /* %Q: Escape ' and enclose in '...' */ + case etSQLESCAPE3: { /* %w: Escape " characters */ int i, j, k, n, isnull; int needQuote; char ch; @@ -675,9 +738,17 @@ void sqlite3VXPrintf( } isnull = escarg==0; if( isnull ) escarg = (xtype==etSQLESCAPE2 ? "NULL" : "(NULL)"); + /* For %q, %Q, and %w, the precision is the number of byte (or + ** characters if the ! flags is present) to use from the input. + ** Because of the extra quoting characters inserted, the number + ** of output characters may be larger than the precision. + */ k = precision; for(i=n=0; k!=0 && (ch=escarg[i])!=0; i++, k--){ if( ch==q ) n++; + if( flag_altform2 && (ch&0xc0)==0xc0 ){ + while( (escarg[i+1]&0xc0)==0x80 ){ i++; } + } } needQuote = !isnull && xtype==etSQLESCAPE2; n += i + 3; @@ -700,10 +771,7 @@ void sqlite3VXPrintf( if( needQuote ) bufpt[j++] = q; bufpt[j] = 0; length = j; - /* The precision in %q and %Q means how many input characters to - ** consume, not the length of the output... - ** if( precision>=0 && precision0 ){ @@ -1092,8 +1163,15 @@ void sqlite3DebugPrintf(const char *zFormat, ...){ sqlite3VXPrintf(&acc, zFormat, ap); va_end(ap); sqlite3StrAccumFinish(&acc); +#ifdef SQLITE_OS_TRACE_PROC + { + extern void SQLITE_OS_TRACE_PROC(const char *zBuf, int nBuf); + SQLITE_OS_TRACE_PROC(zBuf, sizeof(zBuf)); + } +#else fprintf(stdout,"%s", zBuf); fflush(stdout); +#endif } #endif diff --git a/src/resolve.c b/src/resolve.c index 17dbbccfc5..7ae49bde57 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -431,10 +431,16 @@ static int lookupName( ** Because no reference was made to outer contexts, the pNC->nRef ** fields are not changed in any context. */ - if( cnt==0 && zTab==0 && ExprHasProperty(pExpr,EP_DblQuoted) ){ - pExpr->op = TK_STRING; - pExpr->pTab = 0; - return WRC_Prune; + if( cnt==0 && zTab==0 ){ + assert( pExpr->op==TK_ID ); + if( ExprHasProperty(pExpr,EP_DblQuoted) ){ + pExpr->op = TK_STRING; + pExpr->pTab = 0; + return WRC_Prune; + } + if( sqlite3ExprIdToTrueFalse(pExpr) ){ + return WRC_Prune; + } } /* @@ -783,15 +789,30 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ notValid(pParse, pNC, "parameters", NC_IsCheck|NC_PartIdx|NC_IdxExpr); break; } + case TK_IS: + case TK_ISNOT: { + Expr *pRight; + assert( !ExprHasProperty(pExpr, EP_Reduced) ); + /* Handle special cases of "x IS TRUE", "x IS FALSE", "x IS NOT TRUE", + ** and "x IS NOT FALSE". */ + if( (pRight = pExpr->pRight)->op==TK_ID ){ + int rc = resolveExprStep(pWalker, pRight); + if( rc==WRC_Abort ) return WRC_Abort; + if( pRight->op==TK_TRUEFALSE ){ + pExpr->op2 = pExpr->op; + pExpr->op = TK_TRUTH; + return WRC_Continue; + } + } + /* Fall thru */ + } 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: { + case TK_GE: { int nLeft, nRight; if( pParse->db->mallocFailed ) break; assert( pExpr->pLeft!=0 ); @@ -1196,8 +1217,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ */ memset(&sNC, 0, sizeof(sNC)); sNC.pParse = pParse; - if( sqlite3ResolveExprNames(&sNC, p->pLimit) || - sqlite3ResolveExprNames(&sNC, p->pOffset) ){ + if( sqlite3ResolveExprNames(&sNC, p->pLimit) ){ return WRC_Abort; } diff --git a/src/select.c b/src/select.c index 46c69d71fb..9ac4f9f198 100644 --- a/src/select.c +++ b/src/select.c @@ -74,7 +74,6 @@ static void clearSelect(sqlite3 *db, Select *p, int bFree){ sqlite3ExprDelete(db, p->pHaving); sqlite3ExprListDelete(db, p->pOrderBy); sqlite3ExprDelete(db, p->pLimit); - sqlite3ExprDelete(db, p->pOffset); if( OK_IF_ALWAYS_TRUE(p->pWith) ) sqlite3WithDelete(db, p->pWith); if( bFree ) sqlite3DbFreeNN(db, p); p = pPrior; @@ -107,8 +106,7 @@ Select *sqlite3SelectNew( Expr *pHaving, /* the HAVING clause */ ExprList *pOrderBy, /* the ORDER BY clause */ u32 selFlags, /* Flag parameters, such as SF_Distinct */ - Expr *pLimit, /* LIMIT value. NULL means not used */ - Expr *pOffset /* OFFSET value. NULL means no offset */ + Expr *pLimit /* LIMIT value. NULL means not used */ ){ Select *pNew; Select standin; @@ -141,10 +139,7 @@ Select *sqlite3SelectNew( pNew->pPrior = 0; pNew->pNext = 0; pNew->pLimit = pLimit; - pNew->pOffset = pOffset; pNew->pWith = 0; - assert( pOffset==0 || pLimit!=0 || pParse->nErr>0 - || pParse->db->mallocFailed!=0 ); if( pParse->db->mallocFailed ) { clearSelect(pParse->db, pNew, pNew!=&standin); pNew = 0; @@ -1271,12 +1266,15 @@ static void generateSortTail( iSortTab = iTab; bSeq = 1; } - for(i=0, iCol=nKey+bSeq; i=0; i--){ int iRead; if( aOutEx[i].u.x.iOrderByCol ){ iRead = aOutEx[i].u.x.iOrderByCol-1; }else{ - iRead = iCol++; + iRead = iCol--; } sqlite3VdbeAddOp3(v, OP_Column, iSortTab, iRead, regRow+i); VdbeComment((v, "%s", aOutEx[i].zName ? aOutEx[i].zName : aOutEx[i].zSpan)); @@ -1386,8 +1384,9 @@ static const char *columnTypeImpl( assert( pExpr!=0 ); assert( pNC->pSrcList!=0 ); + assert( pExpr->op!=TK_AGG_COLUMN ); /* This routine runes before aggregates + ** are processed */ switch( pExpr->op ){ - case TK_AGG_COLUMN: case TK_COLUMN: { /* The expression is a column. Locate the table the column is being ** extracted from in NameContext.pSrcList. This table may be real @@ -1396,8 +1395,6 @@ static const char *columnTypeImpl( Table *pTab = 0; /* Table structure column is extracted from */ Select *pS = 0; /* Select the column is extracted from */ int iCol = pExpr->iColumn; /* Index of column in pTab */ - testcase( pExpr->op==TK_AGG_COLUMN ); - testcase( pExpr->op==TK_COLUMN ); while( pNC && !pTab ){ SrcList *pTabList = pNC->pSrcList; for(j=0;jnSrc && pTabList->a[j].iCursor!=pExpr->iTable;j++); @@ -1601,6 +1598,7 @@ static void generateColumnNames( if( pParse->colNamesSet || db->mallocFailed ) return; /* Column names are determined by the left-most term of a compound select */ while( pSelect->pPrior ) pSelect = pSelect->pPrior; + SELECTTRACE(1,pParse,pSelect,("generating column names\n")); pTabList = pSelect->pSrc; pEList = pSelect->pEList; assert( v!=0 ); @@ -1709,12 +1707,12 @@ int sqlite3ColumnsFromExprList( pColExpr = pColExpr->pRight; assert( pColExpr!=0 ); } - if( (pColExpr->op==TK_COLUMN || pColExpr->op==TK_AGG_COLUMN) - && pColExpr->pTab!=0 - ){ + assert( pColExpr->op!=TK_AGG_COLUMN ); + if( pColExpr->op==TK_COLUMN ){ /* For columns use the column name name */ int iCol = pColExpr->iColumn; Table *pTab = pColExpr->pTab; + assert( pTab!=0 ); if( iCol<0 ) iCol = pTab->iPKey; zName = iCol>=0 ? pTab->aCol[iCol].zName : "rowid"; }else if( pColExpr->op==TK_ID ){ @@ -1874,7 +1872,7 @@ Vdbe *sqlite3GetVdbe(Parse *pParse){ /* ** Compute the iLimit and iOffset fields of the SELECT based on the -** pLimit and pOffset expressions. pLimit and pOffset hold the expressions +** pLimit expressions. pLimit->pLeft and pLimit->pRight hold the expressions ** that appear in the original SQL statement after the LIMIT and OFFSET ** keywords. Or NULL if those keywords are omitted. iLimit and iOffset ** are the integer memory register numbers for counters used to compute @@ -1882,15 +1880,15 @@ Vdbe *sqlite3GetVdbe(Parse *pParse){ ** iLimit and iOffset are negative. ** ** This routine changes the values of iLimit and iOffset only if -** a limit or offset is defined by pLimit and pOffset. iLimit and -** iOffset should have been preset to appropriate default values (zero) +** a limit or offset is defined by pLimit->pLeft and pLimit->pRight. iLimit +** and iOffset should have been preset to appropriate default values (zero) ** prior to calling this routine. ** ** The iOffset register (if it exists) is initialized to the value ** of the OFFSET. The iLimit register is initialized to LIMIT. Register ** iOffset+1 is initialized to LIMIT+OFFSET. ** -** Only if pLimit!=0 or pOffset!=0 do the limit registers get +** Only if pLimit->pLeft!=0 do the limit registers get ** redefined. The UNION ALL operator uses this property to force ** the reuse of the same limit and offset registers across multiple ** SELECT statements. @@ -1900,6 +1898,8 @@ static void computeLimitRegisters(Parse *pParse, Select *p, int iBreak){ int iLimit = 0; int iOffset; int n; + Expr *pLimit = p->pLimit; + if( p->iLimit ) return; /* @@ -1909,12 +1909,13 @@ static void computeLimitRegisters(Parse *pParse, Select *p, int iBreak){ ** no rows. */ sqlite3ExprCacheClear(pParse); - assert( p->pOffset==0 || p->pLimit!=0 ); - if( p->pLimit ){ + if( pLimit ){ + assert( pLimit->op==TK_LIMIT ); + assert( pLimit->pLeft!=0 ); p->iLimit = iLimit = ++pParse->nMem; v = sqlite3GetVdbe(pParse); assert( v!=0 ); - if( sqlite3ExprIsInteger(p->pLimit, &n) ){ + if( sqlite3ExprIsInteger(pLimit->pLeft, &n) ){ sqlite3VdbeAddOp2(v, OP_Integer, n, iLimit); VdbeComment((v, "LIMIT counter")); if( n==0 ){ @@ -1924,15 +1925,15 @@ static void computeLimitRegisters(Parse *pParse, Select *p, int iBreak){ p->selFlags |= SF_FixedLimit; } }else{ - sqlite3ExprCode(pParse, p->pLimit, iLimit); + sqlite3ExprCode(pParse, pLimit->pLeft, iLimit); sqlite3VdbeAddOp1(v, OP_MustBeInt, iLimit); VdbeCoverage(v); VdbeComment((v, "LIMIT counter")); sqlite3VdbeAddOp2(v, OP_IfNot, iLimit, iBreak); VdbeCoverage(v); } - if( p->pOffset ){ + if( pLimit->pRight ){ p->iOffset = iOffset = ++pParse->nMem; pParse->nMem++; /* Allocate an extra register for limit+offset */ - sqlite3ExprCode(pParse, p->pOffset, iOffset); + sqlite3ExprCode(pParse, pLimit->pRight, iOffset); sqlite3VdbeAddOp1(v, OP_MustBeInt, iOffset); VdbeCoverage(v); VdbeComment((v, "OFFSET counter")); sqlite3VdbeAddOp3(v, OP_OffsetLimit, iLimit, iOffset+1, iOffset); @@ -2062,7 +2063,7 @@ static void generateWithRecursiveQuery( int i; /* Loop counter */ int rc; /* Result code */ ExprList *pOrderBy; /* The ORDER BY clause */ - Expr *pLimit, *pOffset; /* Saved LIMIT and OFFSET */ + Expr *pLimit; /* Saved LIMIT and OFFSET */ int regLimit, regOffset; /* Registers used by LIMIT and OFFSET */ /* Obtain authorization to do a recursive query */ @@ -2073,10 +2074,9 @@ static void generateWithRecursiveQuery( p->nSelectRow = 320; /* 4 billion rows */ computeLimitRegisters(pParse, p, addrBreak); pLimit = p->pLimit; - pOffset = p->pOffset; regLimit = p->iLimit; regOffset = p->iOffset; - p->pLimit = p->pOffset = 0; + p->pLimit = 0; p->iLimit = p->iOffset = 0; pOrderBy = p->pOrderBy; @@ -2169,7 +2169,6 @@ end_of_recursive_query: sqlite3ExprListDelete(pParse->db, p->pOrderBy); p->pOrderBy = pOrderBy; p->pLimit = pLimit; - p->pOffset = pOffset; return; } #endif /* SQLITE_OMIT_CTE */ @@ -2188,9 +2187,14 @@ static int multiSelectOrderBy( ** on a VALUES clause. ** ** Because the Select object originates from a VALUES clause: -** (1) It has no LIMIT or OFFSET +** (1) There is no LIMIT or OFFSET or else there is a LIMIT of exactly 1 ** (2) All terms are UNION ALL ** (3) There is no ORDER BY clause +** +** The "LIMIT of exactly 1" case of condition (1) comes about when a VALUES +** clause occurs within scalar expression (ex: "SELECT (VALUES(1),(2),(3))"). +** The sqlite3CodeSubselect will have added the LIMIT 1 clause in tht case. +** Since the limit is exactly 1, we only need to evalutes the left-most VALUES. */ static int multiSelectValues( Parse *pParse, /* Parsing context */ @@ -2198,14 +2202,13 @@ static int multiSelectValues( SelectDest *pDest /* What to do with query results */ ){ Select *pPrior; + Select *pRightmost = p; int nRow = 1; int rc = 0; assert( p->selFlags & SF_MultiValue ); do{ assert( p->selFlags & SF_Values ); assert( p->op==TK_ALL || (p->op==TK_SELECT && p->pPrior==0) ); - assert( p->pLimit==0 ); - assert( p->pOffset==0 ); assert( p->pNext==0 || p->pEList->nExpr==p->pNext->pEList->nExpr ); if( p->pPrior==0 ) break; assert( p->pPrior->pNext==p ); @@ -2217,7 +2220,7 @@ static int multiSelectValues( p->pPrior = 0; rc = sqlite3Select(pParse, p, pDest); p->pPrior = pPrior; - if( rc ) break; + if( rc || pRightmost->pLimit ) break; p->nSelectRow = nRow; p = p->pNext; } @@ -2332,11 +2335,9 @@ static int multiSelect( pPrior->iLimit = p->iLimit; pPrior->iOffset = p->iOffset; pPrior->pLimit = p->pLimit; - pPrior->pOffset = p->pOffset; explainSetInteger(iSub1, pParse->iNextSelectId); rc = sqlite3Select(pParse, pPrior, &dest); p->pLimit = 0; - p->pOffset = 0; if( rc ){ goto multi_select_end; } @@ -2358,7 +2359,7 @@ static int multiSelect( p->pPrior = pPrior; p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); if( pPrior->pLimit - && sqlite3ExprIsInteger(pPrior->pLimit, &nLimit) + && sqlite3ExprIsInteger(pPrior->pLimit->pLeft, &nLimit) && nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit) ){ p->nSelectRow = sqlite3LogEst((u64)nLimit); @@ -2373,7 +2374,7 @@ static int multiSelect( int unionTab; /* Cursor number of the temporary table holding result */ u8 op = 0; /* One of the SRT_ operations to apply to self */ int priorOp; /* The SRT_ operation to apply to prior selects */ - Expr *pLimit, *pOffset; /* Saved values of p->nLimit and p->nOffset */ + Expr *pLimit; /* Saved values of p->nLimit */ int addr; SelectDest uniondest; @@ -2385,7 +2386,6 @@ static int multiSelect( ** right. */ assert( p->pLimit==0 ); /* Not allowed on leftward elements */ - assert( p->pOffset==0 ); /* Not allowed on leftward elements */ unionTab = dest.iSDParm; }else{ /* We will need to create our own temporary table to hold the @@ -2421,8 +2421,6 @@ static int multiSelect( p->pPrior = 0; pLimit = p->pLimit; p->pLimit = 0; - pOffset = p->pOffset; - p->pOffset = 0; uniondest.eDest = op; explainSetInteger(iSub2, pParse->iNextSelectId); rc = sqlite3Select(pParse, p, &uniondest); @@ -2438,7 +2436,6 @@ static int multiSelect( } sqlite3ExprDelete(db, p->pLimit); p->pLimit = pLimit; - p->pOffset = pOffset; p->iLimit = 0; p->iOffset = 0; @@ -2466,7 +2463,7 @@ static int multiSelect( default: assert( p->op==TK_INTERSECT ); { int tab1, tab2; int iCont, iBreak, iStart; - Expr *pLimit, *pOffset; + Expr *pLimit; int addr; SelectDest intersectdest; int r1; @@ -2502,8 +2499,6 @@ static int multiSelect( p->pPrior = 0; pLimit = p->pLimit; p->pLimit = 0; - pOffset = p->pOffset; - p->pOffset = 0; intersectdest.iSDParm = tab2; explainSetInteger(iSub2, pParse->iNextSelectId); rc = sqlite3Select(pParse, p, &intersectdest); @@ -2513,7 +2508,6 @@ static int multiSelect( if( p->nSelectRow>pPrior->nSelectRow ) p->nSelectRow = pPrior->nSelectRow; sqlite3ExprDelete(db, p->pLimit); p->pLimit = pLimit; - p->pOffset = pOffset; /* Generate code to take the intersection of the two temporary ** tables. @@ -2992,8 +2986,6 @@ static int multiSelectOrderBy( } sqlite3ExprDelete(db, p->pLimit); p->pLimit = 0; - sqlite3ExprDelete(db, p->pOffset); - p->pOffset = 0; regAddrA = ++pParse->nMem; regAddrB = ++pParse->nMem; @@ -3457,7 +3449,7 @@ static int flattenSubquery( ** became arbitrary expressions, we were forced to add restrictions (13) ** and (14). */ if( pSub->pLimit && p->pLimit ) return 0; /* Restriction (13) */ - if( pSub->pOffset ) return 0; /* Restriction (14) */ + if( pSub->pLimit && pSub->pLimit->pRight ) return 0; /* Restriction (14) */ if( (p->selFlags & SF_Compound)!=0 && pSub->pLimit ){ return 0; /* Restriction (15) */ } @@ -3605,16 +3597,13 @@ static int flattenSubquery( Select *pNew; ExprList *pOrderBy = p->pOrderBy; Expr *pLimit = p->pLimit; - Expr *pOffset = p->pOffset; Select *pPrior = p->pPrior; p->pOrderBy = 0; p->pSrc = 0; p->pPrior = 0; p->pLimit = 0; - p->pOffset = 0; pNew = sqlite3SelectDup(db, p, 0); sqlite3SelectSetName(pNew, pSub->zSelName); - p->pOffset = pOffset; p->pLimit = pLimit; p->pOrderBy = pOrderBy; p->pSrc = pSrc; @@ -4080,7 +4069,6 @@ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){ assert( pNew->pPrior!=0 ); pNew->pPrior->pNext = pNew; pNew->pLimit = 0; - pNew->pOffset = 0; return WRC_Continue; } diff --git a/src/shell.c.in b/src/shell.c.in index e85ebd3c93..121a6c3506 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -61,6 +61,9 @@ #include #include #include "sqlite3.h" +typedef sqlite3_int64 i64; +typedef sqlite3_uint64 u64; +typedef unsigned char u8; #if SQLITE_USER_AUTHENTICATION # include "sqlite3userauth.h" #endif @@ -72,9 +75,19 @@ # if !defined(__RTP__) && !defined(_WRS_KERNEL) # include # endif -# include -# include #endif +#if (!defined(_WIN32) && !defined(WIN32)) || defined(__MINGW32__) +# include +# include +# if defined(__MINGW32__) +# define DIRENT dirent +# ifndef S_ISLNK +# define S_ISLNK(mode) (0) +# endif +# endif +#endif +#include +#include #if HAVE_READLINE # include @@ -339,6 +352,11 @@ static void endTimer(void){ */ #define UNUSED_PARAMETER(x) (void)(x) +/* +** Number of elements in an array +*/ +#define ArraySize(X) (int)(sizeof(X)/sizeof(X[0])) + /* ** If the following flag is set, then command execution stops ** at an error if we are not interactive. @@ -611,6 +629,65 @@ static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ } return zResult; } + + +/* +** 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 sqlite3_int64 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; ipLog); } +/* +** SQL function: shell_putsnl(X) +** +** Write the text X to the screen (or whatever output is being directed) +** adding a newline at the end, and then return X. +*/ +static void shellPutsFunc( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + ShellState *p = (ShellState*)sqlite3_user_data(pCtx); + (void)nVal; + utf8_printf(p->out, "%s\n", sqlite3_value_text(apVal[0])); + sqlite3_result_value(pCtx, apVal[0]); +} + +/* +** SQL function: edit(VALUE) +** edit(VALUE,EDITOR) +** +** These steps: +** +** (1) Write VALUE into a temporary file. +** (2) Run program EDITOR on that temporary file. +** (3) Read the temporary file back and return its content as the result. +** (4) Delete the temporary file +** +** If the EDITOR argument is omitted, use the value in the VISUAL +** environment variable. If still there is no EDITOR, through an error. +** +** Also throw an error if the EDITOR program returns a non-zero exit code. +*/ +#ifndef SQLITE_NOHAVE_SYSTEM +static void editFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zEditor; + char *zTempFile = 0; + sqlite3 *db; + char *zCmd = 0; + int bBin; + int rc; + FILE *f = 0; + sqlite3_int64 sz; + sqlite3_int64 x; + unsigned char *p = 0; + + if( argc==2 ){ + zEditor = (const char*)sqlite3_value_text(argv[1]); + }else{ + zEditor = getenv("VISUAL"); + } + if( zEditor==0 ){ + sqlite3_result_error(context, "no editor for edit()", -1); + return; + } + if( sqlite3_value_type(argv[0])==SQLITE_NULL ){ + sqlite3_result_error(context, "NULL input to edit()", -1); + return; + } + db = sqlite3_context_db_handle(context); + zTempFile = 0; + sqlite3_file_control(db, 0, SQLITE_FCNTL_TEMPFILENAME, &zTempFile); + if( zTempFile==0 ){ + sqlite3_uint64 r = 0; + sqlite3_randomness(sizeof(r), &r); + zTempFile = sqlite3_mprintf("temp%llx", r); + if( zTempFile==0 ){ + sqlite3_result_error_nomem(context); + return; + } + } + bBin = sqlite3_value_type(argv[0])==SQLITE_BLOB; + f = fopen(zTempFile, bBin ? "wb" : "w"); + if( f==0 ){ + sqlite3_result_error(context, "edit() cannot open temp file", -1); + goto edit_func_end; + } + sz = sqlite3_value_bytes(argv[0]); + if( bBin ){ + x = fwrite(sqlite3_value_blob(argv[0]), 1, sz, f); + }else{ + x = fwrite(sqlite3_value_text(argv[0]), 1, sz, f); + } + fclose(f); + f = 0; + if( x!=sz ){ + sqlite3_result_error(context, "edit() could not write the whole file", -1); + goto edit_func_end; + } + zCmd = sqlite3_mprintf("%s \"%s\"", zEditor, zTempFile); + if( zCmd==0 ){ + sqlite3_result_error_nomem(context); + goto edit_func_end; + } + rc = system(zCmd); + sqlite3_free(zCmd); + if( rc ){ + sqlite3_result_error(context, "EDITOR returned non-zero", -1); + goto edit_func_end; + } + f = fopen(zTempFile, bBin ? "rb" : "r"); + if( f==0 ){ + sqlite3_result_error(context, + "edit() cannot reopen temp file after edit", -1); + goto edit_func_end; + } + fseek(f, 0, SEEK_END); + sz = ftell(f); + rewind(f); + p = sqlite3_malloc64( sz+(bBin==0) ); + if( p==0 ){ + sqlite3_result_error_nomem(context); + goto edit_func_end; + } + if( bBin ){ + x = fread(p, 1, sz, f); + }else{ + x = fread(p, 1, sz, f); + p[sz] = 0; + } + fclose(f); + f = 0; + if( x!=sz ){ + sqlite3_result_error(context, "could not read back the whole file", -1); + goto edit_func_end; + } + if( bBin ){ + sqlite3_result_blob64(context, p, sz, sqlite3_free); + }else{ + sqlite3_result_text64(context, (const char*)p, sz, + sqlite3_free, SQLITE_UTF8); + } + p = 0; + +edit_func_end: + if( f ) fclose(f); + unlink(zTempFile); + sqlite3_free(zTempFile); + sqlite3_free(p); +} +#endif /* SQLITE_NOHAVE_SYSTEM */ + +/* +** Save or restore the current output mode +*/ +static void outputModePush(ShellState *p){ + p->modePrior = p->mode; + memcpy(p->colSepPrior, p->colSeparator, sizeof(p->colSeparator)); + memcpy(p->rowSepPrior, p->rowSeparator, sizeof(p->rowSeparator)); +} +static void outputModePop(ShellState *p){ + p->mode = p->modePrior; + memcpy(p->colSeparator, p->colSepPrior, sizeof(p->colSeparator)); + memcpy(p->rowSeparator, p->rowSepPrior, sizeof(p->rowSeparator)); +} + /* ** Output the given string as a hex-encoded blob (eg. X'1234' ) */ @@ -1190,12 +1548,9 @@ static void output_csv(ShellState *p, const char *z, int bSep){ } } if( i==0 ){ - putc('"', out); - for(i=0; z[i]; i++){ - if( z[i]=='"' ) putc('"', out); - putc(z[i], out); - } - putc('"', out); + char *zQuoted = sqlite3_mprintf("\"%w\"", z); + utf8_printf(out, "%s", zQuoted); + sqlite3_free(zQuoted); }else{ utf8_printf(out, "%s", z); } @@ -1297,6 +1652,22 @@ static void printSchemaLineN(FILE *out, char *z, int n, const char *zTail){ z[n] = c; } +/* +** Return true if string z[] has nothing but whitespace and comments to the +** end of the first line. +*/ +static int wsToEol(const char *z){ + int i; + for(i=0; z[i]; i++){ + if( z[i]=='\n' ) return 1; + if( IsSpace(z[i]) ) continue; + if( z[i]=='-' && z[i+1]=='-' ) return 1; + return 0; + } + return 1; +} + + /* ** This is the callback routine that the shell ** invokes for each row of a query result. @@ -1436,13 +1807,15 @@ static int shell_callback( while( j>0 && IsSpace(z[j-1]) ){ j--; } z[j] = 0; if( strlen30(z)>=79 ){ - for(i=j=0; (c = z[i])!=0; i++){ + for(i=j=0; (c = z[i])!=0; i++){ /* Copy changes from z[i] back to z[j] */ if( c==cEnd ){ cEnd = 0; }else if( c=='"' || c=='\'' || c=='`' ){ cEnd = c; }else if( c=='[' ){ cEnd = ']'; + }else if( c=='-' && z[i+1]=='-' ){ + cEnd = '\n'; }else if( c=='(' ){ nParen++; }else if( c==')' ){ @@ -1453,7 +1826,9 @@ static int shell_callback( } } z[j++] = c; - if( nParen==1 && (c=='(' || c==',' || c=='\n') ){ + if( nParen==1 && cEnd==0 + && (c=='(' || c=='\n' || (c==',' && !wsToEol(z+i+1))) + ){ if( c=='\n' ) j--; printSchemaLineN(p->out, z, j, "\n "); j = 0; @@ -1851,7 +2226,7 @@ static void displayLinuxIoStats(FILE *out){ }; int i; for(i=0; iexpert.pExpert ); + assert( pzErr==0 || *pzErr==0 ); + return sqlite3_expert_sql(pState->expert.pExpert, zSql, pzErr); +} + +/* +** This function is called either to silently clean up the object +** created by the ".expert" command (if bCancel==1), or to generate a +** report from it and then clean it up (if bCancel==0). +** +** If successful, SQLITE_OK is returned. Otherwise, an SQLite error +** code. In this case, (*pzErr) may be set to point to a buffer containing +** an English language error message. It is the responsibility of the +** caller to eventually free this buffer using sqlite3_free(). +*/ +static int expertFinish( + ShellState *pState, + int bCancel, + char **pzErr +){ + int rc = SQLITE_OK; + sqlite3expert *p = pState->expert.pExpert; + assert( p ); + assert( bCancel || pzErr==0 || *pzErr==0 ); + if( bCancel==0 ){ + FILE *out = pState->out; + int bVerbose = pState->expert.bVerbose; + + rc = sqlite3_expert_analyze(p, pzErr); + if( rc==SQLITE_OK ){ + int nQuery = sqlite3_expert_count(p); + int i; + + if( bVerbose ){ + const char *zCand = sqlite3_expert_report(p,0,EXPERT_REPORT_CANDIDATES); + raw_printf(out, "-- Candidates -----------------------------\n"); + raw_printf(out, "%s\n", zCand); + } + for(i=0; iexpert.pExpert = 0; + return rc; +} + +/* +** Implementation of ".expert" dot command. +*/ +static int expertDotCommand( + ShellState *pState, /* Current shell tool state */ + char **azArg, /* Array of arguments passed to dot command */ + int nArg /* Number of entries in azArg[] */ +){ + int rc = SQLITE_OK; + char *zErr = 0; + int i; + int iSample = 0; + + assert( pState->expert.pExpert==0 ); + memset(&pState->expert, 0, sizeof(ExpertInfo)); + + for(i=1; rc==SQLITE_OK && i=2 && 0==strncmp(z, "-verbose", n) ){ + pState->expert.bVerbose = 1; + } + else if( n>=2 && 0==strncmp(z, "-sample", n) ){ + if( i==(nArg-1) ){ + raw_printf(stderr, "option requires an argument: %s\n", z); + rc = SQLITE_ERROR; + }else{ + iSample = (int)integerValue(azArg[++i]); + if( iSample<0 || iSample>100 ){ + raw_printf(stderr, "value out of range: %s\n", azArg[i]); + rc = SQLITE_ERROR; + } + } + } + else{ + raw_printf(stderr, "unknown option: %s\n", z); + rc = SQLITE_ERROR; + } + } + + if( rc==SQLITE_OK ){ + pState->expert.pExpert = sqlite3_expert_new(pState->db, &zErr); + if( pState->expert.pExpert==0 ){ + raw_printf(stderr, "sqlite3_expert_new: %s\n", zErr); + rc = SQLITE_ERROR; + }else{ + sqlite3_expert_config( + pState->expert.pExpert, EXPERT_CONFIG_SAMPLE, iSample + ); + } + } + + return rc; +} +#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ + /* ** Execute a statement or set of statements. Print ** any result rows/columns depending on the current mode @@ -2278,6 +2784,13 @@ static int shell_exec( *pzErrMsg = NULL; } +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( pArg->expert.pExpert ){ + rc = expertHandleSQL(pArg, zSql, pzErrMsg); + return expertFinish(pArg, (rc!=SQLITE_OK), pzErrMsg); + } +#endif + while( zSql[0] && (SQLITE_OK == rc) ){ static const char *zStmtSql; rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover); @@ -2311,7 +2824,12 @@ static int shell_exec( if( pArg && pArg->autoEQP && sqlite3_strlike("EXPLAIN%",zStmtSql,0)!=0 ){ sqlite3_stmt *pExplain; char *zEQP; + int triggerEQP = 0; disable_debug_trace_modes(); + sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, -1, &triggerEQP); + if( pArg->autoEQP>=AUTOEQP_trigger ){ + sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 1, 0); + } zEQP = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", zStmtSql); rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0); if( rc==SQLITE_OK ){ @@ -2324,7 +2842,7 @@ static int shell_exec( } sqlite3_finalize(pExplain); sqlite3_free(zEQP); - if( pArg->autoEQP>=2 ){ + if( pArg->autoEQP>=AUTOEQP_full ){ /* Also do an EXPLAIN for ".eqp full" mode */ zEQP = sqlite3_mprintf("EXPLAIN %s", zStmtSql); rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0); @@ -2337,6 +2855,7 @@ static int shell_exec( sqlite3_finalize(pExplain); sqlite3_free(zEQP); } + sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, triggerEQP, 0); restore_debug_trace_modes(); } @@ -2677,6 +3196,9 @@ static int run_schema_dump_query( ** Text of a help message */ static char zHelp[] = +#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) + ".archive ... Manage SQL archives: \".archive --help\" for details\n" +#endif #ifndef SQLITE_OMIT_AUTHORIZATION ".auth ON|OFF Show authorizer callbacks\n" #endif @@ -2694,7 +3216,9 @@ static char zHelp[] = " LIKE pattern TABLE.\n" ".echo on|off Turn command echo on or off\n" ".eqp on|off|full Enable or disable automatic EXPLAIN QUERY PLAN\n" + ".excel Display the output of next command in a spreadsheet\n" ".exit Exit this program\n" + ".expert EXPERIMENTAL. Suggest indexes for specified queries\n" /* Because explain mode comes on automatically now, the ".explain" mode ** is removed from the help screen. It is still supported for legacy, however */ /*".explain ?on|off|auto? Turn EXPLAIN output mode on or off or to automatic\n"*/ @@ -2730,10 +3254,13 @@ static char zHelp[] = " 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" + ".once (-e|-x|FILE) Output for the next SQL command only to FILE\n" + " or invoke system text editor (-e) or spreadsheet (-x)\n" + " on the output.\n" ".open ?OPTIONS? ?FILE? Close existing database and reopen FILE\n" " The --new option starts with an empty file\n" - ".output ?FILENAME? Send output to FILENAME or stdout\n" + " Other options: --readonly --append --zip\n" + ".output ?FILE? Send output to FILE or stdout\n" ".print STRING... Print literal STRING\n" ".prompt MAIN CONTINUE Replace the standard prompts\n" ".quit Exit this program\n" @@ -2750,10 +3277,14 @@ static char zHelp[] = ".session CMD ... Create or control sessions\n" #endif ".sha3sum ?OPTIONS...? Compute a SHA3 hash of database content\n" +#ifndef SQLITE_NOHAVE_SYSTEM ".shell CMD ARGS... Run CMD ARGS... in a system shell\n" +#endif ".show Show the current values for various settings\n" ".stats ?on|off? Show stats or turn stats on or off\n" +#ifndef SQLITE_NOHAVE_SYSTEM ".system CMD ARGS... Run CMD ARGS... in a system shell\n" +#endif ".tables ?TABLE? List names of tables\n" " If TABLE specified, only list tables matching\n" " LIKE pattern TABLE.\n" @@ -2879,6 +3410,32 @@ static int session_filter(void *pCtx, const char *zTab){ } #endif +/* +** Try to deduce the type of file for zName based on its content. Return +** one of the SHELL_OPEN_* constants. +*/ +static int deduceDatabaseType(const char *zName){ + FILE *f = fopen(zName, "rb"); + size_t n; + int rc = SHELL_OPEN_UNSPEC; + char zBuf[100]; + if( f==0 ) return SHELL_OPEN_NORMAL; + fseek(f, -25, SEEK_END); + n = fread(zBuf, 25, 1, f); + if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){ + rc = SHELL_OPEN_APPENDVFS; + }else{ + fseek(f, -22, SEEK_END); + n = fread(zBuf, 22, 1, f); + if( n==1 && zBuf[0]==0x50 && zBuf[1]==0x4b && zBuf[2]==0x05 + && zBuf[3]==0x06 ){ + rc = SHELL_OPEN_ZIPFILE; + } + } + fclose(f); + return rc; +} + /* ** Make sure the database is open. If it is not, then open it. If ** the database fails to open, print an error message and exit. @@ -2886,7 +3443,29 @@ static int session_filter(void *pCtx, const char *zTab){ static void open_db(ShellState *p, int keepAlive){ if( p->db==0 ){ sqlite3_initialize(); - sqlite3_open(p->zDbFilename, &p->db); + if( p->openMode==SHELL_OPEN_UNSPEC && access(p->zDbFilename,0)==0 ){ + p->openMode = (u8)deduceDatabaseType(p->zDbFilename); + } + switch( p->openMode ){ + case SHELL_OPEN_APPENDVFS: { + sqlite3_open_v2(p->zDbFilename, &p->db, + SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, "apndvfs"); + break; + } + case SHELL_OPEN_ZIPFILE: { + sqlite3_open(":memory:", &p->db); + break; + } + case SHELL_OPEN_READONLY: { + sqlite3_open_v2(p->zDbFilename, &p->db, SQLITE_OPEN_READONLY, 0); + break; + } + case SHELL_OPEN_UNSPEC: + case SHELL_OPEN_NORMAL: { + sqlite3_open(p->zDbFilename, &p->db); + break; + } + } globalDb = p->db; if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ utf8_printf(stderr,"Error: unable to open database \"%s\": %s\n", @@ -2900,8 +3479,28 @@ static void open_db(ShellState *p, int keepAlive){ sqlite3_fileio_init(p->db, 0, 0); sqlite3_shathree_init(p->db, 0, 0); sqlite3_completion_init(p->db, 0, 0); - sqlite3_create_function(p->db, "shell_add_schema", 2, SQLITE_UTF8, 0, +#ifdef SQLITE_HAVE_ZLIB + sqlite3_zipfile_init(p->db, 0, 0); + sqlite3_sqlar_init(p->db, 0, 0); +#endif + sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0, shellAddSchemaName, 0, 0); + sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0, + shellModuleSchema, 0, 0); + sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p, + shellPutsFunc, 0, 0); +#ifndef SQLITE_NOHAVE_SYSTEM + sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0, + editFunc, 0, 0); + sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0, + editFunc, 0, 0); +#endif + if( p->openMode==SHELL_OPEN_ZIPFILE ){ + char *zSql = sqlite3_mprintf( + "CREATE VIRTUAL TABLE zip USING zipfile(%Q);", p->zDbFilename); + sqlite3_exec(p->db, zSql, 0, 0, 0); + sqlite3_free(zSql); + } } } @@ -2939,7 +3538,7 @@ static char **readline_completion(const char *zText, int iStart, int iEnd){ ** Linenoise completion callback */ static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){ - int nLine = (int)strlen(zLine); + int nLine = strlen30(zLine); int i, iStart; sqlite3_stmt *pStmt = 0; char *zSql; @@ -3029,63 +3628,6 @@ static void resolve_backslashes(char *z){ if( j='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 sqlite3_int64 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; i0 && z[i-1]==';' ){ i--; } utf8_printf(f, "%.*s;\n", i, z); } @@ -3354,7 +3896,7 @@ static void tryToCloneData( char *zInsert = 0; int rc; int i, j, n; - int nTable = (int)strlen(zTable); + int nTable = strlen30(zTable); int k = 0; int cnt = 0; const int spinRate = 10000; @@ -3375,7 +3917,7 @@ static void tryToCloneData( } sqlite3_snprintf(200+nTable,zInsert, "INSERT OR IGNORE INTO \"%s\" VALUES(?", zTable); - i = (int)strlen(zInsert); + i = strlen30(zInsert); for(j=1; jdoXdgOpen flag is set, that means the output was being +** redirected to a temporary file named by p->zTempFile. In that case, +** launch start/open/xdg-open on that temporary file. */ static void output_reset(ShellState *p){ if( p->outfile[0]=='|' ){ @@ -3563,6 +4109,26 @@ static void output_reset(ShellState *p){ #endif }else{ output_file_close(p->out); +#ifndef SQLITE_NOHAVE_SYSTEM + if( p->doXdgOpen ){ + const char *zXdgOpenCmd = +#if defined(_WIN32) + "start"; +#elif defined(__APPLE__) + "open"; +#else + "xdg-open"; +#endif + char *zCmd; + zCmd = sqlite3_mprintf("%s %s", zXdgOpenCmd, p->zTempFile); + if( system(zCmd) ){ + utf8_printf(stderr, "Failed: [%s]\n", zCmd); + } + sqlite3_free(zCmd); + outputModePop(p); + p->doXdgOpen = 0; + } +#endif /* !defined(SQLITE_NOHAVE_SYSTEM) */ } p->outfile[0] = 0; p->out = stdout; @@ -3821,6 +4387,41 @@ int shellDeleteFile(const char *zFilename){ return rc; } +/* +** Try to delete the temporary file (if there is one) and free the +** memory used to hold the name of the temp file. +*/ +static void clearTempFile(ShellState *p){ + if( p->zTempFile==0 ) return; + if( p->doXdgOpen ) return; + if( shellDeleteFile(p->zTempFile) ) return; + sqlite3_free(p->zTempFile); + p->zTempFile = 0; +} + +/* +** Create a new temp file name with the given suffix. +*/ +static void newTempFile(ShellState *p, const char *zSuffix){ + clearTempFile(p); + sqlite3_free(p->zTempFile); + p->zTempFile = 0; + if( p->db ){ + sqlite3_file_control(p->db, 0, SQLITE_FCNTL_TEMPFILENAME, &p->zTempFile); + } + if( p->zTempFile==0 ){ + sqlite3_uint64 r; + sqlite3_randomness(sizeof(r), &r); + p->zTempFile = sqlite3_mprintf("temp%llx.%s", r, zSuffix); + }else{ + p->zTempFile = sqlite3_mprintf("%z.%s", p->zTempFile, zSuffix); + } + if( p->zTempFile==0 ){ + raw_printf(stderr, "out of memory\n"); + exit(1); + } +} + /* ** The implementation of SQL scalar function fkey_collate_clause(), used @@ -3898,10 +4499,10 @@ static int lintFkeyIndexes( ** ** 0. The text of an SQL statement similar to: ** - ** "EXPLAIN QUERY PLAN SELECT rowid FROM child_table WHERE child_key=?" + ** "EXPLAIN QUERY PLAN SELECT 1 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 + ** This SELECT is similar to the one 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. @@ -3929,7 +4530,7 @@ static int lintFkeyIndexes( */ const char *zSql = "SELECT " - " 'EXPLAIN QUERY PLAN SELECT rowid FROM ' || quote(s.name) || ' WHERE '" + " 'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '" " || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' " " || fkey_collate_clause(" " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')" @@ -3957,7 +4558,7 @@ static int lintFkeyIndexes( const char *zGlobIPK = "SEARCH TABLE * USING INTEGER PRIMARY KEY (rowid=?)"; for(i=2; i1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){ bVerbose = 1; } @@ -4060,7 +4661,7 @@ static int lintDotCommand( int nArg /* Number of entries in azArg[] */ ){ int n; - n = (nArg>=2 ? (int)strlen(azArg[1]) : 0); + n = (nArg>=2 ? strlen30(azArg[1]) : 0); if( n<1 || sqlite3_strnicmp(azArg[1], "fkey-indexes", n) ) goto usage; return lintFkeyIndexes(pState, azArg, nArg); @@ -4071,6 +4672,719 @@ static int lintDotCommand( return SQLITE_ERROR; } +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) +/********************************************************************************* +** The ".archive" or ".ar" command. +*/ +static void shellPrepare( + sqlite3 *db, + int *pRc, + const char *zSql, + sqlite3_stmt **ppStmt +){ + *ppStmt = 0; + if( *pRc==SQLITE_OK ){ + int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0); + if( rc!=SQLITE_OK ){ + raw_printf(stderr, "sql error: %s (%d)\n", + sqlite3_errmsg(db), sqlite3_errcode(db) + ); + *pRc = rc; + } + } +} + +static void shellPreparePrintf( + sqlite3 *db, + int *pRc, + sqlite3_stmt **ppStmt, + const char *zFmt, + ... +){ + *ppStmt = 0; + if( *pRc==SQLITE_OK ){ + va_list ap; + char *z; + va_start(ap, zFmt); + z = sqlite3_vmprintf(zFmt, ap); + if( z==0 ){ + *pRc = SQLITE_NOMEM; + }else{ + shellPrepare(db, pRc, z, ppStmt); + sqlite3_free(z); + } + } +} + +static void shellFinalize( + int *pRc, + sqlite3_stmt *pStmt +){ + if( pStmt ){ + sqlite3 *db = sqlite3_db_handle(pStmt); + int rc = sqlite3_finalize(pStmt); + if( *pRc==SQLITE_OK ){ + if( rc!=SQLITE_OK ){ + raw_printf(stderr, "SQL error: %s\n", sqlite3_errmsg(db)); + } + *pRc = rc; + } + } +} + +static void shellReset( + int *pRc, + sqlite3_stmt *pStmt +){ + int rc = sqlite3_reset(pStmt); + if( *pRc==SQLITE_OK ){ + if( rc!=SQLITE_OK ){ + sqlite3 *db = sqlite3_db_handle(pStmt); + raw_printf(stderr, "SQL error: %s\n", sqlite3_errmsg(db)); + } + *pRc = rc; + } +} +/* +** Structure representing a single ".ar" command. +*/ +typedef struct ArCommand ArCommand; +struct ArCommand { + u8 eCmd; /* An AR_CMD_* value */ + u8 bVerbose; /* True if --verbose */ + u8 bZip; /* True if the archive is a ZIP */ + u8 bDryRun; /* True if --dry-run */ + u8 bAppend; /* True if --append */ + int nArg; /* Number of command arguments */ + char *zSrcTable; /* "sqlar", "zipfile($file)" or "zip" */ + const char *zFile; /* --file argument, or NULL */ + const char *zDir; /* --directory argument, or NULL */ + char **azArg; /* Array of command arguments */ + ShellState *p; /* Shell state */ + sqlite3 *db; /* Database containing the archive */ +}; + +/* +** Print a usage message for the .ar command to stderr and return SQLITE_ERROR. +*/ +static int arUsage(FILE *f){ + raw_printf(f, +"\n" +"Usage: .ar [OPTION...] [FILE...]\n" +"The .ar command manages sqlar archives.\n" +"\n" +"Examples:\n" +" .ar -cf archive.sar foo bar # Create archive.sar from files foo and bar\n" +" .ar -tf archive.sar # List members of archive.sar\n" +" .ar -xvf archive.sar # Verbosely extract files from archive.sar\n" +"\n" +"Each command line must feature exactly one command option:\n" +" -c, --create Create a new archive\n" +" -u, --update Update or add files to an existing archive\n" +" -t, --list List contents of archive\n" +" -x, --extract Extract files from archive\n" +"\n" +"And zero or more optional options:\n" +" -v, --verbose Print each filename as it is processed\n" +" -f FILE, --file FILE Operate on archive FILE (default is current db)\n" +" -a FILE, --append FILE Operate on FILE opened using the apndvfs VFS\n" +" -C DIR, --directory DIR Change to directory DIR to read/extract files\n" +" -n, --dryrun Show the SQL that would have occurred\n" +"\n" +"See also: http://sqlite.org/cli.html#sqlar_archive_support\n" +"\n" +); + return SQLITE_ERROR; +} + +/* +** Print an error message for the .ar command to stderr and return +** SQLITE_ERROR. +*/ +static int arErrorMsg(const char *zFmt, ...){ + va_list ap; + char *z; + va_start(ap, zFmt); + z = sqlite3_vmprintf(zFmt, ap); + va_end(ap); + raw_printf(stderr, "Error: %s (try \".ar --help\")\n", z); + sqlite3_free(z); + return SQLITE_ERROR; +} + +/* +** Values for ArCommand.eCmd. +*/ +#define AR_CMD_CREATE 1 +#define AR_CMD_EXTRACT 2 +#define AR_CMD_LIST 3 +#define AR_CMD_UPDATE 4 +#define AR_CMD_HELP 5 + +/* +** Other (non-command) switches. +*/ +#define AR_SWITCH_VERBOSE 6 +#define AR_SWITCH_FILE 7 +#define AR_SWITCH_DIRECTORY 8 +#define AR_SWITCH_APPEND 9 +#define AR_SWITCH_DRYRUN 10 + +static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){ + switch( eSwitch ){ + case AR_CMD_CREATE: + case AR_CMD_EXTRACT: + case AR_CMD_LIST: + case AR_CMD_UPDATE: + case AR_CMD_HELP: + if( pAr->eCmd ){ + return arErrorMsg("multiple command options"); + } + pAr->eCmd = eSwitch; + break; + + case AR_SWITCH_DRYRUN: + pAr->bDryRun = 1; + break; + case AR_SWITCH_VERBOSE: + pAr->bVerbose = 1; + break; + case AR_SWITCH_APPEND: + pAr->bAppend = 1; + /* Fall thru into --file */ + case AR_SWITCH_FILE: + pAr->zFile = zArg; + break; + case AR_SWITCH_DIRECTORY: + pAr->zDir = zArg; + break; + } + + return SQLITE_OK; +} + +/* +** Parse the command line for an ".ar" command. The results are written into +** structure (*pAr). SQLITE_OK is returned if the command line is parsed +** successfully, otherwise an error message is written to stderr and +** SQLITE_ERROR returned. +*/ +static int arParseCommand( + char **azArg, /* Array of arguments passed to dot command */ + int nArg, /* Number of entries in azArg[] */ + ArCommand *pAr /* Populate this object */ +){ + struct ArSwitch { + const char *zLong; + char cShort; + u8 eSwitch; + u8 bArg; + } aSwitch[] = { + { "create", 'c', AR_CMD_CREATE, 0 }, + { "extract", 'x', AR_CMD_EXTRACT, 0 }, + { "list", 't', AR_CMD_LIST, 0 }, + { "update", 'u', AR_CMD_UPDATE, 0 }, + { "help", 'h', AR_CMD_HELP, 0 }, + { "verbose", 'v', AR_SWITCH_VERBOSE, 0 }, + { "file", 'f', AR_SWITCH_FILE, 1 }, + { "append", 'a', AR_SWITCH_APPEND, 1 }, + { "directory", 'C', AR_SWITCH_DIRECTORY, 1 }, + { "dryrun", 'n', AR_SWITCH_DRYRUN, 0 }, + }; + int nSwitch = sizeof(aSwitch) / sizeof(struct ArSwitch); + struct ArSwitch *pEnd = &aSwitch[nSwitch]; + + if( nArg<=1 ){ + return arUsage(stderr); + }else{ + char *z = azArg[1]; + memset(pAr, 0, sizeof(ArCommand)); + + if( z[0]!='-' ){ + /* Traditional style [tar] invocation */ + int i; + int iArg = 2; + for(i=0; z[i]; i++){ + const char *zArg = 0; + struct ArSwitch *pOpt; + for(pOpt=&aSwitch[0]; pOptcShort ) break; + } + if( pOpt==pEnd ){ + return arErrorMsg("unrecognized option: %c", z[i]); + } + if( pOpt->bArg ){ + if( iArg>=nArg ){ + return arErrorMsg("option requires an argument: %c",z[i]); + } + zArg = azArg[iArg++]; + } + if( arProcessSwitch(pAr, pOpt->eSwitch, zArg) ) return SQLITE_ERROR; + } + pAr->nArg = nArg-iArg; + if( pAr->nArg>0 ){ + pAr->azArg = &azArg[iArg]; + } + }else{ + /* Non-traditional invocation */ + int iArg; + for(iArg=1; iArgazArg = &azArg[iArg]; + pAr->nArg = nArg-iArg; + break; + } + n = strlen30(z); + + if( z[1]!='-' ){ + int i; + /* One or more short options */ + for(i=1; icShort ) break; + } + if( pOpt==pEnd ){ + return arErrorMsg("unrecognized option: %c\n", z[i]); + } + if( pOpt->bArg ){ + if( i<(n-1) ){ + zArg = &z[i+1]; + i = n; + }else{ + if( iArg>=(nArg-1) ){ + return arErrorMsg("option requires an argument: %c\n",z[i]); + } + zArg = azArg[++iArg]; + } + } + if( arProcessSwitch(pAr, pOpt->eSwitch, zArg) ) return SQLITE_ERROR; + } + }else if( z[2]=='\0' ){ + /* A -- option, indicating that all remaining command line words + ** are command arguments. */ + pAr->azArg = &azArg[iArg+1]; + pAr->nArg = nArg-iArg-1; + break; + }else{ + /* A long option */ + const char *zArg = 0; /* Argument for option, if any */ + struct ArSwitch *pMatch = 0; /* Matching option */ + struct ArSwitch *pOpt; /* Iterator */ + for(pOpt=&aSwitch[0]; pOptzLong; + if( (n-2)<=strlen30(zLong) && 0==memcmp(&z[2], zLong, n-2) ){ + if( pMatch ){ + return arErrorMsg("ambiguous option: %s",z); + }else{ + pMatch = pOpt; + } + } + } + + if( pMatch==0 ){ + return arErrorMsg("unrecognized option: %s", z); + } + if( pMatch->bArg ){ + if( iArg>=(nArg-1) ){ + return arErrorMsg("option requires an argument: %s", z); + } + zArg = azArg[++iArg]; + } + if( arProcessSwitch(pAr, pMatch->eSwitch, zArg) ) return SQLITE_ERROR; + } + } + } + } + + return SQLITE_OK; +} + +/* +** This function assumes that all arguments within the ArCommand.azArg[] +** array refer to archive members, as for the --extract or --list commands. +** It checks that each of them are present. If any specified file is not +** present in the archive, an error is printed to stderr and an error +** code returned. Otherwise, if all specified arguments are present in +** the archive, SQLITE_OK is returned. +** +** This function strips any trailing '/' characters from each argument. +** This is consistent with the way the [tar] command seems to work on +** Linux. +*/ +static int arCheckEntries(ArCommand *pAr){ + int rc = SQLITE_OK; + if( pAr->nArg ){ + int i, j; + sqlite3_stmt *pTest = 0; + + shellPreparePrintf(pAr->db, &rc, &pTest, + "SELECT name FROM %s WHERE name=$name", + pAr->zSrcTable + ); + j = sqlite3_bind_parameter_index(pTest, "$name"); + for(i=0; inArg && rc==SQLITE_OK; i++){ + char *z = pAr->azArg[i]; + int n = strlen30(z); + int bOk = 0; + while( n>0 && z[n-1]=='/' ) n--; + z[n] = '\0'; + sqlite3_bind_text(pTest, j, z, -1, SQLITE_STATIC); + if( SQLITE_ROW==sqlite3_step(pTest) ){ + bOk = 1; + } + shellReset(&rc, pTest); + if( rc==SQLITE_OK && bOk==0 ){ + utf8_printf(stderr, "not found in archive: %s\n", z); + rc = SQLITE_ERROR; + } + } + shellFinalize(&rc, pTest); + } + return rc; +} + +/* +** Format a WHERE clause that can be used against the "sqlar" table to +** identify all archive members that match the command arguments held +** in (*pAr). Leave this WHERE clause in (*pzWhere) before returning. +** The caller is responsible for eventually calling sqlite3_free() on +** any non-NULL (*pzWhere) value. +*/ +static void arWhereClause( + int *pRc, + ArCommand *pAr, + char **pzWhere /* OUT: New WHERE clause */ +){ + char *zWhere = 0; + if( *pRc==SQLITE_OK ){ + if( pAr->nArg==0 ){ + zWhere = sqlite3_mprintf("1"); + }else{ + int i; + const char *zSep = ""; + for(i=0; inArg; i++){ + const char *z = pAr->azArg[i]; + zWhere = sqlite3_mprintf( + "%z%s name = '%q' OR substr(name,1,%d) = '%q/'", + zWhere, zSep, z, strlen30(z)+1, z + ); + if( zWhere==0 ){ + *pRc = SQLITE_NOMEM; + break; + } + zSep = " OR "; + } + } + } + *pzWhere = zWhere; +} + +/* +** Implementation of .ar "lisT" command. +*/ +static int arListCommand(ArCommand *pAr){ + const char *zSql = "SELECT %s FROM %s WHERE %s"; + const char *azCols[] = { + "name", + "lsmode(mode), sz, datetime(mtime, 'unixepoch'), name" + }; + + char *zWhere = 0; + sqlite3_stmt *pSql = 0; + int rc; + + rc = arCheckEntries(pAr); + arWhereClause(&rc, pAr, &zWhere); + + shellPreparePrintf(pAr->db, &rc, &pSql, zSql, azCols[pAr->bVerbose], + pAr->zSrcTable, zWhere); + if( pAr->bDryRun ){ + utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql)); + }else{ + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ + if( pAr->bVerbose ){ + utf8_printf(pAr->p->out, "%s % 10d %s %s\n", + sqlite3_column_text(pSql, 0), + sqlite3_column_int(pSql, 1), + sqlite3_column_text(pSql, 2), + sqlite3_column_text(pSql, 3) + ); + }else{ + utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0)); + } + } + } + shellFinalize(&rc, pSql); + return rc; +} + + +/* +** Implementation of .ar "eXtract" command. +*/ +static int arExtractCommand(ArCommand *pAr){ + const char *zSql1 = + "SELECT " + " ($dir || name)," + " writefile(($dir || name), %s, mode, mtime) " + "FROM %s WHERE (%s) AND (data IS NULL OR $dirOnly = 0)"; + + const char *azExtraArg[] = { + "sqlar_uncompress(data, sz)", + "data" + }; + + sqlite3_stmt *pSql = 0; + int rc = SQLITE_OK; + char *zDir = 0; + char *zWhere = 0; + int i, j; + + /* If arguments are specified, check that they actually exist within + ** the archive before proceeding. And formulate a WHERE clause to + ** match them. */ + rc = arCheckEntries(pAr); + arWhereClause(&rc, pAr, &zWhere); + + if( rc==SQLITE_OK ){ + if( pAr->zDir ){ + zDir = sqlite3_mprintf("%s/", pAr->zDir); + }else{ + zDir = sqlite3_mprintf(""); + } + if( zDir==0 ) rc = SQLITE_NOMEM; + } + + shellPreparePrintf(pAr->db, &rc, &pSql, zSql1, + azExtraArg[pAr->bZip], pAr->zSrcTable, zWhere + ); + + if( rc==SQLITE_OK ){ + j = sqlite3_bind_parameter_index(pSql, "$dir"); + sqlite3_bind_text(pSql, j, zDir, -1, SQLITE_STATIC); + + /* Run the SELECT statement twice. The first time, writefile() is called + ** for all archive members that should be extracted. The second time, + ** only for the directories. This is because the timestamps for + ** extracted directories must be reset after they are populated (as + ** populating them changes the timestamp). */ + for(i=0; i<2; i++){ + j = sqlite3_bind_parameter_index(pSql, "$dirOnly"); + sqlite3_bind_int(pSql, j, i); + if( pAr->bDryRun ){ + utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql)); + }else{ + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ + if( i==0 && pAr->bVerbose ){ + utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0)); + } + } + } + shellReset(&rc, pSql); + } + shellFinalize(&rc, pSql); + } + + sqlite3_free(zDir); + sqlite3_free(zWhere); + return rc; +} + +/* +** Run the SQL statement in zSql. Or if doing a --dryrun, merely print it out. +*/ +static int arExecSql(ArCommand *pAr, const char *zSql){ + int rc; + if( pAr->bDryRun ){ + utf8_printf(pAr->p->out, "%s\n", zSql); + rc = SQLITE_OK; + }else{ + char *zErr = 0; + rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr); + if( zErr ){ + utf8_printf(stdout, "ERROR: %s\n", zErr); + sqlite3_free(zErr); + } + } + return rc; +} + + +/* +** Implementation of .ar "create" and "update" commands. +** +** Create the "sqlar" table in the database if it does not already exist. +** Then add each file in the azFile[] array to the archive. Directories +** are added recursively. If argument bVerbose is non-zero, a message is +** printed on stdout for each file archived. +** +** The create command is the same as update, except that it drops +** any existing "sqlar" table before beginning. +*/ +static int arCreateOrUpdateCommand( + ArCommand *pAr, /* Command arguments and options */ + int bUpdate /* true for a --create. false for --update */ +){ + const char *zCreate = + "CREATE TABLE IF NOT EXISTS sqlar(\n" + " name TEXT PRIMARY KEY, -- name of the file\n" + " mode INT, -- access permissions\n" + " mtime INT, -- last modification time\n" + " sz INT, -- original file size\n" + " data BLOB -- compressed content\n" + ")"; + const char *zDrop = "DROP TABLE IF EXISTS sqlar"; + const char *zInsertFmt = + "REPLACE INTO sqlar(name,mode,mtime,sz,data)\n" + " SELECT\n" + " %s,\n" + " mode,\n" + " mtime,\n" + " CASE substr(lsmode(mode),1,1)\n" + " WHEN '-' THEN length(data)\n" + " WHEN 'd' THEN 0\n" + " ELSE -1 END,\n" + " CASE WHEN lsmode(mode) LIKE 'd%%' THEN NULL else data END\n" + " FROM fsdir(%Q,%Q)\n" + " WHERE lsmode(mode) NOT LIKE '?%%';"; + int i; /* For iterating through azFile[] */ + int rc; /* Return code */ + + rc = arExecSql(pAr, "SAVEPOINT ar;"); + if( rc!=SQLITE_OK ) return rc; + if( bUpdate==0 ){ + rc = arExecSql(pAr, zDrop); + if( rc!=SQLITE_OK ) return rc; + } + rc = arExecSql(pAr, zCreate); + for(i=0; inArg && rc==SQLITE_OK; i++){ + char *zSql = sqlite3_mprintf(zInsertFmt, + pAr->bVerbose ? "shell_putsnl(name)" : "name", + pAr->azArg[i], pAr->zDir); + rc = arExecSql(pAr, zSql); + sqlite3_free(zSql); + } + if( rc!=SQLITE_OK ){ + arExecSql(pAr, "ROLLBACK TO ar; RELEASE ar;"); + }else{ + rc = arExecSql(pAr, "RELEASE ar;"); + } + return rc; +} + +/* +** Implementation of ".ar" dot command. +*/ +static int arDotCommand( + ShellState *pState, /* Current shell tool state */ + char **azArg, /* Array of arguments passed to dot command */ + int nArg /* Number of entries in azArg[] */ +){ + ArCommand cmd; + int rc; + memset(&cmd, 0, sizeof(cmd)); + rc = arParseCommand(azArg, nArg, &cmd); + if( rc==SQLITE_OK ){ + int eDbType = SHELL_OPEN_UNSPEC; + cmd.p = pState; + cmd.db = pState->db; + if( cmd.zFile ){ + eDbType = deduceDatabaseType(cmd.zFile); + }else{ + eDbType = pState->openMode; + } + if( eDbType==SHELL_OPEN_ZIPFILE ){ + if( cmd.zFile==0 ){ + cmd.zSrcTable = sqlite3_mprintf("zip"); + }else{ + cmd.zSrcTable = sqlite3_mprintf("zipfile(%Q)", cmd.zFile); + } + if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_UPDATE ){ + utf8_printf(stderr, "zip archives are read-only\n"); + rc = SQLITE_ERROR; + goto end_ar_command; + } + cmd.bZip = 1; + }else if( cmd.zFile ){ + int flags; + if( cmd.bAppend ) eDbType = SHELL_OPEN_APPENDVFS; + if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_UPDATE ){ + flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE; + }else{ + flags = SQLITE_OPEN_READONLY; + } + cmd.db = 0; + if( cmd.bDryRun ){ + utf8_printf(pState->out, "-- open database '%s'%s\n", cmd.zFile, + eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : ""); + } + rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, + eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0); + if( rc!=SQLITE_OK ){ + utf8_printf(stderr, "cannot open file: %s (%s)\n", + cmd.zFile, sqlite3_errmsg(cmd.db) + ); + goto end_ar_command; + } + sqlite3_fileio_init(cmd.db, 0, 0); +#ifdef SQLITE_HAVE_ZLIB + sqlite3_sqlar_init(cmd.db, 0, 0); +#endif + sqlite3_create_function(cmd.db, "shell_putsnl", 1, SQLITE_UTF8, cmd.p, + shellPutsFunc, 0, 0); + + } + if( cmd.zSrcTable==0 ){ + if( cmd.eCmd!=AR_CMD_CREATE + && sqlite3_table_column_metadata(cmd.db,0,"sqlar","name",0,0,0,0,0) + ){ + utf8_printf(stderr, "database does not contain an 'sqlar' table\n"); + rc = SQLITE_ERROR; + goto end_ar_command; + } + cmd.zSrcTable = sqlite3_mprintf("sqlar"); + } + + switch( cmd.eCmd ){ + case AR_CMD_CREATE: + rc = arCreateOrUpdateCommand(&cmd, 0); + break; + + case AR_CMD_EXTRACT: + rc = arExtractCommand(&cmd); + break; + + case AR_CMD_LIST: + rc = arListCommand(&cmd); + break; + + case AR_CMD_HELP: + arUsage(pState->out); + break; + + default: + assert( cmd.eCmd==AR_CMD_UPDATE ); + rc = arCreateOrUpdateCommand(&cmd, 1); + break; + } + } +end_ar_command: + if( cmd.db!=pState->db ){ + sqlite3_close(cmd.db); + } + sqlite3_free(cmd.zSrcTable); + + return rc; +} +/* End of the ".archive" or ".ar" command logic +**********************************************************************************/ +#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) */ + /* ** If an input line begins with "." then invoke this routine to @@ -4085,6 +5399,12 @@ static int do_meta_command(char *zLine, ShellState *p){ int rc = 0; char *azArg[50]; +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( p->expert.pExpert ){ + expertFinish(p, 1, 0); + } +#endif + /* Parse the input line into tokens. */ while( zLine[h] && nArg=3 && strncmp(azArg[0], "backup", n)==0) || (c=='s' && n>=3 && strncmp(azArg[0], "save", n)==0) ){ @@ -4400,12 +5728,14 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='e' && strncmp(azArg[0], "eqp", n)==0 ){ if( nArg==2 ){ if( strcmp(azArg[1],"full")==0 ){ - p->autoEQP = 2; + p->autoEQP = AUTOEQP_full; + }else if( strcmp(azArg[1],"trigger")==0 ){ + p->autoEQP = AUTOEQP_trigger; }else{ - p->autoEQP = booleanValue(azArg[1]); + p->autoEQP = (u8)booleanValue(azArg[1]); } }else{ - raw_printf(stderr, "Usage: .eqp on|off|full\n"); + raw_printf(stderr, "Usage: .eqp off|on|trigger|full\n"); rc = 1; } }else @@ -4439,6 +5769,13 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( c=='e' && strncmp(azArg[0], "expert", n)==0 ){ + open_db(p, 0); + expertDotCommand(p, azArg, nArg); + }else +#endif + if( c=='f' && strncmp(azArg[0], "fullschema", n)==0 ){ ShellState data; char *zErrMsg = 0; @@ -4893,13 +6230,13 @@ static int do_meta_command(char *zLine, ShellState *p){ }else{ const char *zFile = azArg[1]; output_file_close(p->pLog); - p->pLog = output_file_open(zFile); + p->pLog = output_file_open(zFile, 0); } }else if( c=='m' && strncmp(azArg[0], "mode", n)==0 ){ const char *zMode = nArg>=2 ? azArg[1] : ""; - int n2 = (int)strlen(zMode); + int n2 = strlen30(zMode); int c2 = zMode[0]; if( c2=='l' && n2>2 && strncmp(azArg[1],"lines",n2)==0 ){ p->mode = MODE_Line; @@ -4964,11 +6301,20 @@ static int do_meta_command(char *zLine, ShellState *p){ p->zDbFilename = 0; sqlite3_free(p->zFreeOnClose); p->zFreeOnClose = 0; + p->openMode = SHELL_OPEN_UNSPEC; /* Check for command-line arguments */ for(iName=1; iNameopenMode = SHELL_OPEN_ZIPFILE; +#endif + }else if( optionMatch(z, "append") ){ + p->openMode = SHELL_OPEN_APPENDVFS; + }else if( optionMatch(z, "readonly") ){ + p->openMode = SHELL_OPEN_READONLY; }else if( z[0]=='-' ){ utf8_printf(stderr, "unknown option: %s\n", z); rc = 1; @@ -4995,18 +6341,27 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else - if( c=='o' - && (strncmp(azArg[0], "output", n)==0 || strncmp(azArg[0], "once", n)==0) + if( (c=='o' + && (strncmp(azArg[0], "output", n)==0||strncmp(azArg[0], "once", n)==0)) + || (c=='e' && n==5 && strcmp(azArg[0],"excel")==0) ){ const char *zFile = nArg>=2 ? azArg[1] : "stdout"; + int bTxtMode = 0; + if( azArg[0][0]=='e' ){ + /* Transform the ".excel" command into ".once -x" */ + nArg = 2; + azArg[0] = "once"; + zFile = azArg[1] = "-x"; + n = 4; + } if( nArg>2 ){ - utf8_printf(stderr, "Usage: .%s FILE\n", azArg[0]); + utf8_printf(stderr, "Usage: .%s [-e|-x|FILE]\n", azArg[0]); rc = 1; goto meta_command_exit; } if( n>1 && strncmp(azArg[0], "once", n)==0 ){ if( nArg<2 ){ - raw_printf(stderr, "Usage: .once FILE\n"); + raw_printf(stderr, "Usage: .once (-e|-x|FILE)\n"); rc = 1; goto meta_command_exit; } @@ -5015,6 +6370,23 @@ static int do_meta_command(char *zLine, ShellState *p){ p->outCount = 0; } output_reset(p); + if( zFile[0]=='-' && zFile[1]=='-' ) zFile++; +#ifndef SQLITE_NOHAVE_SYSTEM + if( strcmp(zFile, "-e")==0 || strcmp(zFile, "-x")==0 ){ + p->doXdgOpen = 1; + outputModePush(p); + if( zFile[1]=='x' ){ + newTempFile(p, "csv"); + p->mode = MODE_Csv; + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf); + }else{ + newTempFile(p, "txt"); + bTxtMode = 1; + } + zFile = p->zTempFile; + } +#endif /* SQLITE_NOHAVE_SYSTEM */ if( zFile[0]=='|' ){ #ifdef SQLITE_OMIT_POPEN raw_printf(stderr, "Error: pipes are not supported in this OS\n"); @@ -5031,7 +6403,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } #endif }else{ - p->out = output_file_open(zFile); + p->out = output_file_open(zFile, bTxtMode); if( p->out==0 ){ if( strcmp(zFile,"off")!=0 ){ utf8_printf(stderr,"Error: cannot write to \"%s\"\n", zFile); @@ -5134,10 +6506,9 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_close(pSrc); }else - if( c=='s' && strncmp(azArg[0], "scanstats", n)==0 ){ if( nArg==2 ){ - p->scanstatsOn = booleanValue(azArg[1]); + p->scanstatsOn = (u8)booleanValue(azArg[1]); #ifndef SQLITE_ENABLE_STMT_SCANSTATUS raw_printf(stderr, "Warning: .scanstats not available in this build.\n"); #endif @@ -5151,59 +6522,48 @@ static int do_meta_command(char *zLine, ShellState *p){ ShellText sSelect; ShellState data; char *zErrMsg = 0; - const char *zDiv = 0; + const char *zDiv = "("; + const char *zName = 0; int iSchema = 0; + int bDebug = 0; + int ii; open_db(p, 0); memcpy(&data, p, sizeof(data)); data.showHeader = 0; data.cMode = data.mode = MODE_Semi; initText(&sSelect); - if( nArg>=2 && optionMatch(azArg[1], "indent") ){ - data.cMode = data.mode = MODE_Pretty; - nArg--; - if( nArg==2 ) azArg[1] = azArg[2]; - } - if( nArg==2 && azArg[1][0]!='-' ){ - int i; - for(i=0; azArg[1][i]; i++) azArg[1][i] = ToLower(azArg[1][i]); - if( strcmp(azArg[1],"sqlite_master")==0 ){ - char *new_argv[2], *new_colv[2]; - new_argv[0] = "CREATE TABLE sqlite_master (\n" - " type text,\n" - " name text,\n" - " tbl_name text,\n" - " rootpage integer,\n" - " sql text\n" - ")"; - new_argv[1] = 0; - new_colv[0] = "sql"; - new_colv[1] = 0; - callback(&data, 1, new_argv, new_colv); - rc = SQLITE_OK; - }else if( strcmp(azArg[1],"sqlite_temp_master")==0 ){ - char *new_argv[2], *new_colv[2]; - new_argv[0] = "CREATE TEMP TABLE sqlite_temp_master (\n" - " type text,\n" - " name text,\n" - " tbl_name text,\n" - " rootpage integer,\n" - " sql text\n" - ")"; - new_argv[1] = 0; - new_colv[0] = "sql"; - new_colv[1] = 0; - callback(&data, 1, new_argv, new_colv); - rc = SQLITE_OK; + for(ii=1; ii1 ){ - char *zQarg = sqlite3_mprintf("%Q", azArg[1]); - if( strchr(azArg[1], '.') ){ + if( zName ){ + char *zQarg = sqlite3_mprintf("%Q", zName); + if( strchr(zName, '.') ){ appendText(&sSelect, "lower(printf('%s.%s',sname,tbl_name))", 0); }else{ appendText(&sSelect, "lower(tbl_name)", 0); } - appendText(&sSelect, strchr(azArg[1], '*') ? " GLOB " : " LIKE ", 0); + appendText(&sSelect, strchr(zName, '*') ? " GLOB " : " LIKE ", 0); appendText(&sSelect, zQarg, 0); appendText(&sSelect, " AND ", 0); sqlite3_free(zQarg); } appendText(&sSelect, "type!='meta' AND sql IS NOT NULL" " ORDER BY snum, rowid", 0); - rc = sqlite3_exec(p->db, sSelect.z, callback, &data, &zErrMsg); + if( bDebug ){ + utf8_printf(p->out, "SQL: %s;\n", sSelect.z); + }else{ + rc = sqlite3_exec(p->db, sSelect.z, callback, &data, &zErrMsg); + } freeText(&sSelect); } if( zErrMsg ){ @@ -5732,6 +7101,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_free(zSql); }else +#ifndef SQLITE_NOHAVE_SYSTEM if( c=='s' && (strncmp(azArg[0], "shell", n)==0 || strncmp(azArg[0],"system",n)==0) ){ @@ -5751,9 +7121,10 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_free(zCmd); if( x ) raw_printf(stderr, "System command returns %d\n", x); }else +#endif /* !defined(SQLITE_NOHAVE_SYSTEM) */ if( c=='s' && strncmp(azArg[0], "show", n)==0 ){ - static const char *azBool[] = { "off", "on", "full", "unk" }; + static const char *azBool[] = { "off", "on", "trigger", "full"}; int i; if( nArg!=1 ){ raw_printf(stderr, "Usage: .show\n"); @@ -5790,7 +7161,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='s' && strncmp(azArg[0], "stats", n)==0 ){ if( nArg==2 ){ - p->statsOn = booleanValue(azArg[1]); + p->statsOn = (u8)booleanValue(azArg[1]); }else if( nArg==1 ){ display_stats(p->db, p, 0); }else{ @@ -5910,7 +7281,7 @@ static int do_meta_command(char *zLine, ShellState *p){ /* 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"); + p->out = output_file_open("testcase-out.txt", 0); if( p->out==0 ){ raw_printf(stderr, "Error: cannot open 'testcase-out.txt'\n"); } @@ -5922,49 +7293,80 @@ static int do_meta_command(char *zLine, ShellState *p){ }else #ifndef SQLITE_UNTESTABLE - if( c=='t' && n>=8 && strncmp(azArg[0], "testctrl", n)==0 && nArg>=2 ){ + if( c=='t' && n>=8 && strncmp(azArg[0], "testctrl", n)==0 ){ static const struct { const char *zCtrlName; /* Name of a test-control option */ int ctrlCode; /* Integer code for that option */ + const char *zUsage; /* Usage notes */ } aCtrl[] = { - { "prng_save", SQLITE_TESTCTRL_PRNG_SAVE }, - { "prng_restore", SQLITE_TESTCTRL_PRNG_RESTORE }, - { "prng_reset", SQLITE_TESTCTRL_PRNG_RESET }, - { "bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST }, - { "fault_install", SQLITE_TESTCTRL_FAULT_INSTALL }, - { "benign_malloc_hooks", SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS }, - { "pending_byte", SQLITE_TESTCTRL_PENDING_BYTE }, - { "assert", SQLITE_TESTCTRL_ASSERT }, - { "always", SQLITE_TESTCTRL_ALWAYS }, - { "reserve", SQLITE_TESTCTRL_RESERVE }, - { "optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS }, - { "iskeyword", SQLITE_TESTCTRL_ISKEYWORD }, - { "byteorder", SQLITE_TESTCTRL_BYTEORDER }, - { "never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT }, - { "imposter", SQLITE_TESTCTRL_IMPOSTER }, + { "always", SQLITE_TESTCTRL_ALWAYS, "BOOLEAN" }, + { "assert", SQLITE_TESTCTRL_ASSERT, "BOOLEAN" }, + /*{ "benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS, "" },*/ + /*{ "bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST, "" },*/ + { "byteorder", SQLITE_TESTCTRL_BYTEORDER, "" }, + /*{ "fault_install", SQLITE_TESTCTRL_FAULT_INSTALL, "" }, */ + { "imposter", SQLITE_TESTCTRL_IMPOSTER, "SCHEMA ON/OFF ROOTPAGE"}, +#ifdef SQLITE_N_KEYWORD + { "iskeyword", SQLITE_TESTCTRL_ISKEYWORD, "IDENTIFIER" }, +#endif + { "localtime_fault", SQLITE_TESTCTRL_LOCALTIME_FAULT,"BOOLEAN" }, + { "never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT, "BOOLEAN" }, + { "optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS, "DISABLE-MASK" }, +#ifdef YYCOVERAGE + { "parser_coverage", SQLITE_TESTCTRL_PARSER_COVERAGE, "" }, +#endif + { "pending_byte", SQLITE_TESTCTRL_PENDING_BYTE, "OFFSET " }, + { "prng_reset", SQLITE_TESTCTRL_PRNG_RESET, "" }, + { "prng_restore", SQLITE_TESTCTRL_PRNG_RESTORE, "" }, + { "prng_save", SQLITE_TESTCTRL_PRNG_SAVE, "" }, + { "reserve", SQLITE_TESTCTRL_RESERVE, "BYTES-OF-RESERVE" }, }; int testctrl = -1; - int rc2 = 0; + int iCtrl = -1; + int rc2 = 0; /* 0: usage. 1: %d 2: %x 3: no-output */ + int isOk = 0; int i, n2; + const char *zCmd = 0; + open_db(p, 0); + zCmd = nArg>=2 ? azArg[1] : "help"; + + /* The argument can optionally begin with "-" or "--" */ + if( zCmd[0]=='-' && zCmd[1] ){ + zCmd++; + if( zCmd[0]=='-' && zCmd[1] ) zCmd++; + } + + /* --help lists all test-controls */ + if( strcmp(zCmd,"help")==0 ){ + utf8_printf(p->out, "Available test-controls:\n"); + for(i=0; iout, " .testctrl %s %s\n", + aCtrl[i].zCtrlName, aCtrl[i].zUsage); + } + rc = 1; + goto meta_command_exit; + } /* convert testctrl text option to value. allow any unique prefix ** of the option name, or a numerical value. */ - n2 = strlen30(azArg[1]); + n2 = strlen30(zCmd); for(i=0; iSQLITE_TESTCTRL_LAST) ){ - utf8_printf(stderr,"Error: invalid testctrl option: %s\n", azArg[1]); + if( testctrl<0 ){ + utf8_printf(stderr,"Error: unknown test-control: %s\n" + "Use \".testctrl --help\" for help\n", zCmd); }else{ switch(testctrl){ @@ -5974,10 +7376,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg==3 ){ int opt = (int)strtol(azArg[2], 0, 0); rc2 = sqlite3_test_control(testctrl, p->db, opt); - raw_printf(p->out, "%d (0x%08x)\n", rc2, rc2); - } else { - utf8_printf(stderr,"Error: testctrl %s takes a single int option\n", - azArg[1]); + isOk = 3; } break; @@ -5988,10 +7387,7 @@ static int do_meta_command(char *zLine, ShellState *p){ case SQLITE_TESTCTRL_BYTEORDER: if( nArg==2 ){ rc2 = sqlite3_test_control(testctrl); - raw_printf(p->out, "%d (0x%08x)\n", rc2, rc2); - } else { - utf8_printf(stderr,"Error: testctrl %s takes no options\n", - azArg[1]); + isOk = testctrl==SQLITE_TESTCTRL_BYTEORDER ? 1 : 3; } break; @@ -6000,24 +7396,27 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg==3 ){ unsigned int opt = (unsigned int)integerValue(azArg[2]); rc2 = sqlite3_test_control(testctrl, opt); - raw_printf(p->out, "%d (0x%08x)\n", rc2, rc2); - } else { - utf8_printf(stderr,"Error: testctrl %s takes a single unsigned" - " int option\n", azArg[1]); + isOk = 3; } break; /* sqlite3_test_control(int, int) */ case SQLITE_TESTCTRL_ASSERT: case SQLITE_TESTCTRL_ALWAYS: + if( nArg==3 ){ + int opt = booleanValue(azArg[2]); + rc2 = sqlite3_test_control(testctrl, opt); + isOk = 1; + } + break; + + /* sqlite3_test_control(int, int) */ + case SQLITE_TESTCTRL_LOCALTIME_FAULT: case SQLITE_TESTCTRL_NEVER_CORRUPT: if( nArg==3 ){ int opt = booleanValue(azArg[2]); rc2 = sqlite3_test_control(testctrl, opt); - raw_printf(p->out, "%d (0x%08x)\n", rc2, rc2); - } else { - utf8_printf(stderr,"Error: testctrl %s takes a single int option\n", - azArg[1]); + isOk = 3; } break; @@ -6027,11 +7426,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg==3 ){ const char *opt = azArg[2]; rc2 = sqlite3_test_control(testctrl, opt); - raw_printf(p->out, "%d (0x%08x)\n", rc2, rc2); - } else { - utf8_printf(stderr, - "Error: testctrl %s takes a single char * option\n", - azArg[1]); + isOk = 1; } break; #endif @@ -6042,22 +7437,27 @@ static int do_meta_command(char *zLine, ShellState *p){ azArg[2], integerValue(azArg[3]), integerValue(azArg[4])); - raw_printf(p->out, "%d (0x%08x)\n", rc2, rc2); - }else{ - raw_printf(stderr,"Usage: .testctrl imposter dbName onoff tnum\n"); + isOk = 3; } break; - case SQLITE_TESTCTRL_BITVEC_TEST: - case SQLITE_TESTCTRL_FAULT_INSTALL: - case SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS: - default: - utf8_printf(stderr, - "Error: CLI support for testctrl %s not implemented\n", - azArg[1]); - break; +#ifdef YYCOVERAGE + case SQLITE_TESTCTRL_PARSER_COVERAGE: + if( nArg==2 ){ + sqlite3_test_control(testctrl, p->out); + isOk = 3; + } +#endif } } + if( isOk==0 && iCtrl>=0 ){ + utf8_printf(p->out, "Usage: .testctrl %s %s\n", zCmd, aCtrl[iCtrl].zUsage); + rc = 1; + }else if( isOk==1 ){ + raw_printf(p->out, "%d\n", rc2); + }else if( isOk==2 ){ + raw_printf(p->out, "0x%08x\n", rc2); + } }else #endif /* !defined(SQLITE_UNTESTABLE) */ @@ -6087,7 +7487,7 @@ static int do_meta_command(char *zLine, ShellState *p){ goto meta_command_exit; } output_file_close(p->traceOut); - p->traceOut = output_file_open(azArg[1]); + p->traceOut = output_file_open(azArg[1], 0); #if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT) if( p->traceOut==0 ){ sqlite3_trace_v2(p->db, 0, 0, 0); @@ -6111,8 +7511,7 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = 1; goto meta_command_exit; } - rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3], - (int)strlen(azArg[3])); + rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3], strlen30(azArg[3])); if( rc ){ utf8_printf(stderr, "Authentication failed for user %s\n", azArg[2]); rc = 1; @@ -6123,8 +7522,7 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = 1; goto meta_command_exit; } - rc = sqlite3_user_add(p->db, azArg[2], - azArg[3], (int)strlen(azArg[3]), + rc = sqlite3_user_add(p->db, azArg[2], azArg[3], strlen30(azArg[3]), booleanValue(azArg[4])); if( rc ){ raw_printf(stderr, "User-Add failed: %d\n", rc); @@ -6136,8 +7534,7 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = 1; goto meta_command_exit; } - rc = sqlite3_user_change(p->db, azArg[2], - azArg[3], (int)strlen(azArg[3]), + rc = sqlite3_user_change(p->db, azArg[2], azArg[3], strlen30(azArg[3]), booleanValue(azArg[4])); if( rc ){ raw_printf(stderr, "User-Edit failed: %d\n", rc); @@ -6165,6 +7562,20 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='v' && strncmp(azArg[0], "version", n)==0 ){ utf8_printf(p->out, "SQLite %s %s\n" /*extra-version-info*/, sqlite3_libversion(), sqlite3_sourceid()); +#if SQLITE_HAVE_ZLIB + utf8_printf(p->out, "zlib version %s\n", zlibVersion()); +#endif +#define CTIMEOPT_VAL_(opt) #opt +#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt) +#if defined(__clang__) && defined(__clang_major__) + utf8_printf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "." + CTIMEOPT_VAL(__clang_minor__) "." + CTIMEOPT_VAL(__clang_patchlevel__) "\n"); +#elif defined(_MSC_VER) + utf8_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) "\n"); +#elif defined(__GNUC__) && defined(__VERSION__) + utf8_printf(p->out, "gcc-" __VERSION__ "\n"); +#endif }else if( c=='v' && strncmp(azArg[0], "vfsinfo", n)==0 ){ @@ -6290,6 +7701,16 @@ static int line_is_command_terminator(const char *zLine){ return 0; } +/* +** We need a default sqlite3_complete() implementation to use in case +** the shell is compiled with SQLITE_OMIT_COMPLETE. The default assumes +** any arbitrary text is a complete SQL statement. This is not very +** user-friendly, but it does seem to work. +*/ +#ifdef SQLITE_OMIT_COMPLETE +int sqlite3_complete(const char *zSql){ return 1; } +#endif + /* ** Return true if zSql is a complete SQL statement. Return false if it ** ends in the middle of a string literal or C-style comment. @@ -6420,6 +7841,8 @@ static int process_input(ShellState *p, FILE *in){ if( p->outCount ){ output_reset(p); p->outCount = 0; + }else{ + clearTempFile(p); } }else if( nSql && _all_whitespace(zSql) ){ if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zSql); @@ -6570,6 +7993,7 @@ static const char zOptions[] = " -nullvalue TEXT set text string for NULL values. Default ''\n" " -pagecache SIZE N use N slots of SZ bytes each for page cache memory\n" " -quote set output mode to 'quote'\n" + " -readonly open the database read-only\n" " -separator SEP set output column separator. Default: '|'\n" " -stats print memory stats before each finalize\n" " -version show SQLite version\n" @@ -6679,21 +8103,39 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ } #endif main_init(&data); + + /* On Windows, we must translate command-line arguments into UTF-8. + ** The SQLite memory allocator subsystem has to be enabled in order to + ** do this. But we want to run an sqlite3_shutdown() afterwards so that + ** subsequent sqlite3_config() calls will work. So copy all results into + ** memory that does not come from the SQLite memory allocator. + */ #if !SQLITE_SHELL_IS_UTF8 sqlite3_initialize(); - argv = sqlite3_malloc64(sizeof(argv[0])*argc); + argv = malloc(sizeof(argv[0])*argc); if( argv==0 ){ raw_printf(stderr, "out of memory\n"); exit(1); } for(i=0; i=1 && argv && argv[0] ); Argv0 = argv[0]; @@ -6813,6 +8255,14 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ utf8_printf(stderr, "no such VFS: \"%s\"\n", argv[i]); exit(1); } +#ifdef SQLITE_HAVE_ZIP + }else if( strcmp(z,"-zip")==0 ){ + data.openMode = SHELL_OPEN_ZIPFILE; +#endif + }else if( strcmp(z,"-append")==0 ){ + data.openMode = SHELL_OPEN_APPENDVFS; + }else if( strcmp(z,"-readonly")==0 ){ + data.openMode = SHELL_OPEN_READONLY; } } if( data.zDbFilename==0 ){ @@ -6825,6 +8275,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ #endif } data.out = stdout; + sqlite3_appendvfs_init(0,0,0); /* Go ahead and open the database file if it already exists. If the ** file does not exist, delay opening it. This prevents empty database @@ -6865,6 +8316,12 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ }else if( strcmp(z,"-csv")==0 ){ data.mode = MODE_Csv; memcpy(data.colSeparator,",",2); +#ifdef SQLITE_HAVE_ZIP + }else if( strcmp(z,"-zip")==0 ){ + data.openMode = SHELL_OPEN_ZIPFILE; +#endif + }else if( strcmp(z,"-append")==0 ){ + data.openMode = SHELL_OPEN_APPENDVFS; }else if( strcmp(z,"-ascii")==0 ){ data.mode = MODE_Ascii; sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, @@ -6887,9 +8344,9 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ }else if( strcmp(z,"-echo")==0 ){ ShellSetFlag(&data, SHFLG_Echo); }else if( strcmp(z,"-eqp")==0 ){ - data.autoEQP = 1; + data.autoEQP = AUTOEQP_on; }else if( strcmp(z,"-eqpfull")==0 ){ - data.autoEQP = 2; + data.autoEQP = AUTOEQP_full; }else if( strcmp(z,"-stats")==0 ){ data.statsOn = 1; }else if( strcmp(z,"-scanstats")==0 ){ @@ -7029,9 +8486,12 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ } sqlite3_free(data.zFreeOnClose); find_home_dir(1); + output_reset(&data); + data.doXdgOpen = 0; + clearTempFile(&data); #if !SQLITE_SHELL_IS_UTF8 - for(i=0; i -** **
SQLITE_DBCONFIG_ENABLE_QPSG
**
^(The SQLITE_DBCONFIG_ENABLE_QPSG option activates or deactivates ** the [query planner stability guarantee] (QPSG). When the QPSG is active, @@ -2066,7 +2069,16 @@ struct sqlite3_mem_methods { ** the QPSG active, SQLite will always use the same query plan in the field as ** was used during testing in the lab. **
-** +**
SQLITE_DBCONFIG_TRIGGER_EQP
+**
By default, the output of EXPLAIN QUERY PLAN commands does not +** include output for any operations performed by trigger programs. This +** option is used to set or clear (the default) a flag that governs this +** behavior. The first parameter passed to this operation is an integer - +** non-zero to enable output for trigger programs, or zero to disable it. +** The second parameter is a pointer to an integer into which is written +** 0 or 1 to indicate whether output-for-triggers has been disabled - 0 if +** it is not disabled, 1 if it is. +**
** */ #define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */ @@ -2077,7 +2089,8 @@ struct sqlite3_mem_methods { #define SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005 /* int int* */ #define SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_QPSG 1007 /* int int* */ - +#define SQLITE_DBCONFIG_TRIGGER_EQP 1008 /* int int* */ +#define SQLITE_DBCONFIG_MAX 1008 /* Largest DBCONFIG */ /* ** CAPI3REF: Enable Or Disable Extended Result Codes @@ -2483,16 +2496,16 @@ void sqlite3_free_table(char **result); ** ** These routines are work-alikes of the "printf()" family of functions ** from the standard C library. -** These routines understand most of the common K&R formatting options, -** plus some additional non-standard formats, detailed below. -** Note that some of the more obscure formatting options from recent -** C-library standards are omitted from this implementation. +** These routines understand most of the common formatting options from +** the standard library printf() +** plus some additional non-standard formats ([%q], [%Q], [%w], and [%z]). +** See the [built-in printf()] documentation for details. ** ** ^The sqlite3_mprintf() and sqlite3_vmprintf() routines write their -** results into memory obtained from [sqlite3_malloc()]. +** results into memory obtained from [sqlite3_malloc64()]. ** The strings returned by these two routines should be ** released by [sqlite3_free()]. ^Both routines return a -** NULL pointer if [sqlite3_malloc()] is unable to allocate enough +** NULL pointer if [sqlite3_malloc64()] is unable to allocate enough ** memory to hold the resulting string. ** ** ^(The sqlite3_snprintf() routine is similar to "snprintf()" from @@ -2516,71 +2529,7 @@ void sqlite3_free_table(char **result); ** ** ^The sqlite3_vsnprintf() routine is a varargs version of sqlite3_snprintf(). ** -** These routines all implement some additional formatting -** options that are useful for constructing SQL statements. -** All of the usual printf() formatting options apply. In addition, there -** is are "%q", "%Q", "%w" and "%z" options. -** -** ^(The %q option works like %s in that it substitutes a nul-terminated -** string from the argument list. But %q also doubles every '\'' character. -** %q is designed for use inside a string literal.)^ By doubling each '\'' -** character it escapes that character and allows it to be inserted into -** the string. -** -** For example, assume the string variable zText contains text as follows: -** -**
-**  char *zText = "It's a happy day!";
-** 
-** -** One can use this text in an SQL statement as follows: -** -**
-**  char *zSQL = sqlite3_mprintf("INSERT INTO table VALUES('%q')", zText);
-**  sqlite3_exec(db, zSQL, 0, 0, 0);
-**  sqlite3_free(zSQL);
-** 
-** -** Because the %q format string is used, the '\'' character in zText -** is escaped and the SQL generated is as follows: -** -**
-**  INSERT INTO table1 VALUES('It''s a happy day!')
-** 
-** -** This is correct. Had we used %s instead of %q, the generated SQL -** would have looked like this: -** -**
-**  INSERT INTO table1 VALUES('It's a happy day!');
-** 
-** -** This second example is an SQL syntax error. As a general rule you should -** always use %q instead of %s when inserting text into a string literal. -** -** ^(The %Q option works like %q except it also adds single quotes around -** the outside of the total string. Additionally, if the parameter in the -** argument list is a NULL pointer, %Q substitutes the text "NULL" (without -** single quotes).)^ So, for example, one could say: -** -**
-**  char *zSQL = sqlite3_mprintf("INSERT INTO table VALUES(%Q)", zText);
-**  sqlite3_exec(db, zSQL, 0, 0, 0);
-**  sqlite3_free(zSQL);
-** 
-** -** The code above will render a correct SQL statement in the zSQL -** variable even if the zText variable is a NULL pointer. -** -** ^(The "%w" formatting option is like "%q" except that it expects to -** be contained within double-quotes instead of single quotes, and it -** escapes the double-quote character instead of the single-quote -** character.)^ The "%w" formatting option is intended for safely inserting -** table and column names into a constructed SQL statement. -** -** ^(The "%z" formatting option works like "%s" but with the -** addition that after the string has been read and copied into -** the result, [sqlite3_free()] is called on the input string.)^ +** See also: [built-in printf()], [printf() SQL function] */ char *sqlite3_mprintf(const char*,...); char *sqlite3_vmprintf(const char*, va_list); @@ -2938,8 +2887,8 @@ SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*, ** KEYWORDS: SQLITE_TRACE ** ** These constants identify classes of events that can be monitored -** using the [sqlite3_trace_v2()] tracing logic. The third argument -** to [sqlite3_trace_v2()] is an OR-ed combination of one or more of +** using the [sqlite3_trace_v2()] tracing logic. The M argument +** to [sqlite3_trace_v2(D,M,X,P)] is an OR-ed combination of one or more of ** the following constants. ^The first argument to the trace callback ** is one of the following constants. ** @@ -3646,13 +3595,13 @@ int sqlite3_limit(sqlite3*, int id, int newVal); ** or [GLOB] operator or if the parameter is compared to an indexed column ** and the [SQLITE_ENABLE_STAT3] compile-time option is enabled. ** +** ** **

^sqlite3_prepare_v3() differs from sqlite3_prepare_v2() only in having ** the extra prepFlags parameter, which is a bit array consisting of zero or ** more of the [SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_*] flags. ^The ** sqlite3_prepare_v2() interface works exactly the same as ** sqlite3_prepare_v3() with a zero prepFlags parameter. -** */ int sqlite3_prepare( sqlite3 *db, /* Database handle */ @@ -4785,6 +4734,9 @@ SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int), ** datatype of the value ** sqlite3_value_numeric_type   ** →  Best numeric datatype of the value +** sqlite3_value_nochange   +** →  True if the column is unchanged in an UPDATE +** against a virtual table. ** ** ** Details: @@ -4833,6 +4785,19 @@ SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int), ** then the conversion is performed. Otherwise no conversion occurs. ** The [SQLITE_INTEGER | datatype] after conversion is returned.)^ ** +** ^Within the [xUpdate] method of a [virtual table], the +** sqlite3_value_nochange(X) interface returns true if and only if +** the column corresponding to X is unchanged by the UPDATE operation +** that the xUpdate method call was invoked to implement and if +** and the prior [xColumn] method call that was invoked to extracted +** the value for that column returned without setting a result (probably +** because it queried [sqlite3_vtab_nochange()] and found that the column +** was unchanging). ^Within an [xUpdate] method, any value for which +** sqlite3_value_nochange(X) is true will in all other respects appear +** to be a NULL value. If sqlite3_value_nochange(X) is invoked anywhere other +** than within an [xUpdate] method call for an UPDATE statement, then +** the return value is arbitrary and meaningless. +** ** Please pay particular attention to the fact that the pointer returned ** from [sqlite3_value_blob()], [sqlite3_value_text()], or ** [sqlite3_value_text16()] can be invalidated by a subsequent call to @@ -4855,6 +4820,7 @@ int sqlite3_value_bytes(sqlite3_value*); int sqlite3_value_bytes16(sqlite3_value*); int sqlite3_value_type(sqlite3_value*); int sqlite3_value_numeric_type(sqlite3_value*); +int sqlite3_value_nochange(sqlite3_value*); /* ** CAPI3REF: Finding The Subtype Of SQL Values @@ -6957,9 +6923,9 @@ sqlite3_mutex *sqlite3_db_mutex(sqlite3*); ** the xFileControl method. ^The return value of the xFileControl ** method becomes the return value of this routine. ** -** ^The SQLITE_FCNTL_FILE_POINTER value for the op parameter causes +** ^The [SQLITE_FCNTL_FILE_POINTER] value for the op parameter causes ** a pointer to the underlying [sqlite3_file] object to be written into -** the space pointed to by the 4th parameter. ^The SQLITE_FCNTL_FILE_POINTER +** the space pointed to by the 4th parameter. ^The [SQLITE_FCNTL_FILE_POINTER] ** case is a short-circuit path which does not actually invoke the ** underlying sqlite3_io_methods.xFileControl method. ** @@ -6971,7 +6937,7 @@ sqlite3_mutex *sqlite3_db_mutex(sqlite3*); ** an incorrect zDbName and an SQLITE_ERROR return from the underlying ** xFileControl method. ** -** See also: [SQLITE_FCNTL_LOCKSTATE] +** See also: [file control opcodes] */ int sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*); @@ -7028,7 +6994,8 @@ int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_ISINIT 23 #define SQLITE_TESTCTRL_SORTER_MMAP 24 #define SQLITE_TESTCTRL_IMPOSTER 25 -#define SQLITE_TESTCTRL_LAST 25 +#define SQLITE_TESTCTRL_PARSER_COVERAGE 26 +#define SQLITE_TESTCTRL_LAST 26 /* Largest TESTCTRL */ /* ** CAPI3REF: SQLite Runtime Status @@ -8282,6 +8249,40 @@ int sqlite3_vtab_config(sqlite3*, int op, ...); */ int sqlite3_vtab_on_conflict(sqlite3 *); +/* +** CAPI3REF: Determine If Virtual Table Column Access Is For UPDATE +** +** If the sqlite3_vtab_nochange(X) routine is called within the [xColumn] +** method of a [virtual table], then it returns true if and only if the +** column is being fetched as part of an UPDATE operation during which the +** column value will not change. Applications might use this to substitute +** a lighter-weight value to return that the corresponding [xUpdate] method +** understands as a "no-change" value. +** +** If the [xColumn] method calls sqlite3_vtab_nochange() and finds that +** the column is not changed by the UPDATE statement, they the xColumn +** method can optionally return without setting a result, without calling +** any of the [sqlite3_result_int|sqlite3_result_xxxxx() interfaces]. +** In that case, [sqlite3_value_nochange(X)] will return true for the +** same column in the [xUpdate] method. +*/ +int sqlite3_vtab_nochange(sqlite3_context*); + +/* +** CAPI3REF: Determine The Collation For a Virtual Table Constraint +** +** This function may only be called from within a call to the [xBestIndex] +** method of a [virtual table]. +** +** The first argument must be the sqlite3_index_info object that is the +** first parameter to the xBestIndex() method. The second argument must be +** an index into the aConstraint[] array belonging to the sqlite3_index_info +** structure passed to xBestIndex. This function returns a pointer to a buffer +** containing the name of the collation sequence for the corresponding +** constraint. +*/ +SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_info*,int); + /* ** CAPI3REF: Conflict resolution modes ** KEYWORDS: {conflict resolution mode} diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h index d1d2c574ae..1409370a6f 100644 --- a/src/sqlite3ext.h +++ b/src/sqlite3ext.h @@ -292,6 +292,9 @@ struct sqlite3_api_routines { int (*bind_pointer)(sqlite3_stmt*,int,void*,const char*,void(*)(void*)); void (*result_pointer)(sqlite3_context*,void*,const char*,void(*)(void*)); void *(*value_pointer)(sqlite3_value*,const char*); + int (*vtab_nochange)(sqlite3_context*); + int (*value_nochange)(sqlite3_value*); + const char *(*vtab_collation)(sqlite3_index_info*,int); }; /* @@ -558,6 +561,10 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_bind_pointer sqlite3_api->bind_pointer #define sqlite3_result_pointer sqlite3_api->result_pointer #define sqlite3_value_pointer sqlite3_api->value_pointer +/* Version 3.22.0 and later */ +#define sqlite3_vtab_nochange sqlite3_api->vtab_nochange +#define sqlite3_value_nochange sqlite3_api->value_nochange +#define sqlite3_vtab_collation sqlite3_api->vtab_collation #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 71d8783c8d..43883d3979 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1063,7 +1063,6 @@ typedef struct Db Db; typedef struct Schema Schema; typedef struct Expr Expr; typedef struct ExprList ExprList; -typedef struct ExprSpan ExprSpan; typedef struct FKey FKey; typedef struct FuncDestructor FuncDestructor; typedef struct FuncDef FuncDef; @@ -1421,7 +1420,7 @@ struct sqlite3 { Hash aModule; /* populated by sqlite3_create_module() */ VtabCtx *pVtabCtx; /* Context for active vtab connect/create */ VTable **aVTrans; /* Virtual tables with open transactions */ - VTable *pDisconnect; /* Disconnect these in next sqlite3_prepare() */ + VTable *pDisconnect; /* Disconnect these in next sqlite3_prepare() */ #endif Hash aFunc; /* Hash table of connection functions */ Hash aCollSeq; /* All collating sequences */ @@ -1496,7 +1495,9 @@ struct sqlite3 { #define SQLITE_QueryOnly 0x00100000 /* Disable database changes */ #define SQLITE_CellSizeCk 0x00200000 /* Check btree cell sizes on load */ #define SQLITE_Fts3Tokenizer 0x00400000 /* Enable fts3_tokenizer(2) */ -#define SQLITE_EnableQPSG 0x00800000 /* Query Planner Stability Guarantee */ +#define SQLITE_EnableQPSG 0x00800000 /* Query Planner Stability Guarantee*/ +#define SQLITE_TriggerEQP 0x01000000 /* Show trigger EXPLAIN QUERY PLAN */ + /* Flags used only if debugging */ #ifdef SQLITE_DEBUG #define SQLITE_SqlTrace 0x08000000 /* Debug print SQL as it executes */ @@ -1629,6 +1630,7 @@ struct FuncDestructor { #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 */ +#define SQLITE_FUNC_OFFSET 0x8000 /* Built-in sqlite_offset() function */ /* ** The following three macros, FUNCTION(), LIKEFUNC() and AGGREGATE() are @@ -1753,6 +1755,7 @@ struct Column { #define COLFLAG_PRIMKEY 0x0001 /* Column is part of the primary key */ #define COLFLAG_HIDDEN 0x0002 /* A hidden column in a virtual table */ #define COLFLAG_HASTYPE 0x0004 /* Type name follows column name */ +#define COLFLAG_UNIQUE 0x0008 /* Column def contains "UNIQUE" or "PK" */ /* ** A "Collating Sequence" is defined by an instance of the following @@ -2173,6 +2176,7 @@ struct Index { unsigned isCovering:1; /* True if this is a covering index */ unsigned noSkipScan:1; /* Do not try to use skip-scan if true */ unsigned hasStat1:1; /* aiRowLogEst values come from sqlite_stat1 */ + unsigned bNoQuery:1; /* Do not use this index to optimize queries */ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 int nSample; /* Number of elements in aSample[] */ int nSampleCol; /* Size of IndexSample.anEq[] and so on */ @@ -2502,17 +2506,6 @@ struct ExprList { } a[1]; /* One slot for each expression in the list */ }; -/* -** An instance of this structure is used by the parser to record both -** the parse tree for an expression and the span of input text for an -** expression. -*/ -struct ExprSpan { - Expr *pExpr; /* The expression parse tree */ - const char *zStart; /* First character of input text */ - const char *zEnd; /* One character past the end of input text */ -}; - /* ** An instance of this structure can hold a simple list of identifiers, ** such as the list "a,b,c" in the following statements: @@ -2750,7 +2743,6 @@ struct Select { Select *pPrior; /* Prior select in a compound select statement */ Select *pNext; /* Next select to the left in a compound */ Expr *pLimit; /* LIMIT expression. NULL means not used. */ - Expr *pOffset; /* OFFSET expression. NULL means not used. */ With *pWith; /* WITH clause attached to this select. Or NULL. */ }; @@ -2986,7 +2978,7 @@ struct Parse { int nMem; /* Number of memory cells used so far */ int nOpAlloc; /* Number of slots allocated for Vdbe.aOp[] */ int szOpAlloc; /* Bytes of memory space allocated for Vdbe.aOp[] */ - int iSelfTab; /* Table for associated with an index on expr, or negative + int iSelfTab; /* Table associated with an index on expr, or negative ** of the base register during check-constraint eval */ int iCacheLevel; /* ColCache valid when aColCache[].iLevel<=iCacheLevel */ int iCacheCnt; /* Counter used to generate aColCache[].lru values */ @@ -3127,6 +3119,7 @@ struct AuthContext { #define OPFLAG_PERMUTE 0x01 /* OP_Compare: use the permutation */ #define OPFLAG_SAVEPOSITION 0x02 /* OP_Delete/Insert: save cursor pos */ #define OPFLAG_AUXDELETE 0x04 /* OP_Delete: index in a DELETE op */ +#define OPFLAG_NOCHNG_MAGIC 0x6d /* OP_MakeRecord: serialtype 10 is ok */ /* * Each trigger present in the database schema is stored as an instance of @@ -3214,6 +3207,7 @@ struct TriggerStep { Expr *pWhere; /* The WHERE clause for DELETE or UPDATE steps */ ExprList *pExprList; /* SET clause for UPDATE. */ IdList *pIdList; /* Column names for INSERT */ + char *zSpan; /* Original SQL text of this command */ TriggerStep *pNext; /* Next in the link-list */ TriggerStep *pLast; /* Last element in link-list. Valid for 1st elem only */ }; @@ -3433,6 +3427,7 @@ struct TreeView { ** using sqlite3_log(). The routines also provide a convenient place ** to set a debugger breakpoint. */ +int sqlite3ReportError(int iErr, int lineno, const char *zType); int sqlite3CorruptError(int); int sqlite3MisuseError(int); int sqlite3CantopenError(int); @@ -3523,6 +3518,7 @@ void *sqlite3DbMallocRaw(sqlite3*, u64); void *sqlite3DbMallocRawNN(sqlite3*, u64); char *sqlite3DbStrDup(sqlite3*,const char*); char *sqlite3DbStrNDup(sqlite3*,const char*, u64); +char *sqlite3DbSpanDup(sqlite3*,const char*,const char*); void *sqlite3Realloc(void*, u64); void *sqlite3DbReallocOrFree(sqlite3 *, void *, u64); void *sqlite3DbRealloc(sqlite3 *, void *, u64); @@ -3591,6 +3587,12 @@ int sqlite3LookasideUsed(sqlite3*,int*); sqlite3_mutex *sqlite3Pcache1Mutex(void); sqlite3_mutex *sqlite3MallocMutex(void); +#if defined(SQLITE_ENABLE_MULTITHREADED_CHECKS) && !defined(SQLITE_MUTEX_OMIT) +void sqlite3MutexWarnOnContention(sqlite3_mutex*); +#else +# define sqlite3MutexWarnOnContention(x) +#endif + #ifndef SQLITE_OMIT_FLOATING_POINT int sqlite3IsNaN(double); #else @@ -3655,7 +3657,7 @@ 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*); +void sqlite3ExprListSetSpan(Parse*,ExprList*,const char*,const char*); void sqlite3ExprListDelete(sqlite3*, ExprList*); u32 sqlite3ExprListFlags(const ExprList*); int sqlite3Init(sqlite3*, char**); @@ -3685,7 +3687,7 @@ void sqlite3AddColumn(Parse*,Token*,Token*); void sqlite3AddNotNull(Parse*, int); void sqlite3AddPrimaryKey(Parse*, ExprList*, int, int, int); void sqlite3AddCheckConstraint(Parse*, Expr*); -void sqlite3AddDefaultValue(Parse*,ExprSpan*); +void sqlite3AddDefaultValue(Parse*,Expr*,const char*,const char*); void sqlite3AddCollateType(Parse*, Token*); void sqlite3EndTable(Parse*,Token*,Token*,u8,Select*); int sqlite3ParseUri(const char*,const char*,unsigned int*, @@ -3757,16 +3759,16 @@ void sqlite3CreateIndex(Parse*,Token*,Token*,SrcList*,ExprList*,int,Token*, void sqlite3DropIndex(Parse*, SrcList*, int); int sqlite3Select(Parse*, Select*, SelectDest*); Select *sqlite3SelectNew(Parse*,ExprList*,SrcList*,Expr*,ExprList*, - Expr*,ExprList*,u32,Expr*,Expr*); + Expr*,ExprList*,u32,Expr*); void sqlite3SelectDelete(sqlite3*, Select*); Table *sqlite3SrcListLookup(Parse*, SrcList*); int sqlite3IsReadOnly(Parse*, Table*, int); void sqlite3OpenTable(Parse*, int iCur, int iDb, Table*, int); #if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY) -Expr *sqlite3LimitWhere(Parse*,SrcList*,Expr*,ExprList*,Expr*,Expr*,char*); +Expr *sqlite3LimitWhere(Parse*,SrcList*,Expr*,ExprList*,Expr*,char*); #endif -void sqlite3DeleteFrom(Parse*, SrcList*, Expr*, ExprList*, Expr*, Expr*); -void sqlite3Update(Parse*, SrcList*, ExprList*,Expr*,int,ExprList*,Expr*,Expr*); +void sqlite3DeleteFrom(Parse*, SrcList*, Expr*, ExprList*, Expr*); +void sqlite3Update(Parse*, SrcList*, ExprList*,Expr*,int,ExprList*,Expr*); WhereInfo *sqlite3WhereBegin(Parse*,SrcList*,Expr*,ExprList*,ExprList*,u16,int); void sqlite3WhereEnd(WhereInfo*); LogEst sqlite3WhereOutputRowCount(WhereInfo*); @@ -3838,6 +3840,8 @@ void sqlite3EndTransaction(Parse*,int); void sqlite3Savepoint(Parse*, int, Token*); void sqlite3CloseSavepoints(sqlite3 *); void sqlite3LeaveMutexAndCloseZombie(sqlite3*); +int sqlite3ExprIdToTrueFalse(Expr*); +int sqlite3ExprTruthValue(const Expr*); int sqlite3ExprIsConstant(Expr*); int sqlite3ExprIsConstantNotJoin(Expr*); int sqlite3ExprIsConstantOrFunction(Expr*, u8); @@ -3890,7 +3894,7 @@ int sqlite3SafetyCheckSickOrOk(sqlite3*); void sqlite3ChangeCookie(Parse*, int); #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) -void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,Expr*,int); +void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int); #endif #ifndef SQLITE_OMIT_TRIGGER @@ -3906,11 +3910,14 @@ void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,Expr*,int); void sqlite3CodeRowTriggerDirect(Parse *, Trigger *, Table *, int, int, int); void sqliteViewTriggers(Parse*, Table*, Expr*, int, ExprList*); void sqlite3DeleteTriggerStep(sqlite3*, TriggerStep*); - TriggerStep *sqlite3TriggerSelectStep(sqlite3*,Select*); + TriggerStep *sqlite3TriggerSelectStep(sqlite3*,Select*, + const char*,const char*); TriggerStep *sqlite3TriggerInsertStep(sqlite3*,Token*, IdList*, - Select*,u8); - TriggerStep *sqlite3TriggerUpdateStep(sqlite3*,Token*,ExprList*, Expr*, u8); - TriggerStep *sqlite3TriggerDeleteStep(sqlite3*,Token*, Expr*); + Select*,u8,const char*,const char*); + TriggerStep *sqlite3TriggerUpdateStep(sqlite3*,Token*,ExprList*, Expr*, u8, + const char*,const char*); + TriggerStep *sqlite3TriggerDeleteStep(sqlite3*,Token*, Expr*, + const char*,const char*); void sqlite3DeleteTrigger(sqlite3*, Trigger*); void sqlite3UnlinkAndDeleteTrigger(sqlite3*,int,const char*); u32 sqlite3TriggerColmask(Parse*,Trigger*,ExprList*,int,int,Table*,int); @@ -4065,6 +4072,9 @@ extern FuncDefHash sqlite3BuiltinFunctions; extern int sqlite3PendingByte; #endif #endif +#ifdef VDBE_PROFILE +extern sqlite3_uint64 sqlite3NProfileCnt; +#endif void sqlite3RootPageMoved(sqlite3*, int, int, int); void sqlite3Reindex(Parse*, Token*, Token*); void sqlite3AlterFunctions(void); @@ -4340,6 +4350,9 @@ void sqlite3Put4byte(u8*, u32); #ifdef SQLITE_DEBUG void sqlite3ParserTrace(FILE*, char *); #endif +#if defined(YYCOVERAGE) + int sqlite3ParserCoverage(FILE*); +#endif /* ** If the SQLITE_ENABLE IOTRACE exists then the global variable diff --git a/src/tclsqlite.c b/src/tclsqlite.c index eed86eee34..252b246e6e 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -64,7 +64,9 @@ # define GETPID getpid #elif !defined(_WIN32_WCE) # ifndef SQLITE_AMALGAMATION -# define WIN32_LEAN_AND_MEAN +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif # include # endif # define GETPID (int)GetCurrentProcessId diff --git a/src/test1.c b/src/test1.c index 26d8439ea8..d24fd8b679 100644 --- a/src/test1.c +++ b/src/test1.c @@ -4559,6 +4559,35 @@ static int SQLITE_TCLAPI test_complete16( return TCL_OK; } +/* +** Usage: sqlite3_normalize SQL +** +** Return the normalized value for an SQL statement. +*/ +static int SQLITE_TCLAPI test_normalize( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + char *zSql; + char *zNorm; + extern char *sqlite3_normalize(const char*); + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "SQL"); + return TCL_ERROR; + } + + zSql = (char*)Tcl_GetString(objv[1]); + zNorm = sqlite3_normalize(zSql); + if( zNorm ){ + Tcl_SetObjResult(interp, Tcl_NewStringObj(zNorm, -1)); + sqlite3_free(zNorm); + } + return TCL_OK; +} + /* ** Usage: sqlite3_step STMT ** @@ -6960,6 +6989,9 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd( extern int sqlite3_totype_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_wholenumber_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_unionvtab_init(sqlite3*,char**,const sqlite3_api_routines*); +#ifdef SQLITE_HAVE_ZLIB + extern int sqlite3_zipfile_init(sqlite3*,char**,const sqlite3_api_routines*); +#endif static const struct { const char *zExtName; int (*pInit)(sqlite3*,char**,const sqlite3_api_routines*); @@ -6981,6 +7013,9 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd( { "totype", sqlite3_totype_init }, { "unionvtab", sqlite3_unionvtab_init }, { "wholenumber", sqlite3_wholenumber_init }, +#ifdef SQLITE_HAVE_ZLIB + { "zipfile", sqlite3_zipfile_init }, +#endif }; sqlite3 *db; const char *zName; @@ -7576,6 +7611,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ { "sqlite3_open16", test_open16 ,0 }, { "sqlite3_open_v2", test_open_v2 ,0 }, { "sqlite3_complete16", test_complete16 ,0 }, + { "sqlite3_normalize", test_normalize ,0 }, { "sqlite3_prepare", test_prepare ,0 }, { "sqlite3_prepare16", test_prepare16 ,0 }, diff --git a/src/test_config.c b/src/test_config.c index 2083ebb381..c9f6e6941e 100644 --- a/src/test_config.c +++ b/src/test_config.c @@ -160,6 +160,12 @@ static void set_options(Tcl_Interp *interp){ Tcl_SetVar2(interp, "sqlite_options", "mem5", "0", TCL_GLOBAL_ONLY); #endif +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + Tcl_SetVar2(interp, "sqlite_options", "offset_sql_func","1",TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options", "offset_sql_func","0",TCL_GLOBAL_ONLY); +#endif + #ifdef SQLITE_ENABLE_PREUPDATE_HOOK Tcl_SetVar2(interp, "sqlite_options", "preupdate", "1", TCL_GLOBAL_ONLY); #else @@ -429,6 +435,12 @@ static void set_options(Tcl_Interp *interp){ Tcl_SetVar2(interp, "sqlite_options", "icu", "0", TCL_GLOBAL_ONLY); #endif +#ifdef SQLITE_ENABLE_ICU_COLLATIONS + Tcl_SetVar2(interp, "sqlite_options", "icu_collations", "1", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options", "icu_collations", "0", TCL_GLOBAL_ONLY); +#endif + #ifdef SQLITE_OMIT_INCRBLOB Tcl_SetVar2(interp, "sqlite_options", "incrblob", "0", TCL_GLOBAL_ONLY); #else @@ -489,6 +501,12 @@ Tcl_SetVar2(interp, "sqlite_options", "long_double", Tcl_SetVar2(interp, "sqlite_options", "mergesort", "1", TCL_GLOBAL_ONLY); +#ifdef SQLITE_ENABLE_NULL_TRIM + Tcl_SetVar2(interp, "sqlite_options", "null_trim", "1", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options", "null_trim", "0", TCL_GLOBAL_ONLY); +#endif + #ifdef SQLITE_OMIT_OR_OPTIMIZATION Tcl_SetVar2(interp, "sqlite_options", "or_opt", "0", TCL_GLOBAL_ONLY); #else @@ -702,6 +720,12 @@ Tcl_SetVar2(interp, "sqlite_options", "mergesort", "1", TCL_GLOBAL_ONLY); Tcl_SetVar2(interp, "sqlite_options", "unlock_notify", "0", TCL_GLOBAL_ONLY); #endif +#ifdef SQLITE_FAST_SECURE_DELETE + Tcl_SetVar2(interp, "sqlite_options", "fast_secure_delete", "1", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options", "fast_secure_delete", "0", TCL_GLOBAL_ONLY); +#endif + #ifdef SQLITE_SECURE_DELETE Tcl_SetVar2(interp, "sqlite_options", "secure_delete", "1", TCL_GLOBAL_ONLY); #else diff --git a/src/test_tclsh.c b/src/test_tclsh.c index 976f7cb248..97f7f5d7a1 100644 --- a/src/test_tclsh.c +++ b/src/test_tclsh.c @@ -104,6 +104,8 @@ const char *sqlite3TestInit(Tcl_Interp *interp){ #ifdef SQLITE_ENABLE_ZIPVFS extern int Zipvfs_Init(Tcl_Interp*); #endif + extern int TestExpert_Init(Tcl_Interp*); + Tcl_CmdInfo cmdInfo; /* Since the primary use case for this binary is testing of SQLite, @@ -166,6 +168,7 @@ const char *sqlite3TestInit(Tcl_Interp *interp){ #if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4) Sqlitetestfts3_Init(interp); #endif + TestExpert_Init(interp); Tcl_CreateObjCommand( interp, "load_testfixture_extensions", load_testfixture_extensions,0,0 diff --git a/src/test_windirent.c b/src/test_windirent.c index ca78d345d9..62165c4bea 100644 --- a/src/test_windirent.c +++ b/src/test_windirent.c @@ -14,7 +14,6 @@ */ #if defined(_WIN32) && defined(_MSC_VER) - #include "test_windirent.h" /* diff --git a/src/test_windirent.h b/src/test_windirent.h index 578e2a7c22..ada5322530 100644 --- a/src/test_windirent.h +++ b/src/test_windirent.h @@ -13,13 +13,17 @@ ** POSIX functions on Win32 using the MSVCRT. */ -#if defined(_WIN32) && defined(_MSC_VER) +#if defined(_WIN32) && defined(_MSC_VER) && !defined(SQLITE_WINDIRENT_H) +#define SQLITE_WINDIRENT_H /* ** We need several data types from the Windows SDK header. */ +#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN +#endif + #include "windows.h" /* @@ -37,6 +41,33 @@ #include #include #include +#include +#include + +/* +** We may need several defines that should have been in "sys/stat.h". +*/ + +#ifndef S_ISREG +#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#endif + +#ifndef S_ISDIR +#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif + +#ifndef S_ISLNK +#define S_ISLNK(mode) (0) +#endif + +/* +** We may need to provide the "mode_t" type. +*/ + +#ifndef MODE_T_DEFINED + #define MODE_T_DEFINED + typedef unsigned short mode_t; +#endif /* ** We may need to provide the "ino_t" type. @@ -75,22 +106,27 @@ ** We need to provide the necessary structures and related types. */ +#ifndef DIRENT_DEFINED +#define DIRENT_DEFINED typedef struct DIRENT DIRENT; -typedef struct DIR DIR; typedef DIRENT *LPDIRENT; -typedef DIR *LPDIR; - struct DIRENT { ino_t d_ino; /* Sequence number, do not use. */ unsigned d_attributes; /* Win32 file attributes. */ char d_name[NAME_MAX + 1]; /* Name within the directory. */ }; +#endif +#ifndef DIR_DEFINED +#define DIR_DEFINED +typedef struct DIR DIR; +typedef DIR *LPDIR; struct DIR { intptr_t d_handle; /* Value returned by "_findfirst". */ DIRENT d_first; /* DIRENT constructed based on "_findfirst". */ DIRENT d_next; /* DIRENT constructed based on "_findnext". */ }; +#endif /* ** Provide a macro, for use by the implementation, to determine if a diff --git a/src/tokenize.c b/src/tokenize.c index 2aab334ae9..e6da3fb547 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -526,7 +526,7 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ }else{ tokenType = TK_SEMI; } - zSql -= n; + n = 0; } if( tokenType>=TK_SPACE ){ assert( tokenType==TK_SPACE || tokenType==TK_ILLEGAL ); diff --git a/src/treeview.c b/src/treeview.c index ba9fa7b2f0..e33fe6e227 100644 --- a/src/treeview.c +++ b/src/treeview.c @@ -153,7 +153,6 @@ void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 moreToFollow){ if( p->pHaving ) n++; if( p->pOrderBy ) n++; if( p->pLimit ) n++; - if( p->pOffset ) n++; } sqlite3TreeViewExprList(pView, p->pEList, (n--)>0, "result-set"); if( p->pSrc && p->pSrc->nSrc ){ @@ -210,12 +209,12 @@ void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 moreToFollow){ } if( p->pLimit ){ sqlite3TreeViewItem(pView, "LIMIT", (n--)>0); - sqlite3TreeViewExpr(pView, p->pLimit, 0); - sqlite3TreeViewPop(pView); - } - if( p->pOffset ){ - sqlite3TreeViewItem(pView, "OFFSET", (n--)>0); - sqlite3TreeViewExpr(pView, p->pOffset, 0); + sqlite3TreeViewExpr(pView, p->pLimit->pLeft, p->pLimit->pRight!=0); + if( p->pLimit->pRight ){ + sqlite3TreeViewItem(pView, "OFFSET", (n--)>0); + sqlite3TreeViewExpr(pView, p->pLimit->pRight, 0); + sqlite3TreeViewPop(pView); + } sqlite3TreeViewPop(pView); } if( p->pPrior ){ @@ -293,6 +292,11 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ sqlite3TreeViewLine(pView,"NULL"); break; } + case TK_TRUEFALSE: { + sqlite3TreeViewLine(pView, + sqlite3ExprTruthValue(pExpr) ? "TRUE" : "FALSE"); + break; + } #ifndef SQLITE_OMIT_BLOB_LITERAL case TK_BLOB: { sqlite3TreeViewLine(pView,"%s", pExpr->u.zToken); @@ -349,6 +353,19 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ case TK_ISNULL: zUniOp = "ISNULL"; break; case TK_NOTNULL: zUniOp = "NOTNULL"; break; + case TK_TRUTH: { + int x; + const char *azOp[] = { + "IS-FALSE", "IS-TRUE", "IS-NOT-FALSE", "IS-NOT-TRUE" + }; + assert( pExpr->op2==TK_IS || pExpr->op2==TK_ISNOT ); + assert( pExpr->pRight ); + assert( pExpr->pRight->op==TK_TRUEFALSE ); + x = (pExpr->op2==TK_ISNOT)*2 + sqlite3ExprTruthValue(pExpr->pRight); + zUniOp = azOp[x]; + break; + } + case TK_SPAN: { sqlite3TreeViewLine(pView, "SPAN %Q", pExpr->u.zToken); sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); @@ -508,12 +525,20 @@ void sqlite3TreeViewBareExprList( sqlite3TreeViewLine(pView, "%s", zLabel); for(i=0; inExpr; i++){ int j = pList->a[i].u.x.iOrderByCol; - if( j ){ + char *zName = pList->a[i].zName; + if( j || zName ){ sqlite3TreeViewPush(pView, 0); + } + if( zName ){ + sqlite3TreeViewLine(pView, "AS %s", zName); + } + if( j ){ sqlite3TreeViewLine(pView, "iOrderByCol=%d", j); } sqlite3TreeViewExpr(pView, pList->a[i].pExpr, inExpr-1); - if( j ) sqlite3TreeViewPop(pView); + if( j || zName ){ + sqlite3TreeViewPop(pView); + } } } } diff --git a/src/trigger.c b/src/trigger.c index a17769ae98..9f7bff5052 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -25,6 +25,7 @@ void sqlite3DeleteTriggerStep(sqlite3 *db, TriggerStep *pTriggerStep){ sqlite3ExprListDelete(db, pTmp->pExprList); sqlite3SelectDelete(db, pTmp->pSelect); sqlite3IdListDelete(db, pTmp->pIdList); + sqlite3DbFree(db, pTmp->zSpan); sqlite3DbFree(db, pTmp); } @@ -339,6 +340,17 @@ triggerfinish_cleanup: sqlite3DeleteTriggerStep(db, pStepList); } +/* +** Duplicate a range of text from an SQL statement, then convert all +** whitespace characters into ordinary space characters. +*/ +static char *triggerSpanDup(sqlite3 *db, const char *zStart, const char *zEnd){ + char *z = sqlite3DbSpanDup(db, zStart, zEnd); + int i; + if( z ) for(i=0; z[i]; i++) if( sqlite3Isspace(z[i]) ) z[i] = ' '; + return z; +} + /* ** Turn a SELECT statement (that the pSelect parameter points to) into ** a trigger step. Return a pointer to a TriggerStep structure. @@ -346,7 +358,12 @@ triggerfinish_cleanup: ** The parser calls this routine when it finds a SELECT statement in ** body of a TRIGGER. */ -TriggerStep *sqlite3TriggerSelectStep(sqlite3 *db, Select *pSelect){ +TriggerStep *sqlite3TriggerSelectStep( + sqlite3 *db, /* Database connection */ + Select *pSelect, /* The SELECT statement */ + const char *zStart, /* Start of SQL text */ + const char *zEnd /* End of SQL text */ +){ TriggerStep *pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep)); if( pTriggerStep==0 ) { sqlite3SelectDelete(db, pSelect); @@ -355,6 +372,7 @@ TriggerStep *sqlite3TriggerSelectStep(sqlite3 *db, Select *pSelect){ pTriggerStep->op = TK_SELECT; pTriggerStep->pSelect = pSelect; pTriggerStep->orconf = OE_Default; + pTriggerStep->zSpan = triggerSpanDup(db, zStart, zEnd); return pTriggerStep; } @@ -367,7 +385,9 @@ TriggerStep *sqlite3TriggerSelectStep(sqlite3 *db, Select *pSelect){ static TriggerStep *triggerStepAllocate( sqlite3 *db, /* Database connection */ u8 op, /* Trigger opcode */ - Token *pName /* The target name */ + Token *pName, /* The target name */ + const char *zStart, /* Start of SQL text */ + const char *zEnd /* End of SQL text */ ){ TriggerStep *pTriggerStep; @@ -378,6 +398,7 @@ static TriggerStep *triggerStepAllocate( sqlite3Dequote(z); pTriggerStep->zTarget = z; pTriggerStep->op = op; + pTriggerStep->zSpan = triggerSpanDup(db, zStart, zEnd); } return pTriggerStep; } @@ -394,13 +415,15 @@ TriggerStep *sqlite3TriggerInsertStep( Token *pTableName, /* Name of the table into which we insert */ IdList *pColumn, /* List of columns in pTableName to insert into */ Select *pSelect, /* A SELECT statement that supplies values */ - u8 orconf /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */ + u8 orconf, /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */ + const char *zStart, /* Start of SQL text */ + const char *zEnd /* End of SQL text */ ){ TriggerStep *pTriggerStep; assert(pSelect != 0 || db->mallocFailed); - pTriggerStep = triggerStepAllocate(db, TK_INSERT, pTableName); + pTriggerStep = triggerStepAllocate(db, TK_INSERT, pTableName, zStart, zEnd); if( pTriggerStep ){ pTriggerStep->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); pTriggerStep->pIdList = pColumn; @@ -423,11 +446,13 @@ TriggerStep *sqlite3TriggerUpdateStep( Token *pTableName, /* Name of the table to be updated */ ExprList *pEList, /* The SET clause: list of column and new values */ Expr *pWhere, /* The WHERE clause */ - u8 orconf /* The conflict algorithm. (OE_Abort, OE_Ignore, etc) */ + u8 orconf, /* The conflict algorithm. (OE_Abort, OE_Ignore, etc) */ + const char *zStart, /* Start of SQL text */ + const char *zEnd /* End of SQL text */ ){ TriggerStep *pTriggerStep; - pTriggerStep = triggerStepAllocate(db, TK_UPDATE, pTableName); + pTriggerStep = triggerStepAllocate(db, TK_UPDATE, pTableName, zStart, zEnd); if( pTriggerStep ){ pTriggerStep->pExprList = sqlite3ExprListDup(db, pEList, EXPRDUP_REDUCE); pTriggerStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE); @@ -446,11 +471,13 @@ TriggerStep *sqlite3TriggerUpdateStep( TriggerStep *sqlite3TriggerDeleteStep( sqlite3 *db, /* Database connection */ Token *pTableName, /* The table from which rows are deleted */ - Expr *pWhere /* The WHERE clause */ + Expr *pWhere, /* The WHERE clause */ + const char *zStart, /* Start of SQL text */ + const char *zEnd /* End of SQL text */ ){ TriggerStep *pTriggerStep; - pTriggerStep = triggerStepAllocate(db, TK_DELETE, pTableName); + pTriggerStep = triggerStepAllocate(db, TK_DELETE, pTableName, zStart, zEnd); if( pTriggerStep ){ pTriggerStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE); pTriggerStep->orconf = OE_Default; @@ -705,13 +732,21 @@ static int codeTriggerProgram( pParse->eOrconf = (orconf==OE_Default)?pStep->orconf:(u8)orconf; assert( pParse->okConstFactor==0 ); +#ifndef SQLITE_OMIT_TRACE + if( pStep->zSpan ){ + sqlite3VdbeAddOp4(v, OP_Trace, 0x7fffffff, 1, 0, + sqlite3MPrintf(db, "-- %s", pStep->zSpan), + P4_DYNAMIC); + } +#endif + switch( pStep->op ){ case TK_UPDATE: { sqlite3Update(pParse, targetSrcList(pParse, pStep), sqlite3ExprListDup(db, pStep->pExprList, 0), sqlite3ExprDup(db, pStep->pWhere, 0), - pParse->eOrconf, 0, 0, 0 + pParse->eOrconf, 0, 0 ); break; } @@ -727,7 +762,7 @@ static int codeTriggerProgram( case TK_DELETE: { sqlite3DeleteFrom(pParse, targetSrcList(pParse, pStep), - sqlite3ExprDup(db, pStep->pWhere, 0), 0, 0, 0 + sqlite3ExprDup(db, pStep->pWhere, 0), 0, 0 ); break; } @@ -845,9 +880,11 @@ static TriggerPrg *codeRowTrigger( pTab->zName )); #ifndef SQLITE_OMIT_TRACE - sqlite3VdbeChangeP4(v, -1, - sqlite3MPrintf(db, "-- TRIGGER %s", pTrigger->zName), P4_DYNAMIC - ); + if( pTrigger->zName ){ + sqlite3VdbeChangeP4(v, -1, + sqlite3MPrintf(db, "-- TRIGGER %s", pTrigger->zName), P4_DYNAMIC + ); + } #endif /* If one was specified, code the WHEN clause. If it evaluates to false @@ -875,7 +912,7 @@ static TriggerPrg *codeRowTrigger( VdbeComment((v, "End: %s.%s", pTrigger->zName, onErrorText(orconf))); transferParseError(pParse, pSubParse); - if( db->mallocFailed==0 ){ + if( db->mallocFailed==0 && pParse->nErr==0 ){ pProgram->aOp = sqlite3VdbeTakeOpArray(v, &pProgram->nOp, &pTop->nMaxArg); } pProgram->nMem = pSubParse->nMem; diff --git a/src/update.c b/src/update.c index 4cfa4019eb..32c1a8371a 100644 --- a/src/update.c +++ b/src/update.c @@ -93,8 +93,7 @@ void sqlite3Update( Expr *pWhere, /* The WHERE clause. May be null */ int onError, /* How to handle constraint errors */ ExprList *pOrderBy, /* ORDER BY clause. May be null */ - Expr *pLimit, /* LIMIT clause. May be null */ - Expr *pOffset /* OFFSET clause. May be null */ + Expr *pLimit /* LIMIT clause. May be null */ ){ int i, j; /* Loop counters */ Table *pTab; /* The table to be updated */ @@ -182,10 +181,10 @@ void sqlite3Update( #ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT if( !isView ){ pWhere = sqlite3LimitWhere( - pParse, pTabList, pWhere, pOrderBy, pLimit, pOffset, "UPDATE" + pParse, pTabList, pWhere, pOrderBy, pLimit, "UPDATE" ); pOrderBy = 0; - pLimit = pOffset = 0; + pLimit = 0; } #endif @@ -358,10 +357,10 @@ void sqlite3Update( #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) if( isView ){ sqlite3MaterializeView(pParse, pTab, - pWhere, pOrderBy, pLimit, pOffset, iDataCur + pWhere, pOrderBy, pLimit, iDataCur ); pOrderBy = 0; - pLimit = pOffset = 0; + pLimit = 0; } #endif @@ -748,7 +747,6 @@ update_cleanup: #if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) sqlite3ExprListDelete(db, pOrderBy); sqlite3ExprDelete(db, pLimit); - sqlite3ExprDelete(db, pOffset); #endif return; } @@ -809,7 +807,7 @@ static void updateVirtualTable( int bOnePass; /* True to use onepass strategy */ int addr; /* Address of OP_OpenEphemeral */ - /* Allocate nArg registers to martial the arguments to VUpdate. Then + /* Allocate nArg registers in which to gather the arguments for VUpdate. Then ** create and open the ephemeral table in which the records created from ** these arguments will be temporarily stored. */ assert( v ); @@ -830,6 +828,7 @@ static void updateVirtualTable( sqlite3ExprCode(pParse, pChanges->a[aXRef[i]].pExpr, regArg+2+i); }else{ sqlite3VdbeAddOp3(v, OP_VColumn, iCsr, i, regArg+2+i); + sqlite3VdbeChangeP5(v, 1); /* Enable sqlite3_vtab_nochange() */ } } if( HasRowid(pTab) ){ @@ -864,6 +863,11 @@ static void updateVirtualTable( /* Create a record from the argument register contents and insert it into ** the ephemeral table. */ sqlite3VdbeAddOp3(v, OP_MakeRecord, regArg, nArg, regRec); +#ifdef SQLITE_DEBUG + /* Signal an assert() within OP_MakeRecord that it is allowed to + ** accept no-change records with serial_type 10 */ + sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG_MAGIC); +#endif sqlite3VdbeAddOp2(v, OP_NewRowid, ephemTab, regRowid); sqlite3VdbeAddOp3(v, OP_Insert, ephemTab, regRec, regRowid); } diff --git a/src/util.c b/src/util.c index a4dbe8fdaf..54f9b93887 100644 --- a/src/util.c +++ b/src/util.c @@ -320,6 +320,45 @@ int sqlite3_strnicmp(const char *zLeft, const char *zRight, int N){ return N<0 ? 0 : UpperToLower[*a] - UpperToLower[*b]; } +/* +** Compute 10 to the E-th power. Examples: E==1 results in 10. +** E==2 results in 100. E==50 results in 1.0e50. +** +** This routine only works for values of E between 1 and 341. +*/ +static LONGDOUBLE_TYPE sqlite3Pow10(int E){ +#if defined(_MSC_VER) + static const LONGDOUBLE_TYPE x[] = { + 1.0e+001, + 1.0e+002, + 1.0e+004, + 1.0e+008, + 1.0e+016, + 1.0e+032, + 1.0e+064, + 1.0e+128, + 1.0e+256 + }; + LONGDOUBLE_TYPE r = 1.0; + int i; + assert( E>=0 && E<=307 ); + for(i=0; E!=0; i++, E >>=1){ + if( E & 1 ) r *= x[i]; + } + return r; +#else + LONGDOUBLE_TYPE x = 10.0; + LONGDOUBLE_TYPE r = 1.0; + while(1){ + if( E & 1 ) r *= x; + E >>= 1; + if( E==0 ) break; + x *= x; + } + return r; +#endif +} + /* ** The string z[] is an text representation of a real number. ** Convert this string to a double and write it into *pResult. @@ -475,11 +514,10 @@ do_atof_calc: if( e==0 ){ /*OPTIMIZATION-IF-TRUE*/ result = (double)s; }else{ - LONGDOUBLE_TYPE scale = 1.0; /* attempt to handle extremely small/large numbers better */ if( e>307 ){ /*OPTIMIZATION-IF-TRUE*/ if( e<342 ){ /*OPTIMIZATION-IF-TRUE*/ - while( e%308 ) { scale *= 1.0e+1; e -= 1; } + LONGDOUBLE_TYPE scale = sqlite3Pow10(e-308); if( esign<0 ){ result = s / scale; result /= 1.0e+308; @@ -499,10 +537,7 @@ do_atof_calc: } } }else{ - /* 1.0e+22 is the largest power of 10 than can be - ** represented exactly. */ - while( e%22 ) { scale *= 1.0e+1; e -= 1; } - while( e>0 ) { scale *= 1.0e+22; e -= 22; } + LONGDOUBLE_TYPE scale = sqlite3Pow10(e); if( esign<0 ){ result = s / scale; }else{ @@ -560,7 +595,7 @@ static int compare2pow63(const char *zNum, int incr){ ** Returns: ** ** 0 Successful transformation. Fits in a 64-bit signed integer. -** 1 Excess text after the integer value +** 1 Excess non-space text after the integer value ** 2 Integer too large for a 64-bit signed integer or is malformed ** 3 Special case of 9223372036854775808 ** @@ -603,47 +638,57 @@ int sqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc){ for(i=0; &zNum[i]='0' && c<='9'; i+=incr){ u = u*10 + c - '0'; } + testcase( i==18*incr ); + testcase( i==19*incr ); + testcase( i==20*incr ); if( u>LARGEST_INT64 ){ + /* This test and assignment is needed only to suppress UB warnings + ** from clang and -fsanitize=undefined. This test and assignment make + ** the code a little larger and slower, and no harm comes from omitting + ** them, but we must appaise the undefined-behavior pharisees. */ *pNum = neg ? SMALLEST_INT64 : LARGEST_INT64; }else if( neg ){ *pNum = -(i64)u; }else{ *pNum = (i64)u; } - testcase( i==18 ); - testcase( i==19 ); - testcase( i==20 ); - if( &zNum[i]19*incr ){ /* Too many digits */ - /* zNum is empty or contains non-numeric text or is longer - ** than 19 digits (thus guaranteeing that it is too large) */ - return 2; - }else if( i<19*incr ){ + if( i<19*incr ){ /* Less than 19 digits, so we know that it fits in 64 bits */ assert( u<=LARGEST_INT64 ); return rc; }else{ /* zNum is a 19-digit numbers. Compare it against 9223372036854775808. */ - c = compare2pow63(zNum, incr); + c = i>19*incr ? 1 : compare2pow63(zNum, incr); if( c<0 ){ /* zNum is less than 9223372036854775808 so it fits */ assert( u<=LARGEST_INT64 ); return rc; - }else if( c>0 ){ - /* zNum is greater than 9223372036854775808 so it overflows */ - return 2; }else{ - /* zNum is exactly 9223372036854775808. Fits if negative. The - ** special case 2 overflow if positive */ - assert( u-1==LARGEST_INT64 ); - return neg ? rc : 3; + *pNum = neg ? SMALLEST_INT64 : LARGEST_INT64; + if( c>0 ){ + /* zNum is greater than 9223372036854775808 so it overflows */ + return 2; + }else{ + /* zNum is exactly 9223372036854775808. Fits if negative. The + ** special case 2 overflow if positive */ + assert( u-1==LARGEST_INT64 ); + return neg ? rc : 3; + } } } } diff --git a/src/vacuum.c b/src/vacuum.c index a94328e1a9..4219e010ed 100644 --- a/src/vacuum.c +++ b/src/vacuum.c @@ -39,8 +39,8 @@ static int execSql(sqlite3 *db, char **pzErrMsg, const char *zSql){ 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' ); + assert( sqlite3_strnicmp(zSubSql,"SELECT",6)!=0 || CORRUPT_DB ); + if( zSubSql && zSubSql[0]!='S' ){ rc = execSql(db, pzErrMsg, zSubSql); if( rc!=SQLITE_OK ) break; } diff --git a/src/vdbe.c b/src/vdbe.c index f250661aea..cb12332df8 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -264,6 +264,11 @@ static void applyNumericAffinity(Mem *pRec, int bTryForInt){ pRec->flags |= MEM_Real; if( bTryForInt ) sqlite3VdbeIntegerAffinity(pRec); } + /* TEXT->NUMERIC is many->one. Hence, it is important to invalidate the + ** string representation after computing a numeric equivalent, because the + ** string representation might not be the canonical representation for the + ** numeric value. Ticket [343634942dd54ab57b7024] 2018-01-31. */ + pRec->flags &= ~MEM_Str; } /* @@ -464,7 +469,7 @@ static void memTracePrint(Mem *p){ if( p->flags & MEM_Undefined ){ printf(" undefined"); }else if( p->flags & MEM_Null ){ - printf(" NULL"); + printf(p->flags & MEM_Zero ? " NULL-nochng" : " NULL"); }else if( (p->flags & (MEM_Int|MEM_Str))==(MEM_Int|MEM_Str) ){ printf(" si:%lld", p->u.i); }else if( p->flags & MEM_Int ){ @@ -643,7 +648,7 @@ int sqlite3VdbeExec( assert( pOp>=aOp && pOp<&aOp[p->nOp]); #ifdef VDBE_PROFILE - start = sqlite3Hwtime(); + start = sqlite3NProfileCnt ? sqlite3NProfileCnt : sqlite3Hwtime(); #endif nVmStep++; #ifdef SQLITE_ENABLE_STMT_SCANSTATUS @@ -2167,18 +2172,8 @@ case OP_Or: { /* same as TK_OR, in1, in2, out3 */ int v1; /* Left operand: 0==FALSE, 1==TRUE, 2==UNKNOWN or NULL */ int v2; /* Right operand: 0==FALSE, 1==TRUE, 2==UNKNOWN or NULL */ - pIn1 = &aMem[pOp->p1]; - if( pIn1->flags & MEM_Null ){ - v1 = 2; - }else{ - v1 = sqlite3VdbeIntValue(pIn1)!=0; - } - pIn2 = &aMem[pOp->p2]; - if( pIn2->flags & MEM_Null ){ - v2 = 2; - }else{ - v2 = sqlite3VdbeIntValue(pIn2)!=0; - } + v1 = sqlite3VdbeBooleanValue(&aMem[pOp->p1], 2); + v2 = sqlite3VdbeBooleanValue(&aMem[pOp->p2], 2); if( pOp->opcode==OP_And ){ static const unsigned char and_logic[] = { 0, 0, 0, 0, 1, 2, 0, 2, 2 }; v1 = and_logic[v1*3+v2]; @@ -2196,6 +2191,35 @@ case OP_Or: { /* same as TK_OR, in1, in2, out3 */ break; } +/* Opcode: IsTrue P1 P2 P3 P4 * +** Synopsis: r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4 +** +** This opcode implements the IS TRUE, IS FALSE, IS NOT TRUE, and +** IS NOT FALSE operators. +** +** Interpret the value in register P1 as a boolean value. Store that +** boolean (a 0 or 1) in register P2. Or if the value in register P1 is +** NULL, then the P3 is stored in register P2. Invert the answer if P4 +** is 1. +** +** The logic is summarized like this: +** +**

    +**
  • If P3==0 and P4==0 then r[P2] := r[P1] IS TRUE +**
  • If P3==1 and P4==1 then r[P2] := r[P1] IS FALSE +**
  • If P3==0 and P4==1 then r[P2] := r[P1] IS NOT TRUE +**
  • If P3==1 and P4==0 then r[P2] := r[P1] IS NOT FALSE +**
+*/ +case OP_IsTrue: { /* in1, out2 */ + assert( pOp->p4type==P4_INT32 ); + assert( pOp->p4.i==0 || pOp->p4.i==1 ); + assert( pOp->p3==0 || pOp->p3==1 ); + sqlite3VdbeMemSetInt64(&aMem[pOp->p2], + sqlite3VdbeBooleanValue(&aMem[pOp->p1], pOp->p3) ^ pOp->p4.i); + break; +} + /* Opcode: Not P1 P2 * * * ** Synopsis: r[P2]= !r[P1] ** @@ -2206,10 +2230,10 @@ case OP_Or: { /* same as TK_OR, in1, in2, out3 */ case OP_Not: { /* same as TK_NOT, in1, out2 */ pIn1 = &aMem[pOp->p1]; pOut = &aMem[pOp->p2]; - sqlite3VdbeMemSetNull(pOut); if( (pIn1->flags & MEM_Null)==0 ){ - pOut->flags = MEM_Int; - pOut->u.i = !sqlite3VdbeIntValue(pIn1); + sqlite3VdbeMemSetInt64(pOut, !sqlite3VdbeBooleanValue(pIn1,0)); + }else{ + sqlite3VdbeMemSetNull(pOut); } break; } @@ -2276,30 +2300,25 @@ case OP_Once: { /* jump */ ** is considered true if it is numeric and non-zero. If the value ** in P1 is NULL then take the jump if and only if P3 is non-zero. */ +case OP_If: { /* jump, in1 */ + int c; + c = sqlite3VdbeBooleanValue(&aMem[pOp->p1], pOp->p3); + VdbeBranchTaken(c!=0, 2); + if( c ) goto jump_to_p2; + break; +} + /* Opcode: IfNot P1 P2 P3 * * ** ** Jump to P2 if the value in register P1 is False. The value ** is considered false if it has a numeric value of zero. If the value ** in P1 is NULL then take the jump if and only if P3 is non-zero. */ -case OP_If: /* jump, in1 */ case OP_IfNot: { /* jump, in1 */ int c; - pIn1 = &aMem[pOp->p1]; - if( pIn1->flags & MEM_Null ){ - c = pOp->p3; - }else{ -#ifdef SQLITE_OMIT_FLOATING_POINT - c = sqlite3VdbeIntValue(pIn1)!=0; -#else - c = sqlite3VdbeRealValue(pIn1)!=0.0; -#endif - if( pOp->opcode==OP_IfNot ) c = !c; - } + c = !sqlite3VdbeBooleanValue(&aMem[pOp->p1], !pOp->p3); VdbeBranchTaken(c!=0, 2); - if( c ){ - goto jump_to_p2; - } + if( c ) goto jump_to_p2; break; } @@ -2349,6 +2368,36 @@ case OP_IfNullRow: { /* jump */ break; } +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC +/* Opcode: Offset P1 P2 P3 * * +** Synopsis: r[P3] = sqlite_offset(P1) +** +** Store in register r[P3] the byte offset into the database file that is the +** start of the payload for the record at which that cursor P1 is currently +** pointing. +** +** P2 is the column number for the argument to the sqlite_offset() function. +** This opcode does not use P2 itself, but the P2 value is used by the +** code generator. The P1, P2, and P3 operands to this opcode are the +** same as for OP_Column. +** +** This opcode is only available if SQLite is compiled with the +** -DSQLITE_ENABLE_OFFSET_SQL_FUNC option. +*/ +case OP_Offset: { /* out3 */ + VdbeCursor *pC; /* The VDBE cursor */ + assert( pOp->p1>=0 && pOp->p1nCursor ); + pC = p->apCsr[pOp->p1]; + pOut = &p->aMem[pOp->p3]; + if( NEVER(pC==0) || pC->eCurType!=CURTYPE_BTREE ){ + sqlite3VdbeMemSetNull(pOut); + }else{ + sqlite3VdbeMemSetInt64(pOut, sqlite3BtreeOffset(pC->uc.pCursor)); + } + break; +} +#endif /* SQLITE_ENABLE_OFFSET_SQL_FUNC */ + /* Opcode: Column P1 P2 P3 P4 P5 ** Synopsis: r[P3]=PX ** @@ -2762,9 +2811,18 @@ case OP_MakeRecord: { pRec = pLast; do{ assert( memIsValid(pRec) ); - pRec->uTemp = serial_type = sqlite3VdbeSerialType(pRec, file_format, &len); + serial_type = sqlite3VdbeSerialType(pRec, file_format, &len); if( pRec->flags & MEM_Zero ){ - if( nData ){ + if( serial_type==0 ){ + /* Values with MEM_Null and MEM_Zero are created by xColumn virtual + ** table methods that never invoke sqlite3_result_xxxxx() while + ** computing an unchanging column value in an UPDATE statement. + ** Give such values a special internal-use-only serial-type of 10 + ** so that they can be passed through to xUpdate and have + ** a true sqlite3_value_nochange(). */ + assert( pOp->p5==OPFLAG_NOCHNG_MAGIC || CORRUPT_DB ); + serial_type = 10; + }else if( nData ){ if( sqlite3VdbeMemExpandBlob(pRec) ) goto no_mem; }else{ nZero += pRec->u.nZero; @@ -2775,6 +2833,7 @@ case OP_MakeRecord: { testcase( serial_type==127 ); testcase( serial_type==128 ); nHdr += serial_type<=127 ? 1 : sqlite3VarintLen(serial_type); + pRec->uTemp = serial_type; if( pRec==pData0 ) break; pRec--; }while(1); @@ -4416,10 +4475,8 @@ case OP_InsertInt: { int seekResult; /* Result of prior seek or 0 if no USESEEKRESULT flag */ const char *zDb; /* database name - used by the update hook */ Table *pTab; /* Table structure - used by update and pre-update hooks */ - int op; /* Opcode for update hook: SQLITE_UPDATE or SQLITE_INSERT */ BtreePayload x; /* Payload to be inserted */ - op = 0; pData = &aMem[pOp->p2]; assert( pOp->p1>=0 && pOp->p1nCursor ); assert( memIsValid(pData) ); @@ -4447,19 +4504,21 @@ case OP_InsertInt: { zDb = db->aDb[pC->iDb].zDbSName; pTab = pOp->p4.pTab; assert( (pOp->p5 & OPFLAG_ISNOOP) || HasRowid(pTab) ); - op = ((pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT); }else{ - pTab = 0; /* Not needed. Silence a compiler warning. */ + pTab = 0; zDb = 0; /* Not needed. Silence a compiler warning. */ } #ifdef SQLITE_ENABLE_PREUPDATE_HOOK /* Invoke the pre-update hook, if any */ - if( db->xPreUpdateCallback - && pOp->p4type==P4_TABLE - && !(pOp->p5 & OPFLAG_ISUPDATE) - ){ - sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, pTab, x.nKey, pOp->p2); + if( pTab ){ + if( db->xPreUpdateCallback && !(pOp->p5 & OPFLAG_ISUPDATE) ){ + sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, pTab, x.nKey,pOp->p2); + } + if( db->xUpdateCallback==0 || pTab->aCol==0 ){ + /* Prevent post-update hook from running in cases when it should not */ + pTab = 0; + } } if( pOp->p5 & OPFLAG_ISNOOP ) break; #endif @@ -4484,8 +4543,12 @@ case OP_InsertInt: { /* Invoke the update-hook if required. */ if( rc ) goto abort_due_to_error; - if( db->xUpdateCallback && op ){ - db->xUpdateCallback(db->pUpdateArg, op, zDb, pTab->zName, x.nKey); + if( pTab ){ + assert( db->xUpdateCallback!=0 ); + assert( pTab->aCol!=0 ); + db->xUpdateCallback(db->pUpdateArg, + (pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT, + zDb, pTab->zName, x.nKey); } break; } @@ -6191,12 +6254,17 @@ case OP_AggStep0: { assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); assert( n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem+1 - p->nCursor)+1) ); assert( pOp->p3p2 || pOp->p3>=pOp->p2+n ); - pCtx = sqlite3DbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sqlite3_value*)); + pCtx = sqlite3DbMallocRawNN(db, n*sizeof(sqlite3_value*) + + (sizeof(pCtx[0]) + sizeof(Mem) - sizeof(sqlite3_value*))); if( pCtx==0 ) goto no_mem; pCtx->pMem = 0; + pCtx->pOut = (Mem*)&(pCtx->argv[n]); + sqlite3VdbeMemInit(pCtx->pOut, db, MEM_Null); pCtx->pFunc = pOp->p4.pFunc; pCtx->iOp = (int)(pOp - aOp); pCtx->pVdbe = p; + pCtx->skipFlag = 0; + pCtx->isError = 0; pCtx->argc = n; pOp->p4type = P4_FUNCCTX; pOp->p4.pCtx = pCtx; @@ -6207,7 +6275,6 @@ case OP_AggStep: { int i; sqlite3_context *pCtx; Mem *pMem; - Mem t; assert( pOp->p4type==P4_FUNCCTX ); pCtx = pOp->p4.pCtx; @@ -6230,26 +6297,28 @@ case OP_AggStep: { #endif pMem->n++; - sqlite3VdbeMemInit(&t, db, MEM_Null); - pCtx->pOut = &t; - pCtx->fErrorOrAux = 0; - pCtx->skipFlag = 0; + assert( pCtx->pOut->flags==MEM_Null ); + assert( pCtx->isError==0 ); + assert( pCtx->skipFlag==0 ); (pCtx->pFunc->xSFunc)(pCtx,pCtx->argc,pCtx->argv); /* IMP: R-24505-23230 */ - if( pCtx->fErrorOrAux ){ - if( pCtx->isError ){ - sqlite3VdbeError(p, "%s", sqlite3_value_text(&t)); + if( pCtx->isError ){ + if( pCtx->isError>0 ){ + sqlite3VdbeError(p, "%s", sqlite3_value_text(pCtx->pOut)); rc = pCtx->isError; } - sqlite3VdbeMemRelease(&t); + if( pCtx->skipFlag ){ + assert( pOp[-1].opcode==OP_CollSeq ); + i = pOp[-1].p1; + if( i ) sqlite3VdbeMemSetInt64(&aMem[i], 1); + pCtx->skipFlag = 0; + } + sqlite3VdbeMemRelease(pCtx->pOut); + pCtx->pOut->flags = MEM_Null; + pCtx->isError = 0; if( rc ) goto abort_due_to_error; - }else{ - assert( t.flags==MEM_Null ); - } - if( pCtx->skipFlag ){ - assert( pOp[-1].opcode==OP_CollSeq ); - i = pOp[-1].p1; - if( i ) sqlite3VdbeMemSetInt64(&aMem[i], 1); } + assert( pCtx->pOut->flags==MEM_Null ); + assert( pCtx->skipFlag==0 ); break; } @@ -6702,12 +6771,18 @@ case OP_VFilter: { /* jump */ #endif /* SQLITE_OMIT_VIRTUALTABLE */ #ifndef SQLITE_OMIT_VIRTUALTABLE -/* Opcode: VColumn P1 P2 P3 * * +/* Opcode: VColumn P1 P2 P3 * P5 ** Synopsis: r[P3]=vcolumn(P2) ** -** Store the value of the P2-th column of -** the row of the virtual-table that the -** P1 cursor is pointing to into register P3. +** Store in register P3 the value of the P2-th column of +** the current row of the virtual-table of cursor P1. +** +** If the VColumn opcode is being used to fetch the value of +** an unchanging column during an UPDATE operation, then the P5 +** value is 1. Otherwise, P5 is 0. The P5 value is returned +** by sqlite3_vtab_nochange() routine can can be used +** by virtual table implementations to return special "no-change" +** marks which can be more efficient, depending on the virtual table. */ case OP_VColumn: { sqlite3_vtab *pVtab; @@ -6729,10 +6804,17 @@ case OP_VColumn: { assert( pModule->xColumn ); memset(&sContext, 0, sizeof(sContext)); sContext.pOut = pDest; - MemSetTypeFlag(pDest, MEM_Null); + if( pOp->p5 ){ + sqlite3VdbeMemSetNull(pDest); + pDest->flags = MEM_Null|MEM_Zero; + pDest->u.nZero = 0; + }else{ + MemSetTypeFlag(pDest, MEM_Null); + } rc = pModule->xColumn(pCur->uc.pVCur, &sContext, pOp->p2); sqlite3VtabImportErrmsg(p, pVtab); - if( sContext.isError ){ + if( sContext.isError>0 ){ + sqlite3VdbeError(p, "%s", sqlite3_value_text(pDest)); rc = sContext.isError; } sqlite3VdbeChangeEncoding(pDest, encoding); @@ -6997,6 +7079,7 @@ case OP_Function0: { pCtx->pFunc = pOp->p4.pFunc; pCtx->iOp = (int)(pOp - aOp); pCtx->pVdbe = p; + pCtx->isError = 0; pCtx->argc = n; pOp->p4type = P4_FUNCCTX; pOp->p4.pCtx = pCtx; @@ -7031,16 +7114,17 @@ case OP_Function: { } #endif MemSetTypeFlag(pOut, MEM_Null); - pCtx->fErrorOrAux = 0; + assert( pCtx->isError==0 ); (*pCtx->pFunc->xSFunc)(pCtx, pCtx->argc, pCtx->argv);/* IMP: R-24505-23230 */ /* If the function returned an error, throw an exception */ - if( pCtx->fErrorOrAux ){ - if( pCtx->isError ){ + if( pCtx->isError ){ + if( pCtx->isError>0 ){ sqlite3VdbeError(p, "%s", sqlite3_value_text(pOut)); rc = pCtx->isError; } sqlite3VdbeDeleteAuxData(db, &p->pAuxData, pCtx->iOp, pOp->p1); + pCtx->isError = 0; if( rc ) goto abort_due_to_error; } @@ -7055,7 +7139,13 @@ case OP_Function: { break; } - +/* Opcode: Trace P1 P2 * P4 * +** +** Write P4 on the statement trace output if statement tracing is +** enabled. +** +** Operand P1 must be 0x7fffffff and P2 must positive. +*/ /* Opcode: Init P1 P2 P3 P4 * ** Synopsis: Start at P2 ** @@ -7074,9 +7164,12 @@ case OP_Function: { ** If P3 is not zero, then it is an address to jump to if an SQLITE_CORRUPT ** error is encountered. */ +case OP_Trace: case OP_Init: { /* jump */ - char *zTrace; int i; +#ifndef SQLITE_OMIT_TRACE + char *zTrace; +#endif /* 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. @@ -7088,7 +7181,9 @@ 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 */ + + /* OP_Init is always instruction 0 */ + assert( pOp==p->aOp || pOp->opcode==OP_Trace ); #ifndef SQLITE_OMIT_TRACE if( (db->mTrace & (SQLITE_TRACE_STMT|SQLITE_TRACE_LEGACY))!=0 @@ -7131,6 +7226,7 @@ case OP_Init: { /* jump */ #endif /* SQLITE_OMIT_TRACE */ assert( pOp->p2>0 ); if( pOp->p1>=sqlite3GlobalConfig.iOnceResetThreshold ){ + if( pOp->opcode==OP_Trace ) break; for(i=1; inOp; i++){ if( p->aOp[i].opcode==OP_Once ) p->aOp[i].p1 = 0; } @@ -7190,7 +7286,7 @@ default: { /* This is really OP_Noop and OP_Explain */ #ifdef VDBE_PROFILE { - u64 endTime = sqlite3Hwtime(); + u64 endTime = sqlite3NProfileCnt ? sqlite3NProfileCnt : sqlite3Hwtime(); if( endTime>start ) pOrigOp->cycles += endTime - start; pOrigOp->cnt++; } diff --git a/src/vdbe.h b/src/vdbe.h index 3e77eb9db5..f002e05d81 100644 --- a/src/vdbe.h +++ b/src/vdbe.h @@ -127,6 +127,7 @@ typedef struct VdbeOpList VdbeOpList; #define P4_INT64 (-14) /* P4 is a 64-bit signed integer */ #define P4_INTARRAY (-15) /* P4 is a vector of 32-bit integers */ #define P4_FUNCCTX (-16) /* P4 is a pointer to an sqlite3_context object */ +#define P4_DYNBLOB (-17) /* Pointer to memory from sqliteMalloc() */ /* Error message codes for OP_Halt */ #define P5_ConstraintNotNull 1 diff --git a/src/vdbeInt.h b/src/vdbeInt.h index cb783653c0..44f901abf7 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -317,7 +317,6 @@ struct sqlite3_context { int iOp; /* Instruction number of OP_Function */ int isError; /* Error code returned by the function. */ u8 skipFlag; /* Skip accumulator loading if true */ - u8 fErrorOrAux; /* isError!=0 or pVdbe->pAuxData modified */ u8 argc; /* Number of arguments */ sqlite3_value *argv[1]; /* Argument set */ }; @@ -487,6 +486,7 @@ int sqlite3VdbeMemStringify(Mem*, u8, u8); i64 sqlite3VdbeIntValue(Mem*); int sqlite3VdbeMemIntegerify(Mem*); double sqlite3VdbeRealValue(Mem*); +int sqlite3VdbeBooleanValue(Mem*, int ifNull); void sqlite3VdbeIntegerAffinity(Mem*); int sqlite3VdbeMemRealify(Mem*); int sqlite3VdbeMemNumerify(Mem*); diff --git a/src/vdbeapi.c b/src/vdbeapi.c index b9df40b8fd..dd4a352003 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -268,6 +268,11 @@ int sqlite3_value_type(sqlite3_value* pVal){ return aType[pVal->flags&MEM_AffMask]; } +/* Return true if a parameter to xUpdate represents an unchanged column */ +int sqlite3_value_nochange(sqlite3_value *pVal){ + return (pVal->flags&(MEM_Null|MEM_Zero))==(MEM_Null|MEM_Zero); +} + /* Make a copy of an sqlite3_value object */ sqlite3_value *sqlite3_value_dup(const sqlite3_value *pOrig){ @@ -367,14 +372,12 @@ void sqlite3_result_double(sqlite3_context *pCtx, double rVal){ void sqlite3_result_error(sqlite3_context *pCtx, const char *z, int n){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); pCtx->isError = SQLITE_ERROR; - pCtx->fErrorOrAux = 1; sqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF8, SQLITE_TRANSIENT); } #ifndef SQLITE_OMIT_UTF16 void sqlite3_result_error16(sqlite3_context *pCtx, const void *z, int n){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); pCtx->isError = SQLITE_ERROR; - pCtx->fErrorOrAux = 1; sqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF16NATIVE, SQLITE_TRANSIENT); } #endif @@ -480,8 +483,7 @@ int sqlite3_result_zeroblob64(sqlite3_context *pCtx, u64 n){ return SQLITE_OK; } void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){ - pCtx->isError = errCode; - pCtx->fErrorOrAux = 1; + pCtx->isError = errCode ? errCode : -1; #ifdef SQLITE_DEBUG if( pCtx->pVdbe ) pCtx->pVdbe->rcApp = errCode; #endif @@ -495,7 +497,6 @@ void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){ void sqlite3_result_error_toobig(sqlite3_context *pCtx){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); pCtx->isError = SQLITE_TOOBIG; - pCtx->fErrorOrAux = 1; sqlite3VdbeMemSetStr(pCtx->pOut, "string or blob too big", -1, SQLITE_UTF8, SQLITE_STATIC); } @@ -505,7 +506,6 @@ void sqlite3_result_error_nomem(sqlite3_context *pCtx){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); sqlite3VdbeMemSetNull(pCtx->pOut); pCtx->isError = SQLITE_NOMEM_BKPT; - pCtx->fErrorOrAux = 1; sqlite3OomFault(pCtx->pOut->db); } @@ -745,6 +745,25 @@ sqlite3 *sqlite3_context_db_handle(sqlite3_context *p){ return p->pOut->db; } +/* +** If this routine is invoked from within an xColumn method of a virtual +** table, then it returns true if and only if the the call is during an +** UPDATE operation and the value of the column will not be modified +** by the UPDATE. +** +** If this routine is called from any context other than within the +** xColumn method of a virtual table, then the return value is meaningless +** and arbitrary. +** +** Virtual table implements might use this routine to optimize their +** performance by substituting a NULL result, or some other light-weight +** value, as a signal to the xUpdate routine that the column is unchanged. +*/ +int sqlite3_vtab_nochange(sqlite3_context *p){ + assert( p ); + return sqlite3_value_nochange(p->pOut); +} + /* ** Return the current time for a statement. If the current time ** is requested more than once within the same run of a single prepared @@ -893,10 +912,7 @@ void sqlite3_set_auxdata( pAuxData->iAuxArg = iArg; pAuxData->pNextAux = pVdbe->pAuxData; pVdbe->pAuxData = pAuxData; - if( pCtx->fErrorOrAux==0 ){ - pCtx->isError = 0; - pCtx->fErrorOrAux = 1; - } + if( pCtx->isError==0 ) pCtx->isError = -1; }else if( pAuxData->xDeleteAux ){ pAuxData->xDeleteAux(pAuxData->pAux); } diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 30c616ddb1..ada03f05d4 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -866,6 +866,7 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){ case P4_REAL: case P4_INT64: case P4_DYNAMIC: + case P4_DYNBLOB: case P4_INTARRAY: { sqlite3DbFree(db, p4); break; @@ -1407,6 +1408,7 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){ sqlite3XPrintf(&x, "program"); break; } + case P4_DYNBLOB: case P4_ADVANCE: { zTemp[0] = 0; break; @@ -1639,6 +1641,8 @@ int sqlite3VdbeList( int i; /* Loop counter */ int rc = SQLITE_OK; /* Return code */ Mem *pMem = &p->aMem[1]; /* First Mem of result set */ + int bListSubprogs = (p->explain==1 || (db->flags & SQLITE_TriggerEQP)!=0); + Op *pOp = 0; assert( p->explain ); assert( p->magic==VDBE_MAGIC_RUN ); @@ -1651,7 +1655,7 @@ int sqlite3VdbeList( releaseMemArray(pMem, 8); p->pResultSet = 0; - if( p->rc==SQLITE_NOMEM_BKPT ){ + if( p->rc==SQLITE_NOMEM ){ /* This happens if a malloc() inside a call to sqlite3_column_text() or ** sqlite3_column_text16() failed. */ sqlite3OomFault(db); @@ -1666,7 +1670,7 @@ int sqlite3VdbeList( ** encountered, but p->pc will eventually catch up to nRow. */ nRow = p->nOp; - if( p->explain==1 ){ + if( bListSubprogs ){ /* The first 8 memory cells are used for the result set. So we will ** commandeer the 9th cell to use as storage for an array of pointers ** to trigger subprograms. The VDBE is guaranteed to have at least 9 @@ -1686,17 +1690,11 @@ int sqlite3VdbeList( do{ i = p->pc++; - }while( iexplain==2 && p->aOp[i].opcode!=OP_Explain ); - if( i>=nRow ){ - p->rc = SQLITE_OK; - rc = SQLITE_DONE; - }else if( db->u1.isInterrupted ){ - p->rc = SQLITE_INTERRUPT; - rc = SQLITE_ERROR; - sqlite3VdbeError(p, sqlite3ErrStr(p->rc)); - }else{ - char *zP4; - Op *pOp; + if( i>=nRow ){ + p->rc = SQLITE_OK; + rc = SQLITE_DONE; + break; + } if( inOp ){ /* The output line number is small enough that we are still in the ** main program. */ @@ -1711,94 +1709,110 @@ int sqlite3VdbeList( } pOp = &apSub[j]->aOp[i]; } - if( p->explain==1 ){ - pMem->flags = MEM_Int; - pMem->u.i = i; /* Program counter */ - pMem++; - - pMem->flags = MEM_Static|MEM_Str|MEM_Term; - pMem->z = (char*)sqlite3OpcodeName(pOp->opcode); /* Opcode */ - assert( pMem->z!=0 ); - pMem->n = sqlite3Strlen30(pMem->z); - pMem->enc = SQLITE_UTF8; - pMem++; - /* When an OP_Program opcode is encounter (the only opcode that has - ** a P4_SUBPROGRAM argument), expand the size of the array of subprograms - ** kept in p->aMem[9].z to hold the new program - assuming this subprogram - ** has not already been seen. - */ - if( pOp->p4type==P4_SUBPROGRAM ){ - int nByte = (nSub+1)*sizeof(SubProgram*); - int j; - for(j=0; jp4.pProgram ) break; - } - if( j==nSub && SQLITE_OK==sqlite3VdbeMemGrow(pSub, nByte, nSub!=0) ){ - apSub = (SubProgram **)pSub->z; - apSub[nSub++] = pOp->p4.pProgram; - pSub->flags |= MEM_Blob; - pSub->n = nSub*sizeof(SubProgram*); + /* When an OP_Program opcode is encounter (the only opcode that has + ** a P4_SUBPROGRAM argument), expand the size of the array of subprograms + ** kept in p->aMem[9].z to hold the new program - assuming this subprogram + ** has not already been seen. + */ + if( bListSubprogs && pOp->p4type==P4_SUBPROGRAM ){ + int nByte = (nSub+1)*sizeof(SubProgram*); + int j; + for(j=0; jp4.pProgram ) break; + } + if( j==nSub ){ + p->rc = sqlite3VdbeMemGrow(pSub, nByte, nSub!=0); + if( p->rc!=SQLITE_OK ){ + rc = SQLITE_ERROR; + break; } + apSub = (SubProgram **)pSub->z; + apSub[nSub++] = pOp->p4.pProgram; + pSub->flags |= MEM_Blob; + pSub->n = nSub*sizeof(SubProgram*); + nRow += pOp->p4.pProgram->nOp; } } + }while( p->explain==2 && pOp->opcode!=OP_Explain ); - pMem->flags = MEM_Int; - pMem->u.i = pOp->p1; /* P1 */ - pMem++; - - pMem->flags = MEM_Int; - pMem->u.i = pOp->p2; /* P2 */ - pMem++; - - pMem->flags = MEM_Int; - pMem->u.i = pOp->p3; /* P3 */ - pMem++; - - if( sqlite3VdbeMemClearAndResize(pMem, 100) ){ /* P4 */ - assert( p->db->mallocFailed ); - return SQLITE_ERROR; - } - 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); + if( rc==SQLITE_OK ){ + if( db->u1.isInterrupted ){ + p->rc = SQLITE_INTERRUPT; + rc = SQLITE_ERROR; + sqlite3VdbeError(p, sqlite3ErrStr(p->rc)); }else{ - assert( pMem->z!=0 ); - pMem->n = sqlite3Strlen30(pMem->z); - pMem->enc = SQLITE_UTF8; - } - pMem++; - - if( p->explain==1 ){ - if( sqlite3VdbeMemClearAndResize(pMem, 4) ){ - assert( p->db->mallocFailed ); - return SQLITE_ERROR; + char *zP4; + if( p->explain==1 ){ + pMem->flags = MEM_Int; + pMem->u.i = i; /* Program counter */ + pMem++; + + pMem->flags = MEM_Static|MEM_Str|MEM_Term; + pMem->z = (char*)sqlite3OpcodeName(pOp->opcode); /* Opcode */ + assert( pMem->z!=0 ); + pMem->n = sqlite3Strlen30(pMem->z); + pMem->enc = SQLITE_UTF8; + pMem++; } - pMem->flags = MEM_Str|MEM_Term; - pMem->n = 2; - sqlite3_snprintf(3, pMem->z, "%.2x", pOp->p5); /* P5 */ - pMem->enc = SQLITE_UTF8; + + pMem->flags = MEM_Int; + pMem->u.i = pOp->p1; /* P1 */ pMem++; - -#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS - if( sqlite3VdbeMemClearAndResize(pMem, 500) ){ + + pMem->flags = MEM_Int; + pMem->u.i = pOp->p2; /* P2 */ + pMem++; + + pMem->flags = MEM_Int; + pMem->u.i = pOp->p3; /* P3 */ + pMem++; + + if( sqlite3VdbeMemClearAndResize(pMem, 100) ){ /* P4 */ assert( p->db->mallocFailed ); return SQLITE_ERROR; } pMem->flags = MEM_Str|MEM_Term; - pMem->n = displayComment(pOp, zP4, pMem->z, 500); - pMem->enc = SQLITE_UTF8; -#else - pMem->flags = MEM_Null; /* Comment */ -#endif - } + 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 ); + pMem->n = sqlite3Strlen30(pMem->z); + pMem->enc = SQLITE_UTF8; + } + pMem++; - p->nResColumn = 8 - 4*(p->explain-1); - p->pResultSet = &p->aMem[1]; - p->rc = SQLITE_OK; - rc = SQLITE_ROW; + if( p->explain==1 ){ + if( sqlite3VdbeMemClearAndResize(pMem, 4) ){ + assert( p->db->mallocFailed ); + return SQLITE_ERROR; + } + pMem->flags = MEM_Str|MEM_Term; + pMem->n = 2; + sqlite3_snprintf(3, pMem->z, "%.2x", pOp->p5); /* P5 */ + pMem->enc = SQLITE_UTF8; + pMem++; + +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + if( sqlite3VdbeMemClearAndResize(pMem, 500) ){ + assert( p->db->mallocFailed ); + return SQLITE_ERROR; + } + pMem->flags = MEM_Str|MEM_Term; + pMem->n = displayComment(pOp, zP4, pMem->z, 500); + pMem->enc = SQLITE_UTF8; +#else + pMem->flags = MEM_Null; /* Comment */ +#endif + } + + p->nResColumn = 8 - 4*(p->explain-1); + p->pResultSet = &p->aMem[1]; + p->rc = SQLITE_OK; + rc = SQLITE_ROW; + } } return rc; } @@ -2268,6 +2282,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ pPager = sqlite3BtreePager(pBt); if( db->aDb[i].safety_level!=PAGER_SYNCHRONOUS_OFF && aMJNeeded[sqlite3PagerGetJournalMode(pPager)] + && sqlite3PagerIsMemdb(pPager)==0 ){ assert( i!=1 ); nTrans++; @@ -3459,7 +3474,13 @@ u32 sqlite3VdbeSerialGet( Mem *pMem /* Memory cell to write value into */ ){ switch( serial_type ){ - case 10: /* Reserved for future use */ + case 10: { /* Internal use only: NULL with virtual table + ** UPDATE no-change flag set */ + pMem->flags = MEM_Null|MEM_Zero; + pMem->n = 0; + pMem->u.nZero = 0; + break; + } case 11: /* Reserved for future use */ case 0: { /* Null */ /* EVIDENCE-OF: R-24078-09375 Value is a NULL. */ diff --git a/src/vdbemem.c b/src/vdbemem.c index f9f58c43f4..8df3c0d53b 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -43,7 +43,7 @@ int sqlite3VdbeCheckMemInvariants(Mem *p){ if( p->flags & MEM_Null ){ /* Cannot be both MEM_Null and some other type */ assert( (p->flags & (MEM_Int|MEM_Real|MEM_Str|MEM_Blob - |MEM_RowSet|MEM_Frame|MEM_Agg|MEM_Zero))==0 ); + |MEM_RowSet|MEM_Frame|MEM_Agg))==0 ); /* If MEM_Null is set, then either the value is a pure NULL (the usual ** case) or it is a pointer set using sqlite3_bind_pointer() or @@ -93,6 +93,51 @@ int sqlite3VdbeCheckMemInvariants(Mem *p){ } #endif +#ifdef SQLITE_DEBUG +/* +** Check that string value of pMem agrees with its integer or real value. +** +** A single int or real value always converts to the same strings. But +** many different strings can be converted into the same int or real. +** If a table contains a numeric value and an index is based on the +** corresponding string value, then it is important that the string be +** derived from the numeric value, not the other way around, to ensure +** that the index and table are consistent. See ticket +** https://www.sqlite.org/src/info/343634942dd54ab (2018-01-31) for +** an example. +** +** This routine looks at pMem to verify that if it has both a numeric +** representation and a string representation then the string rep has +** been derived from the numeric and not the other way around. It returns +** true if everything is ok and false if there is a problem. +** +** This routine is for use inside of assert() statements only. +*/ +int sqlite3VdbeMemConsistentDualRep(Mem *p){ + char zBuf[100]; + char *z; + int i, j, incr; + if( (p->flags & MEM_Str)==0 ) return 1; + if( (p->flags & (MEM_Int|MEM_Real))==0 ) return 1; + if( p->flags & MEM_Int ){ + sqlite3_snprintf(sizeof(zBuf),zBuf,"%lld",p->u.i); + }else{ + sqlite3_snprintf(sizeof(zBuf),zBuf,"%!.15g",p->u.r); + } + z = p->z; + i = j = 0; + incr = 1; + if( p->enc!=SQLITE_UTF8 ){ + incr = 2; + if( p->enc==SQLITE_UTF16BE ) z++; + } + while( zBuf[j] ){ + if( zBuf[j++]!=z[i] ) return 0; + i += incr; + } + return 1; +} +#endif /* SQLITE_DEBUG */ /* ** If pMem is an object with a valid string representation, this routine @@ -527,6 +572,16 @@ double sqlite3VdbeRealValue(Mem *pMem){ } } +/* +** Return 1 if pMem represents true, and return 0 if pMem represents false. +** Return the value ifNull if pMem is NULL. +*/ +int sqlite3VdbeBooleanValue(Mem *pMem, int ifNull){ + if( pMem->flags & MEM_Int ) return pMem->u.i!=0; + if( pMem->flags & MEM_Null ) return ifNull; + return sqlite3VdbeRealValue(pMem)!=0.0; +} + /* ** The MEM structure is already a MEM_Real. Try to also make it a ** MEM_Int if we can. @@ -582,6 +637,18 @@ int sqlite3VdbeMemRealify(Mem *pMem){ return SQLITE_OK; } +/* Compare a floating point value to an integer. Return true if the two +** values are the same within the precision of the floating point value. +** +** For some versions of GCC on 32-bit machines, if you do the more obvious +** comparison of "r1==(double)i" you sometimes get an answer of false even +** though the r1 and (double)i values are bit-for-bit the same. +*/ +static int sqlite3RealSameAsInt(double r1, sqlite3_int64 i){ + double r2 = (double)i; + return memcmp(&r1, &r2, sizeof(r1))==0; +} + /* ** Convert pMem so that it has types MEM_Real or MEM_Int or both. ** Invalidate any prior representations. @@ -601,7 +668,7 @@ int sqlite3VdbeMemNumerify(Mem *pMem){ }else{ i64 i = pMem->u.i; sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc); - if( rc==1 && pMem->u.r==(double)i ){ + if( rc==1 && sqlite3RealSameAsInt(pMem->u.r, i) ){ pMem->u.i = i; MemSetTypeFlag(pMem, MEM_Int); }else{ @@ -1084,6 +1151,7 @@ static SQLITE_NOINLINE const void *valueToText(sqlite3_value* pVal, u8 enc){ assert(pVal->enc==(enc & ~SQLITE_UTF16_ALIGNED) || pVal->db==0 || pVal->db->mallocFailed ); if( pVal->enc==(enc & ~SQLITE_UTF16_ALIGNED) ){ + assert( sqlite3VdbeMemConsistentDualRep(pVal) ); return pVal->z; }else{ return 0; @@ -1106,6 +1174,7 @@ const void *sqlite3ValueText(sqlite3_value* pVal, u8 enc){ assert( (enc&3)==(enc&~SQLITE_UTF16_ALIGNED) ); assert( (pVal->flags & MEM_RowSet)==0 ); if( (pVal->flags&(MEM_Str|MEM_Term))==(MEM_Str|MEM_Term) && pVal->enc==enc ){ + assert( sqlite3VdbeMemConsistentDualRep(pVal) ); return pVal->z; } if( pVal->flags&MEM_Null ){ @@ -1321,7 +1390,11 @@ static int valueFromExpr( assert( pExpr!=0 ); while( (op = pExpr->op)==TK_UPLUS || op==TK_SPAN ) pExpr = pExpr->pLeft; +#if defined(SQLITE_ENABLE_STAT3_OR_STAT4) + if( op==TK_REGISTER ) op = pExpr->op2; +#else if( NEVER(op==TK_REGISTER) ) op = pExpr->op2; +#endif /* Compressed expressions only appear when parsing the DEFAULT clause ** on a table column definition, and hence only when pCtx==0. This @@ -1416,7 +1489,10 @@ static int valueFromExpr( return rc; no_mem: - sqlite3OomFault(db); +#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 + if( pCtx==0 || pCtx->pParse->nErr==0 ) +#endif + sqlite3OomFault(db); sqlite3DbFree(db, zVal); assert( *ppVal==0 ); #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 diff --git a/src/wal.c b/src/wal.c index 94f06e1537..75cb9bf665 100644 --- a/src/wal.c +++ b/src/wal.c @@ -455,6 +455,7 @@ struct Wal { u8 truncateOnCommit; /* True to truncate WAL file on commit */ u8 syncHeader; /* Fsync the WAL header if true */ u8 padToSectorBoundary; /* Pad transactions out to the next sector */ + u8 bShmUnreliable; /* SHM content is read-only and unreliable */ WalIndexHdr hdr; /* Wal-index header for current transaction */ u32 minFrame; /* Ignore wal frames before this one */ u32 iReCksum; /* On commit, recalculate checksums from here */ @@ -545,11 +546,20 @@ struct WalIterator { ** is broken into pages of WALINDEX_PGSZ bytes. Wal-index pages are ** numbered from zero. ** +** If the wal-index is currently smaller the iPage pages then the size +** of the wal-index might be increased, but only if it is safe to do +** so. It is safe to enlarge the wal-index if pWal->writeLock is true +** or pWal->exclusiveMode==WAL_HEAPMEMORY_MODE. +** ** If this call is successful, *ppPage is set to point to the wal-index ** page and SQLITE_OK is returned. If an error (an OOM or VFS error) occurs, ** then an SQLite error code is returned and *ppPage is set to 0. */ -static int walIndexPage(Wal *pWal, int iPage, volatile u32 **ppPage){ +static SQLITE_NOINLINE int walIndexPageRealloc( + Wal *pWal, /* The WAL context */ + int iPage, /* The page we seek */ + volatile u32 **ppPage /* Write the page pointer here */ +){ int rc = SQLITE_OK; /* Enlarge the pWal->apWiData[] array if required */ @@ -568,16 +578,19 @@ static int walIndexPage(Wal *pWal, int iPage, volatile u32 **ppPage){ } /* Request a pointer to the required page from the VFS */ - if( pWal->apWiData[iPage]==0 ){ - if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ){ - pWal->apWiData[iPage] = (u32 volatile *)sqlite3MallocZero(WALINDEX_PGSZ); - if( !pWal->apWiData[iPage] ) rc = SQLITE_NOMEM_BKPT; - }else{ - rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ, - pWal->writeLock, (void volatile **)&pWal->apWiData[iPage] - ); + assert( pWal->apWiData[iPage]==0 ); + if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ){ + pWal->apWiData[iPage] = (u32 volatile *)sqlite3MallocZero(WALINDEX_PGSZ); + if( !pWal->apWiData[iPage] ) rc = SQLITE_NOMEM_BKPT; + }else{ + rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ, + pWal->writeLock, (void volatile **)&pWal->apWiData[iPage] + ); + assert( pWal->apWiData[iPage]!=0 || rc!=SQLITE_OK || pWal->writeLock==0 ); + testcase( pWal->apWiData[iPage]==0 && rc==SQLITE_OK ); + if( (rc&0xff)==SQLITE_READONLY ){ + pWal->readOnly |= WAL_SHM_RDONLY; if( rc==SQLITE_READONLY ){ - pWal->readOnly |= WAL_SHM_RDONLY; rc = SQLITE_OK; } } @@ -587,6 +600,16 @@ static int walIndexPage(Wal *pWal, int iPage, volatile u32 **ppPage){ assert( iPage==0 || *ppPage || rc!=SQLITE_OK ); return rc; } +static int walIndexPage( + Wal *pWal, /* The WAL context */ + int iPage, /* The page we seek */ + volatile u32 **ppPage /* Write the page pointer here */ +){ + if( pWal->nWiData<=iPage || (*ppPage = pWal->apWiData[iPage])==0 ){ + return walIndexPageRealloc(pWal, iPage, ppPage); + } + return SQLITE_OK; +} /* ** Return a pointer to the WalCkptInfo structure in the wal-index. @@ -1100,7 +1123,6 @@ static int walIndexRecover(Wal *pWal){ i64 nSize; /* Size of log file */ u32 aFrameCksum[2] = {0, 0}; int iLock; /* Lock offset to lock for checkpoint */ - int nLock; /* Number of locks to hold */ /* Obtain an exclusive lock on all byte in the locking range not already ** locked by the caller. The caller is guaranteed to have locked the @@ -1113,11 +1135,17 @@ static int walIndexRecover(Wal *pWal){ assert( WAL_CKPT_LOCK==WAL_ALL_BUT_WRITE ); assert( pWal->writeLock ); iLock = WAL_ALL_BUT_WRITE + pWal->ckptLock; - nLock = SQLITE_SHM_NLOCK - iLock; - rc = walLockExclusive(pWal, iLock, nLock); + rc = walLockExclusive(pWal, iLock, WAL_READ_LOCK(0)-iLock); + if( rc==SQLITE_OK ){ + rc = walLockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1); + if( rc!=SQLITE_OK ){ + walUnlockExclusive(pWal, iLock, WAL_READ_LOCK(0)-iLock); + } + } if( rc ){ return rc; } + WALTRACE(("WAL%p: recovery begin...\n", pWal)); memset(&pWal->hdr, 0, sizeof(WalIndexHdr)); @@ -1255,7 +1283,8 @@ finished: recovery_error: WALTRACE(("WAL%p: recovery %s\n", pWal, rc ? "failed" : "ok")); - walUnlockExclusive(pWal, iLock, nLock); + walUnlockExclusive(pWal, iLock, WAL_READ_LOCK(0)-iLock); + walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1); return rc; } @@ -1263,13 +1292,14 @@ recovery_error: ** Close an open wal-index. */ static void walIndexClose(Wal *pWal, int isDelete){ - if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ){ + if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE || pWal->bShmUnreliable ){ int i; for(i=0; inWiData; i++){ sqlite3_free((void *)pWal->apWiData[i]); pWal->apWiData[i] = 0; } - }else{ + } + if( pWal->exclusiveMode!=WAL_HEAPMEMORY_MODE ){ sqlite3OsShmUnmap(pWal->pDbFd, isDelete); } } @@ -2078,6 +2108,12 @@ static int walIndexTryHdr(Wal *pWal, int *pChanged){ return 0; } +/* +** This is the value that walTryBeginRead returns when it needs to +** be retried. +*/ +#define WAL_RETRY (-1) + /* ** Read the wal-index header from the wal-index and into pWal->hdr. ** If the wal-header appears to be corrupt, try to reconstruct the @@ -2101,9 +2137,29 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){ assert( pChanged ); rc = walIndexPage(pWal, 0, &page0); if( rc!=SQLITE_OK ){ - return rc; - }; - assert( page0 || pWal->writeLock==0 ); + assert( rc!=SQLITE_READONLY ); /* READONLY changed to OK in walIndexPage */ + if( rc==SQLITE_READONLY_CANTINIT ){ + /* The SQLITE_READONLY_CANTINIT return means that the shared-memory + ** was openable but is not writable, and this thread is unable to + ** confirm that another write-capable connection has the shared-memory + ** open, and hence the content of the shared-memory is unreliable, + ** since the shared-memory might be inconsistent with the WAL file + ** and there is no writer on hand to fix it. */ + assert( page0==0 ); + assert( pWal->writeLock==0 ); + assert( pWal->readOnly & WAL_SHM_RDONLY ); + pWal->bShmUnreliable = 1; + pWal->exclusiveMode = WAL_HEAPMEMORY_MODE; + *pChanged = 1; + }else{ + return rc; /* Any other non-OK return is just an error */ + } + }else{ + /* page0 can be NULL if the SHM is zero bytes in size and pWal->writeLock + ** is zero, which prevents the SHM from growing */ + testcase( page0!=0 ); + } + assert( page0!=0 || pWal->writeLock==0 ); /* If the first page of the wal-index has been mapped, try to read the ** wal-index header immediately, without holding any lock. This usually @@ -2117,7 +2173,7 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){ */ assert( badHdr==0 || pWal->writeLock==0 ); if( badHdr ){ - if( pWal->readOnly & WAL_SHM_RDONLY ){ + if( pWal->bShmUnreliable==0 && (pWal->readOnly & WAL_SHM_RDONLY) ){ if( SQLITE_OK==(rc = walLockShared(pWal, WAL_WRITE_LOCK)) ){ walUnlockShared(pWal, WAL_WRITE_LOCK); rc = SQLITE_READONLY_RECOVERY; @@ -2147,15 +2203,193 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){ if( badHdr==0 && pWal->hdr.iVersion!=WALINDEX_MAX_VERSION ){ rc = SQLITE_CANTOPEN_BKPT; } + if( pWal->bShmUnreliable ){ + if( rc!=SQLITE_OK ){ + walIndexClose(pWal, 0); + pWal->bShmUnreliable = 0; + assert( pWal->nWiData>0 && pWal->apWiData[0]==0 ); + /* walIndexRecover() might have returned SHORT_READ if a concurrent + ** writer truncated the WAL out from under it. If that happens, it + ** indicates that a writer has fixed the SHM file for us, so retry */ + if( rc==SQLITE_IOERR_SHORT_READ ) rc = WAL_RETRY; + } + pWal->exclusiveMode = WAL_NORMAL_MODE; + } return rc; } /* -** This is the value that walTryBeginRead returns when it needs to -** be retried. +** Open a transaction in a connection where the shared-memory is read-only +** and where we cannot verify that there is a separate write-capable connection +** on hand to keep the shared-memory up-to-date with the WAL file. +** +** This can happen, for example, when the shared-memory is implemented by +** memory-mapping a *-shm file, where a prior writer has shut down and +** left the *-shm file on disk, and now the present connection is trying +** to use that database but lacks write permission on the *-shm file. +** Other scenarios are also possible, depending on the VFS implementation. +** +** Precondition: +** +** The *-wal file has been read and an appropriate wal-index has been +** constructed in pWal->apWiData[] using heap memory instead of shared +** memory. +** +** If this function returns SQLITE_OK, then the read transaction has +** been successfully opened. In this case output variable (*pChanged) +** is set to true before returning if the caller should discard the +** contents of the page cache before proceeding. Or, if it returns +** WAL_RETRY, then the heap memory wal-index has been discarded and +** the caller should retry opening the read transaction from the +** beginning (including attempting to map the *-shm file). +** +** If an error occurs, an SQLite error code is returned. */ -#define WAL_RETRY (-1) +static int walBeginShmUnreliable(Wal *pWal, int *pChanged){ + i64 szWal; /* Size of wal file on disk in bytes */ + i64 iOffset; /* Current offset when reading wal file */ + u8 aBuf[WAL_HDRSIZE]; /* Buffer to load WAL header into */ + u8 *aFrame = 0; /* Malloc'd buffer to load entire frame */ + int szFrame; /* Number of bytes in buffer aFrame[] */ + u8 *aData; /* Pointer to data part of aFrame buffer */ + volatile void *pDummy; /* Dummy argument for xShmMap */ + int rc; /* Return code */ + u32 aSaveCksum[2]; /* Saved copy of pWal->hdr.aFrameCksum */ + + assert( pWal->bShmUnreliable ); + assert( pWal->readOnly & WAL_SHM_RDONLY ); + assert( pWal->nWiData>0 && pWal->apWiData[0] ); + + /* Take WAL_READ_LOCK(0). This has the effect of preventing any + ** writers from running a checkpoint, but does not stop them + ** from running recovery. */ + rc = walLockShared(pWal, WAL_READ_LOCK(0)); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_BUSY ) rc = WAL_RETRY; + goto begin_unreliable_shm_out; + } + pWal->readLock = 0; + + /* Check to see if a separate writer has attached to the shared-memory area, + ** thus making the shared-memory "reliable" again. Do this by invoking + ** the xShmMap() routine of the VFS and looking to see if the return + ** is SQLITE_READONLY instead of SQLITE_READONLY_CANTINIT. + ** + ** If the shared-memory is now "reliable" return WAL_RETRY, which will + ** cause the heap-memory WAL-index to be discarded and the actual + ** shared memory to be used in its place. + ** + ** This step is important because, even though this connection is holding + ** the WAL_READ_LOCK(0) which prevents a checkpoint, a writer might + ** have already checkpointed the WAL file and, while the current + ** is active, wrap the WAL and start overwriting frames that this + ** process wants to use. + ** + ** Once sqlite3OsShmMap() has been called for an sqlite3_file and has + ** returned any SQLITE_READONLY value, it must return only SQLITE_READONLY + ** or SQLITE_READONLY_CANTINIT or some error for all subsequent invocations, + ** even if some external agent does a "chmod" to make the shared-memory + ** writable by us, until sqlite3OsShmUnmap() has been called. + ** This is a requirement on the VFS implementation. + */ + rc = sqlite3OsShmMap(pWal->pDbFd, 0, WALINDEX_PGSZ, 0, &pDummy); + assert( rc!=SQLITE_OK ); /* SQLITE_OK not possible for read-only connection */ + if( rc!=SQLITE_READONLY_CANTINIT ){ + rc = (rc==SQLITE_READONLY ? WAL_RETRY : rc); + goto begin_unreliable_shm_out; + } + + /* We reach this point only if the real shared-memory is still unreliable. + ** Assume the in-memory WAL-index substitute is correct and load it + ** into pWal->hdr. + */ + memcpy(&pWal->hdr, (void*)walIndexHdr(pWal), sizeof(WalIndexHdr)); + + /* Make sure some writer hasn't come in and changed the WAL file out + ** from under us, then disconnected, while we were not looking. + */ + rc = sqlite3OsFileSize(pWal->pWalFd, &szWal); + if( rc!=SQLITE_OK ){ + goto begin_unreliable_shm_out; + } + if( szWalhdr.mxFrame==0 ? SQLITE_OK : WAL_RETRY); + goto begin_unreliable_shm_out; + } + + /* Check the salt keys at the start of the wal file still match. */ + rc = sqlite3OsRead(pWal->pWalFd, aBuf, WAL_HDRSIZE, 0); + if( rc!=SQLITE_OK ){ + goto begin_unreliable_shm_out; + } + if( memcmp(&pWal->hdr.aSalt, &aBuf[16], 8) ){ + /* Some writer has wrapped the WAL file while we were not looking. + ** Return WAL_RETRY which will cause the in-memory WAL-index to be + ** rebuilt. */ + rc = WAL_RETRY; + goto begin_unreliable_shm_out; + } + + /* Allocate a buffer to read frames into */ + szFrame = pWal->hdr.szPage + WAL_FRAME_HDRSIZE; + aFrame = (u8 *)sqlite3_malloc64(szFrame); + if( aFrame==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto begin_unreliable_shm_out; + } + aData = &aFrame[WAL_FRAME_HDRSIZE]; + + /* Check to see if a complete transaction has been appended to the + ** wal file since the heap-memory wal-index was created. If so, the + ** heap-memory wal-index is discarded and WAL_RETRY returned to + ** the caller. */ + aSaveCksum[0] = pWal->hdr.aFrameCksum[0]; + aSaveCksum[1] = pWal->hdr.aFrameCksum[1]; + for(iOffset=walFrameOffset(pWal->hdr.mxFrame+1, pWal->hdr.szPage); + iOffset+szFrame<=szWal; + iOffset+=szFrame + ){ + u32 pgno; /* Database page number for frame */ + u32 nTruncate; /* dbsize field from frame header */ + + /* Read and decode the next log frame. */ + rc = sqlite3OsRead(pWal->pWalFd, aFrame, szFrame, iOffset); + if( rc!=SQLITE_OK ) break; + if( !walDecodeFrame(pWal, &pgno, &nTruncate, aData, aFrame) ) break; + + /* If nTruncate is non-zero, then a complete transaction has been + ** appended to this wal file. Set rc to WAL_RETRY and break out of + ** the loop. */ + if( nTruncate ){ + rc = WAL_RETRY; + break; + } + } + pWal->hdr.aFrameCksum[0] = aSaveCksum[0]; + pWal->hdr.aFrameCksum[1] = aSaveCksum[1]; + + begin_unreliable_shm_out: + sqlite3_free(aFrame); + if( rc!=SQLITE_OK ){ + int i; + for(i=0; inWiData; i++){ + sqlite3_free((void*)pWal->apWiData[i]); + pWal->apWiData[i] = 0; + } + pWal->bShmUnreliable = 0; + sqlite3WalEndReadTransaction(pWal); + *pChanged = 1; + } + return rc; +} /* ** Attempt to start a read transaction. This might fail due to a race or @@ -2217,6 +2451,9 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ assert( pWal->readLock<0 ); /* Not currently locked */ + /* useWal may only be set for read/write connections */ + assert( (pWal->readOnly & WAL_SHM_RDONLY)==0 || useWal==0 ); + /* Take steps to avoid spinning forever if there is a protocol error. ** ** Circumstances that cause a RETRY should only last for the briefest @@ -2245,7 +2482,10 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ } if( !useWal ){ - rc = walIndexReadHdr(pWal, pChanged); + assert( rc==SQLITE_OK ); + if( pWal->bShmUnreliable==0 ){ + rc = walIndexReadHdr(pWal, pChanged); + } if( rc==SQLITE_BUSY ){ /* If there is not a recovery running in another thread or process ** then convert BUSY errors to WAL_RETRY. If recovery is known to @@ -2274,13 +2514,17 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ if( rc!=SQLITE_OK ){ return rc; } + else if( pWal->bShmUnreliable ){ + return walBeginShmUnreliable(pWal, pChanged); + } } + assert( pWal->nWiData>0 ); + assert( pWal->apWiData[0]!=0 ); pInfo = walCkptInfo(pWal); - if( !useWal && pInfo->nBackfill==pWal->hdr.mxFrame + if( !useWal && pInfo->nBackfill==pWal->hdr.mxFrame #ifdef SQLITE_ENABLE_SNAPSHOT - && (pWal->pSnapshot==0 || pWal->hdr.mxFrame==0 - || 0==memcmp(&pWal->hdr, pWal->pSnapshot, sizeof(WalIndexHdr))) + && (pWal->pSnapshot==0 || pWal->hdr.mxFrame==0) #endif ){ /* The WAL has been completely backfilled (or it is empty). @@ -2351,7 +2595,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ } if( mxI==0 ){ assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 ); - return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTLOCK; + return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTINIT; } rc = walLockShared(pWal, WAL_READ_LOCK(mxI)); @@ -2636,7 +2880,7 @@ static int walFindFrame( ** table after the current read-transaction had started. */ iMinHash = walFramePage(pWal->minFrame); - for(iHash=walFramePage(iLast); iHash>=iMinHash && iRead==0; iHash--){ + for(iHash=walFramePage(iLast); iHash>=iMinHash; iHash--){ volatile ht_slot *aHash; /* Pointer to hash table */ volatile u32 *aPgno; /* Pointer to array of page numbers */ u32 iZero; /* Frame number corresponding to aPgno[0] */ @@ -2659,6 +2903,7 @@ static int walFindFrame( return SQLITE_CORRUPT_BKPT; } } + if( iRead ) break; } *piRead = iRead; @@ -2691,7 +2936,7 @@ int sqlite3WalFindFrame( ** then the WAL is ignored by the reader so return early, as if the ** WAL were empty. */ - if( iLast==0 || pWal->readLock==0 ){ + if( iLast==0 || (pWal->readLock==0 && pWal->bShmUnreliable==0) ){ *piRead = 0; return SQLITE_OK; } @@ -2705,8 +2950,8 @@ int sqlite3WalFindFrame( if( rc==SQLITE_OK ){ u32 iRead2 = 0; u32 iTest; - assert( pWal->minFrame>0 ); - for(iTest=iLast; iTest>=pWal->minFrame; iTest--){ + assert( pWal->bShmUnreliable || pWal->minFrame>0 ); + for(iTest=iLast; iTest>=pWal->minFrame && iTest>0; iTest--){ if( walFramePgno(pWal, iTest)==pgno ){ iRead2 = iTest; break; @@ -3675,24 +3920,24 @@ int sqlite3WalExclusiveMode(Wal *pWal, int op){ assert( pWal->readLock>=0 || (op<=0 && pWal->exclusiveMode==0) ); if( op==0 ){ - if( pWal->exclusiveMode ){ - pWal->exclusiveMode = 0; + if( pWal->exclusiveMode!=WAL_NORMAL_MODE ){ + pWal->exclusiveMode = WAL_NORMAL_MODE; if( walLockShared(pWal, WAL_READ_LOCK(pWal->readLock))!=SQLITE_OK ){ - pWal->exclusiveMode = 1; + pWal->exclusiveMode = WAL_EXCLUSIVE_MODE; } - rc = pWal->exclusiveMode==0; + rc = pWal->exclusiveMode==WAL_NORMAL_MODE; }else{ /* Already in locking_mode=NORMAL */ rc = 0; } }else if( op>0 ){ - assert( pWal->exclusiveMode==0 ); + assert( pWal->exclusiveMode==WAL_NORMAL_MODE ); assert( pWal->readLock>=0 ); walUnlockShared(pWal, WAL_READ_LOCK(pWal->readLock)); - pWal->exclusiveMode = 1; + pWal->exclusiveMode = WAL_EXCLUSIVE_MODE; rc = 1; }else{ - rc = pWal->exclusiveMode==0; + rc = pWal->exclusiveMode==WAL_NORMAL_MODE; } return rc; } diff --git a/src/walker.c b/src/walker.c index ae7545bf5f..60bf8226fe 100644 --- a/src/walker.c +++ b/src/walker.c @@ -91,7 +91,6 @@ int sqlite3WalkSelectExpr(Walker *pWalker, Select *p){ if( sqlite3WalkExpr(pWalker, p->pHaving) ) return WRC_Abort; if( sqlite3WalkExprList(pWalker, p->pOrderBy) ) return WRC_Abort; if( sqlite3WalkExpr(pWalker, p->pLimit) ) return WRC_Abort; - if( sqlite3WalkExpr(pWalker, p->pOffset) ) return WRC_Abort; return WRC_Continue; } diff --git a/src/where.c b/src/where.c index 5545a45e87..8c1e3cd01f 100644 --- a/src/where.c +++ b/src/where.c @@ -19,6 +19,21 @@ #include "sqliteInt.h" #include "whereInt.h" +/* +** Extra information appended to the end of sqlite3_index_info but not +** visible to the xBestIndex function, at least not directly. The +** sqlite3_vtab_collation() interface knows how to reach it, however. +** +** This object is not an API and can be changed from one release to the +** next. As long as allocateIndexInfo() and sqlite3_vtab_collation() +** agree on the structure, all will be well. +*/ +typedef struct HiddenIndexInfo HiddenIndexInfo; +struct HiddenIndexInfo { + WhereClause *pWC; /* The Where clause being analyzed */ + Parse *pParse; /* The parsing context */ +}; + /* Forward declaration of methods */ static int whereLoopResize(sqlite3*, WhereLoop*, int); @@ -841,11 +856,11 @@ end_auto_index_create: ** by passing the pointer returned by this function to sqlite3_free(). */ static sqlite3_index_info *allocateIndexInfo( - Parse *pParse, - WhereClause *pWC, + Parse *pParse, /* The parsing context */ + WhereClause *pWC, /* The WHERE clause being analyzed */ Bitmask mUnusable, /* Ignore terms with these prereqs */ - struct SrcList_item *pSrc, - ExprList *pOrderBy, + struct SrcList_item *pSrc, /* The FROM clause term that is the vtab */ + ExprList *pOrderBy, /* The ORDER BY clause */ u16 *pmNoOmit /* Mask of terms not to omit */ ){ int i, j; @@ -853,6 +868,7 @@ static sqlite3_index_info *allocateIndexInfo( struct sqlite3_index_constraint *pIdxCons; struct sqlite3_index_orderby *pIdxOrderBy; struct sqlite3_index_constraint_usage *pUsage; + struct HiddenIndexInfo *pHidden; WhereTerm *pTerm; int nOrderBy; sqlite3_index_info *pIdxInfo; @@ -894,7 +910,7 @@ static sqlite3_index_info *allocateIndexInfo( */ pIdxInfo = sqlite3DbMallocZero(pParse->db, sizeof(*pIdxInfo) + (sizeof(*pIdxCons) + sizeof(*pUsage))*nTerm - + sizeof(*pIdxOrderBy)*nOrderBy ); + + sizeof(*pIdxOrderBy)*nOrderBy + sizeof(*pHidden) ); if( pIdxInfo==0 ){ sqlite3ErrorMsg(pParse, "out of memory"); return 0; @@ -905,7 +921,8 @@ static sqlite3_index_info *allocateIndexInfo( ** changing them. We have to do some funky casting in order to ** initialize those fields. */ - pIdxCons = (struct sqlite3_index_constraint*)&pIdxInfo[1]; + pHidden = (struct HiddenIndexInfo*)&pIdxInfo[1]; + pIdxCons = (struct sqlite3_index_constraint*)&pHidden[1]; pIdxOrderBy = (struct sqlite3_index_orderby*)&pIdxCons[nTerm]; pUsage = (struct sqlite3_index_constraint_usage*)&pIdxOrderBy[nOrderBy]; *(int*)&pIdxInfo->nConstraint = nTerm; @@ -915,6 +932,8 @@ static sqlite3_index_info *allocateIndexInfo( *(struct sqlite3_index_constraint_usage**)&pIdxInfo->aConstraintUsage = pUsage; + pHidden->pWC = pWC; + pHidden->pParse = pParse; for(i=j=0, pTerm=pWC->a; inTerm; i++, pTerm++){ u16 op; if( pTerm->leftCursor != pSrc->iCursor ) continue; @@ -2460,12 +2479,14 @@ static int whereLoopAddBtreeIndex( pNew->wsFlags |= WHERE_COLUMN_EQ; assert( saved_nEq==pNew->u.btree.nEq ); if( iCol==XN_ROWID - || (iCol>0 && nInMul==0 && saved_nEq==pProbe->nKeyCol-1) + || (iCol>=0 && nInMul==0 && saved_nEq==pProbe->nKeyCol-1) ){ - if( iCol>=0 && pProbe->uniqNotNull==0 ){ - pNew->wsFlags |= WHERE_UNQ_WANTED; - }else{ + if( iCol==XN_ROWID || pProbe->uniqNotNull + || (pProbe->nKeyCol==1 && pProbe->onError && eOp==WO_EQ) + ){ pNew->wsFlags |= WHERE_ONEROW; + }else{ + pNew->wsFlags |= WHERE_UNQ_WANTED; } } }else if( eOp & WO_ISNULL ){ @@ -2879,6 +2900,7 @@ static int whereLoopAddBtree( testcase( pNew->iTab!=pSrc->iCursor ); /* See ticket [98d973b8f5] */ continue; /* Partial index inappropriate for this query */ } + if( pProbe->bNoQuery ) continue; rSize = pProbe->aiRowLogEst[0]; pNew->u.btree.nEq = 0; pNew->u.btree.nBtm = 0; @@ -3137,6 +3159,27 @@ static int whereLoopAddVirtualOne( return rc; } +/* +** If this function is invoked from within an xBestIndex() callback, it +** returns a pointer to a buffer containing the name of the collation +** sequence associated with element iCons of the sqlite3_index_info.aConstraint +** array. Or, if iCons is out of range or there is no active xBestIndex +** call, return NULL. +*/ +const char *sqlite3_vtab_collation(sqlite3_index_info *pIdxInfo, int iCons){ + HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; + const char *zRet = 0; + if( iCons>=0 && iConsnConstraint ){ + CollSeq *pC = 0; + int iTerm = pIdxInfo->aConstraint[iCons].iTermOffset; + Expr *pX = pHidden->pWC->a[iTerm].pExpr; + if( pX->pLeft ){ + pC = sqlite3BinaryCompareCollSeq(pHidden->pParse, pX->pLeft, pX->pRight); + } + zRet = (pC ? pC->zName : "BINARY"); + } + return zRet; +} /* ** Add all WhereLoop objects for a table of the join identified by @@ -4590,6 +4633,7 @@ WhereInfo *sqlite3WhereBegin( */ for(ii=0; iinTerm; ii++){ WhereTerm *pT = &sWLB.pWC->a[ii]; + if( pT->wtFlags & TERM_VIRTUAL ) continue; if( pT->prereqAll==0 && (nTabList==0 || exprIsDeterministic(pT->pExpr)) ){ sqlite3ExprIfFalse(pParse, pT->pExpr, pWInfo->iBreak, SQLITE_JUMPIFNULL); pT->wtFlags |= TERM_CODED; @@ -4677,35 +4721,80 @@ WhereInfo *sqlite3WhereBegin( } } #endif - /* Attempt to omit tables from the join that do not effect the result */ + + /* Attempt to omit tables from the join that do not affect the result. + ** For a table to not affect the result, the following must be true: + ** + ** 1) The query must not be an aggregate. + ** 2) The table must be the RHS of a LEFT JOIN. + ** 3) Either the query must be DISTINCT, or else the ON or USING clause + ** must contain a constraint that limits the scan of the table to + ** at most a single row. + ** 4) The table must not be referenced by any part of the query apart + ** from its own USING or ON clause. + ** + ** For example, given: + ** + ** CREATE TABLE t1(ipk INTEGER PRIMARY KEY, v1); + ** CREATE TABLE t2(ipk INTEGER PRIMARY KEY, v2); + ** CREATE TABLE t3(ipk INTEGER PRIMARY KEY, v3); + ** + ** then table t2 can be omitted from the following: + ** + ** SELECT v1, v3 FROM t1 + ** LEFT JOIN t2 USING (t1.ipk=t2.ipk) + ** LEFT JOIN t3 USING (t1.ipk=t3.ipk) + ** + ** or from: + ** + ** SELECT DISTINCT v1, v3 FROM t1 + ** LEFT JOIN t2 + ** LEFT JOIN t3 USING (t1.ipk=t3.ipk) + */ + notReady = ~(Bitmask)0; if( pWInfo->nLevel>=2 - && pResultSet!=0 + && pResultSet!=0 /* guarantees condition (1) above */ && OptimizationEnabled(db, SQLITE_OmitNoopJoin) ){ + int i; Bitmask tabUsed = sqlite3WhereExprListUsage(pMaskSet, pResultSet); if( sWLB.pOrderBy ){ tabUsed |= sqlite3WhereExprListUsage(pMaskSet, sWLB.pOrderBy); } - while( pWInfo->nLevel>=2 ){ + for(i=pWInfo->nLevel-1; i>=1; i--){ WhereTerm *pTerm, *pEnd; - pLoop = pWInfo->a[pWInfo->nLevel-1].pWLoop; - if( (pWInfo->pTabList->a[pLoop->iTab].fg.jointype & JT_LEFT)==0 ) break; + struct SrcList_item *pItem; + pLoop = pWInfo->a[i].pWLoop; + pItem = &pWInfo->pTabList->a[pLoop->iTab]; + if( (pItem->fg.jointype & JT_LEFT)==0 ) continue; if( (wctrlFlags & WHERE_WANT_DISTINCT)==0 && (pLoop->wsFlags & WHERE_ONEROW)==0 ){ - break; + continue; } - if( (tabUsed & pLoop->maskSelf)!=0 ) break; + if( (tabUsed & pLoop->maskSelf)!=0 ) continue; pEnd = sWLB.pWC->a + sWLB.pWC->nTerm; for(pTerm=sWLB.pWC->a; pTermprereqAll & pLoop->maskSelf)!=0 - && !ExprHasProperty(pTerm->pExpr, EP_FromJoin) - ){ - break; + if( (pTerm->prereqAll & pLoop->maskSelf)!=0 ){ + if( !ExprHasProperty(pTerm->pExpr, EP_FromJoin) + || pTerm->pExpr->iRightJoinTable!=pItem->iCursor + ){ + break; + } } } - if( pTerm drop loop %c not used\n", pLoop->cId)); + notReady &= ~pLoop->maskSelf; + for(pTerm=sWLB.pWC->a; pTermprereqAll & pLoop->maskSelf)!=0 ){ + pTerm->wtFlags |= TERM_CODED; + } + } + if( i!=pWInfo->nLevel-1 ){ + int nByte = (pWInfo->nLevel-1-i) * sizeof(WhereLevel); + memmove(&pWInfo->a[i], &pWInfo->a[i+1], nByte); + } pWInfo->nLevel--; nTabList--; } @@ -4715,15 +4804,32 @@ WhereInfo *sqlite3WhereBegin( /* If the caller is an UPDATE or DELETE statement that is requesting ** to use a one-pass algorithm, determine if this is appropriate. + ** + ** A one-pass approach can be used if the caller has requested one + ** and either (a) the scan visits at most one row or (b) each + ** of the following are true: + ** + ** * the caller has indicated that a one-pass approach can be used + ** with multiple rows (by setting WHERE_ONEPASS_MULTIROW), and + ** * the table is not a virtual table, and + ** * either the scan does not use the OR optimization or the caller + ** is a DELETE operation (WHERE_DUPLICATES_OK is only specified + ** for DELETE). + ** + ** The last qualification is because an UPDATE statement uses + ** WhereInfo.aiCurOnePass[1] to determine whether or not it really can + ** use a one-pass approach, and this is not set accurately for scans + ** that use the OR optimization. */ assert( (wctrlFlags & WHERE_ONEPASS_DESIRED)==0 || pWInfo->nLevel==1 ); if( (wctrlFlags & WHERE_ONEPASS_DESIRED)!=0 ){ int wsFlags = pWInfo->a[0].pWLoop->wsFlags; int bOnerow = (wsFlags & WHERE_ONEROW)!=0; - if( bOnerow - || ((wctrlFlags & WHERE_ONEPASS_MULTIROW)!=0 - && 0==(wsFlags & WHERE_VIRTUALTABLE)) - ){ + if( bOnerow || ( + 0!=(wctrlFlags & WHERE_ONEPASS_MULTIROW) + && 0==(wsFlags & WHERE_VIRTUALTABLE) + && (0==(wsFlags & WHERE_MULTI_OR) || (wctrlFlags & WHERE_DUPLICATES_OK)) + )){ pWInfo->eOnePass = bOnerow ? ONEPASS_SINGLE : ONEPASS_MULTI; if( HasRowid(pTabList->a[0].pTab) && (wsFlags & WHERE_IDX_ONLY) ){ if( wctrlFlags & WHERE_ONEPASS_MULTIROW ){ @@ -4860,7 +4966,6 @@ WhereInfo *sqlite3WhereBegin( ** loop below generates code for a single nested loop of the VM ** program. */ - notReady = ~(Bitmask)0; for(ii=0; iieDistinct==WHERE_DISTINCT_ORDERED + && i==pWInfo->nLevel-1 /* Ticket [ef9318757b152e3] 2017-10-21 */ && (pLoop->wsFlags & WHERE_INDEXED)!=0 && (pIdx = pLoop->u.btree.pIndex)->hasStat1 && (n = pLoop->u.btree.nIdxCol)>0 @@ -4990,7 +5096,8 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ addr = sqlite3VdbeAddOp1(v, OP_IfPos, pLevel->iLeftJoin); VdbeCoverage(v); assert( (ws & WHERE_IDX_ONLY)==0 || (ws & WHERE_INDEXED)!=0 ); if( (ws & WHERE_IDX_ONLY)==0 ){ - sqlite3VdbeAddOp1(v, OP_NullRow, pTabList->a[i].iCursor); + assert( pLevel->iTabCur==pTabList->a[pLevel->iFrom].iCursor ); + sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iTabCur); } if( (ws & WHERE_INDEXED) || ((ws & WHERE_MULTI_OR) && pLevel->u.pCovidx) @@ -5059,7 +5166,11 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ pOp = sqlite3VdbeGetOp(v, k); for(; kp1!=pLevel->iTabCur ) continue; - if( pOp->opcode==OP_Column ){ + if( pOp->opcode==OP_Column +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + || pOp->opcode==OP_Offset +#endif + ){ int x = pOp->p2; assert( pIdx->pTable==pTab ); if( !HasRowid(pTab) ){ diff --git a/src/wherecode.c b/src/wherecode.c index da5c686a95..e40a940ac1 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -128,7 +128,7 @@ int sqlite3WhereExplainOneScan( ){ int ret = 0; #if !defined(SQLITE_DEBUG) && !defined(SQLITE_ENABLE_STMT_SCANSTATUS) - if( pParse->explain==2 ) + if( sqlite3ParseToplevel(pParse)->explain==2 ) #endif { struct SrcList_item *pItem = &pTabList->a[pLevel->iFrom]; @@ -377,6 +377,102 @@ static void updateRangeAffinityStr( } } + +/* +** pX is an expression of the form: (vector) IN (SELECT ...) +** In other words, it is a vector IN operator with a SELECT clause on the +** LHS. But not all terms in the vector are indexable and the terms might +** not be in the correct order for indexing. +** +** This routine makes a copy of the input pX expression and then adjusts +** the vector on the LHS with corresponding changes to the SELECT so that +** the vector contains only index terms and those terms are in the correct +** order. The modified IN expression is returned. The caller is responsible +** for deleting the returned expression. +** +** Example: +** +** CREATE TABLE t1(a,b,c,d,e,f); +** CREATE INDEX t1x1 ON t1(e,c); +** SELECT * FROM t1 WHERE (a,b,c,d,e) IN (SELECT v,w,x,y,z FROM t2) +** \_______________________________________/ +** The pX expression +** +** Since only columns e and c can be used with the index, in that order, +** the modified IN expression that is returned will be: +** +** (e,c) IN (SELECT z,x FROM t2) +** +** The reduced pX is different from the original (obviously) and thus is +** only used for indexing, to improve performance. The original unaltered +** IN expression must also be run on each output row for correctness. +*/ +static Expr *removeUnindexableInClauseTerms( + Parse *pParse, /* The parsing context */ + int iEq, /* Look at loop terms starting here */ + WhereLoop *pLoop, /* The current loop */ + Expr *pX /* The IN expression to be reduced */ +){ + sqlite3 *db = pParse->db; + Expr *pNew = sqlite3ExprDup(db, pX, 0); + if( db->mallocFailed==0 ){ + ExprList *pOrigRhs = pNew->x.pSelect->pEList; /* Original unmodified RHS */ + ExprList *pOrigLhs = pNew->pLeft->x.pList; /* Original unmodified LHS */ + ExprList *pRhs = 0; /* New RHS after modifications */ + ExprList *pLhs = 0; /* New LHS after mods */ + int i; /* Loop counter */ + Select *pSelect; /* Pointer to the SELECT on the RHS */ + + for(i=iEq; inLTerm; i++){ + if( pLoop->aLTerm[i]->pExpr==pX ){ + int iField = pLoop->aLTerm[i]->iField - 1; + assert( pOrigRhs->a[iField].pExpr!=0 ); + pRhs = sqlite3ExprListAppend(pParse, pRhs, pOrigRhs->a[iField].pExpr); + pOrigRhs->a[iField].pExpr = 0; + assert( pOrigLhs->a[iField].pExpr!=0 ); + pLhs = sqlite3ExprListAppend(pParse, pLhs, pOrigLhs->a[iField].pExpr); + pOrigLhs->a[iField].pExpr = 0; + } + } + sqlite3ExprListDelete(db, pOrigRhs); + sqlite3ExprListDelete(db, pOrigLhs); + pNew->pLeft->x.pList = pLhs; + pNew->x.pSelect->pEList = pRhs; + if( pLhs && pLhs->nExpr==1 ){ + /* 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. */ + Expr *p = pLhs->a[0].pExpr; + pLhs->a[0].pExpr = 0; + sqlite3ExprDelete(db, pNew->pLeft); + pNew->pLeft = p; + } + pSelect = pNew->x.pSelect; + 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; + } + } + +#if 0 + printf("For indexing, change the IN expr:\n"); + sqlite3TreeViewExpr(0, pX, 0); + printf("Into:\n"); + sqlite3TreeViewExpr(0, pNew, 0); +#endif + } + return pNew; +} + + /* ** Generate code for a single equality term of the WHERE clause. An equality ** term can be either X=expr or X IN (...). pTerm is the term to be @@ -439,68 +535,23 @@ static int codeEqualityTerm( } } for(i=iEq;inLTerm; i++){ - if( ALWAYS(pLoop->aLTerm[i]) && pLoop->aLTerm[i]->pExpr==pX ) nEq++; + assert( pLoop->aLTerm[i]!=0 ); + if( 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 */ + pX = removeUnindexableInClauseTerms(pParse, iEq, pLoop, pX); - 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; + aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*nEq); 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; + pTerm->pExpr->iTable = pX->iTable; } - sqlite3ExprListDelete(pParse->db, pLhs); - sqlite3ExprListDelete(pParse->db, pRhs); + sqlite3ExprDelete(db, pX); + pX = pTerm->pExpr; } if( eType==IN_INDEX_INDEX_DESC ){ @@ -1344,7 +1395,15 @@ Bitmask sqlite3WhereCodeOneLoopStart( if( sqlite3ExprIsVector(pX->pRight) ){ r1 = rTemp = sqlite3GetTempReg(pParse); codeExprOrVector(pParse, pX->pRight, r1, 1); - op = aMoveOp[(pX->op - TK_GT) | 0x0001]; + testcase( pX->op==TK_GT ); + testcase( pX->op==TK_GE ); + testcase( pX->op==TK_LT ); + testcase( pX->op==TK_LE ); + op = aMoveOp[((pX->op - TK_GT - 1) & 0x3) | 0x1]; + assert( pX->op!=TK_GT || op==OP_SeekGE ); + assert( pX->op!=TK_GE || op==OP_SeekGE ); + assert( pX->op!=TK_LT || op==OP_SeekLE ); + assert( pX->op!=TK_LE || op==OP_SeekLE ); }else{ r1 = sqlite3ExprCodeTemp(pParse, pX->pRight, &rTemp); disableTerm(pLevel, pStart); @@ -1639,6 +1698,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( } }else if( bStopAtNull ){ sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq); + sqlite3ExprCacheRemove(pParse, regBase+nEq, 1); endEq = 0; nConstraint++; } @@ -2118,6 +2178,12 @@ Bitmask sqlite3WhereCodeOneLoopStart( WO_EQ|WO_IN|WO_IS, 0); if( pAlt==0 ) continue; if( pAlt->wtFlags & (TERM_CODED) ) continue; + if( (pAlt->eOperator & WO_IN) + && (pAlt->pExpr->flags & EP_xIsSelect) + && (pAlt->pExpr->x.pSelect->pEList->nExpr>1) + ){ + continue; + } testcase( pAlt->eOperator & WO_EQ ); testcase( pAlt->eOperator & WO_IS ); testcase( pAlt->eOperator & WO_IN ); diff --git a/src/whereexpr.c b/src/whereexpr.c index 58f1908cf8..313c5ee9bc 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -876,6 +876,9 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){ for(i=0; inSrc; i++){ mask |= exprSelectUsage(pMaskSet, pSrc->a[i].pSelect); mask |= sqlite3WhereExprUsage(pMaskSet, pSrc->a[i].pOn); + if( pSrc->a[i].fg.isTabFunc ){ + mask |= sqlite3WhereExprListUsage(pMaskSet, pSrc->a[i].u1.pFuncArg); + } } } pS = pS->pPrior; @@ -1288,7 +1291,7 @@ static void exprAnalyze( exprAnalyze(pSrc, pWC, idxNew); } pTerm = &pWC->a[idxTerm]; - pTerm->wtFlags = TERM_CODED|TERM_VIRTUAL; /* Disable the original */ + pTerm->wtFlags |= TERM_CODED|TERM_VIRTUAL; /* Disable the original */ pTerm->eOperator = 0; } diff --git a/test/capi2.test b/test/capi2.test index 39f50dd079..0680cf530c 100644 --- a/test/capi2.test +++ b/test/capi2.test @@ -163,7 +163,7 @@ do_test capi2-3.2 { sqlite3_prepare $DB {select bogus from } -1 TAIL } msg] lappend rc $msg $TAIL -} {1 {(1) near " ": syntax error} {}} +} {1 {(1) incomplete input} {}} do_test capi2-3.3 { set rc [catch { sqlite3_prepare $DB {;;;;select bogus from sqlite_master} -1 TAIL @@ -184,7 +184,7 @@ do_test capi2-3.5 { } {1 {(1) no such column: bogus} {;;x;}} do_test capi2-3.6 { set rc [catch { - sqlite3_prepare $DB {select 5/0} -1 TAIL + sqlite3_prepare $DB {select 5/0;} -1 TAIL } VM] lappend rc $TAIL } {0 {}} diff --git a/test/capi3.test b/test/capi3.test index becf1bf5db..01ce65b6a4 100644 --- a/test/capi3.test +++ b/test/capi3.test @@ -649,6 +649,18 @@ do_test capi3-5.33 { sqlite3_finalize $STMT } SQLITE_OK +# 2018-01-09: If a column is the last token if a string, the column name +# was not being set correctly, due to changes in check-in +# https://sqlite.org/src/info/0fdf97efe5df7455 +# +# This problem was detected by the community during beta-testing. +# +do_test capi3-5.34 { + set STMT [sqlite3_prepare $DB {SELECT :a, :b} -1 TAIL] + sqlite3_column_count $STMT +} 2 +check_header $STMT capi-5.35 {:a :b} {{} {}} +sqlite3_finalize $STMT set ::ENC [execsql {pragma encoding}] db close diff --git a/test/cast.test b/test/cast.test index f47f4bb2bf..f43aa48560 100644 --- a/test/cast.test +++ b/test/cast.test @@ -343,4 +343,49 @@ do_test cast-4.4 { } } {0 abc 0.0 abc} +# Added 2018-01-26 +# +# EVIDENCE-OF: R-48741-32454 If the prefix integer is greater than +# +9223372036854775807 then the result of the cast is exactly +# +9223372036854775807. +do_execsql_test cast-5.1 { + SELECT CAST('9223372036854775808' AS integer); + SELECT CAST(' +000009223372036854775808' AS integer); + SELECT CAST('12345678901234567890123' AS INTEGER); +} {9223372036854775807 9223372036854775807 9223372036854775807} + +# EVIDENCE-OF: R-06028-16857 Similarly, if the prefix integer is less +# than -9223372036854775808 then the result of the cast is exactly +# -9223372036854775808. +do_execsql_test cast-5.2 { + SELECT CAST('-9223372036854775808' AS integer); + SELECT CAST('-9223372036854775809' AS integer); + SELECT CAST('-12345678901234567890123' AS INTEGER); +} {-9223372036854775808 -9223372036854775808 -9223372036854775808} + +# EVIDENCE-OF: R-33990-33527 When casting to INTEGER, if the text looks +# like a floating point value with an exponent, the exponent will be +# ignored because it is no part of the integer prefix. +# EVIDENCE-OF: R-24225-46995 For example, "(CAST '123e+5' AS INTEGER)" +# results in 123, not in 12300000. +do_execsql_test case-5.3 { + SELECT CAST('123e+5' AS INTEGER); + SELECT CAST('123e+5' AS NUMERIC); +} {123 12300000.0} + + +# The following does not have anything to do with the CAST operator, +# but it does deal with affinity transformations. +# +do_execsql_test case-6.1 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a NUMERIC); + INSERT INTO t1 VALUES + ('9000000000000000001'), + ('9000000000000000001 '), + (' 9000000000000000001'), + (' 9000000000000000001 '); + SELECT * FROM t1; +} {9000000000000000001 9000000000000000001 9000000000000000001 9000000000000000001} + finish_test diff --git a/test/colname.test b/test/colname.test index 2e4ae89008..f314f94f6e 100644 --- a/test/colname.test +++ b/test/colname.test @@ -378,6 +378,48 @@ do_test colname-9.210 { execsql2 {SELECT t1.a, v3.a AS n FROM t1 JOIN v3} } {a 1 n 3} +# 2017-12-23: Ticket https://www.sqlite.org/src/info/3b4450072511e621 +# Inconsistent column names in CREATE TABLE AS +# +# Verify that the names of columns in the created table of a CREATE TABLE AS +# are the same as the names of result columns in the SELECT statement. +# +do_execsql_test colname-9.300 { + DROP TABLE IF EXISTS t1; + DROP TABLE IF EXISTS t2; + CREATE TABLE t1(aaa INT); + INSERT INTO t1(aaa) VALUES(123); +} +do_test colname-9.310 { + execsql2 {SELECT BBb FROM (SELECT aaa AS Bbb FROM t1)} +} {Bbb 123} +ifcapable vtab { + do_execsql_test colname-9.320 { + CREATE TABLE t2 AS SELECT BBb FROM (SELECT aaa AS Bbb FROM t1); + SELECT name FROM pragma_table_info('t2'); + } {Bbb} +} + +# Issue detected by OSSFuzz on 2017-12-24 (Christmas Eve) +# caused by check-in https://sqlite.org/src/info/6b2ff26c25 +# +# Prior to being fixed, the following CREATE TABLE was dereferencing +# a NULL pointer and segfaulting. +# +do_catchsql_test colname-9.400 { + CREATE TABLE t4 AS SELECT #0; +} {1 {near "#0": syntax error}} + +# Issue detected by OSSFuzz on 2017-12-25 (Christmas Day) +# also caused by check-in https://sqlite.org/src/info/6b2ff26c25 +# +# Prior to being fixed, the following CREATE TABLE caused an +# assertion fault. +# +do_catchsql_test colname-9.410 { + CREATE TABLE t5 AS SELECT RAISE(abort,a); +} {1 {RAISE() may only be used within a trigger-program}} + # Make sure the quotation marks get removed from the column names # when constructing a new table from an aggregate SELECT. # Email from Juergen Palm on 2017-07-11. diff --git a/test/crash8.test b/test/crash8.test index f3b6f6e244..7916e9b641 100644 --- a/test/crash8.test +++ b/test/crash8.test @@ -142,6 +142,7 @@ proc write_file {zFile zData} { # b) Less than 512, or # c) Greater than SQLITE_MAX_PAGE_SIZE # +if {[atomic_batch_write test.db]==0} { do_test crash8-3.1 { list [file exists test.db-joural] [file exists test.db] } {0 1} @@ -228,6 +229,7 @@ do_test crash8-3.11 { PRAGMA integrity_check } } {6 ok} +} # If a connection running in persistent-journal mode is part of a @@ -266,8 +268,12 @@ ifcapable pragma { UPDATE aux.ab SET b = randstr(1000,1000) WHERE a>=1; UPDATE ab SET b = randstr(1000,1000) WHERE a>=1; } - list [file exists test.db-journal] [file exists test2.db-journal] - } {1 1} + } {persist persist} + if {[atomic_batch_write test.db]==0} { + do_test crash8.4.1.1 { + list [file exists test.db-journal] [file exists test2.db-journal] + } {1 1} + } do_test crash8-4.2 { execsql { diff --git a/test/delete_db.test b/test/delete_db.test index 09c44ff9f3..6edd9c242e 100644 --- a/test/delete_db.test +++ b/test/delete_db.test @@ -17,6 +17,11 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix delete_db +if {[atomic_batch_write test.db]} { + finish_test + return +} + proc delete_all {} { foreach f [glob -nocomplain test2*] { file delete $f } foreach f [glob -nocomplain test3*] { file delete $f } diff --git a/test/distinct2.test b/test/distinct2.test index a7d59db705..31ab355132 100644 --- a/test/distinct2.test +++ b/test/distinct2.test @@ -179,5 +179,55 @@ do_execsql_test 920 { wxYZ wxYz wxYz wxyZ wxyZ wxyz wxyz } +# Ticket https://sqlite.org/src/info/ef9318757b152e3a on 2017-11-21 +# Incorrect result due to a skip-ahead-distinct optimization on a +# join where no rows of the inner loop appear in the result set. +# +db close +sqlite3 db :memory: +do_execsql_test 1000 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER); + CREATE INDEX t1b ON t1(b); + CREATE TABLE t2(x INTEGER PRIMARY KEY, y INTEGER); + CREATE INDEX t2y ON t2(y); + WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM c WHERE x<49) + INSERT INTO t1(b) SELECT x/10 - 1 FROM c; + WITH RECURSIVE c(x) AS (VALUES(-1) UNION ALL SELECT x+1 FROM c WHERE x<19) + INSERT INTO t2(x,y) SELECT x, 1 FROM c; + SELECT DISTINCT y FROM t1, t2 WHERE b=x AND b<>-1; + ANALYZE; + SELECT DISTINCT y FROM t1, t2 WHERE b=x AND b<>-1; +} {1 1} +db close +sqlite3 db :memory: +do_execsql_test 1010 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER); + CREATE INDEX t1b ON t1(b); + CREATE TABLE t2(x INTEGER PRIMARY KEY, y INTEGER); + CREATE INDEX t2y ON t2(y); + WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM c WHERE x<49) + INSERT INTO t1(b) SELECT -(x/10 - 1) FROM c; + WITH RECURSIVE c(x) AS (VALUES(-1) UNION ALL SELECT x+1 FROM c WHERE x<19) + INSERT INTO t2(x,y) SELECT -x, 1 FROM c; + SELECT DISTINCT y FROM t1, t2 WHERE b=x AND b<>1 ORDER BY y DESC; + ANALYZE; + SELECT DISTINCT y FROM t1, t2 WHERE b=x AND b<>1 ORDER BY y DESC; +} {1 1} +db close +sqlite3 db :memory: +do_execsql_test 1020 { + CREATE TABLE t1(a, b); + CREATE INDEX t1a ON t1(a, b); + -- Lots of rows of (1, 'no'), followed by a single (1, 'yes'). + WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<100) + INSERT INTO t1(a, b) SELECT 1, 'no' FROM c; + INSERT INTO t1(a, b) VALUES(1, 'yes'); + CREATE TABLE t2(x PRIMARY KEY); + INSERT INTO t2 VALUES('yes'); + SELECT DISTINCT a FROM t1, t2 WHERE x=b; + ANALYZE; + SELECT DISTINCT a FROM t1, t2 WHERE x=b; +} {1 1} + finish_test diff --git a/test/exclusive.test b/test/exclusive.test index 45f9318205..04de529137 100644 --- a/test/exclusive.test +++ b/test/exclusive.test @@ -252,7 +252,9 @@ db2 close # opens the journal file for exclusive access, preventing its contents # from being inspected externally. # -if {$tcl_platform(platform) != "windows"} { +if {$tcl_platform(platform) != "windows" + && [atomic_batch_write test.db]==0 +} { # Return a list of two booleans (either 0 or 1). The first is true # if the named file exists. The second is true only if the file @@ -391,6 +393,7 @@ do_test exclusive-4.5 { # Tests exclusive-5.X - test that statement journals are truncated # instead of deleted when in exclusive access mode. # +if {[atomic_batch_write test.db]==0} { # Close and reopen the database so that the temp database is no # longer active. @@ -508,4 +511,6 @@ do_execsql_test exclusive-6.5 { SELECT * FROM sqlite_master; } {exclusive} +} ;# atomic_batch_write==0 + finish_test diff --git a/test/expr.test b/test/expr.test index 7a6d477259..3cdc9180e8 100644 --- a/test/expr.test +++ b/test/expr.test @@ -977,6 +977,63 @@ do_execsql_test expr-13.9 { SELECT '' <= ""; } {1} +# 2018-02-26. Ticket https://www.sqlite.org/src/tktview/36fae083b450e3af85 +# +do_execsql_test expr-14.1 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(x); + INSERT INTO t1 VALUES(0),(1),(NULL),(0.5),('1x'),('0x'); + SELECT count(*) FROM t1 + WHERE (x OR (8==9)) != (CASE WHEN x THEN 1 ELSE 0 END); +} {0} +do_execsql_test expr-14.2 { + SELECT count(*) FROM t1 + WHERE (x OR (8==9)) != (NOT NOT x); +} {0} +do_execsql_test expr-14.3 { + SELECT sum(NOT x) FROM t1 + WHERE x +} {0} +do_execsql_test expr-14.4 { + SELECT sum(CASE WHEN x THEN 0 ELSE 1 END) FROM t1 + WHERE x +} {0} +foreach {tn val} [list 1 NaN 2 -NaN 3 NaN0 4 -NaN0 5 Inf 6 -Inf] { + do_execsql_test expr-15.$tn.1 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(x); + INSERT INTO t1 VALUES(0),(1),(NULL),(0.5),('1x'),('0x'); + } + + do_test expr-15.$tn.2 { + set ::STMT [sqlite3_prepare db "INSERT INTO t1 VALUES(?)" -1 TAIL] + sqlite3_bind_double $::STMT 1 $val + sqlite3_step $::STMT + sqlite3_reset $::STMT + sqlite3_finalize $::STMT + } {SQLITE_OK} + + do_execsql_test expr-15.$tn.3 { + SELECT count(*) FROM t1 + WHERE (x OR (8==9)) != (CASE WHEN x THEN 1 ELSE 0 END); + } {0} + + do_execsql_test expr-15.$tn.4 { + SELECT count(*) FROM t1 + WHERE (x OR (8==9)) != (NOT NOT x); + } {0} + + do_execsql_test expr-15.$tn.5 { + SELECT sum(NOT x) FROM t1 + WHERE x + } {0} + + do_execsql_test expr-15.$tn.6 { + SELECT sum(CASE WHEN x THEN 0 ELSE 1 END) FROM t1 + WHERE x + } {0} +} + finish_test diff --git a/test/fallocate.test b/test/fallocate.test index 63d88ea885..0c971c08c1 100644 --- a/test/fallocate.test +++ b/test/fallocate.test @@ -61,6 +61,7 @@ do_test fallocate-1.7 { execsql { BEGIN; INSERT INTO t1 VALUES(1, 2); } if {[permutation] != "inmemory_journal" && [permutation] != "atomic-batch-write" + && [atomic_batch_write test.db]==0 } { hexio_get_int [hexio_read test.db-journal 16 4] } else { diff --git a/test/fkey1.test b/test/fkey1.test index d9b038a022..fa87335888 100644 --- a/test/fkey1.test +++ b/test/fkey1.test @@ -171,6 +171,22 @@ do_catchsql_test fkey1-5.2 { INSERT OR REPLACE INTO t11 VALUES (2, 3); } {1 {FOREIGN KEY constraint failed}} +# Make sure sqlite3_trace() output works with triggers used to implement +# FK constraints +# +ifcapable trace { + proc sqltrace {txt} { + global traceoutput + lappend traceoutput $txt + } + do_test fkey1-5.2.1 { + unset -nocomplain traceoutput + db trace sqltrace + catch {db eval {INSERT OR REPLACE INTO t11 VALUES(2,3);}} + set traceoutput + } {{INSERT OR REPLACE INTO t11 VALUES(2,3);} {INSERT OR REPLACE INTO t11 VALUES(2,3);} {INSERT OR REPLACE INTO t11 VALUES(2,3);}} +} + # A similar test to the above. do_execsql_test fkey1-5.3 { CREATE TABLE Foo ( diff --git a/test/fkey7.test b/test/fkey7.test index 6c646a9a7f..e86fc5c57b 100644 --- a/test/fkey7.test +++ b/test/fkey7.test @@ -68,4 +68,18 @@ ifcapable incrblob { } {SQLITE_CONSTRAINT} } +ifcapable stat4 { + do_execsql_test 3.0 { + CREATE TABLE p4 (id INTEGER NOT NULL PRIMARY KEY); + INSERT INTO p4 VALUES(1), (2), (3); + + CREATE TABLE c4(x INTEGER REFERENCES p4(id) DEFERRABLE INITIALLY DEFERRED); + CREATE INDEX c4_x ON c4(x); + INSERT INTO c4 VALUES(1), (2), (3); + + ANALYZE; + INSERT INTO p4(id) VALUES(4); + } +} + finish_test diff --git a/test/fts3aa.test b/test/fts3aa.test index 10ec273cbf..d5f96d81a7 100644 --- a/test/fts3aa.test +++ b/test/fts3aa.test @@ -250,4 +250,5 @@ do_execsql_test 9.2 { CREATE VIRTUAL TABLE t10 USING fts3(<, b, c); } +expand_all_sql db finish_test diff --git a/test/fts3rank.test b/test/fts3rank.test index 7ee3143a76..fd1a1c89d7 100644 --- a/test/fts3rank.test +++ b/test/fts3rank.test @@ -14,7 +14,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl -set testprefix fts3expr5 +set testprefix fts3rank # If SQLITE_ENABLE_FTS3 is defined, omit this file. ifcapable !fts3 { @@ -56,9 +56,14 @@ do_catchsql_test 1.4 { SELECT * FROM t1 ORDER BY rank(x'0000000000000000') DESC, rowid } {0 {{one two} one {one two} three {one two} two}} -do_catchsql_test 1.5 { - SELECT * FROM t1 ORDER BY rank(x'0100000001000000') DESC, rowid -} {1 {invalid matchinfo blob passed to function rank()}} +if {$tcl_platform(byteOrder)=="littleEndian"} { + do_catchsql_test 1.5le { + SELECT * FROM t1 ORDER BY rank(x'0100000001000000') DESC, rowid + } {1 {invalid matchinfo blob passed to function rank()}} +} else { + do_catchsql_test 1.5be { + SELECT * FROM t1 ORDER BY rank(x'0000000100000001') DESC, rowid + } {1 {invalid matchinfo blob passed to function rank()}} +} finish_test - diff --git a/test/fts4onepass.test b/test/fts4onepass.test index 46cb4b794b..344be4b1df 100644 --- a/test/fts4onepass.test +++ b/test/fts4onepass.test @@ -143,4 +143,18 @@ foreach {tn tcl1 tcl2} { eval $tcl2 } +do_execsql_test 4.0 { + CREATE VIRTUAL TABLE zt USING fts4(a, b); + INSERT INTO zt(rowid, a, b) VALUES(1, 'unus duo', NULL); + INSERT INTO zt(rowid, a, b) VALUES(2, NULL, NULL); + + BEGIN; + UPDATE zt SET b='septum' WHERE rowid = 1; + UPDATE zt SET b='octo' WHERE rowid = 1; + COMMIT; + + SELECT count(*) FROM zt_segdir; +} {3} + + finish_test diff --git a/test/func.test b/test/func.test index 98ae8ddeb5..23a3ae4392 100644 --- a/test/func.test +++ b/test/func.test @@ -507,6 +507,17 @@ if {$encoding=="UTF-16le"} { execsql {SELECT hex(replace('aabcdefg','a','aaa'))} } {616161616161626364656667} } +do_execsql_test func-9.14 { + WITH RECURSIVE c(x) AS ( + VALUES(1) + UNION ALL + SELECT x+1 FROM c WHERE x<1040 + ) + SELECT + count(*), + sum(length(replace(printf('abc%.*cxyz',x,'m'),'m','nnnn'))-(6+x*4)) + FROM c; +} {1040 0} # Use the "sqlite_register_test_function" TCL command which is part of # the text fixture in order to verify correct operation of some of diff --git a/test/func6.test b/test/func6.test new file mode 100644 index 0000000000..1e16a7ca38 --- /dev/null +++ b/test/func6.test @@ -0,0 +1,174 @@ +# 2017-12-16 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#************************************************************************* +# +# Test cases for the sqlite_offset() function. +# +# Some of the tests in this file depend on the exact placement of content +# within b-tree pages. Such placement is at the implementations discretion, +# and so it is possible for results to change from one release to the next. +# +set testdir [file dirname $argv0] +source $testdir/tester.tcl +ifcapable !offset_sql_func { + finish_test + return +} + +set bNullTrim 0 +ifcapable null_trim { + set bNullTrim 1 +} + +do_execsql_test func6-100 { + PRAGMA page_size=4096; + PRAGMA auto_vacuum=NONE; + CREATE TABLE t1(a,b,c,d); + WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<100) + INSERT INTO t1(a,b,c,d) SELECT printf('abc%03x',x), x, 1000-x, NULL FROM c; + CREATE INDEX t1a ON t1(a); + CREATE INDEX t1bc ON t1(b,c); + CREATE TABLE t2(x TEXT PRIMARY KEY, y) WITHOUT ROWID; + INSERT INTO t2(x,y) SELECT a, b FROM t1; +} + +# Load the contents of $file from disk and return it encoded as a hex +# string. +proc loadhex {file} { + set fd [open $file] + fconfigure $fd -translation binary -encoding binary + set data [read $fd] + close $fd + binary encode hex $data +} + +# Each argument is either an integer between 0 and 65535, a text value, or +# an empty string representing an SQL NULL. This command builds an SQLite +# record containing the values passed as arguments and returns it encoded +# as a hex string. +proc hexrecord {args} { + set hdr "" + set body "" + + if {$::bNullTrim} { + while {[llength $args] && [lindex $args end]=={}} { + set args [lrange $args 0 end-1] + } + } + + foreach x $args { + if {$x==""} { + append hdr 00 + } elseif {[string is integer $x]==0} { + set n [string length $x] + append hdr [format %02x [expr $n*2 + 13]] + append body [binary encode hex $x] + } elseif {$x == 0} { + append hdr 08 + } elseif {$x == 1} { + append hdr 09 + } elseif {$x <= 127} { + append hdr 01 + append body [format %02x $x] + } else { + append hdr 02 + append body [format %04x $x] + } + } + set res [format %02x [expr 1 + [string length $hdr]/2]] + append res $hdr + append res $body +} + +# Argument $off is an offset into the database image encoded as a hex string +# in argument $hexdb. This command returns 0 if the offset contains the hex +# $hexrec, or throws an exception otherwise. +# +proc offset_contains_record {off hexdb hexrec} { + set n [string length $hexrec] + set off [expr $off*2] + if { [string compare $hexrec [string range $hexdb $off [expr $off+$n-1]]] } { + error "record not found!" + } + return 0 +} + +# This command is the implementation of SQL function "offrec()". The first +# argument to this is an offset value. The remaining values are used to +# formulate an SQLite record. If database file test.db does not contain +# an equivalent record at the specified offset, an exception is thrown. +# Otherwise, 0 is returned. +# +proc offrec {args} { + set offset [lindex $args 0] + set rec [hexrecord {*}[lrange $args 1 end]] + offset_contains_record $offset $::F $rec +} +set F [loadhex test.db] +db func offrec offrec + +# Test the sanity of the tests. +if {$bNullTrim} { + set offset 8180 +} else { + set offset 8179 +} +do_execsql_test func6-105 { + SELECT sqlite_offset(d) FROM t1 ORDER BY rowid LIMIT 1; +} $offset +do_test func6-106 { + set r [hexrecord abc001 1 999 {}] + offset_contains_record $offset $F $r +} 0 + +set z100 [string trim [string repeat "0 " 100]] + +# Test offsets within table b-tree t1. +do_execsql_test func6-110 { + SELECT offrec(sqlite_offset(d), a, b, c, d) FROM t1 ORDER BY rowid +} $z100 + +do_execsql_test func6-120 { + SELECT a, typeof(sqlite_offset(+a)) FROM t1 + ORDER BY rowid LIMIT 2; +} {abc001 null abc002 null} + +# Test offsets within index b-tree t1a. +do_execsql_test func6-130 { + SELECT offrec(sqlite_offset(a), a, rowid) FROM t1 ORDER BY a +} $z100 + +# Test offsets within table b-tree t1 with a temp b-tree ORDER BY. +do_execsql_test func6-140 { + SELECT offrec(sqlite_offset(d), a, b, c, d) FROM t1 ORDER BY a +} $z100 + +# Test offsets from both index t1a and table t1 in the same query. +do_execsql_test func6-150 { + SELECT offrec(sqlite_offset(a), a, rowid), + offrec(sqlite_offset(d), a, b, c, d) + FROM t1 ORDER BY a +} [concat $z100 $z100] + +# Test offsets from both index t1bc and table t1 in the same query. +do_execsql_test func6-160 { + SELECT offrec(sqlite_offset(b), b, c, rowid), + offrec(sqlite_offset(c), b, c, rowid), + offrec(sqlite_offset(d), a, b, c, d) + FROM t1 + ORDER BY b +} [concat $z100 $z100 $z100] + +# Test offsets in WITHOUT ROWID table t2. +do_execsql_test func6-200 { + SELECT offrec( sqlite_offset(y), x, y ) FROM t2 ORDER BY x +} $z100 + +finish_test diff --git a/test/hook.test b/test/hook.test index 9ba220cded..1c9145baef 100644 --- a/test/hook.test +++ b/test/hook.test @@ -906,5 +906,56 @@ do_preupdate_test 10.3 { DELETE FROM t3 WHERE b=1 } {DELETE main t3 1 1 0 {} 1} +#------------------------------------------------------------------------- +# Test that the "update" hook is not fired for operations on the +# sqlite_stat1 table performed by ANALYZE, even if a pre-update hook is +# registered. +ifcapable analyze { + reset_db + do_execsql_test 11.1 { + CREATE TABLE t1(a, b); + CREATE INDEX idx1 ON t1(a); + CREATE INDEX idx2 ON t1(b); + + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(3, 4); + INSERT INTO t1 VALUES(5, 6); + INSERT INTO t1 VALUES(7, 8); + } + + db preupdate hook preupdate_cb + db update_hook update_cb + + proc preupdate_cb {args} { lappend ::res "preupdate" $args } + proc update_cb {args} { lappend ::res "update" $args } + + set ::res [list] + do_test 11.2 { + execsql ANALYZE + set ::res + } [list {*}{ + preupdate {INSERT main sqlite_stat1 1 1} + preupdate {INSERT main sqlite_stat1 2 2} + }] + + do_execsql_test 11.3 { + INSERT INTO t1 VALUES(9, 10); + INSERT INTO t1 VALUES(11, 12); + INSERT INTO t1 VALUES(13, 14); + INSERT INTO t1 VALUES(15, 16); + } + + set ::res [list] + do_test 11.4 { + execsql ANALYZE + set ::res + } [list {*}{ + preupdate {DELETE main sqlite_stat1 1 1} + preupdate {DELETE main sqlite_stat1 2 2} + preupdate {INSERT main sqlite_stat1 1 1} + preupdate {INSERT main sqlite_stat1 2 2} + }] +} + finish_test diff --git a/test/icu.test b/test/icu.test index 743bcfaea1..4c4e6d14ec 100644 --- a/test/icu.test +++ b/test/icu.test @@ -15,7 +15,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl -ifcapable !icu { +ifcapable !icu&&!icu_collations { finish_test return } @@ -35,54 +35,57 @@ proc test_expr {name settings expr result} { } $settings $expr] $result } -# Tests of the REGEXP operator. -# -test_expr icu-1.1 {i1='hello'} {i1 REGEXP 'hello'} 1 -test_expr icu-1.2 {i1='hello'} {i1 REGEXP '.ello'} 1 -test_expr icu-1.3 {i1='hello'} {i1 REGEXP '.ell'} 0 -test_expr icu-1.4 {i1='hello'} {i1 REGEXP '.ell.*'} 1 -test_expr icu-1.5 {i1=NULL} {i1 REGEXP '.ell.*'} {} +ifcapable icu { -# Some non-ascii characters with defined case mappings -# -set ::EGRAVE "\xC8" -set ::egrave "\xE8" + # Tests of the REGEXP operator. + # + test_expr icu-1.1 {i1='hello'} {i1 REGEXP 'hello'} 1 + test_expr icu-1.2 {i1='hello'} {i1 REGEXP '.ello'} 1 + test_expr icu-1.3 {i1='hello'} {i1 REGEXP '.ell'} 0 + test_expr icu-1.4 {i1='hello'} {i1 REGEXP '.ell.*'} 1 + test_expr icu-1.5 {i1=NULL} {i1 REGEXP '.ell.*'} {} -set ::OGRAVE "\xD2" -set ::ograve "\xF2" + # Some non-ascii characters with defined case mappings + # + set ::EGRAVE "\xC8" + set ::egrave "\xE8" -# That German letter that looks a bit like a B. The -# upper-case version of which is "SS" (two characters). -# -set ::szlig "\xDF" + set ::OGRAVE "\xD2" + set ::ograve "\xF2" -# Tests of the upper()/lower() functions. -# -test_expr icu-2.1 {i1='HellO WorlD'} {upper(i1)} {HELLO WORLD} -test_expr icu-2.2 {i1='HellO WorlD'} {lower(i1)} {hello world} -test_expr icu-2.3 {i1=$::egrave} {lower(i1)} $::egrave -test_expr icu-2.4 {i1=$::egrave} {upper(i1)} $::EGRAVE -test_expr icu-2.5 {i1=$::ograve} {lower(i1)} $::ograve -test_expr icu-2.6 {i1=$::ograve} {upper(i1)} $::OGRAVE -test_expr icu-2.3 {i1=$::EGRAVE} {lower(i1)} $::egrave -test_expr icu-2.4 {i1=$::EGRAVE} {upper(i1)} $::EGRAVE -test_expr icu-2.5 {i1=$::OGRAVE} {lower(i1)} $::ograve -test_expr icu-2.6 {i1=$::OGRAVE} {upper(i1)} $::OGRAVE + # That German letter that looks a bit like a B. The + # upper-case version of which is "SS" (two characters). + # + set ::szlig "\xDF" -test_expr icu-2.7 {i1=$::szlig} {upper(i1)} "SS" -test_expr icu-2.8 {i1='SS'} {lower(i1)} "ss" + # Tests of the upper()/lower() functions. + # + test_expr icu-2.1 {i1='HellO WorlD'} {upper(i1)} {HELLO WORLD} + test_expr icu-2.2 {i1='HellO WorlD'} {lower(i1)} {hello world} + test_expr icu-2.3 {i1=$::egrave} {lower(i1)} $::egrave + test_expr icu-2.4 {i1=$::egrave} {upper(i1)} $::EGRAVE + test_expr icu-2.5 {i1=$::ograve} {lower(i1)} $::ograve + test_expr icu-2.6 {i1=$::ograve} {upper(i1)} $::OGRAVE + test_expr icu-2.3 {i1=$::EGRAVE} {lower(i1)} $::egrave + test_expr icu-2.4 {i1=$::EGRAVE} {upper(i1)} $::EGRAVE + test_expr icu-2.5 {i1=$::OGRAVE} {lower(i1)} $::ograve + test_expr icu-2.6 {i1=$::OGRAVE} {upper(i1)} $::OGRAVE -do_execsql_test icu-2.9 { - SELECT upper(char(0xfb04,0xfb04,0xfb04,0xfb04)); -} {FFLFFLFFLFFL} + test_expr icu-2.7 {i1=$::szlig} {upper(i1)} "SS" + test_expr icu-2.8 {i1='SS'} {lower(i1)} "ss" -# In turkish (locale="tr_TR"), the lower case version of I -# is "small dotless i" (code point 0x131 (decimal 305)). -# -set ::small_dotless_i "\u0131" -test_expr icu-3.1 {i1='I'} {lower(i1)} "i" -test_expr icu-3.2 {i1='I'} {lower(i1, 'tr_tr')} $::small_dotless_i -test_expr icu-3.3 {i1='I'} {lower(i1, 'en_AU')} "i" + do_execsql_test icu-2.9 { + SELECT upper(char(0xfb04,0xfb04,0xfb04,0xfb04)); + } {FFLFFLFFLFFL} + + # In turkish (locale="tr_TR"), the lower case version of I + # is "small dotless i" (code point 0x131 (decimal 305)). + # + set ::small_dotless_i "\u0131" + test_expr icu-3.1 {i1='I'} {lower(i1)} "i" + test_expr icu-3.2 {i1='I'} {lower(i1, 'tr_tr')} $::small_dotless_i + test_expr icu-3.3 {i1='I'} {lower(i1, 'en_AU')} "i" +} #-------------------------------------------------------------------- # Test the collation sequence function. @@ -124,22 +127,23 @@ do_test icu-4.3 { # # http://src.chromium.org/viewvc/chrome/trunk/src/third_party/sqlite/icu-regexp.patch?revision=34807&view=markup # -do_catchsql_test icu-5.1 { SELECT regexp('a[abc]c.*', 'abc') } {0 1} -do_catchsql_test icu-5.2 { - SELECT regexp('a[abc]c.*') -} {1 {wrong number of arguments to function regexp()}} -do_catchsql_test icu-5.3 { - SELECT regexp('a[abc]c.*', 'abc', 'c') -} {1 {wrong number of arguments to function regexp()}} -do_catchsql_test icu-5.4 { - SELECT 'abc' REGEXP 'a[abc]c.*' -} {0 1} -do_catchsql_test icu-5.4 { SELECT 'abc' REGEXP } {1 {near " ": syntax error}} -do_catchsql_test icu-5.5 { SELECT 'abc' REGEXP, 1 } {1 {near ",": syntax error}} - - -do_malloc_test icu-6.10 -sqlbody { - SELECT upper(char(0xfb04,0xdf,0xfb04,0xe8,0xfb04)); +ifcapable icu { + do_catchsql_test icu-5.1 { SELECT regexp('a[abc]c.*', 'abc') } {0 1} + do_catchsql_test icu-5.2 { + SELECT regexp('a[abc]c.*') + } {1 {wrong number of arguments to function regexp()}} + do_catchsql_test icu-5.3 { + SELECT regexp('a[abc]c.*', 'abc', 'c') + } {1 {wrong number of arguments to function regexp()}} + do_catchsql_test icu-5.4 { + SELECT 'abc' REGEXP 'a[abc]c.*' + } {0 1} + do_catchsql_test icu-5.5 {SELECT 'abc' REGEXP } {1 {incomplete input}} + do_catchsql_test icu-5.6 {SELECT 'abc' REGEXP, 1} {1 {near ",": syntax error}} + + do_malloc_test icu-6.10 -sqlbody { + SELECT upper(char(0xfb04,0xdf,0xfb04,0xe8,0xfb04)); + } } finish_test diff --git a/test/indexexpr1.test b/test/indexexpr1.test index 0e24c8066f..e93dcc0cd1 100644 --- a/test/indexexpr1.test +++ b/test/indexexpr1.test @@ -401,5 +401,49 @@ do_execsql_test indexexpr1-1430 { SELECT abs(15+3) IN (SELECT 17 UNION ALL SELECT 18) FROM t1; } {1 1} +# 2018-01-02 ticket https://sqlite.org/src/info/dc3f932f5a147771 +# A REPLACE into a table that uses an index on an expression causes +# an assertion fault. Problem discovered by OSSFuzz. +# +do_execsql_test indexexpr1-1500 { + CREATE TABLE t1500(a INT PRIMARY KEY, b INT UNIQUE); + CREATE INDEX t1500ab ON t1500(a*b); + INSERT INTO t1500(a,b) VALUES(1,2); + REPLACE INTO t1500(a,b) VALUES(1,3); -- formerly caused assertion fault + SELECT * FROM t1500; +} {1 3} + +# 2018-01-03 OSSFuzz discovers another test case for the same problem +# above. +# +do_execsql_test indexexpr-1510 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a PRIMARY KEY,b UNIQUE); + REPLACE INTO t1 VALUES(2, 1); + REPLACE INTO t1 SELECT 6,1; + CREATE INDEX t1aa ON t1(a-a); + REPLACE INTO t1 SELECT a, randomblob(a) FROM t1 +} {} + +# 2018-01-31 https://www.sqlite.org/src/tktview/343634942dd54ab57b702411 +# When an index on an expression depends on the string representation of +# a numeric table column, trouble can arise since there are multiple +# string that can map to the same numeric value. (Ex: 123, 0123, 000123). +# +do_execsql_test indexexpr-1600 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1 (a INTEGER, b); + CREATE INDEX idx1 ON t1 (lower(a)); + INSERT INTO t1 VALUES('0001234',3); + PRAGMA integrity_check; +} {ok} +do_execsql_test indexexpr-1610 { + INSERT INTO t1 VALUES('1234',0),('001234',2),('01234',1); + SELECT b FROM t1 WHERE lower(a)='1234' ORDER BY +b; +} {0 1 2 3} +do_execsql_test indexexpr-1620 { + SELECT b FROM t1 WHERE lower(a)='01234' ORDER BY +b; +} {} + finish_test diff --git a/test/ioerr.test b/test/ioerr.test index e59647fe50..f42beef5b4 100644 --- a/test/ioerr.test +++ b/test/ioerr.test @@ -172,7 +172,7 @@ ifcapable crashtest&&attach { # These tests can't be run on windows because the windows version of # SQLite holds a mandatory exclusive lock on journal files it has open. # -if {$tcl_platform(platform)!="windows"} { +if {$tcl_platform(platform)!="windows" && ![atomic_batch_write test.db]} { do_ioerr_test ioerr-7 -tclprep { db close sqlite3 db2 test2.db @@ -211,7 +211,7 @@ do_ioerr_test ioerr-8 -ckrefcount true -tclprep { # For test coverage: Cause an IO error whilst reading the master-journal # name from a journal file. -if {$tcl_platform(platform)=="unix"} { +if {$tcl_platform(platform)=="unix" && [atomic_batch_write test.db]==0} { do_ioerr_test ioerr-9 -ckrefcount true -tclprep { execsql { CREATE TABLE t1(a,b,c); diff --git a/test/istrue.test b/test/istrue.test new file mode 100644 index 0000000000..250f1f9d5d --- /dev/null +++ b/test/istrue.test @@ -0,0 +1,126 @@ +# 2018-02-26 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The +# focus of this file is testing expressions of the form +# +# x IS TRUE +# x IS FALSE +# x IS NOT TRUE +# x IS NOT FALSE +# +# Tests are also included for the use of TRUE and FALSE as +# literal values. + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +do_execsql_test istrue-100 { + CREATE TABLE t1(x INTEGER PRIMARY KEY, y BOOLEAN); + INSERT INTO t1 VALUES(1, true),(2, false),(3, null); + SELECT x FROM t1 WHERE y IS TRUE; +} {1} +do_execsql_test istrue-110 { + SELECT x FROM t1 WHERE y IS FALSE; +} {2} +do_execsql_test istrue-120 { + SELECT x FROM t1 WHERE y IS NULL; +} {3} +do_execsql_test istrue-130 { + SELECT x FROM t1 WHERE y IS NOT TRUE; +} {2 3} +do_execsql_test istrue-140 { + SELECT x FROM t1 WHERE y IS NOT FALSE; +} {1 3} +do_execsql_test istrue-150 { + SELECT x FROM t1 WHERE y IS NOT NULL; +} {1 2} +unset -nocomplain X +set X 9 +do_execsql_test istrue-160 { + SELECT x FROM t1 WHERE y IS TRUE OR (8==$X) +} {1} +do_execsql_test istrue-170 { + SELECT x FROM t1 WHERE y IS FALSE OR (8==$X) +} {2} +do_execsql_test istrue-180 { + SELECT x FROM t1 WHERE y IS NULL OR (8==$X); +} {3} +do_execsql_test istrue-190 { + SELECT x FROM t1 WHERE y IS NOT TRUE OR (8==$X); +} {2 3} +do_execsql_test istrue-200 { + SELECT x FROM t1 WHERE y IS NOT FALSE OR (8==$X); +} {1 3} +do_execsql_test istrue-210 { + SELECT x FROM t1 WHERE y IS NOT NULL OR (8==$X); +} {1 2} + +do_execsql_test istrue-300 { + SELECT x, + y IS TRUE, y IS FALSE, y is NULL, + y IS NOT TRUE, y IS NOT FALSE, y IS NOT NULL, '|' + FROM t1 ORDER BY x; +} {1 1 0 0 0 1 1 | 2 0 1 0 1 0 1 | 3 0 0 1 1 1 0 |} + +do_execsql_test istrue-400 { + SELECT x FROM t1 WHERE true; +} {1 2 3} +do_execsql_test istrue-410 { + SELECT x FROM t1 WHERE false; +} {} + +do_execsql_test istrue-500 { + CREATE TABLE t2( + a INTEGER PRIMARY KEY, + b BOOLEAN DEFAULT true, + c BOOLEAN DEFAULT(true), + d BOOLEAN DEFAULT false, + e BOOLEAN DEFAULT(false) + ); + INSERT INTO t2 DEFAULT VALUES; + SELECT * FROM t2; +} {1 1 1 0 0} +do_execsql_test istrue-510 { + DROP TABLE t2; + CREATE TABLE t2( + a INTEGER PRIMARY KEY, + b BOOLEAN DEFAULT(not true), + c BOOLEAN DEFAULT(not false) + ); + INSERT INTO t2(a) VALUES(99); + SELECT * FROM t2; +} {99 0 1} +do_execsql_test istrue-520 { + DROP TABLE t2; + CREATE TABLE t2( + a INTEGER PRIMARY KEY, + b BOOLEAN CHECK(b IS TRUE), + c BOOLEAN CHECK(c IS FALSE), + d BOOLEAN CHECK(d IS NOT TRUE), + e BOOLEAN CHECK(e IS NOT FALSE) + ); + INSERT INTO t2 VALUES(1,true,false,null,null); + SELECT * FROM t2; +} {1 1 0 {} {}} +do_catchsql_test istrue-521 { + INSERT INTO t2 VALUES(2,false,false,null,null); +} {1 {CHECK constraint failed: t2}} +do_catchsql_test istrue-522 { + INSERT INTO t2 VALUES(2,true,true,null,null); +} {1 {CHECK constraint failed: t2}} +do_catchsql_test istrue-523 { + INSERT INTO t2 VALUES(2,true,false,true,null); +} {1 {CHECK constraint failed: t2}} +do_catchsql_test istrue-524 { + INSERT INTO t2 VALUES(2,true,false,null,false); +} {1 {CHECK constraint failed: t2}} + +finish_test diff --git a/test/join2.test b/test/join2.test index 9372e770c3..256846ac91 100644 --- a/test/join2.test +++ b/test/join2.test @@ -92,4 +92,177 @@ do_catchsql_test 2.2 { SELECT * FROM aa JOIN cc ON (a=b) JOIN bb ON (b=c); } {0 {one one one}} +#------------------------------------------------------------------------- +# Test that a problem causing where.c to overlook opportunities to +# omit unnecessary tables from a LEFT JOIN when UNIQUE, NOT NULL column +# that makes this possible happens to be the leftmost in its table. +# +reset_db +do_execsql_test 3.0 { + CREATE TABLE t1(k1 INTEGER PRIMARY KEY, k2, k3); + CREATE TABLE t2(k2 INTEGER PRIMARY KEY, v2); + + -- Prior to this problem being fixed, table t3_2 would be omitted from + -- the join queries below, but if t3_1 were used in its place it would + -- not. + CREATE TABLE t3_1(k3 PRIMARY KEY, v3) WITHOUT ROWID; + CREATE TABLE t3_2(v3, k3 PRIMARY KEY) WITHOUT ROWID; +} + +do_eqp_test 3.1 { + SELECT v2 FROM t1 LEFT JOIN t2 USING (k2) LEFT JOIN t3_1 USING (k3); +} { + 0 0 0 {SCAN TABLE t1} + 0 1 1 {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?)} +} + +do_eqp_test 3.2 { + SELECT v2 FROM t1 LEFT JOIN t2 USING (k2) LEFT JOIN t3_2 USING (k3); +} { + 0 0 0 {SCAN TABLE t1} + 0 1 1 {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?)} +} + +#------------------------------------------------------------------------- +# Test that tables other than the rightmost can be omitted from a +# LEFT JOIN query. +# +do_execsql_test 4.0 { + CREATE TABLE c1(k INTEGER PRIMARY KEY, v1); + CREATE TABLE c2(k INTEGER PRIMARY KEY, v2); + CREATE TABLE c3(k INTEGER PRIMARY KEY, v3); + + INSERT INTO c1 VALUES(1, 2); + INSERT INTO c2 VALUES(2, 3); + INSERT INTO c3 VALUES(3, 'v3'); + + INSERT INTO c1 VALUES(111, 1112); + INSERT INTO c2 VALUES(112, 1113); + INSERT INTO c3 VALUES(113, 'v1113'); +} +do_execsql_test 4.1.1 { + SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v2); +} {2 v3 1112 {}} +do_execsql_test 4.1.2 { + SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v1+1); +} {2 v3 1112 {}} + +do_execsql_test 4.1.3 { + SELECT DISTINCT v1, v3 FROM c1 LEFT JOIN c2 LEFT JOIN c3 ON (c3.k=v1+1); +} {2 v3 1112 {}} + +do_execsql_test 4.1.4 { + SELECT v1, v3 FROM c1 LEFT JOIN c2 LEFT JOIN c3 ON (c3.k=v1+1); +} {2 v3 2 v3 1112 {} 1112 {}} + +do_eqp_test 4.1.5 { + SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v2); +} { + 0 0 0 {SCAN TABLE c1} + 0 1 1 {SEARCH TABLE c2 USING INTEGER PRIMARY KEY (rowid=?)} + 0 2 2 {SEARCH TABLE c3 USING INTEGER PRIMARY KEY (rowid=?)} +} +do_eqp_test 4.1.6 { + SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v1+1); +} { + 0 0 0 {SCAN TABLE c1} + 0 1 2 {SEARCH TABLE c3 USING INTEGER PRIMARY KEY (rowid=?)} +} + +do_execsql_test 4.2.0 { + DROP TABLE c1; + DROP TABLE c2; + DROP TABLE c3; + CREATE TABLE c1(k UNIQUE, v1); + CREATE TABLE c2(k UNIQUE, v2); + CREATE TABLE c3(k UNIQUE, v3); + + INSERT INTO c1 VALUES(1, 2); + INSERT INTO c2 VALUES(2, 3); + INSERT INTO c3 VALUES(3, 'v3'); + + INSERT INTO c1 VALUES(111, 1112); + INSERT INTO c2 VALUES(112, 1113); + INSERT INTO c3 VALUES(113, 'v1113'); +} +do_execsql_test 4.2.1 { + SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v2); +} {2 v3 1112 {}} +do_execsql_test 4.2.2 { + SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v1+1); +} {2 v3 1112 {}} + +do_execsql_test 4.2.3 { + SELECT DISTINCT v1, v3 FROM c1 LEFT JOIN c2 LEFT JOIN c3 ON (c3.k=v1+1); +} {2 v3 1112 {}} + +do_execsql_test 4.2.4 { + SELECT v1, v3 FROM c1 LEFT JOIN c2 LEFT JOIN c3 ON (c3.k=v1+1); +} {2 v3 2 v3 1112 {} 1112 {}} + +do_eqp_test 4.2.5 { + SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v2); +} { + 0 0 0 {SCAN TABLE c1} + 0 1 1 {SEARCH TABLE c2 USING INDEX sqlite_autoindex_c2_1 (k=?)} + 0 2 2 {SEARCH TABLE c3 USING INDEX sqlite_autoindex_c3_1 (k=?)} +} +do_eqp_test 4.2.6 { + SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v1+1); +} { + 0 0 0 {SCAN TABLE c1} + 0 1 2 {SEARCH TABLE c3 USING INDEX sqlite_autoindex_c3_1 (k=?)} +} + +# 2017-11-23 (Thanksgiving day) +# OSSFuzz found an assertion fault in the new LEFT JOIN eliminator code. +# +do_execsql_test 4.3.0 { + DROP TABLE IF EXISTS t1; + DROP TABLE IF EXISTS t2; + CREATE TABLE t1(x PRIMARY KEY) WITHOUT ROWID; + CREATE TABLE t2(x); + SELECT a.x + FROM t1 AS a + LEFT JOIN t1 AS b ON (a.x=b.x) + LEFT JOIN t2 AS c ON (a.x=c.x); +} {} +do_execsql_test 4.3.1 { + WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<10) + INSERT INTO t1(x) SELECT x FROM c; + INSERT INTO t2(x) SELECT x+9 FROM t1; + SELECT a.x, c.x + FROM t1 AS a + LEFT JOIN t1 AS b ON (a.x=b.x) + LEFT JOIN t2 AS c ON (a.x=c.x); +} {1 {} 2 {} 3 {} 4 {} 5 {} 6 {} 7 {} 8 {} 9 {} 10 10} + +do_execsql_test 5.0 { + CREATE TABLE s1 (a INTEGER PRIMARY KEY); + CREATE TABLE s2 (a INTEGER PRIMARY KEY); + CREATE TABLE s3 (a INTEGER); + CREATE UNIQUE INDEX ndx on s3(a); +} +do_eqp_test 5.1 { + SELECT s1.a FROM s1 left join s2 using (a); +} { + 0 0 0 {SCAN TABLE s1} +} +do_eqp_test 5.2 { + SELECT s1.a FROM s1 left join s3 using (a); +} { + 0 0 0 {SCAN TABLE s1} +} + +do_execsql_test 6.0 { + CREATE TABLE u1(a INTEGER PRIMARY KEY, b, c); + CREATE TABLE u2(a INTEGER PRIMARY KEY, b, c); + CREATE INDEX u1ab ON u1(b, c); +} +do_eqp_test 6.1 { + SELECT u2.* FROM u2 LEFT JOIN u1 ON( u1.a=u2.a AND u1.b=u2.b AND u1.c=u2.c ); +} { + 0 0 0 {SCAN TABLE u2} +} + finish_test diff --git a/test/journal1.test b/test/journal1.test index c89dd2b4c9..bcbafe30f6 100644 --- a/test/journal1.test +++ b/test/journal1.test @@ -22,7 +22,12 @@ source $testdir/tester.tcl # These tests will not work on windows because windows uses # manditory file locking which breaks the copy_file command. # -if {$tcl_platform(platform)=="windows"} { +# Or with atomic_batch_write systems, as journal files are +# not created. +# +if {$tcl_platform(platform)=="windows" + || [atomic_batch_write test.db] +} { finish_test return } diff --git a/test/journal3.test b/test/journal3.test index 939cc27c70..b907352329 100644 --- a/test/journal3.test +++ b/test/journal3.test @@ -20,7 +20,9 @@ source $testdir/malloc_common.tcl # If a connection is required to create a journal file, it creates it with # the same file-system permissions as the database file itself. Test this. # -if {$::tcl_platform(platform) == "unix"} { +if {$::tcl_platform(platform) == "unix" + && [atomic_batch_write test.db]==0 +} { # Changed on 2012-02-13: umask is deliberately ignored for -wal, -journal, # and -shm files. diff --git a/test/jrnlmode.test b/test/jrnlmode.test index 2ba56f2b00..3112f6184e 100644 --- a/test/jrnlmode.test +++ b/test/jrnlmode.test @@ -302,6 +302,7 @@ ifcapable autovacuum&&pragma { # The following test caes, jrnlmode-5.*, test the journal_size_limit # pragma. ifcapable pragma { +if {[atomic_batch_write test.db]==0} { db close forcedelete test.db test2.db test3.db sqlite3 db test.db @@ -454,8 +455,10 @@ ifcapable pragma { list [file exists test.db-journal] [file size test.db-journal] } {1 0} } +} ifcapable pragma { +if {[atomic_batch_write test.db]==0} { # These tests are not run as part of the "journaltest" permutation, # as the test_journal.c layer is incompatible with in-memory journaling. if {[permutation] ne "journaltest"} { @@ -507,6 +510,7 @@ ifcapable pragma { } {0} } } +} ifcapable pragma { catch { db close } diff --git a/test/jrnlmode2.test b/test/jrnlmode2.test index 6ea87d704b..6cc54dc5df 100644 --- a/test/jrnlmode2.test +++ b/test/jrnlmode2.test @@ -18,6 +18,11 @@ ifcapable {!pager_pragmas} { return } +if {[atomic_batch_write test.db]} { + finish_test + return +} + #------------------------------------------------------------------------- # The tests in this file check that the following two bugs (both now fixed) # do not reappear. diff --git a/test/json101.test b/test/json101.test index 4bfcc2d80d..ac4e11ebc2 100644 --- a/test/json101.test +++ b/test/json101.test @@ -759,7 +759,30 @@ do_execsql_test json-12.120 { FROM t12; } {0} - +# 2018-01-26 +# ticket https://www.sqlite.org/src/tktview/80177f0c226ff54f6ddd41 +# Make sure the query planner knows about the arguments to table-valued functions. +# +do_execsql_test json-13.100 { + DROP TABLE IF EXISTS t1; + DROP TABLE IF EXISTS t2; + CREATE TABLE t1(id, json); + INSERT INTO t1(id,json) VALUES(1,'{"items":[3,5]}'); + CREATE TABLE t2(id, json); + INSERT INTO t2(id,json) VALUES(2,'{"value":2}'); + INSERT INTO t2(id,json) VALUES(3,'{"value":3}'); + INSERT INTO t2(id,json) VALUES(4,'{"value":4}'); + INSERT INTO t2(id,json) VALUES(5,'{"value":5}'); + INSERT INTO t2(id,json) VALUES(6,'{"value":6}'); + SELECT * FROM t1 CROSS JOIN t2 + WHERE EXISTS(SELECT 1 FROM json_each(t1.json,'$.items') AS Z + WHERE Z.value==t2.id); +} {1 {{"items":[3,5]}} 3 {{"value":3}} 1 {{"items":[3,5]}} 5 {{"value":5}}} +do_execsql_test json-13.110 { + SELECT * FROM t2 CROSS JOIN t1 + WHERE EXISTS(SELECT 1 FROM json_each(t1.json,'$.items') AS Z + WHERE Z.value==t2.id); +} {3 {{"value":3}} 1 {{"items":[3,5]}} 5 {{"value":5}} 1 {{"items":[3,5]}}} finish_test diff --git a/test/limit2.test b/test/limit2.test index f6eaefb8aa..83c67506f5 100644 --- a/test/limit2.test +++ b/test/limit2.test @@ -150,4 +150,21 @@ do_execsql_test 502 { SELECT j FROM t502 WHERE i IN (1,2,3,4,5) ORDER BY j LIMIT 3; } {1 3 4} +# Ticket https://www.sqlite.org/src/info/123c9ba32130a6c9 2017-12-13 +# Incorrect result when an idnex is used for an ordered join. +# +# This test case is in the limit2.test module because the problem was first +# exposed by check-in https://www.sqlite.org/src/info/559733b09e which +# implemented the ORDER BY LIMIT optimization that limit2.test strives to +# test. +# +do_execsql_test 600 { + 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(1,3); + CREATE INDEX t1ab ON t1(a,b); + SELECT y FROM t1, t2 WHERE a=x AND b<=y ORDER BY b DESC; +} {3} + finish_test diff --git a/test/lock4.test b/test/lock4.test index b0b1c74fbe..58dd206997 100644 --- a/test/lock4.test +++ b/test/lock4.test @@ -17,6 +17,14 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +if {[atomic_batch_write test.db]} { + # This test uses two processes, one of which blocks until the other + # creates a *-journal file. Which doesn't work if atomic writes are + # available. + finish_test + return +} + do_not_use_codec # Initialize the test.db database so that it is non-empty diff --git a/test/main.test b/test/main.test index 9346cf6ced..13a385b7c4 100644 --- a/test/main.test +++ b/test/main.test @@ -434,7 +434,7 @@ do_test main-3.2.28 { } {0 246} do_test main-3.2.29 { catchsql {select 123/} -} {1 {near "/": syntax error}} +} {1 {incomplete input}} do_test main-3.2.30 { catchsql {select 123--5} } {0 123} @@ -467,7 +467,7 @@ do_test main-3.4 { do_test main-3.5 { set v [catch {execsql {create}} msg] lappend v $msg -} {1 {near "create": syntax error}} +} {1 {incomplete input}} do_test main-3.6 { catchsql {SELECT 'abc' + #9} } {1 {near "#9": syntax error}} diff --git a/test/malloc.test b/test/malloc.test index dbf4699b27..5e82e8028b 100644 --- a/test/malloc.test +++ b/test/malloc.test @@ -329,7 +329,7 @@ ifcapable crashtest&&attach { } } -if {$tcl_platform(platform)!="windows"} { +if {$tcl_platform(platform)!="windows" && [atomic_batch_write test.db]==0} { do_malloc_test 14 -tclprep { catch {db close} sqlite3 db2 test2.db diff --git a/test/malloc3.test b/test/malloc3.test index f4a6c3bbe9..b497ab66e9 100644 --- a/test/malloc3.test +++ b/test/malloc3.test @@ -27,6 +27,17 @@ if {!$MEMDEBUG} { return } +# Do not run these tests if F2FS batch writes are supported. In this case, +# it is possible for a single DML statement in an implicit transaction +# to fail with SQLITE_NOMEM, but for the transaction to still end up +# committed to disk. Which confuses the tests in this module. +# +if {[atomic_batch_write test.db]} { + puts "Skipping malloc3 tests: atomic-batch support" + finish_test + return +} + # Do not run these tests with an in-memory journal. # diff --git a/test/misc1.test b/test/misc1.test index 2acfa5c2dd..05b1b1980f 100644 --- a/test/misc1.test +++ b/test/misc1.test @@ -722,4 +722,24 @@ do_execsql_test misc1-26.0 { SELECT randomblob(min(max(coalesce(EXISTS (SELECT 1 FROM ( SELECT (SELECT 2147483647) NOT IN (SELECT 2147483649 UNION ALL SELECT DISTINCT -1) IN (SELECT 2147483649), 'fault', (SELECT ALL -1 INTERSECT SELECT 'experiments') IN (SELECT ALL 56.1 ORDER BY 'experiments' DESC) FROM (SELECT DISTINCT 2147483648, 'hardware' UNION ALL SELECT -2147483648, 'experiments' ORDER BY 2147483648 LIMIT 1 OFFSET 123456789.1234567899) GROUP BY (SELECT ALL 0 INTERSECT SELECT 'in') IN (SELECT DISTINCT 'experiments' ORDER BY zeroblob(1000) LIMIT 56.1 OFFSET -456) HAVING EXISTS (SELECT 'fault' EXCEPT SELECT DISTINCT 56.1) UNION SELECT 'The', 'The', 2147483649 UNION ALL SELECT DISTINCT 'hardware', 'first', 'experiments' ORDER BY 'hardware' LIMIT 123456789.1234567899 OFFSET -2147483647)) NOT IN (SELECT (SELECT DISTINCT (SELECT 'The') FROM abc ORDER BY EXISTS (SELECT -1 INTERSECT SELECT ALL NULL) ASC) IN (SELECT DISTINCT EXISTS (SELECT ALL 123456789.1234567899 ORDER BY 1 ASC, NULL DESC) FROM sqlite_master INTERSECT SELECT 456)), (SELECT ALL 'injection' UNION ALL SELECT ALL (SELECT DISTINCT 'first' UNION SELECT DISTINCT 'The') FROM (SELECT 456, 'in', 2147483649))),1), 500)), 'first', EXISTS (SELECT DISTINCT 456 FROM abc ORDER BY 'experiments' DESC) FROM abc; } {} +# 2017-12-29 +# +# The following behaviors (duplicate column names on an INSERT or UPDATE) +# are undocumented. These tests are added to ensure that historical behavior +# does not change accidentally. +# +# For duplication columns on an INSERT, the first value is used. +# For duplication columns on an UPDATE, the last value is used. +# +do_execsql_test misc1-27.0 { + CREATE TABLE dup1(a,b,c); + INSERT INTO dup1(a,b,c,a,b,c) VALUES(1,2,3,4,5,6); + SELECT a,b,c FROM dup1; +} {1 2 3} +do_execsql_test misc1-27.1 { + UPDATE dup1 SET a=7, b=8, c=9, a=10, b=11, c=12; + SELECT a,b,c FROM dup1; +} {10 11 12} + + finish_test diff --git a/test/misc7.test b/test/misc7.test index 8fd5fe7546..8df95575c1 100644 --- a/test/misc7.test +++ b/test/misc7.test @@ -14,6 +14,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +set testprefix misc7 if {[clang_sanitize_address]==0} { do_test misc7-1-misuse { @@ -42,15 +43,17 @@ do_test misc7-4 { # Try to open a file with a directory where its journal file should be. # -do_test misc7-5 { - delete_file mydir - file mkdir mydir-journal - sqlite3 db2 ./mydir - catchsql { - CREATE TABLE abc(a, b, c); - } db2 -} {1 {unable to open database file}} -db2 close +if {[atomic_batch_write test.db]==0} { + do_test misc7-5 { + delete_file mydir + file mkdir mydir-journal + sqlite3 db2 ./mydir + catchsql { + CREATE TABLE abc(a, b, c); + } db2 + } {1 {unable to open database file}} + db2 close +} #-------------------------------------------------------------------- # The following tests, misc7-6.* test the libraries behaviour when @@ -518,8 +521,43 @@ do_test misc7-22.3 { do_test misc7-22.4 { sqlite3_extended_errcode db } SQLITE_READONLY_ROLLBACK - -db close +catch { db close } forcedelete test.db +if {$::tcl_platform(platform)=="unix" + && [atomic_batch_write test.db]==0 +} { + reset_db + do_execsql_test 23.0 { + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES(1, 2); + } + + do_test 23.1 { + db close + forcedelete tst + file mkdir tst + forcecopy test.db tst/test.db + file attributes tst -permissions r-xr-xr-x + } {} + + sqlite3 db tst/test.db + do_execsql_test 23.2 { + SELECT * FROM t1; + } {1 2} + + do_catchsql_test 23.3 { + INSERT INTO t1 VALUES(3, 4); + } {1 {attempt to write a readonly database}} + + do_test 23.4 { + sqlite3_extended_errcode db + } {SQLITE_READONLY_DIRECTORY} + + do_test 23.5 { + db close + forcedelete tst + } {} +} + finish_test diff --git a/test/mjournal.test b/test/mjournal.test index aab2c08b51..539ebc6946 100644 --- a/test/mjournal.test +++ b/test/mjournal.test @@ -15,6 +15,11 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix mjournal +if {[permutation]=="inmemory_journal"} { + finish_test + return +} + # Test that nothing bad happens if a journal file contains a pointer to # a master journal file that does not have a "-" in the name. At one point # this was causing a segfault on unix. @@ -79,5 +84,80 @@ do_execsql_test 1.6 { SELECT * FROM t1; } - +#------------------------------------------------------------------------- +# Check that master journals are not created if the transaction involves +# multiple temp files. +# +db close +testvfs tvfs +tvfs filter xOpen +tvfs script open_cb +set ::open "" +proc open_cb {method file arglist} { + lappend ::open $file +} + +proc contains_mj {} { + foreach f $::open { + set t [file tail $f] + if {[string match *mj* $t]} { return 1 } + } + return 0 +} + +# Like [do_execsql_test], except that a boolean indicating whether or +# not a master journal file was opened ([file tail] contains "mj") or +# not. Example: +# +# do_hasmj_test 1.0 { SELECT 'a', 'b' } {0 a b} +# +proc do_hasmj_test {tn sql expected} { + set ::open [list] + uplevel [list do_test $tn [subst -nocommands { + set res [execsql "$sql"] + concat [contains_mj] [set res] + }] [list {*}$expected]] +} + +forcedelete test.db +forcedelete test.db2 +forcedelete test.db3 +sqlite3 db test.db -vfs tvfs + +do_execsql_test 2.0 { + ATTACH 'test.db2' AS dbfile; + ATTACH '' AS dbtemp; + ATTACH ':memory:' AS dbmem; + + CREATE TABLE t1(x); + CREATE TABLE dbfile.t2(x); + CREATE TABLE dbtemp.t3(x); + CREATE TABLE dbmem.t4(x); +} + +# Two real files. +do_hasmj_test 2.1 { + BEGIN; + INSERT INTO t1 VALUES(1); + INSERT INTO t2 VALUES(1); + COMMIT; +} {1} + +# One real, one temp file. +do_hasmj_test 2.2 { + BEGIN; + INSERT INTO t1 VALUES(1); + INSERT INTO t3 VALUES(1); + COMMIT; +} {0} + +# One file, one :memory: db. +do_hasmj_test 2.3 { + BEGIN; + INSERT INTO t1 VALUES(1); + INSERT INTO t4 VALUES(1); + COMMIT; +} {0} + finish_test + diff --git a/test/nockpt.test b/test/nockpt.test index bd3953f1ee..6da3313ad8 100644 --- a/test/nockpt.test +++ b/test/nockpt.test @@ -61,6 +61,86 @@ 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} +if {$::tcl_platform(platform)!="windows"} { +#------------------------------------------------------------------------- +# Test an unusual scenario: +# +# 1. A wal mode db is opened and written. Then sqlite3_close_v2() used +# to close the db handle while there is still an unfinalized +# statement (so the db handle stays open). +# +# 2. The db, wal and *-shm files are deleted from the file system. +# +# 3. Another connection creates a new wal mode db at the same file-system +# location as the previous one. +# +# 4. The statement left unfinalized in (1) is finalized. +# +# The test is to ensure that the connection left open in step (1) does +# not try to delete the wal file from the file-system as part of step +# 4. +# +reset_db +db close + +# Open a connection on a wal database. Write to it a bit. Then prepare +# a statement and call sqlite3_close_v2() (so that the statement handle +# holds the db connection open). +# +set ::db1 [sqlite3_open_v2 test.db SQLITE_OPEN_READWRITE ""] +do_test 2.0 { + lindex [ + sqlite3_exec $::db1 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(x PRIMARY KEY, y UNIQUE, z); + INSERT INTO t1 VALUES(1, 2, 3); + PRAGMA wal_checkpoint; + }] 0 +} {0} +set ::stmt [sqlite3_prepare $::db1 "SELECT * FROM t1" -1 dummy] +sqlite3_close_v2 $::db1 + +# Delete the database, wal and shm files. +# +forcedelete test.db test.db-wal test.db-shm + +# Open and populate a new database file at the same file-system location +# as the one just deleted. Contrive a partial checkpoint on it. +# +sqlite3 db test.db +sqlite3 db2 test.db +do_execsql_test 2.1 { + PRAGMA journal_mode = wal; + CREATE TABLE y1(a PRIMARY KEY, b UNIQUE, c); + INSERT INTO y1 VALUES('a', 'b', 'c'); + INSERT INTO y1 VALUES('d', 'e', 'f'); +} {wal} +do_execsql_test -db db2 2.2 { + BEGIN; + SELECT * FROM y1; +} {a b c d e f} +do_execsql_test 2.3 { + UPDATE y1 SET c='g' WHERE a='d'; + PRAGMA wal_checkpoint; +} {0 11 10} +do_execsql_test -db db2 2.4 { + COMMIT +} + +# Finalize the statement handle, causing the first connection to be +# closed. Test that this has not corrupted the database file by +# deleting the new wal file from the file-system. If it has, this +# test should fail with an IO or corruption error. +# +do_test 2.5 { + sqlite3_finalize $::stmt + sqlite3 db3 test.db + execsql { + PRAGMA integrity_check; + SELECT * FROM y1; + } db3 +} {ok a b c d e g} +} finish_test diff --git a/test/normalize.test b/test/normalize.test new file mode 100644 index 0000000000..8932650c83 --- /dev/null +++ b/test/normalize.test @@ -0,0 +1,72 @@ +# 2018-01-08 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# Tests for the sqlite3_normalize() extension function. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix normalize + +foreach {tnum sql norm} { + 100 + {SELECT * FROM t1 WHERE a IN (1) AND b=51.42} + {select*from t1 where a in(?,?,?)and b=?;} + + 110 + {SELECT a, b+15, c FROM t1 WHERE d NOT IN (SELECT x FROM t2);} + {select a,b+?,c from t1 where d not in(select x from t2);} + + 120 + { SELECT NULL, b FROM t1 -- comment text + WHERE d IN (WITH t(a) AS (VALUES(5)) /* CTE */ + SELECT a FROM t) + OR e='hello'; + } + {select?,b from t1 where d in(with t(a)as(values(?))select a from t)or e=?;} + + 121 + {/*Initial comment*/ + -- another comment line + SELECT NULL /* comment */ , b FROM t1 -- comment text + WHERE d IN (WITH t(a) AS (VALUES(5)) /* CTE */ + SELECT a FROM t) + OR e='hello'; + } + {select?,b from t1 where d in(with t(a)as(values(?))select a from t)or e=?;} + + 130 + {/* Query containing parameters */ + SELECT x,$::abc(15),y,@abc,z,?99,w FROM t1 /* Trailing comment */} + {select x,?,y,?,z,?,w from t1;} + + 140 + {/* Long list on the RHS of IN */ + SELECT 15 IN (1,2,3,(SELECT * FROM t1),'xyz',x'abcd',22*(x+5),null);} + {select?in(?,?,?);} + + 150 + {SELECT x'abc'; -- illegal token} + {} + + 160 + {SELECT a,NULL,b FROM t1 WHERE c IS NOT NULL or D is null or e=5} + {select a,?,b from t1 where c is not null or d is null or e=?;} + + 170 + {/* IN list exactly 5 bytes long */ + SELECT * FROM t1 WHERE x IN (1,2,3);} + {select*from t1 where x in(?,?,?);} +} { + do_test $tnum [list sqlite3_normalize $sql] $norm +} + +finish_test diff --git a/test/notnull.test b/test/notnull.test index 23fd33d4ba..32d95eaf24 100644 --- a/test/notnull.test +++ b/test/notnull.test @@ -561,4 +561,49 @@ do_test notnull-5.5 { execsql { SELECT * FROM t1 } } {1 2} +#------------------------------------------------------------------------- +# Check that UNIQUE NOT NULL indexes are always recognized as such. +# +proc uses_op_next {sql} { + db eval "EXPLAIN $sql" a { + if {$a(opcode)=="Next"} { return 1 } + } + return 0 +} + +proc do_uses_op_next_test {tn sql res} { + uplevel [list do_test $tn [list uses_op_next $sql] $res] +} + +reset_db +do_execsql_test notnull-6.0 { + CREATE TABLE t1(a UNIQUE); + CREATE TABLE t2(a NOT NULL UNIQUE); + CREATE TABLE t3(a UNIQUE NOT NULL); + CREATE TABLE t4(a NOT NULL); + CREATE UNIQUE INDEX t4a ON t4(a); + + CREATE TABLE t5(a PRIMARY KEY); + CREATE TABLE t6(a PRIMARY KEY NOT NULL); + CREATE TABLE t7(a NOT NULL PRIMARY KEY); + CREATE TABLE t8(a PRIMARY KEY) WITHOUT ROWID; + + CREATE TABLE t9(a PRIMARY KEY UNIQUE NOT NULL); + CREATE TABLE t10(a UNIQUE PRIMARY KEY NOT NULL); +} + +do_uses_op_next_test notnull-6.1 "SELECT * FROM t1 WHERE a IS ?" 1 +do_uses_op_next_test notnull-6.2 "SELECT * FROM t2 WHERE a IS ?" 0 +do_uses_op_next_test notnull-6.3 "SELECT * FROM t3 WHERE a IS ?" 0 +do_uses_op_next_test notnull-6.4 "SELECT * FROM t4 WHERE a IS ?" 0 + +do_uses_op_next_test notnull-6.5 "SELECT * FROM t5 WHERE a IS ?" 1 +do_uses_op_next_test notnull-6.6 "SELECT * FROM t6 WHERE a IS ?" 0 +do_uses_op_next_test notnull-6.7 "SELECT * FROM t7 WHERE a IS ?" 0 +do_uses_op_next_test notnull-6.8 "SELECT * FROM t8 WHERE a IS ?" 0 + +do_uses_op_next_test notnull-6.9 "SELECT * FROM t8 WHERE a IS ?" 0 +do_uses_op_next_test notnull-6.10 "SELECT * FROM t8 WHERE a IS ?" 0 + finish_test + diff --git a/test/ossfuzz.c b/test/ossfuzz.c index 7b28cf6a7e..fa6e9142fe 100644 --- a/test/ossfuzz.c +++ b/test/ossfuzz.c @@ -160,6 +160,9 @@ int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { /* Run the SQL. The sqlite_exec() interface expects a zero-terminated ** string, so make a copy. */ zSql = sqlite3_mprintf("%.*s", (int)size, data); +#ifndef SQLITE_OMIT_COMPLETE + sqlite3_complete(zSql); +#endif sqlite3_exec(cx.db, zSql, exec_handler, (void*)&execCnt, &zErrMsg); /* Show any errors */ diff --git a/test/pager1.test b/test/pager1.test index 8451e0b3d2..42edc3d16b 100644 --- a/test/pager1.test +++ b/test/pager1.test @@ -17,6 +17,11 @@ source $testdir/malloc_common.tcl source $testdir/wal_common.tcl set testprefix pager1 +if {[atomic_batch_write test.db]} { + finish_test + return +} + # Do not use a codec for tests in this file, as the database file is # manipulated directly using tcl scripts (using the [hexio_write] command). # diff --git a/test/pager3.test b/test/pager3.test index 23435a79b7..e815f2788b 100644 --- a/test/pager3.test +++ b/test/pager3.test @@ -16,6 +16,10 @@ source $testdir/lock_common.tcl source $testdir/malloc_common.tcl source $testdir/wal_common.tcl +if {[atomic_batch_write test.db]} { + finish_test + return +} foreach {tn sql res j} { 1 "PRAGMA journal_mode = DELETE" delete 0 diff --git a/test/pagerfault.test b/test/pagerfault.test index 392c1a2b98..3006dad7cc 100644 --- a/test/pagerfault.test +++ b/test/pagerfault.test @@ -1203,12 +1203,14 @@ do_faultsim_test pagerfault-26 -prep { set contents [db eval {SELECT * FROM t1}] if {$contents != "1 2"} { error "Bad database contents ($contents)" } - set sz [file size test.db] - if {$testrc!=0 && $sz!=1024*3 && $sz!=4096*3} { - error "Expected file size to be 3072 or 12288 bytes - actual size $sz bytes" - } - if {$testrc==0 && $sz!=4096*3} { - error "Expected file size to be 12288 bytes - actual size $sz bytes" + if {[atomic_batch_write test.db]==0} { + set sz [file size test.db] + if {$testrc!=0 && $sz!=1024*3 && $sz!=4096*3} { + error "Expected file size 3072 or 12288 bytes - actual size $sz bytes" + } + if {$testrc==0 && $sz!=4096*3} { + error "Expected file size to be 12288 bytes - actual size $sz bytes" + } } } diff --git a/test/permutations.test b/test/permutations.test index 76fb72ace5..c1d28d4e09 100644 --- a/test/permutations.test +++ b/test/permutations.test @@ -86,9 +86,10 @@ proc test_set {args} { # set alltests [list] foreach f [glob $testdir/*.test] { lappend alltests [file tail $f] } -foreach f [glob -nocomplain \ - $testdir/../ext/rtree/*.test \ +foreach f [glob -nocomplain \ + $testdir/../ext/rtree/*.test \ $testdir/../ext/fts5/test/*.test \ + $testdir/../ext/expert/*.test \ $testdir/../ext/lsm1/test/*.test \ ] { lappend alltests $f diff --git a/test/pragma5.test b/test/pragma5.test index 625ab92012..d2c58000cf 100644 --- a/test/pragma5.test +++ b/test/pragma5.test @@ -34,7 +34,7 @@ do_execsql_test 1.0 { 1 builtin {} 0 {} 0 } do_execsql_test 1.1 { - SELECT * FROM pragma_function_list WHERE name='upper' + SELECT * FROM pragma_function_list WHERE name='upper' AND builtin } {upper 1} do_execsql_test 1.2 { SELECT * FROM pragma_function_list WHERE name LIKE 'exter%'; diff --git a/test/printf2.test b/test/printf2.test index d30966d167..998038f88e 100644 --- a/test/printf2.test +++ b/test/printf2.test @@ -148,6 +148,63 @@ do_execsql_test printf2-4.10 { SELECT printf('|%,d|%,d|',1234567890,-1234567890); } {|1,234,567,890|-1,234,567,890|} +# 2018-02-19. Unicode characters with %c +do_execsql_test printf2-5.100 { + SELECT printf('(%8c)',char(11106)); +} {{( ⭢)}} +do_execsql_test printf2-5.101 { + SELECT printf('(%-8c)',char(11106)); +} {{(⭢ )}} +do_execsql_test printf2-5.102 { + SELECT printf('(%5.3c)',char(1492)); +} {{( ההה)}} +do_execsql_test printf2-5.103 { + SELECT printf('(%-5.3c)',char(1492)); +} {{(ההה )}} +do_execsql_test printf2-5.104 { + SELECT printf('(%3.3c)',char(1492)); +} {{(ההה)}} +do_execsql_test printf2-5.105 { + SELECT printf('(%-3.3c)',char(1492)); +} {{(ההה)}} +do_execsql_test printf2-5.104 { + SELECT printf('(%2c)',char(1513)); +} {{( ש)}} +do_execsql_test printf2-5.106 { + SELECT printf('(%-2c)',char(1513)); +} {{(ש )}} + +# 2018-02-19. Unicode characters with the "!" flag in %s and friends. +do_execsql_test printf2-6.100 { + SELECT printf('(%!.3s)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד'); +} {(הנה)} +do_execsql_test printf2-6.101 { + SELECT printf('(%.6s)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד'); +} {(הנה)} +do_execsql_test printf2-6.102 { + SELECT printf('(%!5.3s)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד'); +} {{( הנה)}} +do_execsql_test printf2-6.103 { + SELECT printf('(%8.6s)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד'); +} {{( הנה)}} +do_execsql_test printf2-6.104 { + SELECT printf('(%!-5.3s)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד'); +} {{(הנה )}} +do_execsql_test printf2-6.105 { + SELECT printf('(%-8.6s)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד'); +} {{(הנה )}} +do_execsql_test printf2-6.106 { + SELECT printf('(%!.3Q)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד'); +} {('הנה')} +do_execsql_test printf2-6.107 { + SELECT printf('(%.6Q)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד'); +} {('הנה')} +do_execsql_test printf2-6.108 { + SELECT printf('(%!7.3Q)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד'); +} {{( 'הנה')}} +do_execsql_test printf2-6.109 { + SELECT printf('(%10.6Q)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד'); +} {{( 'הנה')}} finish_test diff --git a/test/releasetest.tcl b/test/releasetest.tcl index 5055e81129..599ebd791d 100755 --- a/test/releasetest.tcl +++ b/test/releasetest.tcl @@ -734,6 +734,9 @@ proc makeCommand { targets makeOpts cflags opts } { set nmakeDir [file nativename $::SRCDIR] set nmakeFile [file nativename [file join $nmakeDir Makefile.msc]] lappend result nmake /f $nmakeFile TOP=$nmakeDir + set tclDir [file nativename [file normalize \ + [file dirname [file dirname [info nameofexecutable]]]]] + lappend result "TCLDIR=$tclDir" if {[regexp {USE_STDCALL=1} $cflags]} { lappend result USE_STDCALL=1 } diff --git a/test/rollback.test b/test/rollback.test index 60a6190317..423bf20fce 100644 --- a/test/rollback.test +++ b/test/rollback.test @@ -83,6 +83,7 @@ if {$tcl_platform(platform) == "unix" && [permutation] ne "onefile" && [permutation] ne "inmemory_journal" && [permutation] ne "atomic-batch-write" + && [atomic_batch_write test.db]==0 } { do_test rollback-2.1 { execsql { diff --git a/test/rowvalue.test b/test/rowvalue.test index 5f2701c733..00d9395331 100644 --- a/test/rowvalue.test +++ b/test/rowvalue.test @@ -394,4 +394,156 @@ do_execsql_test 16.5 { 3 i ii iii iv } +do_execsql_test 17.0 { + CREATE TABLE b1(a, b); + CREATE TABLE b2(x); +} + +do_execsql_test 17.1 { + SELECT * FROM b2 CROSS JOIN b1 + WHERE b2.x=b1.a AND (b1.a, 2) + IN (VALUES(1, 2)); +} {} + +do_execsql_test 18.0 { + CREATE TABLE b3 ( a, b, PRIMARY KEY (a, b) ); + CREATE TABLE b4 ( a ); + CREATE TABLE b5 ( a, b ); + INSERT INTO b3 VALUES (1, 1), (1, 2); + INSERT INTO b4 VALUES (1); + INSERT INTO b5 VALUES (1, 1), (1, 2); +} + +do_execsql_test 18.1 { + SELECT * FROM b3 WHERE (SELECT b3.a, b3.b) IN ( SELECT a, b FROM b5 ) +} {1 1 1 2} +do_execsql_test 18.2 { + SELECT * FROM b3 WHERE (VALUES(b3.a, b3.b)) IN ( SELECT a, b FROM b5 ); +} {1 1 1 2} +do_execsql_test 18.3 { + SELECT * FROM b3 WHERE (b3.a, b3.b) IN ( SELECT a, b FROM b5 ); +} {1 1 1 2} +do_execsql_test 18.4 { + SELECT * FROM b3 JOIN b4 ON b4.a = b3.a + WHERE (SELECT b3.a, b3.b) IN ( SELECT a, b FROM b5 ); +} {1 1 1 1 2 1} +do_execsql_test 18.5 { + SELECT * FROM b3 JOIN b4 ON b4.a = b3.a + WHERE (VALUES(b3.a, b3.b)) IN ( SELECT a, b FROM b5 ); +} {1 1 1 1 2 1} +do_execsql_test 18.6 { + SELECT * FROM b3 JOIN b4 ON b4.a = b3.a + WHERE (b3.a, b3.b) IN ( SELECT a, b FROM b5 ); +} {1 1 1 1 2 1} + + +# 2018-02-13 Ticket https://www.sqlite.org/src/tktview/f484b65f3d6230593c3 +# Incorrect result from a row-value comparison in the WHERE clause. +# +do_execsql_test 19.1 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a INTEGER PRIMARY KEY,b); + INSERT INTO t1(a,b) VALUES(1,11),(2,22),(3,33),(4,44); + SELECT * FROM t1 WHERE (a,b)>(0,0) ORDER BY a; +} {1 11 2 22 3 33 4 44} +do_execsql_test 19.2 { + SELECT * FROM t1 WHERE (a,b)>=(0,0) ORDER BY a; +} {1 11 2 22 3 33 4 44} +do_execsql_test 19.3 { + SELECT * FROM t1 WHERE (a,b)<(5,0) ORDER BY a DESC; +} {4 44 3 33 2 22 1 11} +do_execsql_test 19.4 { + SELECT * FROM t1 WHERE (a,b)<=(5,0) ORDER BY a DESC; +} {4 44 3 33 2 22 1 11} +do_execsql_test 19.5 { + SELECT * FROM t1 WHERE (a,b)>(3,0) ORDER BY a; +} {3 33 4 44} +do_execsql_test 19.6 { + SELECT * FROM t1 WHERE (a,b)>=(3,0) ORDER BY a; +} {3 33 4 44} +do_execsql_test 19.7 { + SELECT * FROM t1 WHERE (a,b)<(3,0) ORDER BY a DESC; +} {2 22 1 11} +do_execsql_test 19.8 { + SELECT * FROM t1 WHERE (a,b)<=(3,0) ORDER BY a DESC; +} {2 22 1 11} +do_execsql_test 19.9 { + SELECT * FROM t1 WHERE (a,b)>(3,32) ORDER BY a; +} {3 33 4 44} +do_execsql_test 19.10 { + SELECT * FROM t1 WHERE (a,b)>(3,33) ORDER BY a; +} {4 44} +do_execsql_test 19.11 { + SELECT * FROM t1 WHERE (a,b)>=(3,33) ORDER BY a; +} {3 33 4 44} +do_execsql_test 19.12 { + SELECT * FROM t1 WHERE (a,b)>=(3,34) ORDER BY a; +} {4 44} +do_execsql_test 19.13 { + SELECT * FROM t1 WHERE (a,b)<(3,34) ORDER BY a DESC; +} {3 33 2 22 1 11} +do_execsql_test 19.14 { + SELECT * FROM t1 WHERE (a,b)<(3,33) ORDER BY a DESC; +} {2 22 1 11} +do_execsql_test 19.15 { + SELECT * FROM t1 WHERE (a,b)<=(3,33) ORDER BY a DESC; +} {3 33 2 22 1 11} +do_execsql_test 19.16 { + SELECT * FROM t1 WHERE (a,b)<=(3,32) ORDER BY a DESC; +} {2 22 1 11} +do_execsql_test 19.21 { + SELECT * FROM t1 WHERE (0,0)<(a,b) ORDER BY a; +} {1 11 2 22 3 33 4 44} +do_execsql_test 19.22 { + SELECT * FROM t1 WHERE (0,0)<=(a,b) ORDER BY a; +} {1 11 2 22 3 33 4 44} +do_execsql_test 19.23 { + SELECT * FROM t1 WHERE (5,0)>(a,b) ORDER BY a DESC; +} {4 44 3 33 2 22 1 11} +do_execsql_test 19.24 { + SELECT * FROM t1 WHERE (5,0)>=(a,b) ORDER BY a DESC; +} {4 44 3 33 2 22 1 11} +do_execsql_test 19.25 { + SELECT * FROM t1 WHERE (3,0)<(a,b) ORDER BY a; +} {3 33 4 44} +do_execsql_test 19.26 { + SELECT * FROM t1 WHERE (3,0)<=(a,b) ORDER BY a; +} {3 33 4 44} +do_execsql_test 19.27 { + SELECT * FROM t1 WHERE (3,0)>(a,b) ORDER BY a DESC; +} {2 22 1 11} +do_execsql_test 19.28 { + SELECT * FROM t1 WHERE (3,0)>=(a,b) ORDER BY a DESC; +} {2 22 1 11} +do_execsql_test 19.29 { + SELECT * FROM t1 WHERE (3,32)<(a,b) ORDER BY a; +} {3 33 4 44} +do_execsql_test 19.30 { + SELECT * FROM t1 WHERE (3,33)<(a,b) ORDER BY a; +} {4 44} +do_execsql_test 19.31 { + SELECT * FROM t1 WHERE (3,33)<=(a,b) ORDER BY a; +} {3 33 4 44} +do_execsql_test 19.32 { + SELECT * FROM t1 WHERE (3,34)<=(a,b) ORDER BY a; +} {4 44} +do_execsql_test 19.33 { + SELECT * FROM t1 WHERE (3,34)>(a,b) ORDER BY a DESC; +} {3 33 2 22 1 11} +do_execsql_test 19.34 { + SELECT * FROM t1 WHERE (3,33)>(a,b) ORDER BY a DESC; +} {2 22 1 11} +do_execsql_test 19.35 { + SELECT * FROM t1 WHERE (3,33)>=(a,b) ORDER BY a DESC; +} {3 33 2 22 1 11} +do_execsql_test 19.36 { + SELECT * FROM t1 WHERE (3,32)>=(a,b) ORDER BY a DESC; +} {2 22 1 11} + +# 2018-02-18: Memory leak nexted row-value. Detected by OSSFuzz. +# +do_catchsql_test 20.1 { + SELECT 1 WHERE (2,(2,0)) IS (2,(2,0)); +} {0 1} + finish_test diff --git a/test/securedel.test b/test/securedel.test index a78f466031..8323a30497 100644 --- a/test/securedel.test +++ b/test/securedel.test @@ -17,8 +17,12 @@ source $testdir/tester.tcl unset -nocomplain DEFAULT_SECDEL set DEFAULT_SECDEL 0 -ifcapable secure_delete { - set DEFAULT_SECDEL 1 +ifcapable fast_secure_delete { + set DEFAULT_SECDEL 2 +} else { + ifcapable secure_delete { + set DEFAULT_SECDEL 1 + } } diff --git a/test/select1.test b/test/select1.test index 43b20f6d15..7023a6e65d 100644 --- a/test/select1.test +++ b/test/select1.test @@ -688,7 +688,7 @@ do_test select1-7.2 { do_test select1-7.3 { set v [catch {execsql {SELECT f1 FROM test1 as 'hi', test2 as}} msg] lappend v $msg -} {1 {near "as": syntax error}} +} {1 {incomplete input}} do_test select1-7.4 { set v [catch {execsql { SELECT f1 FROM test1 ORDER BY; diff --git a/test/selectG.test b/test/selectG.test index 86d89b121b..fab4c4ed4d 100644 --- a/test/selectG.test +++ b/test/selectG.test @@ -36,4 +36,24 @@ do_test 100 { } } {100000 5000050000 50000.5 1} +# 2018-01-14. A 100K-entry VALUES clause within a scalar expression does +# not cause processor stack overflow. +# +do_test 110 { + set sql "SELECT (VALUES" + for {set i 1} {$i<100000} {incr i} { + append sql "($i)," + } + append sql "($i));" + db eval $sql +} {1} + +# Only the left-most term of a multi-valued VALUES within a scalar +# expression is evaluated. +# +do_test 120 { + set n [llength [split [db eval "explain $sql"] \n]] + expr {$n<10} +} {1} + finish_test diff --git a/test/sharedA.test b/test/sharedA.test index 146fb26be0..55ed5749bb 100644 --- a/test/sharedA.test +++ b/test/sharedA.test @@ -19,6 +19,11 @@ if {[run_thread_tests]==0} { finish_test ; return } db close set ::testprefix sharedA +if {[atomic_batch_write test.db]} { + finish_test + return +} + set ::enable_shared_cache [sqlite3_enable_shared_cache 1] #------------------------------------------------------------------------- diff --git a/test/shell1.test b/test/shell1.test index ddd72c7180..81883a8d18 100644 --- a/test/shell1.test +++ b/test/shell1.test @@ -495,7 +495,7 @@ do_test shell1-3.15.2 { do_test shell1-3.15.3 { # too many arguments catchcmd "test.db" ".output FOO BAD" -} {1 {Usage: .output FILE}} +} {1 {Usage: .output [-e|-x|FILE]}} # .output stdout Send output to the screen do_test shell1-3.16.1 { @@ -504,7 +504,7 @@ do_test shell1-3.16.1 { do_test shell1-3.16.2 { # too many arguments catchcmd "test.db" ".output stdout BAD" -} {1 {Usage: .output FILE}} +} {1 {Usage: .output [-e|-x|FILE]}} # .prompt MAIN CONTINUE Replace the standard prompts do_test shell1-3.17.1 { @@ -581,8 +581,10 @@ do_test shell1-3.21.4 { } catchcmd "test.db" ".schema" } {0 {CREATE TABLE t1(x); -CREATE VIEW v2 AS SELECT x+1 AS y FROM t1; -CREATE VIEW v1 AS SELECT y+1 FROM v2;}} +CREATE VIEW v2 AS SELECT x+1 AS y FROM t1 +/* v2(y) */; +CREATE VIEW v1 AS SELECT y+1 FROM v2 +/* v1("y+1") */;}} db eval {DROP VIEW v1; DROP VIEW v2; DROP TABLE t1;} } diff --git a/test/shell3.test b/test/shell3.test index bb2524c1cc..63c30a2682 100644 --- a/test/shell3.test +++ b/test/shell3.test @@ -66,7 +66,7 @@ do_test shell3-1.6 { } {0 {}} do_test shell3-1.7 { catchcmd "foo.db \"CREATE TABLE\"" -} {1 {Error: near "TABLE": syntax error}} +} {1 {Error: incomplete input}} #---------------------------------------------------------------------------- # shell3-2.*: Basic tests for running SQL file from command line. @@ -96,6 +96,6 @@ do_test shell3-2.6 { } {0 {}} do_test shell3-2.7 { catchcmd "foo.db" "CREATE TABLE" -} {1 {Error: near line 1: near "TABLE": syntax error}} +} {1 {Error: near line 1: incomplete input}} finish_test diff --git a/test/shell6.test b/test/shell6.test index 25bfa32338..49b4cc3344 100644 --- a/test/shell6.test +++ b/test/shell6.test @@ -93,6 +93,14 @@ foreach {tn schema output} { } { } + 10 { + CREATE TABLE parent (id INTEGER PRIMARY KEY); + CREATE TABLE child2 (id INT PRIMARY KEY, parentID INT REFERENCES parent) + WITHOUT ROWID; + } { + CREATE INDEX 'child2_parentID' ON 'child2'('parentID'); --> parent(id) + } + } { forcedelete test.db sqlite3 db test.db diff --git a/test/shell8.test b/test/shell8.test new file mode 100644 index 0000000000..3658a8ac5d --- /dev/null +++ b/test/shell8.test @@ -0,0 +1,177 @@ +# 2017 December 9 +# +# 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 ".ar" command. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix shell8 + +ifcapable !vtab { + finish_test; return +} +set CLI [test_find_cli] + +# Check to make sure the shell has been compiled with ".archive" support. +# +if {[string match {*unknown command*} [catchcmd :memory: .archive]]} { + finish_test; return +} + +proc populate_dir {dirname spec} { + # First delete the current tree, if one exists. + file delete -force $dirname + + # Recreate the root of the new tree. + file mkdir $dirname + + # Add each file to the new tree. + foreach {f d} $spec { + set path [file join $dirname $f] + file mkdir [file dirname $path] + set fd [open $path w] + puts -nonewline $fd $d + close $fd + } +} + +proc dir_to_list {dirname {n -1}} { + if {$n<0} {set n [llength [file split $dirname]]} + + set res [list] + foreach f [glob -nocomplain $dirname/*] { + set mtime [file mtime $f] + if {$::tcl_platform(platform)!="windows"} { + set perm [file attributes $f -perm] + } else { + set perm 0 + } + set relpath [file join {*}[lrange [file split $f] $n end]] + lappend res + if {[file isdirectory $f]} { + lappend res [list $relpath / $mtime $perm] + lappend res {*}[dir_to_list $f] + } else { + set fd [open $f] + set data [read $fd] + close $fd + lappend res [list $relpath $data $mtime $perm] + } + } + lsort $res +} + +proc dir_compare {d1 d2} { + set l1 [dir_to_list $d1] + set l2 [dir_to_list $d1] + string compare $l1 $l2 +} + +foreach {tn tcl} { + 1 { + set c1 ".ar c ar1" + set x1 ".ar x" + + set c2 ".ar cC ar1 ." + set x2 ".ar Cx ar3" + + set c3 ".ar cCf ar1 test_xyz.db ." + set x3 ".ar Cfx ar3 test_xyz.db" + } + + 2 { + set c1 ".ar -c ar1" + set x1 ".ar -x" + + set c2 ".ar -cC ar1 ." + set x2 ".ar -xC ar3" + + set c3 ".ar -cCar1 -ftest_xyz.db ." + set x3 ".ar -x -C ar3 -f test_xyz.db" + } + + 3 { + set c1 ".ar --create ar1" + set x1 ".ar --extract" + + set c2 ".ar --directory ar1 --create ." + set x2 ".ar --extract --dir ar3" + + set c3 ".ar --creat --dir ar1 --file test_xyz.db ." + set x3 ".ar --e --dir ar3 --f test_xyz.db" + } + + 4 { + set c1 ".ar --cr ar1" + set x1 ".ar --e" + + set c2 ".ar -C ar1 -c ." + set x2 ".ar -x -C ar3" + + set c3 ".ar -c --directory ar1 --file test_xyz.db ." + set x3 ".ar -x --directory ar3 --file test_xyz.db" + } +} { + eval $tcl + + # Populate directory "ar1" with some files. + # + populate_dir ar1 { + file1 "abcd" + file2 "efgh" + dir1/file3 "ijkl" + } + set expected [dir_to_list ar1] + + do_test 1.$tn.1 { + catchcmd test_ar.db $c1 + file delete -force ar1 + catchcmd test_ar.db $x1 + dir_to_list ar1 + } $expected + + do_test 1.$tn.2 { + file delete -force ar3 + catchcmd test_ar.db $c2 + catchcmd test_ar.db $x2 + dir_to_list ar3 + } $expected + + do_test 1.$tn.3 { + file delete -force ar3 + file delete -force test_xyz.db + catchcmd ":memory:" $c3 + catchcmd ":memory:" $x3 + dir_to_list ar3 + } $expected + + # This is a repeat of test 1.$tn.1, except that there is a 2 second + # pause between creating the archive and extracting its contents. + # This is to test that timestamps are set correctly. + # + # Because it is slow, only do this for $tn==1. + if {$tn==1} { + do_test 1.$tn.1 { + catchcmd test_ar.db $c1 + file delete -force ar1 + after 2000 + catchcmd test_ar.db $x1 + dir_to_list ar1 + } $expected + } +} + +finish_test + + + +finish_test diff --git a/test/snapshot3.test b/test/snapshot3.test new file mode 100644 index 0000000000..266f43dfd4 --- /dev/null +++ b/test/snapshot3.test @@ -0,0 +1,100 @@ +# 2016 September 23 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# 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 snapshot3 + +# 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 +} + +#------------------------------------------------------------------------- +# This block of tests verifies that it is not possible to wrap the wal +# file - using a writer or a "PRAGMA wal_checkpoint = TRUNCATE" - while +# there is an open snapshot transaction (transaction opened using +# sqlite3_snapshot_open()). +# +do_execsql_test 1.0 { + CREATE TABLE t1(y); + PRAGMA journal_mode = wal; + INSERT INTO t1 VALUES(1); + INSERT INTO t1 VALUES(2); + INSERT INTO t1 VALUES(3); + INSERT INTO t1 VALUES(4); +} {wal} + +do_test 1.1 { + sqlite3 db2 test.db + sqlite3 db3 test.db + + execsql {SELECT * FROM sqlite_master} db2 + execsql {SELECT * FROM sqlite_master} db3 + + db2 trans { set snap [sqlite3_snapshot_get_blob db2 main] } + db2 eval { SELECT * FROM t1 } +} {1 2 3 4} + +do_test 1.2 { + execsql BEGIN db2 + sqlite3_snapshot_open_blob db2 main $snap + db2 eval { SELECT * FROM t1 } +} {1 2 3 4} + +do_test 1.2 { + execsql END db2 + execsql { PRAGMA wal_checkpoint } + + execsql BEGIN db2 + sqlite3_snapshot_open_blob db2 main $snap + db2 eval { SELECT * FROM t1 } +} {1 2 3 4} + +set sz [file size test.db-wal] +do_test 1.3 { + execsql { PRAGMA wal_checkpoint = truncate } + file size test.db-wal +} $sz + +do_test 1.4 { + execsql BEGIN db3 + list [catch { sqlite3_snapshot_open_blob db3 main $snap } msg] $msg +} {0 {}} + +do_test 1.5 { + db3 eval { SELECT * FROM t1; END } +} {1 2 3 4} + +do_test 1.6 { + db2 eval { SELECT * FROM t1; END } +} {1 2 3 4} + +do_test 1.7 { + execsql { PRAGMA wal_checkpoint = truncate } + file size test.db-wal +} 0 + +do_test 1.8 { + execsql BEGIN db3 + list [catch { sqlite3_snapshot_open_blob db3 main $snap } msg] $msg +} {1 SQLITE_BUSY_SNAPSHOT} + +finish_test + diff --git a/test/speedtest1.c b/test/speedtest1.c index b92801a208..9342da79bf 100644 --- a/test/speedtest1.c +++ b/test/speedtest1.c @@ -32,7 +32,7 @@ static const char zHelp[] = " --size N Relative test size. Default=100\n" " --stats Show statistics at the end\n" " --temp N N from 0 to 9. 0: no temp table. 9: all temp tables\n" - " --testset T Run test-set T (main, cte, rtree, orm, debug)\n" + " --testset T Run test-set T (main, cte, rtree, orm, fp, debug)\n" " --trace Turn on SQL tracing\n" " --threads N Use up to N threads for sorting\n" " --utf16be Set text encoding to UTF-16BE\n" @@ -1120,7 +1120,77 @@ void testset_cte(void){ ); speedtest1_run(); speedtest1_end_test(); +} +/* +** Compute a pseudo-random floating point ascii number. +*/ +void speedtest1_random_ascii_fp(char *zFP){ + int x = speedtest1_random(); + int y = speedtest1_random(); + int z; + z = y%10; + if( z<0 ) z = -z; + y /= 10; + sqlite3_snprintf(100,zFP,"%d.%de%d",y,z,x%200); +} + +/* +** A testset for floating-point numbers. +*/ +void testset_fp(void){ + int n; + int i; + char zFP1[100]; + char zFP2[100]; + + n = g.szTest*5000; + speedtest1_begin_test(100, "Fill a table with %d FP values", n*2); + speedtest1_exec("BEGIN"); + speedtest1_exec("CREATE%s TABLE t1(a REAL %s, b REAL %s);", + isTemp(1), g.zNN, g.zNN); + speedtest1_prepare("INSERT INTO t1 VALUES(?1,?2); -- %d times", n); + for(i=1; i<=n; i++){ + speedtest1_random_ascii_fp(zFP1); + speedtest1_random_ascii_fp(zFP2); + sqlite3_bind_text(g.pStmt, 1, zFP1, -1, SQLITE_STATIC); + sqlite3_bind_text(g.pStmt, 2, zFP2, -1, SQLITE_STATIC); + speedtest1_run(); + } + speedtest1_exec("COMMIT"); + speedtest1_end_test(); + + n = g.szTest/25 + 2; + speedtest1_begin_test(110, "%d range queries", n); + speedtest1_prepare("SELECT sum(b) FROM t1 WHERE a BETWEEN ?1 AND ?2"); + for(i=1; i<=n; i++){ + speedtest1_random_ascii_fp(zFP1); + speedtest1_random_ascii_fp(zFP2); + sqlite3_bind_text(g.pStmt, 1, zFP1, -1, SQLITE_STATIC); + sqlite3_bind_text(g.pStmt, 2, zFP2, -1, SQLITE_STATIC); + speedtest1_run(); + } + speedtest1_end_test(); + + speedtest1_begin_test(120, "CREATE INDEX three times"); + speedtest1_exec("BEGIN;"); + speedtest1_exec("CREATE INDEX t1a ON t1(a);"); + speedtest1_exec("CREATE INDEX t1b ON t1(b);"); + speedtest1_exec("CREATE INDEX t1ab ON t1(a,b);"); + speedtest1_exec("COMMIT;"); + speedtest1_end_test(); + + n = g.szTest/3 + 2; + speedtest1_begin_test(130, "%d indexed range queries", n); + speedtest1_prepare("SELECT sum(b) FROM t1 WHERE a BETWEEN ?1 AND ?2"); + for(i=1; i<=n; i++){ + speedtest1_random_ascii_fp(zFP1); + speedtest1_random_ascii_fp(zFP2); + sqlite3_bind_text(g.pStmt, 1, zFP1, -1, SQLITE_STATIC); + sqlite3_bind_text(g.pStmt, 2, zFP2, -1, SQLITE_STATIC); + speedtest1_run(); + } + speedtest1_end_test(); } #ifdef SQLITE_ENABLE_RTREE @@ -1873,6 +1943,8 @@ int main(int argc, char **argv){ testset_orm(); }else if( strcmp(zTSet,"cte")==0 ){ testset_cte(); + }else if( strcmp(zTSet,"fp")==0 ){ + testset_fp(); }else if( strcmp(zTSet,"rtree")==0 ){ #ifdef SQLITE_ENABLE_RTREE testset_rtree(6, 147); @@ -1881,7 +1953,7 @@ int main(int argc, char **argv){ "the R-Tree tests\n"); #endif }else{ - fatal_error("unknown testset: \"%s\"\nChoices: main debug1 cte rtree\n", + fatal_error("unknown testset: \"%s\"\nChoices: main debug1 cte rtree fp\n", zTSet); } speedtest1_final(); diff --git a/test/spellfix.test b/test/spellfix.test index 8128bb59d2..68bcfd5adb 100644 --- a/test/spellfix.test +++ b/test/spellfix.test @@ -279,7 +279,7 @@ ifcapable trace { do_tracesql_test 6.2.3 { SELECT word, distance FROM t3 WHERE rowid = 10 AND word MATCH 'kiiner'; } {keener 300 - {SELECT id, word, rank, k1 FROM "main"."t3_vocab" WHERE langid=0 AND k2>=?1 AND k2=?1 AND k20 OR b>0; +} + +do_execsql_test 6.2 { + SELECT * FROM d1; +} {3 2} finish_test diff --git a/test/vacuum4.test b/test/vacuum4.test index 326d037276..bbf0dd4379 100644 --- a/test/vacuum4.test +++ b/test/vacuum4.test @@ -65,3 +65,5 @@ do_test vacuum4-1.1 { VACUUM; } } {} + +finish_test diff --git a/test/vacuum5.test b/test/vacuum5.test index 8e76a9393c..f203fb87ba 100644 --- a/test/vacuum5.test +++ b/test/vacuum5.test @@ -143,9 +143,11 @@ if {$::TEMP_STORE<3 && [permutation]!="inmemory_journal"} { db close tvfs delete - do_test 3.2 { - lrange $::openfiles 0 4 - } {test.db test.db-journal test.db-journal {} test.db-journal} + if {[atomic_batch_write test.db]==0} { + do_test 3.2 { + lrange $::openfiles 0 4 + } {test.db test.db-journal test.db-journal {} test.db-journal} + } } diff --git a/test/varint.test b/test/varint.test index 974e88f2a6..fd0ec0d415 100644 --- a/test/varint.test +++ b/test/varint.test @@ -30,3 +30,5 @@ foreach start {0 100 10000 1000000 0x10000000} { } } } + +finish_test diff --git a/test/wal2.test b/test/wal2.test index a27f44b2ee..ac4cb9f133 100644 --- a/test/wal2.test +++ b/test/wal2.test @@ -85,8 +85,8 @@ do_test wal2-1.1 { } {4 10} set RECOVER [list \ - {0 1 lock exclusive} {1 7 lock exclusive} \ - {1 7 unlock exclusive} {0 1 unlock exclusive} \ + {0 1 lock exclusive} {1 2 lock exclusive} {4 4 lock exclusive} \ + {1 2 unlock exclusive} {4 4 unlock exclusive} {0 1 unlock exclusive} \ ] set READ [list \ {4 1 lock shared} {4 1 unlock shared} \ @@ -356,8 +356,10 @@ tvfs delete set expected_locks [list] lappend expected_locks {1 1 lock exclusive} ;# Lock checkpoint lappend expected_locks {0 1 lock exclusive} ;# Lock writer -lappend expected_locks {2 6 lock exclusive} ;# Lock recovery & all aReadMark[] -lappend expected_locks {2 6 unlock exclusive} ;# Unlock recovery & aReadMark[] +lappend expected_locks {2 1 lock exclusive} ;# Lock recovery +lappend expected_locks {4 4 lock exclusive} ;# Lock all aReadMark[] +lappend expected_locks {2 1 unlock exclusive} ;# Unlock recovery +lappend expected_locks {4 4 unlock exclusive} ;# Unlock all aReadMark[] lappend expected_locks {0 1 unlock exclusive} ;# Unlock writer lappend expected_locks {3 1 lock exclusive} ;# Lock aReadMark[0] lappend expected_locks {3 1 unlock exclusive} ;# Unlock aReadMark[0] @@ -545,15 +547,23 @@ do_test wal2-6.3.4 { BEGIN; INSERT INTO t1 VALUES('Groucho'); } - list [file exists test.db-wal] [file exists test.db-journal] -} {0 1} +} {} +if {[atomic_batch_write test.db]==0} { + do_test wal2-6.3.4.1 { + list [file exists test.db-wal] [file exists test.db-journal] + } {0 1} +} do_test wal2-6.3.5 { execsql { PRAGMA lock_status } } {main exclusive temp closed} do_test wal2-6.3.6 { execsql { COMMIT } - list [file exists test.db-wal] [file exists test.db-journal] -} {0 1} +} {} +if {[atomic_batch_write test.db]==0} { + do_test wal2-6.3.6.1 { + list [file exists test.db-wal] [file exists test.db-journal] + } {0 1} +} do_test wal2-6.3.7 { execsql { PRAGMA lock_status } } {main exclusive temp closed} @@ -578,8 +588,8 @@ do_test wal2-6.4.1 { } {} set RECOVERY { - {0 1 lock exclusive} {1 7 lock exclusive} - {1 7 unlock exclusive} {0 1 unlock exclusive} + {0 1 lock exclusive} {1 2 lock exclusive} {4 4 lock exclusive} + {1 2 unlock exclusive} {4 4 unlock exclusive} {0 1 unlock exclusive} } set READMARK0_READ { {3 1 lock shared} {3 1 unlock shared} @@ -1091,7 +1101,7 @@ if {$::tcl_platform(platform) == "unix"} { foreach {tn db_perm wal_perm shm_perm can_open can_read can_write} { 2 00644 00644 00644 1 1 1 3 00644 00400 00644 1 1 0 - 4 00644 00644 00400 1 0 0 + 4 00644 00644 00400 1 1 0 5 00400 00644 00644 1 1 0 7 00644 00000 00644 1 0 0 diff --git a/test/walfault.test b/test/walfault.test index 4e7064d53b..6cb760b258 100644 --- a/test/walfault.test +++ b/test/walfault.test @@ -552,7 +552,7 @@ do_faultsim_test walfault-14 -prep { #------------------------------------------------------------------------- # Test fault-handling when switching out of exclusive-locking mode. # -do_test walfault-14-pre { +do_test walfault-15-pre { faultsim_delete_and_reopen execsql { PRAGMA auto_vacuum = 0; @@ -565,7 +565,7 @@ do_test walfault-14-pre { } faultsim_save_and_close } {} -do_faultsim_test walfault-14 -prep { +do_faultsim_test walfault-15 -prep { faultsim_restore_and_reopen execsql { SELECT count(*) FROM abc; diff --git a/test/walmode.test b/test/walmode.test index 4e14d54d4f..f760823c8d 100644 --- a/test/walmode.test +++ b/test/walmode.test @@ -45,15 +45,17 @@ do_test walmode-1.2 { file size test.db } {1024} -set expected_sync_count 3 -if {$::tcl_platform(platform)!="windows"} { - ifcapable dirsync { - incr expected_sync_count +if {[atomic_batch_write test.db]==0} { + set expected_sync_count 3 + if {$::tcl_platform(platform)!="windows"} { + ifcapable dirsync { + incr expected_sync_count + } } + do_test walmode-1.3 { + set sqlite_sync_count + } $expected_sync_count } -do_test walmode-1.3 { - set sqlite_sync_count -} $expected_sync_count do_test walmode-1.4 { file exists test.db-wal @@ -106,9 +108,11 @@ do_test walmode-4.1 { execsql { INSERT INTO t1 VALUES(1, 2) } execsql { PRAGMA journal_mode = persist } } {persist} -do_test walmode-4.2 { - list [file exists test.db-journal] [file exists test.db-wal] -} {1 0} +if {[atomic_batch_write test.db]==0} { + do_test walmode-4.2 { + list [file exists test.db-journal] [file exists test.db-wal] + } {1 0} +} do_test walmode-4.3 { execsql { SELECT * FROM t1 } } {1 2} @@ -117,9 +121,11 @@ do_test walmode-4.4 { sqlite3 db test.db execsql { SELECT * FROM t1 } } {1 2} -do_test walmode-4.5 { - list [file exists test.db-journal] [file exists test.db-wal] -} {1 0} +if {[atomic_batch_write test.db]==0} { + do_test walmode-4.5 { + list [file exists test.db-journal] [file exists test.db-wal] + } {1 0} +} # Test that nothing goes wrong if a connection is prevented from changing # from WAL to rollback mode because a second connection has the database diff --git a/test/walprotocol.test b/test/walprotocol.test index ee8d0b72a5..b1d9e8c01f 100644 --- a/test/walprotocol.test +++ b/test/walprotocol.test @@ -52,21 +52,21 @@ do_test 1.1 { set ::locks [list] sqlite3 db test.db -vfs T execsql { SELECT * FROM x } - lrange $::locks 0 3 -} [list {0 1 lock exclusive} {1 7 lock exclusive} \ - {1 7 unlock exclusive} {0 1 unlock exclusive} \ + lrange $::locks 0 5 +} [list {0 1 lock exclusive} {1 2 lock exclusive} {4 4 lock exclusive} \ + {1 2 unlock exclusive} {4 4 unlock exclusive} {0 1 unlock exclusive} \ ] do_test 1.2 { db close set ::locks [list] sqlite3 db test.db -vfs T execsql { SELECT * FROM x } - lrange $::locks 0 3 -} [list {0 1 lock exclusive} {1 7 lock exclusive} \ - {1 7 unlock exclusive} {0 1 unlock exclusive} \ + lrange $::locks 0 5 +} [list {0 1 lock exclusive} {1 2 lock exclusive} {4 4 lock exclusive} \ + {1 2 unlock exclusive} {4 4 unlock exclusive} {0 1 unlock exclusive} \ ] proc lock_callback {method filename handle lock} { - if {$lock == "1 7 lock exclusive"} { return SQLITE_BUSY } + if {$lock == "1 2 lock exclusive"} { return SQLITE_BUSY } return SQLITE_OK } puts "# Warning: This next test case causes SQLite to call xSleep(1) 100 times." @@ -90,6 +90,18 @@ do_test 1.4 { sqlite3 db test.db -vfs T catchsql { SELECT * FROM x } } {1 {locking protocol}} + +puts "# Warning: Third time!" +proc lock_callback {method filename handle lock} { + if {$lock == "4 4 lock exclusive"} { return SQLITE_BUSY } + return SQLITE_OK +} +do_test 1.5 { + db close + set ::locks [list] + sqlite3 db test.db -vfs T + catchsql { SELECT * FROM x } +} {1 {locking protocol}} db close T delete @@ -135,13 +147,14 @@ T filter xShmLock T script lock_callback proc lock_callback {method file handle spec} { - if {$spec == "1 7 unlock exclusive"} { + if {$spec == "1 2 unlock exclusive"} { T filter {} set ::r [catchsql { SELECT * FROM b } db2] } } sqlite3 db test.db sqlite3 db2 test.db +puts "# Warning: Another slow test!" do_test 2.5 { execsql { SELECT * FROM b } } {Tehran Qom Markazi Qazvin Gilan Ardabil} @@ -157,12 +170,13 @@ sqlite3 db2 test.db T filter xShmLock T script lock_callback proc lock_callback {method file handle spec} { - if {$spec == "1 7 unlock exclusive"} { + if {$spec == "1 2 unlock exclusive"} { T filter {} set ::r [catchsql { SELECT * FROM b } db2] } } unset ::r +puts "# Warning: Last one!" do_test 2.7 { execsql { SELECT * FROM b } } {Tehran Qom Markazi Qazvin Gilan Ardabil} diff --git a/test/walro.test b/test/walro.test index f46e44d4cb..cae52db6d3 100644 --- a/test/walro.test +++ b/test/walro.test @@ -101,10 +101,11 @@ do_multiclient_test tn { code1 { db close } list [file exists test.db-wal] [file exists test.db-shm] } {1 1} + do_test 1.2.2 { code1 { sqlite3 db file:test.db?readonly_shm=1 } - sql1 { SELECT * FROM t1 } - } {a b c d e f g h i j} + list [catch { sql1 { SELECT * FROM t1 } } msg] $msg + } {0 {a b c d e f g h i j}} do_test 1.2.3 { code1 { db close } @@ -113,10 +114,10 @@ do_multiclient_test tn { file attributes test.db-shm -permissions r--r--r-- code1 { sqlite3 db file:test.db?readonly_shm=1 } csql1 { SELECT * FROM t1 } - } {1 {attempt to write a readonly database}} + } {0 {a b c d e f g h i j}} do_test 1.2.4 { code1 { sqlite3_extended_errcode db } - } {SQLITE_READONLY_RECOVERY} + } {SQLITE_OK} do_test 1.2.5 { file attributes test.db-shm -permissions rw-r--r-- @@ -138,11 +139,15 @@ do_multiclient_test tn { # Now check that if the readonly_shm option is not supplied, or if it # is set to zero, it is not possible to connect to the database without # read-write access to the shm. + # + # UPDATE: os_unix.c now opens the *-shm file in readonly mode + # automatically. + # do_test 1.3.1 { code1 { db close } code1 { sqlite3 db test.db } csql1 { SELECT * FROM t1 } - } {1 {unable to open database file}} + } {0 {a b c d e f g h i j k l}} # Also test that if the -shm file can be opened for read/write access, # it is not if readonly_shm=1 is present in the URI. @@ -161,10 +166,10 @@ do_multiclient_test tn { file attributes test.db-shm -permissions r--r--r-- code1 { sqlite3 db file:test.db?readonly_shm=1 } csql1 { SELECT * FROM t1 } - } {1 {attempt to write a readonly database}} + } {0 {a b c d e f g h i j k l}} do_test 1.3.2.4 { code1 { sqlite3_extended_errcode db } - } {SQLITE_READONLY_RECOVERY} + } {SQLITE_OK} #----------------------------------------------------------------------- # Test cases 1.4.* check that checkpoints and log wraps don't prevent diff --git a/test/walro2.test b/test/walro2.test new file mode 100644 index 0000000000..34408c1695 --- /dev/null +++ b/test/walro2.test @@ -0,0 +1,406 @@ +# 2011 May 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 contains tests for using WAL databases in read-only mode. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl +source $testdir/wal_common.tcl +set ::testprefix walro2 + +# And only if the build is WAL-capable. +# +ifcapable !wal { + finish_test + return +} + +proc copy_to_test2 {bZeroShm} { + forcecopy test.db test.db2 + forcecopy test.db-wal test.db2-wal + if {$bZeroShm} { + forcedelete test.db2-shm + set fd [open test.db2-shm w] + seek $fd [expr [file size test.db-shm]-1] + puts -nonewline $fd "\0" + close $fd + } else { + forcecopy test.db-shm test.db2-shm + } +} + +# Most systems allocate the *-shm file in 32KB trunks. But on UNIX systems +# for which the getpagesize() call returns greater than 32K, the *-shm +# file is allocated in page-sized units (since you cannot mmap part of +# a page). The following code sets variable $MINSHMSZ to the smallest +# possible *-shm file (i.e. the greater of 32KB and the system page-size). +# +do_execsql_test 0.0 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(x); +} {wal} +set MINSHMSZ [file size test.db-shm] + +foreach bZeroShm {0 1} { +set TN [expr $bZeroShm+1] +do_multiclient_test tn { + + # Close all connections and delete the database. + # + code1 { db close } + code2 { db2 close } + code3 { db3 close } + forcedelete test.db + + # Do not run tests with the connections in the same process. + # + if {$tn==2} continue + + foreach c {code1 code2 code3} { + $c { + sqlite3_shutdown + sqlite3_config_uri 1 + } + } + + do_test $TN.1.1 { + code2 { sqlite3 db2 test.db } + sql2 { + CREATE TABLE t1(x, y); + PRAGMA journal_mode = WAL; + INSERT INTO t1 VALUES('a', 'b'); + INSERT INTO t1 VALUES('c', 'd'); + } + file exists test.db-shm + } {1} + + do_test $TN.1.2.1 { + copy_to_test2 $bZeroShm + code1 { + sqlite3 db file:test.db2?readonly_shm=1 + } + + sql1 { SELECT * FROM t1 } + } {a b c d} + do_test $TN.1.2.2 { + sql1 { SELECT * FROM t1 } + } {a b c d} + + do_test $TN.1.3.1 { + code3 { sqlite3 db3 test.db2 } + sql3 { SELECT * FROM t1 } + } {a b c d} + + do_test $TN.1.3.2 { + sql1 { SELECT * FROM t1 } + } {a b c d} + + code1 { db close } + code2 { db2 close } + code3 { db3 close } + + do_test $TN.2.1 { + code2 { sqlite3 db2 test.db } + sql2 { + INSERT INTO t1 VALUES('e', 'f'); + INSERT INTO t1 VALUES('g', 'h'); + } + file exists test.db-shm + } {1} + + do_test $TN.2.2 { + copy_to_test2 $bZeroShm + code1 { + sqlite3 db file:test.db2?readonly_shm=1 + } + sql1 { + BEGIN; + SELECT * FROM t1; + } + } {a b c d e f g h} + + do_test $TN.2.3.1 { + code3 { sqlite3 db3 test.db2 } + sql3 { SELECT * FROM t1 } + } {a b c d e f g h} + do_test $TN.2.3.2 { + sql3 { INSERT INTO t1 VALUES('i', 'j') } + code3 { db3 close } + sql1 { COMMIT } + } {} + do_test $TN.2.3.3 { + sql1 { SELECT * FROM t1 } + } {a b c d e f g h i j} + + + #----------------------------------------------------------------------- + # 3.1.*: That a readonly_shm connection can read a database file if both + # the *-wal and *-shm files are zero bytes in size. + # + # 3.2.*: That it flushes the cache if, between transactions on a db with a + # zero byte *-wal file, some other connection modifies the db, then + # does "PRAGMA wal_checkpoint=truncate" to truncate the wal file + # back to zero bytes in size. + # + # 3.3.*: That, if between transactions some other process wraps the wal + # file, the readonly_shm client reruns recovery. + # + catch { code1 { db close } } + catch { code2 { db2 close } } + catch { code3 { db3 close } } + do_test $TN.3.1.0 { + list [file exists test.db-wal] [file exists test.db-shm] + } {0 0} + do_test $TN.3.1.1 { + close [open test.db-wal w] + close [open test.db-shm w] + code1 { + sqlite3 db file:test.db?readonly_shm=1 + } + sql1 { SELECT * FROM t1 } + } {a b c d e f g h} + + do_test $TN.3.2.0 { + list [file size test.db-wal] [file size test.db-shm] + } {0 0} + do_test $TN.3.2.1 { + code2 { sqlite3 db2 test.db } + sql2 { INSERT INTO t1 VALUES(1, 2) ; PRAGMA wal_checkpoint=truncate } + code2 { db2 close } + sql1 { SELECT * FROM t1 } + } {a b c d e f g h 1 2} + do_test $TN.3.2.2 { + list [file size test.db-wal] [file size test.db-shm] + } [list 0 $MINSHMSZ] + + do_test $TN.3.3.0 { + code2 { sqlite3 db2 test.db } + sql2 { + INSERT INTO t1 VALUES(3, 4); + INSERT INTO t1 VALUES(5, 6); + INSERT INTO t1 VALUES(7, 8); + INSERT INTO t1 VALUES(9, 10); + } + code2 { db2 close } + code1 { db close } + list [file size test.db-wal] [file size test.db-shm] + } [list [wal_file_size 4 1024] $MINSHMSZ] + do_test $TN.3.3.1 { + code1 { sqlite3 db file:test.db?readonly_shm=1 } + sql1 { SELECT * FROM t1 } + } {a b c d e f g h 1 2 3 4 5 6 7 8 9 10} + do_test $TN.3.3.2 { + code2 { sqlite3 db2 test.db } + sql2 { + PRAGMA wal_checkpoint; + DELETE FROM t1; + INSERT INTO t1 VALUES('i', 'ii'); + } + code2 { db2 close } + list [file size test.db-wal] [file size test.db-shm] + } [list [wal_file_size 4 1024] $MINSHMSZ] + do_test $TN.3.3.3 { + sql1 { SELECT * FROM t1 } + } {i ii} + + #----------------------------------------------------------------------- + # + # + catch { code1 { db close } } + catch { code2 { db2 close } } + catch { code3 { db3 close } } + + do_test $TN.4.0 { + code1 { forcedelete test.db } + code1 { sqlite3 db test.db } + sql1 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(x); + INSERT INTO t1 VALUES('hello'); + INSERT INTO t1 VALUES('world'); + } + + copy_to_test2 $bZeroShm + + code1 { db close } + } {} + + do_test $TN.4.1.1 { + code2 { sqlite3 db2 file:test.db2?readonly_shm=1 } + sql2 { SELECT * FROM t1 } + } {hello world} + + do_test $TN.4.1.2 { + code3 { sqlite3 db3 test.db2 } + sql3 { + INSERT INTO t1 VALUES('!'); + PRAGMA wal_checkpoint = truncate; + } + code3 { db3 close } + } {} + do_test $TN.4.1.3 { + sql2 { SELECT * FROM t1 } + } {hello world !} + + catch { code1 { db close } } + catch { code2 { db2 close } } + catch { code3 { db3 close } } + + do_test $TN.4.2.1 { + code1 { sqlite3 db test.db } + sql1 { + INSERT INTO t1 VALUES('!'); + INSERT INTO t1 VALUES('!'); + + PRAGMA cache_size = 10; + CREATE TABLE t2(x); + + BEGIN; + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<500 + ) + INSERT INTO t2 SELECT randomblob(500) FROM s; + SELECT count(*) FROM t2; + } + } {500} + set sz [file size test.db-wal] + do_test $TN.4.2.2.(sz=$sz) { + expr {$sz>400000} + } {1} + do_test $TN.4.2.4 { + file_control_persist_wal db 1; db close + + copy_to_test2 $bZeroShm + code2 { sqlite3 db2 file:test.db2?readonly_shm=1 } + sql2 { + SELECT * FROM t1; + SELECT count(*) FROM t2; + } + } {hello world ! ! 0} + + #----------------------------------------------------------------------- + # + # + catch { code1 { db close } } + catch { code2 { db2 close } } + catch { code3 { db3 close } } + + do_test $TN.5.0 { + code1 { forcedelete test.db } + code1 { sqlite3 db test.db } + sql1 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(x); + INSERT INTO t1 VALUES('hello'); + INSERT INTO t1 VALUES('world'); + INSERT INTO t1 VALUES('!'); + INSERT INTO t1 VALUES('world'); + INSERT INTO t1 VALUES('hello'); + } + + copy_to_test2 $bZeroShm + + code1 { db close } + } {} + + do_test $TN.5.1 { + code2 { sqlite3 db2 file:test.db2?readonly_shm=1 } + sql2 { + SELECT * FROM t1; + } + } {hello world ! world hello} + + do_test $TN.5.2 { + code1 { + proc handle_read {op args} { + if {$op=="xRead" && [file tail [lindex $args 0]]=="test.db2-wal"} { + set ::res2 [sql2 { SELECT * FROM t1 }] + } + puts "$msg xRead $args" + return "SQLITE_OK" + } + testvfs tvfs -fullshm 1 + + sqlite3 db file:test.db2?vfs=tvfs + db eval { SELECT * FROM sqlite_master } + + tvfs filter xRead + tvfs script handle_read + } + sql1 { + PRAGMA wal_checkpoint = truncate; + } + code1 { set ::res2 } + } {hello world ! world hello} + + do_test $TN.5.3 { + code1 { db close } + code1 { tvfs delete } + } {} + + #----------------------------------------------------------------------- + # + # + catch { code1 { db close } } + catch { code2 { db2 close } } + catch { code3 { db3 close } } + + do_test $TN.6.1 { + code1 { forcedelete test.db } + code1 { sqlite3 db test.db } + sql1 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(x); + INSERT INTO t1 VALUES('hello'); + INSERT INTO t1 VALUES('world'); + INSERT INTO t1 VALUES('!'); + INSERT INTO t1 VALUES('world'); + INSERT INTO t1 VALUES('hello'); + } + + copy_to_test2 $bZeroShm + + code1 { db close } + } {} + + do_test $TN.6.2 { + code1 { + set ::nRem 5 + proc handle_read {op args} { + if {$op=="xRead" && [file tail [lindex $args 0]]=="test.db2-wal"} { + incr ::nRem -1 + if {$::nRem==0} { + code2 { sqlite3 db2 test.db2 } + sql2 { PRAGMA wal_checkpoint = truncate } + } + } + return "SQLITE_OK" + } + testvfs tvfs -fullshm 1 + + tvfs filter xRead + tvfs script handle_read + + sqlite3 db file:test.db2?readonly_shm=1&vfs=tvfs + db eval { SELECT * FROM t1 } + } + } {hello world ! world hello} + + do_test $TN.6.3 { + code1 { db close } + code1 { tvfs delete } + } {} +} +} ;# foreach bZeroShm + +finish_test diff --git a/test/walrofault.test b/test/walrofault.test new file mode 100644 index 0000000000..3e66e2d920 --- /dev/null +++ b/test/walrofault.test @@ -0,0 +1,60 @@ +# 2011 May 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 contains tests for using WAL databases in read-only mode. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/malloc_common.tcl +set ::testprefix walro2 + +# And only if the build is WAL-capable. +# +ifcapable !wal { + finish_test + return +} + +db close +sqlite3_shutdown +sqlite3_config_uri 1 +sqlite3 db test.db + +do_execsql_test 1.0 { + CREATE TABLE t1(b); + PRAGMA journal_mode = wal; + INSERT INTO t1 VALUES('hello'); + INSERT INTO t1 VALUES('world'); + INSERT INTO t1 VALUES('!'); + INSERT INTO t1 VALUES('world'); + INSERT INTO t1 VALUES('hello'); + PRAGMA cache_size = 10; + BEGIN; + WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<30 ) + INSERT INTO t1(b) SELECT randomblob(800) FROM s; +} {wal} +file_control_persist_wal db 1; db close +faultsim_save_and_close + +do_faultsim_test 1 -faults oom* -prep { + catch { db close } + faultsim_restore + sqlite3 db file:test.db?readonly_shm=1 +} -body { + execsql { SELECT * FROM t1 } +} -test { + faultsim_test_result {0 {hello world ! world hello}} +} + + + +finish_test diff --git a/test/walthread.test b/test/walthread.test index 6249ce11af..8e5df9e589 100644 --- a/test/walthread.test +++ b/test/walthread.test @@ -327,59 +327,61 @@ do_thread_test2 walthread-1 -seconds $seconds(walthread-1) -init { # the number of write-transactions performed using a rollback journal. # For example, "192 w, 185 r". # -do_thread_test2 walthread-2 -seconds $seconds(walthread-2) -init { - execsql { CREATE TABLE t1(x INTEGER PRIMARY KEY, y UNIQUE) } -} -thread RB 2 { +if {[atomic_batch_write test.db]==0} { + do_thread_test2 walthread-2 -seconds $seconds(walthread-2) -init { + execsql { CREATE TABLE t1(x INTEGER PRIMARY KEY, y UNIQUE) } + } -thread RB 2 { - db close - set nRun 0 - set nDel 0 - while {[tt_continue]} { - sqlite3 db test.db - db busy busyhandler - db eval { SELECT * FROM sqlite_master } - catch { db eval { PRAGMA journal_mode = DELETE } } - db eval { - BEGIN; - INSERT INTO t1 VALUES(NULL, randomblob(100+$E(pid))); - } - incr nRun 1 - incr nDel [file exists test.db-journal] - if {[file exists test.db-journal] + [file exists test.db-wal] != 1} { - error "File-system looks bad..." - } - db eval COMMIT - - integrity_check db close - } - list $nRun $nDel - set {} "[expr $nRun-$nDel] w, $nDel r" - -} -thread WAL 2 { - db close - set nRun 0 - set nDel 0 - while {[tt_continue]} { - sqlite3 db test.db - db busy busyhandler - db eval { SELECT * FROM sqlite_master } - catch { db eval { PRAGMA journal_mode = WAL } } - db eval { - BEGIN; - INSERT INTO t1 VALUES(NULL, randomblob(110+$E(pid))); + set nRun 0 + set nDel 0 + while {[tt_continue]} { + sqlite3 db test.db + db busy busyhandler + db eval { SELECT * FROM sqlite_master } + catch { db eval { PRAGMA journal_mode = DELETE } } + db eval { + BEGIN; + INSERT INTO t1 VALUES(NULL, randomblob(100+$E(pid))); + } + incr nRun 1 + incr nDel [file exists test.db-journal] + if {[file exists test.db-journal] + [file exists test.db-wal] != 1} { + error "File-system looks bad..." + } + db eval COMMIT + + integrity_check + db close } - incr nRun 1 - incr nDel [file exists test.db-journal] - if {[file exists test.db-journal] + [file exists test.db-wal] != 1} { - error "File-system looks bad..." - } - db eval COMMIT - - integrity_check + list $nRun $nDel + set {} "[expr $nRun-$nDel] w, $nDel r" + + } -thread WAL 2 { db close + set nRun 0 + set nDel 0 + while {[tt_continue]} { + sqlite3 db test.db + db busy busyhandler + db eval { SELECT * FROM sqlite_master } + catch { db eval { PRAGMA journal_mode = WAL } } + db eval { + BEGIN; + INSERT INTO t1 VALUES(NULL, randomblob(110+$E(pid))); + } + incr nRun 1 + incr nDel [file exists test.db-journal] + if {[file exists test.db-journal] + [file exists test.db-wal] != 1} { + error "File-system looks bad..." + } + db eval COMMIT + + integrity_check + db close + } + set {} "[expr $nRun-$nDel] w, $nDel r" } - set {} "[expr $nRun-$nDel] w, $nDel r" } do_thread_test walthread-3 -seconds $seconds(walthread-3) -init { diff --git a/test/whereF.test b/test/whereF.test index 8b272a5c53..121cc3cf22 100644 --- a/test/whereF.test +++ b/test/whereF.test @@ -215,4 +215,98 @@ ifcapable json1&&vtab { } {{{"foo":"meep","other":12345}}} } +# 2018-01-27 +# Ticket https://sqlite.org/src/tktview/ec32177c99ccac2b180fd3ea2083 +# Incorrect result when using the new OR clause factoring optimization +# +# This is the original test case as reported on the sqlite-users mailing +# list +# +do_execsql_test 7.1 { + DROP TABLE IF EXISTS cd; + CREATE TABLE cd ( cdid INTEGER PRIMARY KEY NOT NULL, genreid integer ); + CREATE INDEX cd_idx_genreid ON cd (genreid); + INSERT INTO cd ( cdid, genreid ) VALUES + ( 1, 1 ), + ( 2, NULL ), + ( 3, NULL ), + ( 4, NULL ), + ( 5, NULL ); + + SELECT cdid + FROM cd me + WHERE 2 > ( + SELECT COUNT( * ) + FROM cd rownum__emulation + WHERE + ( + me.genreid IS NOT NULL + AND + rownum__emulation.genreid IS NULL + ) + OR + ( + me.genreid IS NOT NULL + AND + rownum__emulation.genreid IS NOT NULL + AND + rownum__emulation.genreid < me.genreid + ) + OR + ( + ( me.genreid = rownum__emulation.genreid OR ( me.genreid IS NULL + AND rownum__emulation.genreid IS NULL ) ) + AND + rownum__emulation.cdid > me.cdid + ) + ); +} {4 5} + +# Simplified test cases from the ticket +# +do_execsql_test 7.2 { + DROP TABLE IF EXISTS t1; + DROP TABLE IF EXISTS t2; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + INSERT INTO t1(a,b) VALUES(1,1); + CREATE TABLE t2(aa INTEGER PRIMARY KEY, bb); + INSERT INTO t2(aa,bb) VALUES(1,1),(2,NULL),(3,NULL); + SELECT ( + SELECT COUNT(*) FROM t2 + WHERE ( t1.b IS NOT NULL AND t2.bb IS NULL ) + OR ( t2.bb < t1.b ) + OR ( t1.b IS t2.bb AND t2.aa > t1.a ) + ) + FROM t1; +} {2} + +# The fix for ticket ec32177c99ccac2b180fd3ea2083 only makes a difference +# in the output when there is a TERM_VNULL entry in the WhereClause array. +# And TERM_VNULL entries are only generated when compiling with +# SQLITE_ENABLE_STAT4. Nevertheless, it is correct that TERM_VIRTUAL terms +# should not participate in the factoring optimization. In all cases other +# than TERM_VNULL, participation is harmless, but it does consume a few +# extra CPU cycles. +# +# The following test verifies that the TERM_VIRTUAL terms resulting from +# a GLOB operator do not appear anywhere in the generated code. This +# confirms that the problem is fixed, even on builds that omit STAT4. +# +do_execsql_test 7.3 { + DROP TABLE IF EXISTS t1; + DROP TABLE IF EXISTS t2; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT); + INSERT INTO t1(a,b) VALUES(1,'abcxyz'); + CREATE TABLE t2(aa INTEGER PRIMARY KEY, bb TEXT); + INSERT INTO t2(aa,bb) VALUES(1,'abc'),(2,'wxyz'),(3,'xyz'); + CREATE INDEX t2bb ON t2(bb); + EXPLAIN SELECT ( + SELECT COUNT(*) FROM t2 + WHERE ( t1.b GLOB 'a*z' AND t2.bb='xyz' ) + OR ( t2.bb = t1.b ) + OR ( t2.aa = t1.a ) + ) + FROM t1; +} {~/ (Lt|Ge) /} + finish_test diff --git a/test/with2.test b/test/with2.test index 02d10b5112..004ec94b97 100644 --- a/test/with2.test +++ b/test/with2.test @@ -326,7 +326,7 @@ do_catchsql_test 6.5 { do_catchsql_test 6.6 { WITH x AS (SELECT * FROM t1) DELETE FROM t2 WHERE -} {/1 {near .* syntax error}/} +} {1 {incomplete input}} do_catchsql_test 6.7 { WITH x AS (SELECT * FROM t1) DELETE FROM t2 WHRE 1; diff --git a/test/with4.test b/test/with4.test new file mode 100644 index 0000000000..b0eeba6d14 --- /dev/null +++ b/test/with4.test @@ -0,0 +1,52 @@ +# 2018-02-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. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The +# focus of this file is testing the WITH clause in TRIGGERs and VIEWs. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix with4 + +ifcapable {!cte} { + finish_test + return +} + +do_execsql_test 100 { + ATTACH ':memory:' AS aux; + CREATE TABLE main.t1(a,b); + CREATE TABLE aux.t2(x,y); + INSERT INTO t1 VALUES(1,2); + INSERT INTO t2 VALUES(3,4); +} {} +do_catchsql_test 110 { + CREATE VIEW v1 AS SELECT * FROM t1, aux.t2; +} {1 {view v1 cannot reference objects in database aux}} +do_catchsql_test 120 { + CREATE VIEW v2 AS WITH v(m,n) AS (SELECT x,y FROM aux.t2) SELECT * FROM t1, v; +} {1 {view v2 cannot reference objects in database aux}} +do_catchsql_test 130 { + CREATE VIEW v2 AS WITH v(m,n) AS (SELECT 5,?2) SELECT * FROM t1, v; +} {1 {parameters are not allowed in views}} + +do_catchsql_test 200 { + CREATE TRIGGER r1 AFTER INSERT ON t1 BEGIN + WITH v(m,n) AS (SELECT x,y FROM aux.t2) SELECT * FROM t1, v; + END; +} {1 {trigger r1 cannot reference objects in database aux}} +do_catchsql_test 210 { + CREATE TRIGGER r1 AFTER INSERT ON t1 BEGIN + WITH v(m,n) AS (SELECT 5,?2) SELECT * FROM t1, v; + END; +} {1 {trigger cannot use variables}} + +finish_test diff --git a/test/zerodamage.test b/test/zerodamage.test index a87e50b7b5..83bae737df 100644 --- a/test/zerodamage.test +++ b/test/zerodamage.test @@ -74,7 +74,7 @@ do_test zerodamage-2.0 { UPDATE t1 SET y=randomblob(50) WHERE x=123; } concat [file_control_powersafe_overwrite db -1] [set ::max_journal_size] -} {0 1 2576} +} [list 0 1 [expr ([atomic_batch_write test.db]==0)*2576]] # Repeat the previous step with zero-damage turned off. This time the # maximum rollback journal size should be much larger. @@ -87,7 +87,7 @@ do_test zerodamage-2.1 { UPDATE t1 SET y=randomblob(50) WHERE x=124; } concat [file_control_powersafe_overwrite db -1] [set ::max_journal_size] -} {0 0 24704} +} [list 0 0 [expr ([atomic_batch_write test.db]==0)*24704]] if {[wal_is_capable]} { # Run a WAL-mode transaction with POWERSAFE_OVERWRITE on to verify that the diff --git a/test/zipfile.test b/test/zipfile.test new file mode 100644 index 0000000000..2bb3f07892 --- /dev/null +++ b/test/zipfile.test @@ -0,0 +1,640 @@ +# 2017 December 9 +# +# 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 zipfile + +ifcapable !vtab { + finish_test; return +} +if {[catch {load_static_extension db zipfile} error]} { + puts "Skipping zipfile tests, hit load error: $error" + finish_test; return +} + +proc readfile {f} { + set fd [open $f] + fconfigure $fd -translation binary -encoding binary + set data [read $fd] + close $fd + set data +} + +if {$::tcl_platform(platform)=="unix" && [catch {exec unzip}]==0} { + set ::UNZIP 1 + load_static_extension db fileio + proc do_unzip {file} { + forcedelete test_unzip + file mkdir test_unzip + exec unzip -d test_unzip $file + + set res [db eval { + SELECT replace(name,'test_unzip/',''),mode,mtime,data + FROM fsdir('test_unzip') + WHERE name!='test_unzip' + ORDER BY name + }] + set res + } +} + + +# The argument is a blob (not a hex string) containing a zip archive. +# This proc removes the extended timestamp fields from the archive +# and returns the result. +# +proc remove_timestamps {blob} { + set hex [binary encode hex $blob] + set hex [string map {55540500 00000500} $hex] + binary decode hex $hex +} + + +# Argument $file is the name of a zip archive on disk. This function +# executes test cases to check that the results of each of the following +# are the same: +# +# SELECT * FROM zipfile($file) +# SELECT * FROM zipfile( readfile($file) ) +# SELECT * FROM zipfile( +# (SELECT zipfile(name,mode,mtime,data,method) FROM zipfile($file)) +# ) +# +proc do_zipfile_blob_test {tn file} { + + db func r readfile + set q1 {SELECT name,mode,mtime,method,quote(data) FROM zipfile($file)} + set q2 {SELECT name,mode,mtime,method,quote(data) FROM zipfile( r($file) )} + set q3 {SELECT name,mode,mtime,method,quote(data) FROM zipfile( + ( SELECT zipfile(name,mode,mtime,data,method) FROM zipfile($file) ) + )} + + + set r1 [db eval $q1] + set r2 [db eval $q2] + set r3 [db eval $q3] + #puts $r1 + #puts $r2 + #puts $r3 + + uplevel [list do_test $tn.1 [list set {} $r2] $r1] + uplevel [list do_test $tn.2 [list set {} $r3] $r1] +} + +# Argument $file is a zip file on disk. This command runs tests to: +# +# 1. Unpack the archive with unix command [unzip] and compare the +# results to reading the same archive using the zipfile() table +# valued function. +# +# 2. Creates a new archive with the same contents using the zipfile() +# aggregate function as follows: +# +# SELECT writefile('test_unzip.zip', +# ( SELECT zipfile(name,mode,mtime,data,method) FROM zipfile($file) ) +# ); +# +# Then tests that unpacking the new archive using [unzip] produces +# the same results as in (1). +# +proc do_unzip_test {tn file} { + if {[info vars ::UNZIP]==""} { return } + db func sss strip_slash + + db eval { + SELECT writefile('test_unzip.zip', + ( SELECT zipfile(name,mode,mtime,data,method) FROM zipfile($file) ) + ); + } + + set r1 [db eval { + SELECT sss(name),mode,mtime,data FROM zipfile($file) ORDER BY name + }] + set r2 [do_unzip $file] + set r3 [do_unzip test_unzip.zip] + + uplevel [list do_test $tn.1 [list set {} $r2] $r1] + uplevel [list do_test $tn.2 [list set {} $r3] $r1] +} +proc strip_slash {in} { regsub {/$} $in {} } + +proc do_zip_tests {tn file} { + uplevel do_zipfile_blob_test $tn.1 $file + uplevel do_unzip_test $tn.2 $file +} + +forcedelete test.zip +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE temp.zz USING zipfile('test.zip'); + PRAGMA table_info(zz); +} { + 0 name {} 1 {} 1 + 1 mode {} 0 {} 0 + 2 mtime {} 0 {} 0 + 3 sz {} 0 {} 0 + 4 rawdata {} 0 {} 0 + 5 data {} 0 {} 0 + 6 method {} 0 {} 0 +} + +do_catchsql_test 1.1.0.1 { + INSERT INTO zz(name, mode, mtime, sz, rawdata, method) + VALUES('f.txt', '-rw-r--r--', 1000000000, 5, 'abcde', 0); +} {1 {constraint failed}} +do_catchsql_test 1.1.0.2 { + INSERT INTO zz(name, mtime, sz, data, method) + VALUES('g.txt', 1000000002, 5, '12345', 0); +} {1 {constraint failed}} +do_catchsql_test 1.1.0.3 { + INSERT INTO zz(name, mtime, rawdata, method) + VALUES('g.txt', 1000000002, '12345', 0); +} {1 {constraint failed}} +do_catchsql_test 1.1.0.4 { + INSERT INTO zz(name, data, method) + VALUES('g.txt', '12345', 7); +} {1 {constraint failed}} + +do_execsql_test 1.1.1 { + INSERT INTO zz(name, mode, mtime, data, method) + VALUES('f.txt', '-rw-r--r--', 1000000000, 'abcde', 0); +} +do_execsql_test 1.1.2 { + INSERT INTO zz(name, mode, mtime, data, method) + VALUES('g.txt', NULL, 1000000002, '12345', 0); +} + +do_execsql_test 1.2 { + SELECT name, mtime, data FROM zipfile('test.zip') +} { + f.txt 1000000000 abcde + g.txt 1000000002 12345 +} +do_zip_tests 1.2a test.zip + +do_execsql_test 1.3 { + INSERT INTO zz(name, mode, mtime, data) VALUES('h.txt', + '-rw-r--r--', 1000000004, 'aaaaaaaaaabbbbbbbbbb' + ); +} +do_zip_tests 1.3a test.zip + +do_execsql_test 1.4 { + SELECT name, mtime, data, method FROM zipfile('test.zip'); +} { + f.txt 1000000000 abcde 0 + g.txt 1000000002 12345 0 + h.txt 1000000004 aaaaaaaaaabbbbbbbbbb 8 +} + +ifcapable json1 { + do_execsql_test 1.4.1 { + SELECT name, json_extract( zipfile_cds(z) , '$.crc32')!=0 + FROM zipfile('test.zip'); + } { + f.txt 1 + g.txt 1 + h.txt 1 + } +} +do_catchsql_test 1.4.2 { + SELECT zipfile_cds(mode) FROM zipfile('test.zip'); +} {0 {{} {} {}}} + +do_execsql_test 1.5.1 { + BEGIN; + INSERT INTO zz(name, mode, mtime, data, method) + VALUES('i.txt', '-rw-r--r--', 1000000006, 'zxcvb', 0); + SELECT name FROM zz; + COMMIT; +} {f.txt g.txt h.txt i.txt} +do_execsql_test 1.5.2 { + SELECT name FROM zz; +} {f.txt g.txt h.txt i.txt} +do_execsql_test 1.5.3 { + SELECT data FROM zz WHERE name='i.txt'; +} {zxcvb} + +do_execsql_test 1.6.0 { + DELETE FROM zz WHERE name='g.txt'; + SELECT name FROM zz; +} {f.txt h.txt i.txt} + +do_execsql_test 1.6.1 { + SELECT name, mode, mtime, data, method FROM zipfile('test.zip'); +} { + f.txt 33188 1000000000 abcde 0 + h.txt 33188 1000000004 aaaaaaaaaabbbbbbbbbb 8 + i.txt 33188 1000000006 zxcvb 0 +} +do_zip_tests 1.6.1a test.zip + +do_execsql_test 1.6.2 { + UPDATE zz SET mtime=4 WHERE name='i.txt'; + SELECT name, mode, mtime, data, method FROM zipfile('test.zip'); +} { + f.txt 33188 1000000000 abcde 0 + h.txt 33188 1000000004 aaaaaaaaaabbbbbbbbbb 8 + i.txt 33188 4 zxcvb 0 +} + +do_execsql_test 1.6.3 { + UPDATE zz SET mode='-rw-r--r-x' WHERE name='h.txt'; + SELECT name, mode, mtime, data, method FROM zipfile('test.zip'); +} { + f.txt 33188 1000000000 abcde 0 + h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8 + i.txt 33188 4 zxcvb 0 +} +do_zip_tests 1.6.3a test.zip + +do_execsql_test 1.6.4 { + UPDATE zz SET name = 'blue.txt' WHERE name='f.txt'; + SELECT name, mode, mtime, data, method FROM zipfile('test.zip'); +} { + blue.txt 33188 1000000000 abcde 0 + h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8 + i.txt 33188 4 zxcvb 0 +} +do_zip_tests 1.6.4a test.zip + +do_execsql_test 1.6.5 { + UPDATE zz SET data = 'edcba' WHERE name='blue.txt'; + SELECT name, mode, mtime, data, method FROM zipfile('test.zip'); +} { + blue.txt 33188 1000000000 edcba 0 + h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8 + i.txt 33188 4 zxcvb 0 +} + +do_execsql_test 1.6.6 { + UPDATE zz SET mode=NULL, data = NULL WHERE name='blue.txt'; + SELECT name, mode, mtime, data, method FROM zipfile('test.zip'); +} { + blue.txt/ 16877 1000000000 {} 0 + h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8 + i.txt 33188 4 zxcvb 0 +} + +do_catchsql_test 1.6.7 { + UPDATE zz SET data=NULL WHERE name='i.txt' +} {1 {constraint failed}} +do_execsql_test 1.6.8 { + SELECT name, mode, mtime, data, method FROM zipfile('test.zip'); +} { + blue.txt/ 16877 1000000000 {} 0 + h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8 + i.txt 33188 4 zxcvb 0 +} + +do_execsql_test 1.6.8 { + UPDATE zz SET data = '' WHERE name='i.txt'; + SELECT name,mode,mtime,data,method from zipfile('test.zip'); +} { + blue.txt/ 16877 1000000000 {} 0 + h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8 + i.txt 33188 4 {} 0 +} + +do_execsql_test 1.6.9 { + SELECT a.name, a.data + FROM zz AS a, zz AS b + WHERE a.name=+b.name AND +a.mode=b.mode +} { + blue.txt/ {} + h.txt aaaaaaaaaabbbbbbbbbb + i.txt {} +} + +do_execsql_test 1.6.10 { + SELECT name, data FROM zz WHERE name LIKE '%txt' +} { + h.txt aaaaaaaaaabbbbbbbbbb + i.txt {} +} + +do_execsql_test 1.7 { + DELETE FROM zz; + SELECT * FROM zz; +} {} + +#------------------------------------------------------------------------- +db close +forcedelete test.zip +reset_db +load_static_extension db fileio +load_static_extension db zipfile +do_execsql_test 2.1 { + CREATE VIRTUAL TABLE zzz USING zipfile('test.zip'); + INSERT INTO zzz(name, mode) VALUES('dirname', 'drwxr-xr-x'); + SELECT name, mode, data FROM zzz; +} {dirname/ 16877 {}} +do_execsql_test 2.2 { + INSERT INTO zzz(name, data) VALUES('dirname2', NULL); + INSERT INTO zzz(name, data) VALUES('dirname2/file1.txt', 'abcdefghijklmnop'); + SELECT name, mode, data FROM zzz; +} { + dirname/ 16877 {} + dirname2/ 16877 {} + dirname2/file1.txt 33188 abcdefghijklmnop +} + +do_catchsql_test 2.3 { + UPDATE zzz SET name = 'dirname3' WHERE name = 'dirname/'; +} {0 {}} +do_execsql_test 2.4 { + SELECT name, mode, data FROM zzz; +} { + dirname3/ 16877 {} + dirname2/ 16877 {} + dirname2/file1.txt 33188 abcdefghijklmnop +} +do_zip_tests 2.4a test.zip + +# If on unix, check that the [unzip] utility can unpack our archive. +# +if {$::tcl_platform(platform)=="unix"} { + do_test 2.5.1 { + forcedelete dirname + forcedelete dirname2 + set rc [catch { exec unzip test.zip > /dev/null } msg] + list $rc $msg + } {0 {}} + do_test 2.5.2 { file isdir dirname3 } 1 + do_test 2.5.3 { file isdir dirname2 } 1 + do_test 2.5.4 { file isdir dirname2/file1.txt } 0 + do_test 2.5.5 { + set fd [open dirname2/file1.txt] + set data [read $fd] + close $fd + set data + } {abcdefghijklmnop} +} + +#------------------------------------------------------------------------- +reset_db +forcedelete test.zip +load_static_extension db zipfile + +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE temp.x1 USING zipfile('test.zip'); + INSERT INTO x1(name, data) VALUES('dir1/', NULL); + INSERT INTO x1(name, data) VALUES('file1', '1234'); + INSERT INTO x1(name, data) VALUES('dir1/file2', '5678'); +} +foreach {tn fname} { + 1 dir1 + 2 file1 + 3 dir1/file2 +} { + do_catchsql_test 3.1.$tn.0 { + INSERT INTO x1(name, data) VALUES($fname, NULL); + } {1 {constraint failed}} + do_catchsql_test 3.1.$tn.1 { + INSERT INTO x1(name, data) VALUES($fname || '/', NULL); + } {1 {constraint failed}} + do_catchsql_test 3.1.$tn.2 { + INSERT INTO x1(name, data) VALUES($fname, 'abcd'); + } {1 {constraint failed}} +} + +do_catchsql_test 3.2 { + SELECT rowid FROM x1 +} {1 {no such column: rowid}} + +#------------------------------------------------------------------------- +# Test some error conditions. +# +do_catchsql_test 4.1 { + CREATE VIRTUAL TABLE yyy USING zipfile(); +} {1 {zipfile constructor requires one argument}} +do_catchsql_test 4.2 { + CREATE VIRTUAL TABLE yyy USING zipfile('test.zip', 'test.zip'); +} {1 {zipfile constructor requires one argument}} + +do_catchsql_test 4.3 { + SELECT * FROM zipfile() +} {1 {zipfile() function requires an argument}} + +do_catchsql_test 4.4 { + SELECT * FROM zipfile('/path/that/does/not/exist') +} {1 {cannot open file: /path/that/does/not/exist}} + +foreach {tn mode} { + 1 abcd + 2 brwxrwxrwx + 3 lrwxrrxrwx +} { + do_catchsql_test 4.5.$tn { + WITH m(m) AS ( SELECT $mode) + SELECT zipfile('a.txt', m, 1000, 'xyz') FROM m + } [list 1 "zipfile: parse error in mode: $mode"] +} + +do_catchsql_test 4.6 { + WITH c(name,data) AS ( SELECT 'a.txt', 'abc') + SELECT zipfile(name) FROM c +} {1 {wrong number of arguments to function zipfile()}} + +do_catchsql_test 4.7 { + WITH c(name,data) AS ( + SELECT 'a.txt', 'abc' UNION ALL + SELECT NULL, 'def' + ) + SELECT zipfile(name,data) FROM c +} {1 {first argument to zipfile() must be non-NULL}} + +do_catchsql_test 4.7 { + WITH c(name,data,method) AS ( + SELECT 'a.txt', 'abc', 0 + UNION SELECT 'b.txt', 'def', 8 + UNION SELECT 'c.txt', 'ghi', 16 + ) + SELECT zipfile(name,NULL,NULL,data,method) FROM c +} {1 {illegal method value: 16}} + +do_catchsql_test 4.8 { + WITH c(name,data) AS ( + SELECT 'a.txt', 'abc' + UNION SELECT 'b.txt', 'def' + UNION SELECT 'c.txt/', 'ghi' + ) + SELECT zipfile(name,NULL,NULL,data) FROM c +} {1 {non-directory name must not end with /}} + +#-------------------------------------------------------------------------- + +db func rt remove_timestamps +do_execsql_test 5.0 { + WITH c(name,mtime,data) AS ( + SELECT 'a.txt', 946684800, 'abc' + ) + SELECT name,mtime,data FROM zipfile( + ( SELECT rt( zipfile(name,NULL,mtime,data,NULL) ) FROM c ) + ) +} { + a.txt 946684800 abc +} + +if {[info vars ::UNZIP]!=""} { +ifcapable datetime { + load_static_extension db fileio + forcedelete test1.zip test2.zip + do_test 6.0 { + execsql { + WITH c(name,mtime,data) AS ( + SELECT 'a.txt', 946684800, 'abc' UNION ALL + SELECT 'b.txt', 1000000000, 'abc' UNION ALL + SELECT 'c.txt', 1111111000, 'abc' + ) + SELECT writefile('test1.zip', rt( zipfile(name, NULL, mtime, data) ) ), + writefile('test2.zip', ( zipfile(name, NULL, mtime, data) ) ) + FROM c; + } + forcedelete test_unzip + file mkdir test_unzip + exec unzip -d test_unzip test1.zip + + db eval { + SELECT name, strftime('%s', mtime, 'unixepoch', 'localtime') + FROM fsdir('test_unzip') WHERE name!='test_unzip' + ORDER BY name + } + } [list {*}{ + test_unzip/a.txt 946684800 + test_unzip/b.txt 1000000000 + test_unzip/c.txt 1111111000 + }] + + do_execsql_test 6.1 { + SELECT name, mtime, data FROM zipfile('test1.zip') + } { + a.txt 946684800 abc + b.txt 1000000000 abc + c.txt 1111111000 abc + } + + do_test 6.2 { + forcedelete test_unzip + file mkdir test_unzip + exec unzip -d test_unzip test2.zip + + db eval { + SELECT name, mtime + FROM fsdir('test_unzip') WHERE name!='test_unzip' + ORDER BY name + } + } [list {*}{ + test_unzip/a.txt 946684800 + test_unzip/b.txt 1000000000 + test_unzip/c.txt 1111111000 + }] + + do_execsql_test 6.3 { + SELECT name, mtime, sz, rawdata, data FROM zipfile('test2.zip') + } { + a.txt 946684800 3 abc abc + b.txt 1000000000 3 abc abc + c.txt 1111111000 3 abc abc + } +} +} + +#------------------------------------------------------------------------- +# Force an IO error by truncating the zip archive to zero bytes in size +# while it is being read. +forcedelete test.zip +do_test 7.0 { + execsql { + WITH c(name,data) AS ( + SELECT '1', randomblob(1000000) UNION ALL + SELECT '2', randomblob(1000000) UNION ALL + SELECT '3', randomblob(1000000) + ) + SELECT writefile('test.zip', zipfile(name, data) ) FROM c; + } + + list [catch { + db eval { SELECT name, data FROM zipfile('test.zip') } { + if {$name==2} { close [open test.zip w+] } + } + } msg] $msg +} {1 {error in fread()}} + +forcedelete test.zip +do_execsql_test 8.0.1 { + CREATE VIRTUAL TABLE zz USING zipfile('test.zip'); + BEGIN; + INSERT INTO zz(name, data) VALUES('a.txt', '1'); + INSERT INTO zz(name, data) VALUES('b.txt', '2'); + INSERT INTO zz(name, data) VALUES('c.txt', '1'); + INSERT INTO zz(name, data) VALUES('d.txt', '2'); + SELECT name, data FROM zz; +} { + a.txt 1 b.txt 2 c.txt 1 d.txt 2 +} +do_test 8.0.2 { + db eval { SELECT name, data FROM zz } { + if { $data=="2" } { db eval { DELETE FROM zz WHERE name=$name } } + } + execsql { SELECT name, data FROM zz } +} {a.txt 1 c.txt 1} +do_test 8.0.3 { + db eval { SELECT name, data FROM zz } { + db eval { DELETE FROM zz WHERE name=$name } + } + execsql { SELECT name, data FROM zz } +} {} +execsql COMMIT + +do_execsql_test 8.1.1 { + CREATE VIRTUAL TABLE nogood USING zipfile('test_unzip'); +} +do_catchsql_test 8.1.2 { + INSERT INTO nogood(name, data) VALUES('abc', 'def'); +} {1 {zipfile: failed to open file test_unzip for writing}} + +do_execsql_test 8.2.1 { + DROP TABLE nogood; + BEGIN; + CREATE VIRTUAL TABLE nogood USING zipfile('test_unzip'); +} +do_catchsql_test 8.2.2 { + INSERT INTO nogood(name, data) VALUES('abc', 'def'); +} {1 {zipfile: failed to open file test_unzip for writing}} +do_execsql_test 8.2.3 { + COMMIT; +} + +forcedelete test.zip +do_execsql_test 8.3.1 { + BEGIN; + CREATE VIRTUAL TABLE ok USING zipfile('test.zip'); + INSERT INTO ok(name, data) VALUES ('sqlite3', 'elf'); + COMMIT; +} + +#------------------------------------------------------------------------- +# Test that the zipfile aggregate correctly adds and removes "/" from +# the ends of directory file names. +do_execsql_test 9.0 { + WITH src(nm) AS ( + VALUES('dir1') UNION ALL + VALUES('dir2/') UNION ALL + VALUES('dir3//') UNION ALL + VALUES('dir4///') UNION ALL + VALUES('/') + ) + SELECT name FROM zipfile((SELECT zipfile(nm, NULL) FROM src)) +} {dir1/ dir2/ dir3/ dir4/ /} +finish_test + diff --git a/test/zipfile2.test b/test/zipfile2.test new file mode 100644 index 0000000000..f3509d0a54 --- /dev/null +++ b/test/zipfile2.test @@ -0,0 +1,208 @@ +# 2018 January 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix zipfile2 + +ifcapable !vtab { + finish_test; return +} +if {[catch {load_static_extension db zipfile} error]} { + puts "Skipping zipfile2 tests, hit load error: $error" + finish_test; return +} + +proc blobliteral {str} { + set concat [string map {" " "" "\n" ""} $str] + return "X'$concat'" +} + +proc blob {str} { + binary decode hex $str +} + +proc findall {needle haystack} { + set L [list] + set start 0 + while { [set idx [string first $needle $haystack $start]]>=0 } { + lappend L $idx + set start [expr $idx+1] + } + set L +} + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE aaa USING zipfile('testzip'); + CREATE VIRTUAL TABLE bbb USING zipfile("testzip"); + CREATE VIRTUAL TABLE ccc USING zipfile(`testzip`); + CREATE VIRTUAL TABLE ddd USING zipfile([testzip]); + CREATE VIRTUAL TABLE eee USING zipfile(testzip); + CREATE VIRTUAL TABLE fff USING zipfile('test''zip'); +} + +do_test 2.0 { + forcedelete testdir + file mkdir testdir + execsql { CREATE VIRTUAL TABLE hhh USING zipfile('testdir') } + catchsql { SELECT * FROM hhh } +} {1 {error in fread()}} + + +set archive { + 504B0304140000080000D4A52BEC09F3B6E0110000001100000005000900612E + 747874555405000140420F00636F6E74656E7473206F6620612E747874504B03 + 04140000080000D4A52BECD98916A7110000001100000005000900622E747874 + 555405000140420F00636F6E74656E7473206F6620622E747874504B01021E03 + 140000080000D4A52BEC09F3B6E0110000001100000005000900000000000000 + 0000A48100000000612E747874555405000140420F00504B01021E0314000008 + 0000D4A52BECD98916A71100000011000000050009000000000000000000A481 + 3D000000622E747874555405000140420F00504B050600000000020002007800 + 00007A0000000000 +} + +if 0 { + # This test is broken - the archive generated is slightly different + # depending on the zlib version used. + do_execsql_test 3.1 { + WITH contents(name,mtime,data) AS ( + VALUES('a.txt', 1000000, 'contents of a.txt') UNION ALL + VALUES('b.txt', 1000000, 'contents of b.txt') + ) SELECT quote( zipfile(name,NULL,mtime,data) ) FROM contents; + } [blobliteral $archive] +} + + +set blob [blob $archive] +do_execsql_test 3.2 { + SELECT name,mtime,data FROM zipfile($blob) +} { + a.txt 1000000 {contents of a.txt} + b.txt 1000000 {contents of b.txt} +} + +# Corrupt each of the 0x50 0x4B (ascii "PK") headers in the file +# Test that in each case this causes an error. +# +set L [findall 504B $archive] +for {set i 0} {$i < [llength $L]} {incr i} { + set idx [lindex $L $i] + set a [string replace $archive $idx [expr $idx+3] 0000] + set blob [blob $a] + do_catchsql_test 3.3.$i { + SELECT name,mtime,data FROM zipfile($blob) + } {/1 .*/} +} + +# Change the "extra info id" for all extended-timestamp fields. +set L [findall 5554 $archive] +for {set i 0} {$i < [llength $L]} {incr i} { + set idx [lindex $L $i] + set a [string replace $archive $idx [expr $idx+3] 1234] + set blob [blob $a] + do_execsql_test 3.4.$i { + SELECT name,data FROM zipfile($blob) + } { + a.txt {contents of a.txt} + b.txt {contents of b.txt} + } +} + +for {set i 0} {$i < [llength $L]} {incr i} { + set idx [lindex $L $i] + set a [string replace $archive [expr $idx+8] [expr $idx+9] 00] + set blob [blob $a] + do_execsql_test 3.5.$i { + SELECT name,data FROM zipfile($blob) + } { + a.txt {contents of a.txt} + b.txt {contents of b.txt} + } +} + +# set blob [db one { +# WITH contents(name,mtime,data) AS ( +# VALUES('a.txt', 1000000, 'aaaaaaaaaaaaaaaaaaaaaaa') +# ) SELECT quote( zipfile(name,NULL,mtime,data) ) FROM contents; +# }] +# set blob [string range $blob 2 end] +# set blob [string range $blob 0 end-1] +# while {[string length $blob]>0} { +# puts [string range $blob 0 63] +# set blob [string range $blob 64 end] +# } +# exit + +set archive2 { + 504B0304140000080800D4A52BEC08F54C6E050000001700000005000900612E + 747874555405000140420F004B4CC40A00504B01021E03140000080800D4A52B + EC08F54C6E0500000017000000050009000000000000000000A4810000000061 + 2E747874555405000140420F00504B050600000000010001003C000000310000 + 000000 +} +set blob [blob $archive2] +do_execsql_test 4.0 { + SELECT name,mtime,data,method FROM zipfile($blob) +} { + a.txt 1000000 aaaaaaaaaaaaaaaaaaaaaaa 8 +} + +set L [findall 17000000 $archive2] +set a $archive2 +foreach i $L { set a [string replace $a $i [expr $i+7] 16000000] } +set blob [blob $a] +do_catchsql_test 4.1 { + SELECT name,mtime,data,method FROM zipfile($blob) +} {1 {inflate() failed (0)}} + +# Check the response to an unknown compression method (set data to NULL). +set blob [blob [string map {0800 0900} $archive2]] +do_execsql_test 4.2 { + SELECT name,mtime,data IS NULL,method FROM zipfile($blob) +} {a.txt 1000000 1 9} + +# Corrupt the EOCDS signature bytes in various ways. +foreach {tn sub} { + 1 {504B0500} + 2 {504B0006} + 3 {50000506} + 4 {004B0506} +} { + set blob [blob [string map [list 504B0506 $sub] $archive2]] + do_catchsql_test 4.3.$tn { + SELECT * FROM zipfile($blob) + } {1 {cannot find end of central directory record}} +} + +#------------------------------------------------------------------------- +# Test that a zero-length file with a '/' at the end is treated as +# a directory (data IS NULL). Even if the mode doesn't indicate +# that it is a directory. + +do_test 5.0 { + set blob [db one { + WITH c(n, d) AS ( + SELECT 'notadir', '' + ) + SELECT zipfile(n, d) FROM c + }] + + set hex [binary encode hex $blob] + set hex [string map {6e6f7461646972 6e6f746164692f} $hex] + set blob2 [binary decode hex $hex] + + execsql { SELECT name, data IS NULL FROM zipfile($blob2) } +} {notadi/ 1} + + +finish_test + diff --git a/test/zipfilefault.test b/test/zipfilefault.test new file mode 100644 index 0000000000..158370695e --- /dev/null +++ b/test/zipfilefault.test @@ -0,0 +1,165 @@ +# 2018 January 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/malloc_common.tcl +set testprefix zipfilefault + +ifcapable !vtab { + finish_test; return +} +if {[catch {load_static_extension db zipfile} error]} { + puts "Skipping zipfile2 tests, hit load error: $error" + finish_test; return +} + +faultsim_save_and_close +do_faultsim_test 1 -prep { + faultsim_restore_and_reopen + load_static_extension db zipfile + execsql { DROP TABLE IF EXISTS aaa } +} -body { + execsql { CREATE VIRTUAL TABLE aaa USING zipfile('test.zip') } +} -test { + faultsim_test_result {0 {}} +} + +forcedelete test.zip +sqlite3 db test.db +load_static_extension db zipfile +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE setup USING zipfile('test.zip'); + INSERT INTO setup(name, data) VALUES('a.txt', '1234567890'); +} + +do_faultsim_test 2.1 -faults oom* -body { + execsql { SELECT name,data FROM zipfile('test.zip') } +} -test { + faultsim_test_result {0 {a.txt 1234567890}} +} +do_faultsim_test 2.2 -faults oom* -body { + execsql { + SELECT json_extract( zipfile_cds(z), '$.version-made-by' ) + FROM zipfile('test.zip') + } +} -test { + faultsim_test_result {0 798} +} + +forcedelete test.zip +reset_db +load_static_extension db zipfile +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE setup USING zipfile('test.zip'); + INSERT INTO setup(name, data) VALUES('a.txt', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaa'); +} + +do_faultsim_test 3 -faults oom* -body { + execsql { SELECT name,data FROM zipfile('test.zip') } +} -test { + faultsim_test_result {0 {a.txt aaaaaaaaaaaaaaaaaaaaaaaaaaaa}} +} + +do_faultsim_test 4 -faults oom* -body { + execsql { + WITH c(n, d) AS ( + SELECT 1, 'aaaaaaaaaaabbbbbbbbbbaaaaaaaaaabbbbbbbbbb' + ) + SELECT name, data FROM zipfile( + (SELECT zipfile(n, d) FROM c) + ); + } +} -test { + faultsim_test_result {0 {1 aaaaaaaaaaabbbbbbbbbbaaaaaaaaaabbbbbbbbbb}} +} + +reset_db +sqlite3_db_config_lookaside db 0 0 0 +load_static_extension db zipfile + +do_execsql_test 5.0 { + CREATE VIRTUAL TABLE setup USING zipfile('test.zip') +} + +do_faultsim_test 5.1 -faults oom* -prep { + forcedelete test.zip +} -body { + execsql { + INSERT INTO setup(name, data) + VALUES('a.txt', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaa'); + } +} -test { + faultsim_test_result {0 {}} +} + +do_faultsim_test 5.2 -faults oom* -prep { + forcedelete test.zip +} -body { + execsql { + INSERT INTO setup(name, data) VALUES('dir', NULL) + } +} -test { + faultsim_test_result {0 {}} +} + +do_faultsim_test 5.3 -faults oom* -prep { + forcedelete test.zip + execsql { + DROP TABLE IF EXISTS setup; + BEGIN; + CREATE VIRTUAL TABLE setup USING zipfile('test.zip') + } +} -body { + execsql { + INSERT INTO setup(name, data) VALUES('dir', NULL) + } +} -test { + catchsql { COMMIT } + faultsim_test_result {0 {}} +} + +do_faultsim_test 6.1 -faults oom* -body { + execsql { + WITH c(n, d) AS ( + VALUES('a.txt', '1234567890') UNION ALL + VALUES('dir', NULL) + ) + SELECT zipfile(n, d) IS NULL FROM c; + } +} -test { + faultsim_test_result {0 0} +} + +set big [string repeat 0123456789 1000] +do_faultsim_test 6.2 -faults oom* -body { + execsql { + WITH c(n, d) AS ( + VALUES('a.txt', $big) + ) + SELECT zipfile(n, NULL, NULL, d, 0) IS NULL FROM c; + } +} -test { + faultsim_test_result {0 0} +} + +do_faultsim_test 7.0 -faults oom* -prep { + catch { db close } + sqlite3 db "" +} -body { + load_static_extension db zipfile +} -test { +} + + +finish_test + diff --git a/tool/addopcodes.tcl b/tool/addopcodes.tcl index d5f843616a..798fbb15ba 100644 --- a/tool/addopcodes.tcl +++ b/tool/addopcodes.tcl @@ -22,6 +22,7 @@ close $in # ILLEGAL *must* be the last two token codes and they must be in that order. # set extras { + TRUEFALSE ISNOT FUNCTION COLUMN @@ -29,6 +30,7 @@ set extras { AGG_COLUMN UMINUS UPLUS + TRUTH REGISTER CONCURRENT VECTOR diff --git a/tool/lemon.c b/tool/lemon.c index acc5450c99..96bbed7473 100644 --- a/tool/lemon.c +++ b/tool/lemon.c @@ -384,6 +384,12 @@ struct lemon { int nrule; /* Number of rules */ int nsymbol; /* Number of terminal and nonterminal symbols */ int nterminal; /* Number of terminal symbols */ + int minShiftReduce; /* Minimum shift-reduce action value */ + int errAction; /* Error action value */ + int accAction; /* Accept action value */ + int noAction; /* No-op action value */ + int minReduce; /* Minimum reduce action */ + int maxAction; /* Maximum action value of any kind */ struct symbol **symbols; /* Sorted array of pointers to symbols */ int errorcnt; /* Number of errors */ struct symbol *errsym; /* The error symbol */ @@ -407,6 +413,7 @@ struct lemon { char *tokenprefix; /* A prefix added to token names in the .h file */ int nconflict; /* Number of parsing conflicts */ int nactiontab; /* Number of entries in the yy_action[] table */ + int nlookaheadtab; /* Number of entries in yy_lookahead[] */ int tablesize; /* Total table size of all tables in bytes */ int basisflag; /* Print only basis configurations */ int has_fallback; /* True if any %fallback is seen in the grammar */ @@ -583,10 +590,12 @@ struct acttab { int mxLookahead; /* Maximum aLookahead[].lookahead */ int nLookahead; /* Used slots in aLookahead[] */ int nLookaheadAlloc; /* Slots allocated in aLookahead[] */ + int nterminal; /* Number of terminal symbols */ + int nsymbol; /* total number of symbols */ }; /* Return the number of entries in the yy_action table */ -#define acttab_size(X) ((X)->nAction) +#define acttab_lookahead_size(X) ((X)->nAction) /* The value for the N-th entry in yy_action */ #define acttab_yyaction(X,N) ((X)->aAction[N].action) @@ -602,13 +611,15 @@ void acttab_free(acttab *p){ } /* Allocate a new acttab structure */ -acttab *acttab_alloc(void){ +acttab *acttab_alloc(int nsymbol, int nterminal){ acttab *p = (acttab *) calloc( 1, sizeof(*p) ); if( p==0 ){ fprintf(stderr,"Unable to allocate memory for a new acttab."); exit(1); } memset(p, 0, sizeof(*p)); + p->nsymbol = nsymbol; + p->nterminal = nterminal; return p; } @@ -649,16 +660,24 @@ void acttab_action(acttab *p, int lookahead, int action){ ** to an empty set in preparation for a new round of acttab_action() calls. ** ** Return the offset into the action table of the new transaction. +** +** If the makeItSafe parameter is true, then the offset is chosen so that +** it is impossible to overread the yy_lookaside[] table regardless of +** the lookaside token. This is done for the terminal symbols, as they +** come from external inputs and can contain syntax errors. When makeItSafe +** is false, there is more flexibility in selecting offsets, resulting in +** a smaller table. For non-terminal symbols, which are never syntax errors, +** makeItSafe can be false. */ -int acttab_insert(acttab *p){ - int i, j, k, n; +int acttab_insert(acttab *p, int makeItSafe){ + int i, j, k, n, end; assert( p->nLookahead>0 ); /* Make sure we have enough space to hold the expanded action table ** in the worst case. The worst case occurs if the transaction set ** must be appended to the current action table */ - n = p->mxLookahead + 1; + n = p->nsymbol + 1; if( p->nAction + n >= p->nActionAlloc ){ int oldAlloc = p->nActionAlloc; p->nActionAlloc = p->nAction + n + p->nActionAlloc + 20; @@ -680,7 +699,8 @@ int acttab_insert(acttab *p){ ** ** i is the index in p->aAction[] where p->mnLookahead is inserted. */ - for(i=p->nAction-1; i>=0; i--){ + end = makeItSafe ? p->mnLookahead : 0; + for(i=p->nAction-1; i>=end; i--){ if( p->aAction[i].lookahead==p->mnLookahead ){ /* All lookaheads and actions in the aLookahead[] transaction ** must match against the candidate aAction[i] entry. */ @@ -710,12 +730,13 @@ int acttab_insert(acttab *p){ ** an empty offset in the aAction[] table in which we can add the ** aLookahead[] transaction. */ - if( i<0 ){ + if( inAction, which means the ** transaction will be appended. */ - for(i=0; inActionAlloc - p->mxLookahead; i++){ + i = makeItSafe ? p->mnLookahead : 0; + for(; inActionAlloc - p->mxLookahead; i++){ if( p->aAction[i].lookahead<0 ){ for(j=0; jnLookahead; j++){ k = p->aLookahead[j].lookahead - p->mnLookahead + i; @@ -733,11 +754,19 @@ int acttab_insert(acttab *p){ } } /* Insert transaction set at index i. */ +#if 0 + printf("Acttab:"); + for(j=0; jnLookahead; j++){ + printf(" %d", p->aLookahead[j].lookahead); + } + printf(" inserted at %d\n", i); +#endif for(j=0; jnLookahead; j++){ k = p->aLookahead[j].lookahead - p->mnLookahead + i; p->aAction[k] = p->aLookahead[j]; if( k>=p->nAction ) p->nAction = k+1; } + if( makeItSafe && i+p->nterminal>=p->nAction ) p->nAction = i+p->nterminal+1; p->nLookahead = 0; /* Return the offset that is added to the lookahead in order to get the @@ -745,6 +774,16 @@ int acttab_insert(acttab *p){ return i - p->mnLookahead; } +/* +** Return the size of the action table without the trailing syntax error +** entries. +*/ +int acttab_action_size(acttab *p){ + int n = p->nAction; + while( n>0 && p->aAction[n-1].lookahead<0 ){ n--; } + return n; +} + /********************** From the file "build.c" *****************************/ /* ** Routines to construction the finite state machine for the LEMON @@ -1718,6 +1757,7 @@ int main(int argc, char **argv) stats_line("states", lem.nxstate); stats_line("conflicts", lem.nconflict); stats_line("action table entries", lem.nactiontab); + stats_line("lookahead table entries", lem.nlookaheadtab); stats_line("total table size (bytes)", lem.tablesize); } if( lem.nconflict > 0 ){ @@ -3020,6 +3060,27 @@ PRIVATE FILE *file_open( return fp; } +/* Print the text of a rule +*/ +void rule_print(FILE *out, struct rule *rp){ + int i, j; + fprintf(out, "%s",rp->lhs->name); + /* if( rp->lhsalias ) fprintf(out,"(%s)",rp->lhsalias); */ + fprintf(out," ::="); + for(i=0; inrhs; i++){ + struct symbol *sp = rp->rhs[i]; + if( sp->type==MULTITERMINAL ){ + fprintf(out," %s", sp->subsym[0]->name); + for(j=1; jnsubsym; j++){ + fprintf(out,"|%s", sp->subsym[j]->name); + } + }else{ + fprintf(out," %s", sp->name); + } + /* if( rp->rhsalias[i] ) fprintf(out,"(%s)",rp->rhsalias[i]); */ + } +} + /* Duplicate the input file without comments and without actions ** on rules */ void Reprint(struct lemon *lemp) @@ -3047,21 +3108,7 @@ void Reprint(struct lemon *lemp) printf("\n"); } for(rp=lemp->rule; rp; rp=rp->next){ - printf("%s",rp->lhs->name); - /* if( rp->lhsalias ) printf("(%s)",rp->lhsalias); */ - printf(" ::="); - for(i=0; inrhs; i++){ - sp = rp->rhs[i]; - if( sp->type==MULTITERMINAL ){ - printf(" %s", sp->subsym[0]->name); - for(j=1; jnsubsym; j++){ - printf("|%s", sp->subsym[j]->name); - } - }else{ - printf(" %s", sp->name); - } - /* if( rp->rhsalias[i] ) printf("(%s)",rp->rhsalias[i]); */ - } + rule_print(stdout, rp); printf("."); if( rp->precsym ) printf(" [%s]",rp->precsym->name); /* if( rp->code ) printf("\n %s",rp->code); */ @@ -3321,16 +3368,19 @@ PRIVATE int compute_action(struct lemon *lemp, struct action *ap) switch( ap->type ){ case SHIFT: act = ap->x.stp->statenum; break; case SHIFTREDUCE: { - act = ap->x.rp->iRule + lemp->nstate; /* Since a SHIFT is inherient after a prior REDUCE, convert any ** SHIFTREDUCE action with a nonterminal on the LHS into a simple ** REDUCE action: */ - if( ap->sp->index>=lemp->nterminal ) act += lemp->nrule; + if( ap->sp->index>=lemp->nterminal ){ + act = lemp->minReduce + ap->x.rp->iRule; + }else{ + act = lemp->minShiftReduce + ap->x.rp->iRule; + } break; } - case REDUCE: act = ap->x.rp->iRule + lemp->nstate+lemp->nrule; break; - case ERROR: act = lemp->nstate + lemp->nrule*2; break; - case ACCEPT: act = lemp->nstate + lemp->nrule*2 + 1; break; + case REDUCE: act = lemp->minReduce + ap->x.rp->iRule; break; + case ERROR: act = lemp->errAction; break; + case ACCEPT: act = lemp->accAction; break; default: act = -1; break; } return act; @@ -4038,6 +4088,13 @@ void ReportTable( int mnNtOfst, mxNtOfst; struct axset *ax; + lemp->minShiftReduce = lemp->nstate; + lemp->errAction = lemp->minShiftReduce + lemp->nrule; + lemp->accAction = lemp->errAction + 1; + lemp->noAction = lemp->accAction + 1; + lemp->minReduce = lemp->noAction + 1; + lemp->maxAction = lemp->minReduce + lemp->nrule; + in = tplt_open(lemp); if( in==0 ) return; out = file_open(lemp,".c","wb"); @@ -4076,7 +4133,7 @@ void ReportTable( minimum_size_type(0, lemp->nsymbol+1, &szCodeType)); lineno++; fprintf(out,"#define YYNOCODE %d\n",lemp->nsymbol+1); lineno++; fprintf(out,"#define YYACTIONTYPE %s\n", - minimum_size_type(0,lemp->nstate+lemp->nrule*2+5,&szActionType)); lineno++; + minimum_size_type(0,lemp->maxAction,&szActionType)); lineno++; if( lemp->wildcard ){ fprintf(out,"#define YYWILDCARD %d\n", lemp->wildcard->index); lineno++; @@ -4144,7 +4201,7 @@ void ReportTable( ** of placing the largest action sets first */ for(i=0; inxstate*2; i++) ax[i].iOrder = i; qsort(ax, lemp->nxstate*2, sizeof(ax[0]), axset_compare); - pActtab = acttab_alloc(); + pActtab = acttab_alloc(lemp->nsymbol, lemp->nterminal); for(i=0; inxstate*2 && ax[i].nAction>0; i++){ stp = ax[i].stp; if( ax[i].isTkn ){ @@ -4155,7 +4212,7 @@ void ReportTable( if( action<0 ) continue; acttab_action(pActtab, ap->sp->index, action); } - stp->iTknOfst = acttab_insert(pActtab); + stp->iTknOfst = acttab_insert(pActtab, 1); if( stp->iTknOfstiTknOfst; if( stp->iTknOfst>mxTknOfst ) mxTknOfst = stp->iTknOfst; }else{ @@ -4167,7 +4224,7 @@ void ReportTable( if( action<0 ) continue; acttab_action(pActtab, ap->sp->index, action); } - stp->iNtOfst = acttab_insert(pActtab); + stp->iNtOfst = acttab_insert(pActtab, 0); if( stp->iNtOfstiNtOfst; if( stp->iNtOfst>mxNtOfst ) mxNtOfst = stp->iNtOfst; } @@ -4200,16 +4257,18 @@ void ReportTable( ** been computed */ fprintf(out,"#define YYNSTATE %d\n",lemp->nxstate); lineno++; fprintf(out,"#define YYNRULE %d\n",lemp->nrule); lineno++; + fprintf(out,"#define YYNTOKEN %d\n",lemp->nterminal); lineno++; fprintf(out,"#define YY_MAX_SHIFT %d\n",lemp->nxstate-1); lineno++; - fprintf(out,"#define YY_MIN_SHIFTREDUCE %d\n",lemp->nstate); lineno++; - i = lemp->nstate + lemp->nrule; + i = lemp->minShiftReduce; + fprintf(out,"#define YY_MIN_SHIFTREDUCE %d\n",i); lineno++; + i += lemp->nrule; fprintf(out,"#define YY_MAX_SHIFTREDUCE %d\n", i-1); lineno++; - fprintf(out,"#define YY_MIN_REDUCE %d\n", i); lineno++; - i = lemp->nstate + lemp->nrule*2; + fprintf(out,"#define YY_ERROR_ACTION %d\n", lemp->errAction); lineno++; + fprintf(out,"#define YY_ACCEPT_ACTION %d\n", lemp->accAction); lineno++; + fprintf(out,"#define YY_NO_ACTION %d\n", lemp->noAction); lineno++; + fprintf(out,"#define YY_MIN_REDUCE %d\n", lemp->minReduce); lineno++; + i = lemp->minReduce + lemp->nrule; fprintf(out,"#define YY_MAX_REDUCE %d\n", i-1); lineno++; - fprintf(out,"#define YY_ERROR_ACTION %d\n", i); lineno++; - fprintf(out,"#define YY_ACCEPT_ACTION %d\n", i+1); lineno++; - fprintf(out,"#define YY_NO_ACTION %d\n", i+2); lineno++; tplt_xfer(lemp->name,in,out,&lineno); /* Now output the action table and its associates: @@ -4225,13 +4284,13 @@ void ReportTable( */ /* Output the yy_action table */ - lemp->nactiontab = n = acttab_size(pActtab); + lemp->nactiontab = n = acttab_action_size(pActtab); lemp->tablesize += n*szActionType; fprintf(out,"#define YY_ACTTAB_COUNT (%d)\n", n); lineno++; fprintf(out,"static const YYACTIONTYPE yy_action[] = {\n"); lineno++; for(i=j=0; instate + lemp->nrule + 2; + if( action<0 ) action = lemp->noAction; if( j==0 ) fprintf(out," /* %5d */ ", i); fprintf(out, " %4d,", action); if( j==9 || i==n-1 ){ @@ -4244,6 +4303,7 @@ void ReportTable( fprintf(out, "};\n"); lineno++; /* Output the yy_lookahead table */ + lemp->nlookaheadtab = n = acttab_lookahead_size(pActtab); lemp->tablesize += n*szCodeType; fprintf(out,"static const YYCODETYPE yy_lookahead[] = {\n"); lineno++; for(i=j=0; inxstate; while( n>0 && lemp->sorted[n-1]->iTknOfst==NO_OFFSET ) n--; - fprintf(out, "#define YY_SHIFT_USE_DFLT (%d)\n", lemp->nactiontab); lineno++; fprintf(out, "#define YY_SHIFT_COUNT (%d)\n", n-1); lineno++; fprintf(out, "#define YY_SHIFT_MIN (%d)\n", mnTknOfst); lineno++; fprintf(out, "#define YY_SHIFT_MAX (%d)\n", mxTknOfst); lineno++; @@ -4288,7 +4347,6 @@ void ReportTable( fprintf(out, "};\n"); lineno++; /* Output the yy_reduce_ofst[] table */ - fprintf(out, "#define YY_REDUCE_USE_DFLT (%d)\n", mnNtOfst-1); lineno++; n = lemp->nxstate; while( n>0 && lemp->sorted[n-1]->iNtOfst==NO_OFFSET ) n--; fprintf(out, "#define YY_REDUCE_COUNT (%d)\n", n-1); lineno++; @@ -4320,7 +4378,11 @@ void ReportTable( for(i=j=0; isorted[i]; if( j==0 ) fprintf(out," /* %5d */ ", i); - fprintf(out, " %4d,", stp->iDfltReduce+lemp->nstate+lemp->nrule); + if( stp->iDfltReduce<0 ){ + fprintf(out, " %4d,", lemp->errAction); + }else{ + fprintf(out, " %4d,", stp->iDfltReduce + lemp->minReduce); + } if( j==9 || i==n-1 ){ fprintf(out, "\n"); lineno++; j = 0; @@ -4354,10 +4416,8 @@ void ReportTable( */ for(i=0; insymbol; i++){ lemon_sprintf(line,"\"%s\",",lemp->symbols[i]->name); - fprintf(out," %-15s",line); - if( (i&3)==3 ){ fprintf(out,"\n"); lineno++; } + fprintf(out," /* %4d */ \"%s\",\n",i, lemp->symbols[i]->name); lineno++; } - if( (i&3)!=0 ){ fprintf(out,"\n"); lineno++; } tplt_xfer(lemp->name,in,out,&lineno); /* Generate a table containing a text string that describes every @@ -4401,7 +4461,7 @@ void ReportTable( if( sp==0 || sp->type==TERMINAL || sp->index<=0 || sp->destructor!=0 ) continue; if( once ){ - fprintf(out, " /* Default NON-TERMINAL Destructor */\n"); lineno++; + fprintf(out, " /* Default NON-TERMINAL Destructor */\n");lineno++; once = 0; } fprintf(out," case %d: /* %s */\n", sp->index, sp->name); lineno++; @@ -4444,8 +4504,10 @@ void ReportTable( ** Note: This code depends on the fact that rules are number ** sequentually beginning with 0. */ - for(rp=lemp->rule; rp; rp=rp->next){ - fprintf(out," { %d, %d },\n",rp->lhs->index,-rp->nrhs); lineno++; + for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){ + fprintf(out," { %4d, %4d }, /* (%d) ",rp->lhs->index,-rp->nrhs,i); + rule_print(out, rp); + fprintf(out," */\n"); lineno++; } tplt_xfer(lemp->name,in,out,&lineno); @@ -4711,7 +4773,7 @@ void ResortStates(struct lemon *lemp) for(i=0; instate; i++){ stp = lemp->sorted[i]; stp->nTknAct = stp->nNtAct = 0; - stp->iDfltReduce = lemp->nrule; /* Init dflt action to "syntax error" */ + stp->iDfltReduce = -1; /* Init dflt action to "syntax error" */ stp->iTknOfst = NO_OFFSET; stp->iNtOfst = NO_OFFSET; for(ap=stp->ap; ap; ap=ap->next){ @@ -4723,7 +4785,7 @@ void ResortStates(struct lemon *lemp) stp->nNtAct++; }else{ assert( stp->autoReduce==0 || stp->pDfltReduce==ap->x.rp ); - stp->iDfltReduce = iAction - lemp->nstate - lemp->nrule; + stp->iDfltReduce = iAction; } } } diff --git a/tool/lempar.c b/tool/lempar.c index 37a5892195..1ade666916 100644 --- a/tool/lempar.c +++ b/tool/lempar.c @@ -72,14 +72,15 @@ ** defined, then do no error processing. ** YYNSTATE the combined number of states. ** YYNRULE the number of rules in the grammar +** YYNTOKEN Number of terminal symbols ** YY_MAX_SHIFT Maximum value for shift actions ** YY_MIN_SHIFTREDUCE Minimum value for shift-reduce actions ** YY_MAX_SHIFTREDUCE Maximum value for shift-reduce actions -** YY_MIN_REDUCE Minimum value for reduce actions -** YY_MAX_REDUCE Maximum value for reduce actions ** YY_ERROR_ACTION The yy_action[] code for syntax error ** YY_ACCEPT_ACTION The yy_action[] code for accept ** YY_NO_ACTION The yy_action[] code for no-op +** YY_MIN_REDUCE Minimum value for reduce actions +** YY_MAX_REDUCE Maximum value for reduce actions */ #ifndef INTERFACE # define INTERFACE 1 @@ -115,9 +116,6 @@ ** N between YY_MIN_SHIFTREDUCE Shift to an arbitrary state then ** and YY_MAX_SHIFTREDUCE reduce by rule N-YY_MIN_SHIFTREDUCE. ** -** N between YY_MIN_REDUCE Reduce by rule N-YY_MIN_REDUCE -** and YY_MAX_REDUCE -** ** N == YY_ERROR_ACTION A syntax error has occurred. ** ** N == YY_ACCEPT_ACTION The parser accepts its input. @@ -125,25 +123,22 @@ ** N == YY_NO_ACTION No such action. Denotes unused ** slots in the yy_action[] table. ** +** N between YY_MIN_REDUCE Reduce by rule N-YY_MIN_REDUCE +** and YY_MAX_REDUCE +** ** The action table is constructed as a single large table named yy_action[]. ** Given state S and lookahead X, the action is computed as either: ** ** (A) N = yy_action[ yy_shift_ofst[S] + X ] ** (B) N = yy_default[S] ** -** The (A) formula is preferred. The B formula is used instead if: -** (1) The yy_shift_ofst[S]+X value is out of range, or -** (2) yy_lookahead[yy_shift_ofst[S]+X] is not equal to X, or -** (3) yy_shift_ofst[S] equal YY_SHIFT_USE_DFLT. -** (Implementation note: YY_SHIFT_USE_DFLT is chosen so that -** YY_SHIFT_USE_DFLT+X will be out of range for all possible lookaheads X. -** Hence only tests (1) and (2) need to be evaluated.) +** The (A) formula is preferred. The B formula is used instead if +** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X. ** ** The formulas above are for computing the action when the lookahead is ** a terminal symbol. If the lookahead is a non-terminal (as occurs after ** a reduce action) then the yy_reduce_ofst[] array is used in place of -** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of -** YY_SHIFT_USE_DFLT. +** the yy_shift_ofst[] array. ** ** The following are the tables generated in this section: ** @@ -259,13 +254,13 @@ void ParseTrace(FILE *TraceFILE, char *zTracePrompt){ } #endif /* NDEBUG */ -#ifndef NDEBUG +#if defined(YYCOVERAGE) || !defined(NDEBUG) /* For tracing shifts, the names of all terminals and nonterminals ** are required. The following table supplies these names */ static const char *const yyTokenName[] = { %% }; -#endif /* NDEBUG */ +#endif /* defined(YYCOVERAGE) || !defined(NDEBUG) */ #ifndef NDEBUG /* For tracing reduce actions, the names of all rules are required. @@ -461,6 +456,43 @@ int ParseStackPeak(void *p){ } #endif +/* This array of booleans keeps track of the parser statement +** coverage. The element yycoverage[X][Y] is set when the parser +** is in state X and has a lookahead token Y. In a well-tested +** systems, every element of this matrix should end up being set. +*/ +#if defined(YYCOVERAGE) +static unsigned char yycoverage[YYNSTATE][YYNTOKEN]; +#endif + +/* +** Write into out a description of every state/lookahead combination that +** +** (1) has not been used by the parser, and +** (2) is not a syntax error. +** +** Return the number of missed state/lookahead combinations. +*/ +#if defined(YYCOVERAGE) +int ParseCoverage(FILE *out){ + int stateno, iLookAhead, i; + int nMissed = 0; + for(stateno=0; statenoyytos->stateno; - if( stateno>=YY_MIN_REDUCE ) return stateno; + if( stateno>YY_MAX_SHIFT ) return stateno; assert( stateno <= YY_SHIFT_COUNT ); +#if defined(YYCOVERAGE) + yycoverage[stateno][iLookAhead] = 1; +#endif do{ i = yy_shift_ofst[stateno]; + assert( i>=0 ); + assert( i+YYNTOKEN<=(int)sizeof(yy_lookahead)/sizeof(yy_lookahead[0]) ); assert( iLookAhead!=YYNOCODE ); + assert( iLookAhead < YYNTOKEN ); i += iLookAhead; - if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + if( yy_lookahead[i]!=iLookAhead ){ #ifdef YYFALLBACK YYCODETYPE iFallback; /* Fallback token */ if( iLookAheadyytos->major], + fprintf(yyTraceFILE,"%s%s '%s', go to state %d\n", + yyTracePrompt, zTag, yyTokenName[yypParser->yytos->major], yyNewState); }else{ - fprintf(yyTraceFILE,"%sShift '%s'\n", - yyTracePrompt,yyTokenName[yypParser->yytos->major]); + fprintf(yyTraceFILE,"%s%s '%s', pending reduce %d\n", + yyTracePrompt, zTag, yyTokenName[yypParser->yytos->major], + yyNewState - YY_MIN_REDUCE); } } } #else -# define yyTraceShift(X,Y) +# define yyTraceShift(X,Y,Z) #endif /* @@ -633,7 +671,7 @@ static void yy_shift( yytos->stateno = (YYACTIONTYPE)yyNewState; yytos->major = (YYCODETYPE)yyMajor; yytos->minor.yy0 = yyMinor; - yyTraceShift(yypParser, yyNewState); + yyTraceShift(yypParser, yyNewState, "Shift"); } /* The following table contains information about every rule that @@ -651,22 +689,38 @@ static void yy_accept(yyParser*); /* Forward Declaration */ /* ** Perform a reduce action and the shift that must immediately ** follow the reduce. +** +** The yyLookahead and yyLookaheadToken parameters provide reduce actions +** access to the lookahead token (if any). The yyLookahead will be YYNOCODE +** if the lookahead token has already been consumed. As this procedure is +** only called from one place, optimizing compilers will in-line it, which +** means that the extra parameters have no performance impact. */ static void yy_reduce( yyParser *yypParser, /* The parser */ - unsigned int yyruleno /* Number of the rule by which to reduce */ + unsigned int yyruleno, /* Number of the rule by which to reduce */ + int yyLookahead, /* Lookahead token, or YYNOCODE if none */ + ParseTOKENTYPE yyLookaheadToken /* Value of the lookahead token */ ){ int yygoto; /* The next state */ int yyact; /* The next action */ yyStackEntry *yymsp; /* The top of the parser's stack */ int yysize; /* Amount to pop the stack */ ParseARG_FETCH; + (void)yyLookahead; + (void)yyLookaheadToken; yymsp = yypParser->yytos; #ifndef NDEBUG if( yyTraceFILE && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){ yysize = yyRuleInfo[yyruleno].nrhs; - fprintf(yyTraceFILE, "%sReduce [%s], go to state %d.\n", yyTracePrompt, - yyRuleName[yyruleno], yymsp[yysize].stateno); + if( yysize ){ + fprintf(yyTraceFILE, "%sReduce %d [%s], go to state %d.\n", + yyTracePrompt, + yyruleno, yyRuleName[yyruleno], yymsp[yysize].stateno); + }else{ + fprintf(yyTraceFILE, "%sReduce %d [%s].\n", + yyTracePrompt, yyruleno, yyRuleName[yyruleno]); + } } #endif /* NDEBUG */ @@ -721,16 +775,11 @@ static void yy_reduce( /* It is not possible for a REDUCE to be followed by an error */ assert( yyact!=YY_ERROR_ACTION ); - if( yyact==YY_ACCEPT_ACTION ){ - yypParser->yytos += yysize; - yy_accept(yypParser); - }else{ - yymsp += yysize+1; - yypParser->yytos = yymsp; - yymsp->stateno = (YYACTIONTYPE)yyact; - yymsp->major = (YYCODETYPE)yygoto; - yyTraceShift(yypParser, yyact); - } + yymsp += yysize+1; + yypParser->yytos = yymsp; + yymsp->stateno = (YYACTIONTYPE)yyact; + yymsp->major = (YYCODETYPE)yygoto; + yyTraceShift(yypParser, yyact, "... then shift"); } /* @@ -840,20 +889,31 @@ void Parse( #ifndef NDEBUG if( yyTraceFILE ){ - fprintf(yyTraceFILE,"%sInput '%s'\n",yyTracePrompt,yyTokenName[yymajor]); + int stateno = yypParser->yytos->stateno; + if( stateno < YY_MIN_REDUCE ){ + fprintf(yyTraceFILE,"%sInput '%s' in state %d\n", + yyTracePrompt,yyTokenName[yymajor],stateno); + }else{ + fprintf(yyTraceFILE,"%sInput '%s' with pending reduce %d\n", + yyTracePrompt,yyTokenName[yymajor],stateno-YY_MIN_REDUCE); + } } #endif do{ yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor); - if( yyact <= YY_MAX_SHIFTREDUCE ){ + if( yyact >= YY_MIN_REDUCE ){ + yy_reduce(yypParser,yyact-YY_MIN_REDUCE,yymajor,yyminor); + }else if( yyact <= YY_MAX_SHIFTREDUCE ){ yy_shift(yypParser,yyact,yymajor,yyminor); #ifndef YYNOERRORRECOVERY yypParser->yyerrcnt--; #endif yymajor = YYNOCODE; - }else if( yyact <= YY_MAX_REDUCE ){ - yy_reduce(yypParser,yyact-YY_MIN_REDUCE); + }else if( yyact==YY_ACCEPT_ACTION ){ + yypParser->yytos--; + yy_accept(yypParser); + return; }else{ assert( yyact == YY_ERROR_ACTION ); yyminorunion.yy0 = yyminor; diff --git a/tool/mkshellc.tcl b/tool/mkshellc.tcl index 807a169826..534ac6156a 100644 --- a/tool/mkshellc.tcl +++ b/tool/mkshellc.tcl @@ -30,16 +30,29 @@ puts $out {/* DO NOT EDIT! ** by "src/shell.c.in", then rerun the tool/mkshellc.tcl script. */} set in [open $topdir/src/shell.c.in rb] +proc omit_redundant_typedefs {line} { + global typedef_seen + if {[regexp {^typedef .*;} $line]} { + if {[info exists typedef_seen($line)]} { + return "/* $line */" + } + set typedef_seen($line) 1 + } + return $line +} while {1} { - set lx [gets $in] + set lx [omit_redundant_typedefs [gets $in]] if {[eof $in]} break; if {[regexp {^INCLUDE } $lx]} { set cfile [lindex $lx 1] puts $out "/************************* Begin $cfile ******************/" set in2 [open $topdir/src/$cfile rb] while {![eof $in2]} { - set lx [gets $in2] + set lx [omit_redundant_typedefs [gets $in2]] if {[regexp {^#include "sqlite} $lx]} continue + if {[regexp {^# *include "test_windirent.h"} $lx]} { + set lx "/* $lx */" + } set lx [string map [list __declspec(dllexport) {}] $lx] puts $out $lx } diff --git a/tool/speed-check.sh b/tool/speed-check.sh index 2cda5c8078..fc05ac98ef 100644 --- a/tool/speed-check.sh +++ b/tool/speed-check.sh @@ -39,6 +39,8 @@ LEAN_OPTS="$LEAN_OPTS -DSQLITE_USE_ALLOCA" BASELINE="trunk" doExplain=0 doCachegrind=1 +doVdbeProfile=0 +doWal=1 while test "$1" != ""; do case $1 in --reprepare) @@ -62,8 +64,11 @@ while test "$1" != ""; do --temp) SPEEDTEST_OPTS="$SPEEDTEST_OPTS --temp 6" ;; + --legacy) + doWal=0 + ;; --wal) - SPEEDTEST_OPTS="$SPEEDTEST_OPTS --journal wal" + doWal=1 ;; --size) shift; SIZE=$1 @@ -78,6 +83,7 @@ while test "$1" != ""; do rm -f vdbe_profile.out CC_OPTS="$CC_OPTS -DVDBE_PROFILE" doCachegrind=0 + doVdbeProfile=1 ;; --lean) CC_OPTS="$CC_OPTS $LEAN_OPTS" @@ -117,6 +123,12 @@ while test "$1" != ""; do --orm) SPEEDTEST_OPTS="$SPEEDTEST_OPTS --testset orm" ;; + --cte) + SPEEDTEST_OPTS="$SPEEDTEST_OPTS --testset cte" + ;; + --fp) + SPEEDTEST_OPTS="$SPEEDTEST_OPTS --testset fp" + ;; -*) CC_OPTS="$CC_OPTS $1" ;; @@ -126,11 +138,17 @@ while test "$1" != ""; do esac shift done +if test $doWal -eq 1; then + SPEEDTEST_OPTS="$SPEEDTEST_OPTS --journal wal" +fi SPEEDTEST_OPTS="$SPEEDTEST_OPTS --size $SIZE" echo "NAME = $NAME" | tee summary-$NAME.txt echo "SPEEDTEST_OPTS = $SPEEDTEST_OPTS" | tee -a summary-$NAME.txt echo "CC_OPTS = $CC_OPTS" | tee -a summary-$NAME.txt rm -f cachegrind.out.* speedtest1 speedtest1.db sqlite3.o +if test $doVdbeProfile -eq 1; then + rm -f vdbe_profile.out +fi $CC -g -Os -Wall -I. $CC_OPTS -c sqlite3.c size sqlite3.o | tee -a summary-$NAME.txt if test $doExplain -eq 1; then @@ -157,6 +175,10 @@ fi if test $doExplain -eq 1; then ./speedtest1 --explain $SPEEDTEST_OPTS | ./sqlite3 >explain-$NAME.txt fi -if test "$NAME" != "$BASELINE"; then +if test $doVdbeProfile -eq 1; then + tclsh ../sqlite/tool/vdbe_profile.tcl >vdbeprofile-$NAME.txt + open vdbeprofile-$NAME.txt +fi +if test "$NAME" != "$BASELINE" -a $doVdbeProfile -ne 1; then fossil test-diff --tk -c 20 cout-$BASELINE.txt cout-$NAME.txt fi diff --git a/tool/sqltclsh.c.in b/tool/sqltclsh.c.in new file mode 100644 index 0000000000..da354ee935 --- /dev/null +++ b/tool/sqltclsh.c.in @@ -0,0 +1,51 @@ +/* +** This is the source code to a "tclsh" that has SQLite built-in. +** +** The startup script is located as follows: +** +** (1) Open the executable as an appended SQLite database and try to +** read the startup script out of that database. +** +** (2) If the first argument is a readable file, try to open that file +** as an SQLite database and read the startup script out of that +** database. +** +** (3) If the first argument is a readable file with a ".tcl" extension, +** then try to run that script directly. +** +** If none of the above steps work, then the program runs as an interactive +** tclsh. +*/ +#define TCLSH_INIT_PROC sqlite3_tclapp_init_proc +#define SQLITE_ENABLE_DBSTAT_VTAB 1 +#undef SQLITE_THREADSAFE +#define SQLITE_THREADSAFE 0 +#undef SQLITE_ENABLE_COLUMN_METADATA +#define SQLITE_OMIT_DECLTYPE 1 +#define SQLITE_OMIT_DEPRECATED 1 +#define SQLITE_OMIT_PROGRESS_CALLBACK 1 +#define SQLITE_OMIT_SHARED_CACHE 1 +#define SQLITE_DEFAULT_MEMSTATUS 0 +#define SQLITE_MAX_EXPR_DEPTH 0 +INCLUDE sqlite3.c +INCLUDE $ROOT/ext/misc/appendvfs.c +#ifdef SQLITE_HAVE_ZLIB +INCLUDE $ROOT/ext/misc/zipfile.c +INCLUDE $ROOT/ext/misc/sqlar.c +#endif +INCLUDE $ROOT/src/tclsqlite.c + +const char *sqlite3_tclapp_init_proc(Tcl_Interp *interp){ + (void)interp; + sqlite3_appendvfs_init(0,0,0); +#ifdef SQLITE_HAVE_ZLIB + sqlite3_auto_extension((void(*)(void))sqlite3_sqlar_init); + sqlite3_auto_extension((void(*)(void))sqlite3_zipfile_init); +#endif + + return +BEGIN_STRING +INCLUDE $ROOT/tool/sqltclsh.tcl +END_STRING +; +} diff --git a/tool/sqltclsh.tcl b/tool/sqltclsh.tcl new file mode 100644 index 0000000000..6a4b1fe1f0 --- /dev/null +++ b/tool/sqltclsh.tcl @@ -0,0 +1,71 @@ +# Try to open the executable as a database and read the "scripts.data" +# field where "scripts.name" is 'main.tcl' +# +catch { + if {![file exists $argv0] && [file exists $argv0.exe]} { + append argv0 .exe + } + sqlite3 db $argv0 -vfs apndvfs -create 0 + set mainscript [db one { + SELECT sqlar_uncompress(data,sz) FROM sqlar WHERE name='main.tcl' + }] +} +if {[info exists mainscript]} { + eval $mainscript + return +} else { + catch {db close} +} + +# Try to open file named in the first argument as a database and +# read the "scripts.data" field where "scripts.name" is 'main.tcl' +# +if {[llength $argv]>0 && [file readable [lindex $argv 0]]} { + catch { + sqlite3 db [lindex $argv 0] -vfs apndvfs -create 0 + set mainscript [db one {SELECT data FROM scripts WHERE name='main.tcl'}] + set argv0 [lindex $argv 0] + set argv [lrange $argv 1 end] + } + if {[info exists mainscript]} { + eval $mainscript + return + } else { + catch {db close} + } + if {[string match *.tcl [lindex $argv 0]]} { + set fd [open [lindex $argv 0] rb] + set mainscript [read $fd] + close $fd + unset fd + set argv0 [lindex $argv 0] + set argv [lrange $argv 1 end] + } + if {[info exists mainscript]} { + eval $mainscript + return + } +} + +# If all else fails, do an interactive loop +# +set line {} +while {![eof stdin]} { + if {$line!=""} { + puts -nonewline "> " + } else { + puts -nonewline "% " + } + flush stdout + append line [gets stdin] + if {[info complete $line]} { + if {[catch {uplevel #0 $line} result]} { + puts stderr "Error: $result" + } elseif {$result!=""} { + puts $result + } + set line {} + } else { + append line \\n" + } +}