diff --git a/Makefile.in b/Makefile.in
index 9556dd8178..26757dd15a 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -83,9 +83,13 @@ TEMP_STORE = -DSQLITE_TEMP_STORE=@TEMP_STORE@
# based on configuration. (-DSQLITE_OMIT*, -DSQLITE_ENABLE*).
# The same set of OMIT and ENABLE flags should be passed to the
# LEMON parser generator and the mkkeywordhash tool as well.
-OPT_FEATURE_FLAGS = @OPT_FEATURE_FLAGS@
+#
+# Add OPTIONS=... on the command line to append additional options
+# to the OPT_FEATURE_FLAGS.
+#
+OPT_FEATURE_FLAGS = @OPT_FEATURE_FLAGS@ $(OPTIONS)
-TCC += $(OPT_FEATURE_FLAGS)
+TCC += $(OPT_FEATURE_FLAGS)
# Add in any optional parameters specified on the make commane line
# ie. make "OPTS=-DSQLITE_ENABLE_FOO=1 -DSQLITE_OMIT_FOO=1".
@@ -117,6 +121,11 @@ HAVE_TCL = @HAVE_TCL@
#
TCLSH_CMD = @TCLSH_CMD@
+# Additional options when running tests using testrunner.tcl
+# This is usually either blank, or else --status
+#
+TSTRNNR_OPTS = @TSTRNNR_OPTS@
+
# Where do we want to install the tcl plugin
#
TCLLIBDIR = @TCLLIBDIR@
@@ -797,6 +806,10 @@ has_tclsh85:
sh $(TOP)/tool/cktclsh.sh 8.5 $(TCLSH_CMD)
touch has_tclsh85
+has_tclconfig:
+ @ if test x"$(HAVE_TCL)" != "x1"; then echo 'ERROR: Requires access to "tclConfig.sh" which "configure" was not able to locate'; exit 1; fi
+ touch has_tclconfig
+
# This target creates a directory named "tsrc" and fills it with
# copies of all of the C source code and header files needed to
@@ -1119,7 +1132,7 @@ tclsqlite-shell.lo: $(TOP)/src/tclsqlite.c $(HDR)
tclsqlite-stubs.lo: $(TOP)/src/tclsqlite.c $(HDR)
$(LTCOMPILE) -DUSE_TCL_STUBS=1 -o $@ -c $(TOP)/src/tclsqlite.c
-tclsqlite3$(TEXE): tclsqlite-shell.lo libsqlite3.la
+tclsqlite3$(TEXE): has_tclconfig tclsqlite-shell.lo libsqlite3.la
$(LTLINK) -o $@ tclsqlite-shell.lo \
libsqlite3.la $(LIBTCL)
@@ -1306,7 +1319,7 @@ TESTFIXTURE_SRC1 = sqlite3.c
TESTFIXTURE_SRC = $(TESTSRC) $(TOP)/src/tclsqlite.c
TESTFIXTURE_SRC += $(TESTFIXTURE_SRC$(USE_AMALGAMATION))
-testfixture$(TEXE): has_tclsh85 $(TESTFIXTURE_SRC)
+testfixture$(TEXE): has_tclconfig has_tclsh85 $(TESTFIXTURE_SRC)
$(LTLINK) -DSQLITE_NO_SYNC=1 $(TEMP_STORE) $(TESTFIXTURE_FLAGS) \
-o $@ $(TESTFIXTURE_SRC) $(LIBTCL) $(TLIBS)
@@ -1355,15 +1368,20 @@ tcltest: ./testfixture$(TEXE)
testrunner: testfixture$(TEXE)
./testfixture$(TEXE) $(TOP)/test/testrunner.tcl
-# Runs both fuzztest and testrunner, consecutively.
+# This is the testing target preferred by the core SQLite developers.
+# It runs tests under a standard configuration, regardless of how
+# ./configure was run. The devs run "make devtest" prior to each
+# check-in, at a minimum. Probably other tests too, but at least this
+# one.
#
-devtest: srctree-check testfixture$(TEXE) fuzztest testrunner
+devtest: srctree-check sourcetest
+ $(TCLSH_CMD) $(TOP)/test/testrunner.tcl mdevtest $(TSTRNNR_OPTS)
mdevtest: srctree-check has_tclsh85
- $(TCLSH_CMD) $(TOP)/test/testrunner.tcl mdevtest
+ $(TCLSH_CMD) $(TOP)/test/testrunner.tcl mdevtest $(TSTRNNR_OPTS)
sdevtest: has_tclsh85
- $(TCLSH_CMD) $(TOP)/test/testrunner.tcl sdevtest
+ $(TCLSH_CMD) $(TOP)/test/testrunner.tcl sdevtest $(TSTRNNR_OPTS)
# Validate that various generated files in the source tree
# are up-to-date.
@@ -1373,16 +1391,19 @@ srctree-check: $(TOP)/tool/srctree-check.tcl
# Testing for a release
#
-releasetest: srctree-check testfixture$(TEXE)
- ./testfixture$(TEXE) $(TOP)/test/testrunner.tcl release
+releasetest: srctree-check has_tclsh85
+ $(TCLSH_CMD) $(TOP)/test/testrunner.tcl release $(TSTRNNR_OPTS)
# Minimal testing that runs in less than 3 minutes
#
quicktest: ./testfixture$(TEXE)
./testfixture$(TEXE) $(TOP)/test/extraquick.test $(TESTOPTS)
-# This is the common case. Run many tests that do not take too long,
-# including fuzzcheck, sqlite3_analyzer, and sqldiff tests.
+# Try to run tests on whatever options are specified by the
+# ./configure. The developers seldom use this target. Instead
+# they use "make devtest" which runs tests on a standard set of
+# options regardless of how SQLite is configured. This "test"
+# target is provided for legacy only.
#
test: srctree-check fuzztest sourcetest $(TESTPROGS) tcltest
@@ -1399,19 +1420,19 @@ valgrindtest: $(TESTPROGS) valgrindfuzz
smoketest: $(TESTPROGS) fuzzcheck$(TEXE)
./testfixture$(TEXE) $(TOP)/test/main.test $(TESTOPTS)
-shelltest: $(TESTPROGS)
- ./testfixture$(TEXT) $(TOP)/test/permutations.test shell
+shelltest:
+ $(TCLSH_CMD) $(TOP)/test/testrunner.tcl release shell
sqlite3_analyzer.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/spaceanal.tcl $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqlite3_analyzer.c.in has_tclsh85
$(TCLSH_CMD) $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqlite3_analyzer.c.in >sqlite3_analyzer.c
-sqlite3_analyzer$(TEXE): sqlite3_analyzer.c
+sqlite3_analyzer$(TEXE): has_tclconfig 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 has_tclsh85
$(TCLSH_CMD) $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqltclsh.c.in >sqltclsh.c
-sqltclsh$(TEXE): sqltclsh.c
+sqltclsh$(TEXE): has_tclconfig 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
@@ -1430,7 +1451,7 @@ CHECKER_DEPS =\
sqlite3_checker.c: $(CHECKER_DEPS) has_tclsh85
$(TCLSH_CMD) $(TOP)/tool/mkccode.tcl $(TOP)/ext/repair/sqlite3_checker.c.in >$@
-sqlite3_checker$(TEXE): sqlite3_checker.c
+sqlite3_checker$(TEXE): has_tclconfig sqlite3_checker.c
$(LTLINK) sqlite3_checker.c -o $@ $(LIBTCL) $(TLIBS)
dbdump$(TEXE): $(TOP)/ext/misc/dbdump.c sqlite3.lo
diff --git a/Makefile.msc b/Makefile.msc
index 8fb7a0bc99..84b85d0da9 100644
--- a/Makefile.msc
+++ b/Makefile.msc
@@ -379,6 +379,7 @@ SQLITE_TCL_DEP =
# the Windows platform.
#
!IFNDEF OPT_FEATURE_FLAGS
+OPT_FEATURE_FLAGS = $(OPT_XTRA)
!IF $(MINIMAL_AMALGAMATION)==0
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS3=1
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS5=1
@@ -392,6 +393,14 @@ OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_BYTECODE_VTAB=1
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_COLUMN_METADATA=1
!ENDIF
+# Additional feature-options above and beyond what are normally used can be
+# be added using OPTIONS=.... on the command-line. These values are
+# appended to the OPT_FEATURE_FLAGS variable.
+#
+!IFDEF OPTIONS
+OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) $(OPTIONS)
+!ENDIF
+
# Should the session extension be enabled? If so, add compilation options
# to enable it.
#
@@ -2536,6 +2545,13 @@ queryplantest: testfixture.exe shell
fuzztest: fuzzcheck.exe
.\fuzzcheck.exe $(FUZZDATA)
+# Legacy testing target for third-party integrators. The SQLite
+# developers seldom use this target themselves. Instead
+# they use "nmake /f Makefile.msc devtest" which runs tests on
+# a standard set of options
+#
+test: $(TESTPROGS) sourcetest fuzztest tcltest
+
# Minimal testing that runs in less than 3 minutes (on a fast machine)
#
quicktest: testfixture.exe sourcetest
@@ -2545,7 +2561,6 @@ quicktest: testfixture.exe sourcetest
# This is the common case. Run many tests that do not take too long,
# including fuzzcheck, sqlite3_analyzer, and sqldiff tests.
#
-test: $(TESTPROGS) sourcetest fuzztest tcltest
# The veryquick.test TCL tests.
#
@@ -2559,17 +2574,27 @@ tcltest: testfixture.exe
testrunner: testfixture.exe
.\testfixture.exe $(TOP)\test\testrunner.tcl
-# Runs both fuzztest and testrunner, consecutively.
+# This is the testing target preferred by the core SQLite developers.
+# It runs tests under a standard configuration. The devs run
+# "nmake /f Makefile.msc devtest" prior to each check-in, at a minimum.
+# Probably other tests too, but at least this one.
#
-devtest: testfixture.exe fuzztest testrunner
+devtest: srctree-check sourcetest
+ $(TCLSH_CMD) $(TOP)\test\testrunner.tcl mdevtest
mdevtest:
$(TCLSH_CMD) $(TOP)\test\testrunner.tcl mdevtest
+# Validate that various generated files in the source tree
+# are up-to-date.
+#
+srctree-check: $(TOP)\tool\srctree-check.tcl
+ $(TCLSH_CMD) $(TOP)\tool\srctree-check.tcl
+
# Testing for a release
#
-releasetest: testfixture.exe
- testfixture.exe $(TOP)\test\testrunner.tcl release
+releasetest:
+ $(TCLSH_CMD) $(TOP)\test\testrunner.tcl release
smoketest: $(TESTPROGS)
diff --git a/README.md b/README.md
index 0975a32d9a..c208f11ea3 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,19 @@
SQLite Source Repository
This repository contains the complete source code for the
-[SQLite database engine](https://sqlite.org/). Some test scripts
-are also included. However, many other test scripts
+[SQLite database engine](https://sqlite.org/), including
+many test scripts. However, other test scripts
and most of the documentation are managed separately.
+See the [on-line documentation](https://sqlite.org/) for more information
+about what SQLite is and how it works from a user's perspective. This
+README file is about the source code that goes into building SQLite,
+not about how SQLite is used.
+
## Version Control
SQLite sources are managed using
-[Fossil](https://www.fossil-scm.org/), a distributed version control system
+[Fossil](https://fossil-scm.org/), a distributed version control system
that was specifically designed and written to support SQLite development.
The [Fossil repository](https://sqlite.org/src/timeline) contains the urtext.
@@ -68,12 +73,11 @@ archives or [SQLite archives](https://sqlite.org/cli.html#sqlar) as follows:
then click on the "Tarball" or "ZIP Archive" links on the information
page.
-If you do want to use Fossil to check out the source tree,
-first install Fossil version 2.0 or later.
-(Source tarballs and precompiled binaries available
-[here](https://www.fossil-scm.org/fossil/uv/download.html). Fossil is
+To access sources directly using Fossil, first install Fossil version 2.0 or later.
+Source tarballs and precompiled binaries available
+[here](https://www.fossil-scm.org/home/uv/download.html). Fossil is
a stand-alone program. To install, simply download or build the single
-executable file and put that file someplace on your $PATH.)
+executable file and put that file someplace on your $PATH.
Then run commands like this:
mkdir -p ~/sqlite ~/Fossils
@@ -81,8 +85,8 @@ Then run commands like this:
fossil clone https://www.sqlite.org/src ~/Fossils/sqlite.fossil
fossil open ~/Fossils/sqlite.fossil
-After setting up a repository using the steps above, you can always
-update to the latest version using:
+After setting up a repository using the steps above, you can do
+bandwidth-efficient updates to the latest version using:
fossil update trunk ;# latest trunk check-in
fossil update release ;# latest official release
@@ -99,15 +103,37 @@ script found at the root of the source tree. Then run "make".
For example:
- tar xzf sqlite.tar.gz ;# Unpack the source tree into "sqlite"
- mkdir bld ;# Build will occur in a sibling directory
- cd bld ;# Change to the build directory
- ../sqlite/configure ;# Run the configure script
- make ;# Builds the "sqlite3" command-line tool
- make sqlite3.c ;# Build the "amalgamation" source file
- make devtest ;# Run some tests (requires Tcl)
+ tar xzf sqlite.tar.gz ;# Unpack the source tree into "sqlite"
+ mkdir bld ;# Build will occur in a sibling directory
+ cd bld ;# Change to the build directory
+ ../sqlite/configure ;# Run the configure script
+ make sqlite3 ;# Builds the "sqlite3" command-line tool
+ make sqlite3.c ;# Build the "amalgamation" source file
+ make devtest ;# Run development tests (requires tcl-dev)
+ make releasetest ;# Run full release tests (requires tcl-dev)
+ make sqldiff ;# Builds the "sqldiff" command-line tool
+ make sqlite3_analyzer ;# Builds the "sqlite3_analyzer" tool (requires tcl-dev)
+ make tclextension-install ;# Build and install the SQLite TCL extension
-See the makefile for additional targets.
+See the makefile for additional targets. For debugging builds, the
+core developers typically run "configure" with options like this:
+
+ ../sqlite/configure --enable-all --enable-debug CFLAGS='-O0 -g'
+
+For release builds, the core developers usually do:
+
+ ../sqlite/configure --enable-all
+
+Almost all makefile targets require a "tclsh" TCL interpreter
+version 8.6 or later. The targets marked with "(requires tcl-dev)" also require
+the TCL development libraries.
+
+On "make" command-lines, one can add "OPTIONS=..." to specify additional
+compile-time options over and above those set by ./configure. For example,
+to compile with the SQLITE_OMIT_DEPRECATED compile-time option, one could say:
+
+ ./configure --enable-all
+ make OPTIONS=-DSQLITE_OMIT_DEPRECATED sqlite3
The configure script uses autoconf 2.61 and libtool. If the configure
script does not work out for you, there is a generic makefile named
@@ -128,48 +154,66 @@ TCL library, using a command like this:
set TCLDIR=c:\Tcl
-SQLite uses "tclsh.exe" as part of the build process, and so that utility
-program will need to be somewhere on your %PATH%. The finished SQLite library
-does not contain any TCL code, but it does use TCL to help with the build process
-and to run tests.
+SQLite uses "tclsh.exe" as part of the build process, and so that
+program will need to be somewhere on your %PATH%. SQLite itself
+does not contain any TCL code, but it does use TCL to help with the
+build process and to run tests.
Build using Makefile.msc. Example:
- nmake /f Makefile.msc
+ nmake /f Makefile.msc sqlite3.exe
nmake /f Makefile.msc sqlite3.c
nmake /f Makefile.msc devtest
nmake /f Makefile.msc releasetest
+ nmake /f Makefile.msc tclextension-install
There are many other makefile targets. See comments in Makefile.msc for
details.
-## Source Code Tour
+As with the unix Makefile, the OPTIONS=... argument can be passed on the nmake
+command-line to enable new compile-time options. For example:
-Most of the core source files are in the **src/** subdirectory. The
-**src/** folder also contains files used to build the "testfixture" test
-harness. The names of the source files used by "testfixture" all begin
-with "test".
-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.
-(Historical note: SQLite began as a Tcl
-extension and only later escaped to the wild as an independent library.)
+ nmake /f Makefile.msc OPTIONS=-DSQLITE_OMIT_DEPRECATED sqlite3.exe
-Test scripts and programs are found in the **test/** subdirectory.
-Additional test code is found in other source repositories.
-See [How SQLite Is Tested](https://www.sqlite.org/testing.html) for
-additional information.
+## Source Tree Map
-The **ext/** subdirectory contains code for extensions. The
-Full-text search engine is in **ext/fts3**. The R-Tree engine is in
-**ext/rtree**. The **ext/misc** subdirectory contains a number of
-smaller, single-file extensions, such as a REGEXP operator.
+ * **src/** - This directory contains the primary source code for the
+ SQLite core. For historical reasons, C-code used for testing is
+ also found here. Source files intended for testing begin with "`test`".
+ The `tclsqlite3.c` and `tclsqlite3.h` files are the TCL interface
+ for SQLite and are also not part of the core.
-The **tool/** subdirectory contains various scripts and programs used
-for building generated source code files or for testing or for generating
-accessory programs such as "sqlite3_analyzer(.exe)".
+ * **test/** - This directory and its subdirectories contains code used
+ for testing. Files that end in "`.test`" are TCL scripts that run
+ tests using an augmented TCL interpreter named "testfixture". Use
+ a command like "`make testfixture`" (unix) or
+ "`nmake /f Makefile.msc testfixture.exe`" (windows) to build that
+ augmented TCL interpreter, then run individual tests using commands like
+ "`testfixture test/main.test`". This test/ subdirectory also contains
+ additional C code modules and scripts for other kinds of testing.
+
+ * **tool/** - This directory contains programs and scripts used to
+ build some of the machine-generated code that goes into the SQLite
+ core, as well as to build and run tests and perform diagnostics.
+ The source code to [the Lemon parser generator](./doc/lemon.html) is
+ found here. There are also TCL scripts used to build and/or transform
+ source code files. For example, the tool/mksqlite3h.tcl script reads
+ the src/sqlite.h.in file and uses it as a template to construct
+ the deliverable "sqlite3.h" file that defines the SQLite interface.
+
+ * **ext/** - Various extensions to SQLite are found under this
+ directory. For example, the FTS5 subsystem is in "ext/fts5/".
+ Some of these extensions (ex: FTS3/4, FTS5, RTREE) might get built
+ into the SQLite amalgamation, but not all of them. The
+ "ext/misc/" subdirectory contains an assortment of one-file extensions,
+ many of which are omitted from the SQLite core, but which are included
+ in the [SQLite CLI](https://sqlite.org/cli.html).
+
+ * **doc/** - Some documentation files about SQLite internals are found
+ here. Note, however, that the primary documentation designed for
+ application developers and users of SQLite is in a completely separate
+ repository. Note also that the primary API documentation is derived
+ from specially constructed comments in the src/sqlite.h.in file.
### Generated Source Code Files
@@ -252,31 +296,37 @@ individual source file exceeds 32K lines in length.
SQLite is modular in design.
See the [architectural description](https://www.sqlite.org/arch.html)
for details. Other documents that are useful in
-(helping to understand how SQLite works include the
+helping to understand how SQLite works include the
[file format](https://www.sqlite.org/fileformat2.html) description,
the [virtual machine](https://www.sqlite.org/opcode.html) that runs
prepared statements, the description of
[how transactions work](https://www.sqlite.org/atomiccommit.html), and
the [overview of the query planner](https://www.sqlite.org/optoverview.html).
-Years of effort have gone into optimizing SQLite, both
+Decades of effort have gone into optimizing SQLite, both
for small size and high performance. And optimizations tend to result in
complex code. So there is a lot of complexity in the current SQLite
implementation. It will not be the easiest library in the world to hack.
-Key files:
+### Key source code files
* **sqlite.h.in** - This file defines the public interface to the SQLite
library. Readers will need to be familiar with this interface before
- trying to understand how the library works internally.
+ trying to understand how the library works internally. This file is
+ really a template that is transformed into the "sqlite3.h" deliverable
+ using a script invoked by the makefile.
* **sqliteInt.h** - this header file defines many of the data objects
used internally by SQLite. In addition to "sqliteInt.h", some
- subsystems have their own header files.
+ subsystems inside of sQLite have their own header files. These internal
+ interfaces are not for use by applications. They can and do change
+ from one release of SQLite to the next.
* **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
- in the parsing process.
+ in the parsing process. The file is processed by the
+ [Lemon Parser Generator](./doc/lemon.html) to produce the actual C code
+ used for parsing.
* **vdbe.c** - This file implements the virtual machine that runs
prepared statements. There are various helper files whose names
@@ -319,6 +369,11 @@ Key files:
(and some other test programs too) is built and run when you type
"make test".
+ * **VERSION**, **manifest**, and **manifest.uuid** - These files define
+ the current SQLite version number. The "VERSION" file is human generated,
+ but the "manifest" and "manifest.uuid" files are automatically generated
+ by the [Fossil version control system](https://fossil-scm/).
+
There are many other source files. Each has a succinct header comment that
describes its purpose and role within the larger system.
@@ -336,7 +391,7 @@ The `manifest.uuid` file should contain the SHA3-256 hash of the
`manifest` file. If all of the above hash comparisons are correct, then
you can be confident that your source tree is authentic and unadulterated.
Details on the format for the `manifest` files are available
-[on the Fossil website](https://fossil-scm.org/fossil/doc/trunk/www/fileformat.wiki#manifest).
+[on the Fossil website](https://fossil-scm.org/home/doc/trunk/www/fileformat.wiki#manifest).
The process of checking source code authenticity is automated by the
makefile:
@@ -357,3 +412,7 @@ The main SQLite website is [https://sqlite.org/](https://sqlite.org/)
with geographically distributed backups at
[https://www2.sqlite.org/](https://www2.sqlite.org) and
[https://www3.sqlite.org/](https://www3.sqlite.org).
+
+Contact the SQLite developers through the
+[SQLite Forum](https://sqlite.org/forum/). In an emergency, you
+can send private email to the lead developer at drh at sqlite dot org.
diff --git a/autoconf/Makefile.msc b/autoconf/Makefile.msc
index a4270fb2ae..f8a65e90cc 100644
--- a/autoconf/Makefile.msc
+++ b/autoconf/Makefile.msc
@@ -301,6 +301,7 @@ SQLITE3EXEPDB = /pdb:sqlite3sh.pdb
# the Windows platform.
#
!IFNDEF OPT_FEATURE_FLAGS
+OPT_FEATURE_FLAGS = $(OPT_XTRA)
!IF $(MINIMAL_AMALGAMATION)==0
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS3=1
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS5=1
@@ -314,6 +315,14 @@ OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_BYTECODE_VTAB=1
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_COLUMN_METADATA=1
!ENDIF
+# Additional feature-options above and beyond what are normally used can be
+# be added using OPTIONS=.... on the command-line. These values are
+# appended to the OPT_FEATURE_FLAGS variable.
+#
+!IFDEF OPTIONS
+OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) $(OPTIONS)
+!ENDIF
+
# Should the session extension be enabled? If so, add compilation options
# to enable it.
#
diff --git a/configure b/configure
index 50d9e2b3d0..22b961e554 100755
--- a/configure
+++ b/configure
@@ -793,6 +793,7 @@ HAVE_WASI_SDK
RELEASE
VERSION
program_prefix
+TSTRNNR_OPTS
TCLLIBDIR
HAVE_TCL
TCL_STUB_LIB_SPEC
@@ -889,6 +890,7 @@ enable_largefile
with_tclsh
with_tcl
enable_tcl
+enable_test_status
with_wasi_sdk
enable_threadsafe
enable_releasemode
@@ -1546,6 +1548,7 @@ Optional Features:
--disable-largefile omit support for large files
--disable-tcl omit building accessory programs that require
TCL-dev
+ --enable-test-status Full-screen status of tests
--disable-threadsafe Disable mutexing
--enable-releasemode Support libtool link to release mode
--enable-tempstore Use an in-ram database for temporary tables
@@ -3939,13 +3942,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:3942: $ac_compile\"" >&5)
+ (eval echo "\"\$as_me:3945: $ac_compile\"" >&5)
(eval "$ac_compile" 2>conftest.err)
cat conftest.err >&5
- (eval echo "\"\$as_me:3945: $NM \\\"conftest.$ac_objext\\\"\"" >&5)
+ (eval echo "\"\$as_me:3948: $NM \\\"conftest.$ac_objext\\\"\"" >&5)
(eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out)
cat conftest.err >&5
- (eval echo "\"\$as_me:3948: output\"" >&5)
+ (eval echo "\"\$as_me:3951: output\"" >&5)
cat conftest.out >&5
if $GREP 'External.*some_variable' conftest.out > /dev/null; then
lt_cv_nm_interface="MS dumpbin"
@@ -5151,7 +5154,7 @@ ia64-*-hpux*)
;;
*-*-irix6*)
# Find out which ABI we are using.
- echo '#line 5154 "configure"' > conftest.$ac_ext
+ echo '#line 5157 "configure"' > conftest.$ac_ext
if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
(eval $ac_compile) 2>&5
ac_status=$?
@@ -6676,11 +6679,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:6679: $lt_compile\"" >&5)
+ (eval echo "\"\$as_me:6682: $lt_compile\"" >&5)
(eval "$lt_compile" 2>conftest.err)
ac_status=$?
cat conftest.err >&5
- echo "$as_me:6683: \$? = $ac_status" >&5
+ echo "$as_me:6686: \$? = $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.
@@ -7015,11 +7018,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:7018: $lt_compile\"" >&5)
+ (eval echo "\"\$as_me:7021: $lt_compile\"" >&5)
(eval "$lt_compile" 2>conftest.err)
ac_status=$?
cat conftest.err >&5
- echo "$as_me:7022: \$? = $ac_status" >&5
+ echo "$as_me:7025: \$? = $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.
@@ -7120,11 +7123,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:7123: $lt_compile\"" >&5)
+ (eval echo "\"\$as_me:7126: $lt_compile\"" >&5)
(eval "$lt_compile" 2>out/conftest.err)
ac_status=$?
cat out/conftest.err >&5
- echo "$as_me:7127: \$? = $ac_status" >&5
+ echo "$as_me:7130: \$? = $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
@@ -7175,11 +7178,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:7178: $lt_compile\"" >&5)
+ (eval echo "\"\$as_me:7181: $lt_compile\"" >&5)
(eval "$lt_compile" 2>out/conftest.err)
ac_status=$?
cat out/conftest.err >&5
- echo "$as_me:7182: \$? = $ac_status" >&5
+ echo "$as_me:7185: \$? = $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
@@ -9555,7 +9558,7 @@ else
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
lt_status=$lt_dlunknown
cat > conftest.$ac_ext <<_LT_EOF
-#line 9558 "configure"
+#line 9561 "configure"
#include "confdefs.h"
#if HAVE_DLFCN_H
@@ -9651,7 +9654,7 @@ else
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
lt_status=$lt_dlunknown
cat > conftest.$ac_ext <<_LT_EOF
-#line 9654 "configure"
+#line 9657 "configure"
#include "confdefs.h"
#if HAVE_DLFCN_H
@@ -10330,6 +10333,7 @@ else
use_tcl=yes
fi
+original_use_tcl=${use_tcl}
if test x"${with_tclsh}" == x -a x"${with_tcl}" == x; then
for ac_prog in tclsh8.6 tclsh tclsh9.0
do
@@ -10388,6 +10392,7 @@ $as_echo "$TCLSH_CMD recommends the tclConfig.sh at ${with_tcl}" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $TCLSH_CMD is unable to recommend a tclConfig.sh" >&5
$as_echo "$as_me: WARNING: $TCLSH_CMD is unable to recommend a tclConfig.sh" >&2;}
+ use_tcl=no
fi
fi
fi
@@ -10438,10 +10443,14 @@ $as_echo "loading TCL configuration from ${tclconfig}" >&6; }
# tclConfig.sh file and that could be included here. But as of right now,
# TCL_LIB_SPEC is the only what that the Makefile uses.
HAVE_TCL=1
-else
+elif test x"${original_use_tcl}" = "xno"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: unable to run tests because of --disable-tcl" >&5
$as_echo "unable to run tests because of --disable-tcl" >&6; }
HAVE_TCL=0
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: unable to run tests because no tclConfig.sh file could be located" >&5
+$as_echo "unable to run tests because no tclConfig.sh file could be located" >&6; }
+ HAVE_TCL=0
fi
if test x"$TCLSH_CMD" == x; then
@@ -10478,6 +10487,23 @@ if test "x${TCLLIBDIR+set}" != "xset" ; then
TCLLIBDIR="${TCLLIBDIR}/sqlite3"
fi
+#########
+# Set up options for running tests.
+#
+# Check whether --enable-test-status was given.
+if test "${enable_test_status+set}" = set; then :
+ enableval=$enable_test_status; use_vt100=$enableval
+else
+ use_vt100=no
+fi
+
+if test $use_vt100 != no; then
+ TSTRNNR_OPTS=--status
+else
+ TSTRNNR_OPTS=
+fi
+
+
#########
# Set up an appropriate program prefix
diff --git a/configure.ac b/configure.ac
index 4f95a5c08c..b3a0dd299f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -123,6 +123,7 @@ AC_ARG_WITH(tclsh, AS_HELP_STRING([--with-tclsh=PATHNAME],[full pathname of a tc
AC_ARG_WITH(tcl, AS_HELP_STRING([--with-tcl=DIR],[directory containing (tclConfig.sh)]))
AC_ARG_ENABLE(tcl, AS_HELP_STRING([--disable-tcl],[omit building accessory programs that require TCL-dev]),
[use_tcl=$enableval],[use_tcl=yes])
+original_use_tcl=${use_tcl}
if test x"${with_tclsh}" == x -a x"${with_tcl}" == x; then
AC_CHECK_PROGS(TCLSH_CMD, [tclsh8.6 tclsh tclsh9.0],none)
with_tclsh=${TCLSH_CMD}
@@ -136,6 +137,7 @@ if test x"${with_tclsh}" != x -a x"${with_tclsh}" != xnone; then
AC_MSG_RESULT([$TCLSH_CMD recommends the tclConfig.sh at ${with_tcl}])
else
AC_MSG_WARN([$TCLSH_CMD is unable to recommend a tclConfig.sh])
+ use_tcl=no
fi
fi
fi
@@ -185,9 +187,12 @@ if test x"${use_tcl}" = "xyes"; then
# tclConfig.sh file and that could be included here. But as of right now,
# TCL_LIB_SPEC is the only what that the Makefile uses.
HAVE_TCL=1
-else
+elif test x"${original_use_tcl}" = "xno"; then
AC_MSG_RESULT([unable to run tests because of --disable-tcl])
HAVE_TCL=0
+else
+ AC_MSG_RESULT([unable to run tests because no tclConfig.sh file could be located])
+ HAVE_TCL=0
fi
AC_SUBST(HAVE_TCL)
if test x"$TCLSH_CMD" == x; then
@@ -222,6 +227,18 @@ if test "x${TCLLIBDIR+set}" != "xset" ; then
TCLLIBDIR="${TCLLIBDIR}/sqlite3"
fi
+#########
+# Set up options for running tests.
+#
+AC_ARG_ENABLE(test-status, AS_HELP_STRING([--enable-test-status],[Full-screen status of tests]),
+ [use_vt100=$enableval],[use_vt100=no])
+if test $use_vt100 != no; then
+ TSTRNNR_OPTS=--status
+else
+ TSTRNNR_OPTS=
+fi
+AC_SUBST(TSTRNNR_OPTS)
+
#########
# Set up an appropriate program prefix
diff --git a/ext/consio/console_io.c b/ext/consio/console_io.c
index 3fa613ba9d..6b837cbafc 100755
--- a/ext/consio/console_io.c
+++ b/ext/consio/console_io.c
@@ -595,6 +595,86 @@ oPutbUtf8(const char *cBuf, int nAccept){
# endif
}
+/*
+** Flush the given output stream. Return non-zero for success, else 0.
+*/
+#if !defined(SQLITE_CIO_NO_FLUSH) && !defined(SQLITE_CIO_NO_SETMODE)
+SQLITE_INTERNAL_LINKAGE int
+fFlushBuffer(FILE *pfOut){
+# if CIO_WIN_WC_XLATE && !defined(SHELL_OMIT_FIO_DUPE)
+ return FlushFileBuffers(handleOfFile(pfOut))? 1 : 0;
+# else
+ return fflush(pfOut);
+# endif
+}
+#endif
+
+#if CIO_WIN_WC_XLATE \
+ && !defined(SHELL_OMIT_FIO_DUPE) \
+ && defined(SQLITE_USE_ONLY_WIN32)
+static struct FileAltIds {
+ int fd;
+ HANDLE fh;
+} altIdsOfFile(FILE *pf){
+ struct FileAltIds rv = { _fileno(pf) };
+ union { intptr_t osfh; HANDLE fh; } fid = {
+ (rv.fd>=0)? _get_osfhandle(rv.fd) : (intptr_t)INVALID_HANDLE_VALUE
+ };
+ rv.fh = fid.fh;
+ return rv;
+}
+
+SQLITE_INTERNAL_LINKAGE size_t
+cfWrite(const void *buf, size_t osz, size_t ocnt, FILE *pf){
+ size_t rv = 0;
+ struct FileAltIds fai = altIdsOfFile(pf);
+ int fmode = _setmode(fai.fd, _O_BINARY);
+ _setmode(fai.fd, fmode);
+ while( rv < ocnt ){
+ size_t nbo = osz;
+ while( nbo > 0 ){
+ DWORD dwno = (nbo>(1L<<24))? 1L<<24 : (DWORD)nbo;
+ BOOL wrc = TRUE;
+ BOOL genCR = (fmode & _O_TEXT)!=0;
+ if( genCR ){
+ const char *pnl = (const char*)memchr(buf, '\n', nbo);
+ if( pnl ) nbo = pnl - (const char*)buf;
+ else genCR = 0;
+ }
+ if( dwno>0 ) wrc = WriteFile(fai.fh, buf, dwno, 0,0);
+ if( genCR && wrc ){
+ wrc = WriteFile(fai.fh, "\r\n", 2, 0,0);
+ ++dwno; /* Skip over the LF */
+ }
+ if( !wrc ) return rv;
+ buf = (const char*)buf + dwno;
+ nbo += dwno;
+ }
+ ++rv;
+ }
+ return rv;
+}
+
+SQLITE_INTERNAL_LINKAGE char *
+cfGets(char *cBuf, int n, FILE *pf){
+ int nci = 0;
+ struct FileAltIds fai = altIdsOfFile(pf);
+ int fmode = _setmode(fai.fd, _O_BINARY);
+ BOOL eatCR = (fmode & _O_TEXT)!=0;
+ _setmode(fai.fd, fmode);
+ while( nci < n-1 ){
+ DWORD nr;
+ if( !ReadFile(fai.fh, cBuf+nci, 1, &nr, 0) || nr==0 ) break;
+ if( nr>0 && (!eatCR || cBuf[nci]!='\r') ) nci += nr;
+ }
+ if( nci < n ) cBuf[nci] = 0;
+ return (nci>0)? cBuf : 0;
+}
+# else
+# define cfWrite(b,os,no,f) fwrite(b,os,no,f)
+# define cfGets(b,n,f) fgets(b,n,f)
+# endif
+
# ifdef CONSIO_EPUTB
SQLITE_INTERNAL_LINKAGE int
ePutbUtf8(const char *cBuf, int nAccept){
@@ -606,7 +686,7 @@ ePutbUtf8(const char *cBuf, int nAccept){
return conZstrEmit(ppst, cBuf, nAccept);
}else {
# endif
- return (int)fwrite(cBuf, 1, nAccept, pfErr);
+ return (int)cfWrite(cBuf, 1, nAccept, pfErr);
# if CIO_WIN_WC_XLATE
}
# endif
@@ -672,7 +752,7 @@ SQLITE_INTERNAL_LINKAGE char* fGetsUtf8(char *cBuf, int ncMax, FILE *pfIn){
# endif
}else{
# endif
- return fgets(cBuf, ncMax, pfIn);
+ return cfGets(cBuf, ncMax, pfIn);
# if CIO_WIN_WC_XLATE
}
# endif
diff --git a/ext/consio/console_io.h b/ext/consio/console_io.h
index 26fd7dd946..1affa15bad 100644
--- a/ext/consio/console_io.h
+++ b/ext/consio/console_io.h
@@ -176,12 +176,19 @@ SQLITE_INTERNAL_LINKAGE int
ePutbUtf8(const char *cBuf, int nAccept);
#endif
+/*
+** Flush the given output stream. Return non-zero for success, else 0.
+*/
+#if !defined(SQLITE_CIO_NO_FLUSH) && !defined(SQLITE_CIO_NO_SETMODE)
+SQLITE_INTERNAL_LINKAGE int
+fFlushBuffer(FILE *pfOut);
+#endif
+
/*
** Collect input like fgets(...) with special provisions for input
-** from the console on platforms that require same. Defers to the
-** C library fgets() when input is not from the console. Newline
-** translation may be done as set by set{Binary,Text}Mode(). As a
-** convenience, pfIn==NULL is treated as stdin.
+** from the console on such platforms as require same. Newline
+** translation may be done as set by set{Binary,Text}Mode().
+** As a convenience, pfIn==NULL is treated as stdin.
*/
SQLITE_INTERNAL_LINKAGE char* fGetsUtf8(char *cBuf, int ncMax, FILE *pfIn);
/* Like fGetsUtf8 except stream is always the designated input. */
diff --git a/ext/fts3/unicode/mkunicode.tcl b/ext/fts3/unicode/mkunicode.tcl
index 58d90c68c7..1306629da8 100644
--- a/ext/fts3/unicode/mkunicode.tcl
+++ b/ext/fts3/unicode/mkunicode.tcl
@@ -628,6 +628,9 @@ proc print_categories {lMap} {
$caseP
$caseS
$caseZ
+
+ default:
+ return 1;
}
return 0;
}
diff --git a/ext/fts5/extract_api_docs.tcl b/ext/fts5/extract_api_docs.tcl
index 6762a036d5..6ee71c262c 100644
--- a/ext/fts5/extract_api_docs.tcl
+++ b/ext/fts5/extract_api_docs.tcl
@@ -82,7 +82,7 @@ proc get_struct_docs {data names} {
set current_doc ""
}
set subject n/a
- regexp {^ *([[:alpha:]]*)} $line -> subject
+ regexp {^ *([[:alnum:]_]*)} $line -> subject
if {[lsearch $names $subject]>=0} {
set current_header $subject
} else {
@@ -108,8 +108,11 @@ proc get_tokenizer_docs {data} {
append res "$line\n"
continue
}
+ if {[regexp {FTS5_TOKENIZER} $line]} {
+ set line
+ }
if {[regexp {SYNONYM SUPPORT} $line]} {
- set line "
Synonym Support
"
+ set line "Synonym Support
"
}
if {[string trim $line] == ""} {
append res "\n"
diff --git a/ext/fts5/fts5.h b/ext/fts5/fts5.h
index d3042fcb8c..ea69f8f023 100644
--- a/ext/fts5/fts5.h
+++ b/ext/fts5/fts5.h
@@ -308,9 +308,32 @@ struct Fts5PhraseIter {
**
** This API can be quite slow if used with an FTS5 table created with the
** "detail=none" or "detail=column" option.
+**
+** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale)
+** If parameter iCol is less than zero, or greater than or equal to the
+** number of columns in the table, SQLITE_RANGE is returned.
+**
+** Otherwise, this function attempts to retrieve the locale associated
+** with column iCol of the current row. Usually, there is no associated
+** locale, and output parameters (*pzLocale) and (*pnLocale) are set
+** to NULL and 0, respectively. However, if the fts5_locale() function
+** was used to associate a locale with the value when it was inserted
+** into the fts5 table, then (*pzLocale) is set to point to a nul-terminated
+** buffer containing the name of the locale in utf-8 encoding. (*pnLocale)
+** is set to the size in bytes of the buffer, not including the
+** nul-terminator.
+**
+** If successful, SQLITE_OK is returned. Or, if an error occurs, an
+** SQLite error code is returned. The final value of the output parameters
+** is undefined in this case.
+**
+** xTokenize_v2:
+** Tokenize text using the tokenizer belonging to the FTS5 table. This
+** API is the same as the xTokenize() API, except that it allows a tokenizer
+** locale to be specified.
*/
struct Fts5ExtensionApi {
- int iVersion; /* Currently always set to 3 */
+ int iVersion; /* Currently always set to 4 */
void *(*xUserData)(Fts5Context*);
@@ -352,6 +375,15 @@ struct Fts5ExtensionApi {
const char **ppToken, int *pnToken
);
int (*xInstToken)(Fts5Context*, int iIdx, int iToken, const char**, int*);
+
+ /* Below this point are iVersion>=4 only */
+ int (*xColumnLocale)(Fts5Context*, int iCol, const char **pz, int *pn);
+ int (*xTokenize_v2)(Fts5Context*,
+ const char *pText, int nText, /* Text to tokenize */
+ const char *pLocale, int nLocale, /* Locale to pass to tokenizer */
+ void *pCtx, /* Context passed to xToken() */
+ int (*xToken)(void*, int, const char*, int, int, int) /* Callback */
+ );
};
/*
@@ -364,6 +396,7 @@ struct Fts5ExtensionApi {
** Applications may also register custom tokenizer types. A tokenizer
** is registered by providing fts5 with a populated instance of the
** following structure. All structure methods must be defined, setting
+**
** any member of the fts5_tokenizer struct to NULL leads to undefined
** behaviour. The structure methods are expected to function as follows:
**
@@ -372,7 +405,7 @@ struct Fts5ExtensionApi {
** A tokenizer instance is required to actually tokenize text.
**
** The first argument passed to this function is a copy of the (void*)
-** pointer provided by the application when the fts5_tokenizer object
+** pointer provided by the application when the fts5_tokenizer_v2 object
** was registered with FTS5 (the third argument to xCreateTokenizer()).
** The second and third arguments are an array of nul-terminated strings
** containing the tokenizer arguments, if any, specified following the
@@ -396,7 +429,7 @@ struct Fts5ExtensionApi {
** argument passed to this function is a pointer to an Fts5Tokenizer object
** returned by an earlier call to xCreate().
**
-** The second argument indicates the reason that FTS5 is requesting
+** The third argument indicates the reason that FTS5 is requesting
** tokenization of the supplied text. This is always one of the following
** four values:
**
@@ -420,6 +453,13 @@ struct Fts5ExtensionApi {
** on a columnsize=0 database.
**
**
+** The sixth and seventh arguments passed to xTokenize() - pLocale and
+** nLocale - are a pointer to a buffer containing the locale to use for
+** tokenization (e.g. "en_US") and its size in bytes, respectively. The
+** pLocale buffer is not nul-terminated. pLocale may be passed NULL (in
+** which case nLocale is always 0) to indicate that the tokenizer should
+** use its default locale.
+**
** For each token in the input string, the supplied callback xToken() must
** be invoked. The first argument to it should be a copy of the pointer
** passed as the second argument to xTokenize(). The third and fourth
@@ -443,6 +483,30 @@ struct Fts5ExtensionApi {
** may abandon the tokenization and return any error code other than
** SQLITE_OK or SQLITE_DONE.
**
+** If the tokenizer is registered using an fts5_tokenizer_v2 object,
+** then the xTokenize() method has two additional arguments - pLocale
+** and nLocale. These specify the locale that the tokenizer should use
+** for the current request. If pLocale and nLocale are both 0, then the
+** tokenizer should use its default locale. Otherwise, pLocale points to
+** an nLocale byte buffer containing the name of the locale to use as utf-8
+** text. pLocale is not nul-terminated.
+**
+** FTS5_TOKENIZER
+**
+** There is also an fts5_tokenizer object. This is an older, deprecated,
+** version of fts5_tokenizer_v2. It is similar except that:
+**
+**
+** - There is no "iVersion" field, and
+**
- The xTokenize() method does not take a locale argument.
+**
+**
+** Legacy fts5_tokenizer tokenizers must be registered using the
+** legacy xCreateTokenizer() function, instead of xCreateTokenizer_v2().
+**
+** Tokenizer implementations registered using either API may be retrieved
+** using both xFindTokenizer() and xFindTokenizer_v2().
+**
** SYNONYM SUPPORT
**
** Custom tokenizers may also support synonyms. Consider a case in which a
@@ -551,6 +615,33 @@ struct Fts5ExtensionApi {
** inefficient.
*/
typedef struct Fts5Tokenizer Fts5Tokenizer;
+typedef struct fts5_tokenizer_v2 fts5_tokenizer_v2;
+struct fts5_tokenizer_v2 {
+ int iVersion; /* Currently always 2 */
+
+ int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut);
+ void (*xDelete)(Fts5Tokenizer*);
+ int (*xTokenize)(Fts5Tokenizer*,
+ void *pCtx,
+ int flags, /* Mask of FTS5_TOKENIZE_* flags */
+ const char *pText, int nText,
+ const char *pLocale, int nLocale,
+ int (*xToken)(
+ void *pCtx, /* Copy of 2nd argument to xTokenize() */
+ int tflags, /* Mask of FTS5_TOKEN_* flags */
+ const char *pToken, /* Pointer to buffer containing token */
+ int nToken, /* Size of token in bytes */
+ int iStart, /* Byte offset of token within input text */
+ int iEnd /* Byte offset of end of token within input text */
+ )
+ );
+};
+
+/*
+** New code should use the fts5_tokenizer_v2 type to define tokenizer
+** implementations. The following type is included for legacy applications
+** that still use it.
+*/
typedef struct fts5_tokenizer fts5_tokenizer;
struct fts5_tokenizer {
int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut);
@@ -570,6 +661,7 @@ struct fts5_tokenizer {
);
};
+
/* Flags that may be passed as the third argument to xTokenize() */
#define FTS5_TOKENIZE_QUERY 0x0001
#define FTS5_TOKENIZE_PREFIX 0x0002
@@ -589,7 +681,7 @@ struct fts5_tokenizer {
*/
typedef struct fts5_api fts5_api;
struct fts5_api {
- int iVersion; /* Currently always set to 2 */
+ int iVersion; /* Currently always set to 3 */
/* Create a new tokenizer */
int (*xCreateTokenizer)(
@@ -616,6 +708,25 @@ struct fts5_api {
fts5_extension_function xFunction,
void (*xDestroy)(void*)
);
+
+ /* APIs below this point are only available if iVersion>=3 */
+
+ /* Create a new tokenizer */
+ int (*xCreateTokenizer_v2)(
+ fts5_api *pApi,
+ const char *zName,
+ void *pUserData,
+ fts5_tokenizer_v2 *pTokenizer,
+ void (*xDestroy)(void*)
+ );
+
+ /* Find an existing tokenizer */
+ int (*xFindTokenizer_v2)(
+ fts5_api *pApi,
+ const char *zName,
+ void **ppUserData,
+ fts5_tokenizer_v2 **ppTokenizer
+ );
};
/*
diff --git a/ext/fts5/fts5Int.h b/ext/fts5/fts5Int.h
index 4311faceb5..7e41119572 100644
--- a/ext/fts5/fts5Int.h
+++ b/ext/fts5/fts5Int.h
@@ -162,10 +162,13 @@ typedef struct Fts5TokenizerConfig Fts5TokenizerConfig;
struct Fts5TokenizerConfig {
Fts5Tokenizer *pTok;
- fts5_tokenizer *pTokApi;
+ fts5_tokenizer_v2 *pApi2;
+ fts5_tokenizer *pApi1;
const char **azArg;
int nArg;
int ePattern; /* FTS_PATTERN_XXX constant */
+ const char *pLocale; /* Current locale to use */
+ int nLocale; /* Size of pLocale in bytes */
};
/*
@@ -206,6 +209,8 @@ struct Fts5TokenizerConfig {
**
** INSERT INTO tbl(tbl, rank) VALUES('prefix-index', $bPrefixIndex);
**
+** bLocale:
+** Set to true if locale=1 was specified when the table was created.
*/
struct Fts5Config {
sqlite3 *db; /* Database handle */
@@ -223,10 +228,12 @@ struct Fts5Config {
char *zContentRowid; /* "content_rowid=" option value */
int bColumnsize; /* "columnsize=" option value (dflt==1) */
int bTokendata; /* "tokendata=" option value (dflt==0) */
+ int bLocale; /* "locale=" option value (dflt==0) */
int eDetail; /* FTS5_DETAIL_XXX value */
char *zContentExprlist;
Fts5TokenizerConfig t;
int bLock; /* True when table is preparing statement */
+
/* Values loaded from the %_config table */
int iVersion; /* fts5 file format 'version' */
@@ -292,6 +299,8 @@ int sqlite3Fts5ConfigSetValue(Fts5Config*, const char*, sqlite3_value*, int*);
int sqlite3Fts5ConfigParseRank(const char*, char**, char**);
+void sqlite3Fts5ConfigErrmsg(Fts5Config *pConfig, const char *zFmt, ...);
+
/*
** End of interface to code in fts5_config.c.
**************************************************************************/
@@ -336,7 +345,7 @@ char *sqlite3Fts5Mprintf(int *pRc, const char *zFmt, ...);
void sqlite3Fts5Put32(u8*, int);
int sqlite3Fts5Get32(const u8*);
-#define FTS5_POS2COLUMN(iPos) (int)(iPos >> 32)
+#define FTS5_POS2COLUMN(iPos) (int)((iPos >> 32) & 0x7FFFFFFF)
#define FTS5_POS2OFFSET(iPos) (int)(iPos & 0x7FFFFFFF)
typedef struct Fts5PoslistReader Fts5PoslistReader;
@@ -627,6 +636,17 @@ Fts5Table *sqlite3Fts5TableFromCsrid(Fts5Global*, i64);
int sqlite3Fts5FlushToDisk(Fts5Table*);
+int sqlite3Fts5ExtractText(
+ Fts5Config *pConfig,
+ sqlite3_value *pVal, /* Value to extract text from */
+ int bContent, /* Loaded from content table */
+ int *pbResetTokenizer, /* OUT: True if ClearLocale() required */
+ const char **ppText, /* OUT: Pointer to text buffer */
+ int *pnText /* OUT: Size of (*ppText) in bytes */
+);
+
+void sqlite3Fts5ClearLocale(Fts5Config *pConfig);
+
/*
** End of interface to code in fts5.c.
**************************************************************************/
@@ -706,7 +726,7 @@ int sqlite3Fts5StorageRename(Fts5Storage*, const char *zName);
int sqlite3Fts5DropAll(Fts5Config*);
int sqlite3Fts5CreateTable(Fts5Config*, const char*, const char*, int, char **);
-int sqlite3Fts5StorageDelete(Fts5Storage *p, i64, sqlite3_value**);
+int sqlite3Fts5StorageDelete(Fts5Storage *p, i64, sqlite3_value**, int);
int sqlite3Fts5StorageContentInsert(Fts5Storage *p, sqlite3_value**, i64*);
int sqlite3Fts5StorageIndexInsert(Fts5Storage *p, sqlite3_value**, i64);
@@ -732,6 +752,9 @@ int sqlite3Fts5StorageOptimize(Fts5Storage *p);
int sqlite3Fts5StorageMerge(Fts5Storage *p, int nMerge);
int sqlite3Fts5StorageReset(Fts5Storage *p);
+void sqlite3Fts5StorageReleaseDeleteRow(Fts5Storage*);
+int sqlite3Fts5StorageFindDeleteRow(Fts5Storage *p, i64 iDel);
+
/*
** End of interface to code in fts5_storage.c.
**************************************************************************/
diff --git a/ext/fts5/fts5_aux.c b/ext/fts5/fts5_aux.c
index 30101fbe25..ad578156da 100644
--- a/ext/fts5/fts5_aux.c
+++ b/ext/fts5/fts5_aux.c
@@ -226,6 +226,7 @@ static int fts5HighlightCb(
return rc;
}
+
/*
** Implementation of highlight() function.
*/
@@ -256,12 +257,19 @@ static void fts5HighlightFunction(
sqlite3_result_text(pCtx, "", -1, SQLITE_STATIC);
rc = SQLITE_OK;
}else if( ctx.zIn ){
+ const char *pLoc = 0; /* Locale of column iCol */
+ int nLoc = 0; /* Size of pLoc in bytes */
if( rc==SQLITE_OK ){
rc = fts5CInstIterInit(pApi, pFts, iCol, &ctx.iter);
}
if( rc==SQLITE_OK ){
- rc = pApi->xTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb);
+ rc = pApi->xColumnLocale(pFts, iCol, &pLoc, &nLoc);
+ }
+ if( rc==SQLITE_OK ){
+ rc = pApi->xTokenize_v2(
+ pFts, ctx.zIn, ctx.nIn, pLoc, nLoc, (void*)&ctx, fts5HighlightCb
+ );
}
if( ctx.bOpen ){
fts5HighlightAppend(&rc, &ctx, ctx.zClose, -1);
@@ -458,6 +466,8 @@ static void fts5SnippetFunction(
memset(&sFinder, 0, sizeof(Fts5SFinder));
for(i=0; ixColumnText(pFts, i, &sFinder.zDoc, &nDoc);
if( rc!=SQLITE_OK ) break;
- rc = pApi->xTokenize(pFts,
- sFinder.zDoc, nDoc, (void*)&sFinder,fts5SentenceFinderCb
+ rc = pApi->xColumnLocale(pFts, i, &pLoc, &nLoc);
+ if( rc!=SQLITE_OK ) break;
+ rc = pApi->xTokenize_v2(pFts,
+ sFinder.zDoc, nDoc, pLoc, nLoc, (void*)&sFinder, fts5SentenceFinderCb
);
if( rc!=SQLITE_OK ) break;
rc = pApi->xColumnSize(pFts, i, &nDocsize);
@@ -524,6 +536,9 @@ static void fts5SnippetFunction(
rc = pApi->xColumnSize(pFts, iBestCol, &nColSize);
}
if( ctx.zIn ){
+ const char *pLoc = 0; /* Locale of column iBestCol */
+ int nLoc = 0; /* Bytes in pLoc */
+
if( rc==SQLITE_OK ){
rc = fts5CInstIterInit(pApi, pFts, iBestCol, &ctx.iter);
}
@@ -542,7 +557,12 @@ static void fts5SnippetFunction(
}
if( rc==SQLITE_OK ){
- rc = pApi->xTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb);
+ rc = pApi->xColumnLocale(pFts, iBestCol, &pLoc, &nLoc);
+ }
+ if( rc==SQLITE_OK ){
+ rc = pApi->xTokenize_v2(
+ pFts, ctx.zIn, ctx.nIn, pLoc, nLoc, (void*)&ctx,fts5HighlightCb
+ );
}
if( ctx.bOpen ){
fts5HighlightAppend(&rc, &ctx, ctx.zClose, -1);
@@ -726,6 +746,53 @@ static void fts5Bm25Function(
}
}
+/*
+** Implementation of fts5_get_locale() function.
+*/
+static void fts5GetLocaleFunction(
+ const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
+ Fts5Context *pFts, /* First arg to pass to pApi functions */
+ sqlite3_context *pCtx, /* Context for returning result/error */
+ int nVal, /* Number of values in apVal[] array */
+ sqlite3_value **apVal /* Array of trailing arguments */
+){
+ int iCol = 0;
+ int eType = 0;
+ int rc = SQLITE_OK;
+ const char *zLocale = 0;
+ int nLocale = 0;
+
+ /* xColumnLocale() must be available */
+ assert( pApi->iVersion>=4 );
+
+ if( nVal!=1 ){
+ const char *z = "wrong number of arguments to function fts5_get_locale()";
+ sqlite3_result_error(pCtx, z, -1);
+ return;
+ }
+
+ eType = sqlite3_value_numeric_type(apVal[0]);
+ if( eType!=SQLITE_INTEGER ){
+ const char *z = "non-integer argument passed to function fts5_get_locale()";
+ sqlite3_result_error(pCtx, z, -1);
+ return;
+ }
+
+ iCol = sqlite3_value_int(apVal[0]);
+ if( iCol<0 || iCol>=pApi->xColumnCount(pFts) ){
+ sqlite3_result_error_code(pCtx, SQLITE_RANGE);
+ return;
+ }
+
+ rc = pApi->xColumnLocale(pFts, iCol, &zLocale, &nLocale);
+ if( rc!=SQLITE_OK ){
+ sqlite3_result_error_code(pCtx, rc);
+ return;
+ }
+
+ sqlite3_result_text(pCtx, zLocale, nLocale, SQLITE_TRANSIENT);
+}
+
int sqlite3Fts5AuxInit(fts5_api *pApi){
struct Builtin {
const char *zFunc; /* Function name (nul-terminated) */
@@ -733,9 +800,10 @@ int sqlite3Fts5AuxInit(fts5_api *pApi){
fts5_extension_function xFunc;/* Callback function */
void (*xDestroy)(void*); /* Destructor function */
} aBuiltin [] = {
- { "snippet", 0, fts5SnippetFunction, 0 },
- { "highlight", 0, fts5HighlightFunction, 0 },
- { "bm25", 0, fts5Bm25Function, 0 },
+ { "snippet", 0, fts5SnippetFunction, 0 },
+ { "highlight", 0, fts5HighlightFunction, 0 },
+ { "bm25", 0, fts5Bm25Function, 0 },
+ { "fts5_get_locale", 0, fts5GetLocaleFunction, 0 },
};
int rc = SQLITE_OK; /* Return code */
int i; /* To iterate through builtin functions */
diff --git a/ext/fts5/fts5_config.c b/ext/fts5/fts5_config.c
index 01f40455a0..3cb1bd3bea 100644
--- a/ext/fts5/fts5_config.c
+++ b/ext/fts5/fts5_config.c
@@ -380,6 +380,16 @@ static int fts5ConfigParseSpecial(
return rc;
}
+ if( sqlite3_strnicmp("locale", zCmd, nCmd)==0 ){
+ if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){
+ *pzErr = sqlite3_mprintf("malformed locale=... directive");
+ rc = SQLITE_ERROR;
+ }else{
+ pConfig->bLocale = (zArg[0]=='1');
+ }
+ return rc;
+ }
+
if( sqlite3_strnicmp("detail", zCmd, nCmd)==0 ){
const Fts5Enum aDetail[] = {
{ "none", FTS5_DETAIL_NONE },
@@ -669,7 +679,11 @@ void sqlite3Fts5ConfigFree(Fts5Config *pConfig){
if( pConfig ){
int i;
if( pConfig->t.pTok ){
- pConfig->t.pTokApi->xDelete(pConfig->t.pTok);
+ if( pConfig->t.pApi1 ){
+ pConfig->t.pApi1->xDelete(pConfig->t.pTok);
+ }else{
+ pConfig->t.pApi2->xDelete(pConfig->t.pTok);
+ }
}
sqlite3_free((char*)pConfig->t.azArg);
sqlite3_free(pConfig->zDb);
@@ -752,9 +766,15 @@ int sqlite3Fts5Tokenize(
rc = sqlite3Fts5LoadTokenizer(pConfig);
}
if( rc==SQLITE_OK ){
- rc = pConfig->t.pTokApi->xTokenize(
- pConfig->t.pTok, pCtx, flags, pText, nText, xToken
- );
+ if( pConfig->t.pApi1 ){
+ rc = pConfig->t.pApi1->xTokenize(
+ pConfig->t.pTok, pCtx, flags, pText, nText, xToken
+ );
+ }else{
+ rc = pConfig->t.pApi2->xTokenize(pConfig->t.pTok, pCtx, flags,
+ pText, nText, pConfig->t.pLocale, pConfig->t.nLocale, xToken
+ );
+ }
}
}
return rc;
@@ -1011,13 +1031,10 @@ int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){
&& iVersion!=FTS5_CURRENT_VERSION_SECUREDELETE
){
rc = SQLITE_ERROR;
- if( pConfig->pzErrmsg ){
- assert( 0==*pConfig->pzErrmsg );
- *pConfig->pzErrmsg = sqlite3_mprintf("invalid fts5 file format "
- "(found %d, expected %d or %d) - run 'rebuild'",
- iVersion, FTS5_CURRENT_VERSION, FTS5_CURRENT_VERSION_SECUREDELETE
- );
- }
+ sqlite3Fts5ConfigErrmsg(pConfig, "invalid fts5 file format "
+ "(found %d, expected %d or %d) - run 'rebuild'",
+ iVersion, FTS5_CURRENT_VERSION, FTS5_CURRENT_VERSION_SECUREDELETE
+ );
}else{
pConfig->iVersion = iVersion;
}
@@ -1027,3 +1044,26 @@ int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){
}
return rc;
}
+
+/*
+** Set (*pConfig->pzErrmsg) to point to an sqlite3_malloc()ed buffer
+** containing the error message created using printf() style formatting
+** string zFmt and its trailing arguments.
+*/
+void sqlite3Fts5ConfigErrmsg(Fts5Config *pConfig, const char *zFmt, ...){
+ va_list ap; /* ... printf arguments */
+ char *zMsg = 0;
+
+ va_start(ap, zFmt);
+ zMsg = sqlite3_vmprintf(zFmt, ap);
+ if( pConfig->pzErrmsg ){
+ assert( *pConfig->pzErrmsg==0 );
+ *pConfig->pzErrmsg = zMsg;
+ }else{
+ sqlite3_free(zMsg);
+ }
+
+ va_end(ap);
+}
+
+
diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c
index 1f089d8757..cd44b96bda 100644
--- a/ext/fts5/fts5_expr.c
+++ b/ext/fts5/fts5_expr.c
@@ -286,11 +286,12 @@ int sqlite3Fts5ExprNew(
}while( sParse.rc==SQLITE_OK && t!=FTS5_EOF );
sqlite3Fts5ParserFree(pEngine, fts5ParseFree);
+ assert( sParse.pExpr || sParse.rc!=SQLITE_OK );
assert_expr_depth_ok(sParse.rc, sParse.pExpr);
/* If the LHS of the MATCH expression was a user column, apply the
** implicit column-filter. */
- if( iColnCol && sParse.pExpr && sParse.rc==SQLITE_OK ){
+ if( sParse.rc==SQLITE_OK && iColnCol ){
int n = sizeof(Fts5Colset);
Fts5Colset *pColset = (Fts5Colset*)sqlite3Fts5MallocZero(&sParse.rc, n);
if( pColset ){
@@ -307,15 +308,7 @@ int sqlite3Fts5ExprNew(
sParse.rc = SQLITE_NOMEM;
sqlite3Fts5ParseNodeFree(sParse.pExpr);
}else{
- if( !sParse.pExpr ){
- const int nByte = sizeof(Fts5ExprNode);
- pNew->pRoot = (Fts5ExprNode*)sqlite3Fts5MallocZero(&sParse.rc, nByte);
- if( pNew->pRoot ){
- pNew->pRoot->bEof = 1;
- }
- }else{
- pNew->pRoot = sParse.pExpr;
- }
+ pNew->pRoot = sParse.pExpr;
pNew->pIndex = 0;
pNew->pConfig = pConfig;
pNew->apExprPhrase = sParse.apPhrase;
@@ -1133,7 +1126,7 @@ static int fts5ExprNodeTest_STRING(
}
}else{
Fts5IndexIter *pIter = pPhrase->aTerm[j].pIter;
- if( pIter->iRowid==iLast || pIter->bEof ) continue;
+ if( pIter->iRowid==iLast ) continue;
bMatch = 0;
if( fts5ExprAdvanceto(pIter, bDesc, &iLast, &rc, &pNode->bEof) ){
return rc;
@@ -1655,9 +1648,6 @@ Fts5ExprNearset *sqlite3Fts5ParseNearset(
Fts5ExprNearset *pRet = 0;
if( pParse->rc==SQLITE_OK ){
- if( pPhrase==0 ){
- return pNear;
- }
if( pNear==0 ){
sqlite3_int64 nByte;
nByte = sizeof(Fts5ExprNearset) + SZALLOC * sizeof(Fts5ExprPhrase*);
diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c
index c862e2f956..03c1bb83fa 100644
--- a/ext/fts5/fts5_main.c
+++ b/ext/fts5/fts5_main.c
@@ -103,11 +103,28 @@ struct Fts5Auxiliary {
** Each tokenizer module registered with the FTS5 module is represented
** by an object of the following type. All such objects are stored as part
** of the Fts5Global.pTok list.
+**
+** bV2Native:
+** True if the tokenizer was registered using xCreateTokenizer_v2(), false
+** for xCreateTokenizer(). If this variable is true, then x2 is populated
+** with the routines as supplied by the caller and x1 contains synthesized
+** wrapper routines. In this case the user-data pointer passed to
+** x1.xCreate should be a pointer to the Fts5TokenizerModule structure,
+** not a copy of pUserData.
+**
+** Of course, if bV2Native is false, then x1 contains the real routines and
+** x2 the synthesized ones. In this case a pointer to the Fts5TokenizerModule
+** object should be passed to x2.xCreate.
+**
+** The synthesized wrapper routines are necessary for xFindTokenizer(_v2)
+** calls.
*/
struct Fts5TokenizerModule {
char *zName; /* Name of tokenizer */
void *pUserData; /* User pointer passed to xCreate() */
- fts5_tokenizer x; /* Tokenizer functions */
+ int bV2Native; /* True if v2 native tokenizer */
+ fts5_tokenizer x1; /* Tokenizer functions */
+ fts5_tokenizer_v2 x2; /* V2 tokenizer functions */
void (*xDestroy)(void*); /* Destructor function */
Fts5TokenizerModule *pNext; /* Next registered tokenizer module */
};
@@ -118,7 +135,7 @@ struct Fts5FullTable {
Fts5Global *pGlobal; /* Global (connection wide) data */
Fts5Cursor *pSortCsr; /* Sort data from this cursor */
int iSavepoint; /* Successful xSavepoint()+1 */
-
+
#ifdef SQLITE_DEBUG
struct Fts5TransactionState ts;
#endif
@@ -195,7 +212,7 @@ struct Fts5Cursor {
Fts5Auxiliary *pAux; /* Currently executing extension function */
Fts5Auxdata *pAuxdata; /* First in linked list of saved aux-data */
- /* Cache used by auxiliary functions xInst() and xInstCount() */
+ /* Cache used by auxiliary API functions xInst() and xInstCount() */
Fts5PoslistReader *aInstIter; /* One for each phrase */
int nInstAlloc; /* Size of aInst[] array (entries / 3) */
int nInstCount; /* Number of phrase instances */
@@ -230,6 +247,12 @@ struct Fts5Cursor {
#define BitFlagAllTest(x,y) (((x) & (y))==(y))
#define BitFlagTest(x,y) (((x) & (y))!=0)
+/*
+** The subtype value and header bytes used by fts5_locale().
+*/
+#define FTS5_LOCALE_SUBTYPE ((unsigned int)'L')
+#define FTS5_LOCALE_HEADER "\x00\xE0\xB2\xEB"
+
/*
** Macros to Set(), Clear() and Test() cursor flags.
@@ -404,8 +427,7 @@ static int fts5InitVtab(
/* Load the initial configuration */
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex);
- sqlite3Fts5IndexRollback(pTab->p.pIndex);
+ rc = sqlite3Fts5ConfigLoad(pTab->p.pConfig, pTab->p.pConfig->iCookie-1);
}
if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){
@@ -607,7 +629,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
if( bSeenRank ) continue;
idxStr[iIdxStr++] = 'r';
bSeenRank = 1;
- }else if( iCol>=0 ){
+ }else{
nSeenMatch++;
idxStr[iIdxStr++] = 'M';
sqlite3_snprintf(6, &idxStr[iIdxStr], "%d", iCol);
@@ -993,7 +1015,7 @@ static int fts5PrepareStatement(
rc = sqlite3_prepare_v3(pConfig->db, zSql, -1,
SQLITE_PREPARE_PERSISTENT, &pRet, 0);
if( rc!=SQLITE_OK ){
- *pConfig->pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(pConfig->db));
+ sqlite3Fts5ConfigErrmsg(pConfig, "%s", sqlite3_errmsg(pConfig->db));
}
sqlite3_free(zSql);
}
@@ -1228,6 +1250,188 @@ static void fts5SetVtabError(Fts5FullTable *p, const char *zFormat, ...){
va_end(ap);
}
+/*
+** Arrange for subsequent calls to sqlite3Fts5Tokenize() to use the locale
+** specified by pLocale/nLocale. The buffer indicated by pLocale must remain
+** valid until after the final call to sqlite3Fts5Tokenize() that will use
+** the locale.
+*/
+static void fts5SetLocale(
+ Fts5Config *pConfig,
+ const char *zLocale,
+ int nLocale
+){
+ Fts5TokenizerConfig *pT = &pConfig->t;
+ pT->pLocale = zLocale;
+ pT->nLocale = nLocale;
+}
+
+/*
+** Clear any locale configured by an earlier call to fts5SetLocale() or
+** sqlite3Fts5ExtractText().
+*/
+void sqlite3Fts5ClearLocale(Fts5Config *pConfig){
+ fts5SetLocale(pConfig, 0, 0);
+}
+
+/*
+** This function is used to extract utf-8 text from an sqlite3_value. This
+** is usually done in order to tokenize it. For example, when:
+**
+** * a value is written to an fts5 table,
+** * a value is deleted from an FTS5_CONTENT_NORMAL table,
+** * a value containing a query expression is passed to xFilter()
+**
+** and so on.
+**
+** This function handles 2 cases:
+**
+** 1) Ordinary values. The text can be extracted from these using
+** sqlite3_value_text().
+**
+** 2) Combination text/locale blobs created by fts5_locale(). There
+** are several cases for these:
+**
+** * Blobs tagged with FTS5_LOCALE_SUBTYPE.
+** * Blobs read from the content table of a locale=1 external-content
+** table, and
+** * Blobs read from the content table of a locale=1 regular
+** content table.
+**
+** The first two cases above should have the 4 byte FTS5_LOCALE_HEADER
+** header. It is an error if a blob with the subtype or a blob read
+** from the content table of an external content table does not have
+** the required header. A blob read from the content table of a regular
+** locale=1 table does not have the header. This is to save space.
+**
+** If successful, SQLITE_OK is returned and output parameters (*ppText)
+** and (*pnText) are set to point to a buffer containing the extracted utf-8
+** text and its length in bytes, respectively. The buffer is not
+** nul-terminated. It has the same lifetime as the sqlite3_value object
+** from which it is extracted.
+**
+** Parameter bContent must be true if the value was read from an indexed
+** column (i.e. not UNINDEXED) of the on disk content.
+**
+** If pbResetTokenizer is not NULL and if case (2) is used, then
+** fts5SetLocale() is called to ensure subsequent sqlite3Fts5Tokenize() calls
+** use the locale. In this case (*pbResetTokenizer) is set to true before
+** returning, to indicate that the caller must call sqlite3Fts5ClearLocale()
+** to clear the locale after tokenizing the text.
+*/
+int sqlite3Fts5ExtractText(
+ Fts5Config *pConfig,
+ sqlite3_value *pVal, /* Value to extract text from */
+ int bContent, /* True if indexed table content */
+ int *pbResetTokenizer, /* OUT: True if xSetLocale(NULL) required */
+ const char **ppText, /* OUT: Pointer to text buffer */
+ int *pnText /* OUT: Size of (*ppText) in bytes */
+){
+ const char *pText = 0;
+ int nText = 0;
+ int rc = SQLITE_OK;
+ int bDecodeBlob = 0;
+
+ assert( pbResetTokenizer==0 || *pbResetTokenizer==0 );
+ assert( bContent==0 || pConfig->eContent!=FTS5_CONTENT_NONE );
+ assert( bContent==0 || sqlite3_value_subtype(pVal)==0 );
+
+ if( sqlite3_value_type(pVal)==SQLITE_BLOB ){
+ if( sqlite3_value_subtype(pVal)==FTS5_LOCALE_SUBTYPE
+ || (bContent && pConfig->bLocale)
+ ){
+ bDecodeBlob = 1;
+ }
+ }
+
+ if( bDecodeBlob ){
+ const int SZHDR = sizeof(FTS5_LOCALE_HEADER)-1;
+ const u8 *pBlob = sqlite3_value_blob(pVal);
+ int nBlob = sqlite3_value_bytes(pVal);
+
+ /* Unless this blob was read from the %_content table of an
+ ** FTS5_CONTENT_NORMAL table, it should have the 4 byte fts5_locale()
+ ** header. Check for this. If it is not found, return an error. */
+ if( (!bContent || pConfig->eContent!=FTS5_CONTENT_NORMAL) ){
+ if( nBlobbLock ){
- pTab->p.base.zErrMsg = sqlite3_mprintf(
- "recursively defined fts5 content table"
- );
- return SQLITE_ERROR;
- }
-
+ assert( pConfig->bLock==0 );
if( pCsr->ePlan ){
fts5FreeCursorComponents(pCsr);
memset(&pCsr->ePlan, 0, sizeof(Fts5Cursor) - ((u8*)&pCsr->ePlan-(u8*)pCsr));
@@ -1293,8 +1491,14 @@ static int fts5FilterMethod(
pRank = apVal[i];
break;
case 'M': {
- const char *zText = (const char*)sqlite3_value_text(apVal[i]);
+ char *zText = 0;
+ int bFreeAndReset = 0;
+ int bInternal = 0;
+
+ rc = fts5ExtractExprText(pConfig, apVal[i], &zText, &bFreeAndReset);
+ if( rc!=SQLITE_OK ) goto filter_out;
if( zText==0 ) zText = "";
+
iCol = 0;
do{
iCol = iCol*10 + (idxStr[iIdxStr]-'0');
@@ -1306,7 +1510,7 @@ static int fts5FilterMethod(
** indicates that the MATCH expression is not a full text query,
** but a request for an internal parameter. */
rc = fts5SpecialMatch(pTab, pCsr, &zText[1]);
- goto filter_out;
+ bInternal = 1;
}else{
char **pzErr = &pTab->p.base.zErrMsg;
rc = sqlite3Fts5ExprNew(pConfig, 0, iCol, zText, &pExpr, pzErr);
@@ -1314,9 +1518,15 @@ static int fts5FilterMethod(
rc = sqlite3Fts5ExprAnd(&pCsr->pExpr, pExpr);
pExpr = 0;
}
- if( rc!=SQLITE_OK ) goto filter_out;
}
+ if( bFreeAndReset ){
+ sqlite3_free(zText);
+ sqlite3Fts5ClearLocale(pConfig);
+ }
+
+ if( bInternal || rc!=SQLITE_OK ) goto filter_out;
+
break;
}
case 'L':
@@ -1624,7 +1834,7 @@ static int fts5SpecialDelete(
int eType1 = sqlite3_value_type(apVal[1]);
if( eType1==SQLITE_INTEGER ){
sqlite3_int64 iDel = sqlite3_value_int64(apVal[1]);
- rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, &apVal[2]);
+ rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, &apVal[2], 0);
}
return rc;
}
@@ -1748,7 +1958,7 @@ static int fts5UpdateMethod(
/* DELETE */
else if( nArg==1 ){
i64 iDel = sqlite3_value_int64(apVal[0]); /* Rowid to delete */
- rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, 0);
+ rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, 0, 0);
bUpdateOrDelete = 1;
}
@@ -1756,16 +1966,31 @@ static int fts5UpdateMethod(
else{
int eType1 = sqlite3_value_numeric_type(apVal[1]);
- if( eType1!=SQLITE_INTEGER && eType1!=SQLITE_NULL ){
- rc = SQLITE_MISMATCH;
+ /* Ensure that no fts5_locale() values are written to locale=0 tables.
+ ** And that no blobs except fts5_locale() blobs are written to indexed
+ ** (i.e. not UNINDEXED) columns of locale=1 tables. */
+ int ii;
+ for(ii=0; iinCol; ii++){
+ if( sqlite3_value_type(apVal[ii+2])==SQLITE_BLOB ){
+ int bSub = (sqlite3_value_subtype(apVal[ii+2])==FTS5_LOCALE_SUBTYPE);
+ if( (pConfig->bLocale && !bSub && pConfig->abUnindexed[ii]==0)
+ || (pConfig->bLocale==0 && bSub)
+ ){
+ if( pConfig->bLocale==0 ){
+ fts5SetVtabError(pTab, "fts5_locale() requires locale=1");
+ }
+ rc = SQLITE_MISMATCH;
+ goto update_out;
+ }
+ }
}
- else if( eType0!=SQLITE_INTEGER ){
+ if( eType0!=SQLITE_INTEGER ){
/* An INSERT statement. If the conflict-mode is REPLACE, first remove
** the current entry (if any). */
if( eConflict==SQLITE_REPLACE && eType1==SQLITE_INTEGER ){
i64 iNew = sqlite3_value_int64(apVal[1]); /* Rowid to delete */
- rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0);
+ rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0, 0);
bUpdateOrDelete = 1;
}
fts5StorageInsert(&rc, pTab, apVal, pRowid);
@@ -1775,28 +2000,35 @@ static int fts5UpdateMethod(
else{
i64 iOld = sqlite3_value_int64(apVal[0]); /* Old rowid */
i64 iNew = sqlite3_value_int64(apVal[1]); /* New rowid */
- if( eType1==SQLITE_INTEGER && iOld!=iNew ){
+ if( eType1!=SQLITE_INTEGER ){
+ rc = SQLITE_MISMATCH;
+ }else if( iOld!=iNew ){
if( eConflict==SQLITE_REPLACE ){
- rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0);
+ rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0, 1);
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0);
+ rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0, 0);
}
fts5StorageInsert(&rc, pTab, apVal, pRowid);
}else{
- rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, pRowid);
+ rc = sqlite3Fts5StorageFindDeleteRow(pTab->pStorage, iOld);
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0);
+ rc = sqlite3Fts5StorageContentInsert(pTab->pStorage,apVal,pRowid);
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0, 1);
}
if( rc==SQLITE_OK ){
rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal,*pRowid);
}
}
}else{
- rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0);
+ rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0, 1);
fts5StorageInsert(&rc, pTab, apVal, pRowid);
}
bUpdateOrDelete = 1;
+ sqlite3Fts5StorageReleaseDeleteRow(pTab->pStorage);
}
+
}
}
@@ -1813,6 +2045,7 @@ static int fts5UpdateMethod(
}
}
+ update_out:
pTab->p.pConfig->pzErrmsg = 0;
return rc;
}
@@ -1834,9 +2067,11 @@ static int fts5SyncMethod(sqlite3_vtab *pVtab){
** Implementation of xBegin() method.
*/
static int fts5BeginMethod(sqlite3_vtab *pVtab){
- fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_BEGIN, 0);
- fts5NewTransaction((Fts5FullTable*)pVtab);
- return SQLITE_OK;
+ int rc = fts5NewTransaction((Fts5FullTable*)pVtab);
+ if( rc==SQLITE_OK ){
+ fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_BEGIN, 0);
+ }
+ return rc;
}
/*
@@ -1890,17 +2125,40 @@ static int fts5ApiRowCount(Fts5Context *pCtx, i64 *pnRow){
return sqlite3Fts5StorageRowCount(pTab->pStorage, pnRow);
}
+/*
+** Implementation of xTokenize_v2() API.
+*/
+static int fts5ApiTokenize_v2(
+ Fts5Context *pCtx,
+ const char *pText, int nText,
+ const char *pLoc, int nLoc,
+ void *pUserData,
+ int (*xToken)(void*, int, const char*, int, int, int)
+){
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+ Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
+ int rc = SQLITE_OK;
+
+ fts5SetLocale(pTab->pConfig, pLoc, nLoc);
+ rc = sqlite3Fts5Tokenize(pTab->pConfig,
+ FTS5_TOKENIZE_AUX, pText, nText, pUserData, xToken
+ );
+ fts5SetLocale(pTab->pConfig, 0, 0);
+
+ return rc;
+}
+
+/*
+** Implementation of xTokenize() API. This is just xTokenize_v2() with NULL/0
+** passed as the locale.
+*/
static int fts5ApiTokenize(
Fts5Context *pCtx,
const char *pText, int nText,
void *pUserData,
int (*xToken)(void*, int, const char*, int, int, int)
){
- Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
- Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
- return sqlite3Fts5Tokenize(
- pTab->pConfig, FTS5_TOKENIZE_AUX, pText, nText, pUserData, xToken
- );
+ return fts5ApiTokenize_v2(pCtx, pText, nText, 0, 0, pUserData, xToken);
}
static int fts5ApiPhraseCount(Fts5Context *pCtx){
@@ -1922,28 +2180,37 @@ static int fts5ApiColumnText(
int rc = SQLITE_OK;
Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
+
+ assert( pCsr->ePlan!=FTS5_PLAN_SPECIAL );
if( iCol<0 || iCol>=pTab->pConfig->nCol ){
rc = SQLITE_RANGE;
- }else if( fts5IsContentless((Fts5FullTable*)(pCsr->base.pVtab))
- || pCsr->ePlan==FTS5_PLAN_SPECIAL
- ){
+ }else if( fts5IsContentless((Fts5FullTable*)(pCsr->base.pVtab)) ){
*pz = 0;
*pn = 0;
}else{
rc = fts5SeekCursor(pCsr, 0);
if( rc==SQLITE_OK ){
- *pz = (const char*)sqlite3_column_text(pCsr->pStmt, iCol+1);
- *pn = sqlite3_column_bytes(pCsr->pStmt, iCol+1);
+ Fts5Config *pConfig = pTab->pConfig;
+ int bContent = (pConfig->abUnindexed[iCol]==0);
+ sqlite3_value *pVal = sqlite3_column_value(pCsr->pStmt, iCol+1);
+ sqlite3Fts5ExtractText(pConfig, pVal, bContent, 0, pz, pn);
}
}
return rc;
}
+/*
+** This is called by various API functions - xInst, xPhraseFirst,
+** xPhraseFirstColumn etc. - to obtain the position list for phrase iPhrase
+** of the current row. This function works for both detail=full tables (in
+** which case the position-list was read from the fts index) or for other
+** detail= modes if the row content is available.
+*/
static int fts5CsrPoslist(
- Fts5Cursor *pCsr,
- int iPhrase,
- const u8 **pa,
- int *pn
+ Fts5Cursor *pCsr, /* Fts5 cursor object */
+ int iPhrase, /* Phrase to find position list for */
+ const u8 **pa, /* OUT: Pointer to position list buffer */
+ int *pn /* OUT: Size of (*pa) in bytes */
){
Fts5Config *pConfig = ((Fts5Table*)(pCsr->base.pVtab))->pConfig;
int rc = SQLITE_OK;
@@ -1951,20 +2218,34 @@ static int fts5CsrPoslist(
if( iPhrase<0 || iPhrase>=sqlite3Fts5ExprPhraseCount(pCsr->pExpr) ){
rc = SQLITE_RANGE;
+ }else if( pConfig->eDetail!=FTS5_DETAIL_FULL
+ && pConfig->eContent==FTS5_CONTENT_NONE
+ ){
+ *pa = 0;
+ *pn = 0;
+ return SQLITE_OK;
}else if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_POSLIST) ){
if( pConfig->eDetail!=FTS5_DETAIL_FULL ){
Fts5PoslistPopulator *aPopulator;
int i;
+
aPopulator = sqlite3Fts5ExprClearPoslists(pCsr->pExpr, bLive);
if( aPopulator==0 ) rc = SQLITE_NOMEM;
+ if( rc==SQLITE_OK ){
+ rc = fts5SeekCursor(pCsr, 0);
+ }
for(i=0; inCol && rc==SQLITE_OK; i++){
- int n; const char *z;
- rc = fts5ApiColumnText((Fts5Context*)pCsr, i, &z, &n);
+ sqlite3_value *pVal = sqlite3_column_value(pCsr->pStmt, i+1);
+ const char *z = 0;
+ int n = 0;
+ int bReset = 0;
+ rc = sqlite3Fts5ExtractText(pConfig, pVal, 1, &bReset, &z, &n);
if( rc==SQLITE_OK ){
rc = sqlite3Fts5ExprPopulatePoslists(
pConfig, pCsr->pExpr, aPopulator, i, z, n
);
}
+ if( bReset ) sqlite3Fts5ClearLocale(pConfig);
}
sqlite3_free(aPopulator);
@@ -1989,7 +2270,6 @@ static int fts5CsrPoslist(
*pn = 0;
}
-
return rc;
}
@@ -2058,7 +2338,8 @@ static int fts5CacheInstArray(Fts5Cursor *pCsr){
aInst[0] = iBest;
aInst[1] = FTS5_POS2COLUMN(aIter[iBest].iPos);
aInst[2] = FTS5_POS2OFFSET(aIter[iBest].iPos);
- if( aInst[1]<0 || aInst[1]>=nCol ){
+ assert( aInst[1]>=0 );
+ if( aInst[1]>=nCol ){
rc = FTS5_CORRUPT;
break;
}
@@ -2145,16 +2426,21 @@ static int fts5ApiColumnSize(Fts5Context *pCtx, int iCol, int *pnToken){
}
}else{
int i;
+ rc = fts5SeekCursor(pCsr, 0);
for(i=0; rc==SQLITE_OK && inCol; i++){
if( pConfig->abUnindexed[i]==0 ){
- const char *z; int n;
- void *p = (void*)(&pCsr->aColumnSize[i]);
+ const char *z = 0;
+ int n = 0;
+ int bReset = 0;
+ sqlite3_value *pVal = sqlite3_column_value(pCsr->pStmt, i+1);
+
pCsr->aColumnSize[i] = 0;
- rc = fts5ApiColumnText(pCtx, i, &z, &n);
+ rc = sqlite3Fts5ExtractText(pConfig, pVal, 1, &bReset, &z, &n);
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5Tokenize(
- pConfig, FTS5_TOKENIZE_AUX, z, n, p, fts5ColumnSizeCb
+ rc = sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_AUX,
+ z, n, (void*)&pCsr->aColumnSize[i], fts5ColumnSizeCb
);
+ if( bReset ) sqlite3Fts5ClearLocale(pConfig);
}
}
}
@@ -2401,8 +2687,71 @@ static int fts5ApiQueryPhrase(Fts5Context*, int, void*,
int(*)(const Fts5ExtensionApi*, Fts5Context*, void*)
);
+/*
+** The xColumnLocale() API.
+*/
+static int fts5ApiColumnLocale(
+ Fts5Context *pCtx,
+ int iCol,
+ const char **pzLocale,
+ int *pnLocale
+){
+ int rc = SQLITE_OK;
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+ Fts5Config *pConfig = ((Fts5Table*)(pCsr->base.pVtab))->pConfig;
+
+ *pzLocale = 0;
+ *pnLocale = 0;
+
+ assert( pCsr->ePlan!=FTS5_PLAN_SPECIAL );
+ if( iCol<0 || iCol>=pConfig->nCol ){
+ rc = SQLITE_RANGE;
+ }else if(
+ pConfig->abUnindexed[iCol]==0
+ && pConfig->eContent!=FTS5_CONTENT_NONE
+ && pConfig->bLocale
+ ){
+ rc = fts5SeekCursor(pCsr, 0);
+ if( rc==SQLITE_OK ){
+ /* Load the value into pVal. pVal is a locale/text pair iff:
+ **
+ ** 1) It is an SQLITE_BLOB, and
+ ** 2) Either the subtype is FTS5_LOCALE_SUBTYPE, or else the
+ ** value was loaded from an FTS5_CONTENT_NORMAL table, and
+ ** 3) It does not begin with an 0x00 byte.
+ */
+ sqlite3_value *pVal = sqlite3_column_value(pCsr->pStmt, iCol+1);
+ if( sqlite3_value_type(pVal)==SQLITE_BLOB ){
+ const u8 *pBlob = (const u8*)sqlite3_value_blob(pVal);
+ int nBlob = sqlite3_value_bytes(pVal);
+ if( pConfig->eContent==FTS5_CONTENT_EXTERNAL ){
+ const int SZHDR = sizeof(FTS5_LOCALE_HEADER)-1;
+ if( nBlobpAux==0 );
+ assert( pCsr->ePlan!=FTS5_PLAN_SPECIAL );
pCsr->pAux = pAux;
pAux->xFunc(&sFts5Api, (Fts5Context*)pCsr, context, argc, argv);
pCsr->pAux = 0;
@@ -2487,6 +2839,21 @@ static Fts5Cursor *fts5CursorFromCsrid(Fts5Global *pGlobal, i64 iCsrId){
return pCsr;
}
+/*
+** Parameter zFmt is a printf() style formatting string. This function
+** formats it using the trailing arguments and returns the result as
+** an error message to the context passed as the first argument.
+*/
+static void fts5ResultError(sqlite3_context *pCtx, const char *zFmt, ...){
+ char *zErr = 0;
+ va_list ap;
+ va_start(ap, zFmt);
+ zErr = sqlite3_vmprintf(zFmt, ap);
+ sqlite3_result_error(pCtx, zErr, -1);
+ sqlite3_free(zErr);
+ va_end(ap);
+}
+
static void fts5ApiCallback(
sqlite3_context *context,
int argc,
@@ -2502,10 +2869,8 @@ static void fts5ApiCallback(
iCsrId = sqlite3_value_int64(argv[0]);
pCsr = fts5CursorFromCsrid(pAux->pGlobal, iCsrId);
- if( pCsr==0 || pCsr->ePlan==0 ){
- char *zErr = sqlite3_mprintf("no such cursor: %lld", iCsrId);
- sqlite3_result_error(context, zErr, -1);
- sqlite3_free(zErr);
+ if( pCsr==0 || (pCsr->ePlan==0 || pCsr->ePlan==FTS5_PLAN_SPECIAL) ){
+ fts5ResultError(context, "no such cursor: %lld", iCsrId);
}else{
sqlite3_vtab *pTab = pCsr->base.pVtab;
fts5ApiInvoke(pAux, pCsr, context, argc-1, &argv[1]);
@@ -2599,6 +2964,57 @@ static int fts5PoslistBlob(sqlite3_context *pCtx, Fts5Cursor *pCsr){
return rc;
}
+/*
+** Value pVal was read from column iCol of the FTS5 table. This function
+** returns it to the owner of pCtx via a call to an sqlite3_result_xxx()
+** function. This function deals with the same cases as
+** sqlite3Fts5ExtractText():
+**
+** 1) Ordinary values. These can be returned using sqlite3_result_value().
+**
+** 2) Blobs from fts5_locale(). The text is extracted from these and
+** returned via sqlite3_result_text(). The locale is discarded.
+*/
+static void fts5ExtractValueFromColumn(
+ sqlite3_context *pCtx,
+ Fts5Config *pConfig,
+ int iCol,
+ sqlite3_value *pVal
+){
+ assert( pConfig->eContent!=FTS5_CONTENT_NONE );
+
+ if( pConfig->bLocale
+ && sqlite3_value_type(pVal)==SQLITE_BLOB
+ && pConfig->abUnindexed[iCol]==0
+ ){
+ const int SZHDR = sizeof(FTS5_LOCALE_HEADER)-1;
+ const u8 *pBlob = sqlite3_value_blob(pVal);
+ int nBlob = sqlite3_value_bytes(pVal);
+ int ii;
+
+ if( pConfig->eContent==FTS5_CONTENT_EXTERNAL ){
+ if( nBlobiCsrId);
}else if( iCol==pConfig->nCol+1 ){
-
/* The value of the "rank" column. */
+
if( pCsr->ePlan==FTS5_PLAN_SOURCE ){
fts5PoslistBlob(pCtx, pCsr);
}else if(
@@ -2640,20 +3056,27 @@ static int fts5ColumnMethod(
fts5ApiInvoke(pCsr->pRank, pCsr, pCtx, pCsr->nRankArg, pCsr->apRankArg);
}
}
- }else if( !fts5IsContentless(pTab) ){
- pConfig->pzErrmsg = &pTab->p.base.zErrMsg;
- rc = fts5SeekCursor(pCsr, 1);
- if( rc==SQLITE_OK ){
- sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1));
+ }else{
+ /* A column created by the user containing values. */
+ int bNochange = sqlite3_vtab_nochange(pCtx);
+
+ if( fts5IsContentless(pTab) ){
+ if( bNochange && pConfig->bContentlessDelete ){
+ fts5ResultError(pCtx, "cannot UPDATE a subset of "
+ "columns on fts5 contentless-delete table: %s", pConfig->zName
+ );
+ }
+ }else if( bNochange==0 || pConfig->eContent!=FTS5_CONTENT_NORMAL ){
+ pConfig->pzErrmsg = &pTab->p.base.zErrMsg;
+ rc = fts5SeekCursor(pCsr, 1);
+ if( rc==SQLITE_OK ){
+ sqlite3_value *pVal = sqlite3_column_value(pCsr->pStmt, iCol+1);
+ fts5ExtractValueFromColumn(pCtx, pConfig, iCol, pVal);
+ }
+ pConfig->pzErrmsg = 0;
}
- pConfig->pzErrmsg = 0;
- }else if( pConfig->bContentlessDelete && sqlite3_vtab_nochange(pCtx) ){
- char *zErr = sqlite3_mprintf("cannot UPDATE a subset of "
- "columns on fts5 contentless-delete table: %s", pConfig->zName
- );
- sqlite3_result_error(pCtx, zErr, -1);
- sqlite3_free(zErr);
}
+
return rc;
}
@@ -2792,9 +3215,180 @@ static int fts5CreateAux(
return rc;
}
+/*
+** This function is used by xCreateTokenizer_v2() and xCreateTokenizer().
+** It allocates and partially populates a new Fts5TokenizerModule object.
+** The new object is already linked into the Fts5Global context before
+** returning.
+**
+** If successful, SQLITE_OK is returned and a pointer to the new
+** Fts5TokenizerModule object returned via output parameter (*ppNew). All
+** that is required is for the caller to fill in the methods in
+** Fts5TokenizerModule.x1 and x2, and to set Fts5TokenizerModule.bV2Native
+** as appropriate.
+**
+** If an error occurs, an SQLite error code is returned and the final value
+** of (*ppNew) undefined.
+*/
+static int fts5NewTokenizerModule(
+ Fts5Global *pGlobal, /* Global context (one per db handle) */
+ const char *zName, /* Name of new function */
+ void *pUserData, /* User data for aux. function */
+ void(*xDestroy)(void*), /* Destructor for pUserData */
+ Fts5TokenizerModule **ppNew
+){
+ int rc = SQLITE_OK;
+ Fts5TokenizerModule *pNew;
+ sqlite3_int64 nName; /* Size of zName and its \0 terminator */
+ sqlite3_int64 nByte; /* Bytes of space to allocate */
+
+ nName = strlen(zName) + 1;
+ nByte = sizeof(Fts5TokenizerModule) + nName;
+ *ppNew = pNew = (Fts5TokenizerModule*)sqlite3Fts5MallocZero(&rc, nByte);
+ if( pNew ){
+ pNew->zName = (char*)&pNew[1];
+ memcpy(pNew->zName, zName, nName);
+ pNew->pUserData = pUserData;
+ pNew->xDestroy = xDestroy;
+ pNew->pNext = pGlobal->pTok;
+ pGlobal->pTok = pNew;
+ if( pNew->pNext==0 ){
+ pGlobal->pDfltTok = pNew;
+ }
+ }
+
+ return rc;
+}
+
+/*
+** An instance of this type is used as the Fts5Tokenizer object for
+** wrapper tokenizers - those that provide access to a v1 tokenizer via
+** the fts5_tokenizer_v2 API, and those that provide access to a v2 tokenizer
+** via the fts5_tokenizer API.
+*/
+typedef struct Fts5VtoVTokenizer Fts5VtoVTokenizer;
+struct Fts5VtoVTokenizer {
+ int bV2Native; /* True if v2 native tokenizer */
+ fts5_tokenizer x1; /* Tokenizer functions */
+ fts5_tokenizer_v2 x2; /* V2 tokenizer functions */
+ Fts5Tokenizer *pReal;
+};
+
+/*
+** Create a wrapper tokenizer. The context argument pCtx points to the
+** Fts5TokenizerModule object.
+*/
+static int fts5VtoVCreate(
+ void *pCtx,
+ const char **azArg,
+ int nArg,
+ Fts5Tokenizer **ppOut
+){
+ Fts5TokenizerModule *pMod = (Fts5TokenizerModule*)pCtx;
+ Fts5VtoVTokenizer *pNew = 0;
+ int rc = SQLITE_OK;
+
+ pNew = (Fts5VtoVTokenizer*)sqlite3Fts5MallocZero(&rc, sizeof(*pNew));
+ if( rc==SQLITE_OK ){
+ pNew->x1 = pMod->x1;
+ pNew->x2 = pMod->x2;
+ pNew->bV2Native = pMod->bV2Native;
+ if( pMod->bV2Native ){
+ rc = pMod->x2.xCreate(pMod->pUserData, azArg, nArg, &pNew->pReal);
+ }else{
+ rc = pMod->x1.xCreate(pMod->pUserData, azArg, nArg, &pNew->pReal);
+ }
+ if( rc!=SQLITE_OK ){
+ sqlite3_free(pNew);
+ pNew = 0;
+ }
+ }
+
+ *ppOut = (Fts5Tokenizer*)pNew;
+ return rc;
+}
+
+/*
+** Delete an Fts5VtoVTokenizer wrapper tokenizer.
+*/
+static void fts5VtoVDelete(Fts5Tokenizer *pTok){
+ Fts5VtoVTokenizer *p = (Fts5VtoVTokenizer*)pTok;
+ if( p ){
+ if( p->bV2Native ){
+ p->x2.xDelete(p->pReal);
+ }else{
+ p->x1.xDelete(p->pReal);
+ }
+ sqlite3_free(p);
+ }
+}
+
+
+/*
+** xTokenizer method for a wrapper tokenizer that offers the v1 interface
+** (no support for locales).
+*/
+static int fts5V1toV2Tokenize(
+ Fts5Tokenizer *pTok,
+ void *pCtx, int flags,
+ const char *pText, int nText,
+ int (*xToken)(void*, int, const char*, int, int, int)
+){
+ Fts5VtoVTokenizer *p = (Fts5VtoVTokenizer*)pTok;
+ assert( p->bV2Native );
+ return p->x2.xTokenize(p->pReal, pCtx, flags, pText, nText, 0, 0, xToken);
+}
+
+/*
+** xTokenizer method for a wrapper tokenizer that offers the v2 interface
+** (with locale support).
+*/
+static int fts5V2toV1Tokenize(
+ Fts5Tokenizer *pTok,
+ void *pCtx, int flags,
+ const char *pText, int nText,
+ const char *pLocale, int nLocale,
+ int (*xToken)(void*, int, const char*, int, int, int)
+){
+ Fts5VtoVTokenizer *p = (Fts5VtoVTokenizer*)pTok;
+ assert( p->bV2Native==0 );
+ UNUSED_PARAM2(pLocale,nLocale);
+ return p->x1.xTokenize(p->pReal, pCtx, flags, pText, nText, xToken);
+}
+
/*
** Register a new tokenizer. This is the implementation of the
-** fts5_api.xCreateTokenizer() method.
+** fts5_api.xCreateTokenizer_v2() method.
+*/
+static int fts5CreateTokenizer_v2(
+ fts5_api *pApi, /* Global context (one per db handle) */
+ const char *zName, /* Name of new function */
+ void *pUserData, /* User data for aux. function */
+ fts5_tokenizer_v2 *pTokenizer, /* Tokenizer implementation */
+ void(*xDestroy)(void*) /* Destructor for pUserData */
+){
+ Fts5Global *pGlobal = (Fts5Global*)pApi;
+ int rc = SQLITE_OK;
+
+ if( pTokenizer->iVersion>2 ){
+ rc = SQLITE_ERROR;
+ }else{
+ Fts5TokenizerModule *pNew = 0;
+ rc = fts5NewTokenizerModule(pGlobal, zName, pUserData, xDestroy, &pNew);
+ if( pNew ){
+ pNew->x2 = *pTokenizer;
+ pNew->bV2Native = 1;
+ pNew->x1.xCreate = fts5VtoVCreate;
+ pNew->x1.xTokenize = fts5V1toV2Tokenize;
+ pNew->x1.xDelete = fts5VtoVDelete;
+ }
+ }
+
+ return rc;
+}
+
+/*
+** The fts5_api.xCreateTokenizer() method.
*/
static int fts5CreateTokenizer(
fts5_api *pApi, /* Global context (one per db handle) */
@@ -2803,37 +3397,29 @@ static int fts5CreateTokenizer(
fts5_tokenizer *pTokenizer, /* Tokenizer implementation */
void(*xDestroy)(void*) /* Destructor for pUserData */
){
- Fts5Global *pGlobal = (Fts5Global*)pApi;
- Fts5TokenizerModule *pNew;
- sqlite3_int64 nName; /* Size of zName and its \0 terminator */
- sqlite3_int64 nByte; /* Bytes of space to allocate */
+ Fts5TokenizerModule *pNew = 0;
int rc = SQLITE_OK;
- nName = strlen(zName) + 1;
- nByte = sizeof(Fts5TokenizerModule) + nName;
- pNew = (Fts5TokenizerModule*)sqlite3_malloc64(nByte);
+ rc = fts5NewTokenizerModule(
+ (Fts5Global*)pApi, zName, pUserData, xDestroy, &pNew
+ );
if( pNew ){
- memset(pNew, 0, (size_t)nByte);
- pNew->zName = (char*)&pNew[1];
- memcpy(pNew->zName, zName, nName);
- pNew->pUserData = pUserData;
- pNew->x = *pTokenizer;
- pNew->xDestroy = xDestroy;
- pNew->pNext = pGlobal->pTok;
- pGlobal->pTok = pNew;
- if( pNew->pNext==0 ){
- pGlobal->pDfltTok = pNew;
- }
- }else{
- rc = SQLITE_NOMEM;
+ pNew->x1 = *pTokenizer;
+ pNew->x2.xCreate = fts5VtoVCreate;
+ pNew->x2.xTokenize = fts5V2toV1Tokenize;
+ pNew->x2.xDelete = fts5VtoVDelete;
}
-
return rc;
}
+/*
+** Search the global context passed as the first argument for a tokenizer
+** module named zName. If found, return a pointer to the Fts5TokenizerModule
+** object. Otherwise, return NULL.
+*/
static Fts5TokenizerModule *fts5LocateTokenizer(
- Fts5Global *pGlobal,
- const char *zName
+ Fts5Global *pGlobal, /* Global (one per db handle) object */
+ const char *zName /* Name of tokenizer module to find */
){
Fts5TokenizerModule *pMod = 0;
@@ -2848,6 +3434,36 @@ static Fts5TokenizerModule *fts5LocateTokenizer(
return pMod;
}
+/*
+** Find a tokenizer. This is the implementation of the
+** fts5_api.xFindTokenizer_v2() method.
+*/
+static int fts5FindTokenizer_v2(
+ fts5_api *pApi, /* Global context (one per db handle) */
+ const char *zName, /* Name of tokenizer */
+ void **ppUserData,
+ fts5_tokenizer_v2 **ppTokenizer /* Populate this object */
+){
+ int rc = SQLITE_OK;
+ Fts5TokenizerModule *pMod;
+
+ pMod = fts5LocateTokenizer((Fts5Global*)pApi, zName);
+ if( pMod ){
+ if( pMod->bV2Native ){
+ *ppUserData = pMod->pUserData;
+ }else{
+ *ppUserData = (void*)pMod;
+ }
+ *ppTokenizer = &pMod->x2;
+ }else{
+ *ppTokenizer = 0;
+ *ppUserData = 0;
+ rc = SQLITE_ERROR;
+ }
+
+ return rc;
+}
+
/*
** Find a tokenizer. This is the implementation of the
** fts5_api.xFindTokenizer() method.
@@ -2863,50 +3479,16 @@ static int fts5FindTokenizer(
pMod = fts5LocateTokenizer((Fts5Global*)pApi, zName);
if( pMod ){
- *pTokenizer = pMod->x;
- *ppUserData = pMod->pUserData;
- }else{
- memset(pTokenizer, 0, sizeof(fts5_tokenizer));
- rc = SQLITE_ERROR;
- }
-
- return rc;
-}
-
-int fts5GetTokenizer(
- Fts5Global *pGlobal,
- const char **azArg,
- int nArg,
- Fts5Config *pConfig,
- char **pzErr
-){
- Fts5TokenizerModule *pMod;
- int rc = SQLITE_OK;
-
- pMod = fts5LocateTokenizer(pGlobal, nArg==0 ? 0 : azArg[0]);
- if( pMod==0 ){
- assert( nArg>0 );
- rc = SQLITE_ERROR;
- if( pzErr ) *pzErr = sqlite3_mprintf("no such tokenizer: %s", azArg[0]);
- }else{
- rc = pMod->x.xCreate(
- pMod->pUserData, (azArg?&azArg[1]:0), (nArg?nArg-1:0), &pConfig->t.pTok
- );
- pConfig->t.pTokApi = &pMod->x;
- if( rc!=SQLITE_OK ){
- if( pzErr && rc!=SQLITE_NOMEM ){
- *pzErr = sqlite3_mprintf("error in tokenizer constructor");
- }
+ if( pMod->bV2Native==0 ){
+ *ppUserData = pMod->pUserData;
}else{
- pConfig->t.ePattern = sqlite3Fts5TokenizerPattern(
- pMod->x.xCreate, pConfig->t.pTok
- );
+ *ppUserData = (void*)pMod;
}
- }
-
- if( rc!=SQLITE_OK ){
- pConfig->t.pTokApi = 0;
- pConfig->t.pTok = 0;
+ *pTokenizer = pMod->x1;
+ }else{
+ memset(pTokenizer, 0, sizeof(*pTokenizer));
+ *ppUserData = 0;
+ rc = SQLITE_ERROR;
}
return rc;
@@ -2916,13 +3498,56 @@ int fts5GetTokenizer(
** Attempt to instantiate the tokenizer.
*/
int sqlite3Fts5LoadTokenizer(Fts5Config *pConfig){
- return fts5GetTokenizer(
- pConfig->pGlobal, pConfig->t.azArg, pConfig->t.nArg,
- pConfig, pConfig->pzErrmsg
- );
+ const char **azArg = pConfig->t.azArg;
+ const int nArg = pConfig->t.nArg;
+ Fts5TokenizerModule *pMod = 0;
+ int rc = SQLITE_OK;
+
+ pMod = fts5LocateTokenizer(pConfig->pGlobal, nArg==0 ? 0 : azArg[0]);
+ if( pMod==0 ){
+ assert( nArg>0 );
+ rc = SQLITE_ERROR;
+ sqlite3Fts5ConfigErrmsg(pConfig, "no such tokenizer: %s", azArg[0]);
+ }else{
+ int (*xCreate)(void*, const char**, int, Fts5Tokenizer**) = 0;
+ if( pMod->bV2Native ){
+ xCreate = pMod->x2.xCreate;
+ pConfig->t.pApi2 = &pMod->x2;
+ }else{
+ pConfig->t.pApi1 = &pMod->x1;
+ xCreate = pMod->x1.xCreate;
+ }
+
+ rc = xCreate(pMod->pUserData,
+ (azArg?&azArg[1]:0), (nArg?nArg-1:0), &pConfig->t.pTok
+ );
+
+ if( rc!=SQLITE_OK ){
+ if( rc!=SQLITE_NOMEM ){
+ sqlite3Fts5ConfigErrmsg(pConfig, "error in tokenizer constructor");
+ }
+ }else if( pMod->bV2Native==0 ){
+ pConfig->t.ePattern = sqlite3Fts5TokenizerPattern(
+ pMod->x1.xCreate, pConfig->t.pTok
+ );
+ }
+ }
+
+ if( rc!=SQLITE_OK ){
+ pConfig->t.pApi1 = 0;
+ pConfig->t.pApi2 = 0;
+ pConfig->t.pTok = 0;
+ }
+
+ return rc;
}
+/*
+** xDestroy callback passed to sqlite3_create_module(). This is invoked
+** when the db handle is being closed. Free memory associated with
+** tokenizers and aux functions registered with this db handle.
+*/
static void fts5ModuleDestroy(void *pCtx){
Fts5TokenizerModule *pTok, *pNextTok;
Fts5Auxiliary *pAux, *pNextAux;
@@ -2943,6 +3568,10 @@ static void fts5ModuleDestroy(void *pCtx){
sqlite3_free(pGlobal);
}
+/*
+** Implementation of the fts5() function used by clients to obtain the
+** API pointer.
+*/
static void fts5Fts5Func(
sqlite3_context *pCtx, /* Function call context */
int nArg, /* Number of args */
@@ -2969,6 +3598,69 @@ static void fts5SourceIdFunc(
sqlite3_result_text(pCtx, "--FTS5-SOURCE-ID--", -1, SQLITE_TRANSIENT);
}
+/*
+** Implementation of fts5_locale(LOCALE, TEXT) function.
+**
+** If parameter LOCALE is NULL, or a zero-length string, then a copy of
+** TEXT is returned. Otherwise, both LOCALE and TEXT are interpreted as
+** text, and the value returned is a blob consisting of:
+**
+** * The 4 bytes 0x00, 0xE0, 0xB2, 0xEb (FTS5_LOCALE_HEADER).
+** * The LOCALE, as utf-8 text, followed by
+** * 0x00, followed by
+** * The TEXT, as utf-8 text.
+**
+** There is no final nul-terminator following the TEXT value.
+*/
+static void fts5LocaleFunc(
+ sqlite3_context *pCtx, /* Function call context */
+ int nArg, /* Number of args */
+ sqlite3_value **apArg /* Function arguments */
+){
+ const char *zLocale = 0;
+ int nLocale = 0;
+ const char *zText = 0;
+ int nText = 0;
+
+ assert( nArg==2 );
+ UNUSED_PARAM(nArg);
+
+ zLocale = (const char*)sqlite3_value_text(apArg[0]);
+ nLocale = sqlite3_value_bytes(apArg[0]);
+
+ zText = (const char*)sqlite3_value_text(apArg[1]);
+ nText = sqlite3_value_bytes(apArg[1]);
+
+ if( zLocale==0 || zLocale[0]=='\0' ){
+ sqlite3_result_text(pCtx, zText, nText, SQLITE_TRANSIENT);
+ }else{
+ u8 *pBlob = 0;
+ u8 *pCsr = 0;
+ int nBlob = 0;
+ const int nHdr = 4;
+ assert( sizeof(FTS5_LOCALE_HEADER)==nHdr+1 );
+
+ nBlob = nHdr + nLocale + 1 + nText;
+ pBlob = (u8*)sqlite3_malloc(nBlob);
+ if( pBlob==0 ){
+ sqlite3_result_error_nomem(pCtx);
+ return;
+ }
+
+ pCsr = pBlob;
+ memcpy(pCsr, FTS5_LOCALE_HEADER, nHdr);
+ pCsr += nHdr;
+ memcpy(pCsr, zLocale, nLocale);
+ pCsr += nLocale;
+ (*pCsr++) = 0x00;
+ if( zText ) memcpy(pCsr, zText, nText);
+ assert( &pCsr[nText]==&pBlob[nBlob] );
+
+ sqlite3_result_blob(pCtx, pBlob, nBlob, sqlite3_free);
+ sqlite3_result_subtype(pCtx, FTS5_LOCALE_SUBTYPE);
+ }
+}
+
/*
** Return true if zName is the extension on one of the shadow tables used
** by this module.
@@ -3061,10 +3753,12 @@ static int fts5Init(sqlite3 *db){
void *p = (void*)pGlobal;
memset(pGlobal, 0, sizeof(Fts5Global));
pGlobal->db = db;
- pGlobal->api.iVersion = 2;
+ pGlobal->api.iVersion = 3;
pGlobal->api.xCreateFunction = fts5CreateAux;
pGlobal->api.xCreateTokenizer = fts5CreateTokenizer;
pGlobal->api.xFindTokenizer = fts5FindTokenizer;
+ pGlobal->api.xCreateTokenizer_v2 = fts5CreateTokenizer_v2;
+ pGlobal->api.xFindTokenizer_v2 = fts5FindTokenizer_v2;
rc = sqlite3_create_module_v2(db, "fts5", &fts5Mod, p, fts5ModuleDestroy);
if( rc==SQLITE_OK ) rc = sqlite3Fts5IndexInit(db);
if( rc==SQLITE_OK ) rc = sqlite3Fts5ExprInit(pGlobal, db);
@@ -3083,6 +3777,13 @@ static int fts5Init(sqlite3 *db){
p, fts5SourceIdFunc, 0, 0
);
}
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_create_function(
+ db, "fts5_locale", 2,
+ SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_RESULT_SUBTYPE,
+ p, fts5LocaleFunc, 0, 0
+ );
+ }
}
/* If SQLITE_FTS5_ENABLE_TEST_MI is defined, assume that the file
diff --git a/ext/fts5/fts5_storage.c b/ext/fts5/fts5_storage.c
index 0b676e6b4f..cf25eb361e 100644
--- a/ext/fts5/fts5_storage.c
+++ b/ext/fts5/fts5_storage.c
@@ -16,13 +16,40 @@
#include "fts5Int.h"
+/*
+** pSavedRow:
+** SQL statement FTS5_STMT_LOOKUP2 is a copy of FTS5_STMT_LOOKUP, it
+** does a by-rowid lookup to retrieve a single row from the %_content
+** table or equivalent external-content table/view.
+**
+** However, FTS5_STMT_LOOKUP2 is only used when retrieving the original
+** values for a row being UPDATEd. In that case, the SQL statement is
+** not reset and pSavedRow is set to point at it. This is so that the
+** insert operation that follows the delete may access the original
+** row values for any new values for which sqlite3_value_nochange() returns
+** true. i.e. if the user executes:
+**
+** CREATE VIRTUAL TABLE ft USING fts5(a, b, c, locale=1);
+** ...
+** UPDATE fts SET a=?, b=? WHERE rowid=?;
+**
+** then the value passed to the xUpdate() method of this table as the
+** new.c value is an sqlite3_value_nochange() value. So in this case it
+** must be read from the saved row stored in Fts5Storage.pSavedRow.
+**
+** This is necessary - using sqlite3_value_nochange() instead of just having
+** SQLite pass the original value back via xUpdate() - so as not to discard
+** any locale information associated with such values.
+**
+*/
struct Fts5Storage {
Fts5Config *pConfig;
Fts5Index *pIndex;
int bTotalsValid; /* True if nTotalRow/aTotalSize[] are valid */
i64 nTotalRow; /* Total number of rows in FTS table */
i64 *aTotalSize; /* Total sizes of each column */
- sqlite3_stmt *aStmt[11];
+ sqlite3_stmt *pSavedRow;
+ sqlite3_stmt *aStmt[12];
};
@@ -36,14 +63,15 @@ struct Fts5Storage {
# error "FTS5_STMT_LOOKUP mismatch"
#endif
-#define FTS5_STMT_INSERT_CONTENT 3
-#define FTS5_STMT_REPLACE_CONTENT 4
-#define FTS5_STMT_DELETE_CONTENT 5
-#define FTS5_STMT_REPLACE_DOCSIZE 6
-#define FTS5_STMT_DELETE_DOCSIZE 7
-#define FTS5_STMT_LOOKUP_DOCSIZE 8
-#define FTS5_STMT_REPLACE_CONFIG 9
-#define FTS5_STMT_SCAN 10
+#define FTS5_STMT_LOOKUP2 3
+#define FTS5_STMT_INSERT_CONTENT 4
+#define FTS5_STMT_REPLACE_CONTENT 5
+#define FTS5_STMT_DELETE_CONTENT 6
+#define FTS5_STMT_REPLACE_DOCSIZE 7
+#define FTS5_STMT_DELETE_DOCSIZE 8
+#define FTS5_STMT_LOOKUP_DOCSIZE 9
+#define FTS5_STMT_REPLACE_CONFIG 10
+#define FTS5_STMT_SCAN 11
/*
** Prepare the two insert statements - Fts5Storage.pInsertContent and
@@ -73,6 +101,7 @@ static int fts5StorageGetStmt(
"SELECT %s FROM %s T WHERE T.%Q >= ? AND T.%Q <= ? ORDER BY T.%Q ASC",
"SELECT %s FROM %s T WHERE T.%Q <= ? AND T.%Q >= ? ORDER BY T.%Q DESC",
"SELECT %s FROM %s T WHERE T.%Q=?", /* LOOKUP */
+ "SELECT %s FROM %s T WHERE T.%Q=?", /* LOOKUP2 */
"INSERT INTO %Q.'%q_content' VALUES(%s)", /* INSERT_CONTENT */
"REPLACE INTO %Q.'%q_content' VALUES(%s)", /* REPLACE_CONTENT */
@@ -88,6 +117,8 @@ static int fts5StorageGetStmt(
Fts5Config *pC = p->pConfig;
char *zSql = 0;
+ assert( ArraySize(azStmt)==ArraySize(p->aStmt) );
+
switch( eStmt ){
case FTS5_STMT_SCAN:
zSql = sqlite3_mprintf(azStmt[eStmt],
@@ -104,6 +135,7 @@ static int fts5StorageGetStmt(
break;
case FTS5_STMT_LOOKUP:
+ case FTS5_STMT_LOOKUP2:
zSql = sqlite3_mprintf(azStmt[eStmt],
pC->zContentExprlist, pC->zContent, pC->zContentRowid
);
@@ -150,7 +182,7 @@ static int fts5StorageGetStmt(
rc = SQLITE_NOMEM;
}else{
int f = SQLITE_PREPARE_PERSISTENT;
- if( eStmt>FTS5_STMT_LOOKUP ) f |= SQLITE_PREPARE_NO_VTAB;
+ if( eStmt>FTS5_STMT_LOOKUP2 ) f |= SQLITE_PREPARE_NO_VTAB;
p->pConfig->bLock++;
rc = sqlite3_prepare_v3(pC->db, zSql, -1, f, &p->aStmt[eStmt], 0);
p->pConfig->bLock--;
@@ -399,15 +431,49 @@ static int fts5StorageInsertCallback(
return sqlite3Fts5IndexWrite(pIdx, pCtx->iCol, pCtx->szCol-1, pToken, nToken);
}
+/*
+** This function is used as part of an UPDATE statement that modifies the
+** rowid of a row. In that case, this function is called first to set
+** Fts5Storage.pSavedRow to point to a statement that may be used to
+** access the original values of the row being deleted - iDel.
+**
+** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
+** It is not considered an error if row iDel does not exist. In this case
+** pSavedRow is not set and SQLITE_OK returned.
+*/
+int sqlite3Fts5StorageFindDeleteRow(Fts5Storage *p, i64 iDel){
+ int rc = SQLITE_OK;
+ sqlite3_stmt *pSeek = 0;
+
+ assert( p->pSavedRow==0 );
+ rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP+1, &pSeek, 0);
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_int64(pSeek, 1, iDel);
+ if( sqlite3_step(pSeek)!=SQLITE_ROW ){
+ rc = sqlite3_reset(pSeek);
+ }else{
+ p->pSavedRow = pSeek;
+ }
+ }
+
+ return rc;
+}
+
/*
** If a row with rowid iDel is present in the %_content table, add the
** delete-markers to the FTS index necessary to delete it. Do not actually
** remove the %_content row at this time though.
+**
+** If parameter bSaveRow is true, then Fts5Storage.pSavedRow is left
+** pointing to a statement (FTS5_STMT_LOOKUP2) that may be used to access
+** the original values of the row being deleted. This is used by UPDATE
+** statements.
*/
static int fts5StorageDeleteFromIndex(
Fts5Storage *p,
i64 iDel,
- sqlite3_value **apVal
+ sqlite3_value **apVal,
+ int bSaveRow /* True to set pSavedRow */
){
Fts5Config *pConfig = p->pConfig;
sqlite3_stmt *pSeek = 0; /* SELECT to read row iDel from %_data */
@@ -416,12 +482,21 @@ static int fts5StorageDeleteFromIndex(
int iCol;
Fts5InsertCtx ctx;
+ assert( bSaveRow==0 || apVal==0 );
+ assert( bSaveRow==0 || bSaveRow==1 );
+ assert( FTS5_STMT_LOOKUP2==FTS5_STMT_LOOKUP+1 );
+
if( apVal==0 ){
- rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP, &pSeek, 0);
- if( rc!=SQLITE_OK ) return rc;
- sqlite3_bind_int64(pSeek, 1, iDel);
- if( sqlite3_step(pSeek)!=SQLITE_ROW ){
- return sqlite3_reset(pSeek);
+ if( p->pSavedRow && bSaveRow ){
+ pSeek = p->pSavedRow;
+ p->pSavedRow = 0;
+ }else{
+ rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP+bSaveRow, &pSeek, 0);
+ if( rc!=SQLITE_OK ) return rc;
+ sqlite3_bind_int64(pSeek, 1, iDel);
+ if( sqlite3_step(pSeek)!=SQLITE_ROW ){
+ return sqlite3_reset(pSeek);
+ }
}
}
@@ -429,26 +504,32 @@ static int fts5StorageDeleteFromIndex(
ctx.iCol = -1;
for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){
if( pConfig->abUnindexed[iCol-1]==0 ){
- const char *zText;
- int nText;
+ sqlite3_value *pVal = 0;
+ const char *pText = 0;
+ int nText = 0;
+ int bReset = 0;
+
assert( pSeek==0 || apVal==0 );
assert( pSeek!=0 || apVal!=0 );
if( pSeek ){
- zText = (const char*)sqlite3_column_text(pSeek, iCol);
- nText = sqlite3_column_bytes(pSeek, iCol);
- }else if( ALWAYS(apVal) ){
- zText = (const char*)sqlite3_value_text(apVal[iCol-1]);
- nText = sqlite3_value_bytes(apVal[iCol-1]);
+ pVal = sqlite3_column_value(pSeek, iCol);
}else{
- continue;
+ pVal = apVal[iCol-1];
}
- ctx.szCol = 0;
- rc = sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_DOCUMENT,
- zText, nText, (void*)&ctx, fts5StorageInsertCallback
+
+ rc = sqlite3Fts5ExtractText(
+ pConfig, pVal, pSeek!=0, &bReset, &pText, &nText
);
- p->aTotalSize[iCol-1] -= (i64)ctx.szCol;
- if( p->aTotalSize[iCol-1]<0 && rc==SQLITE_OK ){
- rc = FTS5_CORRUPT;
+ if( rc==SQLITE_OK ){
+ ctx.szCol = 0;
+ rc = sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_DOCUMENT,
+ pText, nText, (void*)&ctx, fts5StorageInsertCallback
+ );
+ p->aTotalSize[iCol-1] -= (i64)ctx.szCol;
+ if( rc==SQLITE_OK && p->aTotalSize[iCol-1]<0 ){
+ rc = FTS5_CORRUPT;
+ }
+ if( bReset ) sqlite3Fts5ClearLocale(pConfig);
}
}
}
@@ -458,11 +539,29 @@ static int fts5StorageDeleteFromIndex(
p->nTotalRow--;
}
- rc2 = sqlite3_reset(pSeek);
- if( rc==SQLITE_OK ) rc = rc2;
+ if( rc==SQLITE_OK && bSaveRow ){
+ assert( p->pSavedRow==0 );
+ p->pSavedRow = pSeek;
+ }else{
+ rc2 = sqlite3_reset(pSeek);
+ if( rc==SQLITE_OK ) rc = rc2;
+ }
return rc;
}
+/*
+** Reset any saved statement pSavedRow. Zero pSavedRow as well. This
+** should be called by the xUpdate() method of the fts5 table before
+** returning from any operation that may have set Fts5Storage.pSavedRow.
+*/
+void sqlite3Fts5StorageReleaseDeleteRow(Fts5Storage *pStorage){
+ assert( pStorage->pSavedRow==0
+ || pStorage->pSavedRow==pStorage->aStmt[FTS5_STMT_LOOKUP2]
+ );
+ sqlite3_reset(pStorage->pSavedRow);
+ pStorage->pSavedRow = 0;
+}
+
/*
** This function is called to process a DELETE on a contentless_delete=1
** table. It adds the tombstone required to delete the entry with rowid
@@ -519,12 +618,12 @@ static int fts5StorageInsertDocsize(
rc = sqlite3Fts5IndexGetOrigin(p->pIndex, &iOrigin);
sqlite3_bind_int64(pReplace, 3, iOrigin);
}
- if( rc==SQLITE_OK ){
- sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC);
- sqlite3_step(pReplace);
- rc = sqlite3_reset(pReplace);
- sqlite3_bind_null(pReplace, 2);
- }
+ }
+ if( rc==SQLITE_OK ){
+ 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;
@@ -578,7 +677,12 @@ static int fts5StorageSaveTotals(Fts5Storage *p){
/*
** Remove a row from the FTS table.
*/
-int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel, sqlite3_value **apVal){
+int sqlite3Fts5StorageDelete(
+ Fts5Storage *p, /* Storage object */
+ i64 iDel, /* Rowid to delete from table */
+ sqlite3_value **apVal, /* Optional - values to remove from index */
+ int bSaveRow /* If true, set pSavedRow for deleted row */
+){
Fts5Config *pConfig = p->pConfig;
int rc;
sqlite3_stmt *pDel = 0;
@@ -595,7 +699,7 @@ int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel, sqlite3_value **apVal){
if( p->pConfig->bContentlessDelete ){
rc = fts5StorageContentlessDelete(p, iDel);
}else{
- rc = fts5StorageDeleteFromIndex(p, iDel, apVal);
+ rc = fts5StorageDeleteFromIndex(p, iDel, apVal, bSaveRow);
}
}
@@ -684,14 +788,21 @@ int sqlite3Fts5StorageRebuild(Fts5Storage *p){
for(ctx.iCol=0; rc==SQLITE_OK && ctx.iColnCol; ctx.iCol++){
ctx.szCol = 0;
if( pConfig->abUnindexed[ctx.iCol]==0 ){
- const char *zText = (const char*)sqlite3_column_text(pScan, ctx.iCol+1);
- int nText = sqlite3_column_bytes(pScan, ctx.iCol+1);
- rc = sqlite3Fts5Tokenize(pConfig,
- FTS5_TOKENIZE_DOCUMENT,
- zText, nText,
- (void*)&ctx,
- fts5StorageInsertCallback
- );
+ int bReset = 0; /* True if tokenizer locale must be reset */
+ int nText = 0; /* Size of pText in bytes */
+ const char *pText = 0; /* Pointer to buffer containing text value */
+ sqlite3_value *pVal = sqlite3_column_value(pScan, ctx.iCol+1);
+
+ rc = sqlite3Fts5ExtractText(pConfig, pVal, 1, &bReset, &pText, &nText);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3Fts5Tokenize(pConfig,
+ FTS5_TOKENIZE_DOCUMENT,
+ pText, nText,
+ (void*)&ctx,
+ fts5StorageInsertCallback
+ );
+ if( bReset ) sqlite3Fts5ClearLocale(pConfig);
+ }
}
sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol);
p->aTotalSize[ctx.iCol] += (i64)ctx.szCol;
@@ -775,7 +886,31 @@ int sqlite3Fts5StorageContentInsert(
int i; /* Counter variable */
rc = fts5StorageGetStmt(p, FTS5_STMT_INSERT_CONTENT, &pInsert, 0);
for(i=1; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){
- rc = sqlite3_bind_value(pInsert, i, apVal[i]);
+ sqlite3_value *pVal = apVal[i];
+ if( sqlite3_value_nochange(pVal) && p->pSavedRow ){
+ /* This is an UPDATE statement, and column (i-2) was not modified.
+ ** Retrieve the value from Fts5Storage.pSavedRow instead. */
+ pVal = sqlite3_column_value(p->pSavedRow, i-1);
+ }else if( sqlite3_value_subtype(pVal)==FTS5_LOCALE_SUBTYPE ){
+ assert( pConfig->bLocale );
+ assert( i>1 );
+ if( pConfig->abUnindexed[i-2] ){
+ /* At attempt to insert an fts5_locale() value into an UNINDEXED
+ ** column. Strip the locale away and just bind the text. */
+ const char *pText = 0;
+ int nText = 0;
+ rc = sqlite3Fts5ExtractText(pConfig, pVal, 0, 0, &pText, &nText);
+ sqlite3_bind_text(pInsert, i, pText, nText, SQLITE_TRANSIENT);
+ }else{
+ const u8 *pBlob = (const u8*)sqlite3_value_blob(pVal);
+ int nBlob = sqlite3_value_bytes(pVal);
+ assert( nBlob>4 );
+ sqlite3_bind_blob(pInsert, i, pBlob+4, nBlob-4, SQLITE_TRANSIENT);
+ }
+ continue;
+ }
+
+ rc = sqlite3_bind_value(pInsert, i, pVal);
}
if( rc==SQLITE_OK ){
sqlite3_step(pInsert);
@@ -810,14 +945,24 @@ int sqlite3Fts5StorageIndexInsert(
for(ctx.iCol=0; rc==SQLITE_OK && ctx.iColnCol; ctx.iCol++){
ctx.szCol = 0;
if( pConfig->abUnindexed[ctx.iCol]==0 ){
- const char *zText = (const char*)sqlite3_value_text(apVal[ctx.iCol+2]);
- int nText = sqlite3_value_bytes(apVal[ctx.iCol+2]);
- rc = sqlite3Fts5Tokenize(pConfig,
- FTS5_TOKENIZE_DOCUMENT,
- zText, nText,
- (void*)&ctx,
- fts5StorageInsertCallback
- );
+ int bReset = 0; /* True if tokenizer locale must be reset */
+ int nText = 0; /* Size of pText in bytes */
+ const char *pText = 0; /* Pointer to buffer containing text value */
+ sqlite3_value *pVal = apVal[ctx.iCol+2];
+ int bDisk = 0;
+ if( p->pSavedRow && sqlite3_value_nochange(pVal) ){
+ pVal = sqlite3_column_value(p->pSavedRow, ctx.iCol+1);
+ bDisk = 1;
+ }
+ rc = sqlite3Fts5ExtractText(pConfig, pVal, bDisk, &bReset, &pText,&nText);
+ if( rc==SQLITE_OK ){
+ assert( bReset==0 || pConfig->bLocale );
+ rc = sqlite3Fts5Tokenize(pConfig,
+ FTS5_TOKENIZE_DOCUMENT, pText, nText, (void*)&ctx,
+ fts5StorageInsertCallback
+ );
+ if( bReset ) sqlite3Fts5ClearLocale(pConfig);
+ }
}
sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol);
p->aTotalSize[ctx.iCol] += (i64)ctx.szCol;
@@ -988,14 +1133,22 @@ int sqlite3Fts5StorageIntegrity(Fts5Storage *p, int iArg){
rc = sqlite3Fts5TermsetNew(&ctx.pTermset);
}
if( rc==SQLITE_OK ){
- const char *zText = (const char*)sqlite3_column_text(pScan, i+1);
- int nText = sqlite3_column_bytes(pScan, i+1);
- rc = sqlite3Fts5Tokenize(pConfig,
- FTS5_TOKENIZE_DOCUMENT,
- zText, nText,
- (void*)&ctx,
- fts5StorageIntegrityCallback
+ int bReset = 0; /* True if tokenizer locale must be reset */
+ int nText = 0; /* Size of pText in bytes */
+ const char *pText = 0; /* Pointer to buffer containing text value */
+
+ rc = sqlite3Fts5ExtractText(pConfig,
+ sqlite3_column_value(pScan, i+1), 1, &bReset, &pText, &nText
);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3Fts5Tokenize(pConfig,
+ FTS5_TOKENIZE_DOCUMENT,
+ pText, nText,
+ (void*)&ctx,
+ fts5StorageIntegrityCallback
+ );
+ if( bReset ) sqlite3Fts5ClearLocale(pConfig);
+ }
}
if( rc==SQLITE_OK && pConfig->bColumnsize && ctx.szCol!=aColSize[i] ){
rc = FTS5_CORRUPT;
diff --git a/ext/fts5/fts5_tcl.c b/ext/fts5/fts5_tcl.c
index b67b037c45..a8ab44096b 100644
--- a/ext/fts5/fts5_tcl.c
+++ b/ext/fts5/fts5_tcl.c
@@ -96,14 +96,14 @@ static int SQLITE_TCLAPI f5tDbAndApi(
rc = sqlite3_prepare_v2(db, "SELECT fts5(?1)", -1, &pStmt, 0);
if( rc!=SQLITE_OK ){
- Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), 0);
+ Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), (char*)0);
return TCL_ERROR;
}
sqlite3_bind_pointer(pStmt, 1, (void*)&pApi, "fts5_api_ptr", 0);
sqlite3_step(pStmt);
if( sqlite3_finalize(pStmt)!=SQLITE_OK ){
- Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), 0);
+ Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), (char*)0);
return TCL_ERROR;
}
@@ -240,6 +240,7 @@ static int SQLITE_TCLAPI xF5tApi(
{ "xQueryToken", 2, "IPHRASE ITERM" }, /* 18 */
{ "xInstToken", 2, "IDX ITERM" }, /* 19 */
+ { "xColumnLocale", 1, "COL" }, /* 20 */
{ 0, 0, 0}
};
@@ -391,7 +392,7 @@ static int SQLITE_TCLAPI xF5tApi(
CASE(12, "xSetAuxdata") {
F5tAuxData *pData = (F5tAuxData*)sqlite3_malloc(sizeof(F5tAuxData));
if( pData==0 ){
- Tcl_AppendResult(interp, "out of memory", 0);
+ Tcl_AppendResult(interp, "out of memory", (char*)0);
return TCL_ERROR;
}
pData->pObj = objv[2];
@@ -451,7 +452,7 @@ static int SQLITE_TCLAPI xF5tApi(
rc = p->pApi->xPhraseFirst(p->pFts, iPhrase, &iter, &iCol, &iOff);
if( rc!=SQLITE_OK ){
- Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+ Tcl_AppendResult(interp, sqlite3ErrName(rc), (char*)0);
return TCL_ERROR;
}
for( ;iCol>=0; p->pApi->xPhraseNext(p->pFts, &iter, &iCol, &iOff) ){
@@ -528,6 +529,20 @@ static int SQLITE_TCLAPI xF5tApi(
break;
}
+ CASE(20, "xColumnLocale") {
+ const char *z = 0;
+ int n = 0;
+ int iCol;
+ if( Tcl_GetIntFromObj(interp, objv[2], &iCol) ){
+ return TCL_ERROR;
+ }
+ rc = p->pApi->xColumnLocale(p->pFts, iCol, &z, &n);
+ if( rc==SQLITE_OK && z ){
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(z, n));
+ }
+ break;
+ }
+
default:
assert( 0 );
break;
@@ -668,7 +683,7 @@ static int SQLITE_TCLAPI f5tCreateFunction(
pApi, zName, (void*)pCtx, xF5tFunction, xF5tDestroy
);
if( rc!=SQLITE_OK ){
- Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), 0);
+ Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), (char*)0);
return TCL_ERROR;
}
@@ -735,7 +750,7 @@ static int SQLITE_TCLAPI f5tTokenize(
if( objc==5 ){
char *zOpt = Tcl_GetString(objv[1]);
if( strcmp("-subst", zOpt) ){
- Tcl_AppendResult(interp, "unrecognized option: ", zOpt, 0);
+ Tcl_AppendResult(interp, "unrecognized option: ", zOpt, (char*)0);
return TCL_ERROR;
}
}
@@ -744,7 +759,7 @@ static int SQLITE_TCLAPI f5tTokenize(
return TCL_ERROR;
}
if( nArg==0 ){
- Tcl_AppendResult(interp, "no such tokenizer: ", 0);
+ Tcl_AppendResult(interp, "no such tokenizer: ", (char*)0);
Tcl_Free((void*)azArg);
return TCL_ERROR;
}
@@ -752,13 +767,13 @@ static int SQLITE_TCLAPI f5tTokenize(
rc = pApi->xFindTokenizer(pApi, azArg[0], &pUserdata, &tokenizer);
if( rc!=SQLITE_OK ){
- Tcl_AppendResult(interp, "no such tokenizer: ", azArg[0], 0);
+ Tcl_AppendResult(interp, "no such tokenizer: ", azArg[0], (char*)0);
return TCL_ERROR;
}
rc = tokenizer.xCreate(pUserdata, &azArg[1], (int)(nArg-1), &pTok);
if( rc!=SQLITE_OK ){
- Tcl_AppendResult(interp, "error in tokenizer.xCreate()", 0);
+ Tcl_AppendResult(interp, "error in tokenizer.xCreate()", (char*)0);
return TCL_ERROR;
}
@@ -772,7 +787,7 @@ static int SQLITE_TCLAPI f5tTokenize(
);
tokenizer.xDelete(pTok);
if( rc!=SQLITE_OK ){
- Tcl_AppendResult(interp, "error in tokenizer.xTokenize()", 0);
+ Tcl_AppendResult(interp, "error in tokenizer.xTokenize()", (char*)0);
Tcl_DecrRefCount(pRet);
return TCL_ERROR;
}
@@ -796,18 +811,32 @@ typedef struct F5tTokenizerInstance F5tTokenizerInstance;
struct F5tTokenizerContext {
void *pCtx;
int (*xToken)(void*, int, const char*, int, int, int);
+ F5tTokenizerInstance *pInst;
};
struct F5tTokenizerModule {
Tcl_Interp *interp;
Tcl_Obj *pScript;
+ void *pParentCtx;
+ fts5_tokenizer_v2 parent_v2;
+ fts5_tokenizer parent;
F5tTokenizerContext *pContext;
};
+/*
+** zLocale:
+** Within a call to xTokenize_v2(), pLocale/nLocale store the locale
+** passed to the call by fts5. This can be retrieved by a Tcl tokenize
+** script using [sqlite3_fts5_locale].
+*/
struct F5tTokenizerInstance {
Tcl_Interp *interp;
Tcl_Obj *pScript;
+ F5tTokenizerModule *pModule;
+ Fts5Tokenizer *pParent;
F5tTokenizerContext *pContext;
+ const char *pLocale;
+ int nLocale;
};
static int f5tTokenizerCreate(
@@ -816,11 +845,20 @@ static int f5tTokenizerCreate(
int nArg,
Fts5Tokenizer **ppOut
){
+ Fts5Tokenizer *pParent = 0;
F5tTokenizerModule *pMod = (F5tTokenizerModule*)pCtx;
Tcl_Obj *pEval;
int rc = TCL_OK;
int i;
+ assert( pMod->parent_v2.xCreate==0 || pMod->parent.xCreate==0 );
+ if( pMod->parent_v2.xCreate ){
+ rc = pMod->parent_v2.xCreate(pMod->pParentCtx, 0, 0, &pParent);
+ }
+ if( pMod->parent.xCreate ){
+ rc = pMod->parent.xCreate(pMod->pParentCtx, 0, 0, &pParent);
+ }
+
pEval = Tcl_DuplicateObj(pMod->pScript);
Tcl_IncrRefCount(pEval);
for(i=0; rc==TCL_OK && iinterp = pMod->interp;
pInst->pScript = Tcl_GetObjResult(pMod->interp);
pInst->pContext = pMod->pContext;
+ pInst->pParent = pParent;
+ pInst->pModule = pMod;
Tcl_IncrRefCount(pInst->pScript);
*ppOut = (Fts5Tokenizer*)pInst;
}
@@ -850,11 +890,21 @@ static int f5tTokenizerCreate(
static void f5tTokenizerDelete(Fts5Tokenizer *p){
F5tTokenizerInstance *pInst = (F5tTokenizerInstance*)p;
- Tcl_DecrRefCount(pInst->pScript);
- ckfree((char *)pInst);
+ if( pInst ){
+ if( pInst->pParent ){
+ if( pInst->pModule->parent_v2.xDelete ){
+ pInst->pModule->parent_v2.xDelete(pInst->pParent);
+ }else{
+ pInst->pModule->parent.xDelete(pInst->pParent);
+ }
+ }
+ Tcl_DecrRefCount(pInst->pScript);
+ ckfree((char *)pInst);
+ }
}
-static int f5tTokenizerTokenize(
+
+static int f5tTokenizerReallyTokenize(
Fts5Tokenizer *p,
void *pCtx,
int flags,
@@ -862,6 +912,7 @@ static int f5tTokenizerTokenize(
int (*xToken)(void*, int, const char*, int, int, int)
){
F5tTokenizerInstance *pInst = (F5tTokenizerInstance*)p;
+ F5tTokenizerInstance *pOldInst = 0;
void *pOldCtx;
int (*xOldToken)(void*, int, const char*, int, int, int);
Tcl_Obj *pEval;
@@ -870,9 +921,11 @@ static int f5tTokenizerTokenize(
pOldCtx = pInst->pContext->pCtx;
xOldToken = pInst->pContext->xToken;
+ pOldInst = pInst->pContext->pInst;
pInst->pContext->pCtx = pCtx;
pInst->pContext->xToken = xToken;
+ pInst->pContext->pInst = pInst;
assert(
flags==FTS5_TOKENIZE_DOCUMENT
@@ -908,9 +961,105 @@ static int f5tTokenizerTokenize(
pInst->pContext->pCtx = pOldCtx;
pInst->pContext->xToken = xOldToken;
+ pInst->pContext->pInst = pOldInst;
return rc;
}
+typedef struct CallbackCtx CallbackCtx;
+struct CallbackCtx {
+ Fts5Tokenizer *p;
+ void *pCtx;
+ int flags;
+ int (*xToken)(void*, int, const char*, int, int, int);
+};
+
+static int f5tTokenizeCallback(
+ void *pCtx,
+ int tflags,
+ const char *z, int n,
+ int iStart, int iEnd
+){
+ CallbackCtx *p = (CallbackCtx*)pCtx;
+ return f5tTokenizerReallyTokenize(p->p, p->pCtx, p->flags, z, n, p->xToken);
+}
+
+static int f5tTokenizerTokenize_v2(
+ Fts5Tokenizer *p,
+ void *pCtx,
+ int flags,
+ const char *pText, int nText,
+ const char *pLoc, int nLoc,
+ int (*xToken)(void*, int, const char*, int, int, int)
+){
+ int rc = SQLITE_OK;
+ F5tTokenizerInstance *pInst = (F5tTokenizerInstance*)p;
+
+ pInst->pLocale = pLoc;
+ pInst->nLocale = nLoc;
+
+ if( pInst->pParent ){
+ CallbackCtx ctx;
+ ctx.p = p;
+ ctx.pCtx = pCtx;
+ ctx.flags = flags;
+ ctx.xToken = xToken;
+ if( pInst->pModule->parent_v2.xTokenize ){
+ rc = pInst->pModule->parent_v2.xTokenize(
+ pInst->pParent, (void*)&ctx, flags, pText, nText,
+ pLoc, nLoc, f5tTokenizeCallback
+ );
+ }else{
+ rc = pInst->pModule->parent.xTokenize(
+ pInst->pParent, (void*)&ctx, flags, pText, nText, f5tTokenizeCallback
+ );
+ }
+ }else{
+ rc = f5tTokenizerReallyTokenize(p, pCtx, flags, pText, nText, xToken);
+ }
+
+ pInst->pLocale = 0;
+ pInst->nLocale = 0;
+ return rc;
+}
+static int f5tTokenizerTokenize(
+ Fts5Tokenizer *p,
+ void *pCtx,
+ int flags,
+ const char *pText, int nText,
+ int (*xToken)(void*, int, const char*, int, int, int)
+){
+ return f5tTokenizerTokenize_v2(p, pCtx, flags, pText, nText, 0, 0, xToken);
+}
+
+/*
+** sqlite3_fts5_locale
+*/
+static int SQLITE_TCLAPI f5tTokenizerLocale(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ F5tTokenizerContext *p = (F5tTokenizerContext*)clientData;
+
+ if( objc!=1 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "");
+ return TCL_ERROR;
+ }
+
+ if( p->xToken==0 ){
+ Tcl_AppendResult(interp,
+ "sqlite3_fts5_locale may only be used by tokenizer callback", (char*)0
+ );
+ return TCL_ERROR;
+ }
+
+ Tcl_SetObjResult(interp,
+ Tcl_NewStringObj(p->pInst->pLocale, p->pInst->nLocale)
+ );
+ return TCL_OK;
+}
+
/*
** sqlite3_fts5_token ?-colocated? TEXT START END
*/
@@ -949,7 +1098,7 @@ static int SQLITE_TCLAPI f5tTokenizerReturn(
if( p->xToken==0 ){
Tcl_AppendResult(interp,
- "sqlite3_fts5_token may only be used by tokenizer callback", 0
+ "sqlite3_fts5_token may only be used by tokenizer callback", (char*)0
);
return TCL_ERROR;
}
@@ -996,32 +1145,112 @@ static int SQLITE_TCLAPI f5tCreateTokenizer(
fts5_api *pApi;
char *zName;
Tcl_Obj *pScript;
- fts5_tokenizer t;
F5tTokenizerModule *pMod;
- int rc;
+ int rc = SQLITE_OK;
+ int bV2 = 0; /* True to use _v2 API */
+ int iVersion = 2; /* Value for _v2.iVersion */
+ const char *zParent = 0; /* Name of parent tokenizer, if any */
+ int ii = 0;
- if( objc!=4 ){
- Tcl_WrongNumArgs(interp, 1, objv, "DB NAME SCRIPT");
+ if( objc<4 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "?OPTIONS? DB NAME SCRIPT");
return TCL_ERROR;
}
- if( f5tDbAndApi(interp, objv[1], &db, &pApi) ){
+
+ /* Parse any options. Set stack variables bV2 and zParent. */
+ for(ii=1; iiinterp = interp;
pMod->pScript = pScript;
- pMod->pContext = pContext;
Tcl_IncrRefCount(pScript);
- rc = pApi->xCreateTokenizer(pApi, zName, (void*)pMod, &t, f5tDelTokenizer);
+ pMod->pContext = pContext;
+ if( zParent ){
+ if( bV2 ){
+ fts5_tokenizer_v2 *pParent = 0;
+ rc = pApi->xFindTokenizer_v2(pApi, zParent, &pMod->pParentCtx, &pParent);
+ if( rc==SQLITE_OK ){
+ memcpy(&pMod->parent_v2, pParent, sizeof(fts5_tokenizer_v2));
+ pMod->parent_v2.xDelete(0);
+ }
+ }else{
+ rc = pApi->xFindTokenizer(pApi, zParent, &pMod->pParentCtx,&pMod->parent);
+ if( rc==SQLITE_OK ){
+ pMod->parent.xDelete(0);
+ }
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ void *pModCtx = (void*)pMod;
+ if( bV2==0 ){
+ fts5_tokenizer t;
+ t.xCreate = f5tTokenizerCreate;
+ t.xTokenize = f5tTokenizerTokenize;
+ t.xDelete = f5tTokenizerDelete;
+ rc = pApi->xCreateTokenizer(pApi, zName, pModCtx, &t, f5tDelTokenizer);
+ }else{
+ fts5_tokenizer_v2 t2;
+ memset(&t2, 0, sizeof(t2));
+ t2.iVersion = iVersion;
+ t2.xCreate = f5tTokenizerCreate;
+ t2.xTokenize = f5tTokenizerTokenize_v2;
+ t2.xDelete = f5tTokenizerDelete;
+ rc = pApi->xCreateTokenizer_v2(pApi, zName, pModCtx, &t2,f5tDelTokenizer);
+ }
+ }
+
if( rc!=SQLITE_OK ){
- Tcl_AppendResult(interp, "error in fts5_api.xCreateTokenizer()", 0);
+ Tcl_AppendResult(interp, (
+ bV2 ? "error in fts5_api.xCreateTokenizer_v2()"
+ : "error in fts5_api.xCreateTokenizer()"
+ ), (char*)0);
return TCL_ERROR;
}
@@ -1311,12 +1540,92 @@ static int SQLITE_TCLAPI f5tRegisterOriginText(
Tcl_ResetResult(interp);
if( rc!=SQLITE_OK ){
- Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), 0);
+ Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), (void*)0);
return TCL_ERROR;
}
return TCL_OK;
}
+/*
+** This function is used to DROP an fts5 table. It works even if the data
+** structures fts5 stores within the database are corrupt, which sometimes
+** prevents a straight "DROP TABLE" command from succeeding.
+**
+** The first parameter is the database handle to use for the DROP TABLE
+** operation. The second is the name of the database to drop the fts5 table
+** from (i.e. "main", "temp" or the name of an attached database). The
+** third parameter is the name of the fts5 table to drop.
+**
+** SQLITE_OK is returned if the table is successfully dropped. Or, if an
+** error occurs, an SQLite error code.
+*/
+static int sqlite3_fts5_drop_corrupt_table(
+ sqlite3 *db, /* Database handle */
+ const char *zDb, /* Database name ("main", "temp" etc.) */
+ const char *zTab /* Name of fts5 table to drop */
+){
+ int rc = SQLITE_OK;
+ int bDef = 0;
+
+ rc = sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, -1, &bDef);
+ if( rc==SQLITE_OK ){
+ char *zScript = sqlite3_mprintf(
+ "DELETE FROM %Q.'%q_data';"
+ "DELETE FROM %Q.'%q_config';"
+ "INSERT INTO %Q.'%q_data' VALUES(10, X'0000000000');"
+ "INSERT INTO %Q.'%q_config' VALUES('version', 4);"
+ "DROP TABLE %Q.'%q';",
+ zDb, zTab, zDb, zTab, zDb, zTab, zDb, zTab, zDb, zTab
+ );
+
+ if( zScript==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ if( bDef ) sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0);
+ rc = sqlite3_exec(db, zScript, 0, 0, 0);
+ if( bDef ) sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, 0);
+ sqlite3_free(zScript);
+ }
+ }
+
+ return rc;
+}
+
+/*
+** sqlite3_fts5_drop_corrupt_table DB DATABASE TABLE
+**
+** Description...
+*/
+static int SQLITE_TCLAPI f5tDropCorruptTable(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ sqlite3 *db = 0;
+ const char *zDb = 0;
+ const char *zTab = 0;
+ int rc = SQLITE_OK;
+
+ if( objc!=4 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB DATABASE TABLE");
+ return TCL_ERROR;
+ }
+ if( f5tDbPointer(interp, objv[1], &db) ){
+ return TCL_ERROR;
+ }
+ zDb = Tcl_GetString(objv[2]);
+ zTab = Tcl_GetString(objv[3]);
+
+ rc = sqlite3_fts5_drop_corrupt_table(db, zDb, zTab);
+ if( rc!=SQLITE_OK ){
+ Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), (void*)0);
+ return TCL_ERROR;
+ }
+
+ return TCL_OK;
+}
+
/*
** Entry point.
*/
@@ -1328,13 +1637,15 @@ int Fts5tcl_Init(Tcl_Interp *interp){
} aCmd[] = {
{ "sqlite3_fts5_create_tokenizer", f5tCreateTokenizer, 1 },
{ "sqlite3_fts5_token", f5tTokenizerReturn, 1 },
+ { "sqlite3_fts5_locale", f5tTokenizerLocale, 1 },
{ "sqlite3_fts5_tokenize", f5tTokenize, 0 },
{ "sqlite3_fts5_create_function", f5tCreateFunction, 0 },
{ "sqlite3_fts5_may_be_corrupt", f5tMayBeCorrupt, 0 },
{ "sqlite3_fts5_token_hash", f5tTokenHash, 0 },
{ "sqlite3_fts5_register_matchinfo", f5tRegisterMatchinfo, 0 },
{ "sqlite3_fts5_register_fts5tokenize", f5tRegisterTok, 0 },
- { "sqlite3_fts5_register_origintext",f5tRegisterOriginText, 0 }
+ { "sqlite3_fts5_register_origintext",f5tRegisterOriginText, 0 },
+ { "sqlite3_fts5_drop_corrupt_table", f5tDropCorruptTable, 0 }
};
int i;
F5tTokenizerContext *pContext;
diff --git a/ext/fts5/fts5_tokenize.c b/ext/fts5/fts5_tokenize.c
index 9f5cd24c3c..f92529b840 100644
--- a/ext/fts5/fts5_tokenize.c
+++ b/ext/fts5/fts5_tokenize.c
@@ -79,7 +79,7 @@ static int fts5AsciiCreate(
int i;
memset(p, 0, sizeof(AsciiTokenizer));
memcpy(p->aTokenChar, aAsciiTokenChar, sizeof(aAsciiTokenChar));
- for(i=0; rc==SQLITE_OK && ipTokenizer ){
- p->tokenizer.xDelete(p->pTokenizer);
+ p->tokenizer_v2.xDelete(p->pTokenizer);
}
sqlite3_free(p);
}
@@ -587,6 +584,7 @@ static int fts5PorterCreate(
PorterTokenizer *pRet;
void *pUserdata = 0;
const char *zBase = "unicode61";
+ fts5_tokenizer_v2 *pV2 = 0;
if( nArg>0 ){
zBase = azArg[0];
@@ -595,14 +593,15 @@ static int fts5PorterCreate(
pRet = (PorterTokenizer*)sqlite3_malloc(sizeof(PorterTokenizer));
if( pRet ){
memset(pRet, 0, sizeof(PorterTokenizer));
- rc = pApi->xFindTokenizer(pApi, zBase, &pUserdata, &pRet->tokenizer);
+ rc = pApi->xFindTokenizer_v2(pApi, zBase, &pUserdata, &pV2);
}else{
rc = SQLITE_NOMEM;
}
if( rc==SQLITE_OK ){
int nArg2 = (nArg>0 ? nArg-1 : 0);
- const char **azArg2 = (nArg2 ? &azArg[1] : 0);
- rc = pRet->tokenizer.xCreate(pUserdata, azArg2, nArg2, &pRet->pTokenizer);
+ const char **az2 = (nArg2 ? &azArg[1] : 0);
+ memcpy(&pRet->tokenizer_v2, pV2, sizeof(fts5_tokenizer_v2));
+ rc = pRet->tokenizer_v2.xCreate(pUserdata, az2, nArg2, &pRet->pTokenizer);
}
if( rc!=SQLITE_OK ){
@@ -1253,6 +1252,7 @@ static int fts5PorterTokenize(
void *pCtx,
int flags,
const char *pText, int nText,
+ const char *pLoc, int nLoc,
int (*xToken)(void*, int, const char*, int nToken, int iStart, int iEnd)
){
PorterTokenizer *p = (PorterTokenizer*)pTokenizer;
@@ -1260,8 +1260,8 @@ static int fts5PorterTokenize(
sCtx.xToken = xToken;
sCtx.pCtx = pCtx;
sCtx.aBuf = p->aBuf;
- return p->tokenizer.xTokenize(
- p->pTokenizer, (void*)&sCtx, flags, pText, nText, fts5PorterCb
+ return p->tokenizer_v2.xTokenize(
+ p->pTokenizer, (void*)&sCtx, flags, pText, nText, pLoc, nLoc, fts5PorterCb
);
}
@@ -1291,41 +1291,46 @@ static int fts5TriCreate(
Fts5Tokenizer **ppOut
){
int rc = SQLITE_OK;
- TrigramTokenizer *pNew = (TrigramTokenizer*)sqlite3_malloc(sizeof(*pNew));
+ TrigramTokenizer *pNew = 0;
UNUSED_PARAM(pUnused);
- if( pNew==0 ){
- rc = SQLITE_NOMEM;
+ if( nArg%2 ){
+ rc = SQLITE_ERROR;
}else{
int i;
- pNew->bFold = 1;
- pNew->iFoldParam = 0;
- for(i=0; rc==SQLITE_OK && ibFold = 1;
+ pNew->iFoldParam = 0;
+
+ for(i=0; rc==SQLITE_OK && ibFold = (zArg[0]=='0');
+ }
+ }else if( 0==sqlite3_stricmp(azArg[i], "remove_diacritics") ){
+ if( (zArg[0]!='0' && zArg[0]!='1' && zArg[0]!='2') || zArg[1] ){
+ rc = SQLITE_ERROR;
+ }else{
+ pNew->iFoldParam = (zArg[0]!='0') ? 2 : 0;
+ }
}else{
- pNew->bFold = (zArg[0]=='0');
- }
- }else if( 0==sqlite3_stricmp(azArg[i], "remove_diacritics") ){
- if( (zArg[0]!='0' && zArg[0]!='1' && zArg[0]!='2') || zArg[1] ){
rc = SQLITE_ERROR;
- }else{
- pNew->iFoldParam = (zArg[0]!='0') ? 2 : 0;
}
- }else{
+ }
+
+ if( pNew->iFoldParam!=0 && pNew->bFold==0 ){
rc = SQLITE_ERROR;
}
- }
- if( iiFoldParam!=0 && pNew->bFold==0 ){
- rc = SQLITE_ERROR;
- }
-
- if( rc!=SQLITE_OK ){
- fts5TriDelete((Fts5Tokenizer*)pNew);
- pNew = 0;
+
+ if( rc!=SQLITE_OK ){
+ fts5TriDelete((Fts5Tokenizer*)pNew);
+ pNew = 0;
+ }
}
}
*ppOut = (Fts5Tokenizer*)pNew;
@@ -1450,7 +1455,6 @@ int sqlite3Fts5TokenizerInit(fts5_api *pApi){
} aBuiltin[] = {
{ "unicode61", {fts5UnicodeCreate, fts5UnicodeDelete, fts5UnicodeTokenize}},
{ "ascii", {fts5AsciiCreate, fts5AsciiDelete, fts5AsciiTokenize }},
- { "porter", {fts5PorterCreate, fts5PorterDelete, fts5PorterTokenize }},
{ "trigram", {fts5TriCreate, fts5TriDelete, fts5TriTokenize}},
};
@@ -1465,6 +1469,19 @@ int sqlite3Fts5TokenizerInit(fts5_api *pApi){
0
);
}
-
+ if( rc==SQLITE_OK ){
+ fts5_tokenizer_v2 sPorter = {
+ 2,
+ fts5PorterCreate,
+ fts5PorterDelete,
+ fts5PorterTokenize
+ };
+ rc = pApi->xCreateTokenizer_v2(pApi,
+ "porter",
+ (void*)pApi,
+ &sPorter,
+ 0
+ );
+ }
return rc;
}
diff --git a/ext/fts5/fts5_unicode2.c b/ext/fts5/fts5_unicode2.c
index 3e97264fa8..cc164a4569 100644
--- a/ext/fts5/fts5_unicode2.c
+++ b/ext/fts5/fts5_unicode2.c
@@ -364,6 +364,9 @@ int sqlite3Fts5UnicodeCatParse(const char *zCat, u8 *aArray){
default: return 1; }
break;
+
+ default:
+ return 1;
}
return 0;
}
diff --git a/ext/fts5/test/fts5_common.tcl b/ext/fts5/test/fts5_common.tcl
index 7076a52bbf..8ea87dbdd1 100644
--- a/ext/fts5/test/fts5_common.tcl
+++ b/ext/fts5/test/fts5_common.tcl
@@ -51,6 +51,10 @@ proc fts5_test_poslist2 {cmd} {
sort_poslist $res
}
+proc fts5_test_insttoken {cmd iInst iToken} {
+ $cmd xInstToken $iInst $iToken
+}
+
proc fts5_test_collist {cmd} {
set res [list]
@@ -78,6 +82,9 @@ proc fts5_test_columnsize {cmd} {
proc fts5_columntext {cmd iCol} {
$cmd xColumnText $iCol
}
+proc fts5_columnlocale {cmd iCol} {
+ $cmd xColumnLocale $iCol
+}
proc fts5_test_columntext {cmd} {
set res [list]
@@ -87,6 +94,14 @@ proc fts5_test_columntext {cmd} {
set res
}
+proc fts5_test_columnlocale {cmd} {
+ set res [list]
+ for {set i 0} {$i < [$cmd xColumnCount]} {incr i} {
+ lappend res [$cmd xColumnLocale $i]
+ }
+ set res
+}
+
proc fts5_test_columntotalsize {cmd} {
set res [list]
for {set i 0} {$i < [$cmd xColumnCount]} {incr i} {
@@ -165,10 +180,12 @@ proc fts5_aux_test_functions {db} {
foreach f {
fts5_test_columnsize
fts5_test_columntext
+ fts5_test_columnlocale
fts5_test_columntotalsize
fts5_test_poslist
fts5_test_poslist2
fts5_test_collist
+ fts5_test_insttoken
fts5_test_tokenize
fts5_test_rowcount
fts5_test_rowid
@@ -177,6 +194,7 @@ proc fts5_aux_test_functions {db} {
fts5_test_queryphrase
fts5_test_phrasecount
fts5_columntext
+ fts5_columnlocale
fts5_queryphrase
fts5_collist
} {
diff --git a/ext/fts5/test/fts5ah.test b/ext/fts5/test/fts5ah.test
index bc80057833..bf9c9e9dbc 100644
--- a/ext/fts5/test/fts5ah.test
+++ b/ext/fts5/test/fts5ah.test
@@ -163,6 +163,17 @@ do_execsql_test 1.8.2 {
SELECT count(*) FROM t1 WHERE t1 MATCH 'x' AND rowid < 'text';
} {10000}
+do_execsql_test 1.8.3 {
+ SELECT count(*) FROM t1 WHERE t1 MATCH 'x' AND rowid<5000 AND rowid < 'text';
+} {4999}
+do_execsql_test 1.8.4 {
+ SELECT count(*) FROM t1 WHERE t1 MATCH 'x' AND rowid>5000 AND rowid > 'text';
+} {0}
+
+do_catchsql_test 1.9 {
+ SELECT * FROM t1('*xy');
+} {1 {unknown special query: xy}}
+
} ;# foreach_detail_mode
#db eval {SELECT rowid, fts5_decode(rowid, block) aS r FROM t1_data} {puts $r}
diff --git a/ext/fts5/test/fts5al.test b/ext/fts5/test/fts5al.test
index b344e0d2ea..7187ad67c7 100644
--- a/ext/fts5/test/fts5al.test
+++ b/ext/fts5/test/fts5al.test
@@ -293,6 +293,16 @@ do_catchsql_test 4.4.4 {
SELECT *, rank FROM t3 WHERE t3 MATCH 'a' AND rank MATCH NULL
} {1 {parse error in rank function: }}
+# Check that the second and subsequent rank= constraints are ignored.
+#
+do_catchsql_test 4.3.3 {
+ SELECT *, rank FROM t3
+ WHERE t3 MATCH 'a' AND
+ rank MATCH 'nosuch()' AND
+ rank MATCH 'rowidmod(3)'
+ ORDER BY rank ASC
+} {1 {unable to use function MATCH in the requested context}}
+
} ;# foreach_detail_mode
diff --git a/ext/fts5/test/fts5blob.test b/ext/fts5/test/fts5blob.test
new file mode 100644
index 0000000000..4233719fbc
--- /dev/null
+++ b/ext/fts5/test/fts5blob.test
@@ -0,0 +1,166 @@
+# 2024 July 30
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# This file verifies that:
+#
+# * blob values may be written to locale=0 tables.
+#
+# * blob values - other than fts5_locale() values - may not be written
+# to locale=0 tables. This is an SQLITE_MISMATCH error
+#
+# * blob values may be returned by queries on the external-content table
+# of a locale=0 table.
+#
+# * blob values not may be returned by queries on the external-content
+# table of a locale=1 table, apart from fts5_locale() blobs. This is an
+# SQLITE_MISMATCH error.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5blob
+
+# If SQLITE_ENABLE_FTS5 is not defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+# Test that blobs may be stored in normal locale=0 tables.
+#
+foreach {tn enc} {
+ 1 utf8
+ 2 utf16
+} {
+ reset_db
+ fts5_aux_test_functions db
+
+ execsql "PRAGMA encoding = $enc"
+
+ execsql "
+ CREATE VIRTUAL TABLE t1 USING fts5(x, y);
+ "
+ do_execsql_test 1.$tn.0 {
+ CREATE VIRTUAL TABLE tt USING fts5vocab('t1', 'instance');
+ INSERT INTO t1(rowid, x, y) VALUES(1, 555, X'0000000041424320444546');
+ INSERT INTO t1(rowid, x, y) VALUES(2, 666, X'41424300444546');
+ INSERT INTO t1(rowid, x, y) VALUES(3, 777, 'xyz');
+ }
+
+ do_execsql_test 1.$tn.1 {
+ SELECT rowid, quote(x), quote(y) FROM t1
+ } {
+ 1 555 X'0000000041424320444546'
+ 2 666 X'41424300444546'
+ 3 777 'xyz'
+ }
+
+ do_execsql_test 1.$tn.2 {
+ DELETE FROM t1 WHERE rowid=2;
+ DELETE FROM t1 WHERE rowid=1;
+ }
+
+ do_execsql_test 1.$tn.3 {
+ PRAGMA integrity_check;
+ } {ok}
+}
+
+#--------------------------------------------------------------------------
+# Test that a blob may be stored and retrieved in an unindexed column of
+# a regular table with locale=1.
+#
+reset_db
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x, y UNINDEXED, locale=1);
+ INSERT INTO t1(rowid, x, y) VALUES(12, 'twelve', X'0000000041424320444546');
+}
+
+do_execsql_test 2.1 {
+ select rowid, x, quote(y) FROM t1
+} {
+ 12 twelve X'0000000041424320444546'
+}
+
+#--------------------------------------------------------------------------
+# Test that blobs may not be written to any type of table with locale=1
+# set. Except, they may be written to UNINDEXED columns.
+#
+reset_db
+do_execsql_test 3.0 {
+ CREATE TABLE t1(a, b);
+
+ CREATE VIRTUAL TABLE x1 USING fts5(a, b, locale=1);
+ CREATE VIRTUAL TABLE x2 USING fts5(a, b, locale=1, content=t2);
+ CREATE VIRTUAL TABLE x3 USING fts5(a, b, locale=1, content=);
+}
+
+do_catchsql_test 3.1 {
+ INSERT INTO x1(rowid, a, b) VALUES(113, 'hello world', X'123456');
+} {1 {datatype mismatch}}
+do_catchsql_test 3.2 {
+ INSERT INTO x2(rowid, a, b) VALUES(113, 'hello world', X'123456');
+} {1 {datatype mismatch}}
+do_catchsql_test 3.3 {
+ INSERT INTO x3(rowid, a, b) VALUES(113, 'hello world', X'123456');
+} {1 {datatype mismatch}}
+
+
+#--------------------------------------------------------------------------
+# Test that fts5_locale() values may not be written to any type of table
+# without locale=1 set. Even to an UNINDEXED column.
+#
+reset_db
+do_execsql_test 3.0 {
+ CREATE TABLE t1(a, b);
+
+ CREATE VIRTUAL TABLE x1 USING fts5(a, b);
+ CREATE VIRTUAL TABLE x2 USING fts5(a, b, content=t2);
+ CREATE VIRTUAL TABLE x3 USING fts5(a, b, content=);
+
+ CREATE VIRTUAL TABLE x4 USING fts5(a, b, c UNINDEXED);
+}
+
+do_catchsql_test 3.1 {
+ INSERT INTO x1(rowid, a, b)
+ VALUES(113, 'hello world', fts5_locale('en_AU', 'abc'));
+} {1 {fts5_locale() requires locale=1}}
+do_catchsql_test 3.2 {
+ INSERT INTO x2(rowid, a, b)
+ VALUES(113, 'hello world', fts5_locale('en_AU', 'abc'));
+} {1 {fts5_locale() requires locale=1}}
+do_catchsql_test 3.3 {
+ INSERT INTO x3(rowid, a, b)
+ VALUES(113, 'hello world', fts5_locale('en_AU', 'abc'));
+} {1 {fts5_locale() requires locale=1}}
+do_catchsql_test 3.4 {
+ INSERT INTO x4(rowid, a, b, c)
+ VALUES(113, 'hello world', 'yesno', fts5_locale('en_AU', 'abc'));
+} {1 {fts5_locale() requires locale=1}}
+
+
+#-------------------------------------------------------------------------
+#
+reset_db
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE x1 USING fts5(x);
+}
+
+foreach {tn sql} {
+ 1 { INSERT INTO x1(rowid, x) VALUES(4.5, 'abcd') }
+ 2 { INSERT INTO x1(rowid, x) VALUES('xyz', 'abcd') }
+ 3 { INSERT INTO x1(rowid, x) VALUES(X'001122', 'abcd') }
+} {
+ do_catchsql_test 4.1.$tn $sql {1 {datatype mismatch}}
+}
+
+
+finish_test
+
+
diff --git a/ext/fts5/test/fts5cat.test b/ext/fts5/test/fts5cat.test
index 483f64bfef..71e2abe3ae 100644
--- a/ext/fts5/test/fts5cat.test
+++ b/ext/fts5/test/fts5cat.test
@@ -55,5 +55,22 @@ do_execsql_test 1.5 {
SELECT * FROM t4t
} {สนามกีฬา 1 1}
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 2.0 "
+ CREATE VIRTUAL TABLE x1 USING fts5(c,
+ tokenize=\"unicode61 categories ' \t'\");
+"
+
+do_catchsql_test 2.1 "
+ CREATE VIRTUAL TABLE x2 USING fts5(c,
+ tokenize=\"unicode61 categories 'N*\t\tMYZ'\");
+" {1 {error in tokenizer constructor}}
+
+do_catchsql_test 2.2 "
+ CREATE VIRTUAL TABLE x2 USING fts5(c,
+ tokenize=\"unicode61 categories 'N*\t\tXYZ'\");
+" {1 {error in tokenizer constructor}}
+
finish_test
diff --git a/ext/fts5/test/fts5contentless.test b/ext/fts5/test/fts5contentless.test
index eb6b928ab8..991e9888fc 100644
--- a/ext/fts5/test/fts5contentless.test
+++ b/ext/fts5/test/fts5contentless.test
@@ -267,4 +267,24 @@ do_execsql_test 8.2 {
SELECT rowid FROM ft('four');
} {}
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 9.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=0);
+ INSERT INTO ft VALUES('hello world');
+ INSERT INTO ft VALUES('one two three');
+}
+
+do_catchsql_test 9.1 {
+ INSERT INTO ft(ft, rowid, x) VALUES('delete', 1, 'hello world');
+} {0 {}}
+
+do_catchsql_test 9.2 {
+ CREATE VIRTUAL TABLE ft2 USING fts5(x, content='', contentless_delete=2);
+} {1 {malformed contentless_delete=... directive}}
+
+do_catchsql_test 9.3 {
+ CREATE VIRTUAL TABLE ft2 USING fts5(x, content='', contentless_delete=11);
+} {1 {malformed contentless_delete=... directive}}
+
finish_test
diff --git a/ext/fts5/test/fts5corrupt.test b/ext/fts5/test/fts5corrupt.test
index ae07383b23..0abd8b86de 100644
--- a/ext/fts5/test/fts5corrupt.test
+++ b/ext/fts5/test/fts5corrupt.test
@@ -101,4 +101,25 @@ do_catchsql_test 3.1 {
SELECT * FROM t3 WHERE t3 MATCH 'o';
} {1 {fts5: missing row 3 from content table 'main'.'t3_content'}}
+#--------------------------------------------------------------------
+#
+reset_db
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE t2 USING fts5(x);
+ INSERT INTO t2 VALUES('one two three');
+ INSERT INTO t2 VALUES('four five six');
+ INSERT INTO t2 VALUES('seven eight nine');
+ INSERT INTO t2 VALUES('ten eleven twelve');
+}
+do_execsql_test 4.1 {
+ SELECT hex(block) FROM t2_data WHERE id=1;
+} {040C}
+do_execsql_test 4.2 {
+ UPDATE t2_data SET block = X'0402' WHERE id=1
+}
+breakpoint
+do_catchsql_test 4.3 {
+ DELETE FROM t2 WHERE rowid=3
+} {1 {database disk image is malformed}}
+
finish_test
diff --git a/ext/fts5/test/fts5corrupt3.test b/ext/fts5/test/fts5corrupt3.test
index cfe1438eda..3e8b0377cc 100644
--- a/ext/fts5/test/fts5corrupt3.test
+++ b/ext/fts5/test/fts5corrupt3.test
@@ -680,11 +680,11 @@ do_test 12.0 {
do_catchsql_test 11.1 {
SELECT * FROM t1 WHERE t1 MATCH 'abandon';
-} {1 {vtable constructor failed: t1}}
+} {1 {database disk image is malformed}}
do_catchsql_test 11.2 {
INSERT INTO t1(t1, rank) VALUES('merge', 500);
-} {1 {vtable constructor failed: t1}}
+} {1 {database disk image is malformed}}
#-------------------------------------------------------------------------
#
@@ -1040,7 +1040,7 @@ do_test 16.0 {
do_catchsql_test 16.1 {
INSERT INTO t1(t1) VALUES('integrity-check');
-} {1 {vtable constructor failed: t1}}
+} {1 {database disk image is malformed}}
#--------------------------------------------------------------------------
reset_db
@@ -1126,7 +1126,7 @@ do_test 17.0 {
do_catchsql_test 17.1 {
SELECT * FROM t1 WHERE t1 MATCH 'abandon';
-} {1 {vtable constructor failed: t1}}
+} {1 {database disk image is malformed}}
#--------------------------------------------------------------------------
reset_db
@@ -1630,7 +1630,7 @@ do_test 20.0 {
do_catchsql_test 20.1 {
SELECT * FROM t1 WHERE t1 MATCH 'abandon';
-} {1 {vtable constructor failed: t1}}
+} {1 {database disk image is malformed}}
#-------------------------------------------------------------------------
reset_db
@@ -2100,7 +2100,7 @@ do_test 22.0 {
do_catchsql_test 22.1 {
INSERT INTO t1(t1) VALUES('optimize');
-} {1 {vtable constructor failed: t1}}
+} {1 {database disk image is malformed}}
#--------------------------------------------------------------------------
reset_db
@@ -3700,7 +3700,7 @@ do_catchsql_test 32.1 {
highlight(t1, 2, '[', ']')
FROM t1('g + h')
WHERE rank MATCH 'bm25(1.0, 1.0)' ORDER BY rank;
-} {1 {vtable constructor failed: t1}}
+} {1 {database disk image is malformed}}
do_catchsql_test 32.2 {
SELECT * FROM t3;
@@ -15524,6 +15524,160 @@ do_catchsql_test 80.1 {
SELECT snippet(rowid, -1, '.', '..', '[', '(]'),snippet(rowid, -1, '.', '.', '', '(]'), highlight(t1, 29, 1 , '') FROM t1('g+ h') WHERE rank MATCH 'bm25(1.0, 10)' ORDER BY NOT (SELECT 1 FROM t1('g+ æ') WHERE rank MATCH 'bm25(1.0, 10)' ORDER BY rank);
} {1 {database disk image is malformed}}
+#-------------------------------------------------------------------------
+reset_db
+do_test 81.0 {
+ sqlite3 db {}
+ db deserialize [decode_hexdb {
+.open --hexdb
+| size 40960 pagesize 4096 filename crash-44e8035a976422.db
+| page 1 offset 0
+| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
+| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 0a .....@ ........
+| 32: 00 00 00 00 00 00 00 00 00 00 00 0d 00 00 00 04 ................
+| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................
+| 96: 00 00 00 00 0d 00 00 00 0d 0b 6e 00 0f a3 0f 4c ..........n....L
+| 112: 0e e1 0e 81 0e 24 0d cc 0d 72 0d 1b 0c b0 0c 50 .....$...r.....P
+| 128: 0b f8 0b b3 0b 6e 00 00 00 00 00 00 00 00 00 00 .....n..........
+| 2912: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 43 0d ..............C.
+| 2928: 06 17 11 11 08 75 74 61 62 6c 65 74 34 74 34 43 .....utablet4t4C
+| 2944: 52 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 REATE VIRTUAL TA
+| 2960: 42 4c 45 20 74 34 20 55 53 49 4e 47 20 66 74 73 BLE t4 USING fts
+| 2976: 35 76 6f 63 61 62 28 27 74 32 27 2c 20 27 72 6f 5vocab('t2', 'ro
+| 2992: 77 27 29 43 0c 06 17 11 11 08 75 74 61 62 6c 65 w')C......utable
+| 3008: 74 33 74 33 43 52 45 41 54 45 20 56 49 52 54 55 t3t3CREATE VIRTU
+| 3024: 41 4c 20 54 41 42 4c 45 20 74 33 20 55 53 49 4e AL TABLE t3 USIN
+| 3040: 47 20 66 74 73 35 76 6f 63 61 62 28 27 74 31 27 G fts5vocab('t1'
+| 3056: 2c 20 27 72 6f 77 27 29 56 0b 06 17 1f 1f 01 7d , 'row')V.......
+| 3072: 74 61 62 6c 65 74 32 5f 63 6f 6e 66 69 67 74 32 tablet2_configt2
+| 3088: 5f 63 6f 6e 66 69 67 0a 43 52 45 41 54 45 20 54 _config.CREATE T
+| 3104: 41 42 4c 45 20 27 74 32 5f 63 6f 6e 66 69 67 27 ABLE 't2_config'
+| 3120: 28 6b 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 (k PRIMARY KEY,
+| 3136: 76 29 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 v) WITHOUT ROWID
+| 3152: 5e 0a 07 17 21 21 01 81 07 74 61 62 6c 65 74 32 ^...!!...tablet2
+| 3168: 5f 63 6f 6e 74 65 6e 74 74 32 5f 63 6f 6e 74 65 _contentt2_conte
+| 3184: 6e 74 09 43 52 45 41 54 45 20 54 41 42 4c 45 20 nt.CREATE TABLE
+| 3200: 27 74 32 5f 63 6f 6e 74 65 6e 74 27 28 69 64 20 't2_content'(id
+| 3216: 49 4e 54 45 47 45 52 20 50 52 49 4d 41 52 59 20 INTEGER PRIMARY
+| 3232: 4b 45 59 2c 20 63 30 2c 20 63 31 2c 20 63 32 29 KEY, c0, c1, c2)
+| 3248: 69 09 07 17 19 19 01 81 2d 74 61 62 6c 65 74 32 i.......-tablet2
+| 3264: 5f 69 64 78 74 32 5f 69 64 78 08 43 52 45 41 54 _idxt2_idx.CREAT
+| 3280: 45 20 54 41 42 4c 45 20 27 74 32 5f 69 64 78 27 E TABLE 't2_idx'
+| 3296: 28 73 65 67 69 64 2c 20 74 65 72 6d 2c 20 70 67 (segid, term, pg
+| 3312: 6e 6f 2c 20 50 52 49 4d 41 52 59 20 4b 45 59 28 no, PRIMARY KEY(
+| 3328: 73 65 67 69 64 2c 20 74 65 72 6d 29 29 20 57 49 segid, term)) WI
+| 3344: 54 48 4f 55 54 20 52 4f 57 49 44 55 08 07 17 1b THOUT ROWIDU....
+| 3360: 1b 01 81 01 74 61 62 6c 65 74 32 5f 64 61 74 61 ....tablet2_data
+| 3376: 74 32 5f 64 61 74 61 07 43 52 45 41 54 45 20 54 t2_data.CREATE T
+| 3392: 41 42 4c 45 20 27 74 32 5f 64 61 74 61 27 28 69 ABLE 't2_data'(i
+| 3408: 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d 41 52 d INTEGER PRIMAR
+| 3424: 59 20 4b 45 59 2c 20 62 6c 6f 63 6b 20 42 4c 4f Y KEY, block BLO
+| 3440: 42 29 58 07 07 17 11 11 08 81 1d 74 61 62 6c 65 B)X........table
+| 3456: 74 32 74 32 43 52 45 41 54 45 20 56 49 52 54 55 t2t2CREATE VIRTU
+| 3472: 41 4c 20 54 41 42 4c 45 20 74 32 20 55 53 49 4e AL TABLE t2 USIN
+| 3488: 47 20 66 74 73 35 28 27 61 27 2c 5b 62 5d 2c 22 G fts5('a',[b],.
+| 3504: 63 22 2c 64 65 74 61 69 6c 3d 6e 6f 6e 65 2c 63 c.,detail=none,c
+| 3520: 6f 6c 75 6d 6e 73 69 7a 65 3d 30 29 56 06 06 17 olumnsize=0)V...
+| 3536: 1f 1f 01 7d 74 61 62 6c 65 74 31 5f 63 6f 6e 66 ....tablet1_conf
+| 3552: 69 67 74 31 5f 63 6f 6e 66 69 67 06 43 52 45 41 igt1_config.CREA
+| 3568: 54 45 20 54 41 42 4c 45 20 27 74 31 5f 63 6f 6e TE TABLE 't1_con
+| 3584: 66 69 67 27 28 6b 20 50 52 49 4d 41 52 59 20 4b fig'(k PRIMARY K
+| 3600: 45 59 2c 20 76 29 20 57 49 54 48 4f 55 54 20 52 EY, v) WITHOUT R
+| 3616: 4f 57 49 44 5b 05 07 17 21 21 01 81 01 74 61 62 OWID[...!!...tab
+| 3632: 6c 65 74 31 5f 64 6f 63 73 69 7a 65 74 31 5f 64 let1_docsizet1_d
+| 3648: 6f 63 73 69 7a 65 05 43 52 45 41 54 45 20 54 41 ocsize.CREATE TA
+| 3664: 42 4c 45 20 27 74 31 5f 64 6f 63 73 69 7a 65 27 BLE 't1_docsize'
+| 3680: 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d (id INTEGER PRIM
+| 3696: 41 52 59 20 4b 45 59 2c 20 73 7a 20 42 4c 4f 42 ARY KEY, sz BLOB
+| 3712: 29 5e 04 07 17 21 21 01 81 07 74 61 62 6c 65 74 )^...!!...tablet
+| 3728: 31 5f 63 6f 6e 74 65 6e 74 74 31 5f 63 6f 6e 74 1_contentt1_cont
+| 3744: 65 6e 74 04 43 52 45 41 54 45 20 54 41 42 4c 45 ent.CREATE TABLE
+| 3760: 20 27 74 31 5f 63 6f 6e 74 65 6e 74 27 28 69 64 't1_content'(id
+| 3776: 20 49 4e 54 45 47 45 52 20 50 52 49 4d 41 52 59 INTEGER PRIMARY
+| 3792: 20 4b 45 59 2c 20 63 30 2c 20 63 31 2c 20 63 32 KEY, c0, c1, c2
+| 3808: 29 69 03 07 17 19 19 01 81 2d 74 61 62 6c 65 74 )i.......-tablet
+| 3824: 31 5f 69 64 78 74 31 5f 69 64 78 03 43 52 45 41 1_idxt1_idx.CREA
+| 3840: 54 45 20 54 41 42 4c 45 20 27 74 31 5f 69 64 78 TE TABLE 't1_idx
+| 3856: 27 28 73 65 67 69 64 2c 20 74 65 72 6d 2c 20 70 '(segid, term, p
+| 3872: 67 6e 6f 2c 20 50 52 49 4d 41 52 59 20 4b 45 59 gno, PRIMARY KEY
+| 3888: 28 73 65 67 69 64 2c 20 74 65 72 6d 29 29 20 57 (segid, term)) W
+| 3904: 49 54 48 4f 55 54 20 52 4f 57 49 44 55 02 07 17 ITHOUT ROWIDU...
+| 3920: 1b 1b 01 81 01 74 61 62 6c 65 74 31 5f 64 61 74 .....tablet1_dat
+| 3936: 61 74 31 5f 64 61 74 61 02 43 52 45 41 54 45 20 at1_data.CREATE
+| 3952: 54 41 42 4c 45 20 27 74 31 5f 64 61 74 61 27 28 TABLE 't1_data'(
+| 3968: 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d 41 id INTEGER PRIMA
+| 3984: 52 59 20 4b 45 59 2c 20 62 6c 6f 63 6b 20 42 4c RY KEY, block BL
+| 4000: 4f 42 29 5b 01 07 17 11 11 08 81 23 74 61 62 6c OB)[.......#tabl
+| 4016: 65 74 31 74 31 43 52 45 41 54 45 20 56 49 52 54 et1t1CREATE VIRT
+| 4032: 55 41 4c 20 54 41 42 4c 45 20 74 31 20 55 53 49 UAL TABLE t1 USI
+| 4048: 4e 47 20 66 74 73 35 28 61 2c 62 20 75 6e 69 6e NG fts5(a,b unin
+| 4064: 64 65 78 65 64 2c 63 2c 74 6f 6b 65 6e 69 7a 65 dexed,c,tokenize
+| 4080: 3d 22 70 6f 72 74 65 72 20 61 73 63 69 69 22 29 =.porter ascii.)
+| page 2 offset 4096
+| 0: 0d 0f 68 00 05 0f 13 00 0f e6 0f 13 0f a8 0f 7c ..h............|
+| 16: 0f 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .*..............
+| 3856: 00 00 00 15 0a 03 00 30 00 00 00 00 01 03 03 00 .......0........
+| 3872: 03 01 01 01 02 01 01 03 01 01 37 8c 80 80 80 80 ..........7.....
+| 3888: 01 03 00 74 00 00 00 2e 02 30 61 03 02 02 01 01 ...t.....0a.....
+| 3904: 62 03 02 03 01 01 63 03 02 04 01 01 67 03 06 01 b.....c.....g...
+| 3920: 02 02 01 01 68 03 06 01 02 03 01 01 69 03 06 01 ....h.......i...
+| 3936: 02 04 04 06 06 06 08 08 0f ef 00 14 2a 00 00 00 ............*...
+| 3952: 00 01 02 02 00 02 01 01 01 02 01 01 25 88 80 80 ............%...
+| 3968: 80 80 01 03 00 50 00 00 00 1f 02 30 67 02 08 02 .....P.....0g...
+| 3984: 01 02 02 01 01 68 02 08 03 8d 02 03 01 01 6a 42 .....h........jB
+| 4000: 08 04 01 02 04 04 09 09 37 84 80 80 80 80 01 03 ........7.......
+| 4016: 00 74 00 00 00 2e 02 30 61 01 12 02 01 01 62 01 .t.....0a.....b.
+| 4032: 02 03 01 01 63 01 02 04 01 01 67 01 06 01 02 02 ....c.....g.....
+| 4048: 01 01 68 01 05 01 02 03 01 01 69 01 06 01 02 04 ..h.......i.....
+| 4064: 04 06 06 06 08 08 07 01 03 00 14 03 09 00 09 00 ................
+| 4080: 00 00 11 24 00 00 00 00 01 01 01 00 01 01 01 01 ...$............
+| page 3 offset 8192
+| 0: 0a 00 00 00 03 0f ec 00 0f fa 0f f3 0f ec 00 00 ................
+| 4064: 00 00 00 00 00 00 00 00 00 00 00 00 06 04 01 0c ................
+| 4080: 01 03 02 06 04 01 0c 01 02 02 05 04 09 0c 01 02 ................
+| page 4 offset 12288
+| 0: 0d 00 00 00 03 0f be 00 0f ea 00 00 00 00 00 00 ................
+| 4016: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 14 03 ................
+| 4032: 05 00 17 17 17 61 20 62 20 63 67 20 68 20 69 67 .....a b cg h ig
+| 4048: 20 68 20 69 14 02 05 00 17 17 17 67 20 68 20 69 h i.......g h i
+| 4064: 61 20 62 20 63 67 20 68 20 69 14 01 05 00 17 17 a b cg h i......
+| 4080: 17 61 20 62 20 63 64 20 65 20 66 67 20 68 20 69 .a b cd e fg h i
+| page 5 offset 16384
+| 0: 0d 00 00 00 03 0f e8 00 0f f8 0f f0 0f e8 00 00 ................
+| 4064: 00 00 00 00 00 00 00 00 06 03 03 00 12 03 00 03 ................
+| 4080: 06 02 03 00 12 03 00 03 06 01 03 00 12 03 00 03 ................
+| page 6 offset 20480
+| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................
+| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version.
+| page 7 offset 24576
+| 0: 0d 00 00 00 03 0f 9e 00 0f e6 0f ef 0f 9e 00 00 ................
+| 3984: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 41 84 ..............A.
+| 4000: 80 80 80 80 01 04 00 81 06 00 00 00 34 02 30 61 ............4.0a
+| 4016: 01 01 01 01 01 62 01 01 01 01 01 63 01 01 01 01 .....b.....c....
+| 4032: e6 64 01 01 01 65 01 01 01 66 01 01 01 67 01 01 .d...e...f...g..
+| 4048: 01 01 01 68 01 01 01 01 01 69 01 01 01 04 06 06 ...h.....i......
+| 4064: 06 04 04 04 06 06 07 01 03 00 14 03 09 09 09 0f ................
+| 4080: 0a 03 00 24 00 00 00 00 01 01 01 00 01 01 01 01 ...$............
+| page 8 offset 28672
+| 0: 0a 00 00 00 01 0f fa 00 0f fa 00 00 00 00 00 00 ................
+| 4080: 00 00 00 00 00 00 00 00 00 00 05 04 09 0c 01 02 ................
+| page 9 offset 32768
+| 0: 0d 00 00 00 03 0f be 00 0f ea 0f d4 0f be 00 00 ................
+| 4016: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 14 03 ................
+| 4032: 05 00 17 17 17 61 20 62 20 63 67 20 68 20 69 67 .....a b cg h ig
+| 4048: 20 68 21 69 14 02 05 00 17 17 17 67 20 68 20 69 h!i.......g h i
+| 4064: 61 20 62 20 63 67 20 68 20 69 14 01 05 00 17 17 a b cg h i......
+| 4080: 17 61 20 62 20 63 64 20 65 20 66 67 20 68 20 69 .a b cd e fg h i
+| page 10 offset 36864
+| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................
+| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version.
+| end crash-44e8035a976422.db
+}]} {}
+
+do_catchsql_test 81.2 {
+ UPDATE t1 SET b=zeroblob(299);
+} {1 {database disk image is malformed}}
+
sqlite3_fts5_may_be_corrupt 0
finish_test
diff --git a/ext/fts5/test/fts5corrupt8.test b/ext/fts5/test/fts5corrupt8.test
new file mode 100644
index 0000000000..d642920e45
--- /dev/null
+++ b/ext/fts5/test/fts5corrupt8.test
@@ -0,0 +1,94 @@
+# 2024 Aug 28
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5corrupt8
+
+# If SQLITE_ENABLE_FTS5 is not defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+}
+
+do_execsql_test 1.1 {
+ UPDATE t1_data SET block='hello world' WHERE id=10
+}
+
+db close
+sqlite3 db test.db
+
+do_catchsql_test 1.2 {
+ SELECT * FROM t1
+} {1 {database disk image is malformed}}
+do_catchsql_test 1.3 {
+ DROP TABLE t1
+} {0 {}}
+do_execsql_test 1.4 {
+ SELECT * FROM sqlite_schema
+}
+
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+}
+do_execsql_test 2.1 {
+ UPDATE t1_config SET v=555 WHERE k='version'
+}
+db close
+sqlite3 db test.db
+do_catchsql_test 2.2 {
+ SELECT * FROM t1
+} {1 {invalid fts5 file format (found 555, expected 4 or 5) - run 'rebuild'}}
+do_catchsql_test 2.3 {
+ DROP TABLE t1
+} {1 {invalid fts5 file format (found 555, expected 4 or 5) - run 'rebuild'}}
+do_test 2.4 {
+ sqlite3_fts5_drop_corrupt_table db main t1
+} {}
+do_execsql_test 2.5 {
+ SELECT * FROM sqlite_schema
+}
+
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+}
+do_execsql_test 3.1 {
+ DELETE FROM t1_config;
+}
+db close
+sqlite3 db test.db
+do_catchsql_test 3.2 {
+ SELECT * FROM t1
+} {1 {invalid fts5 file format (found 0, expected 4 or 5) - run 'rebuild'}}
+do_catchsql_test 3.3 {
+ DROP TABLE t1
+} {1 {invalid fts5 file format (found 0, expected 4 or 5) - run 'rebuild'}}
+
+
+do_test 3.4 {
+ sqlite3_db_config db DEFENSIVE 1
+} {1}
+do_test 3.5 {
+ sqlite3_fts5_drop_corrupt_table db main t1
+} {}
+do_test 3.6 {
+ sqlite3_db_config db DEFENSIVE -1
+} {1}
+do_execsql_test 3.7 {
+ SELECT * FROM sqlite_schema
+}
+
+finish_test
+
diff --git a/ext/fts5/test/fts5expr.test b/ext/fts5/test/fts5expr.test
index e3938beb0f..49be61d9c4 100644
--- a/ext/fts5/test/fts5expr.test
+++ b/ext/fts5/test/fts5expr.test
@@ -44,5 +44,9 @@ for {set ii 0} {$ii < 300} {incr ii} {
} $res
}
+do_execsql_test 1.2 {
+ SELECT rowid FROM x1 WHERE a MATCH '"..."'
+} {}
+
finish_test
diff --git a/ext/fts5/test/fts5fault4.test b/ext/fts5/test/fts5fault4.test
index 1d0d5c9b7c..2b4f6c4d2a 100644
--- a/ext/fts5/test/fts5fault4.test
+++ b/ext/fts5/test/fts5fault4.test
@@ -90,7 +90,7 @@ set ::res [db eval {SELECT rowid, x1 FROM x1 WHERE x1 MATCH '*reads'}]
do_faultsim_test 4 -faults oom-* -body {
db eval {SELECT rowid, x, x1 FROM x1 WHERE x1 MATCH '*reads'}
} -test {
- faultsim_test_result {0 {0 {} 3}}
+ faultsim_test_result {0 {0 {} 2}}
}
#-------------------------------------------------------------------------
diff --git a/ext/fts5/test/fts5faultI.test b/ext/fts5/test/fts5faultI.test
new file mode 100644
index 0000000000..e74162ee35
--- /dev/null
+++ b/ext/fts5/test/fts5faultI.test
@@ -0,0 +1,282 @@
+# 2010 June 15
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+source $testdir/malloc_common.tcl
+set testprefix fts5faultI
+
+# If SQLITE_ENABLE_FTS5 is not defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+set ::testprefix fts5faultI
+
+do_execsql_test 1.0 {
+ PRAGMA encoding = utf16;
+ CREATE VIRTUAL TABLE t1 USING fts5(x, locale=1);
+ INSERT INTO t1 VALUES('origintext unicode61 ascii porter trigram');
+}
+
+faultsim_save_and_close
+faultsim_restore_and_reopen
+
+do_faultsim_test 1 -faults oom* -prep {
+} -body {
+ execsql {
+ SELECT rowid FROM t1(fts5_locale('en_US', 'origintext'));
+ }
+} -test {
+ faultsim_test_result {0 1}
+}
+
+do_faultsim_test 2 -faults oom* -prep {
+ faultsim_restore_and_reopen
+ execsql {
+ SELECT * FROM t1('ascii');
+ }
+} -body {
+ execsql {
+ UPDATE t1 SET rowid=rowid+1;
+ }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+fts5_aux_test_functions db
+do_faultsim_test 3 -faults oom* -prep {
+} -body {
+ execsql {
+ SELECT fts5_columnlocale(t1, 0) FROM t1('unicode*');
+ }
+} -test {
+ faultsim_test_result {0 {{}}} {1 SQLITE_NOMEM}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE w1 USING fts5(a);
+}
+faultsim_save_and_close
+
+do_faultsim_test 4 -faults oom* -prep {
+ faultsim_restore_and_reopen
+ execsql {
+ BEGIN;
+ INSERT INTO w1 VALUES('token token token');
+ }
+} -body {
+ execsql {
+ INSERT INTO w1(w1, rank) VALUES('rank', 'bm25()');
+ }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+do_faultsim_test 5 -faults oom* -prep {
+ faultsim_restore_and_reopen
+ execsql {
+ BEGIN;
+ INSERT INTO w1 VALUES('one');
+ SAVEPOINT one;
+ INSERT INTO w1 VALUES('two');
+ ROLLBACK TO one;
+ }
+
+} -body {
+ execsql {
+ INSERT INTO w1 VALUES('string');
+ }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 5.0 {
+ CREATE VIRTUAL TABLE w1 USING fts5(a);
+ INSERT INTO w1 VALUES('one two three');
+}
+fts5_aux_test_functions db
+
+do_faultsim_test 5 -faults oom* -prep {
+} -body {
+ execsql {
+ SELECT fts5_test_insttoken(w1, 0, 0) FROM w1('two');
+ }
+} -test {
+ faultsim_test_result {0 two} {1 SQLITE_NOMEM}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 6.0 {
+ CREATE VIRTUAL TABLE w1 USING fts5(a);
+ INSERT INTO w1 VALUES('one two three');
+}
+fts5_aux_test_functions db
+faultsim_save_and_close
+
+do_faultsim_test 6 -faults oom* -prep {
+ faultsim_restore_and_reopen
+ db eval {
+ BEGIN;
+ INSERT INTO w1 VALUES('four five six');
+ SAVEPOINT abc;
+ INSERT INTO w1 VALUES('seven eight nine');
+ SAVEPOINT def;
+ INSERT INTO w1 VALUES('ten eleven twelve');
+ }
+} -body {
+ execsql {
+ RELEASE abc;
+ }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 7.0 {
+ CREATE VIRTUAL TABLE w1 USING fts5(a);
+ INSERT INTO w1 VALUES('one two three');
+ INSERT INTO w1 VALUES('three two one');
+ DELETE FROM w1_content WHERE rowid=1;
+}
+
+faultsim_save_and_close
+
+do_faultsim_test 7 -faults oom* -prep {
+ faultsim_restore_and_reopen
+ db eval { SELECT * FROM w1 }
+} -body {
+ execsql {
+ PRAGMA integrity_check;
+ }
+} -test {
+}
+
+#-------------------------------------------------------------------------
+reset_db
+fts5_tclnum_register db
+fts5_aux_test_functions db
+
+do_execsql_test 8.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(
+ x, tokenize = "tclnum query", detail=columns
+ );
+ INSERT INTO ft VALUES('one two three i ii iii');
+ INSERT INTO ft VALUES('four five six iv v vi');
+ INSERT INTO ft VALUES('eight nine ten viii ix x');
+} {}
+
+do_faultsim_test 8.1 -faults oom* -prep {
+} -body {
+ execsql {
+ SELECT fts5_test_collist (ft) FROM ft('one two');
+ }
+} -test {
+ faultsim_test_result {0 {{0.0 1.0}}} {1 {SQL logic error}} {1 SQLITE_NOMEM}
+}
+
+do_faultsim_test 8.2 -faults oom* -prep {
+} -body {
+ execsql {
+ SELECT rowid FROM ft('one two') ORDER BY rank;
+ }
+} -test {
+ faultsim_test_result {0 1} {1 {SQL logic error}} {1 SQLITE_NOMEM}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 9.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x);
+ INSERT INTO ft VALUES('one two three i ii iii');
+ INSERT INTO ft VALUES('four five six iv v vi');
+ INSERT INTO ft VALUES('eight nine ten viii ix x');
+} {}
+
+faultsim_save_and_close
+
+do_faultsim_test 9.1 -faults oom* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql {
+ UPDATE ft SET rowid=4 WHERE rowid=1
+ }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+do_faultsim_test 9.2 -faults oom* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql {
+ SELECT rowid FROM ft WHERE x MATCH 'one AND two AND three'
+ }
+} -test {
+ faultsim_test_result {0 1}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 10.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, locale=1);
+ INSERT INTO ft VALUES(fts5_locale('hello', 'one two three i ii iii'));
+ INSERT INTO ft VALUES('four five six iv v vi');
+ INSERT INTO ft VALUES('eight nine ten viii ix x');
+} {}
+
+do_execsql_test 10.1 {
+ SELECT fts5_get_locale(ft, 0) FROM ft WHERE x MATCH 'one AND two AND three'
+} {hello}
+
+faultsim_save_and_close
+do_faultsim_test 10 -faults oom* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql {
+ SELECT fts5_get_locale(ft, 0) FROM ft WHERE x MATCH 'one AND two AND three'
+ }
+} -test {
+ faultsim_test_result {0 hello}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 11.0 {
+ CREATE VIRTUAL TABLE f1 USING fts5(content);
+ CREATE TABLE g1(id, content);
+ INSERT INTO g1 VALUES(30000, 'a b c');
+ INSERT INTO g1 VALUES(40000, 'd e f');
+}
+
+faultsim_save_and_close
+
+do_faultsim_test 11 -faults oom* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql {
+ INSERT INTO f1(rowid, content) SELECT id, content FROM g1;
+ }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+finish_test
+
diff --git a/ext/fts5/test/fts5locale.test b/ext/fts5/test/fts5locale.test
new file mode 100644
index 0000000000..684dcecd80
--- /dev/null
+++ b/ext/fts5/test/fts5locale.test
@@ -0,0 +1,667 @@
+# 2014 Dec 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.
+#
+#***********************************************************************
+#
+# Tests focusing on the built-in fts5 tokenizers.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5locale
+
+# If SQLITE_ENABLE_FTS5 is not defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+proc transform_token {locale token} {
+ switch -- $locale {
+ reverse {
+ set ret ""
+ foreach c [split $token ""] {
+ set ret "$c$ret"
+ }
+ set token $ret
+ }
+
+ default {
+ # no-op
+ }
+ }
+
+ set token
+}
+
+proc tcl_create {args} { return "tcl_tokenize" }
+proc tcl_tokenize {tflags text} {
+ set iToken 1
+ set bSkip 0
+ if {[sqlite3_fts5_locale]=="second"} { set bSkip 1 }
+ foreach {w iStart iEnd} [fts5_tokenize_split $text] {
+ incr iToken
+ if {(($iToken) % ($bSkip + 1))} continue
+
+ set w [transform_token [sqlite3_fts5_locale] $w]
+ sqlite3_fts5_token $w $iStart $iEnd
+ }
+}
+
+#-------------------------------------------------------------------------
+# Check that queries can have a locale attached to them.
+#
+reset_db
+sqlite3_fts5_create_tokenizer -v2 db tcl tcl_create
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a, tokenize=tcl);
+ INSERT INTO t1 VALUES('abc');
+ INSERT INTO t1 VALUES('cba');
+} {}
+
+do_execsql_test 1.1 {
+ SELECT rowid, a FROM t1( fts5_locale('en_US', 'abc') );
+} {1 abc}
+
+do_execsql_test 1.2 {
+ SELECT rowid, a FROM t1( fts5_locale('reverse', 'abc') );
+} {2 cba}
+
+#-------------------------------------------------------------------------
+# Test that the locale= option exists and seems to accept values. And
+# that fts5_locale() values may only be inserted into an internal-content
+# table if the locale=1 option was specified.
+#
+reset_db
+sqlite3_fts5_create_tokenizer -v2 db tcl tcl_create
+
+do_execsql_test 2.1 {
+ CREATE VIRTUAL TABLE b1 USING fts5(x, y, locale=1, tokenize=tcl);
+ CREATE VIRTUAL TABLE b2 USING fts5(x, y, locale=0, tokenize=tcl);
+
+ CREATE VIRTUAL TABLE ttt USING fts5vocab('b1', instance);
+}
+
+do_catchsql_test 2.2.1 {
+ CREATE VIRTUAL TABLE b3 USING fts5(x, y, locale=2);
+} {1 {malformed locale=... directive}}
+do_catchsql_test 2.2.2 {
+ CREATE VIRTUAL TABLE b3 USING fts5(x, y, locale=111);
+} {1 {malformed locale=... directive}}
+
+do_catchsql_test 2.3 {
+ INSERT INTO b1(b1, rank) VALUES('locale', 0);
+} {1 {SQL logic error}}
+
+do_execsql_test 2.4 {
+ INSERT INTO b1 VALUES('abc', 'one two three');
+ INSERT INTO b1 VALUES('def', fts5_locale('reverse', 'four five six'));
+}
+
+do_execsql_test 2.5 {
+ INSERT INTO b2 VALUES('abc', 'one two three');
+}
+
+do_catchsql_test 2.6 {
+ INSERT INTO b2 VALUES('def', fts5_locale('reverse', 'four five six'));
+} {1 {fts5_locale() requires locale=1}}
+
+do_execsql_test 2.7 { SELECT rowid FROM b1('one') } {1}
+do_execsql_test 2.8 { SELECT rowid FROM b1('four') } {}
+do_execsql_test 2.9 { SELECT rowid FROM b1('ruof') } 2
+do_execsql_test 2.10 { SELECT rowid FROM b1(fts5_locale('reverse', 'five'))} 2
+
+do_execsql_test 2.11 {
+ SELECT x, quote(y) FROM b1
+} {
+ abc {'one two three'}
+ def {'four five six'}
+}
+
+do_execsql_test 2.12 { SELECT quote(y) FROM b1('ruof') } {
+ {'four five six'}
+}
+
+do_execsql_test 2.13 {
+ INSERT INTO b1(b1) VALUES('integrity-check');
+}
+do_execsql_test 2.14 {
+ INSERT INTO b1(b1) VALUES('rebuild');
+}
+do_execsql_test 2.15 {
+ INSERT INTO b1(b1) VALUES('integrity-check');
+}
+
+do_execsql_test 2.16 {
+ DELETE FROM b1 WHERE rowid=2
+}
+do_execsql_test 2.17 {
+ INSERT INTO b1(b1) VALUES('integrity-check');
+}
+
+do_execsql_test 2.18 {
+ INSERT INTO b1(rowid, x, y) VALUES(
+ test_setsubtype(45, 76), 'abc def', 'def abc'
+ );
+ INSERT INTO b1(b1) VALUES('integrity-check');
+}
+
+#-------------------------------------------------------------------------
+# Test the 'delete' command with contentless tables.
+#
+reset_db
+sqlite3_fts5_create_tokenizer -v2 db tcl tcl_create
+
+do_execsql_test 3.1 {
+ CREATE VIRTUAL TABLE c1 USING fts5(x, content=, tokenize=tcl, locale=1);
+ CREATE VIRTUAL TABLE c2 USING fts5vocab('c1', instance);
+
+ INSERT INTO c1 VALUES('hello world');
+ INSERT INTO c1 VALUES( fts5_locale('reverse', 'one two three') );
+}
+
+do_execsql_test 3.2 {
+ SELECT DISTINCT term FROM c2 ORDER BY 1
+} {
+ eerht eno hello owt world
+}
+
+do_execsql_test 3.3 {
+ INSERT INTO c1(c1, rowid, x)
+ VALUES('delete', 2, fts5_locale('reverse', 'one two three') );
+}
+
+do_execsql_test 3.4 {
+ SELECT DISTINCT term FROM c2 ORDER BY 1
+} {
+ hello world
+}
+
+#-------------------------------------------------------------------------
+# Test that an UPDATE that updates a subset of the columns does not
+# magically discard the locale from those columns not updated.
+#
+reset_db
+sqlite3_fts5_create_tokenizer -v2 db tcl tcl_create
+
+do_execsql_test 4.1 {
+ CREATE VIRTUAL TABLE d1 USING fts5(x, y, locale=1, tokenize=tcl);
+ CREATE VIRTUAL TABLE d2 USING fts5vocab('d1', instance);
+
+ INSERT INTO d1(rowid, x, y) VALUES(1, 'abc', 'def');
+ INSERT INTO d1(rowid, x, y) VALUES(2, 'ghi', fts5_locale('reverse', 'hello'));
+}
+
+do_execsql_test 4.2 {
+ SELECT DISTINCT term FROM d2 ORDER BY 1
+} {
+ abc def ghi olleh
+}
+
+do_execsql_test 4.3 {
+ UPDATE d1 SET x='jkl' WHERE rowid=2;
+}
+
+do_execsql_test 4.4 {
+ SELECT DISTINCT term FROM d2 ORDER BY 1
+} {
+ abc def jkl olleh
+}
+
+do_execsql_test 4.5 {
+ SELECT rowid, * FROM d1
+} {
+ 1 abc def
+ 2 jkl hello
+}
+
+do_execsql_test 4.6 {
+ UPDATE d1 SET rowid=4 WHERE rowid=2
+}
+
+do_execsql_test 4.7 {
+ SELECT rowid, * FROM d1
+} {
+ 1 abc def
+ 4 jkl hello
+}
+
+fts5_aux_test_functions db
+
+do_execsql_test 4.8.1 {
+ SELECT fts5_test_columntext(d1) FROM d1('jkl')
+} {{jkl hello}}
+do_execsql_test 4.8.2 {
+ SELECT fts5_test_columntext(d1) FROM d1(fts5_locale('reverse', 'hello'))
+} {{jkl hello}}
+
+do_execsql_test 4.9 {
+ SELECT fts5_test_columnlocale(d1) FROM d1(fts5_locale('reverse', 'hello'))
+} {{{} reverse}}
+
+do_execsql_test 4.10 {
+ SELECT fts5_test_columnlocale(d1) FROM d1
+} {
+ {{} {}}
+ {{} reverse}
+}
+
+#-------------------------------------------------------------------------
+# Test that if an fts5_locale() value is written to an UNINDEXED
+# column it is stored as text. This is so that blobs and other values
+# can also be stored as is.
+#
+reset_db
+sqlite3_fts5_create_tokenizer -v2 db tcl tcl_create
+
+do_execsql_test 5.1 {
+ CREATE VIRTUAL TABLE t1 USING fts5(
+ x, y UNINDEXED, locale=1, tokenize=tcl
+ );
+
+ INSERT INTO t1(rowid, x, y) VALUES(111,
+ fts5_locale('reverse', 'one two three'),
+ fts5_locale('reverse', 'four five six')
+ );
+}
+
+do_execsql_test 5.2 {
+ SELECT rowid, x, y FROM t1
+} {
+ 111 {one two three} {four five six}
+}
+
+do_execsql_test 5.3 {
+ SELECT typeof(c0), typeof(c1) FROM t1_content
+} {
+ blob text
+}
+
+#-------------------------------------------------------------------------
+
+foreach {tn opt} {
+ 1 {}
+ 2 {, columnsize=0}
+} {
+ reset_db
+ sqlite3_fts5_create_tokenizer -v2 db tcl tcl_create
+ do_execsql_test 6.$tn.1 "
+ CREATE VIRTUAL TABLE y1 USING fts5(t, locale=1, tokenize=tcl $opt);
+ "
+
+ do_execsql_test 6.$tn.2 {
+ INSERT INTO y1(rowid, t) VALUES
+ (1, fts5_locale('second', 'the city of London')),
+ (2, fts5_locale('second', 'shall have all the old')),
+ (3, fts5_locale('second', 'Liberties and Customs')),
+ (4, fts5_locale('second', 'which it hath been used to have'));
+ }
+
+ fts5_aux_test_functions db
+
+ do_execsql_test 5.$tn.3 {
+ SELECT fts5_test_columnsize(y1) FROM y1
+ } {
+ 2 3 2 4
+ }
+
+ do_execsql_test 5.$tn.4 {
+ SELECT rowid, fts5_test_columnsize(y1) FROM y1('shall');
+ } {
+ 2 3
+ }
+
+ do_execsql_test 5.$tn.5 {
+ SELECT rowid, fts5_test_columnsize(y1) FROM y1('shall');
+ } {
+ 2 3
+ }
+
+ do_execsql_test 5.$tn.6 {
+ SELECT rowid, fts5_test_columnsize(y1) FROM y1('have');
+ } {
+ 4 4
+ }
+
+ do_execsql_test 5.$tn.7 {
+ SELECT rowid, highlight(y1, 0, '[', ']') FROM y1('have');
+ } {
+ 4 {which it hath been used to [have]}
+ }
+
+ do_execsql_test 5.$tn.8 {
+ SELECT rowid,
+ highlight(y1, 0, '[', ']'),
+ snippet(y1, 0, '[', ']', '...', 10)
+ FROM y1('Liberties + Customs');
+ } {
+ 3 {[Liberties and Customs]}
+ {[Liberties and Customs]}
+ }
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 6.0 {
+ CREATE VIRTUAL TABLE x1 USING fts5(x);
+}
+do_catchsql_test 6.1 {
+ INSERT INTO x1(rowid, x) VALUES(123, fts5_locale('en_AU', 'hello world'));
+} {1 {fts5_locale() requires locale=1}}
+
+do_execsql_test 6.2 {
+ SELECT typeof( fts5_locale(NULL, 'xyz') ), typeof( fts5_locale('', 'abc') );
+} {text text}
+
+#--------------------------------------------------------------------------
+# Test that fts5_locale() works with external-content tables.
+#
+reset_db
+sqlite3_fts5_create_tokenizer -v2 db tcl tcl_create
+
+do_execsql_test 7.1 {
+ CREATE TABLE t1(ii INTEGER PRIMARY KEY, bb BLOB, tt TEXT, locale TEXT);
+ CREATE VIEW v1 AS
+ SELECT ii AS rowid, bb, fts5_locale(locale, tt) AS tt FROM t1;
+
+ CREATE VIRTUAL TABLE ft USING fts5(
+ bb, tt, locale=1, tokenize=tcl, content=v1
+ );
+
+ INSERT INTO t1 VALUES(1, NULL, 'one two three', NULL);
+ INSERT INTO t1 VALUES(2, '7800616263', 'four five six', 'reverse');
+ INSERT INTO t1 VALUES(3, '000000007800616263', 'seven eight nine', 'second');
+}
+
+do_execsql_test 7.2 {
+ INSERT INTO ft(ft) VALUES('rebuild');
+ INSERT INTO ft(ft) VALUES('integrity-check');
+}
+
+do_execsql_test 7.3 {
+ SELECT rowid, quote(bb), quote(tt) FROM ft
+} {
+ 1 NULL {'one two three'}
+ 2 '7800616263' {'four five six'}
+ 3 '000000007800616263' {'seven eight nine'}
+}
+
+do_execsql_test 7.4 { SELECT rowid FROM ft('six'); }
+do_execsql_test 7.5 { SELECT rowid FROM ft(fts5_locale('reverse','six')); } 2
+
+fts5_aux_test_functions db
+
+do_execsql_test 7.6 {
+ SELECT fts5_test_columnlocale(ft) FROM ft;
+} {
+ {{} {}} {{} reverse} {{} second}
+}
+
+#-------------------------------------------------------------------------
+# Test that the porter tokenizer works with locales.
+#
+reset_db
+sqlite3_fts5_create_tokenizer -v2 db tcl tcl_create
+
+do_execsql_test 8.1 {
+ CREATE VIRTUAL TABLE ft USING fts5(tt, locale=1, tokenize="porter tcl");
+ CREATE VIRTUAL TABLE vocab USING fts5vocab('ft', instance);
+
+ INSERT INTO ft(rowid, tt) VALUES
+ (111, fts5_locale('second', 'the porter tokenizer is a wrapper tokenizer')),
+ (222, fts5_locale('reverse', 'This value may also be set'));
+}
+
+do_execsql_test 8.1 {
+ SELECT DISTINCT term FROM vocab ORDER BY 1
+} {
+ a eb eulav osla sihT te the token yam
+}
+
+#-------------------------------------------------------------------------
+# Test that position-lists (used by xInst, xPhraseFirst etc.) work with
+# locales and modes other than detail=full.
+#
+foreach {tn detail} {
+ 1 detail=full
+ 2 detail=none
+ 3 detail=column
+} {
+ reset_db
+ sqlite3_fts5_create_tokenizer -v2 db tcl tcl_create
+ do_execsql_test 9.$tn.0 "
+ CREATE VIRTUAL TABLE ft USING fts5(tt, locale=1, tokenize=tcl, $detail);
+ "
+ do_execsql_test 9.$tn.1 {
+ CREATE VIRTUAL TABLE vocab USING fts5vocab('ft', instance);
+ INSERT INTO ft(rowid, tt) VALUES
+ (-1, fts5_locale('second', 'it is an ancient mariner'));
+ }
+
+ do_execsql_test 9.$tn.2 {
+ SELECT DISTINCT term FROM vocab
+ } {an it mariner}
+
+ do_execsql_test 9.$tn.3 {
+ SELECT highlight(ft, 0, '[', ']') FROM ft('mariner')
+ } {{it is an ancient [mariner]}}
+}
+
+#-------------------------------------------------------------------------
+# Check some corrupt fts5_locale() blob formats are detected.
+#
+foreach_detail_mode $::testprefix {
+
+ reset_db
+ sqlite3_fts5_create_tokenizer -v2 db tcl tcl_create
+ fts5_aux_test_functions db
+ do_execsql_test 10.1 {
+ CREATE TABLE x1(ii INTEGER PRIMARY KEY, x);
+ CREATE VIRTUAL TABLE ft USING fts5(x,
+ content=x1, content_rowid=ii, locale=1, detail=%DETAIL%, columnsize=0
+ );
+
+ CREATE VIRTUAL TABLE ft2 USING fts5(
+ x, locale=1, detail=%DETAIL%, columnsize=0
+ );
+ }
+
+ foreach {tn v} {
+ 1 X'001122'
+ 2 X'0011223344'
+ 3 X'00E0B2EB68656c6c6f'
+ 4 X'00E0B2EB0068656c6c6f'
+ } {
+ do_execsql_test 10.2.$tn.0 { INSERT INTO ft(ft) VALUES('delete-all') }
+ do_execsql_test 10.2.$tn.1 { DELETE FROM x1; }
+ do_execsql_test 10.2.$tn.2 " INSERT INTO x1 VALUES(NULL, $v) "
+
+ do_catchsql_test 10.2.$tn.3 {
+ INSERT INTO ft(ft) VALUES('rebuild');
+ } {1 {SQL logic error}}
+
+ do_catchsql_test 10.2.$tn.4 "
+ SELECT * FROM ft( test_setsubtype($v, 76) );
+ " {1 {SQL logic error}}
+
+ do_execsql_test 10.2.$tn.5 {
+ INSERT INTO ft(rowid, x) VALUES(1, 'hello world');
+ }
+
+ if {"%DETAIL%"!="full"} {
+ do_catchsql_test 10.2.$tn.6 {
+ SELECT fts5_test_poslist(ft) FROM ft('world');
+ } {1 SQLITE_ERROR}
+
+ do_catchsql_test 10.2.$tn.7 {
+ SELECT fts5_test_columnsize(ft) FROM ft('world');
+ } {1 SQLITE_ERROR}
+
+ do_catchsql_test 10.2.$tn.7 {
+ SELECT fts5_test_columnlocale(ft) FROM ft('world');
+ } {1 SQLITE_ERROR}
+ }
+
+ do_catchsql_test 10.2.$tn.8 {
+ SELECT * FROM ft('hello')
+ } {1 {SQL logic error}}
+
+ do_catchsql_test 10.2.$tn.9 {
+ PRAGMA integrity_check;
+ } {0 ok}
+
+ do_execsql_test 10.2.$tn.10 {
+ DELETE FROM x1;
+ INSERT INTO x1(ii, x) VALUES(1, 'hello world');
+ }
+
+ do_catchsql_test 10.2.$tn.11 "
+ INSERT INTO ft(ft, rowid, x) VALUES('delete', 1, test_setsubtype($v,76) )
+ " {1 {SQL logic error}}
+
+ do_catchsql_test 10.2.$tn.12 "
+ INSERT INTO ft(rowid, x) VALUES(2, test_setsubtype($v,76) )
+ " {1 {SQL logic error}}
+
+ do_execsql_test 10.2.$tn.13 {
+ INSERT INTO ft2(rowid, x) VALUES(1, 'hello world');
+ }
+ do_execsql_test 10.2.$tn.14 "UPDATE ft2_content SET c0=$v"
+
+ do_catchsql_test 10.2.$tn.15 {
+ PRAGMA integrity_check;
+ } {1 {SQL logic error}}
+
+ do_execsql_test 10.2.$tn.16 {
+ DELETE FROM ft2_content;
+ INSERT INTO ft2(ft2) VALUES('rebuild');
+ }
+ }
+
+}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+sqlite3_fts5_create_tokenizer -v2 db tcl tcl_create
+fts5_aux_test_functions db
+do_execsql_test 11.0 {
+ CREATE VIRTUAL TABLE x1 USING fts5(abc, locale=1);
+ INSERT INTO x1(rowid, abc) VALUES(123, fts5_locale('en_US', 'one two three'));
+}
+
+do_catchsql_test 11.1 {
+ SELECT fts5_columnlocale(x1, -1) FROM x1('two');
+} {1 SQLITE_RANGE}
+do_catchsql_test 11.2 {
+ SELECT fts5_columnlocale(x1, 1) FROM x1('two');
+} {1 SQLITE_RANGE}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+do_test 12.0 {
+ list [catch {
+ sqlite3_fts5_create_tokenizer -v2 -version 3 db tcl tcl_create
+ } msg] $msg
+} {1 {error in fts5_api.xCreateTokenizer_v2()}}
+
+#-------------------------------------------------------------------------
+# Tests for auxiliary function fts5_get_locale().
+#
+reset_db
+
+# Check that if the table does not support locale=1, fts5_get_locale()
+# always returns NULL.
+do_execsql_test 13.1.0 {
+ CREATE VIRTUAL TABLE nolocale USING fts5(a, b);
+ INSERT INTO nolocale VALUES('one two three', 'four five six');
+ INSERT INTO nolocale VALUES('three two one', 'seven eight nine');
+}
+do_execsql_test 13.1.1 {
+ SELECT fts5_get_locale(nolocale, 0) IS NULL FROM nolocale;
+} {1 1}
+do_execsql_test 13.1.2 {
+ SELECT fts5_get_locale(nolocale, 1) IS NULL FROM nolocale('one + two');
+} {1}
+do_execsql_test 13.1.3 {
+ SELECT fts5_get_locale(nolocale, 0) IS NULL FROM nolocale('one AND two');
+} {1 1}
+do_execsql_test 13.1.4 {
+ SELECT
+ fts5_get_locale(nolocale, 1) IS NULL
+ FROM nolocale('three AND two') ORDER BY rank
+} {1 1}
+do_catchsql_test 13.1.5 {
+ SELECT fts5_get_locale(nolocale, 2) IS NULL FROM nolocale('three AND two');
+} {1 {column index out of range}}
+do_catchsql_test 13.1.6 {
+ SELECT fts5_get_locale(nolocale, -1) IS NULL FROM nolocale('three AND two');
+} {1 {column index out of range}}
+do_catchsql_test 13.1.7 {
+ SELECT fts5_get_locale(nolocale) IS NULL FROM nolocale('three AND two');
+} {1 {wrong number of arguments to function fts5_get_locale()}}
+do_catchsql_test 13.1.8 {
+ SELECT fts5_get_locale(nolocale, 0, 0) IS NULL FROM nolocale('three AND two');
+} {1 {wrong number of arguments to function fts5_get_locale()}}
+do_catchsql_test 13.1.9 {
+ SELECT fts5_get_locale(nolocale, 'text') FROM nolocale('three AND two');
+} {1 {non-integer argument passed to function fts5_get_locale()}}
+
+
+# Check that if the table does support locale=1, fts5_get_locale()
+# returns the locale of the identified row/column.
+do_execsql_test 13.2.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(a, b, locale=1);
+ INSERT INTO ft VALUES(
+ fts5_locale('th_TH', 'one two three'), 'four five six seven'
+ );
+ INSERT INTO ft VALUES(
+ 'three two one', fts5_locale('en_AU', 'seven eight nine')
+ );
+}
+
+do_execsql_test 13.2.1 {
+ SELECT quote(fts5_get_locale(ft, 0)), quote(fts5_get_locale(ft, 1)) FROM ft
+} { 'th_TH' NULL NULL 'en_AU' }
+do_execsql_test 13.2.2 {
+ SELECT
+ quote(fts5_get_locale(ft, 0)), quote(fts5_get_locale(ft, 1))
+ FROM ft('one AND three')
+} { 'th_TH' NULL NULL 'en_AU' }
+do_execsql_test 13.2.3 {
+ SELECT
+ quote(fts5_get_locale(ft, 0)), quote(fts5_get_locale(ft, 1))
+ FROM ft('one AND three') ORDER BY rank
+} { NULL 'en_AU' 'th_TH' NULL }
+do_execsql_test 13.2.4 {
+ SELECT
+ quote(fts5_get_locale(ft, 0)), quote(fts5_get_locale(ft, 1))
+ FROM ft('one AND three') ORDER BY rowid
+} { 'th_TH' NULL NULL 'en_AU' }
+
+do_execsql_test 13.2.5 {
+ SELECT
+ quote(fts5_get_locale(ft, '0')), quote(fts5_get_locale(ft, 1))
+ FROM ft('one AND three') ORDER BY rowid
+} { 'th_TH' NULL NULL 'en_AU' }
+
+do_catchsql_test 13.2.6 {
+ SELECT
+ quote(fts5_get_locale(ft, '0.0')), quote(fts5_get_locale(ft, 1))
+ FROM ft('one AND three') ORDER BY rowid
+} {1 {non-integer argument passed to function fts5_get_locale()}}
+do_catchsql_test 13.2.7 {
+ SELECT
+ quote(fts5_get_locale(ft, 0.0)), quote(fts5_get_locale(ft, 1))
+ FROM ft('one AND three') ORDER BY rowid
+} {1 {non-integer argument passed to function fts5_get_locale()}}
+
+finish_test
+
diff --git a/ext/fts5/test/fts5misc.test b/ext/fts5/test/fts5misc.test
index abd4fdaf89..c2e580c564 100644
--- a/ext/fts5/test/fts5misc.test
+++ b/ext/fts5/test/fts5misc.test
@@ -21,8 +21,6 @@ ifcapable !fts5 {
return
}
-if 0 {
-
do_execsql_test 1.0 {
CREATE VIRTUAL TABLE t1 USING fts5(a);
}
@@ -37,21 +35,21 @@ do_catchsql_test 1.1.2 {
do_catchsql_test 1.2.1 {
SELECT highlight(t1, 4, '', '') FROM t1('*id');
-} {0 {{}}}
+} {1 {no such cursor: 4}}
do_catchsql_test 1.2.2 {
SELECT a FROM t1
WHERE rank = (SELECT highlight(t1, 4, '', '') FROM t1('*id'));
-} {0 {}}
+} {1 {no such cursor: 6}}
do_catchsql_test 1.3.1 {
SELECT highlight(t1, 4, '', '') FROM t1('*reads');
-} {1 {no such cursor: 2}}
+} {1 {no such cursor: 0}}
do_catchsql_test 1.3.2 {
SELECT a FROM t1
WHERE rank = (SELECT highlight(t1, 4, '', '') FROM t1('*reads'));
-} {1 {no such cursor: 2}}
+} {1 {no such cursor: 0}}
db close
sqlite3 db test.db
@@ -59,7 +57,12 @@ sqlite3 db test.db
do_catchsql_test 1.3.3 {
SELECT a FROM t1
WHERE rank = (SELECT highlight(t1, 4, '', '') FROM t1('*reads'));
-} {1 {no such cursor: 1}}
+} {1 {no such cursor: 0}}
+
+fts5_aux_test_functions db
+do_catchsql_test 1.3.4 {
+ SELECT fts5_columntext(t1) FROM t1('*reads');
+} {1 {no such cursor: 0}}
#-------------------------------------------------------------------------
reset_db
@@ -569,24 +572,98 @@ do_execsql_test 20.5 {
} {3 1}
#-------------------------------------------------------------------------
-}
reset_db
do_execsql_test 21.0 {
+ CREATE TABLE t1(ii INTEGER, x TEXT, y TEXT);
+ CREATE VIRTUAL TABLE xyz USING fts5(content_rowid=ii, content=t1, x, y);
+ INSERT INTO t1 VALUES(1, 'one', 'i');
+ INSERT INTO t1 VALUES(2, 'two', 'ii');
+ INSERT INTO t1 VALUES(3, 'tree', 'iii');
+ INSERT INTO xyz(xyz) VALUES('rebuild');
+}
+
+do_execsql_test 21.1 {
+ UPDATE xyz SET y='TWO' WHERE rowid=2;
+ UPDATE t1 SET y='TWO' WHERE ii=2;
+}
+
+do_execsql_test 21.2 {
+ PRAGMA integrity_check
+} {ok}
+
+breakpoint
+sqlite3_db_config db DEFENSIVE 1
+do_execsql_test 21.3 {
+ CREATE TABLE xyz_notashadow(x, y);
+ DROP TABLE xyz_notashadow;
+}
+sqlite3_db_config db DEFENSIVE 0
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 22.0 {
+ SELECT fts5(NULL);
+} {{}}
+do_execsql_test 22.1 {
+ SELECT count(*) FROM (
+ SELECT fts5_source_id()
+ )
+} {1}
+execsql_pp {
+ SELECT fts5_source_id()
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 23.0 {
+ CREATE VIRTUAL TABLE x1 USING fts5(x);
+ INSERT INTO x1 VALUES('one + two + three');
+ INSERT INTO x1 VALUES('one + xyz + three');
+ INSERT INTO x1 VALUES('xyz + two + xyz');
+}
+do_execsql_test 23.1 {
+ SELECT rowid FROM x1('one + two + three');
+} {1}
+
+do_execsql_test 23.2 {
+ SELECT rowid FROM x1('^".." AND one');
+} {}
+
+do_execsql_test 23.3 {
+ SELECT rowid FROM x1('abc NEAR ".." NEAR def');
+} {}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 24.0 {
CREATE VIRTUAL TABLE t1 USING fts5(a, detail='none');
INSERT INTO t1(a) VALUES('a');
}
-do_execsql_test 21.2 {
+do_execsql_test 24.2 {
SELECT rank FROM ( SELECT rank FROM t1('a NOT "" NOT def') ) ORDER BY 1;
} {-1e-06}
-do_execsql_test 21.3 {
+do_execsql_test 24.3 {
SELECT rank FROM ( SELECT rank FROM t1('a NOT � NOT def') ) ORDER BY 1;
} {-1e-06}
-do_execsql_test 21.4 {
+do_execsql_test 24.4 {
SELECT rank FROM ( SELECT rank FROM t1('a NOT "" NOT def') );
} {-1e-06}
+#-------------------------------------------------------------------------
+reset_db
+fts5_aux_test_functions db
+
+do_execsql_test 25.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a, detail='none', content='');
+ INSERT INTO t1(a) VALUES('a b c');
+}
+
+do_execsql_test 25.0 {
+ SELECT fts5_test_poslist(t1) FROM t1('b') ORDER BY rank;
+} {{}}
+
finish_test
diff --git a/ext/fts5/test/fts5origintext.test b/ext/fts5/test/fts5origintext.test
index cc9d99e2d9..8e975fa17c 100644
--- a/ext/fts5/test/fts5origintext.test
+++ b/ext/fts5/test/fts5origintext.test
@@ -261,8 +261,8 @@ do_execsql_test 5.3 {
reset_db
sqlite3_fts5_register_origintext db
do_execsql_test 6.0 {
- CREATE VIRTUAL TABLE ft USING fts5(
- x, y, tokenize='origintext unicode61', detail=%DETAIL%
+ CREATE VIRTUAL TABLE ft USING fts5(
+ x, y, tokenize='origintext unicode61', detail=%DETAIL%, tokendata=0
);
INSERT INTO ft VALUES('One Two', 'Three two');
@@ -291,6 +291,22 @@ do_execsql_test 6.3 {
SELECT rowid, tokens(ft) FROM ft('Three*');
} {1 {{}} 2 {{}}}
+fts5_aux_test_functions db
+do_catchsql_test 6.4 {
+ SELECT fts5_test_insttoken(ft, -1, 0) FROM ft('one');
+} {1 SQLITE_RANGE}
+
+do_catchsql_test 6.5 {
+ SELECT fts5_test_insttoken(ft, 1, 0) FROM ft('one');
+} {1 SQLITE_RANGE}
+
+do_catchsql_test 6.6 {
+ CREATE VIRTUAL TABLE ft2 USING fts5(x, tokendata=2);
+} {1 {malformed tokendata=... directive}}
+do_catchsql_test 6.7 {
+ CREATE VIRTUAL TABLE ft2 USING fts5(x, content='', tokendata=11);
+} {1 {malformed tokendata=... directive}}
+
}
finish_test
diff --git a/ext/fts5/test/fts5secure8.test b/ext/fts5/test/fts5secure8.test
index 0216bb6ea1..8b65b7c59f 100644
--- a/ext/fts5/test/fts5secure8.test
+++ b/ext/fts5/test/fts5secure8.test
@@ -58,6 +58,10 @@ do_execsql_test 2.1 {
pragma quick_check;
} {ok}
+do_catchsql_test 2.2 {
+ INSERT INTO xyz(xyz, rank) VALUES('secure-delete', 'hello world');
+} {1 {SQL logic error}}
+
diff --git a/ext/fts5/test/fts5simple.test b/ext/fts5/test/fts5simple.test
index 6384095067..ad59bf0d9e 100644
--- a/ext/fts5/test/fts5simple.test
+++ b/ext/fts5/test/fts5simple.test
@@ -350,7 +350,7 @@ do_execsql_test 14.3 {
do_execsql_test 14.4 {
SELECT rowid, x, x1 FROM x1 WHERE x1 MATCH '*reads'
-} {0 {} 3}
+} {0 {} 2}
#-------------------------------------------------------------------------
reset_db
@@ -480,4 +480,33 @@ do_execsql_test 22.0 {
do_catchsql_test 22.1 {SELECT * FROM x1('')} {1 {fts5: syntax error near ""}}
do_catchsql_test 22.2 {SELECT * FROM x1(NULL)} {1 {fts5: syntax error near ""}}
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 23.0 {
+ CREATE VIRTUAL TABLE x1 USING fts5(x);
+ SELECT count(*) FROM x1_data;
+} {2}
+
+do_execsql_test 23.1 {
+ BEGIN;
+ INSERT INTO x1 VALUES('a b c d');
+ INSERT INTO x1 VALUES('a b c d');
+ INSERT INTO x1 VALUES('a b c d');
+}
+
+do_execsql_test 23.2 {
+ SELECT count(*) FROM x1_data;
+} {2}
+
+do_execsql_test 23.3 {
+ INSERT INTO x1(x1) VALUES('flush');
+ SELECT count(*) FROM x1_data;
+} {3}
+
+do_execsql_test 23.4 {
+ ROLLBACK;
+ SELECT count(*) FROM x1_data;
+} {2}
+
+
finish_test
diff --git a/ext/fts5/test/fts5tokenizer3.test b/ext/fts5/test/fts5tokenizer3.test
new file mode 100644
index 0000000000..5cdab743c2
--- /dev/null
+++ b/ext/fts5/test/fts5tokenizer3.test
@@ -0,0 +1,77 @@
+# 2024 Aug 10
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# Tests focusing on the built-in fts5 tokenizers.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5tokenizer3
+
+# If SQLITE_ENABLE_FTS5 is not defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+
+proc get_sod {args} { return "split_on_dot" }
+proc get_lowercase {args} { return "lowercase" }
+
+proc lowercase {flags txt} {
+ set n [string length $txt]
+ sqlite3_fts5_token [string tolower $txt] 0 $n
+ return 0
+}
+
+proc split_on_dot {flags txt} {
+ set iOff 0
+ foreach t [split $txt "."] {
+ set n [string length $txt]
+ sqlite3_fts5_token $t $iOff [expr $iOff+$n]
+ incr iOff [expr {$n+1}]
+ }
+ return ""
+}
+
+foreach {tn script} {
+ 1 {
+ sqlite3_fts5_create_tokenizer db lowercase get_lowercase
+ sqlite3_fts5_create_tokenizer -parent lowercase db split_on_dot get_sod
+ }
+ 2 {
+ sqlite3_fts5_create_tokenizer -v2 db lowercase get_lowercase
+ sqlite3_fts5_create_tokenizer -parent lowercase db split_on_dot get_sod
+ }
+ 3 {
+ sqlite3_fts5_create_tokenizer db lowercase get_lowercase
+ sqlite3_fts5_create_tokenizer -v2 -parent lowercase db split_on_dot get_sod
+ }
+ 4 {
+ sqlite3_fts5_create_tokenizer -v2 db lowercase get_lowercase
+ sqlite3_fts5_create_tokenizer -v2 -parent lowercase db split_on_dot get_sod
+ }
+} {
+ reset_db
+ eval $script
+
+ do_execsql_test 1.$tn.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize=split_on_dot);
+ CREATE VIRTUAL TABLE t1vocab USING fts5vocab(t1, instance);
+ INSERT INTO t1 VALUES('ABC.Def.ghi');
+ }
+
+ do_execsql_test 1.$tn.1 {
+ SELECT term FROM t1vocab ORDER BY 1
+ } {abc def ghi}
+}
+
+
+finish_test
diff --git a/ext/fts5/test/fts5trigram.test b/ext/fts5/test/fts5trigram.test
index 752686620c..3742c647f0 100644
--- a/ext/fts5/test/fts5trigram.test
+++ b/ext/fts5/test/fts5trigram.test
@@ -56,6 +56,7 @@ foreach {tn like res} {
7 {ABCDEFG%} 1
8 {%รุงเ%} 2
9 {%งเ%} 2
+ 10 {%"งเ"%} {}
} {
do_execsql_test 1.3.$tn {
SELECT rowid FROM t1 WHERE y LIKE $like
@@ -200,6 +201,12 @@ do_eqp_test 6.3 {
do_eqp_test 6.4 {
SELECT * FROM ci1 WHERE x GLOB ?
} {VIRTUAL TABLE INDEX 0:G0}
+do_eqp_test 6.5 {
+ SELECT * FROM ci1 WHERE x < ?
+} {{SCAN ci1 VIRTUAL TABLE INDEX 0:}}
+do_eqp_test 6.6 {
+ SELECT * FROM ci0 WHERE x < ?
+} {{SCAN ci0 VIRTUAL TABLE INDEX 0:}}
reset_db
do_execsql_test 7.0 {
@@ -256,4 +263,85 @@ do_execsql_test 8.3 {
{[abcde]}
}
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 9.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(
+ a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12,
+ tokenize=trigram
+ );
+
+ INSERT INTO t1(rowid, a12) VALUES(111, 'thats a tricky case though');
+ INSERT INTO t1(rowid, a12) VALUES(222, 'the query planner cannot do');
+}
+
+do_execsql_test 9.1 {
+ SELECT rowid FROM t1 WHERE a12 LIKE '%tricky%'
+} {111}
+
+do_execsql_test 9.2 {
+ SELECT rowid FROM t1 WHERE a12 LIKE '%tricky%' AND a12 LIKE '%case%'
+} {111}
+
+do_execsql_test 9.3 {
+ SELECT rowid FROM t1 WHERE a12 LIKE NULL
+} {}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 10.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a, tokenize=trigram);
+}
+
+do_test 10.1 {
+ foreach {val} {
+ "abc \UFFjkl\UFF"
+ "abc \UFFFjkl\UFFF"
+ "abc \UFFFFjkl\UFFFF"
+ "abc \UFFFFFjkl\UFFFFF"
+ "\UFFjkl\UFF abc"
+ "\UFFFjkl\UFFF abc"
+ "\UFFFFjkl\UFFFF abc"
+ "\UFFFFFjkl\UFFFFF abc"
+ "\U10001jkl\U10001 abc"
+ } {
+ execsql { INSERT INTO t1 VALUES( $val ) }
+ }
+} {}
+
+do_test 10.2 {
+ foreach {val} {
+ X'E18000626320646566'
+ X'61EDA0806320646566'
+ X'61EDA0806320646566'
+ X'61EFBFBE6320646566'
+ X'76686920E18000626320646566'
+ X'7668692061EDA0806320646566'
+ X'7668692061EDA0806320646566'
+ X'7668692061EFBFBE6320646566'
+ } {
+ execsql " INSERT INTO t1 VALUES( $val ) "
+ }
+} {}
+
+do_test 10.3 {
+ set a [binary format c* {0x61 0xF7 0xBF 0xBF 0xBF 0x62}]
+ set b [binary format c* {0x61 0xF7 0xBF 0xBF 0xBF 0xBF 0x62}]
+ set c [binary format c* {0x61 0xF7 0xBF 0xBF 0xBF 0xBF 0xBF 0x62}]
+ set d [binary format c* {0x61 0xF7 0xBF 0xBF 0xBF 0xBF 0xBF 0xBF 0x62}]
+ execsql {
+ INSERT INTO t1 VALUES($a);
+ INSERT INTO t1 VALUES($b);
+ INSERT INTO t1 VALUES($c);
+ INSERT INTO t1 VALUES($d);
+
+ INSERT INTO t1 VALUES('abcd' || $a);
+ INSERT INTO t1 VALUES('abcd' || $b);
+ INSERT INTO t1 VALUES('abcd' || $c);
+ INSERT INTO t1 VALUES('abcd' || $d);
+ }
+} {}
+
+
+
finish_test
diff --git a/ext/fts5/test/fts5unicode2.test b/ext/fts5/test/fts5unicode2.test
index 3fc1f673a4..7a49a1d83f 100644
--- a/ext/fts5/test/fts5unicode2.test
+++ b/ext/fts5/test/fts5unicode2.test
@@ -470,4 +470,24 @@ do_execsql_test 8.2.3 {
SELECT rowid FROM t4 WHERE t4 MATCH 'a' ORDER BY rowid ASC;
} {2 4}
+#-------------------------------------------------------------------------
+
+foreach {tn val bErr} {
+ 1 0 0
+ 2 1 0
+ 3 2 0
+ 4 3 1
+ 5 11 1
+} {
+ reset_db
+ set aRes(0) {0 {}}
+ set aRes(1) {1 {error in tokenizer constructor}}
+ set res $aRes($bErr)
+ do_catchsql_test 9.1.$tn "
+ CREATE VIRTUAL TABLE bl USING fts5(
+ s, tokenize='trigram remove_diacritics $val'
+ );
+ " $res
+}
+
finish_test
diff --git a/ext/misc/series.c b/ext/misc/series.c
index 0dfed181f6..f2ca75c253 100644
--- a/ext/misc/series.c
+++ b/ext/misc/series.c
@@ -90,6 +90,26 @@
** and a very large cost if either start or stop are unavailable. This
** encourages the query planner to order joins such that the bounds of the
** series are well-defined.
+**
+** Update on 2024-08-22:
+** xBestIndex now also looks for equality and inequality constraints against
+** the value column and uses those constraints as additional bounds against
+** the sequence range. Thus, a query like this:
+**
+** SELECT value FROM generate_series($SA,$EA)
+** WHERE value BETWEEN $SB AND $EB;
+**
+** Is logically the same as:
+**
+** SELECT value FROM generate_series(max($SA,$SB),min($EA,$EB));
+**
+** Constraints on the value column can server as substitutes for constraints
+** on the hidden start and stop columns. So, the following two queries
+** are equivalent:
+**
+** SELECT value FROM generate_series($S,$E);
+** SELECT value FROM generate_series WHERE value BETWEEN $S and $E;
+**
*/
#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
@@ -131,8 +151,10 @@ static sqlite3_int64 genSeqMember(
typedef unsigned char u8;
typedef struct SequenceSpec {
- sqlite3_int64 iBase; /* Starting value ("start") */
- sqlite3_int64 iTerm; /* Given terminal value ("stop") */
+ sqlite3_int64 iOBase; /* Original starting value ("start") */
+ sqlite3_int64 iOTerm; /* Original terminal value ("stop") */
+ sqlite3_int64 iBase; /* Starting value to actually use */
+ sqlite3_int64 iTerm; /* Terminal value to actually use */
sqlite3_int64 iStep; /* Increment ("step") */
sqlite3_uint64 uSeqIndexMax; /* maximum sequence index (aka "n") */
sqlite3_uint64 uSeqIndexNow; /* Current index during generation */
@@ -325,9 +347,9 @@ static int seriesColumn(
series_cursor *pCur = (series_cursor*)cur;
sqlite3_int64 x = 0;
switch( i ){
- case SERIES_COLUMN_START: x = pCur->ss.iBase; break;
- case SERIES_COLUMN_STOP: x = pCur->ss.iTerm; break;
- case SERIES_COLUMN_STEP: x = pCur->ss.iStep; break;
+ case SERIES_COLUMN_START: x = pCur->ss.iOBase; break;
+ case SERIES_COLUMN_STOP: x = pCur->ss.iOTerm; break;
+ case SERIES_COLUMN_STEP: x = pCur->ss.iStep; break;
default: x = pCur->ss.iValueNow; break;
}
sqlite3_result_int64(ctx, x);
@@ -335,7 +357,9 @@ static int seriesColumn(
}
#ifndef LARGEST_UINT64
+#define LARGEST_INT64 (0xffffffff|(((sqlite3_int64)0x7fffffff)<<32))
#define LARGEST_UINT64 (0xffffffff|(((sqlite3_uint64)0xffffffff)<<32))
+#define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64)
#endif
/*
@@ -376,13 +400,18 @@ static int seriesEof(sqlite3_vtab_cursor *cur){
** parameter. (idxStr is not used in this implementation.) idxNum
** is a bitmask showing which constraints are available:
**
-** 0x01: start=VALUE
-** 0x02: stop=VALUE
-** 0x04: step=VALUE
-** 0x08: descending order
-** 0x10: ascending order
-** 0x20: LIMIT VALUE
-** 0x40: OFFSET VALUE
+** 0x0001: start=VALUE
+** 0x0002: stop=VALUE
+** 0x0004: step=VALUE
+** 0x0008: descending order
+** 0x0010: ascending order
+** 0x0020: LIMIT VALUE
+** 0x0040: OFFSET VALUE
+** 0x0080: value=VALUE
+** 0x0100: value>=VALUE
+** 0x0200: value>VALUE
+** 0x1000: value<=VALUE
+** 0x2000: valuess.iBase = sqlite3_value_int64(argv[i++]);
@@ -416,16 +451,98 @@ static int seriesFilter(
}else{
pCur->ss.iStep = 1;
}
+
+ /* If there are constraints on the value column but there are
+ ** no constraints on the start, stop, and step columns, then
+ ** initialize the default range to be the entire range of 64-bit signed
+ ** integers. This range will contracted by the value column constraints
+ ** further below.
+ */
+ if( (idxNum & 0x05)==0 && (idxNum & 0x0380)!=0 ){
+ pCur->ss.iBase = SMALLEST_INT64;
+ }
+ if( (idxNum & 0x06)==0 && (idxNum & 0x3080)!=0 ){
+ pCur->ss.iTerm = LARGEST_INT64;
+ }
+ pCur->ss.iOBase = pCur->ss.iBase;
+ pCur->ss.iOTerm = pCur->ss.iTerm;
+
+ /* Extract the LIMIT and OFFSET values, but do not apply them yet.
+ ** The range must first be constrained by the limits on value.
+ */
if( idxNum & 0x20 ){
- sqlite3_int64 iLimit = sqlite3_value_int64(argv[i++]);
- sqlite3_int64 iTerm;
+ iLimit = sqlite3_value_int64(argv[i++]);
if( idxNum & 0x40 ){
- sqlite3_int64 iOffset = sqlite3_value_int64(argv[i++]);
- if( iOffset>0 ){
- pCur->ss.iBase += pCur->ss.iStep*iOffset;
+ iOffset = sqlite3_value_int64(argv[i++]);
+ }
+ }
+
+ if( idxNum & 0x3380 ){
+ /* Extract the maximum range of output values determined by
+ ** constraints on the "value" column.
+ */
+ if( idxNum & 0x0080 ){
+ iMin = iMax = sqlite3_value_int64(argv[i++]);
+ }else{
+ if( idxNum & 0x0300 ){
+ iMin = sqlite3_value_int64(argv[i++]);
+ if( idxNum & 0x0200 ){
+ if( iMin==LARGEST_INT64 ){
+ returnNoRows = 1;
+ }else{
+ iMin++;
+ }
+ }
+ }
+ if( idxNum & 0x3000 ){
+ iMax = sqlite3_value_int64(argv[i++]);
+ if( idxNum & 0x2000 ){
+ if( iMax==SMALLEST_INT64 ){
+ returnNoRows = 1;
+ }else{
+ iMax--;
+ }
+ }
+ }
+ if( iMin>iMax ){
+ returnNoRows = 1;
}
}
+
+ /* Try to reduce the range of values to be generated based on
+ ** constraints on the "value" column.
+ */
+ if( pCur->ss.iStep>0 ){
+ sqlite3_int64 szStep = pCur->ss.iStep;
+ if( pCur->ss.iBasess.iBase;
+ pCur->ss.iBase += ((d+szStep-1)/szStep)*szStep;
+ }
+ if( pCur->ss.iTerm>iMax ){
+ sqlite3_uint64 d = pCur->ss.iTerm - iMax;
+ pCur->ss.iTerm -= ((d+szStep-1)/szStep)*szStep;
+ }
+ }else{
+ sqlite3_int64 szStep = -pCur->ss.iStep;
+ assert( szStep>0 );
+ if( pCur->ss.iBase>iMax ){
+ sqlite3_uint64 d = pCur->ss.iBase - iMax;
+ pCur->ss.iBase -= ((d+szStep-1)/szStep)*szStep;
+ }
+ if( pCur->ss.iTermss.iTerm;
+ pCur->ss.iTerm += ((d+szStep-1)/szStep)*szStep;
+ }
+ }
+ }
+
+ /* Apply LIMIT and OFFSET constraints, if any */
+ if( idxNum & 0x20 ){
+ if( iOffset>0 ){
+ pCur->ss.iBase += pCur->ss.iStep*iOffset;
+ }
if( iLimit>=0 ){
+ sqlite3_int64 iTerm;
iTerm = pCur->ss.iBase + (iLimit - 1)*pCur->ss.iStep;
if( pCur->ss.iStep<0 ){
if( iTerm>pCur->ss.iTerm ) pCur->ss.iTerm = iTerm;
@@ -434,16 +551,21 @@ static int seriesFilter(
}
}
}
+
+
for(i=0; iss.iBase = 1;
- pCur->ss.iTerm = 0;
- pCur->ss.iStep = 1;
+ returnNoRows = 1;
break;
}
}
+ if( returnNoRows ){
+ pCur->ss.iBase = 1;
+ pCur->ss.iTerm = 0;
+ pCur->ss.iStep = 1;
+ }
if( idxNum & 0x08 ){
pCur->ss.isReversing = pCur->ss.iStep > 0;
}else{
@@ -464,13 +586,35 @@ static int seriesFilter(
**
** The query plan is represented by bits in idxNum:
**
-** 0x01 start = $value -- constraint exists
-** 0x02 stop = $value -- constraint exists
-** 0x04 step = $value -- constraint exists
-** 0x08 output is in descending order
-** 0x10 output is in ascending order
-** 0x20 LIMIT $value -- constraint exists
-** 0x40 OFFSET $value -- constraint exists
+** 0x0001 start = $num
+** 0x0002 stop = $num
+** 0x0004 step = $num
+** 0x0008 output is in descending order
+** 0x0010 output is in ascending order
+** 0x0020 LIMIT $num
+** 0x0040 OFFSET $num
+** 0x0080 value = $num
+** 0x0100 value >= $num
+** 0x0200 value > $num
+** 0x1000 value <= $num
+** 0x2000 value < $num
+**
+** Only one of 0x0100 or 0x0200 will be returned. Similarly, only
+** one of 0x1000 or 0x2000 will be returned. If the 0x0080 is set, then
+** none of the 0xff00 bits will be set.
+**
+** The order of parameters passed to xFilter is as follows:
+**
+** * The argument to start= if bit 0x0001 is in the idxNum mask
+** * The argument to stop= if bit 0x0002 is in the idxNum mask
+** * The argument to step= if bit 0x0004 is in the idxNum mask
+** * The argument to LIMIT if bit 0x0020 is in the idxNum mask
+** * The argument to OFFSET if bit 0x0040 is in the idxNum mask
+** * The argument to value=, or value>= or value> if any of
+** bits 0x0380 are in the idxNum mask
+** * The argument to value<= or value< if either of bits 0x3000
+** are in the mask
+**
*/
static int seriesBestIndex(
sqlite3_vtab *pVTab,
@@ -483,7 +627,9 @@ static int seriesBestIndex(
#endif
int unusableMask = 0; /* Mask of unusable constraints */
int nArg = 0; /* Number of arguments that seriesFilter() expects */
- int aIdx[5]; /* Constraints on start, stop, step, LIMIT, OFFSET */
+ int aIdx[7]; /* Constraints on start, stop, step, LIMIT, OFFSET,
+ ** and value. aIdx[5] covers value=, value>=, and
+ ** value>, aIdx[6] covers value<= and value< */
const struct sqlite3_index_constraint *pConstraint;
/* This implementation assumes that the start, stop, and step columns
@@ -491,7 +637,7 @@ static int seriesBestIndex(
assert( SERIES_COLUMN_STOP == SERIES_COLUMN_START+1 );
assert( SERIES_COLUMN_STEP == SERIES_COLUMN_START+2 );
- aIdx[0] = aIdx[1] = aIdx[2] = aIdx[3] = aIdx[4] = -1;
+ aIdx[0] = aIdx[1] = aIdx[2] = aIdx[3] = aIdx[4] = aIdx[5] = aIdx[6] = -1;
pConstraint = pIdxInfo->aConstraint;
for(i=0; inConstraint; i++, pConstraint++){
int iCol; /* 0 for start, 1 for stop, 2 for step */
@@ -512,7 +658,50 @@ static int seriesBestIndex(
}
continue;
}
- if( pConstraint->iColumniColumn==SERIES_COLUMN_VALUE ){
+ switch( op ){
+ case SQLITE_INDEX_CONSTRAINT_EQ:
+ case SQLITE_INDEX_CONSTRAINT_IS: {
+ idxNum |= 0x0080;
+ idxNum &= ~0x3300;
+ aIdx[5] = i;
+ aIdx[6] = -1;
+ bStartSeen = 1;
+ break;
+ }
+ case SQLITE_INDEX_CONSTRAINT_GE: {
+ if( idxNum & 0x0080 ) break;
+ idxNum |= 0x0100;
+ idxNum &= ~0x0200;
+ aIdx[5] = i;
+ bStartSeen = 1;
+ break;
+ }
+ case SQLITE_INDEX_CONSTRAINT_GT: {
+ if( idxNum & 0x0080 ) break;
+ idxNum |= 0x0200;
+ idxNum &= ~0x0100;
+ aIdx[5] = i;
+ bStartSeen = 1;
+ break;
+ }
+ case SQLITE_INDEX_CONSTRAINT_LE: {
+ if( idxNum & 0x0080 ) break;
+ idxNum |= 0x1000;
+ idxNum &= ~0x2000;
+ aIdx[6] = i;
+ break;
+ }
+ case SQLITE_INDEX_CONSTRAINT_LT: {
+ if( idxNum & 0x0080 ) break;
+ idxNum |= 0x2000;
+ idxNum &= ~0x1000;
+ aIdx[6] = i;
+ break;
+ }
+ }
+ continue;
+ }
iCol = pConstraint->iColumn - SERIES_COLUMN_START;
assert( iCol>=0 && iCol<=2 );
iMask = 1 << iCol;
@@ -534,7 +723,7 @@ static int seriesBestIndex(
idxNum &= ~0x60;
aIdx[4] = 0;
}
- for(i=0; i<5; i++){
+ for(i=0; i<7; i++){
if( (j = aIdx[i])>=0 ){
pIdxInfo->aConstraintUsage[j].argvIndex = ++nArg;
pIdxInfo->aConstraintUsage[j].omit =
@@ -582,6 +771,9 @@ static int seriesBestIndex(
pIdxInfo->estimatedRows = 2147483647;
}
pIdxInfo->idxNum = idxNum;
+#ifdef SQLITE_INDEX_SCAN_HEX
+ pIdxInfo->idxFlags = SQLITE_INDEX_SCAN_HEX;
+#endif
return SQLITE_OK;
}
diff --git a/main.mk b/main.mk
index e2ecbd6613..bf0c315455 100644
--- a/main.mk
+++ b/main.mk
@@ -42,6 +42,10 @@
# build the SQLite library and testing tools.
################################################################################
+# If OPTIONS... is specified on the command-line, append its value to OPTS
+#
+OPTS += $(OPTIONS)
+
# This is how we compile
#
TCCX = $(TCC) $(OPTS) -I. -I$(TOP)/src -I$(TOP)
@@ -959,9 +963,14 @@ tcltest: ./testfixture$(EXE)
testrunner: testfixture$(EXE)
./testfixture$(EXE) $(TOP)/test/testrunner.tcl
-# Runs both fuzztest and testrunner, consecutively.
+# This is the testing target preferred by the core SQLite developers.
+# It runs tests under a standard configuration, regardless of how
+# ./configure was run. The devs run "make devtest" prior to each
+# check-in, at a minimum. Probably other tests too, but at least this
+# one.
#
-devtest: testfixture$(EXE) fuzztest testrunner
+devtest: srctree-check sourcetest
+ tclsh $(TOP)/test/testrunner.tcl mdevtest
mdevtest:
tclsh $(TOP)/test/testrunner.tcl mdevtest
@@ -972,10 +981,19 @@ mdevtest:
quicktest: ./testfixture$(EXE)
./testfixture$(EXE) $(TOP)/test/extraquick.test $(TESTOPTS)
-# The default test case. Runs most of the faster standard TCL tests,
-# and fuzz tests, and sqlite3_analyzer and sqldiff tests.
-test: fuzztest sourcetest $(TESTPROGS) tcltest
+# Validate that various generated files in the source tree
+# are up-to-date.
+#
+srctree-check: $(TOP)/tool/srctree-check.tcl
+ tclsh $(TOP)/tool/srctree-check.tcl
+# Try to run tests on whatever options are specified by the
+# environment variables. The SQLite developers seldom use this target.
+# Instead# they use "make devtest" which runs tests on a standard set of
+# options regardless of how SQLite is configured. This "test"
+# target is provided for legacy only.
+#
+test: fuzztest sourcetest $(TESTPROGS) tcltest
# Run a test using valgrind. This can take a really long time
# because valgrind is so much slower than a native machine.
diff --git a/manifest b/manifest
index 72b2ae7a59..572b7f646c 100644
--- a/manifest
+++ b/manifest
@@ -1,12 +1,12 @@
-C Merge\sthe\slatest\strunk\senhancements\sinto\sthe\swal2\sbranch.
-D 2024-08-16T18:58:50.275
+C Merge\sall\sthe\slatest\strunk\senhancements\sinto\sthe\swal2\sbranch.
+D 2024-08-29T23:33:17.446
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
-F Makefile.in baf73332b7f1eb44791fbfed4d944bea6d12a818d78d66addac97df36885c9e3
+F Makefile.in 51d1e156dc555436dae0684fe63392a1ceb42ffa9a872973a0ac215971aef7a6
F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6
-F Makefile.msc f430e06739c5747ccf741f2307b673f9e22b6845e49ffac7bedbfb371f239bfd
-F README.md 6358805260a03ebead84e168bbf3740ddf3f683b477e478567186aa7afb490d3
+F Makefile.msc bfea4420b3fd32d5022a63307df77932cff1ba3853aa53b5545d0de42a744d34
+F README.md 5b678e264236788390d11991f2c0052bd73f19790173883fc56d638bcb849154
F VERSION 0db40f92c04378404eb45bff93e9e42c148c7e54fd3da99469ed21e22411f5a6
F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
F art/icon-243x273.gif 9750b734f82fdb3dc43127753d5e6fbf3b62c9f4e136c2fbf573b2f57ea87af5
@@ -17,7 +17,7 @@ F art/sqlite370.jpg d512473dae7e378a67e28ff96a34da7cb331def2
F autoconf/INSTALL 83e4a25da9fd053c7b3665eaaaf7919707915903
F autoconf/Makefile.am adedc1324b6a87fdd1265ddd336d2fb7d4f36a0e77b86ea553ae7cc4ea239347
F autoconf/Makefile.fallback 22fe523eb36dfce31e0f6349f782eb084e86a5620b2b0b4f84a2d6133f53f5ac
-F autoconf/Makefile.msc 7ac6c331fc3b8aa57b6782db995b8c0e49230352decd4e2662fd07c06a9ed623
+F autoconf/Makefile.msc 2aced6442addab13ed115696eba28d9ed29caa3dd604a31392c2c7a5da301492
F autoconf/README.first 6c4f34fe115ff55d4e8dbfa3cecf04a0188292f7
F autoconf/README.txt 42cfd21d0b19dc7d5d85fb5c405c5f3c6a4c923021c39128f6ba685355d8fd56
F autoconf/configure.ac ec7fa914c5e74ff212fe879f9bb6918e1234497e05facfb641f30c4d5893b277
@@ -35,8 +35,8 @@ F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea0034
F autoconf/tea/win/rules.vc 7b3bb2ef32ade0f3f14d951231811678722725e3bca240dd9727ae0dfe10f6a5
F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6
F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559
-F configure 2e5058c2ba2c6b50a01cf50c158180c4ec969b2068c2e361dfbfc875ec3f9d1c x
-F configure.ac b7e26a699a2ffc5eb7a25636f32755c27bbb665777b68ef35a90c1ee9bd9d469
+F configure 49523f0a070b583cea040d26eff53a65fb0893eca4663b1343a4d5a9a964da53 x
+F configure.ac a100ebf7a07f5dedd319ef547dd467d1676ed059b85a7877aa9c44ac309f7000
F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad
F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd
F doc/compile-for-windows.md e8635eea9153dcd6a51fd2740666ebc4492b3813cb1ac31cd8e99150df91762d
@@ -54,8 +54,8 @@ F ext/README.md fd5f78013b0a2bc6f0067afb19e6ad040e89a10179b4f6f03eee58fac5f169bd
F ext/async/README.txt e12275968f6fde133a80e04387d0e839b0c51f91
F ext/async/sqlite3async.c 6f247666b495c477628dd19364d279c78ea48cd90c72d9f9b98ad1aff3294f94
F ext/async/sqlite3async.h 46b47c79357b97ad85d20d2795942c0020dc20c532114a49808287f04aa5309a
-F ext/consio/console_io.c b4885dfea71ed583315de8f0792a29d5fc7c7460b4a26c0aebe5cda5da8b38f8 x
-F ext/consio/console_io.h 0548b83d7c4b7270ad544a67f2bb90cebc519637fa39b1838df4744cf0d87646
+F ext/consio/console_io.c 6e02dea912a49f55785b0027fe77960aafee6c236307c23f82ec86a69f1a2001 x
+F ext/consio/console_io.h b5ebe34aa15b357621ebbea3d3f2e2b24750d4280b5802516409e23947fd9ee5
F ext/expert/README.md b321c2762bb93c18ea102d5a5f7753a4b8bac646cb392b3b437f633caf2020c3
F ext/expert/expert.c d548d603a4cc9e61f446cc179c120c6713511c413f82a4a32b1e1e69d3f086a4
F ext/expert/expert1.test 661f873fd451127edf822ef0d520088faa319135f6a15bd10be6801ac284ac9b
@@ -90,29 +90,29 @@ F ext/fts3/tool/fts3cov.sh c331d006359456cf6f8f953e37f2b9c7d568f3863f00bb5f7eb87
F ext/fts3/tool/fts3view.c 413c346399159df81f86c4928b7c4a455caab73bfbc8cd68f950f632e5751674
F ext/fts3/unicode/CaseFolding.txt 8c678ca52ecc95e16bc7afc2dbf6fc9ffa05db8c
F ext/fts3/unicode/UnicodeData.txt cd07314edb62d49fde34debdaf92fa2aa69011e7
-F ext/fts3/unicode/mkunicode.tcl d5aebf022fa4577ee8cdf27468f0d847879993959101f6dbd6348ef0cfc324a7
+F ext/fts3/unicode/mkunicode.tcl 63db9624ccf70d4887836c320eda93ab552f21008f3be7ede551eac3ead62baa
F ext/fts3/unicode/parseunicode.tcl a981bd6466d12dd17967515801c3ff23f74a281be1a03cf1e6f52a6959fc77eb
-F ext/fts5/extract_api_docs.tcl bc3a0ca78be7d3df08e7602c00ca48021ebae40682d75eb001bfdf6e54ffb44e
-F ext/fts5/fts5.h 6b49ce6eb2e395e7fd84557b21d32f5de8041f2fada4c617e481e99427e24b6e
-F ext/fts5/fts5Int.h 41fb3a2dd40e818cc96c6f4176dbdf2aaa8f57043cfc9a8f2676e7e6a72ad764
-F ext/fts5/fts5_aux.c 4584e88878e54828bf7d4d0d83deedd232ec60628b7731be02bad6adb62304b1
+F ext/fts5/extract_api_docs.tcl 009cf59c77afa86d137b0cca3e3b1a5efbe2264faa2df233f9a7aa8563926d15
+F ext/fts5/fts5.h efaaac0df3d3bc740383044c144b582f47921aafa21d7b10eb98f42c24c740b0
+F ext/fts5/fts5Int.h 26a71a09cefa4ef6b4516b204ed48da3e1380970a19b3482eea7c5d805655360
+F ext/fts5/fts5_aux.c 65a0468dd177d6093aa9ae1622e6d86b0136b8d267c62c0ad6493ad1e9a3d759
F ext/fts5/fts5_buffer.c 0eec58bff585f1a44ea9147eae5da2447292080ea435957f7488c70673cb6f09
-F ext/fts5/fts5_config.c 68cb87a49215f8e7028000b681df4057c430a4a6afbd676463886da94c9e1c37
-F ext/fts5/fts5_expr.c 4b7734db98393d6f7fbc5c9c71ebcabe70110f7df08f6b136d096a1eaee0f56a
+F ext/fts5/fts5_config.c 353d2a0d12678cae6ab5b9ce54aed8dac0825667b69248b5a4ed81cbefc109ea
+F ext/fts5/fts5_expr.c 9a56f53700d1860f0ee2f373c2b9074eaf2a7aa0637d0e27a6476de26a3fee33
F ext/fts5/fts5_hash.c adda4272be401566a6e0ba1acbe70ee5cb97fce944bc2e04dc707152a0ec91b1
F ext/fts5/fts5_index.c eb9a0dda3bc6ef969a6be8d2746af56856e67251810ddba08622b45be8477abe
-F ext/fts5/fts5_main.c 6ec7a7d005c632d86e510ddfaca56b197a5b20b61848415764b91bd27d1e4f84
-F ext/fts5/fts5_storage.c 1d7e08d4331da2f3f7e78e70eef2ed6a013d91ba16175c651adbc5ad672235aa
-F ext/fts5/fts5_tcl.c 5ca3e3e35010d326f5b821a563e4fcde3913e052935f5c2c72c264122a26b48f
+F ext/fts5/fts5_main.c 1fddb53f495425d9314c74b30c5848a9dd254be0e5f445bfe38292d5ab21c288
+F ext/fts5/fts5_storage.c 9a9b880be12901f1962ae2a5a7e1b74348b3099a1e728764e419f75d98e3e612
+F ext/fts5/fts5_tcl.c 4db9258a7882c5eac0da4433042132aaf15b87dd1e1636c7a6ca203abd2c8bfe
F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee
F ext/fts5/fts5_test_tok.c 3cb0a9b508b30d17ef025ccddd26ae3dc8ddffbe76c057616e59a9aa85d36f3b
-F ext/fts5/fts5_tokenize.c 63ebe9057ed3f4dfc49944bc4aee3d3b745cc2faff73bc152ed3554ed3bf9cf4
-F ext/fts5/fts5_unicode2.c eca63dbc797f8ff0572e97caf4631389c0ab900d6364861b915bdd4735973f00
+F ext/fts5/fts5_tokenize.c ae9c4fa93174ef06ffc138bd4280a1c37f7e13624d3d2706aad4b80573f23c41
+F ext/fts5/fts5_unicode2.c 6f9b0fb79a8facaed76628ffd4eb9c16d7f2b84b52872784f617cf3422a9b043
F ext/fts5/fts5_varint.c e64d2113f6e1bfee0032972cffc1207b77af63319746951bf1d09885d1dadf80
F ext/fts5/fts5_vocab.c e4830b00809e5da53bc10f93adc59e321407b0f801c7f4167c0e47f5552267e0
F ext/fts5/fts5parse.y eb526940f892ade5693f22ffd6c4f2702543a9059942772526eac1fde256bb05
F ext/fts5/mkportersteps.tcl 5acf962d2e0074f701620bb5308155fa1e4a63ba
-F ext/fts5/test/fts5_common.tcl bc33c6cc65e5d390f28a68aeeb3a526dadd2c3a947d2466ee1986c1a4002df56
+F ext/fts5/test/fts5_common.tcl c5aa7cf7148b6dcffb5b61520ae18212baf169936af734ab265143f59db328fe
F ext/fts5/test/fts5aa.test 015c81b84d53bfcedd77d624202c8b02e9f0cbbb4b51688e3a9c9f90bccbb4ac
F ext/fts5/test/fts5ab.test 4bdb619fee409e11417e8827e320b857e42e926a01a0408fc9f143ec870a6ced
F ext/fts5/test/fts5ac.test 4a73626de86f3d17c95738034880c4f0de8d54741fb943d819b528373657e59b
@@ -120,11 +120,11 @@ F ext/fts5/test/fts5ad.test 058e616612964e61d19f70295f0e6eaedceb4b29b1fbf4f85961
F ext/fts5/test/fts5ae.test 3d49edbd50bb0684199a2e7568aeb30d1d29718f5c0f61751983740fa836d15f
F ext/fts5/test/fts5af.test ae81f08b8da4c5f9b3ec1ef538a4ab6b7c278e92fa9058d6dc5d842c5d9771b9
F ext/fts5/test/fts5ag.test 6667807b5d3fbf460892e756763fbe3d87a2fffe345a06514ba010ca6f6641f7
-F ext/fts5/test/fts5ah.test ac327281c8910cf9b85738a2655003fd0c6a8a76189ef34f3d086b8f9e54263b
+F ext/fts5/test/fts5ah.test e1f01314b35745a30e1b494b46045b82005d71cae74f1ebd9f1338566b77f9fc
F ext/fts5/test/fts5ai.test cbe26d78030998f535bc103f37915350b137a822c71a9db439a077d7666a3539
F ext/fts5/test/fts5aj.test 53c8508dab4acca3e691a4c51eca4b3b018319ab8635e540103d5bbdc91543c9
F ext/fts5/test/fts5ak.test 25e2f8afdcff30d98ca9dee8c5cacca2f26db17501c9401f16d99ee036f70e8d
-F ext/fts5/test/fts5al.test 842c50fd9b287e3fa988dfcab436b27c432866f7406a56aaf3c67f316952cc02
+F ext/fts5/test/fts5al.test f0e655606771b2b5dbaf70e7f0044d560257cf3531d5eea40df58d0d7add8c39
F ext/fts5/test/fts5alter.test ebbee06419c2d3cee5ef7ebb5ba6a9996f1aa374035361c0acd37368cc5f64f3
F ext/fts5/test/fts5auto.test 2278de323172ced485d2844cb1357d00036ac1665f27e70fa1a48ce57bf31c2c
F ext/fts5/test/fts5aux.test 27210687338133b1e9bc0dd669322fca59fd432439f40b126895e2d7c2f899d6
@@ -133,7 +133,8 @@ F ext/fts5/test/fts5auxdata.test 372549088ff792655f73e62b9dfaf4863ce74f5e604c06c
F ext/fts5/test/fts5bigid.test 2860854c2561a57594192b00c33a29f91cb85e25f3d6c03b5c2b8f62708f39dd
F ext/fts5/test/fts5bigpl.test 8f09858aab866c33593560e6480b2b6975ae7ff29ca32ad7b77e2da61402f8ef
F ext/fts5/test/fts5bigtok.test 541119e616c637caea925a8c028c37c2c29e94383e00aa2f9198d530724b6e36
-F ext/fts5/test/fts5cat.test daba0b80659460b0cb60bd1f40b402478a761fe7ea414c3c94c2be25568cc33a
+F ext/fts5/test/fts5blob.test caa33369e93e99ff494cd1103506ae34c5afbc0bcc369ed5e58e135144e33689
+F ext/fts5/test/fts5cat.test bf67dd335f964482ee658287521b81e2b88697b45eb7f73933e15f198ed447cb
F ext/fts5/test/fts5circref.test f880dfd0d99f6fb73b88ccacb0927d18e833672fd906cc47d6b4e529419eaa62
F ext/fts5/test/fts5colset.test 544f4998cdbfe06a3123887fc0221612e8aa8192cdaff152872f1aadb10e6897
F ext/fts5/test/fts5columnsize.test 0af91d63985afdf663455d4b572b935238380140d74079eac362760866d3297b
@@ -141,18 +142,19 @@ F ext/fts5/test/fts5config.test 017daf10d2642496e97402baa0134de8b5b46b9c37e53c22
F ext/fts5/test/fts5conflict.test bf6030a77dbb1bedfcc42e589ed7980846c995765d77460551e448b56d741244
F ext/fts5/test/fts5connect.test 08030168fc96fc278fa81f28654fb7e90566f33aff269c073e19b3ae9126b2f4
F ext/fts5/test/fts5content.test d5c0c2142e64cb305f0968de70c01f8e59dbc3ecc56520c22e739e5dd99ea3bb
-F ext/fts5/test/fts5contentless.test b107465f8cd27dde6313b9c60b61d7158a7753b9c663c5c553695f826bb3c0a5
+F ext/fts5/test/fts5contentless.test 606f063b29ba0f46d4b79aa36cdd1ef4dab5de53eae8c881d731af75a4894aca
F ext/fts5/test/fts5contentless2.test 70ffe6c611d8f278240da56734df8a77948f04e2739b358439e9bdcf56ced35f
F ext/fts5/test/fts5contentless3.test 75eaae5ad6b284ee447788943974d323228f27cc35a1681da997135cff95bc6a
F ext/fts5/test/fts5contentless4.test ec34dc69ef474ca9997dae6d91e072906e0e9a5a4b05ea89964c863833b6eff8
F ext/fts5/test/fts5contentless5.test 40cdcb4fe751672450829c5a96bd32c25fc2f6076279dd2ce5c58ac9a390132a
-F ext/fts5/test/fts5corrupt.test a9bda1ded5112ebf1ee85c5381bd1fe8974952e2523cede4d5072804d2011503
+F ext/fts5/test/fts5corrupt.test 6485f721b88ba355ca5d701e7ee87a4efa3ea578d8e6adb26f51ef956c8328bd
F ext/fts5/test/fts5corrupt2.test 335911e3f68b9625d850325f9e29a128db3f4276a8c9d4e32134580da8f924c4
-F ext/fts5/test/fts5corrupt3.test b5f35d72af85b1d5a092b3d5e437f7944d142dd0b0c87b928fd0436a0aec6987
+F ext/fts5/test/fts5corrupt3.test 4fc3bf129f1616bea00884a23fd9d7b0e46d01791d2b57fe8d68ac36e8d3ff7c
F ext/fts5/test/fts5corrupt4.test dc08d19f5b8943e95a7778a7d8da592042504faf18dd93f68f7d7a0d7d7dd733
F ext/fts5/test/fts5corrupt5.test 11b47126f5772cc37b67e3e8b2ed05895c4d07c05338bc07e4eea225bfe32c76
F ext/fts5/test/fts5corrupt6.test 2d72db743db7b5d9c9a6d0cfef24d799ed1aa5e8192b66c40e871a37ed9eed06
F ext/fts5/test/fts5corrupt7.test 4e830875c33b9ea3c4cf1ba71e692b63893cbb4faae8c69b1071889dc26e211c
+F ext/fts5/test/fts5corrupt8.test b81d802e41631e98100f49a1aadeeffef860e30a62d6ed7d743c2797c477239e
F ext/fts5/test/fts5delete.test 619295b20dbc1d840b403ee07c878f52378849c3c02e44f2ee143b3e978a0aa7
F ext/fts5/test/fts5detail.test 54015e9c43ec4ba542cfb93268abdf280e0300f350efd08ee411284b03595cc4
F ext/fts5/test/fts5determin.test 1b77879b2ae818b5b71c859e534ee334dac088b7cf3ff3bf76a2c82b1c788d11
@@ -160,11 +162,11 @@ F ext/fts5/test/fts5dlidx.test a7c42b0a74dc7c8aa1a46d586e0aadda4b6cc42c24450f8d3
F ext/fts5/test/fts5doclist.test b7cb84758504519746957802db9cd31187bb4e0028b89d9087ba06e26cc4155f
F ext/fts5/test/fts5ea.test cefdf66024550fa7920c03395c71ce5046235ed1a1a7a469d79b19e7aad5afb5
F ext/fts5/test/fts5eb.test 401f756fdb77083aeba8b696c1e0ad4d834c39dbd6f17e492bb55a2ad64b4296
-F ext/fts5/test/fts5expr.test 7e1b2d075b63b727a624a378c2c09f94296a93dc4ae968aad67f8d9f3810c266
+F ext/fts5/test/fts5expr.test c7e208813df7a90badc856fde3796da79569b39382e0fdb43042127f3b8e06a7
F ext/fts5/test/fts5fault1.test d28a65caee75db6897c3cf1358c5230d3bb2a3bf7fb31062c19c7e5382b3d2bd
F ext/fts5/test/fts5fault2.test 69c8fdbef830cd0d450908d4504d5bb86609e255af99c421c20a0756251fe344
F ext/fts5/test/fts5fault3.test da2f9e3e56ff5740d68ebdd6877c97089e7ed28ddff28a0da87a6afea27e5522
-F ext/fts5/test/fts5fault4.test 87a10d0caee57da587c7588b0c8d25d2930197399b4812ad1e4d574c75324cee
+F ext/fts5/test/fts5fault4.test a5c0e849127c24e1751bc453a817f09a1b8d460e75f9ae4764017e216a870db3
F ext/fts5/test/fts5fault5.test a336e4e11847de24c9497f80cce18e00bb3fab7fb11f97d04eb9af898900a762
F ext/fts5/test/fts5fault6.test 40f49976c6ca8927bf7d65d0b8df46009d7ea172e1d4050b294610e7ea0a2979
F ext/fts5/test/fts5fault7.test 0acbec416edb24b8881f154e99c31e9ccf73f539cfcd164090be139e9e97ed4c
@@ -177,6 +179,7 @@ F ext/fts5/test/fts5faultE.test 844586ce71dab4be85bb86880e87b624d089f851654cd22e
F ext/fts5/test/fts5faultF.test 4abef99f86e99d9f0c6460dd68c586a766b6b9f1f660ada55bf2e8266bd1bbc1
F ext/fts5/test/fts5faultG.test 0544411ffcb3e19b42866f757a8a5e0fb8fef3a62c06f61d14deebc571bb7ea9
F ext/fts5/test/fts5faultH.test 2b2b5b8cb1b3fd7679f488c06e22af44107fbc6137eaf45b3e771dc7b149312d
+F ext/fts5/test/fts5faultI.test ae4b83ac953200bd7b66d53038f7d6a4fc29cd64831b8e1795538babcea7c638
F ext/fts5/test/fts5first.test bfd685b96905bf541d99d8644e0a7219d1d833455a08ab64e344071a613b6ba9
F ext/fts5/test/fts5full.test 97d263c1072f4a560929cca31e70f65d2ae232610e17e6affcf7e979df59547b
F ext/fts5/test/fts5fuzz1.test 238d8c45f3b81342aa384de3e581ff2fa330bf922a7b69e484bbc06051a1080e
@@ -186,10 +189,11 @@ F ext/fts5/test/fts5interrupt.test 20d04204d3e341b104c0c24a41596b6393a3a81eba104
F ext/fts5/test/fts5lastrowid.test f36298a1fb9f988bde060a274a7ce638faa9c38a31400f8d2d27ea9373e0c4a1
F ext/fts5/test/fts5leftjoin.test c0b4cafb9661379e576dc4405c0891d8fcc2782680740513c4d1fc114b43d4ad
F ext/fts5/test/fts5limits.test 8ab67cf5d311c124b6ceb0062d0297767176df4572d955fce79fa43004dff01c
+F ext/fts5/test/fts5locale.test 797cf6f5e017462ab11313ce884b9f1df8ff063811e74ef42190cd19ed6b600b
F ext/fts5/test/fts5matchinfo.test 877520582feb86bbfd95ab780099bcba4526f18ac75ee34979144cf86ba3a5a3
F ext/fts5/test/fts5merge.test 2654df0bcdb2d117c2d38b6aeb0168061be01c643f9e9194b36c43a2970e8082
F ext/fts5/test/fts5merge2.test 3ebad1a59d6ad3fb66eff6523a09e95dc6367cbefb3cd73196801dea0425c8e2
-F ext/fts5/test/fts5misc.test b1682a40061bc58dcc62bbad48938fc5214d2ac6a868a8899c0c3d1930f1115d
+F ext/fts5/test/fts5misc.test 8c3cc771f773dc4bb4973620c51e7729e324ca2cc80eb8894f1c2c605e361f0b
F ext/fts5/test/fts5multi.test a15bc91cdb717492e6e1b66fec1c356cb57386b980c7ba5af1915f97fe878581
F ext/fts5/test/fts5multiclient.test 5ff811c028d6108045ffef737f1e9f05028af2458e456c0937c1d1b8dea56d45
F ext/fts5/test/fts5near.test 33d60867581066e5db7016deb5d651628125d7ff4e0233a88175aa5b65874c74
@@ -197,7 +201,7 @@ F ext/fts5/test/fts5onepass.test f9b7d9b2c334900c6542a869760290e2ab5382af8fbd618
F ext/fts5/test/fts5optimize.test 264b9101721c17d06d1d174feb743fda3ddc89fad41dee980fef821428258e47
F ext/fts5/test/fts5optimize2.test 795d4ae5f66a7239cf8d5aef4c2ea96aeb8bcd907bd9be0cfe22064fc71a44ed
F ext/fts5/test/fts5optimize3.test 1653029284e10e0715246819893ba30565c4ead0d0fc470adae92c353ea857d3
-F ext/fts5/test/fts5origintext.test 87c34c78f201b1e22ac93ac6bbe6196dde59f0a7266b1aeb938604a0eb9d5552
+F ext/fts5/test/fts5origintext.test 2015f69bc8abd111152a8e66211fd2d45026378001e07c054159aa4f84e6691d
F ext/fts5/test/fts5origintext2.test f4505ff79bf7369f2b8b10b9cef7476049d844e20b37f29cad3a8b8d5ac6f9ba
F ext/fts5/test/fts5origintext3.test 45c33cf0c91a9ca0e36d298462db3edc7c8fe45fd185649a9dbfd66bb670058b
F ext/fts5/test/fts5origintext4.test 0d3ef0a8038f471dbc83001c34fe5f7ae39b571bfc209670771eb28bc0fc50e8
@@ -221,9 +225,9 @@ F ext/fts5/test/fts5secure4.test 0d10a80590c07891478700af7793b232962042677432b98
F ext/fts5/test/fts5secure5.test c07a68ced5951567ac116c22f2d2aafae497e47fe9fcb6a335c22f9c7a4f2c3a
F ext/fts5/test/fts5secure6.test 74bf04733cc523bccca519bb03d3b4e2ed6f6e3db7c59bf6be82c88a0ac857fd
F ext/fts5/test/fts5secure7.test fd03d0868d64340a1db8615b02e5508fea409de13910114e4f19eaefc120777a
-F ext/fts5/test/fts5secure8.test e68c0ac4447f415ff3e4e82531e99548289286f9f3a29c8cd53036113fe28602
+F ext/fts5/test/fts5secure8.test 808ade9d172ed07b24b85c57dd53b6d2b1aba018b4e634d267ce572221de80e0
F ext/fts5/test/fts5securefault.test c34a28c7cd2f31a8b8907563889e1329a97da975c08df2d951422bcef8e2ebc5
-F ext/fts5/test/fts5simple.test 847fb828262328744733847dc76d6b5d4a6bd4c5d9b282cb819f6504340e061a
+F ext/fts5/test/fts5simple.test 302cdb4f8a3350b091f4f1bccd82d05610428657f6f9e81c17703ba48267ec40
F ext/fts5/test/fts5simple2.test d10d963a357b8ec77b99032e4c816459b4dbdb1f6eee25eada7ef3ed245cb2dc
F ext/fts5/test/fts5simple3.test 146ec3dc8f5763d6212641c9f0a2f1cba41679353d2add7b963beceb115dc7f4
F ext/fts5/test/fts5synonym.test becc8cea6cfc958a50b30c572c68cbfdf7455971d0fe988202ce67638d2c6cf6
@@ -232,12 +236,13 @@ F ext/fts5/test/fts5tok1.test 1f7817499f5971450d8c4a652114b3d833393c8134e32422d0
F ext/fts5/test/fts5tok2.test dcacb32d4a2a3f0dd3215d4a3987f78ae4be21a2
F ext/fts5/test/fts5tokenizer.test 7937cec672b148223fff8746d21d3e7ed0965fd7caf35ccdc888a005bb452f98
F ext/fts5/test/fts5tokenizer2.test ddb8b10fbe4b84b2a75812671f127774c1d2e3e2bf82d2e0e4f0bb1cd8a2b2d6
-F ext/fts5/test/fts5trigram.test be914555deb8504dde682bd5aa343d00c4da37dfad20709a5bac30d5f97f2ef5
+F ext/fts5/test/fts5tokenizer3.test eea778f7bb7024c3e904e28915f9d53286141671b138722148be22a9c758bdc3
+F ext/fts5/test/fts5trigram.test fb9ee982edd76280ce979905a2251081cd04ae4c470248bd5d391b2d096430ab
F ext/fts5/test/fts5trigram2.test 6fde9de7f63a6b4aa18dc731be56dbd6be4e755c9b13dcd55479e200d1df0e61
F ext/fts5/test/fts5ubsan.test 9a2dcf399dc8d0e0de661f0d93884d1d27e5b7f0693cfceb97dd24d818df5dd2
F ext/fts5/test/fts5umlaut.test a42fe2fe6387c40c49ab27ccbd070e1ae38e07f38d05926482cc0bccac9ad602
F ext/fts5/test/fts5unicode.test 41898f7e476e6515cd4b737c02a442cda5a580a74509788aa9072a2074948e0e
-F ext/fts5/test/fts5unicode2.test a5c38179b311a188b24376772309389b073c996f52b79bb9ca760a19e62043ea
+F ext/fts5/test/fts5unicode2.test 3bbd30152f9f760bf13886e5b1e5ec23ff62f56758ddda5d9c775a6082fb4c7c
F ext/fts5/test/fts5unicode3.test f4891a3dac3b49c3d7c0fdb29566e9eb0ecff35263370c89f9661b1952b20818
F ext/fts5/test/fts5unicode4.test 728c8f0caafb05567f524ad313d9f8b780fa45987b8a8df04eff87923c74b4d0
F ext/fts5/test/fts5unindexed.test 168838d2c385e131120bbf5b516d2432a5fabc4caa2259c932e1d49ae209a4ae
@@ -415,7 +420,7 @@ F ext/misc/regexp.c 4bdd0045912f81c84908bd535ec5ad3b1c8540b4287c70ab840709636240
F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c
F ext/misc/rot13.c 51ac5f51e9d5fd811db58a9c23c628ad5f333c173f1fc53c8491a3603d38556c
F ext/misc/scrub.c 2a44b0d44c69584c0580ad2553f6290a307a49df4668941d2812135bfb96a946
-F ext/misc/series.c d96e5aac21658c6b5d54f918ac140460ec7197734c1a4fba806950831a7b1e7a
+F ext/misc/series.c a6089b5e8e3002bd1e5d9877cee6aead0b9a6426e406c09a399817db9e9ae823
F ext/misc/sha1.c 4011aef176616872b2a0d5bccf0ecfb1f7ce3fe5c3d107f3a8e949d8e1e3f08d
F ext/misc/shathree.c 1821d90a0040c9accdbe3e3527d378d30569475d758aa70f6848924c0b430e8c
F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52
@@ -682,7 +687,7 @@ F ext/wasm/wasmfs.make 8a4955882aaa0783b3f60a9484a1f0f3d8b6f775c0fcd17c082f31966
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0
-F main.mk f389096383355ec6ba23572aef9e1785b1207af53fec3f2eb85ac4b1c30717b0
+F main.mk ea649b141bd1c6479a9243747937ea88e528c88ec885fdc661b15d469edcb8c3
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
F mptest/crash01.test 61e61469e257df0850df4293d7d4d6c2af301421
@@ -694,38 +699,38 @@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b
F sqlite3.1 acdff36db796e2d00225b911d3047d580cd136547298435426ce9d40347973cc
F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a
F sqlite_cfg.h.in baf2e409c63d4e7a765e17769b6ff17c5a82bbd9cbf1e284fd2e4cefaff3fcf2
-F src/alter.c bb663fddf1fe0e2e6d8758b2b7fb6374e7c057a6ca3955f37a48986806029765
+F src/alter.c aa93e37e4a36a0525bbb2a2aeda20d2018f0aa995542c7dc658e031375e3f532
F src/analyze.c 30bf40ec4208ead9e977bec017bccc8a9681820936e38ca5a4a7443100a6d5c5
-F src/attach.c cc9d00d30da916ff656038211410ccf04ed784b7564639b9b61d1839ed69fd39
-F src/auth.c 19b7ccacae3dfba23fc6f1d0af68134fa216e9040e53b0681b4715445ea030b4
+F src/attach.c 08235ab62ed5ccc93c22bf36e640d19effcd632319615851bccf724ec9341333
+F src/auth.c 4c1ea890e0069ad73bead5d17a5b12c34cfa4f1a24175c8147ea439b64be271c
F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523
F src/bitvec.c 9eac5f42c11914d5ef00a75605bb205e934f435c579687f985f1f8b0995c8645
F src/btmutex.c 79a43670447eacc651519a429f6ece9fd638563cf95b469d6891185ddae2b522
-F src/btree.c 58e22c98219b15b1846bc0ce82dd156bdf380a76904fd1feec402ae7a727bad9
+F src/btree.c 85af9a7a69b6c64870479c6493769e98a88a0d952f3035e7d75505a45b75a02c
F src/btree.h 55066f513eb095db935169dab1dc2f7c7a747ef223c533f5d4ad4dfed346cbd0
F src/btreeInt.h 98aadb6dcb77b012cab2574d6a728fad56b337fc946839b9898c4b4c969e30b6
-F src/build.c 237ccc0290d131d646be722f418e92ee0a38043aee25e7dfdc75f8ce5b3abe4e
+F src/build.c 3a1840d9d171ce2d24f4c1f7acda7266ab796c664290c1acba65ff98ce2bd01e
F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490
F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
F src/ctime.c 1842f79c04abcf79cb3037e5dc110bc76f91951bb4b4fc4a41bcbd370371b39c
-F src/date.c 13dd752847afb32ed70510ad7345a5b9c841f51ad904dba5d010f1fa3a6a324e
+F src/date.c 89ce1ff20512a7fa5070ba6e7dd5c171148ca7d580955795bf97c79c2456144a
F src/dbpage.c 80e46e1df623ec40486da7a5086cb723b0275a6e2a7b01d9f9b5da0f04ba2782
-F src/dbstat.c 3b677254d512fcafd4d0b341bf267b38b235ccfddbef24f9154e19360fa22e43
-F src/delete.c cb766727c78e715f9fb7ec8a7d03658ed2a3016343ca687acfcec9083cdca500
-F src/expr.c fe958028b36af640b70b2174354c044f75b8c4a4645c921592122aa2a022083a
+F src/dbstat.c 73362c0df0f40ad5523a6f5501224959d0976757b511299bf892313e79d14f5c
+F src/delete.c 444c4d1eaac40103461e3b6f0881846dd3aafc1cec1dd169d3482fa331667da7
+F src/expr.c 6d5f2c38fe3ec06a7eac599dac822788b36064124e20112a844e9cd5156cb239
F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
-F src/fkey.c 852f93c0ef995e0c2b8983059a2b97151c194cc8259e21f5bc2b7ac508348c2a
+F src/fkey.c 928ed2517e8732113d2b9821aa37af639688d752f4ea9ac6e0e393d713eeb76f
F src/func.c 1f61e32e7a357e615b5d2e774bee563761fce4f2fd97ecb0f72c33e62a2ada5f
F src/global.c 61a419dd9e993b9be0f91de4c4ccf322b053eb829868e089f0321dd669be3b90
F src/hash.c 9ee4269fb1d6632a6fecfb9479c93a1f29271bddbbaf215dd60420bcb80c7220
F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51
F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6
F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
-F src/insert.c 8ff11e9e54c5fc1fe89707b3d41cf44ad2822f712bd3b5da68338ea42518847e
+F src/insert.c f8d1a0f8ee258411009c6b7f2d93170e351bd19f5ad89d57e1180644297cbe70
F src/json.c 5b6a1d6015997b9ee848a32948720bdb26a0ef2de5a2127ebf7355ce66dbdc0d
F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa
F src/loadext.c 7432c944ff197046d67a1207790a1b13eec4548c85a9457eb0896bb3641dfb36
-F src/main.c 8a59d297ec77e6b78550433bfccb95a1b26f2fb69aaaf233206e21579a1cfcc1
+F src/main.c a520c325c7400b249242945f2c602acb19662c40f5db1c04d88664cfaa8b85ac
F src/malloc.c 410e570b30c26cc36e3372577df50f7a96ee3eed5b2b161c6b6b48773c650c5e
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2
@@ -746,28 +751,28 @@ F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63
F src/os_common.h 6c0eb8dd40ef3e12fe585a13e709710267a258e2c8dd1c40b1948a1d14582e06
F src/os_kv.c 4d39e1f1c180b11162c6dc4aa8ad34053873a639bac6baae23272fc03349986a
F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d872107
-F src/os_unix.c 2ea8d3ed496b8d1f9332a9505653424e5464fd797ea9d91f8e2e62f9dd0298d0
+F src/os_unix.c 6e3e4fc75904ff85184091dbab996e6e35c1799e771788961cc3b4fcbe8f852c
F src/os_win.c 6ff43bac175bd9ed79e7c0f96840b139f2f51d01689a638fd05128becf94908a
F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
F src/pager.c d2f77fe2133a3c33ee5552bde00efec2afae790a75e7903aeff3bdb193f30a8a
F src/pager.h fc0c95d27f2aefffcb39b4373b3557e89e1b4b9a36b183cc04d6f22002bf0ad2
-F src/parse.y 318ef86fbe358b1a93262a42e152f37b97b3fddae8d319dffbd24ce2300f6c88
+F src/parse.y 5972b7d00af4c8d96fdad781af1ea1d5d51fc3b907ad61bda60e49503274e5ed
F src/pcache.c 588cc3c5ccaaadde689ed35ce5c5c891a1f7b1f4d1f56f6cf0143b74d8ee6484
F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5
F src/pcache1.c 49516ad7718a3626f28f710fa7448ef1fce3c07fd169acbb4817341950264319
F src/pragma.c d206a9a757faceb812183faa2ed328145795c8067a44f981b0478736f68d4144
F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7
F src/prepare.c d99931f45416652895e502328ca49fe782cfc4e1ebdcda13b3736d991ebf42ce
-F src/printf.c 8b250972305e14b365561be5117ed0fd364e4fd58968776df1ce64c6280b90f9
+F src/printf.c 6a87534ebfb9e5346011191b1f3a7ebc457f5938c7e4feeea478ecf53f6a41b2
F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
-F src/resolve.c 0aee8a2e5340ba95a966917305dfaff5147fcad78d0839cd364b16e4746b8bcb
+F src/resolve.c 2c127880c0634962837f16f2f48a295e514357af959330cc038de73015d5b5e8
F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
-F src/select.c 6a95a2bffa6c09584dea99db5a7ae10c813305c09c92920ffc54f6eae2ba399e
-F src/shell.c.in 94571558b0fb28c37a5cf6dbd6ea27285341023a28a8cb5795cd2768fab67704
-F src/sqlite.h.in 1ad9110150773c38ebababbad11b5cb361bcd3997676dec1c91ac5e0416a7b86
+F src/select.c 4b14337a2742f0c0beeba490e9a05507e9b4b12184b9cd12773501d08d48e3fe
+F src/shell.c.in 40de636c1d90fb8a9ca7f49dc8f50d930f1b60736e73aca5eb37c4c7d0e47f9d
+F src/sqlite.h.in f07bff4225a1244efd604a0ffef81ed69f29d3dbaed7e22f906f26229ba3ca9e
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h 3f046c04ea3595d6bfda99b781926b17e672fd6d27da2ba6d8d8fc39981dcb54
-F src/sqliteInt.h 128b9004698cc79993d5369d7d1763deaf8bbf26e5e8931ec540707e5a7238df
+F src/sqliteInt.h 28c878bdf528879afefe1994ac007d094f8061f2fdacdc55d6055d7e9341151e
F src/sqliteLimit.h 6878ab64bdeb8c24a1d762d45635e34b96da21132179023338c93f820eee6728
F src/status.c cb11f8589a6912af2da3bb1ec509a94dd8ef27df4d4c1a97e0bcf2309ece972b
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
@@ -826,19 +831,19 @@ F src/test_window.c 6d80e11fba89a1796525e6f0048ff0c7789aa2c6b0b11c80827dc1437bd8
F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9
F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c
F src/tokenize.c 3f703cacdab728d7741e5a6ac242006d74fe1c2754d4f03ed889d7253259bd68
-F src/treeview.c 774838df4e25956ca34ff79bef150266412cfc2640620d04e22d5c8a55a98992
-F src/trigger.c 0858f75818ed1580332db274f1032bcc5effe567cb132df5c5be8b1d800ca97f
-F src/update.c 732404a04d1737ef14bb6ec6b84f74edf28b3c102a92ae46b4855438a710efe7
-F src/upsert.c 2e60567a0e9e8520c18671b30712a88dc73534474304af94f32bb5f3ef65ac65
+F src/treeview.c 88aa39b754f5ef7214385c1bbbdd2f3dc20efafeed0cf590e8d1199b9c6e44aa
+F src/trigger.c 0bb986a5b96047fd597c6aac28588853df56064e576e6b81ba777ef2ccaac461
+F src/update.c 0e01aa6a3edf9ec112b33eb714b9016a81241497b1fb7c3e74332f4f71756508
+F src/upsert.c 215328c3f91623c520ec8672c44323553f12caeb4f01b1090ebdca99fdf7b4f1
F src/utf.c f23165685a67b4caf8ec08fb274cb3f319103decfb2a980b7cfd55d18dfa855e
F src/util.c 5d1a0134cf4240648d1c6bb5cc8efaca0ea2b5d5c840985aec7e947271f04375
-F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104
-F src/vdbe.c 5ca2ffb6ed0c26f12a81822bdc782650d1e9fcb40b55ff37367f1d1bb0d4a935
+F src/vacuum.c b763b6457bd058d2072ef9364832351fd8d11e8abf70cbb349657360f7d55c40
+F src/vdbe.c f2e6668400689ed448c6865fc5748fcbbdc1ed0861e3773d10c69ffd7001d742
F src/vdbe.h c2549a215898a390de6669cfa32adba56f0d7e17ba5a7f7b14506d6fd5f0c36a
F src/vdbeInt.h 949669dfd8a41550d27dcb905b494f2ccde9a2e6c1b0b04daa1227e2e74c2b2c
F src/vdbeapi.c 80235ac380e9467fec1cb0883354d841f2a771976e766995f7e0c77f845406df
F src/vdbeaux.c cac65e6512c51f2a2419c53f41c8162b8344a99053bc77844d66536f5e4fd3fb
-F src/vdbeblob.c 13f9287b55b6356b4b1845410382d6bede203ceb29ef69388a4a3d007ffacbe5
+F src/vdbeblob.c 255be187436da38b01f276c02e6a08103489bbe2a7c6c21537b7aecbe0e1f797
F src/vdbemem.c 831a244831eaa45335f9ae276b50a7a82ee10d8c46c2c72492d4eb8c98d94d89
F src/vdbesort.c d0a3c7056c081703c8b6d91ad60f17da5e062a5c64bf568ed0fa1b5f4cae311f
F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823
@@ -847,12 +852,12 @@ F src/vtab.c 5fb499d20494b7eecaadb7584634af9afcb374cb0524912b475fcb1712458a1b
F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
F src/wal.c 7fe7aa272006e5070b31109757bb45d9e387d4df0c58a7ee56614764aaa39651
F src/wal.h 97b8a9903387401377b59507e86b93a148ef1ad4e5ce0f23659a12dcdce56af2
-F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2
-F src/where.c f5be664f3379c9f930696e339ec4ef4c1187af860cca727411156101fae6b677
-F src/whereInt.h 6444b888ce395cb80511284b8a73b63472d34247fcb1b125ee06a54fa6ae878e
-F src/wherecode.c c9cac0b0b8e809c5e7e79d7796918907fb685ad99be2aaa9737f9787aa47349c
-F src/whereexpr.c 7d0d34b42b9edfd8e8ca66beb3a6ef63fe211c001af54caf2ccbcd989b783290
-F src/window.c 1e40ffc509bae21e466f6106382d238e91eb73edd4ba10e66ca4fd7af2b96896
+F src/walker.c d5006d6b005e4ea7302ad390957a8d41ed83faa177e412f89bc5600a7462a014
+F src/where.c c046dd58c3410f7b7528e1e6317cb876398557bad346d568ed8562321a7d002d
+F src/whereInt.h a5d079c346a658b7a6e9e47bb943d021e02fa1e6aed3b964ca112112a4892192
+F src/wherecode.c 5172d647798134e7c92536ddffe7e530c393d79b5dedd648b88faf2646c65baf
+F src/whereexpr.c 44f41ae554c7572e1de1485b3169b233ee04d464b2ee5881687ede3bf07cacfa
+F src/window.c 499d48f315a09242dc68f2fac635ed27dcf6bbb0d9ab9084857898c64489e975
F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627
F test/affinity3.test f094773025eddf31135c7ad4cde722b7696f8eb07b97511f98585addf2a510a9
@@ -1062,6 +1067,7 @@ F test/date.test c8ff835023f2107b57ce7a45c92265d51c98a23fc93231e998f12d850831aad
F test/date2.test 7e12ec14aaf4d5e6294b4ba140445b0eca06ea50062a9c3a69c4ee13d0b6f8b1
F test/date3.test a1b77abf05c6772fe5ca2337cac1398892f2a41e62bce7e6be0f4a08a0e64ae5
F test/date4.test 75dc8401e8c0639a228cd26a6eaa4ff5ea8ccda912b9853d1c9462c476670e17
+F test/date5.test 14ba189bc4d03efc371dd5302e035764f6633355a3e13acb4a45e7b33530231e
F test/dbdata.test 042f49acff3438f940eeba5868d3af080ae64ddf26ae78f80c92bec3ca7d8603
F test/dbfuzz.c 73047c920d6210e5912c87cdffd9a1c281d4252e
F test/dbfuzz001.test 6c9a4622029d69dc38926f115864b055cb2f39badd25ec22cbfb130c8ba8e9c3
@@ -1438,7 +1444,7 @@ F test/memjournal2.test dbc2c5cb5f7b38950f4f6dc3e73fcecf0fcbed3fc32c7ce913bba164
F test/memleak.test 10b9c6c57e19fc68c32941495e9ba1c50123f6e2
F test/memsubsys1.test 86b8158752af9188ed5b32a30674a1ef71183e6bc4e6808e815cd658ca9058a6
F test/memsubsys2.test 774b93cb09ca50d1b759bb7c645baa2a9ce172edc3a3da67d5150a26a9fc2a08
-F test/merge1.test 2de6d6ef8d25402764b1aab49d8f9d7f89208c89a6674e437f76de4c812157b8
+F test/merge1.test 7dd9dc6838bcd0623a069485fe3a8dd498a051c16e1877cf84f506c0d6a29b43
F test/minmax.test fe638b55d77d2375531a8f549b338eafcd9adfbd2f72df37ed77d9b26ca0a71a
F test/minmax2.test cf9311babb6f0518d04e42fd6a42c619531c4309a9dd790a2c4e9b3bc595e0de
F test/minmax3.test cc1e8b010136db0d01a6f2a29ba5a9f321034354
@@ -1622,10 +1628,10 @@ F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8e
F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939
F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
F test/shell1.test 490bf9d0c7c9564fea318c46d49369f4690b825b584c9a544dbdccf61bc0babc
-F test/shell2.test 56da24128304c9ab67da2964cc80beff7b35761c446ec6e6e98bff2775b15026
+F test/shell2.test 01a01f76ed98088ce598794fbf5b359e148271541a8ddbf79d21cc353cc67a24
F test/shell3.test db1953a8e59d08e9240b7cc5948878e184f7eb2623591587f8fd1f1a5bd536d8
F test/shell4.test 522fdc628c55eff697b061504fb0a9e4e6dfc5d9087a633ab0f3dd11bcc4f807
-F test/shell5.test bafa4c0b67b7a8027e729970a625c9225cb7ef854acc4e52624c45074faaaddf
+F test/shell5.test 0e5f8ce08206b9998a778cfe1989e20e47839153c05af2da29198150172e22fc
F test/shell6.test e3b883b61d4916b6906678a35f9d19054861123ad91b856461e0a456273bdbb8
F test/shell7.test 43fd8e511c533bab5232e95c7b4be93b243451709e89582600d4b6e67693d5c3
F test/shell8.test aea51ecbcd4494c746b096aeff51d841d04d5f0dc4b62eb42427f16109b87acd
@@ -1697,7 +1703,7 @@ F test/sync.test 89539f4973c010eda5638407e71ca7fddbcd8e0594f4c9980229f804d433309
F test/sync2.test 8f9f7d4f6d5be8ca8941a8dadcc4299e558cb6a1ff653a9469146c7a76ef2039
F test/syscall.test a067468b43b8cb2305e9f9fe414e5f40c875bb5d2cba5f00b8154396e95fcf37
F test/sysfault.test c9f2b0d8d677558f74de750c75e12a5454719d04
-F test/tabfunc01.test f150d206294471d20f50029e6b46b76b87a7a010b16dc57eb44245c76dd02802
+F test/tabfunc01.test 6002a5f37b76355f173c75c2b3b03173b19d6a8b078c5baaa4c78bbcd0fa6323
F test/table.test 7862a00b58b5541511a26757ea9c5c7c3f8298766e98aa099deec703d9c0a8e0
F test/tableapi.test ecbcc29c4ab62c1912c3717c48ea5c5e59f7d64e4a91034e6148bd2b82f177f4
F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930
@@ -1709,9 +1715,9 @@ F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30
F test/temptable2.test 76821347810ecc88203e6ef0dd6897b6036ac788e9dd3e6b04fd4d1631311a16
F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637
F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc
-F test/tester.tcl fa7006ff3843b9bf77a09ac474fa411ccaa89e33fb45ba30224e31fd15ab0937
-F test/testrunner.tcl 5d02deeba7a53baeadae6aa7641d90aac58fdfa3a7bcac85cfcfd752b1aab87c
-F test/testrunner_data.tcl c5ae2b1f9a99210b0600d002fb3af1fee350997cee9416551e83b93501360ebf
+F test/tester.tcl b4b9cc1520542d34ee061f9f12df0944d6ad1c438feba9db4078f6214e0a8111
+F test/testrunner.tcl ff5f42f683a9c868fe01a855d81b9f08e1afb031edc5c340e5cf0fe5deaa0041
+F test/testrunner_data.tcl f1cbff53fe42087cac3d43ca02f9574bd212c842307442e2b6fff2183f5ccbfe
F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899
F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502
F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7
@@ -2078,13 +2084,13 @@ F test/windowA.test 6d63dc1260daa17141a55007600581778523a8b420629f1282d2acfc36af
F test/windowB.test aad7c31739999f68a98a813cfd78390918fc70f56d2d925317a1523cab548ecf
F test/windowC.test 6fd75f5bb2f1343d34e470e36e68f0ff638d8a42f6aa7d99471261b31a0d42f2
F test/windowD.test 65cf5a765fb8072450e8a0de2979ce7f09a38d87724fe1280c6444073e3da49b
-F test/windowE.test 6ba0c8048e4cc02b942e56640f8fcd50fd7ca72c876656c40f6baf42e316684c
+F test/windowE.test d045a5fbaaf50ecac9483e1249dd317ba4f9d189c405a730ba6effdefb87b94f
F test/windowerr.tcl f5acd6fbc210d7b5546c0e879d157888455cd4a17a1d3f28f07c1c8a387019e0
F test/windowerr.test a8b752402109c15aa1c5efe1b93ccb0ce1ef84fa964ae1cd6684dd0b3cc1819b
F test/windowfault.test 15094c1529424e62f798bc679e3fe9dfab6e8ba2f7dfe8c923b6248c31660a7c
F test/windowpushd.test c420e2265f0e09a0e798d0513a660d71b51602088d81b3dbd038918ee1339dcc
F test/with1.test b93833890e5d2a368e78747f124503a0159aa029b98e9ed4795ebf630b2efd3d
-F test/with2.test a1df41b987198383b9b70bf5e5fda390582e46398653858dbc6ceb24253b28df
+F test/with2.test 181674a6cc86a601ca2ac052741cdfad5b529e07e870435d2f6cdb92d589ff17
F test/with3.test e30369ea27aa27eb1bda4c5e510c8a9f782c8afd2ab99d1a02b8a7f25a5d3e65
F test/with4.test 257be66c0c67fee1defbbac0f685c3465e2cad037f21ce65f23f86084f198205
F test/with5.test 6248213c41fab36290b5b73aa3f937309dfba337004d9d8434c3fabc8c7d4be8
@@ -2139,7 +2145,7 @@ F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439
F tool/merge-test.tcl de76b62f2de2a92d4c1ca4f976bce0aea6899e0229e250479b229b2a1914b176
F tool/mkautoconfamal.sh cbdcf993fa83dccbef7fb77b39cdeb31ef9f77d9d88c9e343b58d35ca3898a6a
F tool/mkccode.tcl 86463e68ce9c15d3041610fedd285ce32a5cf7a58fc88b3202b8b76837650dbe x
-F tool/mkctimec.tcl eec31c692f65585a33c567e4592e0a14817789d43618343cf942b6bfd957cbab x
+F tool/mkctimec.tcl ec66114bec6d490d6857436604dc03a2940c7f1019cabf0148661b66cfe7f8e1 x
F tool/mkkeywordhash.c b9faa0ae7e14e4dbbcd951cddd786bf46b8a65bb07b129ba8c0cfade723aaffd
F tool/mkmsvcmin.tcl d76c45efda1cce2d4005bcea7b8a22bb752e3256009f331120fb4fecb14ebb7a
F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61a07ef
@@ -2219,8 +2225,8 @@ F vsixtest/vsixtest.tcl 6195aba1f12a5e10efc2b8c0009532167be5e301abe5b31385638080
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 681d228023efb902b9039acf33600dd91f158e622d4dc0b434ed74f39ce0070a 41a41c173a9d15d94f23d73a5c04bfb1616cb9223bc81d41808f9b4d00817fbf 7a0cdc7edb704a88a77b748cd28f6e00c49849cc2c1af838b95b34232ecc21f9
-R e2d0f4bcac8e3e78ac9fc97bce4611d5
+P a78208b597ee34b1121dd8014b3c1376b46baddff41448c96a37723cc9ede921 854b3776ee1fcaa5931e3a0ed104978ca350d218e553586d1c40c2420e1be498
+R 5cb6666fbe975bfe91972ff398cb8598
U drh
-Z d38ddf7cbe3129369a9ec5b4eb3fffb1
+Z 7fadaa5fa5a0acfd0bbdeb40d1608a3d
# Remove this line to create a well-formed Fossil manifest.
diff --git a/manifest.uuid b/manifest.uuid
index 08e2b5c130..d1c78147c7 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-a78208b597ee34b1121dd8014b3c1376b46baddff41448c96a37723cc9ede921
+dfecc79c8fa4b39e5211f1a7051783c750bb7e1132f3e3a60f8ae0837dbbe486
diff --git a/src/alter.c b/src/alter.c
index a8556d115e..ff20757589 100644
--- a/src/alter.c
+++ b/src/alter.c
@@ -1366,8 +1366,9 @@ static int renameResolveTrigger(Parse *pParse){
int i;
for(i=0; ipFrom->nSrc && rc==SQLITE_OK; i++){
SrcItem *p = &pStep->pFrom->a[i];
- if( p->pSelect ){
- sqlite3SelectPrep(pParse, p->pSelect, 0);
+ if( p->fg.isSubquery ){
+ assert( p->u4.pSubq!=0 );
+ sqlite3SelectPrep(pParse, p->u4.pSubq->pSelect, 0);
}
}
}
@@ -1435,8 +1436,12 @@ static void renameWalkTrigger(Walker *pWalker, Trigger *pTrigger){
}
if( pStep->pFrom ){
int i;
- for(i=0; ipFrom->nSrc; i++){
- sqlite3WalkSelect(pWalker, pStep->pFrom->a[i].pSelect);
+ SrcList *pFrom = pStep->pFrom;
+ for(i=0; inSrc; i++){
+ if( pFrom->a[i].fg.isSubquery ){
+ assert( pFrom->a[i].u4.pSubq!=0 );
+ sqlite3WalkSelect(pWalker, pFrom->a[i].u4.pSubq->pSelect);
+ }
}
}
}
@@ -1683,7 +1688,7 @@ static int renameTableSelectCb(Walker *pWalker, Select *pSelect){
}
for(i=0; inSrc; i++){
SrcItem *pItem = &pSrc->a[i];
- if( pItem->pTab==p->pTab ){
+ if( pItem->pSTab==p->pTab ){
renameTokenFind(pWalker->pParse, p, pItem->zName);
}
}
diff --git a/src/attach.c b/src/attach.c
index 4a6a25bf04..76476685fb 100644
--- a/src/attach.c
+++ b/src/attach.c
@@ -479,20 +479,21 @@ static int fixSelectCb(Walker *p, Select *pSelect){
if( NEVER(pList==0) ) return WRC_Continue;
for(i=0, pItem=pList->a; inSrc; i++, pItem++){
- if( pFix->bTemp==0 ){
- if( pItem->zDatabase ){
- if( iDb!=sqlite3FindDbName(db, pItem->zDatabase) ){
+ if( pFix->bTemp==0 && pItem->fg.isSubquery==0 ){
+ if( pItem->fg.fixedSchema==0 && pItem->u4.zDatabase!=0 ){
+ if( iDb!=sqlite3FindDbName(db, pItem->u4.zDatabase) ){
sqlite3ErrorMsg(pFix->pParse,
"%s %T cannot reference objects in database %s",
- pFix->zType, pFix->pName, pItem->zDatabase);
+ pFix->zType, pFix->pName, pItem->u4.zDatabase);
return WRC_Abort;
}
- sqlite3DbFree(db, pItem->zDatabase);
- pItem->zDatabase = 0;
+ sqlite3DbFree(db, pItem->u4.zDatabase);
pItem->fg.notCte = 1;
+ pItem->fg.hadSchema = 1;
}
- pItem->pSchema = pFix->pSchema;
+ pItem->u4.pSchema = pFix->pSchema;
pItem->fg.fromDDL = 1;
+ pItem->fg.fixedSchema = 1;
}
#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER)
if( pList->a[i].fg.isUsing==0
diff --git a/src/auth.c b/src/auth.c
index fa6c82d85d..fba2c09905 100644
--- a/src/auth.c
+++ b/src/auth.c
@@ -165,7 +165,7 @@ void sqlite3AuthRead(
assert( pTabList );
for(iSrc=0; iSrcnSrc; iSrc++){
if( pExpr->iTable==pTabList->a[iSrc].iCursor ){
- pTab = pTabList->a[iSrc].pTab;
+ pTab = pTabList->a[iSrc].pSTab;
break;
}
}
diff --git a/src/btree.c b/src/btree.c
index 34f494aa08..cac7e6ea23 100644
--- a/src/btree.c
+++ b/src/btree.c
@@ -5989,7 +5989,7 @@ int sqlite3BtreeIndexMoveto(
&& indexCellCompare(pCur, 0, pIdxKey, xRecordCompare)<=0
&& pIdxKey->errCode==SQLITE_OK
){
- pCur->curFlags &= ~BTCF_ValidOvfl;
+ pCur->curFlags &= ~(BTCF_ValidOvfl|BTCF_AtLast);
if( !pCur->pPage->isInit ){
return SQLITE_CORRUPT_BKPT;
}
diff --git a/src/build.c b/src/build.c
index 9747810e82..943d862e97 100644
--- a/src/build.c
+++ b/src/build.c
@@ -497,12 +497,12 @@ Table *sqlite3LocateTableItem(
SrcItem *p
){
const char *zDb;
- assert( p->pSchema==0 || p->zDatabase==0 );
- if( p->pSchema ){
- int iDb = sqlite3SchemaToIndex(pParse->db, p->pSchema);
+ if( p->fg.fixedSchema ){
+ int iDb = sqlite3SchemaToIndex(pParse->db, p->u4.pSchema);
zDb = pParse->db->aDb[iDb].zDbSName;
}else{
- zDb = p->zDatabase;
+ assert( !p->fg.isSubquery );
+ zDb = p->u4.zDatabase;
}
return sqlite3LocateTable(pParse, flags, p->zName, zDb);
}
@@ -3487,6 +3487,8 @@ void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, int noErr){
}
assert( pParse->nErr==0 );
assert( pName->nSrc==1 );
+ assert( pName->a[0].fg.fixedSchema==0 );
+ assert( pName->a[0].fg.isSubquery==0 );
if( sqlite3ReadSchema(pParse) ) goto exit_drop_table;
if( noErr ) db->suppressErr++;
assert( isView==0 || isView==LOCATE_VIEW );
@@ -3495,7 +3497,7 @@ void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, int noErr){
if( pTab==0 ){
if( noErr ){
- sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase);
+ sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].u4.zDatabase);
sqlite3ForceNotReadOnly(pParse);
}
goto exit_drop_table;
@@ -4586,15 +4588,17 @@ void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){
}
assert( pParse->nErr==0 ); /* Never called with prior non-OOM errors */
assert( pName->nSrc==1 );
+ assert( pName->a[0].fg.fixedSchema==0 );
+ assert( pName->a[0].fg.isSubquery==0 );
if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){
goto exit_drop_index;
}
- pIndex = sqlite3FindIndex(db, pName->a[0].zName, pName->a[0].zDatabase);
+ pIndex = sqlite3FindIndex(db, pName->a[0].zName, pName->a[0].u4.zDatabase);
if( pIndex==0 ){
if( !ifExists ){
sqlite3ErrorMsg(pParse, "no such index: %S", pName->a);
}else{
- sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase);
+ sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].u4.zDatabase);
sqlite3ForceNotReadOnly(pParse);
}
pParse->checkSchema = 1;
@@ -4891,12 +4895,14 @@ SrcList *sqlite3SrcListAppend(
if( pDatabase && pDatabase->z==0 ){
pDatabase = 0;
}
+ assert( pItem->fg.fixedSchema==0 );
+ assert( pItem->fg.isSubquery==0 );
if( pDatabase ){
pItem->zName = sqlite3NameFromToken(db, pDatabase);
- pItem->zDatabase = sqlite3NameFromToken(db, pTable);
+ pItem->u4.zDatabase = sqlite3NameFromToken(db, pTable);
}else{
pItem->zName = sqlite3NameFromToken(db, pTable);
- pItem->zDatabase = 0;
+ pItem->u4.zDatabase = 0;
}
return pList;
}
@@ -4912,13 +4918,40 @@ void sqlite3SrcListAssignCursors(Parse *pParse, SrcList *pList){
for(i=0, pItem=pList->a; inSrc; i++, pItem++){
if( pItem->iCursor>=0 ) continue;
pItem->iCursor = pParse->nTab++;
- if( pItem->pSelect ){
- sqlite3SrcListAssignCursors(pParse, pItem->pSelect->pSrc);
+ if( pItem->fg.isSubquery ){
+ assert( pItem->u4.pSubq!=0 );
+ assert( pItem->u4.pSubq->pSelect!=0 );
+ assert( pItem->u4.pSubq->pSelect->pSrc!=0 );
+ sqlite3SrcListAssignCursors(pParse, pItem->u4.pSubq->pSelect->pSrc);
}
}
}
}
+/*
+** Delete a Subquery object and its substructure.
+*/
+void sqlite3SubqueryDelete(sqlite3 *db, Subquery *pSubq){
+ assert( pSubq!=0 && pSubq->pSelect!=0 );
+ sqlite3SelectDelete(db, pSubq->pSelect);
+ sqlite3DbFree(db, pSubq);
+}
+
+/*
+** Remove a Subquery from a SrcItem. Return the associated Select object.
+** The returned Select becomes the responsibility of the caller.
+*/
+Select *sqlite3SubqueryDetach(sqlite3 *db, SrcItem *pItem){
+ Select *pSel;
+ assert( pItem!=0 );
+ assert( pItem->fg.isSubquery );
+ pSel = pItem->u4.pSubq->pSelect;
+ sqlite3DbFree(db, pItem->u4.pSubq);
+ pItem->u4.pSubq = 0;
+ pItem->fg.isSubquery = 0;
+ return pSel;
+}
+
/*
** Delete an entire SrcList including all its substructure.
*/
@@ -4928,13 +4961,24 @@ void sqlite3SrcListDelete(sqlite3 *db, SrcList *pList){
assert( db!=0 );
if( pList==0 ) return;
for(pItem=pList->a, i=0; inSrc; i++, pItem++){
- if( pItem->zDatabase ) sqlite3DbNNFreeNN(db, pItem->zDatabase);
+
+ /* Check invariants on SrcItem */
+ assert( !pItem->fg.isIndexedBy || !pItem->fg.isTabFunc );
+ assert( !pItem->fg.isCte || !pItem->fg.isIndexedBy );
+ assert( !pItem->fg.fixedSchema || !pItem->fg.isSubquery );
+ assert( !pItem->fg.isSubquery || (pItem->u4.pSubq!=0 &&
+ pItem->u4.pSubq->pSelect!=0) );
+
if( pItem->zName ) sqlite3DbNNFreeNN(db, pItem->zName);
if( pItem->zAlias ) sqlite3DbNNFreeNN(db, pItem->zAlias);
+ if( pItem->fg.isSubquery ){
+ sqlite3SubqueryDelete(db, pItem->u4.pSubq);
+ }else if( pItem->fg.fixedSchema==0 && pItem->u4.zDatabase!=0 ){
+ sqlite3DbNNFreeNN(db, pItem->u4.zDatabase);
+ }
if( pItem->fg.isIndexedBy ) sqlite3DbFree(db, pItem->u1.zIndexedBy);
if( pItem->fg.isTabFunc ) sqlite3ExprListDelete(db, pItem->u1.pFuncArg);
- sqlite3DeleteTable(db, pItem->pTab);
- if( pItem->pSelect ) sqlite3SelectDelete(db, pItem->pSelect);
+ sqlite3DeleteTable(db, pItem->pSTab);
if( pItem->fg.isUsing ){
sqlite3IdListDelete(db, pItem->u3.pUsing);
}else if( pItem->u3.pOn ){
@@ -4944,6 +4988,54 @@ void sqlite3SrcListDelete(sqlite3 *db, SrcList *pList){
sqlite3DbNNFreeNN(db, pList);
}
+/*
+** Attach a Subquery object to pItem->uv.pSubq. Set the
+** pSelect value but leave all the other values initialized
+** to zero.
+**
+** A copy of the Select object is made if dupSelect is true, and the
+** SrcItem takes responsibility for deleting the copy. If dupSelect is
+** false, ownership of the Select passes to the SrcItem. Either way,
+** the SrcItem will take responsibility for deleting the Select.
+**
+** When dupSelect is zero, that means the Select might get deleted right
+** away if there is an OOM error. Beware.
+**
+** Return non-zero on success. Return zero on an OOM error.
+*/
+int sqlite3SrcItemAttachSubquery(
+ Parse *pParse, /* Parsing context */
+ SrcItem *pItem, /* Item to which the subquery is to be attached */
+ Select *pSelect, /* The subquery SELECT. Must be non-NULL */
+ int dupSelect /* If true, attach a copy of pSelect, not pSelect itself.*/
+){
+ Subquery *p;
+ assert( pSelect!=0 );
+ assert( pItem->fg.isSubquery==0 );
+ if( pItem->fg.fixedSchema ){
+ pItem->u4.pSchema = 0;
+ pItem->fg.fixedSchema = 0;
+ }else if( pItem->u4.zDatabase!=0 ){
+ sqlite3DbFree(pParse->db, pItem->u4.zDatabase);
+ pItem->u4.zDatabase = 0;
+ }
+ if( dupSelect ){
+ pSelect = sqlite3SelectDup(pParse->db, pSelect, 0);
+ if( pSelect==0 ) return 0;
+ }
+ p = pItem->u4.pSubq = sqlite3DbMallocRawNN(pParse->db, sizeof(Subquery));
+ if( p==0 ){
+ sqlite3SelectDelete(pParse->db, pSelect);
+ return 0;
+ }
+ pItem->fg.isSubquery = 1;
+ p->pSelect = pSelect;
+ assert( offsetof(Subquery, pSelect)==0 );
+ memset(((char*)p)+sizeof(p->pSelect), 0, sizeof(*p)-sizeof(p->pSelect));
+ return 1;
+}
+
+
/*
** This routine is called by the parser to add a new term to the
** end of a growing FROM clause. The "p" parameter is the part of
@@ -4993,10 +5085,12 @@ SrcList *sqlite3SrcListAppendFromTerm(
if( pAlias->n ){
pItem->zAlias = sqlite3NameFromToken(db, pAlias);
}
+ assert( pSubquery==0 || pDatabase==0 );
if( pSubquery ){
- pItem->pSelect = pSubquery;
- if( pSubquery->selFlags & SF_NestedFrom ){
- pItem->fg.isNestedFrom = 1;
+ if( sqlite3SrcItemAttachSubquery(pParse, pItem, pSubquery, 0) ){
+ if( pSubquery->selFlags & SF_NestedFrom ){
+ pItem->fg.isNestedFrom = 1;
+ }
}
}
assert( pOnUsing==0 || pOnUsing->pOn==0 || pOnUsing->pUsing==0 );
diff --git a/src/date.c b/src/date.c
index 8a609ae3cc..8c48a81fa5 100644
--- a/src/date.c
+++ b/src/date.c
@@ -271,8 +271,8 @@ static void computeJD(DateTime *p){
Y--;
M += 12;
}
- A = Y/100;
- B = 2 - A + (A/4);
+ A = (Y+4800)/100;
+ B = 38 - A + (A/4);
X1 = 36525*(Y+4716)/100;
X2 = 306001*(M+1)/10000;
p->iJD = (sqlite3_int64)((X1 + X2 + D + B - 1524.5 ) * 86400000);
@@ -456,7 +456,7 @@ static int validJulianDay(sqlite3_int64 iJD){
** Compute the Year, Month, and Day from the julian day number.
*/
static void computeYMD(DateTime *p){
- int Z, A, B, C, D, E, X1;
+ int Z, alpha, A, B, C, D, E, X1;
if( p->validYMD ) return;
if( !p->validJD ){
p->Y = 2000;
@@ -467,8 +467,8 @@ static void computeYMD(DateTime *p){
return;
}else{
Z = (int)((p->iJD + 43200000)/86400000);
- A = (int)((Z - 1867216.25)/36524.25);
- A = Z + 1 + A - (A/4);
+ alpha = (int)((Z + 32044.75)/36524.25) - 52;
+ A = Z + 1 + alpha - ((alpha+100)/4) + 25;
B = A + 1524;
C = (int)((B - 122.1)/365.25);
D = (36525*(C&32767))/100;
diff --git a/src/dbstat.c b/src/dbstat.c
index c70d806370..d635a82975 100644
--- a/src/dbstat.c
+++ b/src/dbstat.c
@@ -279,6 +279,7 @@ static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
pIdxInfo->orderByConsumed = 1;
pIdxInfo->idxNum |= 0x08;
}
+ pIdxInfo->idxFlags |= SQLITE_INDEX_SCAN_HEX;
return SQLITE_OK;
}
diff --git a/src/delete.c b/src/delete.c
index 2baff5b3d4..4cdb3946e3 100644
--- a/src/delete.c
+++ b/src/delete.c
@@ -24,8 +24,8 @@
**
** The following fields are initialized appropriate in pSrc:
**
-** pSrc->a[0].pTab Pointer to the Table object
-** pSrc->a[0].pIndex Pointer to the INDEXED BY index, if there is one
+** pSrc->a[0].spTab Pointer to the Table object
+** pSrc->a[0].u2.pIBIndex Pointer to the INDEXED BY index, if there is one
**
*/
Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){
@@ -33,8 +33,8 @@ Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){
Table *pTab;
assert( pItem && pSrc->nSrc>=1 );
pTab = sqlite3LocateTableItem(pParse, 0, pItem);
- if( pItem->pTab ) sqlite3DeleteTable(pParse->db, pItem->pTab);
- pItem->pTab = pTab;
+ if( pItem->pSTab ) sqlite3DeleteTable(pParse->db, pItem->pSTab);
+ pItem->pSTab = pTab;
pItem->fg.notCte = 1;
if( pTab ){
pTab->nTabRef++;
@@ -156,7 +156,8 @@ void sqlite3MaterializeView(
if( pFrom ){
assert( pFrom->nSrc==1 );
pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName);
- pFrom->a[0].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zDbSName);
+ assert( pFrom->a[0].fg.fixedSchema==0 && pFrom->a[0].fg.isSubquery==0 );
+ pFrom->a[0].u4.zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zDbSName);
assert( pFrom->a[0].fg.isUsing==0 );
assert( pFrom->a[0].u3.pOn==0 );
}
@@ -218,7 +219,7 @@ Expr *sqlite3LimitWhere(
** );
*/
- pTab = pSrc->a[0].pTab;
+ pTab = pSrc->a[0].pSTab;
if( HasRowid(pTab) ){
pLhs = sqlite3PExpr(pParse, TK_ROW, 0, 0);
pEList = sqlite3ExprListAppend(
@@ -251,9 +252,9 @@ Expr *sqlite3LimitWhere(
/* duplicate the FROM clause as it is needed by both the DELETE/UPDATE tree
** and the SELECT subtree. */
- pSrc->a[0].pTab = 0;
+ pSrc->a[0].pSTab = 0;
pSelectSrc = sqlite3SrcListDup(db, pSrc, 0);
- pSrc->a[0].pTab = pTab;
+ pSrc->a[0].pSTab = pTab;
if( pSrc->a[0].fg.isIndexedBy ){
assert( pSrc->a[0].fg.isCte==0 );
pSrc->a[0].u2.pIBIndex = 0;
diff --git a/src/expr.c b/src/expr.c
index 53b0170ab4..1b18828dd6 100644
--- a/src/expr.c
+++ b/src/expr.c
@@ -1877,15 +1877,30 @@ SrcList *sqlite3SrcListDup(sqlite3 *db, const SrcList *p, int flags){
SrcItem *pNewItem = &pNew->a[i];
const SrcItem *pOldItem = &p->a[i];
Table *pTab;
- pNewItem->pSchema = pOldItem->pSchema;
- pNewItem->zDatabase = sqlite3DbStrDup(db, pOldItem->zDatabase);
+ pNewItem->fg = pOldItem->fg;
+ if( pOldItem->fg.isSubquery ){
+ Subquery *pNewSubq = sqlite3DbMallocRaw(db, sizeof(Subquery));
+ if( pNewSubq==0 ){
+ assert( db->mallocFailed );
+ pNewItem->fg.isSubquery = 0;
+ }else{
+ memcpy(pNewSubq, pOldItem->u4.pSubq, sizeof(*pNewSubq));
+ pNewSubq->pSelect = sqlite3SelectDup(db, pNewSubq->pSelect, flags);
+ if( pNewSubq->pSelect==0 ){
+ sqlite3DbFree(db, pNewSubq);
+ pNewSubq = 0;
+ pNewItem->fg.isSubquery = 0;
+ }
+ }
+ pNewItem->u4.pSubq = pNewSubq;
+ }else if( pOldItem->fg.fixedSchema ){
+ pNewItem->u4.pSchema = pOldItem->u4.pSchema;
+ }else{
+ pNewItem->u4.zDatabase = sqlite3DbStrDup(db, pOldItem->u4.zDatabase);
+ }
pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
pNewItem->zAlias = sqlite3DbStrDup(db, pOldItem->zAlias);
- pNewItem->fg = pOldItem->fg;
pNewItem->iCursor = pOldItem->iCursor;
- pNewItem->addrFillSub = pOldItem->addrFillSub;
- pNewItem->regReturn = pOldItem->regReturn;
- pNewItem->regResult = pOldItem->regResult;
if( pNewItem->fg.isIndexedBy ){
pNewItem->u1.zIndexedBy = sqlite3DbStrDup(db, pOldItem->u1.zIndexedBy);
}else if( pNewItem->fg.isTabFunc ){
@@ -1898,11 +1913,10 @@ SrcList *sqlite3SrcListDup(sqlite3 *db, const SrcList *p, int flags){
if( pNewItem->fg.isCte ){
pNewItem->u2.pCteUse->nUse++;
}
- pTab = pNewItem->pTab = pOldItem->pTab;
+ pTab = pNewItem->pSTab = pOldItem->pSTab;
if( pTab ){
pTab->nTabRef++;
}
- pNewItem->pSelect = sqlite3SelectDup(db, pOldItem->pSelect, flags);
if( pOldItem->fg.isUsing ){
assert( pNewItem->fg.isUsing );
pNewItem->u3.pUsing = sqlite3IdListDup(db, pOldItem->u3.pUsing);
@@ -1976,7 +1990,6 @@ Select *sqlite3SelectDup(sqlite3 *db, const Select *pDup, int flags){
pp = &pNew->pPrior;
pNext = pNew;
}
-
return pRet;
}
#else
@@ -2996,8 +3009,8 @@ static Select *isCandidateForInOpt(const Expr *pX){
pSrc = p->pSrc;
assert( pSrc!=0 );
if( pSrc->nSrc!=1 ) return 0; /* Single term in FROM clause */
- if( pSrc->a[0].pSelect ) return 0; /* FROM is not a subquery or view */
- pTab = pSrc->a[0].pTab;
+ if( pSrc->a[0].fg.isSubquery) return 0;/* FROM is not a subquery or view */
+ pTab = pSrc->a[0].pSTab;
assert( pTab!=0 );
assert( !IsView(pTab) ); /* FROM clause is not a view */
if( IsVirtual(pTab) ) return 0; /* FROM clause not a virtual table */
@@ -3180,7 +3193,7 @@ int sqlite3FindInIndex(
assert( p->pEList!=0 ); /* Because of isCandidateForInOpt(p) */
assert( p->pEList->a[0].pExpr!=0 ); /* Because of isCandidateForInOpt(p) */
assert( p->pSrc!=0 ); /* Because of isCandidateForInOpt(p) */
- pTab = p->pSrc->a[0].pTab;
+ pTab = p->pSrc->a[0].pSTab;
/* Code an OP_Transaction and OP_TableLock for . */
iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
diff --git a/src/fkey.c b/src/fkey.c
index 1efdc0bba1..f1117a8845 100644
--- a/src/fkey.c
+++ b/src/fkey.c
@@ -1043,9 +1043,9 @@ void sqlite3FkCheck(
pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0);
if( pSrc ){
SrcItem *pItem = pSrc->a;
- pItem->pTab = pFKey->pFrom;
+ pItem->pSTab = pFKey->pFrom;
pItem->zName = pFKey->pFrom->zName;
- pItem->pTab->nTabRef++;
+ pItem->pSTab->nTabRef++;
pItem->iCursor = pParse->nTab++;
if( regNew!=0 ){
@@ -1337,7 +1337,8 @@ static Trigger *fkActionTrigger(
if( pSrc ){
assert( pSrc->nSrc==1 );
pSrc->a[0].zName = sqlite3DbStrDup(db, zFrom);
- pSrc->a[0].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zDbSName);
+ assert( pSrc->a[0].fg.fixedSchema==0 && pSrc->a[0].fg.isSubquery==0 );
+ pSrc->a[0].u4.zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zDbSName);
}
pSelect = sqlite3SelectNew(pParse,
sqlite3ExprListAppend(pParse, 0, pRaise),
diff --git a/src/insert.c b/src/insert.c
index a7e94420bf..d380281bed 100644
--- a/src/insert.c
+++ b/src/insert.c
@@ -585,8 +585,11 @@ void sqlite3AutoincrementEnd(Parse *pParse){
void sqlite3MultiValuesEnd(Parse *pParse, Select *pVal){
if( ALWAYS(pVal) && pVal->pSrc->nSrc>0 ){
SrcItem *pItem = &pVal->pSrc->a[0];
- sqlite3VdbeEndCoroutine(pParse->pVdbe, pItem->regReturn);
- sqlite3VdbeJumpHere(pParse->pVdbe, pItem->addrFillSub - 1);
+ assert( (pItem->fg.isSubquery && pItem->u4.pSubq!=0) || pParse->nErr );
+ if( pItem->fg.isSubquery ){
+ sqlite3VdbeEndCoroutine(pParse->pVdbe, pItem->u4.pSubq->regReturn);
+ sqlite3VdbeJumpHere(pParse->pVdbe, pItem->u4.pSubq->addrFillSub - 1);
+ }
}
}
@@ -714,6 +717,7 @@ Select *sqlite3MultiValues(Parse *pParse, Select *pLeft, ExprList *pRow){
if( pRet ){
SelectDest dest;
+ Subquery *pSubq;
pRet->pSrc->nSrc = 1;
pRet->pPrior = pLeft->pPrior;
pRet->op = pLeft->op;
@@ -723,28 +727,32 @@ Select *sqlite3MultiValues(Parse *pParse, Select *pLeft, ExprList *pRow){
assert( pLeft->pNext==0 );
assert( pRet->pNext==0 );
p = &pRet->pSrc->a[0];
- p->pSelect = pLeft;
p->fg.viaCoroutine = 1;
- p->addrFillSub = sqlite3VdbeCurrentAddr(v) + 1;
- p->regReturn = ++pParse->nMem;
p->iCursor = -1;
+ assert( !p->fg.isIndexedBy && !p->fg.isTabFunc );
p->u1.nRow = 2;
- sqlite3VdbeAddOp3(v,OP_InitCoroutine,p->regReturn,0,p->addrFillSub);
- sqlite3SelectDestInit(&dest, SRT_Coroutine, p->regReturn);
+ if( sqlite3SrcItemAttachSubquery(pParse, p, pLeft, 0) ){
+ pSubq = p->u4.pSubq;
+ pSubq->addrFillSub = sqlite3VdbeCurrentAddr(v) + 1;
+ pSubq->regReturn = ++pParse->nMem;
+ sqlite3VdbeAddOp3(v, OP_InitCoroutine,
+ pSubq->regReturn, 0, pSubq->addrFillSub);
+ sqlite3SelectDestInit(&dest, SRT_Coroutine, pSubq->regReturn);
- /* Allocate registers for the output of the co-routine. Do so so
- ** that there are two unused registers immediately before those
- ** used by the co-routine. This allows the code in sqlite3Insert()
- ** to use these registers directly, instead of copying the output
- ** of the co-routine to a separate array for processing. */
- dest.iSdst = pParse->nMem + 3;
- dest.nSdst = pLeft->pEList->nExpr;
- pParse->nMem += 2 + dest.nSdst;
+ /* Allocate registers for the output of the co-routine. Do so so
+ ** that there are two unused registers immediately before those
+ ** used by the co-routine. This allows the code in sqlite3Insert()
+ ** to use these registers directly, instead of copying the output
+ ** of the co-routine to a separate array for processing. */
+ dest.iSdst = pParse->nMem + 3;
+ dest.nSdst = pLeft->pEList->nExpr;
+ pParse->nMem += 2 + dest.nSdst;
- pLeft->selFlags |= SF_MultiValue;
- sqlite3Select(pParse, pLeft, &dest);
- p->regResult = dest.iSdst;
- assert( pParse->nErr || dest.iSdst>0 );
+ pLeft->selFlags |= SF_MultiValue;
+ sqlite3Select(pParse, pLeft, &dest);
+ pSubq->regResult = dest.iSdst;
+ assert( pParse->nErr || dest.iSdst>0 );
+ }
pLeft = pRet;
}
}else{
@@ -754,12 +762,18 @@ Select *sqlite3MultiValues(Parse *pParse, Select *pLeft, ExprList *pRow){
}
if( pParse->nErr==0 ){
+ Subquery *pSubq;
assert( p!=0 );
- if( p->pSelect->pEList->nExpr!=pRow->nExpr ){
- sqlite3SelectWrongNumTermsError(pParse, p->pSelect);
+ assert( p->fg.isSubquery );
+ pSubq = p->u4.pSubq;
+ assert( pSubq!=0 );
+ assert( pSubq->pSelect!=0 );
+ assert( pSubq->pSelect->pEList!=0 );
+ if( pSubq->pSelect->pEList->nExpr!=pRow->nExpr ){
+ sqlite3SelectWrongNumTermsError(pParse, pSubq->pSelect);
}else{
- sqlite3ExprCodeExprList(pParse, pRow, p->regResult, 0, 0);
- sqlite3VdbeAddOp1(pParse->pVdbe, OP_Yield, p->regReturn);
+ sqlite3ExprCodeExprList(pParse, pRow, pSubq->regResult, 0, 0);
+ sqlite3VdbeAddOp1(pParse->pVdbe, OP_Yield, pSubq->regReturn);
}
}
sqlite3ExprListDelete(pParse->db, pRow);
@@ -1110,9 +1124,14 @@ void sqlite3Insert(
&& pSelect->pPrior==0
){
SrcItem *pItem = &pSelect->pSrc->a[0];
- dest.iSDParm = pItem->regReturn;
- regFromSelect = pItem->regResult;
- nColumn = pItem->pSelect->pEList->nExpr;
+ Subquery *pSubq;
+ assert( pItem->fg.isSubquery );
+ pSubq = pItem->u4.pSubq;
+ dest.iSDParm = pSubq->regReturn;
+ regFromSelect = pSubq->regResult;
+ assert( pSubq->pSelect!=0 );
+ assert( pSubq->pSelect->pEList!=0 );
+ nColumn = pSubq->pSelect->pEList->nExpr;
ExplainQueryPlan((pParse, 0, "SCAN %S", pItem));
if( bIdListInOrder && nColumn==pTab->nCol ){
regData = regFromSelect;
@@ -3032,7 +3051,7 @@ static int xferOptimization(
if( pSelect->pSrc->nSrc!=1 ){
return 0; /* FROM clause must have exactly one term */
}
- if( pSelect->pSrc->a[0].pSelect ){
+ if( pSelect->pSrc->a[0].fg.isSubquery ){
return 0; /* FROM clause cannot contain a subquery */
}
if( pSelect->pWhere ){
diff --git a/src/main.c b/src/main.c
index bff801a87d..5d6212208e 100644
--- a/src/main.c
+++ b/src/main.c
@@ -4393,6 +4393,18 @@ int sqlite3_test_control(int op, ...){
break;
}
+ /* sqlite3_test_control(SQLITE_TESTCTRL_GETOPT, sqlite3 *db, int *N)
+ **
+ ** Write the current optimization settings into *N. A zero bit means that
+ ** the optimization is on, and a 1 bit means that the optimization is off.
+ */
+ case SQLITE_TESTCTRL_GETOPT: {
+ sqlite3 *db = va_arg(ap, sqlite3*);
+ int *pN = va_arg(ap, int*);
+ *pN = db->dbOptFlags;
+ break;
+ }
+
/* sqlite3_test_control(SQLITE_TESTCTRL_LOCALTIME_FAULT, onoff, xAlt);
**
** If parameter onoff is 1, subsequent calls to localtime() fail.
diff --git a/src/os_unix.c b/src/os_unix.c
index c94c0c111f..5d1dc9ac6b 100644
--- a/src/os_unix.c
+++ b/src/os_unix.c
@@ -322,7 +322,7 @@ static pid_t randomnessPid = 0;
#define UNIXFILE_EXCL 0x01 /* Connections from one process only */
#define UNIXFILE_RDONLY 0x02 /* Connection is read only */
#define UNIXFILE_PERSIST_WAL 0x04 /* Persistent WAL mode */
-#ifndef SQLITE_DISABLE_DIRSYNC
+#if !defined(SQLITE_DISABLE_DIRSYNC) && !defined(_AIX)
# define UNIXFILE_DIRSYNC 0x08 /* Directory sync needed */
#else
# define UNIXFILE_DIRSYNC 0x00
diff --git a/src/parse.y b/src/parse.y
index 68c00f0a7f..926dd6e7d1 100644
--- a/src/parse.y
+++ b/src/parse.y
@@ -740,11 +740,21 @@ seltablist(A) ::= stl_prefix(A) nm(Y) dbnm(D) LP exprlist(E) RP as(Z) on_using(N
if( A ){
SrcItem *pNew = &A->a[A->nSrc-1];
SrcItem *pOld = F->a;
+ assert( pOld->fg.fixedSchema==0 );
pNew->zName = pOld->zName;
- pNew->zDatabase = pOld->zDatabase;
- pNew->pSelect = pOld->pSelect;
- if( pNew->pSelect && (pNew->pSelect->selFlags & SF_NestedFrom)!=0 ){
- pNew->fg.isNestedFrom = 1;
+ assert( pOld->fg.fixedSchema==0 );
+ if( pOld->fg.isSubquery ){
+ pNew->fg.isSubquery = 1;
+ pNew->u4.pSubq = pOld->u4.pSubq;
+ pOld->u4.pSubq = 0;
+ pOld->fg.isSubquery = 0;
+ assert( pNew->u4.pSubq!=0 && pNew->u4.pSubq->pSelect!=0 );
+ if( (pNew->u4.pSubq->pSelect->selFlags & SF_NestedFrom)!=0 ){
+ pNew->fg.isNestedFrom = 1;
+ }
+ }else{
+ pNew->u4.zDatabase = pOld->u4.zDatabase;
+ pOld->u4.zDatabase = 0;
}
if( pOld->fg.isTabFunc ){
pNew->u1.pFuncArg = pOld->u1.pFuncArg;
@@ -752,8 +762,7 @@ seltablist(A) ::= stl_prefix(A) nm(Y) dbnm(D) LP exprlist(E) RP as(Z) on_using(N
pOld->fg.isTabFunc = 0;
pNew->fg.isTabFunc = 1;
}
- pOld->zName = pOld->zDatabase = 0;
- pOld->pSelect = 0;
+ pOld->zName = 0;
}
sqlite3SrcListDelete(pParse->db, F);
}else{
diff --git a/src/printf.c b/src/printf.c
index c0dcc5d0fa..a140565146 100644
--- a/src/printf.c
+++ b/src/printf.c
@@ -848,16 +848,19 @@ void sqlite3_str_vappendf(
if( pItem->zAlias && !flag_altform2 ){
sqlite3_str_appendall(pAccum, pItem->zAlias);
}else if( pItem->zName ){
- if( pItem->zDatabase ){
- sqlite3_str_appendall(pAccum, pItem->zDatabase);
+ if( pItem->fg.fixedSchema==0
+ && pItem->fg.isSubquery==0
+ && pItem->u4.zDatabase!=0
+ ){
+ sqlite3_str_appendall(pAccum, pItem->u4.zDatabase);
sqlite3_str_append(pAccum, ".", 1);
}
sqlite3_str_appendall(pAccum, pItem->zName);
}else if( pItem->zAlias ){
sqlite3_str_appendall(pAccum, pItem->zAlias);
- }else{
- Select *pSel = pItem->pSelect;
- assert( pSel!=0 ); /* Because of tag-20240424-1 */
+ }else if( ALWAYS(pItem->fg.isSubquery) ){/* Because of tag-20240424-1 */
+ Select *pSel = pItem->u4.pSubq->pSelect;
+ assert( pSel!=0 );
if( pSel->selFlags & SF_NestedFrom ){
sqlite3_str_appendf(pAccum, "(join-%u)", pSel->selId);
}else if( pSel->selFlags & SF_MultiValue ){
diff --git a/src/resolve.c b/src/resolve.c
index dbb54198cb..b755cc8646 100644
--- a/src/resolve.c
+++ b/src/resolve.c
@@ -215,7 +215,7 @@ static void extendFJMatch(
if( pNew ){
pNew->iTable = pMatch->iCursor;
pNew->iColumn = iColumn;
- pNew->y.pTab = pMatch->pTab;
+ pNew->y.pTab = pMatch->pSTab;
assert( (pMatch->fg.jointype & (JT_LEFT|JT_LTORJ))!=0 );
ExprSetProperty(pNew, EP_CanBeNull);
*ppList = sqlite3ExprListAppend(pParse, *ppList, pNew);
@@ -346,10 +346,10 @@ static int lookupName(
if( pSrcList ){
for(i=0, pItem=pSrcList->a; inSrc; i++, pItem++){
u8 hCol;
- pTab = pItem->pTab;
+ pTab = pItem->pSTab;
assert( pTab!=0 && pTab->zName!=0 );
assert( pTab->nCol>0 || pParse->nErr );
- assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem->pSelect) );
+ assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem));
if( pItem->fg.isNestedFrom ){
/* In this case, pItem is a subquery that has been formed from a
** parenthesized subset of the FROM clause terms. Example:
@@ -358,8 +358,12 @@ static int lookupName(
** This pItem -------------^
*/
int hit = 0;
- assert( pItem->pSelect!=0 );
- pEList = pItem->pSelect->pEList;
+ Select *pSel;
+ assert( pItem->fg.isSubquery );
+ assert( pItem->u4.pSubq!=0 );
+ pSel = pItem->u4.pSubq->pSelect;
+ assert( pSel!=0 );
+ pEList = pSel->pEList;
assert( pEList!=0 );
assert( pEList->nExpr==pTab->nCol );
for(j=0; jnExpr; j++){
@@ -483,8 +487,8 @@ static int lookupName(
if( cntTab==0
|| (cntTab==1
&& ALWAYS(pMatch!=0)
- && ALWAYS(pMatch->pTab!=0)
- && (pMatch->pTab->tabFlags & TF_Ephemeral)!=0
+ && ALWAYS(pMatch->pSTab!=0)
+ && (pMatch->pSTab->tabFlags & TF_Ephemeral)!=0
&& (pTab->tabFlags & TF_Ephemeral)==0)
){
cntTab = 1;
@@ -505,7 +509,7 @@ static int lookupName(
if( pMatch ){
pExpr->iTable = pMatch->iCursor;
assert( ExprUseYTab(pExpr) );
- pExpr->y.pTab = pMatch->pTab;
+ pExpr->y.pTab = pMatch->pSTab;
if( (pMatch->fg.jointype & (JT_LEFT|JT_LTORJ))!=0 ){
ExprSetProperty(pExpr, EP_CanBeNull);
}
@@ -547,7 +551,7 @@ static int lookupName(
if( (pNC->ncFlags & NC_UUpsert)!=0 && zTab!=0 ){
Upsert *pUpsert = pNC->uNC.pUpsert;
if( pUpsert && sqlite3StrICmp("excluded",zTab)==0 ){
- pTab = pUpsert->pUpsertSrc->a[0].pTab;
+ pTab = pUpsert->pUpsertSrc->a[0].pSTab;
pExpr->iTable = EXCLUDED_TABLE_NUMBER;
}
}
@@ -630,11 +634,11 @@ static int lookupName(
&& pMatch
&& (pNC->ncFlags & (NC_IdxExpr|NC_GenCol))==0
&& sqlite3IsRowid(zCol)
- && ALWAYS(VisibleRowid(pMatch->pTab) || pMatch->fg.isNestedFrom)
+ && ALWAYS(VisibleRowid(pMatch->pSTab) || pMatch->fg.isNestedFrom)
){
cnt = cntTab;
#if SQLITE_ALLOW_ROWID_IN_VIEW+0==2
- if( pMatch->pTab!=0 && IsView(pMatch->pTab) ){
+ if( pMatch->pSTab!=0 && IsView(pMatch->pSTab) ){
eNewExprOp = TK_NULL;
}
#endif
@@ -871,7 +875,7 @@ Expr *sqlite3CreateColumnExpr(sqlite3 *db, SrcList *pSrc, int iSrc, int iCol){
SrcItem *pItem = &pSrc->a[iSrc];
Table *pTab;
assert( ExprUseYTab(p) );
- pTab = p->y.pTab = pItem->pTab;
+ pTab = p->y.pTab = pItem->pSTab;
p->iTable = pItem->iCursor;
if( p->y.pTab->iPKey==iCol ){
p->iColumn = -1;
@@ -990,7 +994,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
pItem = pSrcList->a;
pExpr->op = TK_COLUMN;
assert( ExprUseYTab(pExpr) );
- pExpr->y.pTab = pItem->pTab;
+ pExpr->y.pTab = pItem->pSTab;
pExpr->iTable = pItem->iCursor;
pExpr->iColumn--;
pExpr->affExpr = SQLITE_AFF_INTEGER;
@@ -1296,9 +1300,9 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
sqlite3WalkExprList(pWalker, pExpr->pLeft->x.pList);
}
#ifndef SQLITE_OMIT_WINDOWFUNC
- if( pWin ){
+ if( pWin && pParse->nErr==0 ){
Select *pSel = pNC->pWinSelect;
- assert( pWin==0 || (ExprUseYWin(pExpr) && pWin==pExpr->y.pWin) );
+ assert( ExprUseYWin(pExpr) && pWin==pExpr->y.pWin );
if( IN_RENAME_OBJECT==0 ){
sqlite3WindowUpdate(pParse, pSel ? pSel->pWinDefn : 0, pWin, pDef);
if( pParse->db->mallocFailed ) break;
@@ -1880,7 +1884,11 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
** moves the pOrderBy down to the sub-query. It will be moved back
** after the names have been resolved. */
if( p->selFlags & SF_Converted ){
- Select *pSub = p->pSrc->a[0].pSelect;
+ Select *pSub;
+ assert( p->pSrc->a[0].fg.isSubquery );
+ assert( p->pSrc->a[0].u4.pSubq!=0 );
+ pSub = p->pSrc->a[0].u4.pSubq->pSelect;
+ assert( pSub!=0 );
assert( p->pSrc->nSrc==1 && p->pOrderBy );
assert( pSub->pPrior && pSub->pOrderBy==0 );
pSub->pOrderBy = p->pOrderBy;
@@ -1892,13 +1900,16 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
if( pOuterNC ) pOuterNC->nNestedSelect++;
for(i=0; ipSrc->nSrc; i++){
SrcItem *pItem = &p->pSrc->a[i];
- assert( pItem->zName!=0 || pItem->pSelect!=0 );/* Test of tag-20240424-1*/
- if( pItem->pSelect && (pItem->pSelect->selFlags & SF_Resolved)==0 ){
+ assert( pItem->zName!=0
+ || pItem->fg.isSubquery ); /* Test of tag-20240424-1*/
+ if( pItem->fg.isSubquery
+ && (pItem->u4.pSubq->pSelect->selFlags & SF_Resolved)==0
+ ){
int nRef = pOuterNC ? pOuterNC->nRef : 0;
const char *zSavedContext = pParse->zAuthContext;
if( pItem->zName ) pParse->zAuthContext = pItem->zName;
- sqlite3ResolveSelectNames(pParse, pItem->pSelect, pOuterNC);
+ sqlite3ResolveSelectNames(pParse, pItem->u4.pSubq->pSelect, pOuterNC);
pParse->zAuthContext = zSavedContext;
if( pParse->nErr ) return WRC_Abort;
assert( db->mallocFailed==0 );
@@ -2000,7 +2011,10 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
** These integers will be replaced by copies of the corresponding result
** set expressions by the call to resolveOrderGroupBy() below. */
if( p->selFlags & SF_Converted ){
- Select *pSub = p->pSrc->a[0].pSelect;
+ Select *pSub;
+ assert( p->pSrc->a[0].fg.isSubquery );
+ pSub = p->pSrc->a[0].u4.pSubq->pSelect;
+ assert( pSub!=0 );
p->pOrderBy = pSub->pOrderBy;
pSub->pOrderBy = 0;
}
@@ -2267,7 +2281,7 @@ int sqlite3ResolveSelfReference(
if( pTab ){
sSrc.nSrc = 1;
sSrc.a[0].zName = pTab->zName;
- sSrc.a[0].pTab = pTab;
+ sSrc.a[0].pSTab = pTab;
sSrc.a[0].iCursor = -1;
if( pTab->pSchema!=pParse->db->aDb[1].pSchema ){
/* Cause EP_FromDDL to be set on TK_FUNCTION nodes of non-TEMP
diff --git a/src/select.c b/src/select.c
index 4b0b554295..9fcf30ff4a 100644
--- a/src/select.c
+++ b/src/select.c
@@ -332,11 +332,13 @@ int sqlite3ColumnIndex(Table *pTab, const char *zCol){
*/
void sqlite3SrcItemColumnUsed(SrcItem *pItem, int iCol){
assert( pItem!=0 );
- assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem->pSelect) );
+ assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem) );
if( pItem->fg.isNestedFrom ){
ExprList *pResults;
- assert( pItem->pSelect!=0 );
- pResults = pItem->pSelect->pEList;
+ assert( pItem->fg.isSubquery );
+ assert( pItem->u4.pSubq!=0 );
+ assert( pItem->u4.pSubq->pSelect!=0 );
+ pResults = pItem->u4.pSubq->pSelect->pEList;
assert( pResults!=0 );
assert( iCol>=0 && iColnExpr );
pResults->a[iCol].fg.bUsed = 1;
@@ -370,9 +372,9 @@ static int tableAndColumnIndex(
assert( (piTab==0)==(piCol==0) ); /* Both or neither are NULL */
for(i=iStart; i<=iEnd; i++){
- iCol = sqlite3ColumnIndex(pSrc->a[i].pTab, zCol);
+ iCol = sqlite3ColumnIndex(pSrc->a[i].pSTab, zCol);
if( iCol>=0
- && (bIgnoreHidden==0 || IsHiddenColumn(&pSrc->a[i].pTab->aCol[iCol])==0)
+ && (bIgnoreHidden==0 || IsHiddenColumn(&pSrc->a[i].pSTab->aCol[iCol])==0)
){
if( piTab ){
sqlite3SrcItemColumnUsed(&pSrc->a[i], iCol);
@@ -501,10 +503,10 @@ static int sqlite3ProcessJoin(Parse *pParse, Select *p){
pLeft = &pSrc->a[0];
pRight = &pLeft[1];
for(i=0; inSrc-1; i++, pRight++, pLeft++){
- Table *pRightTab = pRight->pTab;
+ Table *pRightTab = pRight->pSTab;
u32 joinType;
- if( NEVER(pLeft->pTab==0 || pRightTab==0) ) continue;
+ if( NEVER(pLeft->pSTab==0 || pRightTab==0) ) continue;
joinType = (pRight->fg.jointype & JT_OUTER)!=0 ? EP_OuterON : EP_InnerON;
/* If this is a NATURAL join, synthesize an appropriate USING clause
@@ -1930,8 +1932,12 @@ static const char *columnTypeImpl(
SrcList *pTabList = pNC->pSrcList;
for(j=0;jnSrc && pTabList->a[j].iCursor!=pExpr->iTable;j++);
if( jnSrc ){
- pTab = pTabList->a[j].pTab;
- pS = pTabList->a[j].pSelect;
+ pTab = pTabList->a[j].pSTab;
+ if( pTabList->a[j].fg.isSubquery ){
+ pS = pTabList->a[j].u4.pSubq->pSelect;
+ }else{
+ pS = 0;
+ }
}else{
pNC = pNC->pNext;
}
@@ -3983,7 +3989,9 @@ static void substSelect(
pSrc = p->pSrc;
assert( pSrc!=0 );
for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){
- substSelect(pSubst, pItem->pSelect, 1);
+ if( pItem->fg.isSubquery ){
+ substSelect(pSubst, pItem->u4.pSubq->pSelect, 1);
+ }
if( pItem->fg.isTabFunc ){
substExprList(pSubst, pItem->u1.pFuncArg);
}
@@ -4014,7 +4022,7 @@ static void recomputeColumnsUsed(
SrcItem *pSrcItem /* Which FROM clause item to recompute */
){
Walker w;
- if( NEVER(pSrcItem->pTab==0) ) return;
+ if( NEVER(pSrcItem->pSTab==0) ) return;
memset(&w, 0, sizeof(w));
w.xExprCallback = recomputeColumnsUsedExpr;
w.xSelectCallback = sqlite3SelectWalkNoop;
@@ -4054,8 +4062,10 @@ static void srclistRenumberCursors(
aCsrMap[pItem->iCursor+1] = pParse->nTab++;
}
pItem->iCursor = aCsrMap[pItem->iCursor+1];
- for(p=pItem->pSelect; p; p=p->pPrior){
- srclistRenumberCursors(pParse, aCsrMap, p->pSrc, -1);
+ if( pItem->fg.isSubquery ){
+ for(p=pItem->u4.pSubq->pSelect; p; p=p->pPrior){
+ srclistRenumberCursors(pParse, aCsrMap, p->pSrc, -1);
+ }
}
}
}
@@ -4366,7 +4376,8 @@ static int flattenSubquery(
assert( pSrc && iFrom>=0 && iFromnSrc );
pSubitem = &pSrc->a[iFrom];
iParent = pSubitem->iCursor;
- pSub = pSubitem->pSelect;
+ assert( pSubitem->fg.isSubquery );
+ pSub = pSubitem->u4.pSubq->pSelect;
assert( pSub!=0 );
#ifndef SQLITE_OMIT_WINDOWFUNC
@@ -4419,7 +4430,7 @@ static int flattenSubquery(
*/
if( (pSubitem->fg.jointype & (JT_OUTER|JT_LTORJ))!=0 ){
if( pSubSrc->nSrc>1 /* (3a) */
- || IsVirtual(pSubSrc->a[0].pTab) /* (3b) */
+ || IsVirtual(pSubSrc->a[0].pSTab) /* (3b) */
|| (p->selFlags & SF_Distinct)!=0 /* (3d) */
|| (pSubitem->fg.jointype & JT_RIGHT)!=0 /* (26) */
){
@@ -4505,14 +4516,18 @@ static int flattenSubquery(
pParse->zAuthContext = zSavedAuthContext;
/* Delete the transient structures associated with the subquery */
- pSub1 = pSubitem->pSelect;
- sqlite3DbFree(db, pSubitem->zDatabase);
+
+ if( ALWAYS(pSubitem->fg.isSubquery) ){
+ pSub1 = sqlite3SubqueryDetach(db, pSubitem);
+ }else{
+ pSub1 = 0;
+ }
+ assert( pSubitem->fg.isSubquery==0 );
+ assert( pSubitem->fg.fixedSchema==0 );
sqlite3DbFree(db, pSubitem->zName);
sqlite3DbFree(db, pSubitem->zAlias);
- pSubitem->zDatabase = 0;
pSubitem->zName = 0;
pSubitem->zAlias = 0;
- pSubitem->pSelect = 0;
assert( pSubitem->fg.isUsing!=0 || pSubitem->u3.pOn==0 );
/* If the sub-query is a compound SELECT statement, then (by restrictions
@@ -4553,8 +4568,8 @@ static int flattenSubquery(
ExprList *pOrderBy = p->pOrderBy;
Expr *pLimit = p->pLimit;
Select *pPrior = p->pPrior;
- Table *pItemTab = pSubitem->pTab;
- pSubitem->pTab = 0;
+ Table *pItemTab = pSubitem->pSTab;
+ pSubitem->pSTab = 0;
p->pOrderBy = 0;
p->pPrior = 0;
p->pLimit = 0;
@@ -4562,7 +4577,7 @@ static int flattenSubquery(
p->pLimit = pLimit;
p->pOrderBy = pOrderBy;
p->op = TK_ALL;
- pSubitem->pTab = pItemTab;
+ pSubitem->pSTab = pItemTab;
if( pNew==0 ){
p->pPrior = pPrior;
}else{
@@ -4577,11 +4592,14 @@ static int flattenSubquery(
TREETRACE(0x4,pParse,p,("compound-subquery flattener"
" creates %u as peer\n",pNew->selId));
}
- assert( pSubitem->pSelect==0 );
+ assert( pSubitem->fg.isSubquery==0 );
}
sqlite3DbFree(db, aCsrMap);
if( db->mallocFailed ){
- pSubitem->pSelect = pSub1;
+ assert( pSubitem->fg.fixedSchema==0 );
+ assert( pSubitem->fg.isSubquery==0 );
+ assert( pSubitem->u4.zDatabase==0 );
+ sqlite3SrcItemAttachSubquery(pParse, pSubitem, pSub1, 0);
return 1;
}
@@ -4592,8 +4610,8 @@ static int flattenSubquery(
**
** pSubitem->pTab is always non-NULL by test restrictions and tests above.
*/
- if( ALWAYS(pSubitem->pTab!=0) ){
- Table *pTabToDel = pSubitem->pTab;
+ if( ALWAYS(pSubitem->pSTab!=0) ){
+ Table *pTabToDel = pSubitem->pSTab;
if( pTabToDel->nTabRef==1 ){
Parse *pToplevel = sqlite3ParseToplevel(pParse);
sqlite3ParserAddCleanup(pToplevel, sqlite3DeleteTableGeneric, pTabToDel);
@@ -4601,7 +4619,7 @@ static int flattenSubquery(
}else{
pTabToDel->nTabRef--;
}
- pSubitem->pTab = 0;
+ pSubitem->pSTab = 0;
}
/* The following loop runs once for each term in a compound-subquery
@@ -4657,8 +4675,11 @@ static int flattenSubquery(
*/
for(i=0; ia[i+iFrom];
- if( pItem->fg.isUsing ) sqlite3IdListDelete(db, pItem->u3.pUsing);
assert( pItem->fg.isTabFunc==0 );
+ assert( pItem->fg.isSubquery
+ || pItem->fg.fixedSchema
+ || pItem->u4.zDatabase==0 );
+ if( pItem->fg.isUsing ) sqlite3IdListDelete(db, pItem->u3.pUsing);
*pItem = pSubSrc->a[i];
pItem->fg.jointype |= ltorj;
iNewParent = pSubSrc->a[i].iCursor;
@@ -5342,10 +5363,10 @@ static int disableUnusedSubqueryResultColumns(SrcItem *pItem){
if( pItem->fg.isCorrelated || pItem->fg.isCte ){
return 0;
}
- assert( pItem->pTab!=0 );
- pTab = pItem->pTab;
- assert( pItem->pSelect!=0 );
- pSub = pItem->pSelect;
+ assert( pItem->pSTab!=0 );
+ pTab = pItem->pSTab;
+ assert( pItem->fg.isSubquery );
+ pSub = pItem->u4.pSubq->pSelect;
assert( pSub->pEList->nExpr==pTab->nCol );
for(pX=pSub; pX; pX=pX->pPrior){
if( (pX->selFlags & (SF_Distinct|SF_Aggregate))!=0 ){
@@ -5474,13 +5495,13 @@ static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){
if( p->pWhere
|| p->pEList->nExpr!=1
|| p->pSrc->nSrc!=1
- || p->pSrc->a[0].pSelect
+ || p->pSrc->a[0].fg.isSubquery
|| pAggInfo->nFunc!=1
|| p->pHaving
){
return 0;
}
- pTab = p->pSrc->a[0].pTab;
+ pTab = p->pSrc->a[0].pSTab;
assert( pTab!=0 );
assert( !IsView(pTab) );
if( !IsOrdinaryTable(pTab) ) return 0;
@@ -5505,7 +5526,7 @@ static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){
** pFrom->pIndex and return SQLITE_OK.
*/
int sqlite3IndexedByLookup(Parse *pParse, SrcItem *pFrom){
- Table *pTab = pFrom->pTab;
+ Table *pTab = pFrom->pSTab;
char *zIndexedBy = pFrom->u1.zIndexedBy;
Index *pIdx;
assert( pTab!=0 );
@@ -5582,7 +5603,11 @@ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){
if( pNew==0 ) return WRC_Abort;
memset(&dummy, 0, sizeof(dummy));
pNewSrc = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&dummy,pNew,0);
- if( pNewSrc==0 ) return WRC_Abort;
+ assert( pNewSrc!=0 || pParse->nErr );
+ if( pParse->nErr ){
+ sqlite3SrcListDelete(db, pNewSrc);
+ return WRC_Abort;
+ }
*pNew = *p;
p->pSrc = pNewSrc;
p->pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db, TK_ASTERISK, 0));
@@ -5637,7 +5662,7 @@ static struct Cte *searchWith(
){
const char *zName = pItem->zName;
With *p;
- assert( pItem->zDatabase==0 );
+ assert( pItem->fg.fixedSchema || pItem->u4.zDatabase==0 );
assert( zName!=0 );
for(p=pWith; p; p=p->pOuter){
int i;
@@ -5707,7 +5732,7 @@ static int resolveFromTermToCte(
Cte *pCte; /* Matched CTE (or NULL if no match) */
With *pWith; /* The matching WITH */
- assert( pFrom->pTab==0 );
+ assert( pFrom->pSTab==0 );
if( pParse->pWith==0 ){
/* There are no WITH clauses in the stack. No match is possible */
return 0;
@@ -5717,7 +5742,8 @@ static int resolveFromTermToCte(
** go no further. */
return 0;
}
- if( pFrom->zDatabase!=0 ){
+ assert( pFrom->fg.hadSchema==0 || pFrom->fg.notCte!=0 );
+ if( pFrom->fg.fixedSchema==0 && pFrom->u4.zDatabase!=0 ){
/* The FROM term contains a schema qualifier (ex: main.t1) and so
** it cannot possibly be a CTE reference. */
return 0;
@@ -5753,7 +5779,7 @@ static int resolveFromTermToCte(
}
if( cannotBeFunction(pParse, pFrom) ) return 2;
- assert( pFrom->pTab==0 );
+ assert( pFrom->pSTab==0 );
pTab = sqlite3DbMallocZero(db, sizeof(Table));
if( pTab==0 ) return 2;
pCteUse = pCte->pUse;
@@ -5767,26 +5793,29 @@ static int resolveFromTermToCte(
}
pCteUse->eM10d = pCte->eM10d;
}
- pFrom->pTab = pTab;
+ pFrom->pSTab = pTab;
pTab->nTabRef = 1;
pTab->zName = sqlite3DbStrDup(db, pCte->zName);
pTab->iPKey = -1;
pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) );
pTab->tabFlags |= TF_Ephemeral | TF_NoVisibleRowid;
- pFrom->pSelect = sqlite3SelectDup(db, pCte->pSelect, 0);
+ sqlite3SrcItemAttachSubquery(pParse, pFrom, pCte->pSelect, 1);
if( db->mallocFailed ) return 2;
- pFrom->pSelect->selFlags |= SF_CopyCte;
- assert( pFrom->pSelect );
+ assert( pFrom->fg.isSubquery && pFrom->u4.pSubq );
+ pSel = pFrom->u4.pSubq->pSelect;
+ assert( pSel!=0 );
+ pSel->selFlags |= SF_CopyCte;
if( pFrom->fg.isIndexedBy ){
sqlite3ErrorMsg(pParse, "no such index: \"%s\"", pFrom->u1.zIndexedBy);
return 2;
}
+ assert( !pFrom->fg.isIndexedBy );
pFrom->fg.isCte = 1;
pFrom->u2.pCteUse = pCteUse;
pCteUse->nUse++;
/* Check if this is a recursive CTE. */
- pRecTerm = pSel = pFrom->pSelect;
+ pRecTerm = pSel;
bMayRecursive = ( pSel->op==TK_ALL || pSel->op==TK_UNION );
while( bMayRecursive && pRecTerm->op==pSel->op ){
int i;
@@ -5794,11 +5823,13 @@ static int resolveFromTermToCte(
assert( pRecTerm->pPrior!=0 );
for(i=0; inSrc; i++){
SrcItem *pItem = &pSrc->a[i];
- if( pItem->zDatabase==0
- && pItem->zName!=0
+ if( pItem->zName!=0
+ && !pItem->fg.hadSchema
+ && ALWAYS( !pItem->fg.isSubquery )
+ && (pItem->fg.fixedSchema || pItem->u4.zDatabase==0)
&& 0==sqlite3StrICmp(pItem->zName, pCte->zName)
){
- pItem->pTab = pTab;
+ pItem->pSTab = pTab;
pTab->nTabRef++;
pItem->fg.isRecursive = 1;
if( pRecTerm->selFlags & SF_Recursive ){
@@ -5900,11 +5931,14 @@ void sqlite3SelectPopWith(Walker *pWalker, Select *p){
** SQLITE_NOMEM.
*/
int sqlite3ExpandSubquery(Parse *pParse, SrcItem *pFrom){
- Select *pSel = pFrom->pSelect;
+ Select *pSel;
Table *pTab;
+ assert( pFrom->fg.isSubquery );
+ assert( pFrom->u4.pSubq!=0 );
+ pSel = pFrom->u4.pSubq->pSelect;
assert( pSel );
- pFrom->pTab = pTab = sqlite3DbMallocZero(pParse->db, sizeof(Table));
+ pFrom->pSTab = pTab = sqlite3DbMallocZero(pParse->db, sizeof(Table));
if( pTab==0 ) return SQLITE_NOMEM;
pTab->nTabRef = 1;
if( pFrom->zAlias ){
@@ -6024,33 +6058,35 @@ static int selectExpander(Walker *pWalker, Select *p){
*/
for(i=0, pFrom=pTabList->a; inSrc; i++, pFrom++){
Table *pTab;
- assert( pFrom->fg.isRecursive==0 || pFrom->pTab!=0 );
- if( pFrom->pTab ) continue;
+ assert( pFrom->fg.isRecursive==0 || pFrom->pSTab!=0 );
+ if( pFrom->pSTab ) continue;
assert( pFrom->fg.isRecursive==0 );
if( pFrom->zName==0 ){
#ifndef SQLITE_OMIT_SUBQUERY
- Select *pSel = pFrom->pSelect;
+ Select *pSel;
+ assert( pFrom->fg.isSubquery && pFrom->u4.pSubq!=0 );
+ pSel = pFrom->u4.pSubq->pSelect;
/* A sub-query in the FROM clause of a SELECT */
assert( pSel!=0 );
- assert( pFrom->pTab==0 );
+ assert( pFrom->pSTab==0 );
if( sqlite3WalkSelect(pWalker, pSel) ) return WRC_Abort;
if( sqlite3ExpandSubquery(pParse, pFrom) ) return WRC_Abort;
#endif
#ifndef SQLITE_OMIT_CTE
}else if( (rc = resolveFromTermToCte(pParse, pWalker, pFrom))!=0 ){
if( rc>1 ) return WRC_Abort;
- pTab = pFrom->pTab;
+ pTab = pFrom->pSTab;
assert( pTab!=0 );
#endif
}else{
/* An ordinary table or view name in the FROM clause */
- assert( pFrom->pTab==0 );
- pFrom->pTab = pTab = sqlite3LocateTableItem(pParse, 0, pFrom);
+ assert( pFrom->pSTab==0 );
+ pFrom->pSTab = pTab = sqlite3LocateTableItem(pParse, 0, pFrom);
if( pTab==0 ) return WRC_Abort;
if( pTab->nTabRef>=0xffff ){
sqlite3ErrorMsg(pParse, "too many references to \"%s\": max 65535",
pTab->zName);
- pFrom->pTab = 0;
+ pFrom->pSTab = 0;
return WRC_Abort;
}
pTab->nTabRef++;
@@ -6062,7 +6098,7 @@ static int selectExpander(Walker *pWalker, Select *p){
i16 nCol;
u8 eCodeOrig = pWalker->eCode;
if( sqlite3ViewGetColumnNames(pParse, pTab) ) return WRC_Abort;
- assert( pFrom->pSelect==0 );
+ assert( pFrom->fg.isSubquery==0 );
if( IsView(pTab) ){
if( (db->flags & SQLITE_EnableView)==0
&& pTab->pSchema!=db->aDb[1].pSchema
@@ -6070,7 +6106,7 @@ static int selectExpander(Walker *pWalker, Select *p){
sqlite3ErrorMsg(pParse, "access to view \"%s\" prohibited",
pTab->zName);
}
- pFrom->pSelect = sqlite3SelectDup(db, pTab->u.view.pSelect, 0);
+ sqlite3SrcItemAttachSubquery(pParse, pFrom, pTab->u.view.pSelect, 1);
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
else if( ALWAYS(IsVirtual(pTab))
@@ -6086,7 +6122,9 @@ static int selectExpander(Walker *pWalker, Select *p){
nCol = pTab->nCol;
pTab->nCol = -1;
pWalker->eCode = 1; /* Turn on Select.selId renumbering */
- sqlite3WalkSelect(pWalker, pFrom->pSelect);
+ if( pFrom->fg.isSubquery ){
+ sqlite3WalkSelect(pWalker, pFrom->u4.pSubq->pSelect);
+ }
pWalker->eCode = eCodeOrig;
pTab->nCol = nCol;
}
@@ -6173,7 +6211,7 @@ static int selectExpander(Walker *pWalker, Select *p){
}
for(i=0, pFrom=pTabList->a; inSrc; i++, pFrom++){
int nAdd; /* Number of cols including rowid */
- Table *pTab = pFrom->pTab; /* Table for this data source */
+ Table *pTab = pFrom->pSTab; /* Table for this data source */
ExprList *pNestedFrom; /* Result-set of a nested FROM clause */
char *zTabName; /* AS name for this data source */
const char *zSchemaName = 0; /* Schema name for this data source */
@@ -6184,10 +6222,11 @@ static int selectExpander(Walker *pWalker, Select *p){
zTabName = pTab->zName;
}
if( db->mallocFailed ) break;
- assert( (int)pFrom->fg.isNestedFrom == IsNestedFrom(pFrom->pSelect) );
+ assert( (int)pFrom->fg.isNestedFrom == IsNestedFrom(pFrom) );
if( pFrom->fg.isNestedFrom ){
- assert( pFrom->pSelect!=0 );
- pNestedFrom = pFrom->pSelect->pEList;
+ assert( pFrom->fg.isSubquery && pFrom->u4.pSubq );
+ assert( pFrom->u4.pSubq->pSelect!=0 );
+ pNestedFrom = pFrom->u4.pSubq->pSelect->pEList;
assert( pNestedFrom!=0 );
assert( pNestedFrom->nExpr==pTab->nCol );
assert( VisibleRowid(pTab)==0 || ViewCanHaveRowid );
@@ -6426,14 +6465,12 @@ static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){
assert( (p->selFlags & SF_Resolved) );
pTabList = p->pSrc;
for(i=0, pFrom=pTabList->a; inSrc; i++, pFrom++){
- Table *pTab = pFrom->pTab;
+ Table *pTab = pFrom->pSTab;
assert( pTab!=0 );
- if( (pTab->tabFlags & TF_Ephemeral)!=0 ){
+ if( (pTab->tabFlags & TF_Ephemeral)!=0 && pFrom->fg.isSubquery ){
/* A sub-query in the FROM clause of a SELECT */
- Select *pSel = pFrom->pSelect;
- if( pSel ){
- sqlite3SubqueryColumnTypes(pParse, pTab, pSel, SQLITE_AFF_NONE);
- }
+ Select *pSel = pFrom->u4.pSubq->pSelect;
+ sqlite3SubqueryColumnTypes(pParse, pTab, pSel, SQLITE_AFF_NONE);
}
}
}
@@ -6747,6 +6784,7 @@ static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){
for(i=0, pF=pAggInfo->aFunc; inFunc; i++, pF++){
ExprList *pList;
assert( ExprUseXList(pF->pFExpr) );
+ if( pParse->nErr ) return;
pList = pF->pFExpr->x.pList;
if( pF->iOBTab>=0 ){
/* For an ORDER BY aggregate, calls to OP_AggStep were deferred. Inputs
@@ -6956,6 +6994,7 @@ static void updateAccumulator(
if( addrNext ){
sqlite3VdbeResolveLabel(v, addrNext);
}
+ if( pParse->nErr ) return;
}
if( regHit==0 && pAggInfo->nAccumulator ){
regHit = regAcc;
@@ -6965,6 +7004,7 @@ static void updateAccumulator(
}
for(i=0, pC=pAggInfo->aCol; inAccumulator; i++, pC++){
sqlite3ExprCode(pParse, pC->pCExpr, AggInfoColumnReg(pAggInfo,i));
+ if( pParse->nErr ) return;
}
pAggInfo->directMode = 0;
@@ -7080,25 +7120,28 @@ static SrcItem *isSelfJoinView(
int iFirst, int iEnd /* Range of FROM-clause entries to search. */
){
SrcItem *pItem;
- assert( pThis->pSelect!=0 );
- if( pThis->pSelect->selFlags & SF_PushDown ) return 0;
+ Select *pSel;
+ assert( pThis->fg.isSubquery );
+ pSel = pThis->u4.pSubq->pSelect;
+ assert( pSel!=0 );
+ if( pSel->selFlags & SF_PushDown ) return 0;
while( iFirsta[iFirst++];
- if( pItem->pSelect==0 ) continue;
+ if( !pItem->fg.isSubquery ) continue;
if( pItem->fg.viaCoroutine ) continue;
if( pItem->zName==0 ) continue;
- assert( pItem->pTab!=0 );
- assert( pThis->pTab!=0 );
- if( pItem->pTab->pSchema!=pThis->pTab->pSchema ) continue;
+ assert( pItem->pSTab!=0 );
+ assert( pThis->pSTab!=0 );
+ if( pItem->pSTab->pSchema!=pThis->pSTab->pSchema ) continue;
if( sqlite3_stricmp(pItem->zName, pThis->zName)!=0 ) continue;
- pS1 = pItem->pSelect;
- if( pItem->pTab->pSchema==0 && pThis->pSelect->selId!=pS1->selId ){
+ pS1 = pItem->u4.pSubq->pSelect;
+ if( pItem->pSTab->pSchema==0 && pSel->selId!=pS1->selId ){
/* The query flattener left two different CTE tables with identical
** names in the same FROM clause. */
continue;
}
- if( pItem->pSelect->selFlags & SF_PushDown ){
+ if( pS1->selFlags & SF_PushDown ){
/* The view was modified by some other optimization such as
** pushDownWhereTerms() */
continue;
@@ -7142,6 +7185,7 @@ static int countOfViewOptimization(Parse *pParse, Select *p){
Expr *pExpr;
Expr *pCount;
sqlite3 *db;
+ SrcItem *pFrom;
if( (p->selFlags & SF_Aggregate)==0 ) return 0; /* This is an aggregate */
if( p->pEList->nExpr!=1 ) return 0; /* Single result column */
if( p->pWhere ) return 0;
@@ -7156,8 +7200,9 @@ static int countOfViewOptimization(Parse *pParse, Select *p){
if( pExpr->x.pList!=0 ) return 0; /* Must be count(*) */
if( p->pSrc->nSrc!=1 ) return 0; /* One table in FROM */
if( ExprHasProperty(pExpr, EP_WinFunc) ) return 0;/* Not a window function */
- pSub = p->pSrc->a[0].pSelect;
- if( pSub==0 ) return 0; /* The FROM is a subquery */
+ pFrom = p->pSrc->a;
+ if( pFrom->fg.isSubquery==0 ) return 0; /* FROM is a subquery */
+ pSub = pFrom->u4.pSubq->pSelect;
if( pSub->pPrior==0 ) return 0; /* Must be a compound */
if( pSub->selFlags & SF_CopyCte ) return 0; /* Not a CTE */
do{
@@ -7166,7 +7211,7 @@ static int countOfViewOptimization(Parse *pParse, Select *p){
if( pSub->pLimit ) return 0; /* No LIMIT clause */
if( pSub->selFlags & SF_Aggregate ) return 0; /* Not an aggregate */
assert( pSub->pHaving==0 ); /* Due to the previous */
- pSub = pSub->pPrior; /* Repeat over compound */
+ pSub = pSub->pPrior; /* Repeat over compound */
}while( pSub );
/* If we reach this point then it is OK to perform the transformation */
@@ -7174,8 +7219,7 @@ static int countOfViewOptimization(Parse *pParse, Select *p){
db = pParse->db;
pCount = pExpr;
pExpr = 0;
- pSub = p->pSrc->a[0].pSelect;
- p->pSrc->a[0].pSelect = 0;
+ pSub = sqlite3SubqueryDetach(db, pFrom);
sqlite3SrcListDelete(db, p->pSrc);
p->pSrc = sqlite3DbMallocZero(pParse->db, sizeof(*p->pSrc));
while( pSub ){
@@ -7220,12 +7264,12 @@ static int sameSrcAlias(SrcItem *p0, SrcList *pSrc){
for(i=0; inSrc; i++){
SrcItem *p1 = &pSrc->a[i];
if( p1==p0 ) continue;
- if( p0->pTab==p1->pTab && 0==sqlite3_stricmp(p0->zAlias, p1->zAlias) ){
+ if( p0->pSTab==p1->pSTab && 0==sqlite3_stricmp(p0->zAlias, p1->zAlias) ){
return 1;
}
- if( p1->pSelect
- && (p1->pSelect->selFlags & SF_NestedFrom)!=0
- && sameSrcAlias(p0, p1->pSelect->pSrc)
+ if( p1->fg.isSubquery
+ && (p1->u4.pSubq->pSelect->selFlags & SF_NestedFrom)!=0
+ && sameSrcAlias(p0, p1->u4.pSubq->pSelect->pSrc)
){
return 1;
}
@@ -7290,13 +7334,13 @@ static int fromClauseTermCanBeCoroutine(
if( i==0 ) break;
i--;
pItem--;
- if( pItem->pSelect!=0 ) return 0; /* (1c-i) */
+ if( pItem->fg.isSubquery ) return 0; /* (1c-i) */
}
return 1;
}
/*
-** Generate code for the SELECT statement given in the p argument.
+** Generate byte-code for the SELECT statement given in the p argument.
**
** The results are returned according to the SelectDest structure.
** See comments in sqliteInt.h for further information.
@@ -7307,6 +7351,40 @@ static int fromClauseTermCanBeCoroutine(
**
** This routine does NOT free the Select structure passed in. The
** calling function needs to do that.
+**
+** This is a long function. The following is an outline of the processing
+** steps, with tags referencing various milestones:
+**
+** * Resolve names and similar preparation tag-select-0100
+** * Scan of the FROM clause tag-select-0200
+** + OUTER JOIN strength reduction tag-select-0220
+** + Sub-query ORDER BY removal tag-select-0230
+** + Query flattening tag-select-0240
+** * Separate subroutine for compound-SELECT tag-select-0300
+** * WHERE-clause constant propagation tag-select-0330
+** * Count()-of-VIEW optimization tag-select-0350
+** * Scan of the FROM clause again tag-select-0400
+** + Authorize unreferenced tables tag-select-0410
+** + Predicate push-down optimization tag-select-0420
+** + Omit unused subquery columns optimization tag-select-0440
+** + Generate code to implement subqueries tag-select-0480
+** - Co-routines tag-select-0482
+** - Reuse previously computed CTE tag-select-0484
+** - REuse previously computed VIEW tag-select-0486
+** - Materialize a VIEW or CTE tag-select-0488
+** * DISTINCT ORDER BY -> GROUP BY optimization tag-select-0500
+** * Set up for ORDER BY tag-select-0600
+** * Create output table tag-select-0630
+** * Prepare registers for LIMIT tag-select-0650
+** * Setup for DISTINCT tag-select-0680
+** * Generate code for non-aggregate and non-GROUP BY tag-select-0700
+** * Generate code for aggregate and/or GROUP BY tag-select-0800
+** + GROUP BY queries tag-select-0810
+** + non-GROUP BY queries tag-select-0820
+** - Special case of count() w/o GROUP BY tag-select-0821
+** - General case of non-GROUP BY aggregates tag-select-0822
+** * Sort results, as needed tag-select-0900
+** * Internal self-checks tag-select-1000
*/
int sqlite3Select(
Parse *pParse, /* The parser context */
@@ -7350,6 +7428,7 @@ int sqlite3Select(
}
#endif
+ /* tag-select-0100 */
assert( p->pOrderBy==0 || pDest->eDest!=SRT_DistFifo );
assert( p->pOrderBy==0 || pDest->eDest!=SRT_Fifo );
assert( p->pOrderBy==0 || pDest->eDest!=SRT_DistQueue );
@@ -7401,7 +7480,7 @@ int sqlite3Select(
if( sameSrcAlias(p0, p->pSrc) ){
sqlite3ErrorMsg(pParse,
"target object/alias may not appear in FROM clause: %s",
- p0->zAlias ? p0->zAlias : p0->pTab->zName
+ p0->zAlias ? p0->zAlias : p0->pSTab->zName
);
goto select_end;
}
@@ -7436,12 +7515,13 @@ int sqlite3Select(
/* Try to do various optimizations (flattening subqueries, and strength
** reduction of join operators) in the FROM clause up into the main query
+ ** tag-select-0200
*/
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
for(i=0; !p->pPrior && inSrc; i++){
SrcItem *pItem = &pTabList->a[i];
- Select *pSub = pItem->pSelect;
- Table *pTab = pItem->pTab;
+ Select *pSub = pItem->fg.isSubquery ? pItem->u4.pSubq->pSelect : 0;
+ Table *pTab = pItem->pSTab;
/* The expander should have already created transient Table objects
** even for FROM clause elements such as subqueries that do not correspond
@@ -7458,6 +7538,7 @@ int sqlite3Select(
** way that the i-th table cannot be the NULL row of a join, then
** perform the appropriate simplification. This is called
** "OUTER JOIN strength reduction" in the SQLite documentation.
+ ** tag-select-0220
*/
if( (pItem->fg.jointype & (JT_LEFT|JT_LTORJ))!=0
&& sqlite3ExprImpliesNonNullRow(p->pWhere, pItem->iCursor,
@@ -7528,7 +7609,8 @@ int sqlite3Select(
if( (pSub->selFlags & SF_Aggregate)!=0 ) continue;
assert( pSub->pGroupBy==0 );
- /* If a FROM-clause subquery has an ORDER BY clause that is not
+ /* tag-select-0230:
+ ** If a FROM-clause subquery has an ORDER BY clause that is not
** really doing anything, then delete it now so that it does not
** interfere with query flattening. See the discussion at
** https://sqlite.org/forum/forumpost/2d76f2bcf65d256a
@@ -7594,6 +7676,7 @@ int sqlite3Select(
continue;
}
+ /* tag-select-0240 */
if( flattenSubquery(pParse, p, i, isAgg) ){
if( pParse->nErr ) goto select_end;
/* This subquery can be absorbed into its parent. */
@@ -7609,7 +7692,7 @@ int sqlite3Select(
#ifndef SQLITE_OMIT_COMPOUND_SELECT
/* Handle compound SELECT statements using the separate multiSelect()
- ** procedure.
+ ** procedure. tag-select-0300
*/
if( p->pPrior ){
rc = multiSelect(pParse, p, pDest);
@@ -7625,9 +7708,9 @@ int sqlite3Select(
#endif
/* Do the WHERE-clause constant propagation optimization if this is
- ** a join. No need to speed time on this operation for non-join queries
+ ** a join. No need to spend time on this operation for non-join queries
** as the equivalent optimization will be handled by query planner in
- ** sqlite3WhereBegin().
+ ** sqlite3WhereBegin(). tag-select-0330
*/
if( p->pWhere!=0
&& p->pWhere->op==TK_AND
@@ -7644,6 +7727,7 @@ int sqlite3Select(
TREETRACE(0x2000,pParse,p,("Constant propagation not helpful\n"));
}
+ /* tag-select-0350 */
if( OptimizationEnabled(db, SQLITE_QueryFlattener|SQLITE_CountOfView)
&& countOfViewOptimization(pParse, p)
){
@@ -7651,20 +7735,26 @@ int sqlite3Select(
pTabList = p->pSrc;
}
- /* For each term in the FROM clause, do two things:
- ** (1) Authorized unreferenced tables
- ** (2) Generate code for all sub-queries
+ /* Loop over all terms in the FROM clause and do two things for each term:
+ **
+ ** (1) Authorize unreferenced tables
+ ** (2) Generate code for all sub-queries
+ **
+ ** tag-select-0400
*/
for(i=0; inSrc; i++){
SrcItem *pItem = &pTabList->a[i];
SrcItem *pPrior;
SelectDest dest;
+ Subquery *pSubq;
Select *pSub;
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
const char *zSavedAuthContext;
#endif
- /* Issue SQLITE_READ authorizations with a fake column name for any
+ /* Authorized unreferenced tables. tag-select-0410
+ **
+ ** Issue SQLITE_READ authorizations with a fake column name for any
** tables that are referenced but from which no values are extracted.
** Examples of where these kinds of null SQLITE_READ authorizations
** would occur:
@@ -7681,17 +7771,28 @@ int sqlite3Select(
** string for the fake column name seems safer.
*/
if( pItem->colUsed==0 && pItem->zName!=0 ){
- sqlite3AuthCheck(pParse, SQLITE_READ, pItem->zName, "", pItem->zDatabase);
+ const char *zDb;
+ if( pItem->fg.fixedSchema ){
+ int iDb = sqlite3SchemaToIndex(pParse->db, pItem->u4.pSchema);
+ zDb = db->aDb[iDb].zDbSName;
+ }else if( pItem->fg.isSubquery ){
+ zDb = 0;
+ }else{
+ zDb = pItem->u4.zDatabase;
+ }
+ sqlite3AuthCheck(pParse, SQLITE_READ, pItem->zName, "", zDb);
}
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
/* Generate code for all sub-queries in the FROM clause
*/
- pSub = pItem->pSelect;
- if( pSub==0 || pItem->addrFillSub!=0 ) continue;
+ if( pItem->fg.isSubquery==0 ) continue;
+ pSubq = pItem->u4.pSubq;
+ assert( pSubq!=0 );
+ pSub = pSubq->pSelect;
/* The code for a subquery should only be generated once. */
- assert( pItem->addrFillSub==0 );
+ if( pSubq->addrFillSub!=0 ) continue;
/* Increment Parse.nHeight by the height of the largest expression
** tree referred to by this, the parent select. The child select
@@ -7704,6 +7805,7 @@ int sqlite3Select(
/* Make copies of constant WHERE-clause terms in the outer query down
** inside the subquery. This can help the subquery to run more efficiently.
+ ** This is the "predicate push-down optimization". tag-select-0420
*/
if( OptimizationEnabled(db, SQLITE_PushDown)
&& (pItem->fg.isCte==0
@@ -7717,13 +7819,14 @@ int sqlite3Select(
sqlite3TreeViewSelect(0, p, 0);
}
#endif
- assert( pItem->pSelect && (pItem->pSelect->selFlags & SF_PushDown)!=0 );
+ assert( pSubq->pSelect && (pSub->selFlags & SF_PushDown)!=0 );
}else{
TREETRACE(0x4000,pParse,p,("WHERE-lcause push-down not possible\n"));
}
/* Convert unused result columns of the subquery into simple NULL
** expressions, to avoid unneeded searching and computation.
+ ** tag-select-0440
*/
if( OptimizationEnabled(db, SQLITE_NullUnusedCols)
&& disableUnusedSubqueryResultColumns(pItem)
@@ -7741,32 +7844,33 @@ int sqlite3Select(
zSavedAuthContext = pParse->zAuthContext;
pParse->zAuthContext = pItem->zName;
- /* Generate code to implement the subquery
+ /* Generate byte-code to implement the subquery tag-select-0480
*/
if( fromClauseTermCanBeCoroutine(pParse, pTabList, i, p->selFlags) ){
/* Implement a co-routine that will return a single row of the result
- ** set on each invocation.
+ ** set on each invocation. tag-select-0482
*/
int addrTop = sqlite3VdbeCurrentAddr(v)+1;
- pItem->regReturn = ++pParse->nMem;
- sqlite3VdbeAddOp3(v, OP_InitCoroutine, pItem->regReturn, 0, addrTop);
+ pSubq->regReturn = ++pParse->nMem;
+ sqlite3VdbeAddOp3(v, OP_InitCoroutine, pSubq->regReturn, 0, addrTop);
VdbeComment((v, "%!S", pItem));
- pItem->addrFillSub = addrTop;
- sqlite3SelectDestInit(&dest, SRT_Coroutine, pItem->regReturn);
+ pSubq->addrFillSub = addrTop;
+ sqlite3SelectDestInit(&dest, SRT_Coroutine, pSubq->regReturn);
ExplainQueryPlan((pParse, 1, "CO-ROUTINE %!S", pItem));
sqlite3Select(pParse, pSub, &dest);
- pItem->pTab->nRowLogEst = pSub->nSelectRow;
+ pItem->pSTab->nRowLogEst = pSub->nSelectRow;
pItem->fg.viaCoroutine = 1;
- pItem->regResult = dest.iSdst;
- sqlite3VdbeEndCoroutine(v, pItem->regReturn);
+ pSubq->regResult = dest.iSdst;
+ sqlite3VdbeEndCoroutine(v, pSubq->regReturn);
+ VdbeComment((v, "end %!S", pItem));
sqlite3VdbeJumpHere(v, addrTop-1);
sqlite3ClearTempRegCache(pParse);
}else if( pItem->fg.isCte && pItem->u2.pCteUse->addrM9e>0 ){
/* This is a CTE for which materialization code has already been
** generated. Invoke the subroutine to compute the materialization,
- ** the make the pItem->iCursor be a copy of the ephemeral table that
- ** holds the result of the materialization. */
+ ** then make the pItem->iCursor be a copy of the ephemeral table that
+ ** holds the result of the materialization. tag-select-0484 */
CteUse *pCteUse = pItem->u2.pCteUse;
sqlite3VdbeAddOp2(v, OP_Gosub, pCteUse->regRtn, pCteUse->addrM9e);
if( pItem->iCursor!=pCteUse->iCur ){
@@ -7776,25 +7880,30 @@ int sqlite3Select(
pSub->nSelectRow = pCteUse->nRowEst;
}else if( (pPrior = isSelfJoinView(pTabList, pItem, 0, i))!=0 ){
/* This view has already been materialized by a prior entry in
- ** this same FROM clause. Reuse it. */
- if( pPrior->addrFillSub ){
- sqlite3VdbeAddOp2(v, OP_Gosub, pPrior->regReturn, pPrior->addrFillSub);
+ ** this same FROM clause. Reuse it. tag-select-0486 */
+ Subquery *pPriorSubq;
+ assert( pPrior->fg.isSubquery );
+ pPriorSubq = pPrior->u4.pSubq;
+ assert( pPriorSubq!=0 );
+ if( pPriorSubq->addrFillSub ){
+ sqlite3VdbeAddOp2(v, OP_Gosub, pPriorSubq->regReturn,
+ pPriorSubq->addrFillSub);
}
sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pPrior->iCursor);
- pSub->nSelectRow = pPrior->pSelect->nSelectRow;
+ pSub->nSelectRow = pPriorSubq->pSelect->nSelectRow;
}else{
/* Materialize the view. If the view is not correlated, generate a
** subroutine to do the materialization so that subsequent uses of
- ** the same view can reuse the materialization. */
+ ** the same view can reuse the materialization. tag-select-0488 */
int topAddr;
int onceAddr = 0;
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
int addrExplain;
#endif
- pItem->regReturn = ++pParse->nMem;
+ pSubq->regReturn = ++pParse->nMem;
topAddr = sqlite3VdbeAddOp0(v, OP_Goto);
- pItem->addrFillSub = topAddr+1;
+ pSubq->addrFillSub = topAddr+1;
pItem->fg.isMaterialized = 1;
if( pItem->fg.isCorrelated==0 ){
/* If the subquery is not correlated and if we are not inside of
@@ -7809,17 +7918,17 @@ int sqlite3Select(
ExplainQueryPlan2(addrExplain, (pParse, 1, "MATERIALIZE %!S", pItem));
sqlite3Select(pParse, pSub, &dest);
- pItem->pTab->nRowLogEst = pSub->nSelectRow;
+ pItem->pSTab->nRowLogEst = pSub->nSelectRow;
if( onceAddr ) sqlite3VdbeJumpHere(v, onceAddr);
- sqlite3VdbeAddOp2(v, OP_Return, pItem->regReturn, topAddr+1);
+ sqlite3VdbeAddOp2(v, OP_Return, pSubq->regReturn, topAddr+1);
VdbeComment((v, "end %!S", pItem));
sqlite3VdbeScanStatusRange(v, addrExplain, addrExplain, -1);
sqlite3VdbeJumpHere(v, topAddr);
sqlite3ClearTempRegCache(pParse);
if( pItem->fg.isCte && pItem->fg.isCorrelated==0 ){
CteUse *pCteUse = pItem->u2.pCteUse;
- pCteUse->addrM9e = pItem->addrFillSub;
- pCteUse->regRtn = pItem->regReturn;
+ pCteUse->addrM9e = pSubq->addrFillSub;
+ pCteUse->regRtn = pSubq->regReturn;
pCteUse->iCur = pItem->iCursor;
pCteUse->nRowEst = pSub->nSelectRow;
}
@@ -7845,7 +7954,9 @@ int sqlite3Select(
}
#endif
- /* If the query is DISTINCT with an ORDER BY but is not an aggregate, and
+ /* tag-select-0500
+ **
+ ** If the query is DISTINCT with an ORDER BY but is not an aggregate, and
** if the select-list is the same as the ORDER BY list, then this query
** can be rewritten as a GROUP BY. In other words, this:
**
@@ -7895,7 +8006,7 @@ int sqlite3Select(
** If that is the case, then the OP_OpenEphemeral instruction will be
** changed to an OP_Noop once we figure out that the sorting index is
** not needed. The sSort.addrSortIndex variable is used to facilitate
- ** that change.
+ ** that change. tag-select-0600
*/
if( sSort.pOrderBy ){
KeyInfo *pKeyInfo;
@@ -7912,6 +8023,7 @@ int sqlite3Select(
}
/* If the output is destined for a temporary table, open that table.
+ ** tag-select-0630
*/
if( pDest->eDest==SRT_EphemTab ){
sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pDest->iSDParm, pEList->nExpr);
@@ -7929,7 +8041,7 @@ int sqlite3Select(
}
}
- /* Set the limiter.
+ /* Set the limiter. tag-select-0650
*/
iEnd = sqlite3VdbeMakeLabel(pParse);
if( (p->selFlags & SF_FixedLimit)==0 ){
@@ -7941,7 +8053,7 @@ int sqlite3Select(
sSort.sortFlags |= SORTFLAG_UseSorter;
}
- /* Open an ephemeral index to use for the distinct set.
+ /* Open an ephemeral index to use for the distinct set. tag-select-0680
*/
if( p->selFlags & SF_Distinct ){
sDistinct.tabTnct = pParse->nTab++;
@@ -7956,7 +8068,7 @@ int sqlite3Select(
}
if( !isAgg && pGroupBy==0 ){
- /* No aggregate functions and no GROUP BY clause */
+ /* No aggregate functions and no GROUP BY clause. tag-select-0700 */
u16 wctrlFlags = (sDistinct.isTnct ? WHERE_WANT_DISTINCT : 0)
| (p->selFlags & SF_FixedLimit);
#ifndef SQLITE_OMIT_WINDOWFUNC
@@ -8029,8 +8141,8 @@ int sqlite3Select(
sqlite3WhereEnd(pWInfo);
}
}else{
- /* This case when there exist aggregate functions or a GROUP BY clause
- ** or both */
+ /* This case is for when there exist aggregate functions or a GROUP BY
+ ** clause or both. tag-select-0800 */
NameContext sNC; /* Name context for processing aggregate information */
int iAMem; /* First Mem address for storing current GROUP BY */
int iBMem; /* First Mem address for previous GROUP BY */
@@ -8149,7 +8261,7 @@ int sqlite3Select(
/* Processing for aggregates with GROUP BY is very different and
- ** much more complex than aggregates without a GROUP BY.
+ ** much more complex than aggregates without a GROUP BY. tag-select-0810
*/
if( pGroupBy ){
KeyInfo *pKeyInfo; /* Keying information for the group by clause */
@@ -8446,9 +8558,12 @@ int sqlite3Select(
}
} /* endif pGroupBy. Begin aggregate queries without GROUP BY: */
else {
+ /* Aggregate functions without GROUP BY. tag-select-0820 */
Table *pTab;
if( (pTab = isSimpleCount(p, pAggInfo))!=0 ){
- /* If isSimpleCount() returns a pointer to a Table structure, then
+ /* tag-select-0821
+ **
+ ** If isSimpleCount() returns a pointer to a Table structure, then
** the SQL statement is of the form:
**
** SELECT count(*) FROM
@@ -8507,6 +8622,8 @@ int sqlite3Select(
sqlite3VdbeAddOp1(v, OP_Close, iCsr);
explainSimpleCount(pParse, pTab, pBest);
}else{
+ /* The general case of an aggregate query without GROUP BY
+ ** tag-select-0822 */
int regAcc = 0; /* "populate accumulators" flag */
ExprList *pDistinct = 0;
u16 distFlag = 0;
@@ -8595,7 +8712,7 @@ int sqlite3Select(
}
/* If there is an ORDER BY clause, then we need to sort the results
- ** and send them to the callback one by one.
+ ** and send them to the callback one by one. tag-select-0900
*/
if( sSort.pOrderBy ){
assert( p->pEList==pEList );
@@ -8618,6 +8735,7 @@ select_end:
assert( db->mallocFailed==0 || pParse->nErr!=0 );
sqlite3ExprListDelete(db, pMinMaxOrderBy);
#ifdef SQLITE_DEBUG
+ /* Internal self-checks. tag-select-1000 */
if( pAggInfo && !db->mallocFailed ){
#if TREETRACE_ENABLED
if( sqlite3TreeTrace & 0x20 ){
diff --git a/src/shell.c.in b/src/shell.c.in
index 5b0a1a84b0..b8f9327948 100644
--- a/src/shell.c.in
+++ b/src/shell.c.in
@@ -246,6 +246,7 @@ extern LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText);
# define SQLITE_CIO_NO_CLASSIFY
# define SQLITE_CIO_NO_TRANSLATE
# define SQLITE_CIO_NO_SETMODE
+# define SQLITE_CIO_NO_FLUSH
#endif
INCLUDE ../ext/consio/console_io.h
INCLUDE ../ext/consio/console_io.c
@@ -279,11 +280,12 @@ INCLUDE ../ext/consio/console_io.c
# define eputz(z) ePutsUtf8(z)
# define eputf ePrintfUtf8
# define oputb(buf,na) oPutbUtf8(buf,na)
+# define fflush(s) fFlushBuffer(s);
#else
/* For Fiddle, all console handling and emit redirection is omitted. */
/* These next 3 macros are for emitting formatted output. When complaints
- * from the WASM build are issued for non-formatted output, (when a mere
+ * from the WASM build are issued for non-formatted output, when a mere
* string literal is to be emitted, the ?putz(z) forms should be used.
* (This permits compile-time checking of format string / argument mismatch.)
*/
@@ -295,6 +297,7 @@ INCLUDE ../ext/consio/console_io.c
# define eputz(z) fputs(z,stderr)
# define sputz(fp,z) fputs(z,fp)
# define oputb(buf,na) fwrite(buf,1,na,stdout)
+# undef fflush
#endif
/* True if the timer is enabled */
@@ -534,6 +537,14 @@ static char *shell_strncpy(char *dest, const char *src, size_t n){
return dest;
}
+/*
+** strcpy() workalike to squelch an unwarranted link-time warning
+** from OpenBSD.
+*/
+static void shell_strcpy(char *dest, const char *src){
+ while( (*(dest++) = *(src++))!=0 ){}
+}
+
/*
** Optionally disable dynamic continuation prompt.
** Unless disabled, the continuation prompt shows open SQL lexemes if any,
@@ -599,7 +610,7 @@ static char *dynamicContinuePrompt(void){
size_t ncp = strlen(continuePrompt);
size_t ndp = strlen(dynPrompt.zScannerAwaits);
if( ndp > ncp-3 ) return continuePrompt;
- strcpy(dynPrompt.dynamicPrompt, dynPrompt.zScannerAwaits);
+ shell_strcpy(dynPrompt.dynamicPrompt, dynPrompt.zScannerAwaits);
while( ndp<3 ) dynPrompt.dynamicPrompt[ndp++] = ' ';
shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3,
PROMPT_LEN_MAX-4);
@@ -3548,10 +3559,11 @@ static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){
}else if( strncmp(zVar, "$int_", 5)==0 ){
sqlite3_bind_int(pStmt, i, atoi(&zVar[5]));
}else if( strncmp(zVar, "$text_", 6)==0 ){
- char *zBuf = sqlite3_malloc64( strlen(zVar)-5 );
+ size_t szVar = strlen(zVar);
+ char *zBuf = sqlite3_malloc64( szVar-5 );
if( zBuf ){
- memcpy(zBuf, &zVar[6], strlen(zVar)-5);
- sqlite3_bind_text64(pStmt, i, zBuf, -1, sqlite3_free, SQLITE_UTF8);
+ memcpy(zBuf, &zVar[6], szVar-5);
+ sqlite3_bind_text64(pStmt, i, zBuf, szVar-6, sqlite3_free, SQLITE_UTF8);
}
}else{
sqlite3_bind_null(pStmt, i);
@@ -6343,12 +6355,17 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){
}
#endif /* SQLITE_SHELL_HAVE_RECOVER */
+/*
+** Print the given string as an error message.
+*/
+static void shellEmitError(const char *zErr){
+ eputf("Error: %s\n", zErr);
+}
/*
** Print the current sqlite3_errmsg() value to stderr and return 1.
*/
static int shellDatabaseError(sqlite3 *db){
- const char *zErr = sqlite3_errmsg(db);
- eputf("Error: %s\n", zErr);
+ shellEmitError(sqlite3_errmsg(db));
return 1;
}
@@ -6893,7 +6910,7 @@ static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){
va_start(ap, zFmt);
z = sqlite3_vmprintf(zFmt, ap);
va_end(ap);
- eputf("Error: %s\n", z);
+ shellEmitError(z);
if( pAr->fromCmdLine ){
eputz("Use \"-A\" for more help\n");
}else{
@@ -8124,7 +8141,7 @@ static int do_meta_command(char *zLine, ShellState *p){
open_db(p, 0);
pBackup = sqlite3_backup_init(pDest, "main", p->db, zDb);
if( pBackup==0 ){
- eputf("Error: %s\n", sqlite3_errmsg(pDest));
+ shellDatabaseError(pDest);
close_db(pDest);
return 1;
}
@@ -8133,7 +8150,7 @@ static int do_meta_command(char *zLine, ShellState *p){
if( rc==SQLITE_DONE ){
rc = 0;
}else{
- eputf("Error: %s\n", sqlite3_errmsg(pDest));
+ shellDatabaseError(pDest);
rc = 1;
}
close_db(pDest);
@@ -8309,7 +8326,7 @@ static int do_meta_command(char *zLine, ShellState *p){
open_db(p, 0);
rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
if( rc ){
- eputf("Error: %s\n", sqlite3_errmsg(p->db));
+ shellDatabaseError(p->db);
rc = 1;
}else{
while( sqlite3_step(pStmt)==SQLITE_ROW ){
@@ -9005,7 +9022,7 @@ static int do_meta_command(char *zLine, ShellState *p){
zSql = 0;
if( rc ){
if (pStmt) sqlite3_finalize(pStmt);
- eputf("Error: %s\n", sqlite3_errmsg(p->db));
+ shellDatabaseError(p->db);
import_cleanup(&sCtx);
rc = 1;
goto meta_command_exit;
@@ -9049,7 +9066,7 @@ static int do_meta_command(char *zLine, ShellState *p){
sqlite3_free(zSql);
zSql = 0;
if( rc ){
- eputf("Error: %s\n", sqlite3_errmsg(p->db));
+ shellDatabaseError(p->db);
if (pStmt) sqlite3_finalize(pStmt);
import_cleanup(&sCtx);
rc = 1;
@@ -9343,7 +9360,7 @@ static int do_meta_command(char *zLine, ShellState *p){
open_db(p, 0);
rc = sqlite3_load_extension(p->db, zFile, zProc, &zErrMsg);
if( rc!=SQLITE_OK ){
- eputf("Error: %s\n", zErrMsg);
+ shellEmitError(zErrMsg);
sqlite3_free(zErrMsg);
rc = 1;
}
@@ -9965,7 +9982,7 @@ static int do_meta_command(char *zLine, ShellState *p){
open_db(p, 0);
pBackup = sqlite3_backup_init(p->db, zDb, pSrc, "main");
if( pBackup==0 ){
- eputf("Error: %s\n", sqlite3_errmsg(p->db));
+ shellDatabaseError(p->db);
close_db(pSrc);
return 1;
}
@@ -9983,7 +10000,7 @@ static int do_meta_command(char *zLine, ShellState *p){
eputz("Error: source database is busy\n");
rc = 1;
}else{
- eputf("Error: %s\n", sqlite3_errmsg(p->db));
+ shellDatabaseError(p->db);
rc = 1;
}
close_db(pSrc);
@@ -10080,7 +10097,7 @@ static int do_meta_command(char *zLine, ShellState *p){
rc = sqlite3_prepare_v2(p->db, "SELECT name FROM pragma_database_list",
-1, &pStmt, 0);
if( rc ){
- eputf("Error: %s\n", sqlite3_errmsg(p->db));
+ shellDatabaseError(p->db);
sqlite3_finalize(pStmt);
rc = 1;
goto meta_command_exit;
@@ -10149,7 +10166,7 @@ static int do_meta_command(char *zLine, ShellState *p){
freeText(&sSelect);
}
if( zErrMsg ){
- eputf("Error: %s\n", zErrMsg);
+ shellEmitError(zErrMsg);
sqlite3_free(zErrMsg);
rc = 1;
}else if( rc != SQLITE_OK ){
@@ -10920,7 +10937,7 @@ static int do_meta_command(char *zLine, ShellState *p){
{"json_selfcheck", SQLITE_TESTCTRL_JSON_SELFCHECK ,0,"BOOLEAN" },
{"localtime_fault", SQLITE_TESTCTRL_LOCALTIME_FAULT,0,"BOOLEAN" },
{"never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT,1, "BOOLEAN" },
- {"optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS,0,"DISABLE-MASK" },
+ {"optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS,0,"DISABLE-MASK ..."},
#ifdef YYCOVERAGE
{"parser_coverage", SQLITE_TESTCTRL_PARSER_COVERAGE,0,"" },
#endif
@@ -10984,8 +11001,118 @@ static int do_meta_command(char *zLine, ShellState *p){
}else{
switch(testctrl){
+ /* Special processing for .testctrl opt MASK ...
+ ** Each MASK argument can be one of:
+ **
+ ** +LABEL Enable the named optimization
+ **
+ ** -LABEL Disable the named optimization
+ **
+ ** INTEGER Mask of optimizations to disable
+ */
+ case SQLITE_TESTCTRL_OPTIMIZATIONS: {
+ static const struct {
+ unsigned int mask; /* Mask for this optimization */
+ unsigned int bDsply; /* Display this on output */
+ const char *zLabel; /* Name of optimization */
+ } aLabel[] = {
+ { 0x00000001, 1, "QueryFlattener" },
+ { 0x00000001, 0, "Flatten" },
+ { 0x00000002, 1, "WindowFunc" },
+ { 0x00000004, 1, "GroupByOrder" },
+ { 0x00000008, 1, "FactorOutConst" },
+ { 0x00000010, 1, "DistinctOpt" },
+ { 0x00000020, 1, "CoverIdxScan" },
+ { 0x00000040, 1, "OrderByIdxJoin" },
+ { 0x00000080, 1, "Transitive" },
+ { 0x00000100, 1, "OmitNoopJoin" },
+ { 0x00000200, 1, "CountOfView" },
+ { 0x00000400, 1, "CurosrHints" },
+ { 0x00000800, 1, "Stat4" },
+ { 0x00001000, 1, "PushDown" },
+ { 0x00002000, 1, "SimplifyJoin" },
+ { 0x00004000, 1, "SkipScan" },
+ { 0x00008000, 1, "PropagateConst" },
+ { 0x00010000, 1, "MinMaxOpt" },
+ { 0x00020000, 1, "SeekScan" },
+ { 0x00040000, 1, "OmitOrderBy" },
+ { 0x00080000, 1, "BloomFilter" },
+ { 0x00100000, 1, "BloomPulldown" },
+ { 0x00200000, 1, "BalancedMerge" },
+ { 0x00400000, 1, "ReleaseReg" },
+ { 0x00800000, 1, "FlttnUnionAll" },
+ { 0x01000000, 1, "IndexedEXpr" },
+ { 0x02000000, 1, "Coroutines" },
+ { 0x04000000, 1, "NullUnusedCols" },
+ { 0x08000000, 1, "OnePass" },
+ { 0x10000000, 1, "OrderBySubq" },
+ { 0xffffffff, 0, "All" },
+ };
+ unsigned int curOpt;
+ unsigned int newOpt;
+ int ii;
+ sqlite3_test_control(SQLITE_TESTCTRL_GETOPT, p->db, &curOpt);
+ newOpt = curOpt;
+ for(ii=2; ii=ArraySize(aLabel) ){
+ eputf("Error: no such optimization: \"%s\"\n", zLabel);
+ eputz("Should be one of:");
+ for(jj=0; jjdb,newOpt);
+ }else if( nArg<3 ){
+ curOpt = ~newOpt;
+ }
+ if( newOpt==0 ){
+ oputz("+All\n");
+ }else if( newOpt==0xffffffff ){
+ oputz("-All\n");
+ }else{
+ int jj;
+ for(jj=0; jj The ON clause of a join */
- IdList *pUsing; /* fg.isUsing==1 => The USING clause of a join */
- } u3;
Bitmask colUsed; /* Bit N set if column N used. Details above for N>62 */
union {
char *zIndexedBy; /* Identifier from "INDEXED BY " clause */
@@ -3339,6 +3359,15 @@ struct SrcItem {
Index *pIBIndex; /* Index structure corresponding to u1.zIndexedBy */
CteUse *pCteUse; /* CTE Usage info when fg.isCte is true */
} u2;
+ union {
+ Expr *pOn; /* fg.isUsing==0 => The ON clause of a join */
+ IdList *pUsing; /* fg.isUsing==1 => The USING clause of a join */
+ } u3;
+ union {
+ Schema *pSchema; /* Schema to which this item is fixed */
+ char *zDatabase; /* Name of database holding this table */
+ Subquery *pSubq; /* Description of a subquery */
+ } u4;
};
/*
@@ -3598,8 +3627,10 @@ struct Select {
#define SF_UpdateFrom 0x10000000 /* Query originates with UPDATE FROM */
#define SF_Correlated 0x20000000 /* True if references the outer context */
-/* True if S exists and has SF_NestedFrom */
-#define IsNestedFrom(S) ((S)!=0 && ((S)->selFlags&SF_NestedFrom)!=0)
+/* True if SrcItem X is a subquery that has SF_NestedFrom */
+#define IsNestedFrom(X) \
+ ((X)->fg.isSubquery && \
+ ((X)->u4.pSubq->pSelect->selFlags&SF_NestedFrom)!=0)
/*
** The results of a SELECT can be distributed in several ways, as defined
@@ -4991,6 +5022,9 @@ int sqlite3IdListIndex(IdList*,const char*);
SrcList *sqlite3SrcListEnlarge(Parse*, SrcList*, int, int);
SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2);
SrcList *sqlite3SrcListAppend(Parse*, SrcList*, Token*, Token*);
+void sqlite3SubqueryDelete(sqlite3*,Subquery*);
+Select *sqlite3SubqueryDetach(sqlite3*,SrcItem*);
+int sqlite3SrcItemAttachSubquery(Parse*, SrcItem*, Select*, int);
SrcList *sqlite3SrcListAppendFromTerm(Parse*, SrcList*, Token*, Token*,
Token*, Select*, OnOrUsing*);
void sqlite3SrcListIndexedBy(Parse *, SrcList *, Token *);
diff --git a/src/treeview.c b/src/treeview.c
index 3960d2859e..de67161229 100644
--- a/src/treeview.c
+++ b/src/treeview.c
@@ -193,9 +193,9 @@ void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){
sqlite3StrAccumInit(&x, 0, zLine, sizeof(zLine), 0);
x.printfFlags |= SQLITE_PRINTF_INTERNAL;
sqlite3_str_appendf(&x, "{%d:*} %!S", pItem->iCursor, pItem);
- if( pItem->pTab ){
+ if( pItem->pSTab ){
sqlite3_str_appendf(&x, " tab=%Q nCol=%d ptr=%p used=%llx%s",
- pItem->pTab->zName, pItem->pTab->nCol, pItem->pTab,
+ pItem->pSTab->zName, pItem->pSTab->nCol, pItem->pSTab,
pItem->colUsed,
pItem->fg.rowidUsed ? "+rowid" : "");
}
@@ -226,23 +226,30 @@ void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){
if( pItem->fg.viaCoroutine ) sqlite3_str_appendf(&x, " viaCoroutine");
if( pItem->fg.notCte ) sqlite3_str_appendf(&x, " notCte");
if( pItem->fg.isNestedFrom ) sqlite3_str_appendf(&x, " isNestedFrom");
+ if( pItem->fg.fixedSchema ) sqlite3_str_appendf(&x, " fixedSchema");
+ if( pItem->fg.hadSchema ) sqlite3_str_appendf(&x, " hadSchema");
+ if( pItem->fg.isSubquery ) sqlite3_str_appendf(&x, " isSubquery");
sqlite3StrAccumFinish(&x);
sqlite3TreeViewItem(pView, zLine, inSrc-1);
n = 0;
- if( pItem->pSelect ) n++;
+ if( pItem->fg.isSubquery ) n++;
if( pItem->fg.isTabFunc ) n++;
if( pItem->fg.isUsing ) n++;
if( pItem->fg.isUsing ){
sqlite3TreeViewIdList(pView, pItem->u3.pUsing, (--n)>0, "USING");
}
- if( pItem->pSelect ){
- if( pItem->pTab ){
- Table *pTab = pItem->pTab;
+ if( pItem->fg.isSubquery ){
+ assert( n==1 );
+ if( pItem->pSTab ){
+ Table *pTab = pItem->pSTab;
sqlite3TreeViewColumnList(pView, pTab->aCol, pTab->nCol, 1);
}
- assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem->pSelect) );
- sqlite3TreeViewSelect(pView, pItem->pSelect, (--n)>0);
+ assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem) );
+ sqlite3TreeViewPush(&pView, 0);
+ sqlite3TreeViewLine(pView, "SUBQUERY");
+ sqlite3TreeViewPop(&pView);
+ sqlite3TreeViewSelect(pView, pItem->u4.pSubq->pSelect, 0);
}
if( pItem->fg.isTabFunc ){
sqlite3TreeViewExprList(pView, pItem->u1.pFuncArg, 0, "func-args:");
diff --git a/src/trigger.c b/src/trigger.c
index 98e8da58c1..ff2df82cbc 100644
--- a/src/trigger.c
+++ b/src/trigger.c
@@ -152,8 +152,10 @@ void sqlite3BeginTrigger(
** name on pTableName if we are reparsing out of the schema table
*/
if( db->init.busy && iDb!=1 ){
- sqlite3DbFree(db, pTableName->a[0].zDatabase);
- pTableName->a[0].zDatabase = 0;
+ assert( pTableName->a[0].fg.fixedSchema==0 );
+ assert( pTableName->a[0].fg.isSubquery==0 );
+ sqlite3DbFree(db, pTableName->a[0].u4.zDatabase);
+ pTableName->a[0].u4.zDatabase = 0;
}
/* If the trigger name was unqualified, and the table is a temp table,
@@ -631,7 +633,8 @@ void sqlite3DropTrigger(Parse *pParse, SrcList *pName, int noErr){
}
assert( pName->nSrc==1 );
- zDb = pName->a[0].zDatabase;
+ assert( pName->a[0].fg.fixedSchema==0 && pName->a[0].fg.isSubquery==0 );
+ zDb = pName->a[0].u4.zDatabase;
zName = pName->a[0].zName;
assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) );
for(i=OMIT_TEMPDB; inDb; i++){
@@ -868,7 +871,9 @@ SrcList *sqlite3TriggerStepSrc(
Schema *pSchema = pStep->pTrig->pSchema;
pSrc->a[0].zName = zName;
if( pSchema!=db->aDb[1].pSchema ){
- pSrc->a[0].pSchema = pSchema;
+ assert( pSrc->a[0].fg.fixedSchema || pSrc->a[0].u4.zDatabase==0 );
+ pSrc->a[0].u4.pSchema = pSchema;
+ pSrc->a[0].fg.fixedSchema = 1;
}
if( pStep->pFrom ){
SrcList *pDup = sqlite3SrcListDup(db, pStep->pFrom, 0);
@@ -981,7 +986,7 @@ static int sqlite3ReturningSubqueryCorrelated(Walker *pWalker, Select *pSelect){
pSrc = pSelect->pSrc;
assert( pSrc!=0 );
for(i=0; inSrc; i++){
- if( pSrc->a[i].pTab==pWalker->u.pTab ){
+ if( pSrc->a[i].pSTab==pWalker->u.pTab ){
testcase( pSelect->selFlags & SF_Correlated );
pSelect->selFlags |= SF_Correlated;
pWalker->eCode = 1;
@@ -1052,7 +1057,7 @@ static void codeReturningTrigger(
sSelect.pEList = sqlite3ExprListDup(db, pReturning->pReturnEL, 0);
sSelect.pSrc = &sFrom;
sFrom.nSrc = 1;
- sFrom.a[0].pTab = pTab;
+ sFrom.a[0].pSTab = pTab;
sFrom.a[0].zName = pTab->zName; /* tag-20240424-1 */
sFrom.a[0].iCursor = -1;
sqlite3SelectPrep(pParse, &sSelect, 0);
diff --git a/src/update.c b/src/update.c
index b6068caa79..a8e7f77803 100644
--- a/src/update.c
+++ b/src/update.c
@@ -202,7 +202,7 @@ static void updateFromSelect(
Expr *pLimit2 = 0;
ExprList *pOrderBy2 = 0;
sqlite3 *db = pParse->db;
- Table *pTab = pTabList->a[0].pTab;
+ Table *pTab = pTabList->a[0].pSTab;
SrcList *pSrc;
Expr *pWhere2;
int eDest;
@@ -226,8 +226,8 @@ static void updateFromSelect(
if( pSrc ){
assert( pSrc->a[0].fg.notCte );
pSrc->a[0].iCursor = -1;
- pSrc->a[0].pTab->nTabRef--;
- pSrc->a[0].pTab = 0;
+ pSrc->a[0].pSTab->nTabRef--;
+ pSrc->a[0].pSTab = 0;
}
if( pPk ){
for(i=0; inKeyCol; i++){
diff --git a/src/upsert.c b/src/upsert.c
index f74d4fabf5..82295d52ae 100644
--- a/src/upsert.c
+++ b/src/upsert.c
@@ -104,7 +104,7 @@ int sqlite3UpsertAnalyzeTarget(
int nClause = 0; /* Counter of ON CONFLICT clauses */
assert( pTabList->nSrc==1 );
- assert( pTabList->a[0].pTab!=0 );
+ assert( pTabList->a[0].pSTab!=0 );
assert( pUpsert!=0 );
assert( pUpsert->pUpsertTarget!=0 );
@@ -123,7 +123,7 @@ int sqlite3UpsertAnalyzeTarget(
if( rc ) return rc;
/* Check to see if the conflict target matches the rowid. */
- pTab = pTabList->a[0].pTab;
+ pTab = pTabList->a[0].pSTab;
pTarget = pUpsert->pUpsertTarget;
iCursor = pTabList->a[0].iCursor;
if( HasRowid(pTab)
diff --git a/src/vacuum.c b/src/vacuum.c
index c0ae4bc1e1..e203f68c65 100644
--- a/src/vacuum.c
+++ b/src/vacuum.c
@@ -162,6 +162,9 @@ SQLITE_NOINLINE int sqlite3RunVacuum(
const char *zDbMain; /* Schema name of database to vacuum */
const char *zOut; /* Name of output file */
u32 pgflags = PAGER_SYNCHRONOUS_OFF; /* sync flags for output db */
+ u64 iRandom; /* Random value used for zDbVacuum[] */
+ char zDbVacuum[42]; /* Name of the ATTACH-ed database used for vacuum */
+
if( !db->autoCommit ){
sqlite3SetString(pzErrMsg, db, "cannot VACUUM from within a transaction");
@@ -202,27 +205,29 @@ SQLITE_NOINLINE int sqlite3RunVacuum(
pMain = db->aDb[iDb].pBt;
isMemDb = sqlite3PagerIsMemdb(sqlite3BtreePager(pMain));
- /* Attach the temporary database as 'vacuum_db'. The synchronous pragma
+ /* Attach the temporary database as 'vacuum_XXXXXX'. The synchronous pragma
** can be set to 'off' for this file, as it is not recovered if a crash
** occurs anyway. The integrity of the database is maintained by a
** (possibly synchronous) transaction opened on the main database before
** sqlite3BtreeCopyFile() is called.
**
** An optimization would be to use a non-journaled pager.
- ** (Later:) I tried setting "PRAGMA vacuum_db.journal_mode=OFF" but
+ ** (Later:) I tried setting "PRAGMA vacuum_XXXXXX.journal_mode=OFF" but
** that actually made the VACUUM run slower. Very little journalling
** actually occurs when doing a vacuum since the vacuum_db is initially
** empty. Only the journal header is written. Apparently it takes more
** time to parse and run the PRAGMA to turn journalling off than it does
** to write the journal header file.
*/
+ sqlite3_randomness(sizeof(iRandom),&iRandom);
+ sqlite3_snprintf(sizeof(zDbVacuum), zDbVacuum, "vacuum_%016llx", iRandom);
nDb = db->nDb;
- rc = execSqlF(db, pzErrMsg, "ATTACH %Q AS vacuum_db", zOut);
+ rc = execSqlF(db, pzErrMsg, "ATTACH %Q AS %s", zOut, zDbVacuum);
db->openFlags = saved_openFlags;
if( rc!=SQLITE_OK ) goto end_of_vacuum;
assert( (db->nDb-1)==nDb );
pDb = &db->aDb[nDb];
- assert( strcmp(pDb->zDbSName,"vacuum_db")==0 );
+ assert( strcmp(pDb->zDbSName,zDbVacuum)==0 );
pTemp = pDb->pBt;
if( pOut ){
sqlite3_file *id = sqlite3PagerFile(sqlite3BtreePager(pTemp));
@@ -299,11 +304,11 @@ SQLITE_NOINLINE int sqlite3RunVacuum(
** the contents to the temporary database.
*/
rc = execSqlF(db, pzErrMsg,
- "SELECT'INSERT INTO vacuum_db.'||quote(name)"
+ "SELECT'INSERT INTO %s.'||quote(name)"
"||' SELECT*FROM\"%w\".'||quote(name)"
- "FROM vacuum_db.sqlite_schema "
+ "FROM %s.sqlite_schema "
"WHERE type='table'AND coalesce(rootpage,1)>0",
- zDbMain
+ zDbVacuum, zDbMain, zDbVacuum
);
assert( (db->mDbFlags & DBFLAG_Vacuum)!=0 );
db->mDbFlags &= ~DBFLAG_Vacuum;
@@ -315,11 +320,11 @@ SQLITE_NOINLINE int sqlite3RunVacuum(
** from the schema table.
*/
rc = execSqlF(db, pzErrMsg,
- "INSERT INTO vacuum_db.sqlite_schema"
+ "INSERT INTO %s.sqlite_schema"
" SELECT*FROM \"%w\".sqlite_schema"
" WHERE type IN('view','trigger')"
" OR(type='table'AND rootpage=0)",
- zDbMain
+ zDbVacuum, zDbMain
);
if( rc ) goto end_of_vacuum;
diff --git a/src/vdbe.c b/src/vdbe.c
index 62456e0f5f..4ca3b3b053 100644
--- a/src/vdbe.c
+++ b/src/vdbe.c
@@ -2096,7 +2096,7 @@ case OP_RealAffinity: { /* in1 */
}
#endif
-#if !defined(SQLITE_OMIT_CAST) && !defined(SQLITE_OMIT_ANALYZE)
+#if !defined(SQLITE_OMIT_CAST) || !defined(SQLITE_OMIT_ANALYZE)
/* Opcode: Cast P1 P2 * * *
** Synopsis: affinity(r[P1])
**
diff --git a/src/vdbeblob.c b/src/vdbeblob.c
index 522447dbc1..6cb36da37a 100644
--- a/src/vdbeblob.c
+++ b/src/vdbeblob.c
@@ -167,6 +167,11 @@ int sqlite3_blob_open(
pTab = 0;
sqlite3ErrorMsg(&sParse, "cannot open table without rowid: %s", zTable);
}
+ if( pTab && (pTab->tabFlags&TF_HasGenerated)!=0 ){
+ pTab = 0;
+ sqlite3ErrorMsg(&sParse, "cannot open table with generated columns: %s",
+ zTable);
+ }
#ifndef SQLITE_OMIT_VIEW
if( pTab && IsView(pTab) ){
pTab = 0;
diff --git a/src/walker.c b/src/walker.c
index 0fe4a1d379..c8735e39b8 100644
--- a/src/walker.c
+++ b/src/walker.c
@@ -171,7 +171,9 @@ int sqlite3WalkSelectFrom(Walker *pWalker, Select *p){
pSrc = p->pSrc;
if( ALWAYS(pSrc) ){
for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){
- if( pItem->pSelect && sqlite3WalkSelect(pWalker, pItem->pSelect) ){
+ if( pItem->fg.isSubquery
+ && sqlite3WalkSelect(pWalker, pItem->u4.pSubq->pSelect)
+ ){
return WRC_Abort;
}
if( pItem->fg.isTabFunc
diff --git a/src/where.c b/src/where.c
index 631b7581fb..2b835009db 100644
--- a/src/where.c
+++ b/src/where.c
@@ -644,7 +644,7 @@ static int isDistinctRedundant(
** clause is redundant. */
if( pTabList->nSrc!=1 ) return 0;
iBase = pTabList->a[0].iCursor;
- pTab = pTabList->a[0].pTab;
+ pTab = pTabList->a[0].pSTab;
/* If any of the expressions is an IPK column on table iBase, then return
** true. Note: The (p->iTable==iBase) part of this test may be false if the
@@ -908,10 +908,10 @@ static int termCanDriveIndex(
assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 );
leftCol = pTerm->u.x.leftColumn;
if( leftCol<0 ) return 0;
- aff = pSrc->pTab->aCol[leftCol].affinity;
+ aff = pSrc->pSTab->aCol[leftCol].affinity;
if( !sqlite3IndexAffinityOk(pTerm->pExpr, aff) ) return 0;
testcase( pTerm->pExpr->op==TK_IS );
- return columnIsGoodIndexCandidate(pSrc->pTab, leftCol);
+ return columnIsGoodIndexCandidate(pSrc->pSTab, leftCol);
}
#endif
@@ -1019,7 +1019,7 @@ static SQLITE_NOINLINE void constructAutomaticIndex(
nKeyCol = 0;
pTabList = pWC->pWInfo->pTabList;
pSrc = &pTabList->a[pLevel->iFrom];
- pTable = pSrc->pTab;
+ pTable = pSrc->pSTab;
pWCEnd = &pWC->a[pWC->nTerm];
pLoop = pLevel->pWLoop;
idxCols = 0;
@@ -1161,12 +1161,17 @@ static SQLITE_NOINLINE void constructAutomaticIndex(
/* Fill the automatic index with content */
assert( pSrc == &pWC->pWInfo->pTabList->a[pLevel->iFrom] );
if( pSrc->fg.viaCoroutine ){
- int regYield = pSrc->regReturn;
+ int regYield;
+ Subquery *pSubq;
+ assert( pSrc->fg.isSubquery );
+ pSubq = pSrc->u4.pSubq;
+ assert( pSubq!=0 );
+ regYield = pSubq->regReturn;
addrCounter = sqlite3VdbeAddOp2(v, OP_Integer, 0, 0);
- sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, pSrc->addrFillSub);
+ sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, pSubq->addrFillSub);
addrTop = sqlite3VdbeAddOp1(v, OP_Yield, regYield);
VdbeCoverage(v);
- VdbeComment((v, "next row of %s", pSrc->pTab->zName));
+ VdbeComment((v, "next row of %s", pSrc->pSTab->zName));
}else{
addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, pLevel->iTabCur); VdbeCoverage(v);
}
@@ -1188,11 +1193,12 @@ static SQLITE_NOINLINE void constructAutomaticIndex(
sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT);
if( pPartial ) sqlite3VdbeResolveLabel(v, iContinue);
if( pSrc->fg.viaCoroutine ){
+ assert( pSrc->fg.isSubquery && pSrc->u4.pSubq!=0 );
sqlite3VdbeChangeP2(v, addrCounter, regBase+n);
testcase( pParse->db->mallocFailed );
assert( pLevel->iIdxCur>0 );
translateColumnToCopy(pParse, addrTop, pLevel->iTabCur,
- pSrc->regResult, pLevel->iIdxCur);
+ pSrc->u4.pSubq->regResult, pLevel->iIdxCur);
sqlite3VdbeGoto(v, addrTop);
pSrc->fg.viaCoroutine = 0;
}else{
@@ -1283,7 +1289,7 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter(
iSrc = pLevel->iFrom;
pItem = &pTabList->a[iSrc];
assert( pItem!=0 );
- pTab = pItem->pTab;
+ pTab = pItem->pSTab;
assert( pTab!=0 );
sz = sqlite3LogEstToInt(pTab->nRowLogEst);
if( sz<10000 ){
@@ -1314,7 +1320,7 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter(
int r1 = sqlite3GetTempRange(pParse, n);
int jj;
for(jj=0; jjpTable==pItem->pTab );
+ assert( pIdx->pTable==pItem->pSTab );
sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iCur, jj, r1+jj);
}
sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pLevel->regFilter, 0, r1, n);
@@ -1395,7 +1401,7 @@ static sqlite3_index_info *allocateIndexInfo(
WhereClause *p;
assert( pSrc!=0 );
- pTab = pSrc->pTab;
+ pTab = pSrc->pSTab;
assert( pTab!=0 );
assert( IsVirtual(pTab) );
@@ -2403,7 +2409,7 @@ void sqlite3WhereLoopPrint(const WhereLoop *p, const WhereClause *pWC){
WhereInfo *pWInfo = pWC->pWInfo;
int nb = 1+(pWInfo->pTabList->nSrc+3)/4;
SrcItem *pItem = pWInfo->pTabList->a + p->iTab;
- Table *pTab = pItem->pTab;
+ Table *pTab = pItem->pSTab;
Bitmask mAll = (((Bitmask)1)<<(nb*4)) - 1;
sqlite3DebugPrintf("%c%2d.%0*llx.%0*llx", p->cId,
p->iTab, nb, p->maskSelf, nb, p->prereq & mAll);
@@ -3391,7 +3397,7 @@ static int whereLoopAddBtreeIndex(
** 2. Stepping forward in the index pNew->nOut times to find all
** additional matching entries.
*/
- assert( pSrc->pTab->szTabRow>0 );
+ assert( pSrc->pSTab->szTabRow>0 );
if( pProbe->idxType==SQLITE_IDXTYPE_IPK ){
/* The pProbe->szIdxRow is low for an IPK table since the interior
** pages are small. Thus szIdxRow gives a good estimate of seek cost.
@@ -3399,7 +3405,7 @@ static int whereLoopAddBtreeIndex(
** under-estimate the scanning cost. */
rCostIdx = pNew->nOut + 16;
}else{
- rCostIdx = pNew->nOut + 1 + (15*pProbe->szIdxRow)/pSrc->pTab->szTabRow;
+ rCostIdx = pNew->nOut + 1 + (15*pProbe->szIdxRow)/pSrc->pSTab->szTabRow;
}
rCostIdx = sqlite3LogEstAdd(rLogSize, rCostIdx);
@@ -3864,9 +3870,9 @@ static int whereLoopAddBtree(
pWInfo = pBuilder->pWInfo;
pTabList = pWInfo->pTabList;
pSrc = pTabList->a + pNew->iTab;
- pTab = pSrc->pTab;
+ pTab = pSrc->pSTab;
pWC = pBuilder->pWC;
- assert( !IsVirtual(pSrc->pTab) );
+ assert( !IsVirtual(pSrc->pSTab) );
if( pSrc->fg.isIndexedBy ){
assert( pSrc->fg.isCte==0 );
@@ -3891,7 +3897,7 @@ static int whereLoopAddBtree(
sPk.idxType = SQLITE_IDXTYPE_IPK;
aiRowEstPk[0] = pTab->nRowLogEst;
aiRowEstPk[1] = 0;
- pFirst = pSrc->pTab->pIndex;
+ pFirst = pSrc->pSTab->pIndex;
if( pSrc->fg.notIndexed==0 ){
/* The real indices of the table are only considered if the
** NOT INDEXED qualifier is omitted from the FROM clause */
@@ -3981,6 +3987,7 @@ static int whereLoopAddBtree(
pNew->prereq = mPrereq;
pNew->nOut = rSize;
pNew->u.btree.pIndex = pProbe;
+ pNew->u.btree.pOrderBy = 0;
b = indexMightHelpWithOrderBy(pBuilder, pProbe, pSrc->iCursor);
/* The ONEPASS_DESIRED flags never occurs together with ORDER BY */
@@ -4010,9 +4017,9 @@ static int whereLoopAddBtree(
#endif
ApplyCostMultiplier(pNew->rRun, pTab->costMult);
whereLoopOutputAdjust(pWC, pNew, rSize);
- if( pSrc->pSelect ){
+ if( pSrc->fg.isSubquery ){
if( pSrc->fg.viaCoroutine ) pNew->wsFlags |= WHERE_COROUTINE;
- pNew->u.btree.pOrderBy = pSrc->pSelect->pOrderBy;
+ pNew->u.btree.pOrderBy = pSrc->u4.pSubq->pSelect->pOrderBy;
}
rc = whereLoopInsert(pBuilder, pNew);
pNew->nOut = rSize;
@@ -4238,7 +4245,7 @@ static int whereLoopAddVirtualOne(
pHidden->mHandleIn = 0;
/* Invoke the virtual table xBestIndex() method */
- rc = vtabBestIndex(pParse, pSrc->pTab, pIdxInfo);
+ rc = vtabBestIndex(pParse, pSrc->pSTab, pIdxInfo);
if( rc ){
if( rc==SQLITE_CONSTRAINT ){
/* If the xBestIndex method returns SQLITE_CONSTRAINT, that means
@@ -4268,7 +4275,7 @@ static int whereLoopAddVirtualOne(
|| pNew->aLTerm[iTerm]!=0
|| pIdxCons->usable==0
){
- sqlite3ErrorMsg(pParse,"%s.xBestIndex malfunction",pSrc->pTab->zName);
+ sqlite3ErrorMsg(pParse,"%s.xBestIndex malfunction",pSrc->pSTab->zName);
freeIdxStr(pIdxInfo);
return SQLITE_ERROR;
}
@@ -4331,7 +4338,7 @@ static int whereLoopAddVirtualOne(
if( pNew->aLTerm[i]==0 ){
/* The non-zero argvIdx values must be contiguous. Raise an
** error if they are not */
- sqlite3ErrorMsg(pParse,"%s.xBestIndex malfunction",pSrc->pTab->zName);
+ sqlite3ErrorMsg(pParse,"%s.xBestIndex malfunction",pSrc->pSTab->zName);
freeIdxStr(pIdxInfo);
return SQLITE_ERROR;
}
@@ -4343,6 +4350,7 @@ static int whereLoopAddVirtualOne(
pNew->u.vtab.idxStr = pIdxInfo->idxStr;
pNew->u.vtab.isOrdered = (i8)(pIdxInfo->orderByConsumed ?
pIdxInfo->nOrderBy : 0);
+ pNew->u.vtab.bIdxNumHex = (pIdxInfo->idxFlags&SQLITE_INDEX_SCAN_HEX)!=0;
pNew->rSetup = 0;
pNew->rRun = sqlite3LogEstFromDouble(pIdxInfo->estimatedCost);
pNew->nOut = sqlite3LogEst(pIdxInfo->estimatedRows);
@@ -4533,7 +4541,7 @@ static int whereLoopAddVirtual(
pWC = pBuilder->pWC;
pNew = pBuilder->pNew;
pSrc = &pWInfo->pTabList->a[pNew->iTab];
- assert( IsVirtual(pSrc->pTab) );
+ assert( IsVirtual(pSrc->pSTab) );
p = allocateIndexInfo(pWInfo, pWC, mUnusable, pSrc, &mNoOmit);
if( p==0 ) return SQLITE_NOMEM_BKPT;
pNew->rSetup = 0;
@@ -4547,7 +4555,7 @@ static int whereLoopAddVirtual(
}
/* First call xBestIndex() with all constraints usable. */
- WHERETRACE(0x800, ("BEGIN %s.addVirtual()\n", pSrc->pTab->zName));
+ WHERETRACE(0x800, ("BEGIN %s.addVirtual()\n", pSrc->pSTab->zName));
WHERETRACE(0x800, (" VirtualOne: all usable\n"));
rc = whereLoopAddVirtualOne(
pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn, &bRetry
@@ -4629,7 +4637,7 @@ static int whereLoopAddVirtual(
}
freeIndexInfo(pParse->db, p);
- WHERETRACE(0x800, ("END %s.addVirtual(), rc=%d\n", pSrc->pTab->zName, rc));
+ WHERETRACE(0x800, ("END %s.addVirtual(), rc=%d\n", pSrc->pSTab->zName, rc));
return rc;
}
#endif /* SQLITE_OMIT_VIRTUALTABLE */
@@ -4701,7 +4709,7 @@ static int whereLoopAddOr(
}
#endif
#ifndef SQLITE_OMIT_VIRTUALTABLE
- if( IsVirtual(pItem->pTab) ){
+ if( IsVirtual(pItem->pSTab) ){
rc = whereLoopAddVirtual(&sSubBuild, mPrereq, mUnusable);
}else
#endif
@@ -4815,7 +4823,7 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){
mPrereq = 0;
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
- if( IsVirtual(pItem->pTab) ){
+ if( IsVirtual(pItem->pSTab) ){
SrcItem *p;
for(p=&pItem[1]; pfg.jointype & (JT_OUTER|JT_CROSS)) ){
@@ -5451,7 +5459,7 @@ static int computeMxChoice(WhereInfo *pWInfo, LogEst nRowEst){
if( sqlite3WhereTrace&0x4 ){
SrcItem *pItem = pWInfo->pTabList->a + iLoop;
sqlite3DebugPrintf("Fact-table %s: %d dimensions, cost reduced %d\n",
- pItem->zAlias ? pItem->zAlias : pItem->pTab->zName,
+ pItem->zAlias ? pItem->zAlias : pItem->pSTab->zName,
nDep, rDelta);
}
#endif
@@ -6001,7 +6009,7 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){
if( pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE ) return 0;
assert( pWInfo->pTabList->nSrc>=1 );
pItem = pWInfo->pTabList->a;
- pTab = pItem->pTab;
+ pTab = pItem->pSTab;
if( IsVirtual(pTab) ) return 0;
if( pItem->fg.isIndexedBy || pItem->fg.notIndexed ){
testcase( pItem->fg.isIndexedBy );
@@ -6264,7 +6272,7 @@ static SQLITE_NOINLINE void whereCheckIfBloomFilterIsUseful(
WhereLoop *pLoop = pWInfo->a[i].pWLoop;
const unsigned int reqFlags = (WHERE_SELFCULL|WHERE_COLUMN_EQ);
SrcItem *pItem = &pWInfo->pTabList->a[pLoop->iTab];
- Table *pTab = pItem->pTab;
+ Table *pTab = pItem->pSTab;
if( (pTab->tabFlags & TF_HasStat1)==0 ) break;
pTab->tabFlags |= TF_MaybeReanalyze;
if( i>=1
@@ -6421,8 +6429,8 @@ static SQLITE_NOINLINE void whereReverseScanOrder(WhereInfo *pWInfo){
SrcItem *pItem = &pWInfo->pTabList->a[ii];
if( !pItem->fg.isCte
|| pItem->u2.pCteUse->eM10d!=M10d_Yes
- || NEVER(pItem->pSelect==0)
- || pItem->pSelect->pOrderBy==0
+ || NEVER(pItem->fg.isSubquery==0)
+ || pItem->u4.pSubq->pSelect->pOrderBy==0
){
pWInfo->revMask |= MASKBIT(ii);
}
@@ -6912,15 +6920,15 @@ WhereInfo *sqlite3WhereBegin(
if( (wctrlFlags & WHERE_ONEPASS_DESIRED)!=0 ){
int wsFlags = pWInfo->a[0].pWLoop->wsFlags;
int bOnerow = (wsFlags & WHERE_ONEROW)!=0;
- assert( !(wsFlags & WHERE_VIRTUALTABLE) || IsVirtual(pTabList->a[0].pTab) );
+ assert( !(wsFlags&WHERE_VIRTUALTABLE) || IsVirtual(pTabList->a[0].pSTab) );
if( bOnerow || (
0!=(wctrlFlags & WHERE_ONEPASS_MULTIROW)
- && !IsVirtual(pTabList->a[0].pTab)
+ && !IsVirtual(pTabList->a[0].pSTab)
&& (0==(wsFlags & WHERE_MULTI_OR) || (wctrlFlags & WHERE_DUPLICATES_OK))
&& OptimizationEnabled(db, SQLITE_OnePass)
)){
pWInfo->eOnePass = bOnerow ? ONEPASS_SINGLE : ONEPASS_MULTI;
- if( HasRowid(pTabList->a[0].pTab) && (wsFlags & WHERE_IDX_ONLY) ){
+ if( HasRowid(pTabList->a[0].pSTab) && (wsFlags & WHERE_IDX_ONLY) ){
if( wctrlFlags & WHERE_ONEPASS_MULTIROW ){
bFordelete = OPFLAG_FORDELETE;
}
@@ -6938,7 +6946,7 @@ WhereInfo *sqlite3WhereBegin(
SrcItem *pTabItem;
pTabItem = &pTabList->a[pLevel->iFrom];
- pTab = pTabItem->pTab;
+ pTab = pTabItem->pSTab;
iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
pLoop = pLevel->pWLoop;
if( (pTab->tabFlags & TF_Ephemeral)!=0 || IsView(pTab) ){
@@ -7009,7 +7017,7 @@ WhereInfo *sqlite3WhereBegin(
iIndexCur = pLevel->iTabCur;
op = 0;
}else if( pWInfo->eOnePass!=ONEPASS_OFF ){
- Index *pJ = pTabItem->pTab->pIndex;
+ Index *pJ = pTabItem->pSTab->pIndex;
iIndexCur = iAuxArg;
assert( wctrlFlags & WHERE_ONEPASS_DESIRED );
while( ALWAYS(pJ) && pJ!=pIx ){
@@ -7076,7 +7084,7 @@ WhereInfo *sqlite3WhereBegin(
sqlite3VdbeAddOp2(v, OP_Blob, 65536, pRJ->regBloom);
pRJ->regReturn = ++pParse->nMem;
sqlite3VdbeAddOp2(v, OP_Null, 0, pRJ->regReturn);
- assert( pTab==pTabItem->pTab );
+ assert( pTab==pTabItem->pSTab );
if( HasRowid(pTab) ){
KeyInfo *pInfo;
sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRJ->iMatch, 1);
@@ -7115,13 +7123,18 @@ WhereInfo *sqlite3WhereBegin(
wsFlags = pLevel->pWLoop->wsFlags;
pSrc = &pTabList->a[pLevel->iFrom];
if( pSrc->fg.isMaterialized ){
- if( pSrc->fg.isCorrelated ){
- sqlite3VdbeAddOp2(v, OP_Gosub, pSrc->regReturn, pSrc->addrFillSub);
+ Subquery *pSubq;
+ int iOnce = 0;
+ assert( pSrc->fg.isSubquery );
+ pSubq = pSrc->u4.pSubq;
+ if( pSrc->fg.isCorrelated==0 ){
+ iOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
}else{
- int iOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_Gosub, pSrc->regReturn, pSrc->addrFillSub);
- sqlite3VdbeJumpHere(v, iOnce);
+ iOnce = 0;
}
+ sqlite3VdbeAddOp2(v, OP_Gosub, pSubq->regReturn, pSubq->addrFillSub);
+ VdbeComment((v, "materialize %!S", pSrc));
+ if( iOnce ) sqlite3VdbeJumpHere(v, iOnce);
}
assert( pTabList == pWInfo->pTabList );
if( (wsFlags & (WHERE_AUTO_INDEX|WHERE_BLOOMFILTER))!=0 ){
@@ -7334,9 +7347,10 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
assert( pLevel->iTabCur==pSrc->iCursor );
if( pSrc->fg.viaCoroutine ){
int m, n;
- n = pSrc->regResult;
- assert( pSrc->pTab!=0 );
- m = pSrc->pTab->nCol;
+ assert( pSrc->fg.isSubquery );
+ n = pSrc->u4.pSubq->regResult;
+ assert( pSrc->pSTab!=0 );
+ m = pSrc->pSTab->nCol;
sqlite3VdbeAddOp3(v, OP_Null, 0, n, n+m-1);
}
sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iTabCur);
@@ -7360,7 +7374,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
sqlite3VdbeJumpHere(v, addr);
}
VdbeModuleComment((v, "End WHERE-loop%d: %s", i,
- pWInfo->pTabList->a[pLevel->iFrom].pTab->zName));
+ pWInfo->pTabList->a[pLevel->iFrom].pSTab->zName));
}
assert( pWInfo->nLevel<=pTabList->nSrc );
@@ -7369,7 +7383,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
VdbeOp *pOp, *pLastOp;
Index *pIdx = 0;
SrcItem *pTabItem = &pTabList->a[pLevel->iFrom];
- Table *pTab = pTabItem->pTab;
+ Table *pTab = pTabItem->pSTab;
assert( pTab!=0 );
pLoop = pLevel->pWLoop;
@@ -7388,9 +7402,10 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
*/
if( pTabItem->fg.viaCoroutine ){
testcase( pParse->db->mallocFailed );
- assert( pTabItem->regResult>=0 );
+ assert( pTabItem->fg.isSubquery );
+ assert( pTabItem->u4.pSubq->regResult>=0 );
translateColumnToCopy(pParse, pLevel->addrBody, pLevel->iTabCur,
- pTabItem->regResult, 0);
+ pTabItem->u4.pSubq->regResult, 0);
continue;
}
diff --git a/src/whereInt.h b/src/whereInt.h
index c9afed267b..8247528a93 100644
--- a/src/whereInt.h
+++ b/src/whereInt.h
@@ -149,6 +149,7 @@ struct WhereLoop {
int idxNum; /* Index number */
u32 needFree : 1; /* True if sqlite3_free(idxStr) is needed */
u32 bOmitOffset : 1; /* True to let virtual table handle offset */
+ u32 bIdxNumHex : 1; /* Show idxNum as hex in EXPLAIN QUERY PLAN */
i8 isOrdered; /* True if satisfies ORDER BY */
u16 omitMask; /* Terms that may be omitted */
char *idxStr; /* Index identifier string */
diff --git a/src/wherecode.c b/src/wherecode.c
index 098af7375e..0951e5e204 100644
--- a/src/wherecode.c
+++ b/src/wherecode.c
@@ -157,7 +157,7 @@ int sqlite3WhereExplainOneScan(
assert( pLoop->u.btree.pIndex!=0 );
pIdx = pLoop->u.btree.pIndex;
assert( !(flags&WHERE_AUTO_INDEX) || (flags&WHERE_IDX_ONLY) );
- if( !HasRowid(pItem->pTab) && IsPrimaryKeyIndex(pIdx) ){
+ if( !HasRowid(pItem->pSTab) && IsPrimaryKeyIndex(pIdx) ){
if( isSearch ){
zFmt = "PRIMARY KEY";
}
@@ -200,7 +200,9 @@ int sqlite3WhereExplainOneScan(
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
else if( (flags & WHERE_VIRTUALTABLE)!=0 ){
- sqlite3_str_appendf(&str, " VIRTUAL TABLE INDEX %d:%s",
+ sqlite3_str_appendall(&str, " VIRTUAL TABLE INDEX ");
+ sqlite3_str_appendf(&str,
+ pLoop->u.vtab.bIdxNumHex ? "0x%x:%s" : "%d:%s",
pLoop->u.vtab.idxNum, pLoop->u.vtab.idxStr);
}
#endif
@@ -254,7 +256,7 @@ int sqlite3WhereExplainBloomFilter(
sqlite3_str_appendf(&str, "BLOOM FILTER ON %S (", pItem);
pLoop = pLevel->pWLoop;
if( pLoop->wsFlags & WHERE_IPK ){
- const Table *pTab = pItem->pTab;
+ const Table *pTab = pItem->pSTab;
if( pTab->iPKey>=0 ){
sqlite3_str_appendf(&str, "%s=?", pTab->aCol[pTab->iPKey].zCnName);
}else{
@@ -317,7 +319,9 @@ void sqlite3WhereAddScanStatus(
sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iIdxCur);
}
}else{
- int addr = pSrclist->a[pLvl->iFrom].addrFillSub;
+ int addr;
+ assert( pSrclist->a[pLvl->iFrom].fg.isSubquery );
+ addr = pSrclist->a[pLvl->iFrom].u4.pSubq->addrFillSub;
VdbeOp *pOp = sqlite3VdbeGetOp(v, addr-1);
assert( sqlite3VdbeDb(v)->mallocFailed || pOp->opcode==OP_InitCoroutine );
assert( sqlite3VdbeDb(v)->mallocFailed || pOp->p2>addr );
@@ -1454,7 +1458,8 @@ Bitmask sqlite3WhereCodeOneLoopStart(
iCur = pTabItem->iCursor;
pLevel->notReady = notReady & ~sqlite3WhereGetMask(&pWInfo->sMaskSet, iCur);
bRev = (pWInfo->revMask>>iLevel)&1;
- VdbeModuleComment((v, "Begin WHERE-loop%d: %s",iLevel,pTabItem->pTab->zName));
+ VdbeModuleComment((v, "Begin WHERE-loop%d: %s",
+ iLevel, pTabItem->pSTab->zName));
#if WHERETRACE_ENABLED /* 0x4001 */
if( sqlite3WhereTrace & 0x1 ){
sqlite3DebugPrintf("Coding level %d of %d: notReady=%llx iFrom=%d\n",
@@ -1509,11 +1514,15 @@ Bitmask sqlite3WhereCodeOneLoopStart(
/* Special case of a FROM clause subquery implemented as a co-routine */
if( pTabItem->fg.viaCoroutine ){
- int regYield = pTabItem->regReturn;
- sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, pTabItem->addrFillSub);
+ int regYield;
+ Subquery *pSubq;
+ assert( pTabItem->fg.isSubquery && pTabItem->u4.pSubq!=0 );
+ pSubq = pTabItem->u4.pSubq;
+ regYield = pSubq->regReturn;
+ sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, pSubq->addrFillSub);
pLevel->p2 = sqlite3VdbeAddOp2(v, OP_Yield, regYield, addrBrk);
VdbeCoverage(v);
- VdbeComment((v, "next row of %s", pTabItem->pTab->zName));
+ VdbeComment((v, "next row of %s", pTabItem->pSTab->zName));
pLevel->op = OP_Goto;
}else
@@ -2242,7 +2251,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
int untestedTerms = 0; /* Some terms not completely tested */
int ii; /* Loop counter */
Expr *pAndExpr = 0; /* An ".. AND (...)" expression */
- Table *pTab = pTabItem->pTab;
+ Table *pTab = pTabItem->pSTab;
pTerm = pLoop->aLTerm[0];
assert( pTerm!=0 );
@@ -2701,7 +2710,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
** least once. This is accomplished by storing the PK for the row in
** both the iMatch index and the regBloom Bloom filter.
*/
- pTab = pWInfo->pTabList->a[pLevel->iFrom].pTab;
+ pTab = pWInfo->pTabList->a[pLevel->iFrom].pSTab;
if( HasRowid(pTab) ){
r = sqlite3GetTempRange(pParse, 2);
sqlite3ExprCodeGetColumnOfTable(v, pTab, pLevel->iTabCur, -1, r+1);
@@ -2808,7 +2817,7 @@ SQLITE_NOINLINE void sqlite3WhereRightJoinLoop(
Bitmask mAll = 0;
int k;
- ExplainQueryPlan((pParse, 1, "RIGHT-JOIN %s", pTabItem->pTab->zName));
+ ExplainQueryPlan((pParse, 1, "RIGHT-JOIN %s", pTabItem->pSTab->zName));
sqlite3VdbeNoJumpsOutsideSubrtn(v, pRJ->addrSubrtn, pRJ->endSubrtn,
pRJ->regReturn);
for(k=0; kpTabList->a[pWInfo->a[k].iFrom];
mAll |= pWInfo->a[k].pWLoop->maskSelf;
if( pRight->fg.viaCoroutine ){
+ Subquery *pSubq;
+ assert( pRight->fg.isSubquery && pRight->u4.pSubq!=0 );
+ pSubq = pRight->u4.pSubq;
+ assert( pSubq->pSelect!=0 && pSubq->pSelect->pEList!=0 );
sqlite3VdbeAddOp3(
- v, OP_Null, 0, pRight->regResult,
- pRight->regResult + pRight->pSelect->pEList->nExpr-1
+ v, OP_Null, 0, pSubq->regResult,
+ pSubq->regResult + pSubq->pSelect->pEList->nExpr-1
);
}
sqlite3VdbeAddOp1(v, OP_NullRow, pWInfo->a[k].iTabCur);
@@ -2858,7 +2871,7 @@ SQLITE_NOINLINE void sqlite3WhereRightJoinLoop(
int nPk;
int jmp;
int addrCont = sqlite3WhereContinueLabel(pSubWInfo);
- Table *pTab = pTabItem->pTab;
+ Table *pTab = pTabItem->pSTab;
if( HasRowid(pTab) ){
sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, -1, r);
nPk = 1;
diff --git a/src/whereexpr.c b/src/whereexpr.c
index dcda75d264..7ea2956a75 100644
--- a/src/whereexpr.c
+++ b/src/whereexpr.c
@@ -220,11 +220,20 @@ static int isLikeOrGlob(
}
if( z ){
- /* Count the number of prefix characters prior to the first wildcard */
+ /* Count the number of prefix characters prior to the first wildcard.
+ ** If the underlying database has a UTF16LE encoding, then only consider
+ ** ASCII characters. Note that the encoding of z[] is UTF8 - we are
+ ** dealing with only UTF8 here in this code, but the database engine
+ ** itself might be processing content using a different encoding. */
cnt = 0;
while( (c=z[cnt])!=0 && c!=wc[0] && c!=wc[1] && c!=wc[2] ){
cnt++;
- if( c==wc[3] && z[cnt]!=0 ) cnt++;
+ if( c==wc[3] && z[cnt]!=0 ){
+ cnt++;
+ }else if( c>=0x80 && ENC(db)==SQLITE_UTF16LE ){
+ cnt--;
+ break;
+ }
}
/* The optimization is possible only if (1) the pattern does not begin
@@ -239,7 +248,7 @@ static int isLikeOrGlob(
Expr *pPrefix;
/* A "complete" match if the pattern ends with "*" or "%" */
- *pisComplete = c==wc[0] && z[cnt+1]==0;
+ *pisComplete = c==wc[0] && z[cnt+1]==0 && ENC(db)!=SQLITE_UTF16LE;
/* Get the pattern prefix. Remove all escapes from the prefix. */
pPrefix = sqlite3Expr(db, TK_STRING, (char*)z);
@@ -958,7 +967,9 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){
if( ALWAYS(pSrc!=0) ){
int i;
for(i=0; inSrc; i++){
- mask |= exprSelectUsage(pMaskSet, pSrc->a[i].pSelect);
+ if( pSrc->a[i].fg.isSubquery ){
+ mask |= exprSelectUsage(pMaskSet, pSrc->a[i].u4.pSubq->pSelect);
+ }
if( pSrc->a[i].fg.isUsing==0 ){
mask |= sqlite3WhereExprUsage(pMaskSet, pSrc->a[i].u3.pOn);
}
@@ -996,7 +1007,7 @@ static SQLITE_NOINLINE int exprMightBeIndexed2(
int iCur;
do{
iCur = pFrom->a[j].iCursor;
- for(pIdx=pFrom->a[j].pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ for(pIdx=pFrom->a[j].pSTab->pIndex; pIdx; pIdx=pIdx->pNext){
if( pIdx->aColExpr==0 ) continue;
for(i=0; inKeyCol; i++){
if( pIdx->aiColumn[i]!=XN_EXPR ) continue;
@@ -1040,7 +1051,7 @@ static int exprMightBeIndexed(
for(i=0; inSrc; i++){
Index *pIdx;
- for(pIdx=pFrom->a[i].pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ for(pIdx=pFrom->a[i].pSTab->pIndex; pIdx; pIdx=pIdx->pNext){
if( pIdx->aColExpr ){
return exprMightBeIndexed2(pFrom,aiCurCol,pExpr,i);
}
@@ -1628,7 +1639,7 @@ void SQLITE_NOINLINE sqlite3WhereAddLimit(WhereClause *pWC, Select *p){
assert( p!=0 && p->pLimit!=0 ); /* 1 -- checked by caller */
if( p->pGroupBy==0
&& (p->selFlags & (SF_Distinct|SF_Aggregate))==0 /* 2 */
- && (p->pSrc->nSrc==1 && IsVirtual(p->pSrc->a[0].pTab)) /* 3 */
+ && (p->pSrc->nSrc==1 && IsVirtual(p->pSrc->a[0].pSTab)) /* 3 */
){
ExprList *pOrderBy = p->pOrderBy;
int iCsr = p->pSrc->a[0].iCursor;
@@ -1849,7 +1860,7 @@ void sqlite3WhereTabFuncArgs(
Expr *pColRef;
Expr *pTerm;
if( pItem->fg.isTabFunc==0 ) return;
- pTab = pItem->pTab;
+ pTab = pItem->pSTab;
assert( pTab!=0 );
pArgs = pItem->u1.pFuncArg;
if( pArgs==0 ) return;
diff --git a/src/window.c b/src/window.c
index e5a78e37d2..d4083beeb3 100644
--- a/src/window.c
+++ b/src/window.c
@@ -1077,9 +1077,10 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){
assert( pSub!=0 || p->pSrc==0 ); /* Due to db->mallocFailed test inside
** of sqlite3DbMallocRawNN() called from
** sqlite3SrcListAppend() */
- if( p->pSrc ){
+ if( p->pSrc==0 ){
+ sqlite3SelectDelete(db, pSub);
+ }else if( sqlite3SrcItemAttachSubquery(pParse, &p->pSrc->a[0], pSub, 0) ){
Table *pTab2;
- p->pSrc->a[0].pSelect = pSub;
p->pSrc->a[0].fg.isCorrelated = 1;
sqlite3SrcListAssignCursors(pParse, p->pSrc);
pSub->selFlags |= SF_Expanded|SF_OrderByReqd;
@@ -1093,7 +1094,7 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){
}else{
memcpy(pTab, pTab2, sizeof(Table));
pTab->tabFlags |= TF_Ephemeral;
- p->pSrc->a[0].pTab = pTab;
+ p->pSrc->a[0].pSTab = pTab;
pTab = pTab2;
memset(&w, 0, sizeof(w));
w.xExprCallback = sqlite3WindowExtraAggFuncDepth;
@@ -1101,8 +1102,6 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){
w.xSelectCallback2 = sqlite3WalkerDepthDecrease;
sqlite3WalkSelect(&w, pSub);
}
- }else{
- sqlite3SelectDelete(db, pSub);
}
if( db->mallocFailed ) rc = SQLITE_NOMEM;
@@ -1389,10 +1388,15 @@ int sqlite3WindowCompare(
** and initialize registers and cursors used by sqlite3WindowCodeStep().
*/
void sqlite3WindowCodeInit(Parse *pParse, Select *pSelect){
- int nEphExpr = pSelect->pSrc->a[0].pSelect->pEList->nExpr;
- Window *pMWin = pSelect->pWin;
Window *pWin;
- Vdbe *v = sqlite3GetVdbe(pParse);
+ int nEphExpr;
+ Window *pMWin;
+ Vdbe *v;
+
+ assert( pSelect->pSrc->a[0].fg.isSubquery );
+ nEphExpr = pSelect->pSrc->a[0].u4.pSubq->pSelect->pEList->nExpr;
+ pMWin = pSelect->pWin;
+ v = sqlite3GetVdbe(pParse);
sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pMWin->iEphCsr, nEphExpr);
sqlite3VdbeAddOp2(v, OP_OpenDup, pMWin->iEphCsr+1, pMWin->iEphCsr);
@@ -2789,7 +2793,7 @@ void sqlite3WindowCodeStep(
Vdbe *v = sqlite3GetVdbe(pParse);
int csrWrite; /* Cursor used to write to eph. table */
int csrInput = p->pSrc->a[0].iCursor; /* Cursor of sub-select */
- int nInput = p->pSrc->a[0].pTab->nCol; /* Number of cols returned by sub */
+ int nInput = p->pSrc->a[0].pSTab->nCol; /* Number of cols returned by sub */
int iInput; /* To iterate through sub cols */
int addrNe; /* Address of OP_Ne */
int addrGosubFlush = 0; /* Address of OP_Gosub to flush: */
diff --git a/test/date5.test b/test/date5.test
new file mode 100644
index 0000000000..688f84d0f1
--- /dev/null
+++ b/test/date5.test
@@ -0,0 +1,86 @@
+# 2024-08-19
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# https://sqlite.org/forum/forumpost/eaa0a09786c6368b
+#
+# Apparently SQLite has been miscomputing leap-year dates before
+# the year 0400.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+# Skip this whole file if date and time functions are omitted
+# at compile-time
+#
+ifcapable {!datetime} {
+ finish_test
+ return
+}
+
+# Data sources:
+# 1-10 https://ssd.jpl.nasa.gov/tools/jdc/#/cd
+# 11 Jean Meeus, Astronomical Algorithms, ISBN 0-943396-61-1, p.59
+# 12 https://en.wikipedia.org/wiki/Julian_day
+#
+# ID YEAR MONTH DAY JD
+set date5data {
+ 1 2024 2 29 2460369.5
+ 2 2024 3 1 2460370.5
+ 3 2023 2 28 2460003.5
+ 4 2023 3 1 2460004.5
+ 5 2000 2 29 2451603.5
+ 6 2000 3 1 2451604.5
+ 7 1900 2 28 2415078.5
+ 8 1900 3 1 2415079.5
+ 9 1712 2 29 2346413.5
+ 10 1712 3 1 2346414.5
+ 11 1977 4 26 2443259.5
+ 12 2013 1 1 2456293.5
+}
+
+foreach {id y m d jd} $date5data {
+ set date [format %04d-%02d-%02d $y $m $d]
+ do_execsql_test date5-jd$jd {
+ SELECT date($::jd);
+ } $date
+ do_execsql_test date5-cal/$date {
+ SELECT julianday($::date);
+ } $jd
+ for {set i 1} {$y+400*$i<=9999} {incr i} {
+ set y2 [expr {$y+400*$i}]
+ set date2 [format %04d-%02d-%02d $y2 $m $d]
+ set jd2 [expr {$jd+146097*$i}]
+ do_execsql_test date5-jd$jd2 {
+ SELECT date($::jd2);
+ } $date2
+ do_execsql_test date5-cal/$date2 {
+ SELECT julianday($::date2);
+ } $jd2
+ }
+ for {set i 1} {$y-400*$i>=-4712} {incr i} {
+ set y2 [expr {$y-400*$i}]
+ if {$y2<0} {
+ set date2 [format -%04d-%02d-%02d [expr {-$y2}] $m $d]
+ } else {
+ set date2 [format %04d-%02d-%02d $y2 $m $d]
+ }
+ set jd2 [expr {$jd-146097*$i}]
+ do_execsql_test date5-jd$jd2 {
+ SELECT date($::jd2);
+ } $date2
+ do_execsql_test date5-cal/$date2 {
+ SELECT julianday($::date2);
+ } $jd2
+ }
+}
+
+finish_test
diff --git a/test/merge1.test b/test/merge1.test
index 7ec4dab108..686271648a 100644
--- a/test/merge1.test
+++ b/test/merge1.test
@@ -64,21 +64,21 @@ do_eqp_test 101 {
| |--LEFT
| | `--MERGE (UNION ALL)
| | |--LEFT
- | | | `--SCAN generate_series VIRTUAL TABLE INDEX 23:
+ | | | `--SCAN generate_series VIRTUAL TABLE INDEX 0x17:
| | `--RIGHT
- | | `--SCAN generate_series VIRTUAL TABLE INDEX 23:
+ | | `--SCAN generate_series VIRTUAL TABLE INDEX 0x17:
| `--RIGHT
- | `--SCAN generate_series VIRTUAL TABLE INDEX 23:
+ | `--SCAN generate_series VIRTUAL TABLE INDEX 0x17:
`--RIGHT
`--MERGE (UNION ALL)
|--LEFT
| `--MERGE (UNION ALL)
| |--LEFT
- | | `--SCAN generate_series VIRTUAL TABLE INDEX 23:
+ | | `--SCAN generate_series VIRTUAL TABLE INDEX 0x17:
| `--RIGHT
- | `--SCAN generate_series VIRTUAL TABLE INDEX 23:
+ | `--SCAN generate_series VIRTUAL TABLE INDEX 0x17:
`--RIGHT
- `--SCAN generate_series VIRTUAL TABLE INDEX 23:
+ `--SCAN generate_series VIRTUAL TABLE INDEX 0x17:
}
# Same test with the blanced-merge optimization
@@ -129,17 +129,17 @@ do_eqp_test 111 {
| | | |--LEFT
| | | | `--MERGE (UNION ALL)
| | | | |--LEFT
- | | | | | `--SCAN generate_series VIRTUAL TABLE INDEX 23:
+ | | | | | `--SCAN generate_series VIRTUAL TABLE INDEX 0x17:
| | | | `--RIGHT
- | | | | `--SCAN generate_series VIRTUAL TABLE INDEX 23:
+ | | | | `--SCAN generate_series VIRTUAL TABLE INDEX 0x17:
| | | `--RIGHT
- | | | `--SCAN generate_series VIRTUAL TABLE INDEX 23:
+ | | | `--SCAN generate_series VIRTUAL TABLE INDEX 0x17:
| | `--RIGHT
- | | `--SCAN generate_series VIRTUAL TABLE INDEX 23:
+ | | `--SCAN generate_series VIRTUAL TABLE INDEX 0x17:
| `--RIGHT
- | `--SCAN generate_series VIRTUAL TABLE INDEX 23:
+ | `--SCAN generate_series VIRTUAL TABLE INDEX 0x17:
`--RIGHT
- `--SCAN generate_series VIRTUAL TABLE INDEX 23:
+ `--SCAN generate_series VIRTUAL TABLE INDEX 0x17:
}
finish_test
diff --git a/test/shell2.test b/test/shell2.test
index c6c27d2165..ee5ae4bdd9 100644
--- a/test/shell2.test
+++ b/test/shell2.test
@@ -217,6 +217,7 @@ do_test shell2-1.4.9 {
done
2}}
+ifcapable vtab {
# Verify that generate_series stays sane near 64-bit range boundaries.
# See overflow report at https://sqlite.org/forum/forumpost/5d34ce5280
do_test shell2-1.4.10 {
@@ -247,7 +248,9 @@ do_test shell2-1.4.10 {
0
1
2}}
+} ;# ifcapable vtab
+ifcapable vtab {
# Bug discovered while messing around, .import hangs with
# bit 7 set in column separator.
do_test shell2-1.4.11 {
@@ -262,6 +265,7 @@ do_test shell2-1.4.11 {
.import dummy.csv t
SELECT count(*) FROM t;}]]
} {0 1}
+} ;# ifcapable vtab
# Bug from forum post 7cbe081746dd3803
# Keywords as column names were producing an error message.
diff --git a/test/shell5.test b/test/shell5.test
index 31d5449fdc..8eb905974b 100644
--- a/test/shell5.test
+++ b/test/shell5.test
@@ -84,6 +84,14 @@ do_test shell5-1.4.1 {
.import FOO t1}]
} {1 {Error: cannot open "FOO"}}
+# the remainder of these test cases require virtual tables.
+#
+ifcapable !vtab {
+ puts "Skipping subsequent tests due to SQLITE_OMIT_VIRTUALTABLE"
+ finish_test
+ return
+}
+
# empty import file
do_test shell5-1.4.2 {
forcedelete shell5.csv
diff --git a/test/tabfunc01.test b/test/tabfunc01.test
index 3a62b81f99..8c24198e1d 100644
--- a/test/tabfunc01.test
+++ b/test/tabfunc01.test
@@ -121,26 +121,26 @@ do_eqp_test tabfunc01-3.10 {
SELECT value FROM generate_series(1,10) ORDER BY value;
} {
QUERY PLAN
- `--SCAN generate_series VIRTUAL TABLE INDEX 19:
+ `--SCAN generate_series VIRTUAL TABLE INDEX 0x13:
}
do_eqp_test tabfunc01-3.11 {
SELECT value FROM generate_series(1,10) ORDER BY +value;
} {
QUERY PLAN
- |--SCAN generate_series VIRTUAL TABLE INDEX 3:
+ |--SCAN generate_series VIRTUAL TABLE INDEX 0x3:
`--USE TEMP B-TREE FOR ORDER BY
}
do_eqp_test tabfunc01-3.12 {
SELECT value FROM generate_series(1,10) ORDER BY value, stop;
} {
QUERY PLAN
- `--SCAN generate_series VIRTUAL TABLE INDEX 19:
+ `--SCAN generate_series VIRTUAL TABLE INDEX 0x13:
}
do_eqp_test tabfunc01-3.13 {
SELECT value FROM generate_series(1,10) ORDER BY stop, value;
} {
QUERY PLAN
- |--SCAN generate_series VIRTUAL TABLE INDEX 3:
+ |--SCAN generate_series VIRTUAL TABLE INDEX 0x3:
`--USE TEMP B-TREE FOR ORDER BY
}
@@ -156,9 +156,9 @@ do_eqp_test tabfunc01-3.20 {
QUERY PLAN
`--MERGE (UNION ALL)
|--LEFT
- | `--SCAN generate_series VIRTUAL TABLE INDEX 23:
+ | `--SCAN generate_series VIRTUAL TABLE INDEX 0x17:
`--RIGHT
- `--SCAN generate_series VIRTUAL TABLE INDEX 23:
+ `--SCAN generate_series VIRTUAL TABLE INDEX 0x17:
}
diff --git a/test/tester.tcl b/test/tester.tcl
index 192024039b..adad184e56 100644
--- a/test/tester.tcl
+++ b/test/tester.tcl
@@ -986,7 +986,7 @@ proc query_plan_graph {sql} {
}
set a "\n QUERY PLAN\n"
append a [append_graph " " dx cx 0]
- regsub -all { 0x[A-F0-9]+\y} $a { xxxxxx} a
+ regsub -all {SUBQUERY 0x[A-F0-9]+\y} $a {SUBQUERY xxxxxx} a
regsub -all {(MATERIALIZE|CO-ROUTINE|SUBQUERY) \d+\y} $a {\1 xxxxxx} a
regsub -all {\((join|subquery)-\d+\)} $a {(\1-xxxxxx)} a
return $a
diff --git a/test/testrunner.tcl b/test/testrunner.tcl
index d3a2c1f4c9..58c718fe47 100644
--- a/test/testrunner.tcl
+++ b/test/testrunner.tcl
@@ -89,7 +89,9 @@ Other PERMUTATION arguments must be run using testfixture, not tclsh:
If no PATTERN arguments are present, all tests specified by the PERMUTATION
are run. Otherwise, each pattern is interpreted as a glob pattern. Only
those tcl tests for which the final component of the filename matches at
-least one specified pattern are run.
+least one specified pattern are run. The glob wildcard '*' is prepended
+to the pattern if it does not start with '^' and appended to every
+pattern that does not end with '$'.
If no PATTERN arguments are present, then various fuzztest, threadtest
and other tests are run as part of the "release" permutation. These are
@@ -277,10 +279,12 @@ set TRG(schema) {
priority INTEGER NOT NULL, -- higher priority jobs may run earlier
/* Fields updated as jobs run */
- starttime INTEGER,
- endtime INTEGER,
+ starttime INTEGER, -- Start time (milliseconds since 1970)
+ endtime INTEGER, -- End time
state TEXT CHECK( state IN ('','ready','running','done','failed','omit') ),
- output TEXT
+ ntest INT, -- Number of test cases run
+ nerr INT, -- Number of errors reported
+ output TEXT -- test output
);
CREATE TABLE config(
@@ -389,14 +393,36 @@ if {[string compare -nocase script [lindex $argv 0]]==0} {
exit
}
+# Compute an elapse time string MM:SS or HH:MM:SS based on the
+# number of milliseconds in the argument.
+#
+proc elapsetime {ms} {
+ set s [expr {int(($ms+500.0)*0.001)}]
+ set hr [expr {$s/3600}]
+ set mn [expr {($s/60)%60}]
+ set sc [expr {$s%60}]
+ if {$hr>0} {
+ return [format %02d:%02d:%02d $hr $mn $sc]
+ } else {
+ return [format %02d:%02d $mn $sc]
+ }
+}
+
# Helper routine for show_status
#
proc display_job {jobdict {tm ""}} {
array set job $jobdict
- set dfname [format %-60s $job(displayname)]
+ if {[string length $job(displayname)]>65} {
+ set dfname [format %.65s... $job(displayname)]
+ } else {
+ set dfname [format %-68s $job(displayname)]
+ }
set dtm ""
if {$tm!=""} {
- set dtm [format %-10s "\[[expr {$tm-$job(starttime)}]ms\]"]
+ set dtm [expr {$tm-$job(starttime)}]
+ set dtm [format %8s [elapsetime $dtm]]
+ } else {
+ set dtm [format %8s ""]
}
puts " $dfname $dtm"
}
@@ -431,6 +457,11 @@ proc show_status {db cls} {
incr S($state) $cnt
incr total $cnt
}
+ set nt 0
+ set ne 0
+ $db eval {
+ SELECT sum(ntest) AS nt, sum(nerr) AS ne FROM jobs HAVING nt>0
+ } break
set fin [expr $S(done)+$S(failed)]
if {$cmdline!=""} {set cmdline " $cmdline"}
@@ -439,38 +470,47 @@ proc show_status {db cls} {
# overwrite.
puts -nonewline "\033\[H"
flush stdout
- set clreol "\033\[K"
- } else {
- set clreol ""
}
- set f ""
- if {$S(failed)>0} {
- set f "$S(failed) FAILED, "
- }
- puts "Command line: \[testrunner.tcl$cmdline\]$clreol"
- puts "Jobs: $nJob "
- puts "Summary: ${tm}ms, ($fin/$total) finished,\
- ${f}$S(running) running "
+ puts [format %-79.79s "Command: \[testrunner.tcl$cmdline\]"]
+ puts [format %-79.79s "Summary: [elapsetime $tm], $fin/$total jobs,\
+ $ne errors, $nt tests"]
set srcdir [file dirname [file dirname $TRG(info_script)]]
+ set nrun 0
+ puts [format %-79s "Running: $S(running) (max: $nJob)"]
if {$S(running)>0} {
- puts "Running: "
$db eval {
SELECT * FROM jobs WHERE state='running' ORDER BY starttime
} job {
+ incr nrun
display_job [array get job] $now
}
}
if {$S(failed)>0} {
- puts "Failures: "
+ # $toshow is the number of failures to report. In $cls mode,
+ # status tries to limit the number of failure reported so that
+ # the status display does not overflow a 24-line terminal. It will
+ # always show at least the most recent 4 failures, even if an overflow
+ # is needed. No limit is imposed for a status within $cls.
+ #
+ if {$cls && $S(failed)>18-$S(running)} {
+ set toshow [expr {18-$S(running)}]
+ if {$toshow<4} {set toshow 4}
+ set shown " (must recent $toshow shown)"
+ } else {
+ set toshow $S(failed)
+ set shown ""
+ }
+ puts [format %-79s "Failed: $S(failed) $shown"]
$db eval {
- SELECT * FROM jobs WHERE state='failed' ORDER BY starttime
+ SELECT * FROM jobs WHERE state='failed'
+ ORDER BY endtime DESC LIMIT $toshow
} job {
display_job [array get job]
}
set nOmit [$db one {SELECT count(*) FROM jobs WHERE state='omit'}]
if {$nOmit} {
- puts "$nOmit jobs omitted due to failures$clreol"
+ puts [format %-79s " ... $nOmit jobs omitted due to failures"]
}
}
if {$cls} {
@@ -842,7 +882,18 @@ proc add_tcl_jobs {build config patternlist {shelldepid ""}} {
if {[llength $patternlist]>0} {
set bMatch 0
foreach p $patternlist {
- if {[string match $p [file tail $f]]} {
+ set p [string trim $p *]
+ if {[string index $p 0]=="^"} {
+ set p [string range $p 1 end]
+ } else {
+ set p "*$p"
+ }
+ if {[string index $p end]=="\$"} {
+ set p [string range $p 0 end-1]
+ } else {
+ set p "$p*"
+ }
+ if {[string match $p "$config [file tail $f]"]} {
set bMatch 1
break
}
@@ -1068,7 +1119,7 @@ proc add_jobs_from_cmdline {patternlist} {
if {[regexp "\\y$b\\y" $TRG(omitconfig)]} continue
set bld [add_build_job $b $TRG(testfixture)]
foreach c [trd_configs $TRG(platform) $b] {
- add_tcl_jobs $bld $c $patternlist
+ add_tcl_jobs $bld $c $patternlist SHELL
}
if {$patternlist==""} {
@@ -1080,6 +1131,15 @@ proc add_jobs_from_cmdline {patternlist} {
}
}
}
+
+ if {[trdb one "SELECT EXISTS(SELECT 1
+ FROM jobs WHERE depid='SHELL')"]} {
+ set sbld [add_shell_build_job $b [lindex $bld 1] [lindex $bld 0]]
+ set sbldid [lindex $sbld 0]
+ trdb eval {
+ UPDATE jobs SET depid=$sbldid WHERE depid='SHELL'
+ }
+ }
}
}
@@ -1120,15 +1180,25 @@ proc make_new_testset {} {
}
proc mark_job_as_finished {jobid output state endtm} {
+ set ntest 1
+ set nerr 0
+ if {$endtm>0} {
+ if {[regexp {\y(\d+) errors out of (\d+) tests} $output all a b]} {
+ set nerr $a
+ set ntest $b
+ }
+ }
r_write_db {
if {$state=="failed"} {
set childstate omit
+ if {$nerr<=0} {set nerr 1}
} else {
set childstate ready
}
trdb eval {
UPDATE jobs
- SET output=$output, state=$state, endtime=$endtm
+ SET output=$output, state=$state, endtime=$endtm,
+ ntest=$ntest, nerr=$nerr
WHERE jobid=$jobid;
UPDATE jobs SET state=$childstate WHERE depid=$jobid;
}
@@ -1371,6 +1441,17 @@ proc run_testset {} {
puts "\nTest database is $TRG(dbname)"
puts "Test log is $TRG(logname)"
+ trdb eval {
+ SELECT sum(ntest) AS totaltest,
+ sum(nerr) AS totalerr
+ FROM jobs
+ } break
+ trdb eval {
+ SELECT max(endtime)-min(starttime) AS totaltime
+ FROM jobs WHERE endtime>0
+ } break;
+ set et [elapsetime $totaltime]
+ puts "$totalerr errors out of $totaltest tests in about $et"
}
# Handle the --buildonly option, if it was specified.
@@ -1401,7 +1482,14 @@ proc explain_layer {indent depid} {
puts "${indent}$displayname in $dirname"
explain_layer "${indent} " $jobid
} elseif {$showtests} {
- puts "${indent}[lindex $displayname end]"
+ set tail [lindex $displayname end]
+ set e1 [lindex $displayname 1]
+ if {[string match config=* $e1]} {
+ set cfg [string range $e1 7 end]
+ puts "${indent}($cfg) $tail"
+ } else {
+ puts "${indent}$tail"
+ }
}
}
}
@@ -1416,7 +1504,7 @@ if {$TRG(explain)} {
explain_tests
} else {
if {$TRG(nJob)>1} {
- puts "splitting work across $TRG(nJob) jobs"
+ puts "splitting work across $TRG(nJob) cores"
}
puts "built testset in [expr $tm/1000]ms.."
handle_buildonly
diff --git a/test/testrunner_data.tcl b/test/testrunner_data.tcl
index af480fc4cf..71a42ab404 100644
--- a/test/testrunner_data.tcl
+++ b/test/testrunner_data.tcl
@@ -16,7 +16,6 @@ namespace eval trd {
set tcltest(linux.Have-Not) veryquick
set tcltest(linux.Secure-Delete) veryquick
set tcltest(linux.Unlock-Notify) veryquick
- set tcltest(linux.User-Auth) veryquick
set tcltest(linux.Update-Delete-Limit) veryquick
set tcltest(linux.Extra-Robustness) veryquick
set tcltest(linux.Device-Two) veryquick
@@ -116,6 +115,7 @@ namespace eval trd {
}
set build(Stdcall) {
-DUSE_STDCALL=1
+ -DSQLITE_USE_ONLY_WIN32=1
-O2
}
@@ -139,10 +139,6 @@ namespace eval trd {
-DSQLITE_THREADSAFE
-DSQLITE_TCL_DEFAULT_FULLMUTEX=1
}
- set build(User-Auth) {
- -O2
- -DSQLITE_USER_AUTHENTICATION=1
- }
set build(Secure-Delete) {
-O2
-DSQLITE_SECURE_DELETE=1
diff --git a/test/windowE.test b/test/windowE.test
index f20bcdaaa8..1cb67f56b0 100644
--- a/test/windowE.test
+++ b/test/windowE.test
@@ -54,5 +54,57 @@ do_execsql_test 1.3 {
5 5,4 5,4,1 5,4,1,6 5,4,1,6,3 5,4,1,6,3,2
}
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 2.0 {
+ CREATE TABLE t1(x);
+}
+
+sqlite3_create_aggregate db
+
+breakpoint
+do_catchsql_test 2.1 {
+ SELECT min(x) OVER w1 FROM t1
+ WINDOW w1 AS (PARTITION BY x_count(x) OVER w1);
+} {1 {x_count() may not be used as a window function}}
+
+do_catchsql_test 2.2 {
+ SELECT min(x) FILTER (WHERE x_count(x) OVER w1) OVER w1 FROM t1
+ WINDOW w1 AS (PARTITION BY x OVER w1);
+} {1 {near "OVER": syntax error}}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 3.0 {
+ BEGIN TRANSACTION;
+ CREATE TABLE t2(c1 INT, c2 REAL);
+ INSERT INTO t2 VALUES
+ (447,0.0), (448,0.0), (449,0.0), (452,0.0), (453,0.0), (454,0.0), (455,0.0),
+ (456,0.0), (459,0.0), (460,0.0), (462,0.0), (463,0.0), (466,0.0), (467,0.0),
+ (468,0.0), (469,0.0), (470,0.0), (473,0.0), (474,0.0), (475,0.0), (476,0.0),
+ (477,0.0), (480,0.0), (481,0.0), (482,0.0), (483,0.0), (484,0.0), (487,0.0),
+ (488,0.0), (489,0.0), (490,0.0), (491,0.0), (494,0.0), (495,0.0), (496,0.0),
+ (497,0.0), (498,0.0), (501,0.0), (502,0.0), (503,0.0), (504,0.0), (505,0.0),
+ (508,0.0), (509,0.0), (510,0.0), (511,0.0), (512,0.0), (515,0.0), (516,0.0),
+ (517,0.0), (518,0.0), (519,0.0), (522,0.0), (523,0.0), (524,0.0), (525,0.0),
+ (526,0.0), (529,0.0), (530,0.0), (531,0.0), (532,0.0), (533,0.0), (536,0.0),
+ (537,1.0), (538,0.0), (539,0.0), (540,0.0), (543,0.0), (544,0.0);
+ COMMIT;
+}
+
+do_execsql_test 3.1 {
+ select c1, max(c2) over (order by c1 range 366.0 preceding) from t2;
+} {
+ 447 0.0 448 0.0 449 0.0 452 0.0 453 0.0 454 0.0 455 0.0 456 0.0 459 0.0
+ 460 0.0 462 0.0 463 0.0 466 0.0 467 0.0 468 0.0 469 0.0 470 0.0 473 0.0
+ 474 0.0 475 0.0 476 0.0 477 0.0 480 0.0 481 0.0 482 0.0 483 0.0 484 0.0
+ 487 0.0 488 0.0 489 0.0 490 0.0 491 0.0 494 0.0 495 0.0 496 0.0 497 0.0
+ 498 0.0 501 0.0 502 0.0 503 0.0 504 0.0 505 0.0 508 0.0 509 0.0 510 0.0
+ 511 0.0 512 0.0 515 0.0 516 0.0 517 0.0 518 0.0 519 0.0 522 0.0 523 0.0
+ 524 0.0 525 0.0 526 0.0 529 0.0 530 0.0 531 0.0 532 0.0 533 0.0 536 0.0
+ 537 1.0 538 1.0 539 1.0 540 1.0 543 1.0 544 1.0
+}
+
+
finish_test
diff --git a/test/with2.test b/test/with2.test
index 660df52b77..68790fe860 100644
--- a/test/with2.test
+++ b/test/with2.test
@@ -156,6 +156,16 @@ do_execsql_test 1.15 {
SELECT * FROM t4;
} {4 5}
+do_execsql_test 1.15.2 {
+ WITH
+ t4(x) AS (
+ VALUES(4)
+ UNION ALL
+ SELECT x+1 FROM (SELECT * FROM main.t4) WHERE x<10
+ )
+ SELECT * FROM t4;
+} {4 5}
+
do_catchsql_test 1.16 {
WITH
t4(x) AS (
diff --git a/tool/mkctimec.tcl b/tool/mkctimec.tcl
index f97df070b8..0e92bae6bc 100755
--- a/tool/mkctimec.tcl
+++ b/tool/mkctimec.tcl
@@ -450,6 +450,7 @@ if {[catch {set cfd [open $destfile w]}]!=0} {
puts stderr "File '$destfile' unwritable."
exit 1;
}
+fconfigure $cfd -translation binary
puts $cfd $::headWarning;
puts $cfd $::headCode;