Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cb6105badd | |||
| 090e39c7db | |||
| 2688bc35cc | |||
| 6f5ebb23c4 | |||
| 3f00c1b31c | |||
| 67276c648b | |||
| ff549b1384 | |||
| 5b9ba67d8e | |||
| c2526e2c2d | |||
| 29ef48a097 | |||
| 47f478c9da | |||
| a93344297f | |||
| b2bc4c9338 | |||
| d07908335d | |||
| 76214b6661 | |||
| c5c26d7f59 | |||
| 27039a32d5 | |||
| 8276a5f336 | |||
| 0febad8fdf | |||
| 8d6a941353 | |||
| da3c2bb7db | |||
| 2295e72ef6 | |||
| 7722f9a60d | |||
| 1088fc8387 | |||
| fe8d9f4287 | |||
| 1c6b0e5db9 | |||
| e467488d36 | |||
| 0495cf05a0 | |||
| c8f9803dc3 | |||
| b4b2be91dd | |||
| 6292bb4fc0 | |||
| 90d7893739 | |||
| efba33ed48 | |||
| e6fa99aebd | |||
| c028221c3f | |||
| fef8c0b942 | |||
| 1c83877d82 | |||
| 1dda1e22c9 | |||
| 84548fbd21 | |||
| 8fa99e9568 | |||
| 02726b172a | |||
| 61d0a37cdf | |||
| 4ba9f44d18 | |||
| bd0d38f410 | |||
| eedca24422 | |||
| 241677519a | |||
| 22402d7d05 | |||
| c283f969aa | |||
| 26b57aaed7 | |||
| 6edc7ab317 | |||
| 937ea25bcc | |||
| 2f51f78f97 | |||
| a048396674 | |||
| f78c986679 | |||
| 3414ff463a | |||
| 5f61fa6789 | |||
| 599e56af76 | |||
| 5e34405f74 | |||
| 6d12b9f850 | |||
| a7dc704f70 | |||
| 9d8ad69b1f | |||
| 11379c0809 | |||
| d9369971ef |
+15
-41
@@ -418,8 +418,6 @@ TESTSRC = \
|
||||
$(TOP)/ext/recover/sqlite3recover.c \
|
||||
$(TOP)/ext/recover/dbdata.c \
|
||||
$(TOP)/ext/recover/test_recover.c \
|
||||
$(TOP)/ext/intck/test_intck.c \
|
||||
$(TOP)/ext/intck/sqlite3intck.c \
|
||||
$(TOP)/ext/rbu/test_rbu.c
|
||||
|
||||
# Statically linked extensions
|
||||
@@ -449,7 +447,6 @@ TESTSRC += \
|
||||
$(TOP)/ext/misc/percentile.c \
|
||||
$(TOP)/ext/misc/prefixes.c \
|
||||
$(TOP)/ext/misc/qpvtab.c \
|
||||
$(TOP)/ext/misc/randomjson.c \
|
||||
$(TOP)/ext/misc/regexp.c \
|
||||
$(TOP)/ext/misc/remember.c \
|
||||
$(TOP)/ext/misc/series.c \
|
||||
@@ -603,7 +600,6 @@ SHELL_OPT += -DSQLITE_ENABLE_DBPAGE_VTAB
|
||||
SHELL_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB
|
||||
SHELL_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB
|
||||
SHELL_OPT += -DSQLITE_ENABLE_OFFSET_SQL_FUNC
|
||||
SHELL_OPT += -DSQLITE_STRICT_SUBTYPE=1
|
||||
FUZZERSHELL_OPT =
|
||||
FUZZCHECK_OPT += -I$(TOP)/test
|
||||
FUZZCHECK_OPT += -I$(TOP)/ext/recover
|
||||
@@ -634,9 +630,7 @@ FUZZCHECK_OPT += \
|
||||
-DSQLITE_MAX_MMAP_SIZE=0 \
|
||||
-DSQLITE_OMIT_LOAD_EXTENSION \
|
||||
-DSQLITE_PRINTF_PRECISION_LIMIT=1000 \
|
||||
-DSQLITE_PRIVATE="" \
|
||||
-DSQLITE_STRICT_SUBTYPE=1 \
|
||||
-DSQLITE_STATIC_RANDOMJSON
|
||||
-DSQLITE_PRIVATE=""
|
||||
|
||||
FUZZCHECK_SRC += $(TOP)/test/fuzzcheck.c
|
||||
FUZZCHECK_SRC += $(TOP)/test/ossfuzz.c
|
||||
@@ -644,7 +638,6 @@ FUZZCHECK_SRC += $(TOP)/test/fuzzinvariants.c
|
||||
FUZZCHECK_SRC += $(TOP)/ext/recover/dbdata.c
|
||||
FUZZCHECK_SRC += $(TOP)/ext/recover/sqlite3recover.c
|
||||
FUZZCHECK_SRC += $(TOP)/test/vt02.c
|
||||
FUZZCHECK_SRC += $(TOP)/ext/misc/randomjson.c
|
||||
DBFUZZ_OPT =
|
||||
ST_OPT = -DSQLITE_OS_KV_OPTIONAL
|
||||
|
||||
@@ -717,21 +710,6 @@ fuzzcheck-asan$(TEXE): $(FUZZCHECK_SRC) sqlite3.c sqlite3.h $(FUZZCHECK_DEP)
|
||||
fuzzcheck-ubsan$(TEXE): $(FUZZCHECK_SRC) sqlite3.c sqlite3.h $(FUZZCHECK_DEP)
|
||||
$(LTLINK) -o $@ -fsanitize=undefined $(FUZZCHECK_OPT) $(FUZZCHECK_SRC) sqlite3.c $(TLIBS)
|
||||
|
||||
# Usage: FUZZDB=filename make run-fuzzcheck
|
||||
#
|
||||
# Where filename is a fuzzcheck database, this target builds and runs
|
||||
# fuzzcheck, fuzzcheck-asan, and fuzzcheck-ubsan on that database.
|
||||
#
|
||||
# FUZZDB can be a glob pattern of two or more databases. Example:
|
||||
#
|
||||
# FUZZDB=test/fuzzdata*.db make run-fuzzcheck
|
||||
#
|
||||
run-fuzzcheck: fuzzcheck$(TEXE) fuzzcheck-asan$(TEXE) fuzzcheck-ubsan$(TEXE)
|
||||
@if test "$(FUZZDB)" = ""; then echo 'ERROR: No FUZZDB specified. Rerun with FUZZDB=filename'; exit 1; fi
|
||||
./fuzzcheck$(TEXE) --spinner $(FUZZDB)
|
||||
./fuzzcheck-asan$(TEXE) --spinner $(FUZZDB)
|
||||
./fuzzcheck-ubsan$(TEXE) --spinner $(FUZZDB)
|
||||
|
||||
ossshell$(TEXE): $(TOP)/test/ossfuzz.c $(TOP)/test/ossshell.c sqlite3.c sqlite3.h
|
||||
$(LTLINK) -o $@ $(FUZZCHECK_OPT) $(TOP)/test/ossshell.c \
|
||||
$(TOP)/test/ossfuzz.c sqlite3.c $(TLIBS)
|
||||
@@ -817,8 +795,7 @@ has_tclsh85:
|
||||
touch .target_source
|
||||
|
||||
sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl src-verify has_tclsh84
|
||||
$(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_LINE_MACROS) \
|
||||
$(EXTRA_SRC)
|
||||
$(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_LINE_MACROS)
|
||||
cp tsrc/sqlite3ext.h .
|
||||
cp $(TOP)/ext/session/sqlite3session.h .
|
||||
|
||||
@@ -829,8 +806,7 @@ sqlite3r.c: sqlite3.c sqlite3r.h has_tclsh84
|
||||
cp $(TOP)/ext/recover/sqlite3recover.c tsrc/
|
||||
cp $(TOP)/ext/recover/sqlite3recover.h tsrc/
|
||||
cp $(TOP)/ext/recover/dbdata.c tsrc/
|
||||
$(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl --enable-recover \
|
||||
$(AMALGAMATION_LINE_MACROS) $(EXTRA_SRC)
|
||||
$(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl --enable-recover $(AMALGAMATION_LINE_MACROS)
|
||||
|
||||
sqlite3ext.h: .target_source
|
||||
cp tsrc/sqlite3ext.h .
|
||||
@@ -1159,21 +1135,21 @@ keywordhash.h: $(TOP)/tool/mkkeywordhash.c
|
||||
# Source files that go into making shell.c
|
||||
SHELL_SRC = \
|
||||
$(TOP)/src/shell.c.in \
|
||||
$(TOP)/ext/consio/console_io.c \
|
||||
$(TOP)/ext/consio/console_io.h \
|
||||
$(TOP)/ext/misc/appendvfs.c \
|
||||
$(TOP)/ext/misc/appendvfs.c \
|
||||
$(TOP)/ext/misc/completion.c \
|
||||
$(TOP)/ext/misc/decimal.c \
|
||||
$(TOP)/ext/misc/basexx.c \
|
||||
$(TOP)/ext/misc/base64.c \
|
||||
$(TOP)/ext/misc/base85.c \
|
||||
$(TOP)/ext/consio/console_io.c \
|
||||
$(TOP)/ext/consio/console_io.h \
|
||||
$(TOP)/ext/misc/decimal.c \
|
||||
$(TOP)/ext/misc/basexx.c \
|
||||
$(TOP)/ext/misc/base64.c \
|
||||
$(TOP)/ext/misc/base85.c \
|
||||
$(TOP)/ext/misc/fileio.c \
|
||||
$(TOP)/ext/misc/ieee754.c \
|
||||
$(TOP)/ext/misc/regexp.c \
|
||||
$(TOP)/ext/misc/series.c \
|
||||
$(TOP)/ext/misc/ieee754.c \
|
||||
$(TOP)/ext/misc/regexp.c \
|
||||
$(TOP)/ext/misc/series.c \
|
||||
$(TOP)/ext/misc/shathree.c \
|
||||
$(TOP)/ext/misc/sqlar.c \
|
||||
$(TOP)/ext/misc/uint.c \
|
||||
$(TOP)/ext/misc/uint.c \
|
||||
$(TOP)/ext/expert/sqlite3expert.c \
|
||||
$(TOP)/ext/expert/sqlite3expert.h \
|
||||
$(TOP)/ext/misc/zipfile.c \
|
||||
@@ -1182,7 +1158,7 @@ SHELL_SRC = \
|
||||
$(TOP)/ext/recover/dbdata.c \
|
||||
$(TOP)/ext/recover/sqlite3recover.c \
|
||||
$(TOP)/ext/recover/sqlite3recover.h \
|
||||
$(TOP)/src/test_windirent.c
|
||||
$(TOP)/src/test_windirent.c
|
||||
|
||||
shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl has_tclsh84
|
||||
$(TCLSH_CMD) $(TOP)/tool/mkshellc.tcl >shell.c
|
||||
@@ -1300,8 +1276,6 @@ TESTFIXTURE_FLAGS += -DSQLITE_ENABLE_STMTVTAB
|
||||
TESTFIXTURE_FLAGS += -DSQLITE_ENABLE_DBPAGE_VTAB
|
||||
TESTFIXTURE_FLAGS += -DSQLITE_ENABLE_BYTECODE_VTAB
|
||||
TESTFIXTURE_FLAGS += -DSQLITE_CKSUMVFS_STATIC
|
||||
TESTFIXTURE_FLAGS += -DSQLITE_STATIC_RANDOMJSON
|
||||
TESTFIXTURE_FLAGS += -DSQLITE_STRICT_SUBTYPE=1
|
||||
|
||||
TESTFIXTURE_SRC0 = $(TESTSRC2) libsqlite3.la
|
||||
TESTFIXTURE_SRC1 = sqlite3.c
|
||||
|
||||
+4
-17
@@ -16,10 +16,6 @@ TOP = .
|
||||
!IFNDEF USE_AMALGAMATION
|
||||
USE_AMALGAMATION = 1
|
||||
!ENDIF
|
||||
|
||||
!IFNDEF EXTRA_SRC
|
||||
EXTRA_SRC =
|
||||
!ENDIF
|
||||
# <</mark>>
|
||||
|
||||
# Set this non-0 to enable full warnings (-W4, etc) when compiling.
|
||||
@@ -1588,7 +1584,6 @@ TESTEXT = \
|
||||
$(TOP)\ext\misc\percentile.c \
|
||||
$(TOP)\ext\misc\prefixes.c \
|
||||
$(TOP)\ext\misc\qpvtab.c \
|
||||
$(TOP)\ext\misc\randomjson.c \
|
||||
$(TOP)\ext\misc\regexp.c \
|
||||
$(TOP)\ext\misc\remember.c \
|
||||
$(TOP)\ext\misc\series.c \
|
||||
@@ -1599,8 +1594,6 @@ TESTEXT = \
|
||||
$(TOP)\ext\rtree\test_rtreedoc.c \
|
||||
$(TOP)\ext\recover\sqlite3recover.c \
|
||||
$(TOP)\ext\recover\test_recover.c \
|
||||
$(TOP)\ext\intck\test_intck.c \
|
||||
$(TOP)\ext\intck\sqlite3intck.c \
|
||||
$(TOP)\ext\recover\dbdata.c
|
||||
|
||||
# If use of zlib is enabled, add the "zipfile.c" source file.
|
||||
@@ -1699,7 +1692,6 @@ SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1
|
||||
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC=1
|
||||
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION=1
|
||||
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_STMT_SCANSTATUS=1
|
||||
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_STRICT_SUBTYPE=1
|
||||
!ENDIF
|
||||
|
||||
# <<mark>>
|
||||
@@ -1736,8 +1728,6 @@ FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_MAX_MMAP_SIZE=0
|
||||
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_OMIT_LOAD_EXTENSION
|
||||
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_PRINTF_PRECISION_LIMIT=1000
|
||||
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_PRIVATE=""
|
||||
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_STRICT_SUBTYPE=1
|
||||
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_STATIC_RANDOMJSON
|
||||
|
||||
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_MAX_MEMORY=50000000
|
||||
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_PRINTF_PRECISION_LIMIT=1000
|
||||
@@ -1754,7 +1744,6 @@ FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\test\fuzzinvariants.c
|
||||
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\test\vt02.c
|
||||
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\recover\dbdata.c
|
||||
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\recover\sqlite3recover.c
|
||||
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\randomjson.c
|
||||
|
||||
OSSSHELL_SRC = $(TOP)\test\ossshell.c $(TOP)\test\ossfuzz.c
|
||||
DBFUZZ_COMPILE_OPTS = -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION
|
||||
@@ -1834,8 +1823,8 @@ $(SQLITE3EXE): shell.c $(SHELL_CORE_DEP) $(LIBRESOBJS) $(SHELL_CORE_SRC) $(SQLIT
|
||||
/link $(SQLITE3EXEPDB) $(LDFLAGS) $(LTLINKOPTS) $(SHELL_LINK_OPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS)
|
||||
|
||||
# <<mark>>
|
||||
sqldiff.exe: $(TOP)\tool\sqldiff.c $(TOP)\ext\consio\console_io.h $(TOP)\ext\consio\console_io.c $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS)
|
||||
$(LTLINK) $(NO_WARN) -I$(TOP)\ext\consio $(TOP)\tool\sqldiff.c $(TOP)\ext\consio\console_io.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(LIBRESOBJS)
|
||||
sqldiff.exe: $(TOP)\tool\sqldiff.c $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS)
|
||||
$(LTLINK) $(NO_WARN) $(TOP)\tool\sqldiff.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(LIBRESOBJS)
|
||||
|
||||
dbhash.exe: $(TOP)\tool\dbhash.c $(SQLITE3C) $(SQLITE3H)
|
||||
$(LTLINK) $(NO_WARN) $(TOP)\tool\dbhash.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
@@ -1919,7 +1908,7 @@ mptest: mptester.exe
|
||||
echo > .target_source
|
||||
|
||||
sqlite3.c: .target_source sqlite3ext.h sqlite3session.h $(MKSQLITE3C_TOOL) src-verify.exe
|
||||
$(TCLSH_CMD) $(MKSQLITE3C_TOOL) $(MKSQLITE3C_ARGS) $(EXTRA_SRC)
|
||||
$(TCLSH_CMD) $(MKSQLITE3C_TOOL) $(MKSQLITE3C_ARGS)
|
||||
|
||||
sqlite3-all.c: sqlite3.c $(TOP)\tool\split-sqlite3c.tcl
|
||||
$(TCLSH_CMD) $(TOP)\tool\split-sqlite3c.tcl
|
||||
@@ -2444,8 +2433,6 @@ TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_ENABLE_DBPAGE_VTAB=1
|
||||
TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_ENABLE_BYTECODE_VTAB=1
|
||||
TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_CKSUMVFS_STATIC=1
|
||||
TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) $(TEST_CCONV_OPTS)
|
||||
TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_STATIC_RANDOMJSON
|
||||
TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_STRICT_SUBTYPE=1
|
||||
|
||||
TESTFIXTURE_SRC0 = $(TESTEXT) $(TESTSRC2)
|
||||
TESTFIXTURE_SRC1 = $(TESTEXT) $(SQLITE3C)
|
||||
@@ -2556,7 +2543,7 @@ smoketest: $(TESTPROGS)
|
||||
shelltest: $(TESTPROGS)
|
||||
.\testfixture.exe $(TOP)\test\permutations.test shell
|
||||
|
||||
sqlite3_analyzer.c: $(SQLITE3C) $(SQLITE3H) $(TOP)\src\tclsqlite.c $(TOP)\tool\spaceanal.tcl $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqlite3_analyzer.c.in $(TOP)\ext\consio\console_io.h $(TOP)\ext\consio\console_io.c $(SQLITE_TCL_DEP)
|
||||
sqlite3_analyzer.c: $(SQLITE3C) $(SQLITE3H) $(TOP)\src\tclsqlite.c $(TOP)\tool\spaceanal.tcl $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqlite3_analyzer.c.in $(SQLITE_TCL_DEP)
|
||||
$(TCLSH_CMD) $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqlite3_analyzer.c.in > $@
|
||||
|
||||
sqlite3_analyzer.exe: sqlite3_analyzer.c $(LIBRESOBJS)
|
||||
|
||||
@@ -159,7 +159,7 @@ extension and only later escaped to the wild as an independent library.)
|
||||
|
||||
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
|
||||
See [How SQLite Is Tested](http://www.sqlite.org/testing.html) for
|
||||
additional information.
|
||||
|
||||
The **ext/** subdirectory contains code for extensions. The
|
||||
@@ -183,7 +183,7 @@ manually-edited files and automatically-generated files.
|
||||
|
||||
The SQLite interface is defined by the **sqlite3.h** header file, which is
|
||||
generated from src/sqlite.h.in, ./manifest.uuid, and ./VERSION. The
|
||||
[Tcl script](https://www.tcl.tk) at tool/mksqlite3h.tcl does the conversion.
|
||||
[Tcl script](http://www.tcl.tk) at tool/mksqlite3h.tcl does the conversion.
|
||||
The manifest.uuid file contains the SHA3 hash of the particular check-in
|
||||
and is used to generate the SQLITE\_SOURCE\_ID macro. The VERSION file
|
||||
contains the current SQLite version number. The sqlite3.h header is really
|
||||
@@ -250,14 +250,14 @@ individual source file exceeds 32K lines in length.
|
||||
## How It All Fits Together
|
||||
|
||||
SQLite is modular in design.
|
||||
See the [architectural description](https://www.sqlite.org/arch.html)
|
||||
See the [architectural description](http://www.sqlite.org/arch.html)
|
||||
for details. Other documents that are useful in
|
||||
(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
|
||||
[file format](http://www.sqlite.org/fileformat2.html) description,
|
||||
the [virtual machine](http://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).
|
||||
[how transactions work](http://www.sqlite.org/atomiccommit.html), and
|
||||
the [overview of the query planner](http://www.sqlite.org/optoverview.html).
|
||||
|
||||
Years of effort have gone into optimizing SQLite, both
|
||||
for small size and high performance. And optimizations tend to result in
|
||||
@@ -353,7 +353,7 @@ hidden by also modifying the makefiles.
|
||||
|
||||
## Contacts
|
||||
|
||||
The main SQLite website is [https://sqlite.org/](https://sqlite.org/)
|
||||
The main SQLite website is [http:/sqlite.org/](http://sqlite.org/)
|
||||
with geographically distributed backups at
|
||||
[https://www2.sqlite.org/](https://www2.sqlite.org) and
|
||||
[https://www3.sqlite.org/](https://www3.sqlite.org).
|
||||
[http://www2.sqlite.org/](http://www2.sqlite.org) and
|
||||
[http://www3.sqlite.org/](http://www3.sqlite.org).
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.3 KiB |
@@ -990,7 +990,6 @@ SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1
|
||||
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC=1
|
||||
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION=1
|
||||
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_STMT_SCANSTATUS=1
|
||||
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_STRICT_SUBTYPE=1
|
||||
!ENDIF
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ dnl to configure the system for the local environment.
|
||||
# so that we create the export library with the dll.
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
AC_INIT([sqlite],[3.46.0])
|
||||
AC_INIT([sqlite],[3.44.4])
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Call TEA_INIT as the first TEA_ macro to set up initial vars.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.69 for sqlite 3.46.0.
|
||||
# Generated by GNU Autoconf 2.69 for sqlite 3.44.4.
|
||||
#
|
||||
#
|
||||
# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
|
||||
@@ -726,8 +726,8 @@ MAKEFLAGS=
|
||||
# Identity of this package.
|
||||
PACKAGE_NAME='sqlite'
|
||||
PACKAGE_TARNAME='sqlite'
|
||||
PACKAGE_VERSION='3.46.0'
|
||||
PACKAGE_STRING='sqlite 3.46.0'
|
||||
PACKAGE_VERSION='3.44.4'
|
||||
PACKAGE_STRING='sqlite 3.44.4'
|
||||
PACKAGE_BUGREPORT=''
|
||||
PACKAGE_URL=''
|
||||
|
||||
@@ -1472,7 +1472,7 @@ if test "$ac_init_help" = "long"; then
|
||||
# Omit some internal or obsolete options to make the list less imposing.
|
||||
# This message is too long to be a string in the A/UX 3.1 sh.
|
||||
cat <<_ACEOF
|
||||
\`configure' configures sqlite 3.46.0 to adapt to many kinds of systems.
|
||||
\`configure' configures sqlite 3.44.4 to adapt to many kinds of systems.
|
||||
|
||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||
|
||||
@@ -1537,7 +1537,7 @@ fi
|
||||
|
||||
if test -n "$ac_init_help"; then
|
||||
case $ac_init_help in
|
||||
short | recursive ) echo "Configuration of sqlite 3.46.0:";;
|
||||
short | recursive ) echo "Configuration of sqlite 3.44.4:";;
|
||||
esac
|
||||
cat <<\_ACEOF
|
||||
|
||||
@@ -1668,7 +1668,7 @@ fi
|
||||
test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
sqlite configure 3.46.0
|
||||
sqlite configure 3.44.4
|
||||
generated by GNU Autoconf 2.69
|
||||
|
||||
Copyright (C) 2012 Free Software Foundation, Inc.
|
||||
@@ -2087,7 +2087,7 @@ cat >config.log <<_ACEOF
|
||||
This file contains any messages produced by compilers while
|
||||
running configure, to aid debugging if configure makes a mistake.
|
||||
|
||||
It was created by sqlite $as_me 3.46.0, which was
|
||||
It was created by sqlite $as_me 3.44.4, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
$ $0 $@
|
||||
@@ -12481,7 +12481,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
|
||||
# report actual input values of CONFIG_FILES etc. instead of their
|
||||
# values after options handling.
|
||||
ac_log="
|
||||
This file was extended by sqlite $as_me 3.46.0, which was
|
||||
This file was extended by sqlite $as_me 3.44.4, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
@@ -12547,7 +12547,7 @@ _ACEOF
|
||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
||||
ac_cs_version="\\
|
||||
sqlite config.status 3.46.0
|
||||
sqlite config.status 3.44.4
|
||||
configured by $0, generated by GNU Autoconf 2.69,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@
|
||||
# you don't need (for example BLT) by erasing or commenting out
|
||||
# the corresponding code.
|
||||
#
|
||||
AC_INIT([sqlite],m4_esyscmd(cat VERSION | tr -d '\n'))
|
||||
AC_INIT([sqlite],[m4_esyscmd(cat VERSION | tr -d '\n')])
|
||||
|
||||
dnl Make sure the local VERSION file matches this configure script
|
||||
sqlite_version_sanity_check=`cat $srcdir/VERSION | tr -d '\n'`
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Notes On Compiling SQLite On Windows 11
|
||||
|
||||
Here are step-by-step instructions on how to build SQLite from
|
||||
canonical source on a new Windows 11 PC, as of 2023-11-01:
|
||||
canonical source on a new Windows 11 PC, as of 2023-08-16:
|
||||
|
||||
1. Install Microsoft Visual Studio. The free "community edition"
|
||||
will work fine. Do a standard install for C++ development.
|
||||
@@ -84,18 +84,6 @@ following minor changes:
|
||||
<li> `set PATH=c:\tcl32\bin;%PATH%`
|
||||
</ul>
|
||||
|
||||
## Building a DLL
|
||||
|
||||
The command the developers use for building the deliverable DLL on the
|
||||
[download page](https://sqlite.org/download.html) is as follows:
|
||||
|
||||
> ~~~~
|
||||
nmake /f Makefile.msc sqlite3.dll USE_NATIVE_LIBPATHS=1 "OPTS=-DSQLITE_ENABLE_FTS3=1 -DSQLITE_ENABLE_FTS4=1 -DSQLITE_ENABLE_FTS5=1 -DSQLITE_ENABLE_RTREE=1 -DSQLITE_ENABLE_JSON1=1 -DSQLITE_ENABLE_GEOPOLY=1 -DSQLITE_ENABLE_SESSION=1 -DSQLITE_ENABLE_PREUPDATE_HOOK=1 -DSQLITE_ENABLE_SERIALIZE=1 -DSQLITE_ENABLE_MATH_FUNCTIONS=1"
|
||||
~~~~
|
||||
|
||||
That command generates both the sqlite3.dll and sqlite3.def files. The same
|
||||
command works for both 32-bit and 64-bit builds.
|
||||
|
||||
## Statically Linking The TCL Library
|
||||
|
||||
Some utility programs associated with SQLite need to be linked
|
||||
|
||||
-290
@@ -1,290 +0,0 @@
|
||||
# The JSONB Format
|
||||
|
||||
This document describes SQLite's JSONB binary encoding of
|
||||
JSON.
|
||||
|
||||
## 1.0 What Is JSONB?
|
||||
|
||||
Beginning with version 3.45.0 (circa 2024-01-01), SQLite supports an
|
||||
alternative binary encoding of JSON which we call "JSONB". JSONB is
|
||||
a binary format that stored as a BLOB.
|
||||
|
||||
The advantage of JSONB over ordinary text RFC 8259 JSON is that JSONB
|
||||
is both slightly smaller (by between 5% and 10% in most cases) and
|
||||
can be processed in less than half the number of CPU cycles. The built-in
|
||||
[JSON SQL functions] of SQLite can accept either ordinary text JSON
|
||||
or the binary JSONB encoding for any of their JSON inputs.
|
||||
|
||||
The "JSONB" name is inspired by [PostgreSQL](https://postgresql.org), but the
|
||||
on-disk format for SQLite's JSONB is not the same as PostgreSQL's.
|
||||
The two formats have the same name, but they have wildly different internal
|
||||
representations and are not in any way binary compatible.
|
||||
|
||||
The central idea behind this JSONB specification is that each element
|
||||
begins with a header that includes the size and type of that element.
|
||||
The header takes the place of punctuation such as double-quotes,
|
||||
curly-brackes, square-brackets, commas, and colons. Since the size
|
||||
and type of each element is contained in its header, the element can
|
||||
be read faster since it is no longer necessary to carefully scan forward
|
||||
looking for the closing delimiter. The payload of JSONB is the same
|
||||
as for corresponding text JSON. The same payload bytes occur in the
|
||||
same order. The only real difference between JSONB and ordinary text
|
||||
JSON is that JSONB includes a binary header on
|
||||
each element and omits delimiter and separator punctuation.
|
||||
|
||||
### 1.1 Internal Use Only
|
||||
|
||||
The details of the JSONB are not intended to be visible to application
|
||||
developers. Application developers should look at JSONB as an opaque BLOB
|
||||
used internally by SQLite. Nevertheless, we want the format to be backwards
|
||||
compatible across all future versions of SQLite. To that end, the format
|
||||
is documented by this file in the source tree. But this file should be
|
||||
used only by SQLite core developers, not by developers of applications
|
||||
that only use SQLite.
|
||||
|
||||
## 2.0 The Purpose Of This Document
|
||||
|
||||
JSONB is not intended as an external format to be used by
|
||||
applications. JSONB is designed for internal use by SQLite only.
|
||||
Programmers do not need to understand the JSONB format in order to
|
||||
use it effectively.
|
||||
Applications should access JSONB only through the [JSON SQL functions],
|
||||
not by looking at individual bytes of the BLOB.
|
||||
|
||||
However, JSONB is intended to be portable and backwards compatible
|
||||
for all future versions of SQLite. In other words, you should not have
|
||||
to export and reimport your SQLite database files when you upgrade to
|
||||
a newer SQLite version. For that reason, the JSONB format needs to
|
||||
be well-defined.
|
||||
|
||||
This document is therefore similar in purpose to the
|
||||
[SQLite database file format] document that describes the on-disk
|
||||
format of an SQLite database file. Applications are not expected
|
||||
to directly read and write the bits and bytes of SQLite database files.
|
||||
The SQLite database file format is carefully documented so that it
|
||||
can be stable and enduring. In the same way, the JSONB representation
|
||||
of JSON is documented here so that it too can be stable and enduring,
|
||||
not so that applications can read or writes individual bytes.
|
||||
|
||||
## 3.0 Encoding
|
||||
|
||||
JSONB is a direct translation of the underlying text JSON. The difference
|
||||
is that JSONB uses a binary encoding that is faster to parse compared to
|
||||
the detailed syntax of text JSON.
|
||||
|
||||
Each JSON element is encoded as a header and a payload. The header
|
||||
determines type of element (string, numeric, boolean, null, object, or
|
||||
array) and the size of the payload. The header can be between 1 and
|
||||
9 bytes in size. The payload can be any size from zero bytes up to the
|
||||
maximum allowed BLOB size.
|
||||
|
||||
### 3.1 Payload Size
|
||||
|
||||
The upper four bits of the first byte of the header determine size of the
|
||||
header and possibly also the size of the payload.
|
||||
If the upper four bits have a value between 0 and 11, then the header is
|
||||
exactly one byte in size and the payload size is determined by those
|
||||
upper four bits. If the upper four bits have a value between 12 and 15,
|
||||
that means that the total header size is 2, 3, 5, or 9 bytes and the
|
||||
payload size is unsigned big-endian integer that is contained in the
|
||||
subsequent bytes. The size integer is the one byte that following the
|
||||
initial header byte if the upper four bits
|
||||
are 12, two bytes if the upper bits are 13, four bytes if the upper bits
|
||||
are 14, and eight bytes if the upper bits are 15. The current design
|
||||
of SQLite does not support BLOB values larger than 2GiB, so the eight-byte
|
||||
variant of the payload size integer will never be used by the current code.
|
||||
The eight-byte payload size integer is included in the specification
|
||||
to allow for future expansion.
|
||||
|
||||
The header for an element does *not* need to be in its simplest
|
||||
form. For example, consider the JSON numeric value "`1`".
|
||||
That element can be encode in five different ways:
|
||||
|
||||
* `0x13 0x31`
|
||||
* `0xc3 0x01 0x31`
|
||||
* `0xd3 0x00 0x01 0x31`
|
||||
* `0xe3 0x00 0x00 0x00 0x01 0x31`
|
||||
* `0xf3 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x01 0x31`
|
||||
|
||||
The shortest encoding is preferred, of course, and usually happens with
|
||||
primitive elements such as numbers. However the total size of an array
|
||||
or object might not be known exactly when the header of the element is
|
||||
first generated. It is convenient to reserve space for the largest
|
||||
possible header and then go back and fill in the correct payload size
|
||||
at the end. This technique can result in array or object headers that
|
||||
are larger than absolutely necessary.
|
||||
|
||||
### 3.2 Element Type
|
||||
|
||||
The least-significant four bits of the first byte of the header (the first
|
||||
byte masked against 0x0f) determine element type. The following codes are
|
||||
used:
|
||||
|
||||
<ol>
|
||||
<li type="0"><p><b>NULL</b> →
|
||||
The element is a JSON "null". The payload size for a true JSON NULL must
|
||||
must be zero. Future versions of SQLite might extend the JSONB format
|
||||
with elements that have a zero element type but a non-zero size. In that
|
||||
way, legacy versions of SQLite will interpret the element as a NULL
|
||||
for backwards compatibility while newer versions will interpret the
|
||||
element in some other way.
|
||||
|
||||
<li value="1"><p><b>TRUE</b> →
|
||||
The element is a JSON "true". The payload size must be zero for a actual
|
||||
"true" value. Elements with type 1 and a non-zero payload size are
|
||||
reserved for future expansion. Legacy implementations that see an element
|
||||
type of 1 with a non-zero payload size should continue to interpret that
|
||||
element as "true" for compatibility.
|
||||
|
||||
<li value="2"><p><b>FALSE</b> →
|
||||
The element is a JSON "false". The payload size must be zero for a actual
|
||||
"false" value. Elements with type 2 and a non-zero payload size are
|
||||
reserved for future expansion. Legacy implementations that see an element
|
||||
type of 2 with a non-zero payload size should continue to interpret that
|
||||
element as "false" for compatibility.
|
||||
|
||||
<li value="3"><p><b>INT</b> →
|
||||
The element is a JSON integer value in the canonical
|
||||
RFC 8259 format, without extensions. The payload is the ASCII
|
||||
text representation of that numeric value.
|
||||
|
||||
<li value="4"><p><b>INT5</b> →
|
||||
The element is a JSON integer value that is not in the
|
||||
canonical format. The payload is the ASCII
|
||||
text representation of that numeric value. Because the payload is in a
|
||||
non-standard format, it will need to be translated when the JSONB is
|
||||
converted into RFC 8259 text JSON.
|
||||
|
||||
<li value="5"><p><b>FLOAT</b> →
|
||||
The element is a JSON floating-point value in the canonical
|
||||
RFC 8259 format, without extensions. The payload is the ASCII
|
||||
text representation of that numeric value.
|
||||
|
||||
<li value="6"><p><b>FLOAT5</b> →
|
||||
The element is a JSON floating-point value that is not in the
|
||||
canonical format. The payload is the ASCII
|
||||
text representation of that numeric value. Because the payload is in a
|
||||
non-standard format, it will need to be translated when the JSONB is
|
||||
converted into RFC 8259 text JSON.
|
||||
|
||||
<li value="7"><p><b>TEXT</b> →
|
||||
The element is a JSON string value that does not contain
|
||||
any escapes nor any characters that need to be escaped for either SQL or
|
||||
JSON. The payload is the UTF8 text representation of the string value.
|
||||
The payload does <i>not</i> include string delimiters.
|
||||
|
||||
<li value="8"><p><b>TEXTJ</b> →
|
||||
The element is a JSON string value that contains
|
||||
RFC 8259 character escapes (such as "<tt>\n</tt>" or "<tt>\u0020</tt>").
|
||||
Those escapes will need to be translated into actual UTF8 if this element
|
||||
is [json_extract|extracted] into SQL.
|
||||
The payload is the UTF8 text representation of the escaped string value.
|
||||
The payload does <i>not</i> include string delimiters.
|
||||
|
||||
<li value="9"><p><b>TEXT5</b> →
|
||||
The element is a JSON string value that contains
|
||||
character escapes, including some character escapes that part of JSON5
|
||||
and which are not found in the canonical RFC 8259 spec.
|
||||
Those escapes will need to be translated into standard JSON prior to
|
||||
rendering the JSON as text, or into their actual UTF8 characters if this
|
||||
element is [json_extract|extracted] into SQL.
|
||||
The payload is the UTF8 text representation of the escaped string value.
|
||||
The payload does <i>not</i> include string delimiters.
|
||||
|
||||
<li value="10"><p><b>TEXTRAW</b> →
|
||||
The element is a JSON string value that contains
|
||||
UTF8 characters that need to be escaped if this string is rendered into
|
||||
standard JSON text.
|
||||
The payload does <i>not</i> include string delimiters.
|
||||
|
||||
<li value="11"><p><b>ARRAY</b> →
|
||||
The element is a JSON array. The payload contains
|
||||
JSONB elements that comprise values contained within the array.
|
||||
|
||||
<li value="12"><p><b>OBJECT</b> →
|
||||
The element is a JSON object. The payload contains
|
||||
pairs of JSONB elements that comprise entries for the JSON object.
|
||||
The first element in each pair must be a string (types 7 through 10).
|
||||
The second element of each pair may be any types, including nested
|
||||
arrays or objects.
|
||||
|
||||
<li value="13"><p><b>RESERVED-13</b> →
|
||||
Reserved for future expansion. Legacy implements that encounter this
|
||||
element type should raise an error.
|
||||
|
||||
<li value="14"><p><b>RESERVED-14</b> →
|
||||
Reserved for future expansion. Legacy implements that encounter this
|
||||
element type should raise an error.
|
||||
|
||||
<li value="15"><p><b>RESERVED-15</b> →
|
||||
Reserved for future expansion. Legacy implements that encounter this
|
||||
element type should raise an error.
|
||||
</ol>
|
||||
|
||||
Element types outside the range of 0 to 12 are reserved for future
|
||||
expansion. The current implement raises an error if see an element type
|
||||
other than those listed above. However, future versions of SQLite might
|
||||
use of the three remaining element types to implement indexing or similar
|
||||
optimizations, to speed up lookup against large JSON arrays and/or objects.
|
||||
|
||||
### 3.3 Design Rationale For Element Types
|
||||
|
||||
A key goal of JSONB is that it should be quick to translate
|
||||
to and from text JSON and/or be constructed from SQL values.
|
||||
When converting from text into JSONB, we do not want the
|
||||
converter subroutine to burn CPU cycles converting elements
|
||||
values into some standard format which might never be used.
|
||||
Format conversion is "lazy" - it is deferred until actually
|
||||
needed. This has implications for the JSONB format design:
|
||||
|
||||
1. Numeric values are stored as text, not a numbers. The values are
|
||||
a direct copy of the text JSON values from which they are derived.
|
||||
|
||||
2. There are multiple element types depending on the details of value
|
||||
formats. For example, INT is used for pure RFC-8259 integer
|
||||
literals and INT5 exists for JSON5 extensions such as hexadecimal
|
||||
notation. FLOAT is used for pure RFC-8259 floating point literals
|
||||
and FLOAT5 is used for JSON5 extensions. There are four different
|
||||
representations of strings, depending on where the string came from
|
||||
and how special characters within the string are escaped.
|
||||
|
||||
A second goal of JSONB is that it should be capable of serving as the
|
||||
"parse tree" for JSON when a JSON value is being processed by the
|
||||
various [JSON SQL functions] built into SQLite. Before JSONB was
|
||||
developed, operations such [json_replace()] and [json_patch()]
|
||||
and similar worked in three stages:
|
||||
|
||||
|
||||
1. Translate the text JSON into a internal format that is
|
||||
easier to scan and edit.
|
||||
2. Perform the requested operation on the JSON.
|
||||
3. Translate the internal format back into text.
|
||||
|
||||
JSONB seeks to serve as the internal format directly - bypassing
|
||||
the first and third stages of that process. Since most of the CPU
|
||||
cycles are spent on the first and third stages, that suggests that
|
||||
JSONB processing will be much faster than text JSON processing.
|
||||
|
||||
So when processing JSONB, only the second stage of the three-stage
|
||||
process is required. But when processing text JSON, it is still necessary
|
||||
to do stages one and three. If JSONB is to be used as the internal
|
||||
binary representation, this is yet another reason to store numeric
|
||||
values as text. Storing numbers as text minimizes the amount of
|
||||
conversion work needed for stages one and three. This is also why
|
||||
there are four different representations of text in JSONB. Different
|
||||
text representations are used for text coming from different sources
|
||||
(RFC-8259 JSON, JSON5, or SQL string values) and conversions only
|
||||
happen if and when they are actually needed.
|
||||
|
||||
### 3.4 Valid JSONB BLOBs
|
||||
|
||||
A valid JSONB BLOB consists of a single JSON element. The element must
|
||||
exactly fill the BLOB. This one element is often a JSON object or array
|
||||
and those usually contain additional elements as its payload, but the
|
||||
element can be a primite value such a string, number, boolean, or null.
|
||||
|
||||
When the built-in JSON functions are attempting to determine if a BLOB
|
||||
argument is a JSONB or just a random BLOB, they look at the header of
|
||||
the outer element to see that it is well-formed and that the element
|
||||
completely fills the BLOB. If these conditions are met, then the BLOB
|
||||
is accepted as a JSONB value.
|
||||
@@ -683,7 +683,6 @@ other than that, the order of directives in Lemon is arbitrary.</p>
|
||||
<li><tt><a href='#pifdef'>%endif</a></tt>
|
||||
<li><tt><a href='#extraarg'>%extra_argument</a></tt>
|
||||
<li><tt><a href='#pfallback'>%fallback</a></tt>
|
||||
<li><tt><a href='#reallc'>%free</a></tt>
|
||||
<li><tt><a href='#pifdef'>%if</a></tt>
|
||||
<li><tt><a href='#pifdef'>%ifdef</a></tt>
|
||||
<li><tt><a href='#pifdef'>%ifndef</a></tt>
|
||||
@@ -694,7 +693,6 @@ other than that, the order of directives in Lemon is arbitrary.</p>
|
||||
<li><tt><a href='#parse_accept'>%parse_accept</a></tt>
|
||||
<li><tt><a href='#parse_failure'>%parse_failure</a></tt>
|
||||
<li><tt><a href='#pright'>%right</a></tt>
|
||||
<li><tt><a href='#reallc'>%realloc</a></tt>
|
||||
<li><tt><a href='#stack_overflow'>%stack_overflow</a></tt>
|
||||
<li><tt><a href='#stack_size'>%stack_size</a></tt>
|
||||
<li><tt><a href='#start_symbol'>%start_symbol</a></tt>
|
||||
@@ -1202,21 +1200,6 @@ match any input token.</p>
|
||||
the wildcard token and some other token, the other token is always used.
|
||||
The wildcard token is only matched if there are no alternatives.</p>
|
||||
|
||||
<a id='reallc'></a>
|
||||
<h4>4.4.26 The <tt>%realloc</tt> and <tt>%free</tt> directives</h4>
|
||||
|
||||
<p>The <tt>%realloc</tt> and <tt>%free</tt> directives defines function
|
||||
that allocate and free heap memory. The signatures of these functions
|
||||
should be the same as the realloc() and free() functions from the standard
|
||||
C library.
|
||||
|
||||
<p>If both of these functions are defined
|
||||
then these functions are used to allocate and free
|
||||
memory for supplemental parser stack space, if the initial
|
||||
parse stack space is exceeded. The initial parser stack size
|
||||
is specified by either <tt>%stack_size</tt> or the
|
||||
-DYYSTACKDEPTH compile-time flag.
|
||||
|
||||
<a id='errors'></a>
|
||||
<h2>5.0 Error Processing</h2>
|
||||
|
||||
@@ -1241,7 +1224,6 @@ to begin parsing a new file. This is what will happen at the very
|
||||
first syntax error, of course, if there are no instances of the
|
||||
"error" non-terminal in your grammar.</p>
|
||||
|
||||
|
||||
<a id='history'></a>
|
||||
<h2>6.0 History of Lemon</h2>
|
||||
|
||||
|
||||
+9
-69
@@ -2,26 +2,6 @@
|
||||
|
||||
# The testrunner.tcl Script
|
||||
|
||||
<ul type=none>
|
||||
<li> 1. <a href=#overview>Overview</a>
|
||||
<li> 2. <a href=#binary_tests>Binary Tests</a>
|
||||
<ul type=none>
|
||||
<li> 2.1. <a href=#organization_tests>Organization of Tcl Tests</a>
|
||||
<li> 2.2. <a href=#run_tests>Commands to Run Tests</a>
|
||||
<li> 2.3. <a href=#binary_test_failures>Investigating Binary Test Failures</a>
|
||||
</ul>
|
||||
<li> 3. <a href=#source_code_tests>Source Tests</a>
|
||||
<ul type=none>
|
||||
<li> 3.1. <a href=#commands_to_run_tests>Commands to Run SQLite Tests</a>
|
||||
<li> 3.2. <a href=#zipvfs_tests>Running ZipVFS Tests</a>
|
||||
<li> 3.3. <a href=#source_code_test_failures>Investigating Source Code Test Failures</a>
|
||||
</ul>
|
||||
<li> 4. <a href=#testrunner_options>Extra testrunner.tcl Options</a>
|
||||
# 4. Extra testrunner.tcl Options
|
||||
<li> 5. <a href=#cpu_cores>Controlling CPU Core Utilization</a>
|
||||
</ul>
|
||||
|
||||
<a name=overview></a>
|
||||
# 1. Overview
|
||||
|
||||
testrunner.tcl is a Tcl script used to run multiple SQLite tests using
|
||||
@@ -64,7 +44,6 @@ Sometimes testrunner.tcl uses the [testfixture] binary that it is run with
|
||||
to run tests (see "Binary Tests" below). Sometimes it builds testfixture and
|
||||
other binaries in specific configurations to test (see "Source Tests").
|
||||
|
||||
<a name=binary_tests></a>
|
||||
# 2. Binary Tests
|
||||
|
||||
The commands described in this section all run various combinations of the Tcl
|
||||
@@ -82,7 +61,6 @@ these tests is therefore:
|
||||
The following sub-sections describe the various options that can be
|
||||
passed to testrunner.tcl to test binary testfixture builds.
|
||||
|
||||
<a name=organization_tests></a>
|
||||
## 2.1. Organization of Tcl Tests
|
||||
|
||||
Tcl tests are stored in files that match the pattern *\*.test*. They are
|
||||
@@ -113,7 +91,6 @@ Running **all** tests is to run all tests in the full test set, plus a dozen
|
||||
or so permutations. The specific permutations that are run as part of "all"
|
||||
are defined in file *testrunner_data.tcl*.
|
||||
|
||||
<a name=run_tests></a>
|
||||
## 2.2. Commands to Run Tests
|
||||
|
||||
To run the "veryquick" test set, use either of the following:
|
||||
@@ -137,12 +114,6 @@ a specified pattern (e.g. all tests that start with "fts5"), either of:
|
||||
./testfixture $TESTDIR/testrunner.tcl 'fts5*'
|
||||
```
|
||||
|
||||
Strictly speaking, for a test to be run the pattern must match the script
|
||||
filename, not including the directory, using the rules of Tcl's
|
||||
\[string match\] command. Except that before the matching is done, any "%"
|
||||
characters specified as part of the pattern are transformed to "\*".
|
||||
|
||||
|
||||
To run "all" tests (full + permutations):
|
||||
|
||||
```
|
||||
@@ -170,7 +141,6 @@ Or, if the failure occured as part of a permutation:
|
||||
|
||||
TODO: An example instead of "$PERMUTATION" and $PATH\_TO\_SCRIPT?
|
||||
|
||||
<a name=source_code_tests></a>
|
||||
# 3. Source Code Tests
|
||||
|
||||
The commands described in this section invoke the C compiler to build
|
||||
@@ -189,8 +159,7 @@ shell that supports SQLite 3.31.1 or newer via "package require sqlite3".
|
||||
|
||||
TODO: ./configure + Makefile.msc build systems.
|
||||
|
||||
<a name=commands_to_run_tests></a>
|
||||
## 3.1. Commands to Run SQLite Tests
|
||||
## Commands to Run SQLite Tests
|
||||
|
||||
The **mdevtest** command is equivalent to running the veryquick tests and
|
||||
the [make fuzztest] target once for each of two --enable-all builds - one
|
||||
@@ -232,18 +201,7 @@ of the specific tests run.
|
||||
tclsh $TESTDIR/testrunner.tcl release
|
||||
```
|
||||
|
||||
As with <a href=#source code tests>source code tests</a>, one or more patterns
|
||||
may be appended to any of the above commands (mdevtest, sdevtest or release).
|
||||
In that case only Tcl tests (no fuzz or other tests) that match the specified
|
||||
pattern are run. For example, to run the just the Tcl rtree tests in all
|
||||
builds and configurations supported by "release":
|
||||
|
||||
```
|
||||
tclsh $TESTDIR/testrunner.tcl release rtree%
|
||||
```
|
||||
|
||||
<a name=zipvfs_tests></a>
|
||||
## 3.2. Running ZipVFS Tests
|
||||
## Running ZipVFS Tests
|
||||
|
||||
testrunner.tcl can build a zipvfs-enabled testfixture and use it to run
|
||||
tests from the Zipvfs project with the following command:
|
||||
@@ -259,8 +217,7 @@ test both SQLite and Zipvfs with a single command:
|
||||
tclsh $TESTDIR/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS mdevtest
|
||||
```
|
||||
|
||||
<a name=source_code_test_failures></a>
|
||||
## 3.3. Investigating Source Code Test Failures
|
||||
## Investigating Source Code Test Failures
|
||||
|
||||
Investigating a test failure that occurs during source code testing is a
|
||||
two step process:
|
||||
@@ -287,31 +244,9 @@ target to build. This may be used either to run a [make] command test directly,
|
||||
or else to build a testfixture (or testfixture.exe) binary with which to
|
||||
run a Tcl test script, as <a href=#binary_test_failures>described above</a>.
|
||||
|
||||
<a name=testrunner_options></a>
|
||||
# 4. Extra testrunner.tcl Options
|
||||
|
||||
The testrunner.tcl script options in this section may be used with both source
|
||||
code and binary tests.
|
||||
|
||||
The **--buildonly** option instructs testrunner.tcl just to build the binaries
|
||||
required by a test, not to run any actual tests. For example:
|
||||
|
||||
```
|
||||
# Build binaries required by release test.
|
||||
tclsh $TESTDIR/testrunner.tcl --buildonly release"
|
||||
```
|
||||
|
||||
The **--dryrun** option prevents testrunner.tcl from building any binaries
|
||||
or running any tests. Instead, it just writes the shell commands that it
|
||||
would normally execute into the testrunner.log file. Example:
|
||||
|
||||
```
|
||||
# Log the shell commmands that make up the mdevtest test.
|
||||
tclsh $TESTDIR/testrunner.tcl --dryrun mdevtest"
|
||||
```
|
||||
|
||||
<a name=cpu_cores></a>
|
||||
# 5. Controlling CPU Core Utilization
|
||||
# 4. Controlling CPU Core Utilization
|
||||
|
||||
When running either binary or source code tests, testrunner.tcl reports the
|
||||
number of jobs it intends to use to stdout. e.g.
|
||||
@@ -342,3 +277,8 @@ testrunner.log and testrunner.db files:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+10
-22
@@ -24,13 +24,8 @@
|
||||
# include <stdlib.h>
|
||||
# include <limits.h>
|
||||
# include <assert.h>
|
||||
# include "sqlite3.h"
|
||||
#endif
|
||||
#ifndef HAVE_CONSOLE_IO_H
|
||||
# include "console_io.h"
|
||||
#endif
|
||||
#if defined(_MSC_VER)
|
||||
# pragma warning(disable : 4204)
|
||||
# include "sqlite3.h"
|
||||
#endif
|
||||
|
||||
#ifndef SQLITE_CIO_NO_TRANSLATE
|
||||
@@ -130,10 +125,6 @@ static short streamOfConsole(FILE *pf, /* out */ PerStreamTags *ppst){
|
||||
# endif
|
||||
}
|
||||
|
||||
# ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
# define ENABLE_VIRTUAL_TERMINAL_PROCESSING (0x4)
|
||||
# endif
|
||||
|
||||
# if CIO_WIN_WC_XLATE
|
||||
/* Define console modes for use with the Windows Console API. */
|
||||
# define SHELL_CONI_MODE \
|
||||
@@ -558,11 +549,12 @@ zSkipValidUtf8(const char *z, int nAccept, long ccm){
|
||||
#endif /*!(defined(SQLITE_CIO_NO_UTF8SCAN)&&defined(SQLITE_CIO_NO_TRANSLATE))*/
|
||||
|
||||
#ifndef SQLITE_CIO_NO_TRANSLATE
|
||||
# ifdef CONSIO_SPUTB
|
||||
|
||||
#ifdef CONSIO_SPUTB
|
||||
SQLITE_INTERNAL_LINKAGE int
|
||||
fPutbUtf8(FILE *pfO, const char *cBuf, int nAccept){
|
||||
assert(pfO!=0);
|
||||
# if CIO_WIN_WC_XLATE
|
||||
# if CIO_WIN_WC_XLATE
|
||||
PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */
|
||||
PerStreamTags *ppst = getEmitStreamInfo(0, &pst, &pfO);
|
||||
if( pstReachesConsole(ppst) ){
|
||||
@@ -572,13 +564,13 @@ fPutbUtf8(FILE *pfO, const char *cBuf, int nAccept){
|
||||
if( 0 == isKnownWritable(ppst->pf) ) restoreConsoleArb(ppst);
|
||||
return rv;
|
||||
}else {
|
||||
# endif
|
||||
return (int)fwrite(cBuf, 1, nAccept, pfO);
|
||||
# if CIO_WIN_WC_XLATE
|
||||
}
|
||||
# endif
|
||||
}
|
||||
# endif
|
||||
return (int)fwrite(cBuf, 1, nAccept, pfO);
|
||||
# if CIO_WIN_WC_XLATE
|
||||
}
|
||||
# endif
|
||||
}
|
||||
#endif /* defined(CONSIO_SPUTB) */
|
||||
|
||||
SQLITE_INTERNAL_LINKAGE int
|
||||
oPutbUtf8(const char *cBuf, int nAccept){
|
||||
@@ -684,8 +676,4 @@ SQLITE_INTERNAL_LINKAGE char* fGetsUtf8(char *cBuf, int ncMax, FILE *pfIn){
|
||||
}
|
||||
#endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# pragma warning(default : 4204)
|
||||
#endif
|
||||
|
||||
#undef SHELL_INVALID_FILE_PTR
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
** CIO_WIN_WC_XLATE is defined as 0 or 1, reflecting whether console I/O
|
||||
** translation for Windows is effected for the build.
|
||||
*/
|
||||
#define HAVE_CONSOLE_IO_H 1
|
||||
|
||||
#ifndef SQLITE_INTERNAL_LINKAGE
|
||||
# define SQLITE_INTERNAL_LINKAGE extern /* external to translation unit */
|
||||
# include <stdio.h>
|
||||
@@ -166,8 +166,8 @@ SQLITE_INTERNAL_LINKAGE int ePutsUtf8(const char *z);
|
||||
#ifdef CONSIO_SPUTB
|
||||
SQLITE_INTERNAL_LINKAGE int
|
||||
fPutbUtf8(FILE *pfOut, const char *cBuf, int nAccept);
|
||||
/* Like fPutbUtf8 except stream is always the designated output. */
|
||||
#endif
|
||||
/* Like fPutbUtf8 except stream is always the designated output. */
|
||||
SQLITE_INTERNAL_LINKAGE int
|
||||
oPutbUtf8(const char *cBuf, int nAccept);
|
||||
/* Like fPutbUtf8 except stream is always the designated error. */
|
||||
|
||||
+22
-15
@@ -4006,7 +4006,7 @@ static int fts3ShadowName(const char *zName){
|
||||
** Implementation of the xIntegrity() method on the FTS3/FTS4 virtual
|
||||
** table.
|
||||
*/
|
||||
static int fts3IntegrityMethod(
|
||||
static int fts3Integrity(
|
||||
sqlite3_vtab *pVtab, /* The virtual table to be checked */
|
||||
const char *zSchema, /* Name of schema in which pVtab lives */
|
||||
const char *zTabname, /* Name of the pVTab table */
|
||||
@@ -4014,24 +4014,31 @@ static int fts3IntegrityMethod(
|
||||
char **pzErr /* Write error message here */
|
||||
){
|
||||
Fts3Table *p = (Fts3Table*)pVtab;
|
||||
int rc = SQLITE_OK;
|
||||
int bOk = 0;
|
||||
char *zSql;
|
||||
int rc;
|
||||
char *zErr = 0;
|
||||
|
||||
assert( pzErr!=0 );
|
||||
assert( *pzErr==0 );
|
||||
UNUSED_PARAMETER(isQuick);
|
||||
rc = sqlite3Fts3IntegrityCheck(p, &bOk);
|
||||
assert( rc!=SQLITE_CORRUPT_VTAB );
|
||||
if( rc==SQLITE_ERROR || (rc&0xFF)==SQLITE_CORRUPT ){
|
||||
*pzErr = sqlite3_mprintf("unable to validate the inverted index for"
|
||||
" FTS%d table %s.%s: %s",
|
||||
p->bFts4 ? 4 : 3, zSchema, zTabname, sqlite3_errstr(rc));
|
||||
if( *pzErr ) rc = SQLITE_OK;
|
||||
}else if( rc==SQLITE_OK && bOk==0 ){
|
||||
zSql = sqlite3_mprintf(
|
||||
"INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');",
|
||||
zSchema, zTabname, zTabname);
|
||||
if( zSql==0 ){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
rc = sqlite3_exec(p->db, zSql, 0, 0, &zErr);
|
||||
sqlite3_free(zSql);
|
||||
if( (rc&0xff)==SQLITE_CORRUPT ){
|
||||
*pzErr = sqlite3_mprintf("malformed inverted index for FTS%d table %s.%s",
|
||||
p->bFts4 ? 4 : 3, zSchema, zTabname);
|
||||
if( *pzErr==0 ) rc = SQLITE_NOMEM;
|
||||
}else if( rc!=SQLITE_OK ){
|
||||
*pzErr = sqlite3_mprintf("unable to validate the inverted index for"
|
||||
" FTS%d table %s.%s: %s",
|
||||
p->bFts4 ? 4 : 3, zSchema, zTabname, zErr);
|
||||
}
|
||||
sqlite3Fts3SegmentsClose(p);
|
||||
return rc;
|
||||
sqlite3_free(zErr);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -4061,7 +4068,7 @@ static const sqlite3_module fts3Module = {
|
||||
/* xRelease */ fts3ReleaseMethod,
|
||||
/* xRollbackTo */ fts3RollbackToMethod,
|
||||
/* xShadowName */ fts3ShadowName,
|
||||
/* xIntegrity */ fts3IntegrityMethod,
|
||||
/* xIntegrity */ fts3Integrity,
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -653,7 +653,5 @@ int sqlite3FtsUnicodeIsdiacritic(int);
|
||||
|
||||
int sqlite3Fts3ExprIterate(Fts3Expr*, int (*x)(Fts3Expr*,int,void*), void*);
|
||||
|
||||
int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk);
|
||||
|
||||
#endif /* !SQLITE_CORE || SQLITE_ENABLE_FTS3 */
|
||||
#endif /* _FTSINT_H */
|
||||
|
||||
@@ -5294,7 +5294,7 @@ static u64 fts3ChecksumIndex(
|
||||
** If an error occurs (e.g. an OOM or IO error), return an SQLite error
|
||||
** code. The final value of *pbOk is undefined in this case.
|
||||
*/
|
||||
int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk){
|
||||
static int fts3IntegrityCheck(Fts3Table *p, int *pbOk){
|
||||
int rc = SQLITE_OK; /* Return code */
|
||||
u64 cksum1 = 0; /* Checksum based on FTS index contents */
|
||||
u64 cksum2 = 0; /* Checksum based on %_content contents */
|
||||
@@ -5372,12 +5372,7 @@ int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk){
|
||||
sqlite3_finalize(pStmt);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_CORRUPT_VTAB ){
|
||||
rc = SQLITE_OK;
|
||||
*pbOk = 0;
|
||||
}else{
|
||||
*pbOk = (rc==SQLITE_OK && cksum1==cksum2);
|
||||
}
|
||||
*pbOk = (cksum1==cksum2);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -5417,7 +5412,7 @@ static int fts3DoIntegrityCheck(
|
||||
){
|
||||
int rc;
|
||||
int bOk = 0;
|
||||
rc = sqlite3Fts3IntegrityCheck(p, &bOk);
|
||||
rc = fts3IntegrityCheck(p, &bOk);
|
||||
if( rc==SQLITE_OK && bOk==0 ) rc = FTS_CORRUPT_VTAB;
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -223,12 +223,10 @@ proc main {data} {
|
||||
Fts5ExtensionApi {
|
||||
set struct [get_fts5_struct $data "^struct Fts5ExtensionApi" "^.;"]
|
||||
set map [list]
|
||||
set lKey [list]
|
||||
foreach {k v} [get_struct_members $data] {
|
||||
if {[string match x* $k]==0} continue
|
||||
lappend lKey $k
|
||||
lappend map $k "<a href=#$k>$k</a>"
|
||||
}
|
||||
foreach k [lsort -decr $lKey] { lappend map $k "<a href=#$k>$k</a>" }
|
||||
output [string map $map $struct]
|
||||
}
|
||||
|
||||
|
||||
+9
-59
@@ -88,11 +88,8 @@ struct Fts5PhraseIter {
|
||||
** created with the "columnsize=0" option.
|
||||
**
|
||||
** xColumnText:
|
||||
** 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 text of column iCol of
|
||||
** the current document. If successful, (*pz) is set to point to a buffer
|
||||
** This function attempts to retrieve the text of column iCol of the
|
||||
** current document. If successful, (*pz) is set to point to a buffer
|
||||
** containing the text in utf-8 encoding, (*pn) is set to the size in bytes
|
||||
** (not characters) of the buffer and SQLITE_OK is returned. Otherwise,
|
||||
** if an error occurs, an SQLite error code is returned and the final values
|
||||
@@ -102,10 +99,8 @@ struct Fts5PhraseIter {
|
||||
** Returns the number of phrases in the current query expression.
|
||||
**
|
||||
** xPhraseSize:
|
||||
** If parameter iCol is less than zero, or greater than or equal to the
|
||||
** number of phrases in the current query, as returned by xPhraseCount,
|
||||
** 0 is returned. Otherwise, this function returns the number of tokens in
|
||||
** phrase iPhrase of the query. Phrases are numbered starting from zero.
|
||||
** Returns the number of tokens in phrase iPhrase of the query. Phrases
|
||||
** are numbered starting from zero.
|
||||
**
|
||||
** xInstCount:
|
||||
** Set *pnInst to the total number of occurrences of all phrases within
|
||||
@@ -121,13 +116,12 @@ struct Fts5PhraseIter {
|
||||
** Query for the details of phrase match iIdx within the current row.
|
||||
** Phrase matches are numbered starting from zero, so the iIdx argument
|
||||
** should be greater than or equal to zero and smaller than the value
|
||||
** output by xInstCount(). If iIdx is less than zero or greater than
|
||||
** or equal to the value returned by xInstCount(), SQLITE_RANGE is returned.
|
||||
** output by xInstCount().
|
||||
**
|
||||
** Otherwise, output parameter *piPhrase is set to the phrase number, *piCol
|
||||
** Usually, output parameter *piPhrase is set to the phrase number, *piCol
|
||||
** to the column in which it occurs and *piOff the token offset of the
|
||||
** first token of the phrase. SQLITE_OK is returned if successful, or an
|
||||
** error code (i.e. SQLITE_NOMEM) if an error occurs.
|
||||
** first token of the phrase. Returns SQLITE_OK if successful, or an error
|
||||
** code (i.e. SQLITE_NOMEM) if an error occurs.
|
||||
**
|
||||
** This API can be quite slow if used with an FTS5 table created with the
|
||||
** "detail=none" or "detail=column" option.
|
||||
@@ -153,10 +147,6 @@ struct Fts5PhraseIter {
|
||||
** Invoking Api.xUserData() returns a copy of the pointer passed as
|
||||
** the third argument to pUserData.
|
||||
**
|
||||
** If parameter iPhrase is less than zero, or greater than or equal to
|
||||
** the number of phrases in the query, as returned by xPhraseCount(),
|
||||
** this function returns SQLITE_RANGE.
|
||||
**
|
||||
** If the callback function returns any value other than SQLITE_OK, the
|
||||
** query is abandoned and the xQueryPhrase function returns immediately.
|
||||
** If the returned value is SQLITE_DONE, xQueryPhrase returns SQLITE_OK.
|
||||
@@ -271,42 +261,9 @@ struct Fts5PhraseIter {
|
||||
**
|
||||
** xPhraseNextColumn()
|
||||
** See xPhraseFirstColumn above.
|
||||
**
|
||||
** xQueryToken(pFts5, iPhrase, iToken, ppToken, pnToken)
|
||||
** This is used to access token iToken of phrase iPhrase of the current
|
||||
** query. Before returning, output parameter *ppToken is set to point
|
||||
** to a buffer containing the requested token, and *pnToken to the
|
||||
** size of this buffer in bytes.
|
||||
**
|
||||
** If iPhrase or iToken are less than zero, or if iPhrase is greater than
|
||||
** or equal to the number of phrases in the query as reported by
|
||||
** xPhraseCount(), or if iToken is equal to or greater than the number of
|
||||
** tokens in the phrase, SQLITE_RANGE is returned and *ppToken and *pnToken
|
||||
are both zeroed.
|
||||
**
|
||||
** The output text is not a copy of the query text that specified the
|
||||
** token. It is the output of the tokenizer module. For tokendata=1
|
||||
** tables, this includes any embedded 0x00 and trailing data.
|
||||
**
|
||||
** xInstToken(pFts5, iIdx, iToken, ppToken, pnToken)
|
||||
** This is used to access token iToken of phrase hit iIdx within the
|
||||
** current row. If iIdx is less than zero or greater than or equal to the
|
||||
** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise,
|
||||
** output variable (*ppToken) is set to point to a buffer containing the
|
||||
** matching document token, and (*pnToken) to the size of that buffer in
|
||||
** bytes. This API is not available if the specified token matches a
|
||||
** prefix query term. In that case both output variables are always set
|
||||
** to 0.
|
||||
**
|
||||
** The output text is not a copy of the document text that was tokenized.
|
||||
** It is the output of the tokenizer module. For tokendata=1 tables, this
|
||||
** includes any embedded 0x00 and trailing data.
|
||||
**
|
||||
** This API can be quite slow if used with an FTS5 table created with the
|
||||
** "detail=none" or "detail=column" option.
|
||||
*/
|
||||
struct Fts5ExtensionApi {
|
||||
int iVersion; /* Currently always set to 3 */
|
||||
int iVersion; /* Currently always set to 2 */
|
||||
|
||||
void *(*xUserData)(Fts5Context*);
|
||||
|
||||
@@ -341,13 +298,6 @@ struct Fts5ExtensionApi {
|
||||
|
||||
int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*);
|
||||
void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol);
|
||||
|
||||
/* Below this point are iVersion>=3 only */
|
||||
int (*xQueryToken)(Fts5Context*,
|
||||
int iPhrase, int iToken,
|
||||
const char **ppToken, int *pnToken
|
||||
);
|
||||
int (*xInstToken)(Fts5Context*, int iIdx, int iToken, const char**, int*);
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
+7
-26
@@ -196,7 +196,6 @@ struct Fts5Config {
|
||||
char *zContent; /* content table */
|
||||
char *zContentRowid; /* "content_rowid=" option value */
|
||||
int bColumnsize; /* "columnsize=" option value (dflt==1) */
|
||||
int bTokendata; /* "tokendata=" option value (dflt==0) */
|
||||
int eDetail; /* FTS5_DETAIL_XXX value */
|
||||
char *zContentExprlist;
|
||||
Fts5Tokenizer *pTok;
|
||||
@@ -385,19 +384,17 @@ struct Fts5IndexIter {
|
||||
/*
|
||||
** Values used as part of the flags argument passed to IndexQuery().
|
||||
*/
|
||||
#define FTS5INDEX_QUERY_PREFIX 0x0001 /* Prefix query */
|
||||
#define FTS5INDEX_QUERY_DESC 0x0002 /* Docs in descending rowid order */
|
||||
#define FTS5INDEX_QUERY_TEST_NOIDX 0x0004 /* Do not use prefix index */
|
||||
#define FTS5INDEX_QUERY_SCAN 0x0008 /* Scan query (fts5vocab) */
|
||||
#define FTS5INDEX_QUERY_PREFIX 0x0001 /* Prefix query */
|
||||
#define FTS5INDEX_QUERY_DESC 0x0002 /* Docs in descending rowid order */
|
||||
#define FTS5INDEX_QUERY_TEST_NOIDX 0x0004 /* Do not use prefix index */
|
||||
#define FTS5INDEX_QUERY_SCAN 0x0008 /* Scan query (fts5vocab) */
|
||||
|
||||
/* The following are used internally by the fts5_index.c module. They are
|
||||
** defined here only to make it easier to avoid clashes with the flags
|
||||
** above. */
|
||||
#define FTS5INDEX_QUERY_SKIPEMPTY 0x0010
|
||||
#define FTS5INDEX_QUERY_NOOUTPUT 0x0020
|
||||
#define FTS5INDEX_QUERY_SKIPHASH 0x0040
|
||||
#define FTS5INDEX_QUERY_NOTOKENDATA 0x0080
|
||||
#define FTS5INDEX_QUERY_SCANONETERM 0x0100
|
||||
#define FTS5INDEX_QUERY_SKIPEMPTY 0x0010
|
||||
#define FTS5INDEX_QUERY_NOOUTPUT 0x0020
|
||||
#define FTS5INDEX_QUERY_SKIPHASH 0x0040
|
||||
|
||||
/*
|
||||
** Create/destroy an Fts5Index object.
|
||||
@@ -466,10 +463,6 @@ void *sqlite3Fts5StructureRef(Fts5Index*);
|
||||
void sqlite3Fts5StructureRelease(void*);
|
||||
int sqlite3Fts5StructureTest(Fts5Index*, void*);
|
||||
|
||||
/*
|
||||
** Used by xInstToken():
|
||||
*/
|
||||
int sqlite3Fts5IterToken(Fts5IndexIter*, i64, int, int, const char**, int*);
|
||||
|
||||
/*
|
||||
** Insert or remove data to or from the index. Each time a document is
|
||||
@@ -547,13 +540,6 @@ int sqlite3Fts5IndexLoadConfig(Fts5Index *p);
|
||||
int sqlite3Fts5IndexGetOrigin(Fts5Index *p, i64 *piOrigin);
|
||||
int sqlite3Fts5IndexContentlessDelete(Fts5Index *p, i64 iOrigin, i64 iRowid);
|
||||
|
||||
void sqlite3Fts5IndexIterClearTokendata(Fts5IndexIter*);
|
||||
|
||||
/* Used to populate hash tables for xInstToken in detail=none/column mode. */
|
||||
int sqlite3Fts5IndexIterWriteTokendata(
|
||||
Fts5IndexIter*, const char*, int, i64 iRowid, int iCol, int iOff
|
||||
);
|
||||
|
||||
/*
|
||||
** End of interface to code in fts5_index.c.
|
||||
**************************************************************************/
|
||||
@@ -659,7 +645,6 @@ void sqlite3Fts5HashScanNext(Fts5Hash*);
|
||||
int sqlite3Fts5HashScanEof(Fts5Hash*);
|
||||
void sqlite3Fts5HashScanEntry(Fts5Hash *,
|
||||
const char **pzTerm, /* OUT: term (nul-terminated) */
|
||||
int *pnTerm, /* OUT: Size of term in bytes */
|
||||
const u8 **ppDoclist, /* OUT: pointer to doclist */
|
||||
int *pnDoclist /* OUT: size of doclist in bytes */
|
||||
);
|
||||
@@ -786,10 +771,6 @@ int sqlite3Fts5ExprClonePhrase(Fts5Expr*, int, Fts5Expr**);
|
||||
|
||||
int sqlite3Fts5ExprPhraseCollist(Fts5Expr *, int, const u8 **, int *);
|
||||
|
||||
int sqlite3Fts5ExprQueryToken(Fts5Expr*, int, int, const char**, int*);
|
||||
int sqlite3Fts5ExprInstToken(Fts5Expr*, i64, int, int, int, int, const char**, int*);
|
||||
void sqlite3Fts5ExprClearTokens(Fts5Expr*);
|
||||
|
||||
/*******************************************
|
||||
** The fts5_expr.c API above this point is used by the other hand-written
|
||||
** C code in this module. The interfaces below this point are called by
|
||||
|
||||
+2
-12
@@ -211,14 +211,6 @@ static int fts5HighlightCb(
|
||||
}
|
||||
|
||||
if( iPos==p->iRangeEnd ){
|
||||
if( p->bOpen ){
|
||||
if( p->iter.iStart>=0 && iPos>=p->iter.iStart ){
|
||||
fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff);
|
||||
p->iOff = iEndOff;
|
||||
}
|
||||
fts5HighlightAppend(&rc, p, p->zClose, -1);
|
||||
p->bOpen = 0;
|
||||
}
|
||||
fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff);
|
||||
p->iOff = iEndOff;
|
||||
}
|
||||
@@ -252,10 +244,8 @@ static void fts5HighlightFunction(
|
||||
ctx.zClose = (const char*)sqlite3_value_text(apVal[2]);
|
||||
ctx.iRangeEnd = -1;
|
||||
rc = pApi->xColumnText(pFts, iCol, &ctx.zIn, &ctx.nIn);
|
||||
if( rc==SQLITE_RANGE ){
|
||||
sqlite3_result_text(pCtx, "", -1, SQLITE_STATIC);
|
||||
rc = SQLITE_OK;
|
||||
}else if( ctx.zIn ){
|
||||
|
||||
if( ctx.zIn ){
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fts5CInstIterInit(pApi, pFts, iCol, &ctx.iter);
|
||||
}
|
||||
|
||||
@@ -68,7 +68,6 @@ void sqlite3Fts5BufferAppendBlob(
|
||||
){
|
||||
if( nData ){
|
||||
if( fts5BufferGrow(pRc, pBuf, nData) ) return;
|
||||
assert( pBuf->p!=0 );
|
||||
memcpy(&pBuf->p[pBuf->n], pData, nData);
|
||||
pBuf->n += nData;
|
||||
}
|
||||
@@ -170,7 +169,6 @@ int sqlite3Fts5PoslistNext64(
|
||||
i64 *piOff /* IN/OUT: Current offset */
|
||||
){
|
||||
int i = *pi;
|
||||
assert( a!=0 || i==0 );
|
||||
if( i>=n ){
|
||||
/* EOF */
|
||||
*piOff = -1;
|
||||
@@ -178,7 +176,6 @@ int sqlite3Fts5PoslistNext64(
|
||||
}else{
|
||||
i64 iOff = *piOff;
|
||||
u32 iVal;
|
||||
assert( a!=0 );
|
||||
fts5FastGetVarint32(a, i, iVal);
|
||||
if( iVal<=1 ){
|
||||
if( iVal==0 ){
|
||||
|
||||
@@ -398,16 +398,6 @@ static int fts5ConfigParseSpecial(
|
||||
return rc;
|
||||
}
|
||||
|
||||
if( sqlite3_strnicmp("tokendata", zCmd, nCmd)==0 ){
|
||||
if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){
|
||||
*pzErr = sqlite3_mprintf("malformed tokendata=... directive");
|
||||
rc = SQLITE_ERROR;
|
||||
}else{
|
||||
pConfig->bTokendata = (zArg[0]=='1');
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
*pzErr = sqlite3_mprintf("unrecognized option: \"%.*s\"", nCmd, zCmd);
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
|
||||
+44
-164
@@ -100,9 +100,7 @@ struct Fts5ExprNode {
|
||||
struct Fts5ExprTerm {
|
||||
u8 bPrefix; /* True for a prefix term */
|
||||
u8 bFirst; /* True if token must be first in column */
|
||||
char *pTerm; /* Term data */
|
||||
int nQueryTerm; /* Effective size of term in bytes */
|
||||
int nFullTerm; /* Size of term in bytes incl. tokendata */
|
||||
char *zTerm; /* nul-terminated term */
|
||||
Fts5IndexIter *pIter; /* Iterator for this term */
|
||||
Fts5ExprTerm *pSynonym; /* Pointer to first in list of synonyms */
|
||||
};
|
||||
@@ -969,7 +967,7 @@ static int fts5ExprNearInitAll(
|
||||
p->pIter = 0;
|
||||
}
|
||||
rc = sqlite3Fts5IndexQuery(
|
||||
pExpr->pIndex, p->pTerm, p->nQueryTerm,
|
||||
pExpr->pIndex, p->zTerm, (int)strlen(p->zTerm),
|
||||
(pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0) |
|
||||
(pExpr->bDesc ? FTS5INDEX_QUERY_DESC : 0),
|
||||
pNear->pColset,
|
||||
@@ -1606,7 +1604,7 @@ static void fts5ExprPhraseFree(Fts5ExprPhrase *pPhrase){
|
||||
Fts5ExprTerm *pSyn;
|
||||
Fts5ExprTerm *pNext;
|
||||
Fts5ExprTerm *pTerm = &pPhrase->aTerm[i];
|
||||
sqlite3_free(pTerm->pTerm);
|
||||
sqlite3_free(pTerm->zTerm);
|
||||
sqlite3Fts5IterClose(pTerm->pIter);
|
||||
for(pSyn=pTerm->pSynonym; pSyn; pSyn=pNext){
|
||||
pNext = pSyn->pSynonym;
|
||||
@@ -1704,7 +1702,6 @@ Fts5ExprNearset *sqlite3Fts5ParseNearset(
|
||||
typedef struct TokenCtx TokenCtx;
|
||||
struct TokenCtx {
|
||||
Fts5ExprPhrase *pPhrase;
|
||||
Fts5Config *pConfig;
|
||||
int rc;
|
||||
};
|
||||
|
||||
@@ -1738,12 +1735,8 @@ static int fts5ParseTokenize(
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
memset(pSyn, 0, (size_t)nByte);
|
||||
pSyn->pTerm = ((char*)pSyn) + sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer);
|
||||
pSyn->nFullTerm = pSyn->nQueryTerm = nToken;
|
||||
if( pCtx->pConfig->bTokendata ){
|
||||
pSyn->nQueryTerm = (int)strlen(pSyn->pTerm);
|
||||
}
|
||||
memcpy(pSyn->pTerm, pToken, nToken);
|
||||
pSyn->zTerm = ((char*)pSyn) + sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer);
|
||||
memcpy(pSyn->zTerm, pToken, nToken);
|
||||
pSyn->pSynonym = pPhrase->aTerm[pPhrase->nTerm-1].pSynonym;
|
||||
pPhrase->aTerm[pPhrase->nTerm-1].pSynonym = pSyn;
|
||||
}
|
||||
@@ -1768,11 +1761,7 @@ static int fts5ParseTokenize(
|
||||
if( rc==SQLITE_OK ){
|
||||
pTerm = &pPhrase->aTerm[pPhrase->nTerm++];
|
||||
memset(pTerm, 0, sizeof(Fts5ExprTerm));
|
||||
pTerm->pTerm = sqlite3Fts5Strndup(&rc, pToken, nToken);
|
||||
pTerm->nFullTerm = pTerm->nQueryTerm = nToken;
|
||||
if( pCtx->pConfig->bTokendata && rc==SQLITE_OK ){
|
||||
pTerm->nQueryTerm = (int)strlen(pTerm->pTerm);
|
||||
}
|
||||
pTerm->zTerm = sqlite3Fts5Strndup(&rc, pToken, nToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1839,7 +1828,6 @@ Fts5ExprPhrase *sqlite3Fts5ParseTerm(
|
||||
|
||||
memset(&sCtx, 0, sizeof(TokenCtx));
|
||||
sCtx.pPhrase = pAppend;
|
||||
sCtx.pConfig = pConfig;
|
||||
|
||||
rc = fts5ParseStringFromToken(pToken, &z);
|
||||
if( rc==SQLITE_OK ){
|
||||
@@ -1887,15 +1875,12 @@ int sqlite3Fts5ExprClonePhrase(
|
||||
Fts5Expr **ppNew
|
||||
){
|
||||
int rc = SQLITE_OK; /* Return code */
|
||||
Fts5ExprPhrase *pOrig = 0; /* The phrase extracted from pExpr */
|
||||
Fts5ExprPhrase *pOrig; /* The phrase extracted from pExpr */
|
||||
Fts5Expr *pNew = 0; /* Expression to return via *ppNew */
|
||||
TokenCtx sCtx = {0,0,0}; /* Context object for fts5ParseTokenize */
|
||||
if( iPhrase<0 || iPhrase>=pExpr->nPhrase ){
|
||||
rc = SQLITE_RANGE;
|
||||
}else{
|
||||
pOrig = pExpr->apExprPhrase[iPhrase];
|
||||
pNew = (Fts5Expr*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Expr));
|
||||
}
|
||||
TokenCtx sCtx = {0,0}; /* Context object for fts5ParseTokenize */
|
||||
|
||||
pOrig = pExpr->apExprPhrase[iPhrase];
|
||||
pNew = (Fts5Expr*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Expr));
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew->apExprPhrase = (Fts5ExprPhrase**)sqlite3Fts5MallocZero(&rc,
|
||||
sizeof(Fts5ExprPhrase*));
|
||||
@@ -1908,7 +1893,7 @@ int sqlite3Fts5ExprClonePhrase(
|
||||
pNew->pRoot->pNear = (Fts5ExprNearset*)sqlite3Fts5MallocZero(&rc,
|
||||
sizeof(Fts5ExprNearset) + sizeof(Fts5ExprPhrase*));
|
||||
}
|
||||
if( rc==SQLITE_OK && ALWAYS(pOrig!=0) ){
|
||||
if( rc==SQLITE_OK ){
|
||||
Fts5Colset *pColsetOrig = pOrig->pNode->pNear->pColset;
|
||||
if( pColsetOrig ){
|
||||
sqlite3_int64 nByte;
|
||||
@@ -1922,27 +1907,26 @@ int sqlite3Fts5ExprClonePhrase(
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
if( pOrig->nTerm ){
|
||||
int i; /* Used to iterate through phrase terms */
|
||||
sCtx.pConfig = pExpr->pConfig;
|
||||
for(i=0; rc==SQLITE_OK && i<pOrig->nTerm; i++){
|
||||
int tflags = 0;
|
||||
Fts5ExprTerm *p;
|
||||
for(p=&pOrig->aTerm[i]; p && rc==SQLITE_OK; p=p->pSynonym){
|
||||
rc = fts5ParseTokenize((void*)&sCtx,tflags,p->pTerm,p->nFullTerm,0,0);
|
||||
tflags = FTS5_TOKEN_COLOCATED;
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
sCtx.pPhrase->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix;
|
||||
sCtx.pPhrase->aTerm[i].bFirst = pOrig->aTerm[i].bFirst;
|
||||
}
|
||||
if( pOrig->nTerm ){
|
||||
int i; /* Used to iterate through phrase terms */
|
||||
for(i=0; rc==SQLITE_OK && i<pOrig->nTerm; i++){
|
||||
int tflags = 0;
|
||||
Fts5ExprTerm *p;
|
||||
for(p=&pOrig->aTerm[i]; p && rc==SQLITE_OK; p=p->pSynonym){
|
||||
const char *zTerm = p->zTerm;
|
||||
rc = fts5ParseTokenize((void*)&sCtx, tflags, zTerm, (int)strlen(zTerm),
|
||||
0, 0);
|
||||
tflags = FTS5_TOKEN_COLOCATED;
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
sCtx.pPhrase->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix;
|
||||
sCtx.pPhrase->aTerm[i].bFirst = pOrig->aTerm[i].bFirst;
|
||||
}
|
||||
}else{
|
||||
/* This happens when parsing a token or quoted phrase that contains
|
||||
** no token characters at all. (e.g ... MATCH '""'). */
|
||||
sCtx.pPhrase = sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprPhrase));
|
||||
}
|
||||
}else{
|
||||
/* This happens when parsing a token or quoted phrase that contains
|
||||
** no token characters at all. (e.g ... MATCH '""'). */
|
||||
sCtx.pPhrase = sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprPhrase));
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK && ALWAYS(sCtx.pPhrase) ){
|
||||
@@ -2312,13 +2296,11 @@ static Fts5ExprNode *fts5ParsePhraseToAnd(
|
||||
if( parseGrowPhraseArray(pParse) ){
|
||||
fts5ExprPhraseFree(pPhrase);
|
||||
}else{
|
||||
Fts5ExprTerm *p = &pNear->apPhrase[0]->aTerm[ii];
|
||||
Fts5ExprTerm *pTo = &pPhrase->aTerm[0];
|
||||
pParse->apPhrase[pParse->nPhrase++] = pPhrase;
|
||||
pPhrase->nTerm = 1;
|
||||
pTo->pTerm = sqlite3Fts5Strndup(&pParse->rc, p->pTerm, p->nFullTerm);
|
||||
pTo->nQueryTerm = p->nQueryTerm;
|
||||
pTo->nFullTerm = p->nFullTerm;
|
||||
pPhrase->aTerm[0].zTerm = sqlite3Fts5Strndup(
|
||||
&pParse->rc, pNear->apPhrase[0]->aTerm[ii].zTerm, -1
|
||||
);
|
||||
pRet->apChild[ii] = sqlite3Fts5ParseNode(pParse, FTS5_STRING,
|
||||
0, 0, sqlite3Fts5ParseNearset(pParse, 0, pPhrase)
|
||||
);
|
||||
@@ -2503,17 +2485,16 @@ static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){
|
||||
|
||||
/* Determine the maximum amount of space required. */
|
||||
for(p=pTerm; p; p=p->pSynonym){
|
||||
nByte += pTerm->nQueryTerm * 2 + 3 + 2;
|
||||
nByte += (int)strlen(pTerm->zTerm) * 2 + 3 + 2;
|
||||
}
|
||||
zQuoted = sqlite3_malloc64(nByte);
|
||||
|
||||
if( zQuoted ){
|
||||
int i = 0;
|
||||
for(p=pTerm; p; p=p->pSynonym){
|
||||
char *zIn = p->pTerm;
|
||||
char *zEnd = &zIn[p->nQueryTerm];
|
||||
char *zIn = p->zTerm;
|
||||
zQuoted[i++] = '"';
|
||||
while( zIn<zEnd ){
|
||||
while( *zIn ){
|
||||
if( *zIn=='"' ) zQuoted[i++] = '"';
|
||||
zQuoted[i++] = *zIn++;
|
||||
}
|
||||
@@ -2591,10 +2572,8 @@ static char *fts5ExprPrintTcl(
|
||||
|
||||
zRet = fts5PrintfAppend(zRet, " {");
|
||||
for(iTerm=0; zRet && iTerm<pPhrase->nTerm; iTerm++){
|
||||
Fts5ExprTerm *p = &pPhrase->aTerm[iTerm];
|
||||
zRet = fts5PrintfAppend(zRet, "%s%.*s", iTerm==0?"":" ",
|
||||
p->nQueryTerm, p->pTerm
|
||||
);
|
||||
char *zTerm = pPhrase->aTerm[iTerm].zTerm;
|
||||
zRet = fts5PrintfAppend(zRet, "%s%s", iTerm==0?"":" ", zTerm);
|
||||
if( pPhrase->aTerm[iTerm].bPrefix ){
|
||||
zRet = fts5PrintfAppend(zRet, "*");
|
||||
}
|
||||
@@ -2995,17 +2974,6 @@ static int fts5ExprColsetTest(Fts5Colset *pColset, int iCol){
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** pToken is a buffer nToken bytes in size that may or may not contain
|
||||
** an embedded 0x00 byte. If it does, return the number of bytes in
|
||||
** the buffer before the 0x00. If it does not, return nToken.
|
||||
*/
|
||||
static int fts5QueryTerm(const char *pToken, int nToken){
|
||||
int ii;
|
||||
for(ii=0; ii<nToken && pToken[ii]; ii++){}
|
||||
return ii;
|
||||
}
|
||||
|
||||
static int fts5ExprPopulatePoslistsCb(
|
||||
void *pCtx, /* Copy of 2nd argument to xTokenize() */
|
||||
int tflags, /* Mask of FTS5_TOKEN_* flags */
|
||||
@@ -3017,33 +2985,22 @@ static int fts5ExprPopulatePoslistsCb(
|
||||
Fts5ExprCtx *p = (Fts5ExprCtx*)pCtx;
|
||||
Fts5Expr *pExpr = p->pExpr;
|
||||
int i;
|
||||
int nQuery = nToken;
|
||||
i64 iRowid = pExpr->pRoot->iRowid;
|
||||
|
||||
UNUSED_PARAM2(iUnused1, iUnused2);
|
||||
|
||||
if( nQuery>FTS5_MAX_TOKEN_SIZE ) nQuery = FTS5_MAX_TOKEN_SIZE;
|
||||
if( pExpr->pConfig->bTokendata ){
|
||||
nQuery = fts5QueryTerm(pToken, nQuery);
|
||||
}
|
||||
if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE;
|
||||
if( (tflags & FTS5_TOKEN_COLOCATED)==0 ) p->iOff++;
|
||||
for(i=0; i<pExpr->nPhrase; i++){
|
||||
Fts5ExprTerm *pT;
|
||||
Fts5ExprTerm *pTerm;
|
||||
if( p->aPopulator[i].bOk==0 ) continue;
|
||||
for(pT=&pExpr->apExprPhrase[i]->aTerm[0]; pT; pT=pT->pSynonym){
|
||||
if( (pT->nQueryTerm==nQuery || (pT->nQueryTerm<nQuery && pT->bPrefix))
|
||||
&& memcmp(pT->pTerm, pToken, pT->nQueryTerm)==0
|
||||
for(pTerm=&pExpr->apExprPhrase[i]->aTerm[0]; pTerm; pTerm=pTerm->pSynonym){
|
||||
int nTerm = (int)strlen(pTerm->zTerm);
|
||||
if( (nTerm==nToken || (nTerm<nToken && pTerm->bPrefix))
|
||||
&& memcmp(pTerm->zTerm, pToken, nTerm)==0
|
||||
){
|
||||
int rc = sqlite3Fts5PoslistWriterAppend(
|
||||
&pExpr->apExprPhrase[i]->poslist, &p->aPopulator[i].writer, p->iOff
|
||||
);
|
||||
if( rc==SQLITE_OK && pExpr->pConfig->bTokendata && !pT->bPrefix ){
|
||||
int iCol = p->iOff>>32;
|
||||
int iTokOff = p->iOff & 0x7FFFFFFF;
|
||||
rc = sqlite3Fts5IndexIterWriteTokendata(
|
||||
pT->pIter, pToken, nToken, iRowid, iCol, iTokOff
|
||||
);
|
||||
}
|
||||
if( rc ) return rc;
|
||||
break;
|
||||
}
|
||||
@@ -3178,80 +3135,3 @@ int sqlite3Fts5ExprPhraseCollist(
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Does the work of the fts5_api.xQueryToken() API method.
|
||||
*/
|
||||
int sqlite3Fts5ExprQueryToken(
|
||||
Fts5Expr *pExpr,
|
||||
int iPhrase,
|
||||
int iToken,
|
||||
const char **ppOut,
|
||||
int *pnOut
|
||||
){
|
||||
Fts5ExprPhrase *pPhrase = 0;
|
||||
|
||||
if( iPhrase<0 || iPhrase>=pExpr->nPhrase ){
|
||||
return SQLITE_RANGE;
|
||||
}
|
||||
pPhrase = pExpr->apExprPhrase[iPhrase];
|
||||
if( iToken<0 || iToken>=pPhrase->nTerm ){
|
||||
return SQLITE_RANGE;
|
||||
}
|
||||
|
||||
*ppOut = pPhrase->aTerm[iToken].pTerm;
|
||||
*pnOut = pPhrase->aTerm[iToken].nFullTerm;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Does the work of the fts5_api.xInstToken() API method.
|
||||
*/
|
||||
int sqlite3Fts5ExprInstToken(
|
||||
Fts5Expr *pExpr,
|
||||
i64 iRowid,
|
||||
int iPhrase,
|
||||
int iCol,
|
||||
int iOff,
|
||||
int iToken,
|
||||
const char **ppOut,
|
||||
int *pnOut
|
||||
){
|
||||
Fts5ExprPhrase *pPhrase = 0;
|
||||
Fts5ExprTerm *pTerm = 0;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
if( iPhrase<0 || iPhrase>=pExpr->nPhrase ){
|
||||
return SQLITE_RANGE;
|
||||
}
|
||||
pPhrase = pExpr->apExprPhrase[iPhrase];
|
||||
if( iToken<0 || iToken>=pPhrase->nTerm ){
|
||||
return SQLITE_RANGE;
|
||||
}
|
||||
pTerm = &pPhrase->aTerm[iToken];
|
||||
if( pTerm->bPrefix==0 ){
|
||||
if( pExpr->pConfig->bTokendata ){
|
||||
rc = sqlite3Fts5IterToken(
|
||||
pTerm->pIter, iRowid, iCol, iOff+iToken, ppOut, pnOut
|
||||
);
|
||||
}else{
|
||||
*ppOut = pTerm->pTerm;
|
||||
*pnOut = pTerm->nFullTerm;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Clear the token mappings for all Fts5IndexIter objects mannaged by
|
||||
** the expression passed as the only argument.
|
||||
*/
|
||||
void sqlite3Fts5ExprClearTokens(Fts5Expr *pExpr){
|
||||
int ii;
|
||||
for(ii=0; ii<pExpr->nPhrase; ii++){
|
||||
Fts5ExprTerm *pT;
|
||||
for(pT=&pExpr->apExprPhrase[ii]->aTerm[0]; pT; pT=pT->pSynonym){
|
||||
sqlite3Fts5IndexIterClearTokendata(pT->pIter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
-30
@@ -36,15 +36,10 @@ struct Fts5Hash {
|
||||
|
||||
/*
|
||||
** Each entry in the hash table is represented by an object of the
|
||||
** following type. Each object, its key, and its current data are stored
|
||||
** in a single memory allocation. The key immediately follows the object
|
||||
** in memory. The position list data immediately follows the key data
|
||||
** in memory.
|
||||
**
|
||||
** The key is Fts5HashEntry.nKey bytes in size. It consists of a single
|
||||
** byte identifying the index (either the main term index or a prefix-index),
|
||||
** followed by the term data. For example: "0token". There is no
|
||||
** nul-terminator - in this case nKey=6.
|
||||
** following type. Each object, its key (a nul-terminated string) and
|
||||
** its current data are stored in a single memory allocation. The
|
||||
** key immediately follows the object in memory. The position list
|
||||
** data immediately follows the key data in memory.
|
||||
**
|
||||
** The data that follows the key is in a similar, but not identical format
|
||||
** to the doclist data stored in the database. It is:
|
||||
@@ -179,7 +174,8 @@ static int fts5HashResize(Fts5Hash *pHash){
|
||||
unsigned int iHash;
|
||||
Fts5HashEntry *p = apOld[i];
|
||||
apOld[i] = p->pHashNext;
|
||||
iHash = fts5HashKey(nNew, (u8*)fts5EntryKey(p), p->nKey);
|
||||
iHash = fts5HashKey(nNew, (u8*)fts5EntryKey(p),
|
||||
(int)strlen(fts5EntryKey(p)));
|
||||
p->pHashNext = apNew[iHash];
|
||||
apNew[iHash] = p;
|
||||
}
|
||||
@@ -263,7 +259,7 @@ int sqlite3Fts5HashWrite(
|
||||
for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){
|
||||
char *zKey = fts5EntryKey(p);
|
||||
if( zKey[0]==bByte
|
||||
&& p->nKey==nToken+1
|
||||
&& p->nKey==nToken
|
||||
&& memcmp(&zKey[1], pToken, nToken)==0
|
||||
){
|
||||
break;
|
||||
@@ -293,9 +289,9 @@ int sqlite3Fts5HashWrite(
|
||||
zKey[0] = bByte;
|
||||
memcpy(&zKey[1], pToken, nToken);
|
||||
assert( iHash==fts5HashKey(pHash->nSlot, (u8*)zKey, nToken+1) );
|
||||
p->nKey = nToken+1;
|
||||
p->nKey = nToken;
|
||||
zKey[nToken+1] = '\0';
|
||||
p->nData = nToken+1 + sizeof(Fts5HashEntry);
|
||||
p->nData = nToken+1 + 1 + sizeof(Fts5HashEntry);
|
||||
p->pHashNext = pHash->aSlot[iHash];
|
||||
pHash->aSlot[iHash] = p;
|
||||
pHash->nEntry++;
|
||||
@@ -412,17 +408,12 @@ static Fts5HashEntry *fts5HashEntryMerge(
|
||||
*ppOut = p1;
|
||||
p1 = 0;
|
||||
}else{
|
||||
int i = 0;
|
||||
char *zKey1 = fts5EntryKey(p1);
|
||||
char *zKey2 = fts5EntryKey(p2);
|
||||
int nMin = MIN(p1->nKey, p2->nKey);
|
||||
while( zKey1[i]==zKey2[i] ) i++;
|
||||
|
||||
int cmp = memcmp(zKey1, zKey2, nMin);
|
||||
if( cmp==0 ){
|
||||
cmp = p1->nKey - p2->nKey;
|
||||
}
|
||||
assert( cmp!=0 );
|
||||
|
||||
if( cmp>0 ){
|
||||
if( ((u8)zKey1[i])>((u8)zKey2[i]) ){
|
||||
/* p2 is smaller */
|
||||
*ppOut = p2;
|
||||
ppOut = &p2->pScanNext;
|
||||
@@ -464,7 +455,7 @@ static int fts5HashEntrySort(
|
||||
Fts5HashEntry *pIter;
|
||||
for(pIter=pHash->aSlot[iSlot]; pIter; pIter=pIter->pHashNext){
|
||||
if( pTerm==0
|
||||
|| (pIter->nKey>=nTerm && 0==memcmp(fts5EntryKey(pIter), pTerm, nTerm))
|
||||
|| (pIter->nKey+1>=nTerm && 0==memcmp(fts5EntryKey(pIter), pTerm, nTerm))
|
||||
){
|
||||
Fts5HashEntry *pEntry = pIter;
|
||||
pEntry->pScanNext = 0;
|
||||
@@ -503,11 +494,12 @@ int sqlite3Fts5HashQuery(
|
||||
|
||||
for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){
|
||||
zKey = fts5EntryKey(p);
|
||||
if( nTerm==p->nKey && memcmp(zKey, pTerm, nTerm)==0 ) break;
|
||||
assert( p->nKey+1==(int)strlen(zKey) );
|
||||
if( nTerm==p->nKey+1 && memcmp(zKey, pTerm, nTerm)==0 ) break;
|
||||
}
|
||||
|
||||
if( p ){
|
||||
int nHashPre = sizeof(Fts5HashEntry) + nTerm;
|
||||
int nHashPre = sizeof(Fts5HashEntry) + nTerm + 1;
|
||||
int nList = p->nData - nHashPre;
|
||||
u8 *pRet = (u8*)(*ppOut = sqlite3_malloc64(nPre + nList + 10));
|
||||
if( pRet ){
|
||||
@@ -568,22 +560,19 @@ int sqlite3Fts5HashScanEof(Fts5Hash *p){
|
||||
void sqlite3Fts5HashScanEntry(
|
||||
Fts5Hash *pHash,
|
||||
const char **pzTerm, /* OUT: term (nul-terminated) */
|
||||
int *pnTerm, /* OUT: Size of term in bytes */
|
||||
const u8 **ppDoclist, /* OUT: pointer to doclist */
|
||||
int *pnDoclist /* OUT: size of doclist in bytes */
|
||||
){
|
||||
Fts5HashEntry *p;
|
||||
if( (p = pHash->pScan) ){
|
||||
char *zKey = fts5EntryKey(p);
|
||||
int nTerm = p->nKey;
|
||||
int nTerm = (int)strlen(zKey);
|
||||
fts5HashAddPoslistSize(pHash, p, 0);
|
||||
*pzTerm = zKey;
|
||||
*pnTerm = nTerm;
|
||||
*ppDoclist = (const u8*)&zKey[nTerm];
|
||||
*pnDoclist = p->nData - (sizeof(Fts5HashEntry) + nTerm);
|
||||
*ppDoclist = (const u8*)&zKey[nTerm+1];
|
||||
*pnDoclist = p->nData - (sizeof(Fts5HashEntry) + nTerm + 1);
|
||||
}else{
|
||||
*pzTerm = 0;
|
||||
*pnTerm = 0;
|
||||
*ppDoclist = 0;
|
||||
*pnDoclist = 0;
|
||||
}
|
||||
|
||||
+73
-777
File diff suppressed because it is too large
Load Diff
+30
-87
@@ -656,15 +656,12 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
|
||||
}
|
||||
idxStr[iIdxStr] = '\0';
|
||||
|
||||
/* Set idxFlags flags for the ORDER BY clause
|
||||
**
|
||||
** Note that tokendata=1 tables cannot currently handle "ORDER BY rowid DESC".
|
||||
*/
|
||||
/* Set idxFlags flags for the ORDER BY clause */
|
||||
if( pInfo->nOrderBy==1 ){
|
||||
int iSort = pInfo->aOrderBy[0].iColumn;
|
||||
if( iSort==(pConfig->nCol+1) && bSeenMatch ){
|
||||
idxFlags |= FTS5_BI_ORDER_RANK;
|
||||
}else if( iSort==-1 && (!pInfo->aOrderBy[0].desc || !pConfig->bTokendata) ){
|
||||
}else if( iSort==-1 ){
|
||||
idxFlags |= FTS5_BI_ORDER_ROWID;
|
||||
}
|
||||
if( BitFlagTest(idxFlags, FTS5_BI_ORDER_RANK|FTS5_BI_ORDER_ROWID) ){
|
||||
@@ -916,16 +913,6 @@ static int fts5NextMethod(sqlite3_vtab_cursor *pCursor){
|
||||
);
|
||||
assert( !CsrFlagTest(pCsr, FTS5CSR_EOF) );
|
||||
|
||||
/* If this cursor uses FTS5_PLAN_MATCH and this is a tokendata=1 table,
|
||||
** clear any token mappings accumulated at the fts5_index.c level. In
|
||||
** other cases, specifically FTS5_PLAN_SOURCE and FTS5_PLAN_SORTED_MATCH,
|
||||
** we need to retain the mappings for the entire query. */
|
||||
if( pCsr->ePlan==FTS5_PLAN_MATCH
|
||||
&& ((Fts5Table*)pCursor->pVtab)->pConfig->bTokendata
|
||||
){
|
||||
sqlite3Fts5ExprClearTokens(pCsr->pExpr);
|
||||
}
|
||||
|
||||
if( pCsr->ePlan<3 ){
|
||||
int bSkip = 0;
|
||||
if( (rc = fts5CursorReseek(pCsr, &bSkip)) || bSkip ) return rc;
|
||||
@@ -1914,10 +1901,7 @@ static int fts5ApiColumnText(
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
|
||||
Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
|
||||
if( iCol<0 || iCol>=pTab->pConfig->nCol ){
|
||||
rc = SQLITE_RANGE;
|
||||
}else if( fts5IsContentless((Fts5FullTable*)(pCsr->base.pVtab))
|
||||
if( fts5IsContentless((Fts5FullTable*)(pCsr->base.pVtab))
|
||||
|| pCsr->ePlan==FTS5_PLAN_SPECIAL
|
||||
){
|
||||
*pz = 0;
|
||||
@@ -1942,9 +1926,8 @@ static int fts5CsrPoslist(
|
||||
int rc = SQLITE_OK;
|
||||
int bLive = (pCsr->pSorter==0);
|
||||
|
||||
if( iPhrase<0 || iPhrase>=sqlite3Fts5ExprPhraseCount(pCsr->pExpr) ){
|
||||
rc = SQLITE_RANGE;
|
||||
}else if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_POSLIST) ){
|
||||
if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_POSLIST) ){
|
||||
|
||||
if( pConfig->eDetail!=FTS5_DETAIL_FULL ){
|
||||
Fts5PoslistPopulator *aPopulator;
|
||||
int i;
|
||||
@@ -1968,21 +1951,15 @@ static int fts5CsrPoslist(
|
||||
CsrFlagClear(pCsr, FTS5CSR_REQUIRE_POSLIST);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
if( pCsr->pSorter && pConfig->eDetail==FTS5_DETAIL_FULL ){
|
||||
Fts5Sorter *pSorter = pCsr->pSorter;
|
||||
int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]);
|
||||
*pn = pSorter->aIdx[iPhrase] - i1;
|
||||
*pa = &pSorter->aPoslist[i1];
|
||||
}else{
|
||||
*pn = sqlite3Fts5ExprPoslist(pCsr->pExpr, iPhrase, pa);
|
||||
}
|
||||
if( pCsr->pSorter && pConfig->eDetail==FTS5_DETAIL_FULL ){
|
||||
Fts5Sorter *pSorter = pCsr->pSorter;
|
||||
int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]);
|
||||
*pn = pSorter->aIdx[iPhrase] - i1;
|
||||
*pa = &pSorter->aPoslist[i1];
|
||||
}else{
|
||||
*pa = 0;
|
||||
*pn = 0;
|
||||
*pn = sqlite3Fts5ExprPoslist(pCsr->pExpr, iPhrase, pa);
|
||||
}
|
||||
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -2089,6 +2066,12 @@ static int fts5ApiInst(
|
||||
){
|
||||
if( iIdx<0 || iIdx>=pCsr->nInstCount ){
|
||||
rc = SQLITE_RANGE;
|
||||
#if 0
|
||||
}else if( fts5IsOffsetless((Fts5Table*)pCsr->base.pVtab) ){
|
||||
*piPhrase = pCsr->aInst[iIdx*3];
|
||||
*piCol = pCsr->aInst[iIdx*3 + 2];
|
||||
*piOff = -1;
|
||||
#endif
|
||||
}else{
|
||||
*piPhrase = pCsr->aInst[iIdx*3];
|
||||
*piCol = pCsr->aInst[iIdx*3 + 1];
|
||||
@@ -2343,56 +2326,13 @@ static int fts5ApiPhraseFirstColumn(
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** xQueryToken() API implemenetation.
|
||||
*/
|
||||
static int fts5ApiQueryToken(
|
||||
Fts5Context* pCtx,
|
||||
int iPhrase,
|
||||
int iToken,
|
||||
const char **ppOut,
|
||||
int *pnOut
|
||||
){
|
||||
Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
|
||||
return sqlite3Fts5ExprQueryToken(pCsr->pExpr, iPhrase, iToken, ppOut, pnOut);
|
||||
}
|
||||
|
||||
/*
|
||||
** xInstToken() API implemenetation.
|
||||
*/
|
||||
static int fts5ApiInstToken(
|
||||
Fts5Context *pCtx,
|
||||
int iIdx,
|
||||
int iToken,
|
||||
const char **ppOut, int *pnOut
|
||||
){
|
||||
Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
|
||||
int rc = SQLITE_OK;
|
||||
if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_INST)==0
|
||||
|| SQLITE_OK==(rc = fts5CacheInstArray(pCsr))
|
||||
){
|
||||
if( iIdx<0 || iIdx>=pCsr->nInstCount ){
|
||||
rc = SQLITE_RANGE;
|
||||
}else{
|
||||
int iPhrase = pCsr->aInst[iIdx*3];
|
||||
int iCol = pCsr->aInst[iIdx*3 + 1];
|
||||
int iOff = pCsr->aInst[iIdx*3 + 2];
|
||||
i64 iRowid = fts5CursorRowid(pCsr);
|
||||
rc = sqlite3Fts5ExprInstToken(
|
||||
pCsr->pExpr, iRowid, iPhrase, iCol, iOff, iToken, ppOut, pnOut
|
||||
);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
static int fts5ApiQueryPhrase(Fts5Context*, int, void*,
|
||||
int(*)(const Fts5ExtensionApi*, Fts5Context*, void*)
|
||||
);
|
||||
|
||||
static const Fts5ExtensionApi sFts5Api = {
|
||||
3, /* iVersion */
|
||||
2, /* iVersion */
|
||||
fts5ApiUserData,
|
||||
fts5ApiColumnCount,
|
||||
fts5ApiRowCount,
|
||||
@@ -2412,8 +2352,6 @@ static const Fts5ExtensionApi sFts5Api = {
|
||||
fts5ApiPhraseNext,
|
||||
fts5ApiPhraseFirstColumn,
|
||||
fts5ApiPhraseNextColumn,
|
||||
fts5ApiQueryToken,
|
||||
fts5ApiInstToken
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -2971,23 +2909,28 @@ static int fts5IntegrityMethod(
|
||||
char **pzErr /* Write error message here */
|
||||
){
|
||||
Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
|
||||
Fts5Config *pConfig = pTab->p.pConfig;
|
||||
char *zSql;
|
||||
char *zErr = 0;
|
||||
int rc;
|
||||
|
||||
assert( pzErr!=0 && *pzErr==0 );
|
||||
UNUSED_PARAM(isQuick);
|
||||
rc = sqlite3Fts5StorageIntegrity(pTab->pStorage, 0);
|
||||
zSql = sqlite3_mprintf(
|
||||
"INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');",
|
||||
zSchema, zTabname, pConfig->zName);
|
||||
if( zSql==0 ) return SQLITE_NOMEM;
|
||||
rc = sqlite3_exec(pConfig->db, zSql, 0, 0, &zErr);
|
||||
sqlite3_free(zSql);
|
||||
if( (rc&0xff)==SQLITE_CORRUPT ){
|
||||
*pzErr = sqlite3_mprintf("malformed inverted index for FTS5 table %s.%s",
|
||||
zSchema, zTabname);
|
||||
rc = (*pzErr) ? SQLITE_OK : SQLITE_NOMEM;
|
||||
}else if( rc!=SQLITE_OK ){
|
||||
*pzErr = sqlite3_mprintf("unable to validate the inverted index for"
|
||||
" FTS5 table %s.%s: %s",
|
||||
zSchema, zTabname, sqlite3_errstr(rc));
|
||||
zSchema, zTabname, zErr);
|
||||
}
|
||||
sqlite3Fts5IndexCloseReader(pTab->p.pIndex);
|
||||
|
||||
return rc;
|
||||
sqlite3_free(zErr);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
static int fts5Init(sqlite3 *db){
|
||||
|
||||
@@ -673,7 +673,7 @@ int sqlite3Fts5StorageRebuild(Fts5Storage *p){
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, pConfig->pzErrmsg);
|
||||
rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, 0);
|
||||
}
|
||||
|
||||
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pScan) ){
|
||||
|
||||
+1
-207
@@ -244,9 +244,6 @@ static int SQLITE_TCLAPI xF5tApi(
|
||||
{ "xGetAuxdataInt", 1, "CLEAR" }, /* 15 */
|
||||
{ "xPhraseForeach", 4, "IPHRASE COLVAR OFFVAR SCRIPT" }, /* 16 */
|
||||
{ "xPhraseColumnForeach", 3, "IPHRASE COLVAR SCRIPT" }, /* 17 */
|
||||
|
||||
{ "xQueryToken", 2, "IPHRASE ITERM" }, /* 18 */
|
||||
{ "xInstToken", 2, "IDX ITERM" }, /* 19 */
|
||||
{ 0, 0, 0}
|
||||
};
|
||||
|
||||
@@ -503,38 +500,6 @@ static int SQLITE_TCLAPI xF5tApi(
|
||||
break;
|
||||
}
|
||||
|
||||
CASE(18, "xQueryToken") {
|
||||
const char *pTerm = 0;
|
||||
int nTerm = 0;
|
||||
int iPhrase = 0;
|
||||
int iTerm = 0;
|
||||
|
||||
if( Tcl_GetIntFromObj(interp, objv[2], &iPhrase) ) return TCL_ERROR;
|
||||
if( Tcl_GetIntFromObj(interp, objv[3], &iTerm) ) return TCL_ERROR;
|
||||
rc = p->pApi->xQueryToken(p->pFts, iPhrase, iTerm, &pTerm, &nTerm);
|
||||
if( rc==SQLITE_OK ){
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(pTerm, nTerm));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
CASE(19, "xInstToken") {
|
||||
const char *pTerm = 0;
|
||||
int nTerm = 0;
|
||||
int iIdx = 0;
|
||||
int iTerm = 0;
|
||||
|
||||
if( Tcl_GetIntFromObj(interp, objv[2], &iIdx) ) return TCL_ERROR;
|
||||
if( Tcl_GetIntFromObj(interp, objv[3], &iTerm) ) return TCL_ERROR;
|
||||
rc = p->pApi->xInstToken(p->pFts, iIdx, iTerm, &pTerm, &nTerm);
|
||||
if( rc==SQLITE_OK ){
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(pTerm, nTerm));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
assert( 0 );
|
||||
break;
|
||||
@@ -1152,176 +1117,6 @@ static int SQLITE_TCLAPI f5tRegisterTok(
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
typedef struct OriginTextCtx OriginTextCtx;
|
||||
struct OriginTextCtx {
|
||||
sqlite3 *db;
|
||||
fts5_api *pApi;
|
||||
};
|
||||
|
||||
typedef struct OriginTextTokenizer OriginTextTokenizer;
|
||||
struct OriginTextTokenizer {
|
||||
Fts5Tokenizer *pTok; /* Underlying tokenizer object */
|
||||
fts5_tokenizer tokapi; /* API implementation for pTok */
|
||||
};
|
||||
|
||||
/*
|
||||
** Delete the OriginTextCtx object indicated by the only argument.
|
||||
*/
|
||||
static void f5tOrigintextTokenizerDelete(void *pCtx){
|
||||
OriginTextCtx *p = (OriginTextCtx*)pCtx;
|
||||
ckfree((char*)p);
|
||||
}
|
||||
|
||||
static int f5tOrigintextCreate(
|
||||
void *pCtx,
|
||||
const char **azArg,
|
||||
int nArg,
|
||||
Fts5Tokenizer **ppOut
|
||||
){
|
||||
OriginTextCtx *p = (OriginTextCtx*)pCtx;
|
||||
OriginTextTokenizer *pTok = 0;
|
||||
void *pTokCtx = 0;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
pTok = (OriginTextTokenizer*)sqlite3_malloc(sizeof(OriginTextTokenizer));
|
||||
if( pTok==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else if( nArg<1 ){
|
||||
rc = SQLITE_ERROR;
|
||||
}else{
|
||||
/* Locate the underlying tokenizer */
|
||||
rc = p->pApi->xFindTokenizer(p->pApi, azArg[0], &pTokCtx, &pTok->tokapi);
|
||||
}
|
||||
|
||||
/* Create the new tokenizer instance */
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = pTok->tokapi.xCreate(pTokCtx, &azArg[1], nArg-1, &pTok->pTok);
|
||||
}
|
||||
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3_free(pTok);
|
||||
pTok = 0;
|
||||
}
|
||||
*ppOut = (Fts5Tokenizer*)pTok;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void f5tOrigintextDelete(Fts5Tokenizer *pTokenizer){
|
||||
OriginTextTokenizer *p = (OriginTextTokenizer*)pTokenizer;
|
||||
if( p->pTok ){
|
||||
p->tokapi.xDelete(p->pTok);
|
||||
}
|
||||
sqlite3_free(p);
|
||||
}
|
||||
|
||||
typedef struct OriginTextCb OriginTextCb;
|
||||
struct OriginTextCb {
|
||||
void *pCtx;
|
||||
const char *pText;
|
||||
int nText;
|
||||
int (*xToken)(void *, int, const char *, int, int, int);
|
||||
|
||||
char *aBuf; /* Buffer to use */
|
||||
int nBuf; /* Allocated size of aBuf[] */
|
||||
};
|
||||
|
||||
static int xOriginToken(
|
||||
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 */
|
||||
){
|
||||
OriginTextCb *p = (OriginTextCb*)pCtx;
|
||||
int ret = 0;
|
||||
|
||||
if( nToken==(iEnd-iStart) && 0==memcmp(pToken, &p->pText[iStart], nToken) ){
|
||||
/* Token exactly matches document text. Pass it through as is. */
|
||||
ret = p->xToken(p->pCtx, tflags, pToken, nToken, iStart, iEnd);
|
||||
}else{
|
||||
int nReq = nToken + 1 + (iEnd-iStart);
|
||||
if( nReq>p->nBuf ){
|
||||
sqlite3_free(p->aBuf);
|
||||
p->aBuf = sqlite3_malloc(nReq*2);
|
||||
if( p->aBuf==0 ) return SQLITE_NOMEM;
|
||||
p->nBuf = nReq*2;
|
||||
}
|
||||
|
||||
memcpy(p->aBuf, pToken, nToken);
|
||||
p->aBuf[nToken] = '\0';
|
||||
memcpy(&p->aBuf[nToken+1], &p->pText[iStart], iEnd-iStart);
|
||||
ret = p->xToken(p->pCtx, tflags, p->aBuf, nReq, iStart, iEnd);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static int f5tOrigintextTokenize(
|
||||
Fts5Tokenizer *pTokenizer,
|
||||
void *pCtx,
|
||||
int flags, /* Mask of FTS5_TOKENIZE_* flags */
|
||||
const char *pText, int nText,
|
||||
int (*xToken)(void *, int, const char *, int, int, int)
|
||||
){
|
||||
OriginTextTokenizer *p = (OriginTextTokenizer*)pTokenizer;
|
||||
OriginTextCb cb;
|
||||
int ret;
|
||||
|
||||
memset(&cb, 0, sizeof(cb));
|
||||
cb.pCtx = pCtx;
|
||||
cb.pText = pText;
|
||||
cb.nText = nText;
|
||||
cb.xToken = xToken;
|
||||
|
||||
ret = p->tokapi.xTokenize(p->pTok,(void*)&cb,flags,pText,nText,xOriginToken);
|
||||
sqlite3_free(cb.aBuf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
** sqlite3_fts5_register_origintext DB
|
||||
**
|
||||
** Description...
|
||||
*/
|
||||
static int SQLITE_TCLAPI f5tRegisterOriginText(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
sqlite3 *db = 0;
|
||||
fts5_api *pApi = 0;
|
||||
int rc;
|
||||
fts5_tokenizer tok = {0, 0, 0};
|
||||
OriginTextCtx *pCtx = 0;
|
||||
|
||||
if( objc!=2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "DB");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( f5tDbAndApi(interp, objv[1], &db, &pApi) ) return TCL_ERROR;
|
||||
|
||||
pCtx = (OriginTextCtx*)ckalloc(sizeof(OriginTextCtx));
|
||||
pCtx->db = db;
|
||||
pCtx->pApi = pApi;
|
||||
|
||||
tok.xCreate = f5tOrigintextCreate;
|
||||
tok.xDelete = f5tOrigintextDelete;
|
||||
tok.xTokenize = f5tOrigintextTokenize;
|
||||
rc = pApi->xCreateTokenizer(
|
||||
pApi, "origintext", (void*)pCtx, &tok, f5tOrigintextTokenizerDelete
|
||||
);
|
||||
|
||||
Tcl_ResetResult(interp);
|
||||
if( rc!=SQLITE_OK ){
|
||||
Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), 0);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Entry point.
|
||||
*/
|
||||
@@ -1338,8 +1133,7 @@ int Fts5tcl_Init(Tcl_Interp *interp){
|
||||
{ "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_fts5tokenize", f5tRegisterTok, 0 }
|
||||
};
|
||||
int i;
|
||||
F5tTokenizerContext *pContext;
|
||||
|
||||
+26
-69
@@ -229,12 +229,6 @@ static const unsigned char sqlite3Utf8Trans1[] = {
|
||||
|
||||
#endif /* ifndef SQLITE_AMALGAMATION */
|
||||
|
||||
#define FTS5_SKIP_UTF8(zIn) { \
|
||||
if( ((unsigned char)(*(zIn++)))>=0xc0 ){ \
|
||||
while( (((unsigned char)*zIn) & 0xc0)==0x80 ){ zIn++; } \
|
||||
} \
|
||||
}
|
||||
|
||||
typedef struct Unicode61Tokenizer Unicode61Tokenizer;
|
||||
struct Unicode61Tokenizer {
|
||||
unsigned char aTokenChar[128]; /* ASCII range token characters */
|
||||
@@ -1270,7 +1264,6 @@ static int fts5PorterTokenize(
|
||||
typedef struct TrigramTokenizer TrigramTokenizer;
|
||||
struct TrigramTokenizer {
|
||||
int bFold; /* True to fold to lower-case */
|
||||
int iFoldParam; /* Parameter to pass to Fts5UnicodeFold() */
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -1297,7 +1290,6 @@ static int fts5TriCreate(
|
||||
}else{
|
||||
int i;
|
||||
pNew->bFold = 1;
|
||||
pNew->iFoldParam = 0;
|
||||
for(i=0; rc==SQLITE_OK && i<nArg; i+=2){
|
||||
const char *zArg = azArg[i+1];
|
||||
if( 0==sqlite3_stricmp(azArg[i], "case_sensitive") ){
|
||||
@@ -1306,21 +1298,10 @@ static int fts5TriCreate(
|
||||
}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{
|
||||
rc = SQLITE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if( pNew->iFoldParam!=0 && pNew->bFold==0 ){
|
||||
rc = SQLITE_ERROR;
|
||||
}
|
||||
|
||||
if( rc!=SQLITE_OK ){
|
||||
fts5TriDelete((Fts5Tokenizer*)pNew);
|
||||
pNew = 0;
|
||||
@@ -1343,62 +1324,40 @@ static int fts5TriTokenize(
|
||||
TrigramTokenizer *p = (TrigramTokenizer*)pTok;
|
||||
int rc = SQLITE_OK;
|
||||
char aBuf[32];
|
||||
char *zOut = aBuf;
|
||||
int ii;
|
||||
const unsigned char *zIn = (const unsigned char*)pText;
|
||||
const unsigned char *zEof = &zIn[nText];
|
||||
u32 iCode;
|
||||
int aStart[3]; /* Input offset of each character in aBuf[] */
|
||||
|
||||
UNUSED_PARAM(unusedFlags);
|
||||
|
||||
/* Populate aBuf[] with the characters for the first trigram. */
|
||||
for(ii=0; ii<3; ii++){
|
||||
do {
|
||||
aStart[ii] = zIn - (const unsigned char*)pText;
|
||||
READ_UTF8(zIn, zEof, iCode);
|
||||
if( iCode==0 ) return SQLITE_OK;
|
||||
if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam);
|
||||
}while( iCode==0 );
|
||||
WRITE_UTF8(zOut, iCode);
|
||||
}
|
||||
|
||||
/* At the start of each iteration of this loop:
|
||||
**
|
||||
** aBuf: Contains 3 characters. The 3 characters of the next trigram.
|
||||
** zOut: Points to the byte following the last character in aBuf.
|
||||
** aStart[3]: Contains the byte offset in the input text corresponding
|
||||
** to the start of each of the three characters in the buffer.
|
||||
*/
|
||||
assert( zIn<=zEof );
|
||||
while( 1 ){
|
||||
int iNext; /* Start of character following current tri */
|
||||
const char *z1;
|
||||
char *zOut = aBuf;
|
||||
int iStart = zIn - (const unsigned char*)pText;
|
||||
const unsigned char *zNext;
|
||||
|
||||
/* Read characters from the input up until the first non-diacritic */
|
||||
do {
|
||||
iNext = zIn - (const unsigned char*)pText;
|
||||
READ_UTF8(zIn, zEof, iCode);
|
||||
if( iCode==0 ) break;
|
||||
zNext = zIn;
|
||||
if( zIn<zEof ){
|
||||
if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, 0);
|
||||
WRITE_UTF8(zOut, iCode);
|
||||
READ_UTF8(zIn, zEof, iCode);
|
||||
if( iCode==0 ) break;
|
||||
if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam);
|
||||
}while( iCode==0 );
|
||||
|
||||
/* Pass the current trigram back to fts5 */
|
||||
rc = xToken(pCtx, 0, aBuf, zOut-aBuf, aStart[0], iNext);
|
||||
if( iCode==0 || rc!=SQLITE_OK ) break;
|
||||
|
||||
/* Remove the first character from buffer aBuf[]. Append the character
|
||||
** with codepoint iCode. */
|
||||
z1 = aBuf;
|
||||
FTS5_SKIP_UTF8(z1);
|
||||
memmove(aBuf, z1, zOut - z1);
|
||||
zOut -= (z1 - aBuf);
|
||||
WRITE_UTF8(zOut, iCode);
|
||||
|
||||
/* Update the aStart[] array */
|
||||
aStart[0] = aStart[1];
|
||||
aStart[1] = aStart[2];
|
||||
aStart[2] = iNext;
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
if( zIn<zEof ){
|
||||
if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, 0);
|
||||
WRITE_UTF8(zOut, iCode);
|
||||
READ_UTF8(zIn, zEof, iCode);
|
||||
if( iCode==0 ) break;
|
||||
if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, 0);
|
||||
WRITE_UTF8(zOut, iCode);
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
rc = xToken(pCtx, 0, aBuf, zOut-aBuf, iStart, iStart + zOut-aBuf);
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
zIn = zNext;
|
||||
}
|
||||
|
||||
return rc;
|
||||
@@ -1421,9 +1380,7 @@ int sqlite3Fts5TokenizerPattern(
|
||||
){
|
||||
if( xCreate==fts5TriCreate ){
|
||||
TrigramTokenizer *p = (TrigramTokenizer*)pTok;
|
||||
if( p->iFoldParam==0 ){
|
||||
return p->bFold ? FTS5_PATTERN_LIKE : FTS5_PATTERN_GLOB;
|
||||
}
|
||||
return p->bFold ? FTS5_PATTERN_LIKE : FTS5_PATTERN_GLOB;
|
||||
}
|
||||
return FTS5_PATTERN_NONE;
|
||||
}
|
||||
|
||||
@@ -629,7 +629,7 @@ static int fts5VocabFilterMethod(
|
||||
if( pEq ){
|
||||
zTerm = (const char *)sqlite3_value_text(pEq);
|
||||
nTerm = sqlite3_value_bytes(pEq);
|
||||
f = FTS5INDEX_QUERY_NOTOKENDATA;
|
||||
f = 0;
|
||||
}else{
|
||||
if( pGe ){
|
||||
zTerm = (const char *)sqlite3_value_text(pGe);
|
||||
|
||||
@@ -61,12 +61,6 @@ proc fts5_test_collist {cmd} {
|
||||
set res
|
||||
}
|
||||
|
||||
proc fts5_collist {cmd iPhrase} {
|
||||
set res [list]
|
||||
$cmd xPhraseColumnForeach $iPhrase c { lappend res $c }
|
||||
set res
|
||||
}
|
||||
|
||||
proc fts5_test_columnsize {cmd} {
|
||||
set res [list]
|
||||
for {set i 0} {$i < [$cmd xColumnCount]} {incr i} {
|
||||
@@ -75,10 +69,6 @@ proc fts5_test_columnsize {cmd} {
|
||||
set res
|
||||
}
|
||||
|
||||
proc fts5_columntext {cmd iCol} {
|
||||
$cmd xColumnText $iCol
|
||||
}
|
||||
|
||||
proc fts5_test_columntext {cmd} {
|
||||
set res [list]
|
||||
for {set i 0} {$i < [$cmd xColumnCount]} {incr i} {
|
||||
@@ -135,13 +125,6 @@ proc fts5_test_queryphrase {cmd} {
|
||||
set res
|
||||
}
|
||||
|
||||
proc fts5_queryphrase {cmd iPhrase} {
|
||||
set cnt [list]
|
||||
for {set j 0} {$j < [$cmd xColumnCount]} {incr j} { lappend cnt 0 }
|
||||
$cmd xQueryPhrase $iPhrase [list test_queryphrase_cb cnt]
|
||||
set cnt
|
||||
}
|
||||
|
||||
proc fts5_test_phrasecount {cmd} {
|
||||
$cmd xPhraseCount
|
||||
}
|
||||
@@ -171,9 +154,6 @@ proc fts5_aux_test_functions {db} {
|
||||
|
||||
fts5_test_queryphrase
|
||||
fts5_test_phrasecount
|
||||
fts5_columntext
|
||||
fts5_queryphrase
|
||||
fts5_collist
|
||||
} {
|
||||
sqlite3_fts5_create_function $db $f $f
|
||||
}
|
||||
@@ -458,20 +438,6 @@ proc detail_is_none {} { detail_check ; expr {$::detail == "none"} }
|
||||
proc detail_is_col {} { detail_check ; expr {$::detail == "col" } }
|
||||
proc detail_is_full {} { detail_check ; expr {$::detail == "full"} }
|
||||
|
||||
proc foreach_tokenizer_mode {prefix script} {
|
||||
set saved $::testprefix
|
||||
foreach {d mapping} {
|
||||
"" {}
|
||||
"-origintext" {, tokenize="origintext unicode61", tokendata=1}
|
||||
} {
|
||||
set s [string map [list %TOKENIZER% $mapping] $script]
|
||||
set ::testprefix "$prefix$d"
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
uplevel $s
|
||||
}
|
||||
set ::testprefix $saved
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Convert a poslist of the type returned by fts5_test_poslist() to a
|
||||
|
||||
+24
-44
@@ -22,7 +22,6 @@ ifcapable !fts5 {
|
||||
}
|
||||
|
||||
foreach_detail_mode $::testprefix {
|
||||
foreach_tokenizer_mode $::testprefix {
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(a, b, c);
|
||||
@@ -45,7 +44,7 @@ do_execsql_test 1.1 {
|
||||
#
|
||||
|
||||
do_execsql_test 2.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x, y, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x, y, detail=%DETAIL%);
|
||||
}
|
||||
do_execsql_test 2.1 {
|
||||
INSERT INTO t1 VALUES('a b c', 'd e f');
|
||||
@@ -74,9 +73,8 @@ do_execsql_test 2.4 {
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 3.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL%);
|
||||
}
|
||||
foreach {i x y} {
|
||||
1 {g f d b f} {h h e i a}
|
||||
@@ -99,9 +97,8 @@ foreach {i x y} {
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 4.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL%);
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
|
||||
}
|
||||
foreach {i x y} {
|
||||
@@ -124,9 +121,8 @@ foreach {i x y} {
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 5.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL%);
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
|
||||
}
|
||||
foreach {i x y} {
|
||||
@@ -149,9 +145,8 @@ foreach {i x y} {
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 6.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL%);
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
|
||||
}
|
||||
|
||||
@@ -186,7 +181,6 @@ do_execsql_test 6.6 {
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
expr srand(0)
|
||||
do_execsql_test 7.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x,y,z);
|
||||
@@ -228,7 +222,6 @@ for {set i 1} {$i <= 10} {incr i} {
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 8.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x, prefix="1,2,3");
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
|
||||
@@ -243,7 +236,6 @@ do_execsql_test 8.1 {
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
|
||||
expr srand(0)
|
||||
|
||||
@@ -288,9 +280,8 @@ for {set i 1} {$i <= 10} {incr i} {
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 10.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL%);
|
||||
}
|
||||
set d10 {
|
||||
1 {g f d b f} {h h e i a}
|
||||
@@ -323,19 +314,19 @@ do_execsql_test 10.4.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
do_catchsql_test 11.1 {
|
||||
CREATE VIRTUAL TABLE t2 USING fts5(a, b, c, rank, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE t2 USING fts5(a, b, c, rank, detail=%DETAIL%);
|
||||
} {1 {reserved fts5 column name: rank}}
|
||||
do_catchsql_test 11.2 {
|
||||
CREATE VIRTUAL TABLE rank USING fts5(a, b, c, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE rank USING fts5(a, b, c, detail=%DETAIL%);
|
||||
} {1 {reserved fts5 table name: rank}}
|
||||
do_catchsql_test 11.3 {
|
||||
CREATE VIRTUAL TABLE t2 USING fts5(a, b, c, rowid, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE t2 USING fts5(a, b, c, rowid, detail=%DETAIL%);
|
||||
} {1 {reserved fts5 column name: rowid}}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
do_execsql_test 12.1 {
|
||||
CREATE VIRTUAL TABLE t2 USING fts5(x,y, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE t2 USING fts5(x,y, detail=%DETAIL%);
|
||||
} {}
|
||||
|
||||
do_catchsql_test 12.2 {
|
||||
@@ -350,9 +341,8 @@ do_test 12.3 {
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 13.1 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x, detail=%DETAIL%);
|
||||
INSERT INTO t1(rowid, x) VALUES(1, 'o n e'), (2, 't w o');
|
||||
} {}
|
||||
|
||||
@@ -375,9 +365,8 @@ do_execsql_test 13.6 {
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 14.1 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x, y, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x, y, detail=%DETAIL%);
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
|
||||
WITH d(x,y) AS (
|
||||
SELECT NULL, 'xyz xyz xyz xyz xyz xyz'
|
||||
@@ -460,9 +449,8 @@ do_catchsql_test 16.2 {
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 17.1 {
|
||||
CREATE VIRTUAL TABLE b2 USING fts5(x, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE b2 USING fts5(x, detail=%DETAIL%);
|
||||
INSERT INTO b2 VALUES('a');
|
||||
INSERT INTO b2 VALUES('b');
|
||||
INSERT INTO b2 VALUES('c');
|
||||
@@ -478,9 +466,8 @@ do_test 17.2 {
|
||||
|
||||
if {[string match n* %DETAIL%]==0} {
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 17.3 {
|
||||
CREATE VIRTUAL TABLE c2 USING fts5(x, y, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE c2 USING fts5(x, y, detail=%DETAIL%);
|
||||
INSERT INTO c2 VALUES('x x x', 'x x x');
|
||||
SELECT rowid FROM c2 WHERE c2 MATCH 'y:x';
|
||||
} {1}
|
||||
@@ -489,9 +476,8 @@ if {[string match n* %DETAIL%]==0} {
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 17.1 {
|
||||
CREATE VIRTUAL TABLE uio USING fts5(ttt, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE uio USING fts5(ttt, detail=%DETAIL%);
|
||||
INSERT INTO uio VALUES(NULL);
|
||||
INSERT INTO uio SELECT NULL FROM uio;
|
||||
INSERT INTO uio SELECT NULL FROM uio;
|
||||
@@ -538,8 +524,8 @@ do_execsql_test 17.9 {
|
||||
#--------------------------------------------------------------------
|
||||
#
|
||||
do_execsql_test 18.1 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(a, b, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE t2 USING fts5(c, d, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(a, b, detail=%DETAIL%);
|
||||
CREATE VIRTUAL TABLE t2 USING fts5(c, d, detail=%DETAIL%);
|
||||
INSERT INTO t1 VALUES('abc*', NULL);
|
||||
INSERT INTO t2 VALUES(1, 'abcdefg');
|
||||
}
|
||||
@@ -554,9 +540,8 @@ do_execsql_test 18.3 {
|
||||
# fts5 table in the temp schema.
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 19.0 {
|
||||
CREATE VIRTUAL TABLE temp.t1 USING fts5(x, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE temp.t1 USING fts5(x, detail=%DETAIL%);
|
||||
INSERT INTO t1 VALUES('x y z');
|
||||
INSERT INTO t1 VALUES('w x 1');
|
||||
SELECT rowid FROM t1 WHERE t1 MATCH 'x';
|
||||
@@ -566,9 +551,8 @@ do_execsql_test 19.0 {
|
||||
# Test that 6 and 7 byte varints can be read.
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 20.0 {
|
||||
CREATE VIRTUAL TABLE temp.tmp USING fts5(x, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE temp.tmp USING fts5(x, detail=%DETAIL%);
|
||||
}
|
||||
set ::ids [list \
|
||||
0 [expr 1<<36] [expr 2<<36] [expr 1<<43] [expr 2<<43]
|
||||
@@ -586,7 +570,7 @@ do_test 20.1 {
|
||||
#
|
||||
do_execsql_test 21.0 {
|
||||
CREATE TEMP TABLE t8(a, b);
|
||||
CREATE VIRTUAL TABLE ft USING fts5(x, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE ft USING fts5(x, detail=%DETAIL%);
|
||||
}
|
||||
|
||||
do_execsql_test 21.1 {
|
||||
@@ -597,7 +581,7 @@ do_execsql_test 21.1 {
|
||||
}
|
||||
|
||||
do_execsql_test 22.0 {
|
||||
CREATE VIRTUAL TABLE t9 USING fts5(x, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE t9 USING fts5(x, detail=%DETAIL%);
|
||||
INSERT INTO t9(rowid, x) VALUES(2, 'bbb');
|
||||
BEGIN;
|
||||
INSERT INTO t9(rowid, x) VALUES(1, 'aaa');
|
||||
@@ -612,7 +596,7 @@ do_execsql_test 22.1 {
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
do_execsql_test 23.0 {
|
||||
CREATE VIRTUAL TABLE t10 USING fts5(x, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE t10 USING fts5(x, detail=%DETAIL%);
|
||||
CREATE TABLE t11(x);
|
||||
}
|
||||
do_execsql_test 23.1 {
|
||||
@@ -624,7 +608,7 @@ do_execsql_test 23.2 {
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
do_execsql_test 24.0 {
|
||||
CREATE VIRTUAL TABLE t12 USING fts5(x, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE t12 USING fts5(x, detail=%DETAIL%);
|
||||
INSERT INTO t12 VALUES('aaaa');
|
||||
}
|
||||
do_execsql_test 24.1 {
|
||||
@@ -634,9 +618,6 @@ do_execsql_test 24.1 {
|
||||
INSERT INTO t12 VALUES('aaaa');
|
||||
END;
|
||||
}
|
||||
execsql_pp {
|
||||
SELECT rowid, hex(block) FROM t12_data
|
||||
}
|
||||
do_execsql_test 24.2 {
|
||||
INSERT INTO t12(t12) VALUES('integrity-check');
|
||||
}
|
||||
@@ -646,7 +627,7 @@ do_execsql_test 24.3 {
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
do_execsql_test 25.0 {
|
||||
CREATE VIRTUAL TABLE t13 USING fts5(x, detail=%DETAIL% %TOKENIZER%);
|
||||
CREATE VIRTUAL TABLE t13 USING fts5(x, detail=%DETAIL%);
|
||||
}
|
||||
do_execsql_test 25.1 {
|
||||
BEGIN;
|
||||
@@ -657,7 +638,6 @@ SELECT * FROM t13('BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
expand_all_sql db
|
||||
|
||||
@@ -334,47 +334,4 @@ do_execsql_test 11.2 {
|
||||
SELECT fts5_hitcount(x1) FROM x1('one') LIMIT 1;
|
||||
} {5}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that xColumnText returns SQLITE_RANGE when it should.
|
||||
#
|
||||
reset_db
|
||||
fts5_aux_test_functions db
|
||||
do_execsql_test 12.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(a, b, c);
|
||||
INSERT INTO t1 VALUES('one', 'two', 'three');
|
||||
INSERT INTO t1 VALUES('one', 'one', 'one');
|
||||
INSERT INTO t1 VALUES('two', 'two', 'two');
|
||||
INSERT INTO t1 VALUES('three', 'three', 'three');
|
||||
}
|
||||
|
||||
do_catchsql_test 12.1.1 {
|
||||
SELECT fts5_columntext(t1, -1) FROM t1('two');
|
||||
} {1 SQLITE_RANGE}
|
||||
do_catchsql_test 12.1.2 {
|
||||
SELECT fts5_columntext(t1, 3) FROM t1('two');
|
||||
} {1 SQLITE_RANGE}
|
||||
do_catchsql_test 12.1.2 {
|
||||
SELECT fts5_columntext(t1, 1) FROM t1('one AND two');
|
||||
} {0 two}
|
||||
|
||||
do_catchsql_test 12.2.1 {
|
||||
SELECT fts5_queryphrase(t1, -1) FROM t1('one AND two');
|
||||
} {1 SQLITE_RANGE}
|
||||
do_catchsql_test 12.2.2 {
|
||||
SELECT fts5_queryphrase(t1, 2) FROM t1('one AND two');
|
||||
} {1 SQLITE_RANGE}
|
||||
do_catchsql_test 12.2.3 {
|
||||
SELECT fts5_queryphrase(t1, 1) FROM t1('one AND two');
|
||||
} {0 {{1 2 1}}}
|
||||
|
||||
do_catchsql_test 12.3.1 {
|
||||
SELECT fts5_collist(t1, -1) FROM t1('one AND two');
|
||||
} {1 SQLITE_RANGE}
|
||||
do_catchsql_test 12.3.2 {
|
||||
SELECT fts5_collist(t1, 2) FROM t1('one AND two');
|
||||
} {1 SQLITE_RANGE}
|
||||
do_catchsql_test 12.3.3 {
|
||||
SELECT fts5_collist(t1, 1) FROM t1('one AND two');
|
||||
} {0 1}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -293,39 +293,4 @@ do_catchsql_test 7.2.5 {
|
||||
SELECT * FROM t1('abc') ORDER BY rank;
|
||||
} {1 {recursively defined fts5 content table}}
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Check that if the content table is a view, and that view contains an
|
||||
# error, a reasonable error message is returned if the user tries to
|
||||
# read from the view via the fts5 table.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 8.1 {
|
||||
CREATE VIEW a1 AS
|
||||
SELECT 1 AS r, text_value(1) AS t
|
||||
UNION ALL
|
||||
SELECT 2 AS r, text_value(2) AS t;
|
||||
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(t, content='a1', content_rowid='r');
|
||||
}
|
||||
|
||||
foreach {tn sql} {
|
||||
1 "SELECT * FROM t1"
|
||||
2 "INSERT INTO t1(t1) VALUES('rebuild')"
|
||||
3 "SELECT * FROM t1 WHERE rowid=1"
|
||||
} {
|
||||
do_catchsql_test 8.2.$tn $sql {1 {no such function: text_value}}
|
||||
}
|
||||
|
||||
proc text_value {i} {
|
||||
if {$i==1} { return "one" }
|
||||
if {$i==2} { return "two" }
|
||||
return "many"
|
||||
}
|
||||
db func text_value text_value
|
||||
|
||||
do_execsql_test 8.3.1 { SELECT * FROM t1 } {one two}
|
||||
do_execsql_test 8.3.2 { INSERT INTO t1(t1) VALUES('rebuild') }
|
||||
do_execsql_test 8.3.3 { SELECT * FROM t1 WHERE rowid=1 } {one}
|
||||
do_execsql_test 8.3.4 { SELECT rowid FROM t1('two') } {2}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -966,262 +966,6 @@ do_catchsql_test 6.2 {
|
||||
UPDATE t1 SET content=randomblob(500) WHERE t1;
|
||||
} {1 {constraint failed}}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_test 7.0 {
|
||||
sqlite3 db {}
|
||||
db deserialize [decode_hexdb {
|
||||
.open --hexdb
|
||||
| size 40960 pagesize 4096 filename crash-d8b4a99207c10b.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 62 00 0f 97 0f 40 ..........b....@
|
||||
| 112: 0e d5 0e 75 0e 18 0d c0 0d 66 0d 0f 0c a4 0c 44 ...u.....f.....D
|
||||
| 128: 0b ec 0b a7 0b 62 00 00 00 00 00 00 00 00 00 00 .....b..........
|
||||
| 2912: 00 00 43 0d 06 17 11 11 08 75 74 61 62 6c 65 74 ..C......utablet
|
||||
| 2928: 34 74 34 43 52 45 41 54 45 20 56 49 52 54 55 41 4t4CREATE VIRTUA
|
||||
| 2944: 4c 20 54 41 42 4c 45 20 74 34 20 55 53 49 4e 47 L TABLE t4 USING
|
||||
| 2960: 20 66 74 73 35 76 6f 63 61 62 28 27 74 32 27 2c fts5vocab('t2',
|
||||
| 2976: 20 27 72 6f 77 27 29 43 0c 06 17 11 11 08 75 74 'row')C......ut
|
||||
| 2992: 61 62 6c 65 74 33 74 33 43 52 45 41 54 45 20 56 ablet3t3CREATE V
|
||||
| 3008: 49 52 54 55 41 4c 20 54 41 42 4c 45 20 74 33 20 IRTUAL TABLE t3
|
||||
| 3024: 55 53 49 4e 47 20 66 74 73 35 76 6f 63 61 62 28 USING fts5vocab(
|
||||
| 3040: 27 74 31 27 2c 20 27 72 6f 77 27 29 56 0b 06 17 't1', 'row')V...
|
||||
| 3056: 1f 1f 01 7d 74 61 62 6c 65 74 32 5f 63 6f 6e 66 ....tablet2_conf
|
||||
| 3072: 69 67 74 32 5f 63 6f 6e 66 69 67 0a 43 52 45 41 igt2_config.CREA
|
||||
| 3088: 54 45 20 54 41 42 4c 45 20 27 74 32 5f 63 6f 6e TE TABLE 't2_con
|
||||
| 3104: 66 69 67 27 28 6b 20 50 52 49 4d 41 52 59 20 4b fig'(k PRIMARY K
|
||||
| 3120: 45 59 2c 20 76 29 20 57 49 54 48 4f 55 54 20 52 EY, v) WITHOUT R
|
||||
| 3136: 4f 57 49 44 5e 0a 07 17 21 21 01 81 07 74 61 62 OWID^...!!...tab
|
||||
| 3152: 6c 65 74 32 5f 63 6f 6e 74 65 6e 74 74 32 5f 63 let2_contentt2_c
|
||||
| 3168: 6f 6e 74 65 6e 74 09 43 52 45 41 54 45 20 54 41 ontent.CREATE TA
|
||||
| 3184: 42 4c 45 20 27 74 32 5f 63 6f 6e 74 65 6e 74 27 BLE 't2_content'
|
||||
| 3200: 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d (id INTEGER PRIM
|
||||
| 3216: 41 52 59 20 4b 45 59 2c 20 63 30 2c 20 63 31 2c ARY KEY, c0, c1,
|
||||
| 3232: 20 63 32 29 69 09 07 17 19 19 01 81 2d 74 61 62 c2)i.......-tab
|
||||
| 3248: 6c 65 74 32 5f 69 64 78 74 32 5f 69 64 78 08 43 let2_idxt2_idx.C
|
||||
| 3264: 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 32 5f REATE TABLE 't2_
|
||||
| 3280: 69 64 78 27 28 73 65 67 69 64 2c 20 74 65 72 6d idx'(segid, term
|
||||
| 3296: 2c 20 70 67 6e 6f 2c 20 50 52 49 4d 41 52 59 20 , pgno, PRIMARY
|
||||
| 3312: 4b 45 59 28 73 65 67 69 64 2c 20 74 65 72 6d 29 KEY(segid, term)
|
||||
| 3328: 29 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 55 ) WITHOUT ROWIDU
|
||||
| 3344: 08 07 17 1b 1b 01 81 01 74 61 62 6c 65 74 32 5f ........tablet2_
|
||||
| 3360: 64 61 74 61 74 32 5f 64 61 74 61 07 43 52 45 41 datat2_data.CREA
|
||||
| 3376: 54 45 20 54 41 42 4c 45 20 27 74 32 5f 64 61 74 TE TABLE 't2_dat
|
||||
| 3392: 61 27 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 a'(id INTEGER PR
|
||||
| 3408: 49 4d 41 52 59 20 4b 45 59 2c 20 62 6c 6f 63 6b IMARY KEY, block
|
||||
| 3424: 20 42 4c 4f 42 29 58 07 07 17 11 11 08 81 1d 74 BLOB)X........t
|
||||
| 3440: 61 62 6c 65 74 32 74 32 43 52 45 41 54 45 20 56 ablet2t2CREATE V
|
||||
| 3456: 49 52 54 55 41 4c 20 54 41 42 4c 45 20 74 32 20 IRTUAL TABLE t2
|
||||
| 3472: 55 53 49 4e 47 20 66 74 73 35 28 27 61 27 2c 5b USING fts5('a',[
|
||||
| 3488: 62 5d 2c 22 63 22 2c 64 65 74 61 69 6c 3d 6e 6f b],.c.,detail=no
|
||||
| 3504: 6e 65 2c 63 6f 6c 75 6d 6e 73 69 7a 65 3d 30 29 ne,columnsize=0)
|
||||
| 3520: 56 06 06 17 1f 1f 01 7d 74 61 62 6c 65 74 31 5f V.......tablet1_
|
||||
| 3536: 63 6f 6e 66 69 67 74 31 5f 63 6f 6e 66 69 67 06 configt1_config.
|
||||
| 3552: 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 CREATE TABLE 't1
|
||||
| 3568: 5f 63 6f 6e 66 69 67 27 28 6b 20 50 52 49 4d 41 _config'(k PRIMA
|
||||
| 3584: 52 59 20 4b 45 59 2c 20 76 29 20 57 49 54 48 4f RY KEY, v) WITHO
|
||||
| 3600: 55 54 20 52 4f 57 49 44 5b 05 07 17 21 21 01 81 UT ROWID[...!!..
|
||||
| 3616: 01 74 61 62 6c 65 74 31 5f 64 6f 63 73 69 7a 65 .tablet1_docsize
|
||||
| 3632: 74 31 5f 64 6f 63 73 69 7a 65 05 43 52 45 41 54 t1_docsize.CREAT
|
||||
| 3648: 45 20 54 41 42 4c 45 20 27 74 31 5f 64 6f 63 73 E TABLE 't1_docs
|
||||
| 3664: 69 7a 65 27 28 69 64 20 49 4e 54 45 47 45 52 20 ize'(id INTEGER
|
||||
| 3680: 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 73 7a 20 PRIMARY KEY, sz
|
||||
| 3696: 42 4c 4f 42 29 5e 04 07 17 21 21 01 81 07 74 61 BLOB)^...!!...ta
|
||||
| 3712: 62 6c 65 74 31 5f 63 6f 6e 74 65 6e 74 74 31 5f blet1_contentt1_
|
||||
| 3728: 63 6f 6e 74 65 6e 74 04 43 52 45 41 54 45 20 54 content.CREATE T
|
||||
| 3744: 41 42 4c 45 20 27 74 31 5f 63 6f 6e 74 65 6e 74 ABLE 't1_content
|
||||
| 3760: 27 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 '(id INTEGER PRI
|
||||
| 3776: 4d 41 52 59 20 4b 45 59 2c 20 63 30 2c 20 63 31 MARY KEY, c0, c1
|
||||
| 3792: 2c 20 63 32 29 69 03 07 17 19 19 01 81 2d 74 61 , c2)i.......-ta
|
||||
| 3808: 62 6c 65 74 31 5f 69 64 78 74 31 5f 69 64 78 03 blet1_idxt1_idx.
|
||||
| 3824: 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 CREATE TABLE 't1
|
||||
| 3840: 5f 69 64 78 27 28 73 65 67 69 64 2c 20 74 65 72 _idx'(segid, ter
|
||||
| 3856: 6d 2c 20 70 67 6e 6f 2c 20 50 52 49 4d 41 52 59 m, pgno, PRIMARY
|
||||
| 3872: 20 4b 45 59 28 73 65 67 69 64 2c 20 74 65 72 6d KEY(segid, term
|
||||
| 3888: 29 29 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 )) WITHOUT ROWID
|
||||
| 3904: 55 02 07 17 1b 1b 01 81 01 74 61 62 6c 65 74 31 U........tablet1
|
||||
| 3920: 5f 64 61 74 61 74 31 5f 64 61 74 61 02 43 52 45 _datat1_data.CRE
|
||||
| 3936: 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 64 61 ATE TABLE 't1_da
|
||||
| 3952: 74 61 27 28 69 64 20 49 4e 54 45 47 45 52 20 50 ta'(id INTEGER P
|
||||
| 3968: 52 49 4d 41 52 59 20 4b 45 59 2c 20 62 6c 6f 63 RIMARY KEY, bloc
|
||||
| 3984: 6b 20 42 4c 4f 42 29 67 01 07 17 11 11 08 81 3b k BLOB)g.......;
|
||||
| 4000: 74 61 62 6c 65 74 31 74 31 43 52 45 41 54 45 20 tablet1t1CREATE
|
||||
| 4016: 56 49 52 54 55 41 4c 20 54 41 42 4c 45 20 74 31 VIRTUAL TABLE t1
|
||||
| 4032: 20 55 53 49 4e 47 20 66 74 73 35 28 61 2c 62 20 USING fts5(a,b
|
||||
| 4048: 75 6e 69 6e 64 65 78 65 64 2c 63 2c 74 6f 6b 65 unindexed,c,toke
|
||||
| 4064: 6e 69 7a 65 3d 22 70 6f 72 74 65 72 20 61 73 63 nize=.porter asc
|
||||
| 4080: 69 69 22 2c 74 6f 6b 65 6e 64 61 74 61 3d 31 29 ii.,tokendata=1)
|
||||
| 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 01 02 03 01 01 69 02 .....h........i.
|
||||
| 4000: 08 04 01 02 04 04 09 09 37 84 80 80 80 7f f1 03 ........7.......
|
||||
| 4016: 00 74 00 00 00 2e 02 30 61 01 02 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 06 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 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 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: 01 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 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 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-d8b4a99207c10b.db
|
||||
}]} {}
|
||||
|
||||
do_catchsql_test 7.1 {
|
||||
SELECT snippet(t1, -1, '.', '..', '[', ']'),
|
||||
highlight(t1, 2, '[', ']')
|
||||
FROM t1('g + h')
|
||||
WHERE rank MATCH 'bm25(1.0, 1.0)' ORDER BY rank;
|
||||
} {1 {database disk image is malformed}}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_test 8.0 {
|
||||
sqlite3 db {}
|
||||
db deserialize [decode_hexdb {
|
||||
.open --hexdb
|
||||
| size 20480 pagesize 4096 filename crash-d57c01958e48ab.db
|
||||
| page 1 offset 0
|
||||
| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
|
||||
| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 05 .....@ ........
|
||||
| 32: 00 00 00 00 00 00 00 00 00 00 00 05 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 05 0e 10 00 0f 97 0f 40 ...............@
|
||||
| 112: 0e d5 0e 68 0e 10 01 00 00 00 00 00 00 00 00 00 ...h............
|
||||
| 3600: 56 05 06 17 1f 1f 01 7d 74 61 62 6c 65 74 31 5f V.......tablet1_
|
||||
| 3616: 63 6f 6e 66 69 67 74 31 5f 63 6f 6e 66 69 67 05 configt1_config.
|
||||
| 3632: 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 CREATE TABLE 't1
|
||||
| 3648: 5f 63 6f 6e 66 69 67 27 28 6b 20 50 52 49 4d 41 _config'(k PRIMA
|
||||
| 3664: 52 59 20 4b 45 59 2c 20 76 29 20 57 49 54 48 4f RY KEY, v) WITHO
|
||||
| 3680: 55 54 20 52 4f 57 49 44 6b 04 07 17 21 21 01 81 UT ROWIDk...!!..
|
||||
| 3696: 21 74 61 62 6c 65 74 31 5f 64 6f 63 73 69 7a 65 !tablet1_docsize
|
||||
| 3712: 74 31 5f 64 6f 63 73 69 7a 65 04 43 52 45 41 54 t1_docsize.CREAT
|
||||
| 3728: 45 20 54 41 42 4c 45 20 27 74 31 5f 64 6f 63 73 E TABLE 't1_docs
|
||||
| 3744: 69 7a 65 27 28 69 64 20 49 4e 54 45 47 45 52 20 ize'(id INTEGER
|
||||
| 3760: 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 73 7a 20 PRIMARY KEY, sz
|
||||
| 3776: 42 4c 4f 42 2c 20 6f 72 69 67 69 6e 20 49 4e 54 BLOB, origin INT
|
||||
| 3792: 45 47 45 52 29 69 03 07 17 19 19 01 81 2d 74 61 EGER)i.......-ta
|
||||
| 3808: 62 6c 65 74 31 5f 69 64 78 74 31 5f 69 64 78 03 blet1_idxt1_idx.
|
||||
| 3824: 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 CREATE TABLE 't1
|
||||
| 3840: 5f 69 64 78 27 28 73 65 67 69 64 2c 20 74 65 72 _idx'(segid, ter
|
||||
| 3856: 6d 2c 20 70 67 6e 6f 2c 20 50 52 49 4d 41 52 59 m, pgno, PRIMARY
|
||||
| 3872: 20 4b 45 59 28 73 65 67 69 64 2c 20 74 65 72 6d KEY(segid, term
|
||||
| 3888: 29 29 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 )) WITHOUT ROWID
|
||||
| 3904: 55 02 07 17 1b 1b 01 81 01 74 61 62 6c 65 74 31 U........tablet1
|
||||
| 3920: 5f 64 61 74 61 74 31 5f 64 61 74 61 02 43 52 45 _datat1_data.CRE
|
||||
| 3936: 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 64 61 ATE TABLE 't1_da
|
||||
| 3952: 74 61 27 28 69 64 20 49 4e 54 45 47 45 52 20 50 ta'(id INTEGER P
|
||||
| 3968: 52 49 4d 41 52 59 20 4b 45 59 2c 20 62 6c 6f 63 RIMARY KEY, bloc
|
||||
| 3984: 6b 20 42 4c 4f 42 29 67 01 07 17 11 11 08 81 3b k BLOB)g.......;
|
||||
| 4000: 74 61 62 6c 65 74 31 74 31 43 52 45 41 54 45 20 tablet1t1CREATE
|
||||
| 4016: 56 49 52 54 55 41 4c 20 54 41 42 4c 45 20 74 31 VIRTUAL TABLE t1
|
||||
| 4032: 20 55 53 49 4e 47 20 66 74 73 35 28 61 2c 20 62 USING fts5(a, b
|
||||
| 4048: 2c 20 63 6f 6e 74 65 6e 74 3d 27 27 2c 20 63 6f , content='', co
|
||||
| 4064: 6e 74 65 6e 74 6c 65 73 73 5f 64 65 6c 65 74 65 ntentless_delete
|
||||
| 4080: 3d 31 2c 20 74 6f 6b 65 6e 64 61 74 61 3d 31 29 =1, tokendata=1)
|
||||
| page 2 offset 4096
|
||||
| 0: 0d 0f eb 00 03 0e 17 00 0f e2 0e 17 0e 31 00 00 .............1..
|
||||
| 16: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
| 3600: 00 00 00 00 00 00 00 18 0a 03 00 36 00 00 00 00 ...........6....
|
||||
| 3616: ff 00 00 01 01 01 01 00 01 01 01 01 01 01 00 00 ................
|
||||
| 3632: 07 83 29 84 80 80 80 80 01 04 00 86 56 00 00 01 ..).........V...
|
||||
| 3648: 96 04 30 61 61 61 01 02 02 01 04 02 04 01 08 02 ..0aaa..........
|
||||
| 3664: 04 04 04 01 10 02 04 04 04 04 04 04 04 01 20 02 .............. .
|
||||
| 3680: 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 01 ................
|
||||
| 3696: 40 02 04 04 04 04 04 04 04 04 04 04 04 04 04 04 @...............
|
||||
| 3712: 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 ................
|
||||
| 3728: 04 01 81 00 02 04 04 04 04 04 04 04 04 04 04 04 ................
|
||||
| 3744: 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 ................
|
||||
| 3760: 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 ................
|
||||
| 3776: 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 ................
|
||||
| 3792: 04 04 04 04 02 02 62 63 01 06 01 01 02 01 03 62 ......bc.......b
|
||||
| 3808: 62 62 02 02 03 01 04 03 06 01 08 03 06 06 06 01 bb..............
|
||||
| 3824: 10 03 06 06 06 06 06 06 06 01 20 03 06 06 06 06 .......... .....
|
||||
| 3840: 06 06 06 06 06 06 06 06 06 06 06 01 40 03 06 06 ............@...
|
||||
| 3856: 06 06 06 06 06 06 06 06 06 06 06 06 06 06 06 06 ................
|
||||
| 3872: 06 06 06 06 06 06 06 06 06 06 16 06 06 02 02 63 ...............c
|
||||
| 3888: 64 02 06 01 01 02 01 03 63 63 63 03 02 05 01 04 d.......ccc.....
|
||||
| 3904: 05 0a 01 08 05 0a 0a 0a 01 10 05 0a 0a 0a 0a 0a ................
|
||||
| 3920: 0a 0a 01 20 05 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a ... ............
|
||||
| 3936: 0a 0a 0a 0a 02 02 64 65 03 06 01 01 02 01 03 64 ......de.......d
|
||||
| 3952: 64 64 04 02 09 01 04 09 12 01 08 09 12 12 12 01 dd..............
|
||||
| 3968: 10 09 12 12 12 12 12 12 12 02 02 65 66 04 06 01 ...........ef...
|
||||
| 3984: 01 02 01 03 65 65 65 05 02 11 01 04 11 22 01 08 ....eee.........
|
||||
| 4000: 11 22 22 22 02 02 66 67 05 06 01 01 02 01 03 66 ......fg.......f
|
||||
| 4016: 56 66 06 02 21 01 04 21 42 02 02 67 68 06 06 01 Vf..!..!B..gh...
|
||||
| 4032: 01 02 cb 03 67 67 67 07 02 41 02 02 68 69 07 06 ....ggg..A..hi..
|
||||
| 4048: 01 01 02 04 81 13 09 50 09 2e 09 1c 09 12 09 0c .......P........
|
||||
| 4064: 09 08 07 01 03 00 14 07 81 77 07 00 00 00 15 22 .........w......
|
||||
| 4080: 00 00 00 00 ff 00 00 01 00 00 00 00 00 00 05 0c ................
|
||||
| page 3 offset 8192
|
||||
| 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 4 offset 12288
|
||||
| 0: 0d 00 00 00 07 0f c8 00 0f f8 0f f0 0f e8 0f e0 ................
|
||||
| 16: 0f d8 0f d0 0f c8 00 00 00 00 00 00 00 00 00 00 ................
|
||||
| 4032: 00 00 00 00 00 00 00 00 06 07 04 00 10 09 7f 01 ................
|
||||
| 4048: 06 06 04 00 10 09 3f 01 06 05 04 00 10 09 1f 01 ......?.........
|
||||
| 4064: 06 04 04 00 10 09 0f 01 06 03 04 00 10 09 07 01 ................
|
||||
| 4080: 06 02 04 00 10 09 03 01 06 01 04 00 10 09 01 01 ................
|
||||
| page 5 offset 16384
|
||||
| 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-d57c01958e48ab.db
|
||||
}]} {}
|
||||
|
||||
do_catchsql_test 8.1 {
|
||||
SELECT rowid FROM t1('a* NOT ý') ;
|
||||
} {0 {1 2 3 4 5 6 7}}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_test 9.0 {
|
||||
@@ -1453,4 +1197,3 @@ do_catchsql_test 9.2 {
|
||||
|
||||
sqlite3_fts5_may_be_corrupt 0
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ foreach_detail_mode $testprefix {
|
||||
|
||||
} ;# foreach_detail_mode...
|
||||
|
||||
|
||||
do_execsql_test 4.0 {
|
||||
CREATE VIRTUAL TABLE x2 USING fts5(a);
|
||||
INSERT INTO x2(x2, rank) VALUES('crisismerge', 2);
|
||||
@@ -79,18 +80,5 @@ do_faultsim_test 4 -faults oom-* -prep {
|
||||
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
|
||||
}
|
||||
|
||||
set TMPDBERROR {1 {unable to open a temporary database file for storing temporary tables}}
|
||||
|
||||
do_faultsim_test 5 -faults oom-t* -prep {
|
||||
faultsim_restore_and_reopen
|
||||
execsql { PRAGMA temp_store = memory }
|
||||
} -body {
|
||||
execsql { PRAGMA integrity_check }
|
||||
} -test {
|
||||
if {[string match {*error code=7*} $testresult]==0} {
|
||||
faultsim_test_result {0 ok} {1 SQLITE_NOMEM} $::TMPDBERROR
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
# 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 fts5faultG
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
set ::testprefix fts5faultH
|
||||
|
||||
sqlite3_fts5_register_origintext db
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(
|
||||
x, tokenize="origintext unicode61", tokendata=1
|
||||
);
|
||||
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES('oNe tWo thRee');
|
||||
INSERT INTO t1 VALUES('One Two Three');
|
||||
INSERT INTO t1 VALUES('onE twO threE');
|
||||
COMMIT;
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES('one two three');
|
||||
INSERT INTO t1 VALUES('one two three');
|
||||
INSERT INTO t1 VALUES('one two three');
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
do_faultsim_test 1 -faults oom* -prep {
|
||||
} -body {
|
||||
execsql {
|
||||
SELECT rowid FROM t1('three');
|
||||
}
|
||||
} -test {
|
||||
faultsim_integrity_check
|
||||
faultsim_test_result {0 {1 2 3 4 5 6}}
|
||||
}
|
||||
|
||||
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 2.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(
|
||||
x, tokenize="origintext unicode61", tokendata=1
|
||||
);
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
|
||||
|
||||
BEGIN;
|
||||
INSERT INTO t1(rowid, x) VALUES(10, 'aaa bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(12, 'bbb bbb bbb');
|
||||
INSERT INTO t1(rowid, x) VALUES(13, 'bbb bbb bbb');
|
||||
INSERT INTO t1(rowid, x) VALUES(14, 'bbb BBB bbb');
|
||||
INSERT INTO t1(rowid, x) VALUES(15, 'bbb bbb bbb');
|
||||
INSERT INTO t1(rowid, x) VALUES(16, 'bbb bbb bbb');
|
||||
INSERT INTO t1(rowid, x) VALUES(17, 'bbb bbb bbb');
|
||||
INSERT INTO t1(rowid, x) VALUES(18, 'bbb bbb bbb');
|
||||
INSERT INTO t1(rowid, x) VALUES(19, 'bbb bbb bbb');
|
||||
INSERT INTO t1(rowid, x) VALUES(20, 'bbb bbb bbb');
|
||||
INSERT INTO t1(rowid, x) VALUES(21, 'bbb bbb bbb');
|
||||
INSERT INTO t1(rowid, x) VALUES(22, 'bbb bbb bbb');
|
||||
INSERT INTO t1(rowid, x) VALUES(23, 'bbb bbb bbb');
|
||||
INSERT INTO t1(rowid, x) VALUES(24, 'aaa bbb BBB');
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
do_faultsim_test 2 -faults oom* -prep {
|
||||
} -body {
|
||||
execsql {
|
||||
SELECT rowid FROM t1('BBB AND AAA');
|
||||
}
|
||||
} -test {
|
||||
faultsim_integrity_check
|
||||
faultsim_test_result {0 {10 24}}
|
||||
}
|
||||
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 3.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(
|
||||
x, tokenize="origintext unicode61", tokendata=1
|
||||
);
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
|
||||
|
||||
INSERT INTO t1(rowid, x) VALUES(9, 'bbb Bbb BBB');
|
||||
BEGIN;
|
||||
INSERT INTO t1(rowid, x) VALUES(10, 'aaa bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(11, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(12, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(13, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(14, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(15, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(16, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(17, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(18, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(19, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(20, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(21, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(22, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(23, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(24, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(25, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(26, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(27, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(28, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(29, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(30, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(31, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(32, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(33, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(34, 'bbb Bbb BBB');
|
||||
INSERT INTO t1(rowid, x) VALUES(35, 'aaa bbb BBB');
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
do_faultsim_test 3.1 -faults oom* -prep {
|
||||
} -body {
|
||||
execsql {
|
||||
SELECT rowid FROM t1('BBB AND AAA');
|
||||
}
|
||||
} -test {
|
||||
faultsim_integrity_check
|
||||
faultsim_test_result {0 {10 35}}
|
||||
}
|
||||
do_faultsim_test 3.2 -faults oom* -prep {
|
||||
} -body {
|
||||
execsql {
|
||||
SELECT count(*) FROM t1('BBB');
|
||||
}
|
||||
} -test {
|
||||
faultsim_integrity_check
|
||||
faultsim_test_result {0 27}
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
@@ -355,30 +355,4 @@ do_execsql_test 11.4 {
|
||||
PRAGMA integrity_check(t2);
|
||||
} {ok}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
reset_db
|
||||
|
||||
do_execsql_test 12.1 {
|
||||
CREATE VIRTUAL TABLE x1 USING fts5(a, b);
|
||||
INSERT INTO x1 VALUES('one', 'two');
|
||||
INSERT INTO x1 VALUES('three', 'four');
|
||||
INSERT INTO x1 VALUES('five', 'six');
|
||||
}
|
||||
|
||||
do_execsql_test 12.2 {
|
||||
PRAGMA integrity_check
|
||||
} {ok}
|
||||
|
||||
db close
|
||||
sqlite3 db test.db -readonly 1
|
||||
|
||||
explain_i {
|
||||
PRAGMA integrity_check
|
||||
}
|
||||
do_execsql_test 12.3 {
|
||||
PRAGMA integrity_check
|
||||
} {ok}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -1,297 +0,0 @@
|
||||
# 2014 Jan 08
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# Tests focused on phrase queries.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5origintext
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
foreach_detail_mode $testprefix {
|
||||
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(
|
||||
x, tokenize="origintext unicode61", detail=%DETAIL%
|
||||
);
|
||||
CREATE VIRTUAL TABLE vocab USING fts5vocab(ft, instance);
|
||||
}
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
INSERT INTO ft VALUES('Hello world');
|
||||
}
|
||||
|
||||
do_execsql_test 1.2 {
|
||||
INSERT INTO ft(ft) VALUES('integrity-check');
|
||||
}
|
||||
|
||||
proc b {x} { string map [list "\0" "."] $x }
|
||||
db func b b
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
select b(term) from vocab;
|
||||
} {
|
||||
hello.Hello
|
||||
world
|
||||
}
|
||||
|
||||
do_execsql_test 1.4 {
|
||||
SELECT rowid FROM ft('Hello');
|
||||
} {1}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
|
||||
# Return a random integer between 0 and n-1.
|
||||
#
|
||||
proc random {n} {
|
||||
expr {abs(int(rand()*$n))}
|
||||
}
|
||||
|
||||
proc select_one {list} {
|
||||
set n [llength $list]
|
||||
lindex $list [random $n]
|
||||
}
|
||||
|
||||
proc term {} {
|
||||
set first_letter {
|
||||
a b c d e f g h i j k l m n o p q r s t u v w x y z
|
||||
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
|
||||
}
|
||||
|
||||
set term [select_one $first_letter]
|
||||
append term [random 100]
|
||||
}
|
||||
|
||||
proc document {} {
|
||||
set nTerm [expr [random 5] + 5]
|
||||
set doc ""
|
||||
for {set ii 0} {$ii < $nTerm} {incr ii} {
|
||||
lappend doc [term]
|
||||
}
|
||||
set doc
|
||||
}
|
||||
db func document document
|
||||
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 2.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(
|
||||
x, tokenize="origintext unicode61", detail=%DETAIL%
|
||||
);
|
||||
INSERT INTO ft(ft, rank) VALUES('pgsz', 128);
|
||||
CREATE VIRTUAL TABLE vocab USING fts5vocab(ft, instance);
|
||||
}
|
||||
|
||||
do_test 2.1 {
|
||||
for {set ii 0} {$ii < 500} {incr ii} {
|
||||
execsql { INSERT INTO ft VALUES( document() ) }
|
||||
}
|
||||
} {}
|
||||
|
||||
do_execsql_test 2.2 {
|
||||
INSERT INTO ft(ft) VALUES('integrity-check');
|
||||
}
|
||||
|
||||
do_execsql_test 2.3 {
|
||||
INSERT INTO ft(ft, rank) VALUES('merge', 16);
|
||||
}
|
||||
|
||||
do_execsql_test 2.4 {
|
||||
INSERT INTO ft(ft) VALUES('integrity-check');
|
||||
}
|
||||
|
||||
do_execsql_test 2.5 {
|
||||
INSERT INTO ft(ft) VALUES('optimize');
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 3.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(
|
||||
x, tokenize="origintext unicode61", detail=%DETAIL%
|
||||
);
|
||||
CREATE VIRTUAL TABLE vocab USING fts5vocab(ft, instance);
|
||||
|
||||
INSERT INTO ft(rowid, x) VALUES(1, 'hello');
|
||||
INSERT INTO ft(rowid, x) VALUES(2, 'Hello');
|
||||
INSERT INTO ft(rowid, x) VALUES(3, 'HELLO');
|
||||
}
|
||||
|
||||
#proc b {x} { string map [list "\0" "."] $x }
|
||||
#db func b b
|
||||
#execsql_pp { SELECT b(term) FROM vocab }
|
||||
|
||||
do_execsql_test 3.1.1 { SELECT rowid FROM ft('hello') } 1
|
||||
do_execsql_test 3.1.2 { SELECT rowid FROM ft('Hello') } 2
|
||||
do_execsql_test 3.1.3 { SELECT rowid FROM ft('HELLO') } 3
|
||||
|
||||
do_execsql_test 3.2 {
|
||||
CREATE VIRTUAL TABLE ft2 USING fts5(x,
|
||||
tokenize="origintext unicode61",
|
||||
tokendata=1,
|
||||
detail=%DETAIL%
|
||||
);
|
||||
CREATE VIRTUAL TABLE vocab2 USING fts5vocab(ft2, instance);
|
||||
|
||||
INSERT INTO ft2(rowid, x) VALUES(1, 'hello');
|
||||
INSERT INTO ft2(rowid, x) VALUES(2, 'Hello');
|
||||
INSERT INTO ft2(rowid, x) VALUES(3, 'HELLO');
|
||||
|
||||
INSERT INTO ft2(rowid, x) VALUES(10, 'helloooo');
|
||||
}
|
||||
|
||||
#proc b {x} { string map [list "\0" "."] $x }
|
||||
#db func b b
|
||||
#execsql_pp { SELECT b(term) FROM vocab }
|
||||
|
||||
do_execsql_test 3.3.1 { SELECT rowid FROM ft2('hello') } {1 2 3}
|
||||
do_execsql_test 3.3.2 { SELECT rowid FROM ft2('Hello') } {1 2 3}
|
||||
do_execsql_test 3.3.3 { SELECT rowid FROM ft2('HELLO') } {1 2 3}
|
||||
|
||||
do_execsql_test 3.3.4 { SELECT rowid FROM ft2('hello*') } {1 2 3 10}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
proc querytoken {cmd iPhrase iToken} {
|
||||
set txt [$cmd xQueryToken $iPhrase $iToken]
|
||||
string map [list "\0" "."] $txt
|
||||
}
|
||||
sqlite3_fts5_create_function db querytoken querytoken
|
||||
|
||||
do_execsql_test 4.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(
|
||||
x, tokenize='origintext unicode61', tokendata=1, detail=%DETAIL%
|
||||
);
|
||||
INSERT INTO ft VALUES('one two three four');
|
||||
}
|
||||
|
||||
do_execsql_test 4.1 {
|
||||
SELECT rowid, querytoken(ft, 0, 0) FROM ft('TwO')
|
||||
} {1 two.TwO}
|
||||
do_execsql_test 4.2 {
|
||||
SELECT rowid, querytoken(ft, 0, 0) FROM ft('one TWO ThreE')
|
||||
} {1 one}
|
||||
do_execsql_test 4.3 {
|
||||
SELECT rowid, querytoken(ft, 1, 0) FROM ft('one TWO ThreE')
|
||||
} {1 two.TWO}
|
||||
|
||||
if {"%DETAIL%"=="full"} {
|
||||
# Phrase queries are only supported for detail=full.
|
||||
#
|
||||
do_execsql_test 4.4 {
|
||||
SELECT rowid, querytoken(ft, 0, 2) FROM ft('"one TWO ThreE"')
|
||||
} {1 three.ThreE}
|
||||
do_catchsql_test 4.5 {
|
||||
SELECT rowid, querytoken(ft, 0, 3) FROM ft('"one TWO ThreE"')
|
||||
} {1 SQLITE_RANGE}
|
||||
do_catchsql_test 4.6 {
|
||||
SELECT rowid, querytoken(ft, 1, 0) FROM ft('"one TWO ThreE"')
|
||||
} {1 SQLITE_RANGE}
|
||||
do_catchsql_test 4.7 {
|
||||
SELECT rowid, querytoken(ft, -1, 0) FROM ft('"one TWO ThreE"')
|
||||
} {1 SQLITE_RANGE}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
proc insttoken {cmd iIdx iToken} {
|
||||
set txt [$cmd xInstToken $iIdx $iToken]
|
||||
string map [list "\0" "."] $txt
|
||||
}
|
||||
sqlite3_fts5_create_function db insttoken insttoken
|
||||
fts5_aux_test_functions db
|
||||
|
||||
do_execsql_test 5.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(
|
||||
x, tokenize='origintext unicode61', tokendata=1, detail=%DETAIL%
|
||||
);
|
||||
INSERT INTO ft VALUES('one ONE One oNe oNE one');
|
||||
}
|
||||
|
||||
do_execsql_test 5.1 {
|
||||
SELECT insttoken(ft, 0, 0),
|
||||
insttoken(ft, 1, 0),
|
||||
insttoken(ft, 2, 0),
|
||||
insttoken(ft, 3, 0),
|
||||
insttoken(ft, 4, 0),
|
||||
insttoken(ft, 5, 0)
|
||||
FROM ft('one');
|
||||
} {
|
||||
one one.ONE one.One one.oNe one.oNE one
|
||||
}
|
||||
|
||||
do_execsql_test 5.2 {
|
||||
SELECT insttoken(ft, 1, 0) FROM ft('one');
|
||||
} {
|
||||
one.ONE
|
||||
}
|
||||
|
||||
do_execsql_test 5.3 {
|
||||
SELECT fts5_test_poslist(ft) FROM ft('one');
|
||||
} {
|
||||
{0.0.0 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test the xInstToken() API with:
|
||||
#
|
||||
# * a non tokendata=1 table.
|
||||
# * prefix queries.
|
||||
#
|
||||
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%
|
||||
);
|
||||
|
||||
INSERT INTO ft VALUES('One Two', 'Three two');
|
||||
INSERT INTO ft VALUES('three Three', 'one One');
|
||||
}
|
||||
proc tokens {cmd} {
|
||||
set ret [list]
|
||||
for {set iTok 0} {$iTok < [$cmd xInstCount]} {incr iTok} {
|
||||
set txt [$cmd xInstToken $iTok 0]
|
||||
set txt [string map [list "\0" "."] $txt]
|
||||
lappend ret $txt
|
||||
}
|
||||
set ret
|
||||
}
|
||||
sqlite3_fts5_create_function db tokens tokens
|
||||
|
||||
do_execsql_test 6.1 {
|
||||
SELECT rowid, tokens(ft) FROM ft('One');
|
||||
} {1 one.One 2 one.One}
|
||||
|
||||
do_execsql_test 6.2 {
|
||||
SELECT rowid, tokens(ft) FROM ft('on*');
|
||||
} {1 {{}} 2 {{} {}}}
|
||||
|
||||
do_execsql_test 6.3 {
|
||||
SELECT rowid, tokens(ft) FROM ft('Three*');
|
||||
} {1 {{}} 2 {{}}}
|
||||
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
# 2014 Jan 08
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# Tests focused on phrase queries.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5origintext2
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(
|
||||
x, tokenize="origintext unicode61", tokendata=1
|
||||
);
|
||||
}
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
BEGIN;
|
||||
INSERT INTO ft VALUES('Hello');
|
||||
INSERT INTO ft VALUES('hello');
|
||||
INSERT INTO ft VALUES('HELLO');
|
||||
INSERT INTO ft VALUES('today');
|
||||
INSERT INTO ft VALUES('today');
|
||||
INSERT INTO ft VALUES('today');
|
||||
INSERT INTO ft VALUES('World');
|
||||
INSERT INTO ft VALUES('world');
|
||||
INSERT INTO ft VALUES('WORLD');
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
do_execsql_test 1.2 { SELECT rowid FROM ft('hello'); } {1 2 3}
|
||||
do_execsql_test 1.3 { SELECT rowid FROM ft('today'); } {4 5 6}
|
||||
do_execsql_test 1.4 { SELECT rowid FROM ft('world'); } {7 8 9}
|
||||
|
||||
do_execsql_test 1.5 {
|
||||
SELECT count(*) FROM ft_data
|
||||
} 3
|
||||
|
||||
do_execsql_test 1.6 {
|
||||
DELETE FROM ft;
|
||||
INSERT INTO ft(ft, rank) VALUES('pgsz', 64);
|
||||
BEGIN;
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100
|
||||
)
|
||||
INSERT INTO ft SELECT 'Hello Hello Hello Hello Hello Hello Hello' FROM s;
|
||||
INSERT INTO ft VALUES ('hELLO hELLO hELLO');
|
||||
INSERT INTO ft VALUES('today today today today today today today');
|
||||
INSERT INTO ft VALUES('today today today today today today today');
|
||||
INSERT INTO ft VALUES('today today today today today today today');
|
||||
INSERT INTO ft VALUES('today today today today today today today');
|
||||
INSERT INTO ft VALUES('today today today today today today today');
|
||||
INSERT INTO ft VALUES('today today today today today today today');
|
||||
INSERT INTO ft VALUES('World World World World World World World');
|
||||
INSERT INTO ft VALUES('world world world world world world world');
|
||||
INSERT INTO ft VALUES('WORLD WORLD WORLD WORLD WORLD WORLD WORLD');
|
||||
INSERT INTO ft VALUES('World World World World World World World');
|
||||
INSERT INTO ft VALUES('world world world world world world world');
|
||||
INSERT INTO ft VALUES('WORLD WORLD WORLD WORLD WORLD WORLD WORLD');
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
do_execsql_test 1.7 {
|
||||
SELECT count(*) FROM ft_data;
|
||||
} 23
|
||||
|
||||
do_execsql_test 1.8 { SELECT rowid FROM ft('hello') WHERE rowid>100; } {101}
|
||||
|
||||
do_execsql_test 1.9 {
|
||||
DELETE FROM ft;
|
||||
INSERT INTO ft(ft) VALUES('optimize');
|
||||
SELECT count(*) FROM ft_data;
|
||||
} {2}
|
||||
do_execsql_test 1.10 {
|
||||
BEGIN;
|
||||
INSERT INTO ft VALUES('Hello');
|
||||
INSERT INTO ft VALUES('hello');
|
||||
INSERT INTO ft VALUES('HELLO');
|
||||
INSERT INTO ft VALUES('today');
|
||||
INSERT INTO ft VALUES('today');
|
||||
INSERT INTO ft VALUES('today');
|
||||
INSERT INTO ft VALUES('World');
|
||||
INSERT INTO ft VALUES('world');
|
||||
INSERT INTO ft VALUES('WORLD');
|
||||
}
|
||||
|
||||
do_execsql_test 1.11 { SELECT rowid FROM ft('hello'); } {1 2 3}
|
||||
do_execsql_test 1.12 { SELECT rowid FROM ft('today'); } {4 5 6}
|
||||
do_execsql_test 1.13 { SELECT rowid FROM ft('world'); } {7 8 9}
|
||||
do_execsql_test 1.14 { SELECT rowid FROM ft('hello') ORDER BY rank; } {1 2 3}
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
reset_db
|
||||
sqlite3_fts5_register_origintext db
|
||||
proc tokens {cmd} {
|
||||
set ret [list]
|
||||
for {set iTok 0} {$iTok < [$cmd xInstCount]} {incr iTok} {
|
||||
set txt [$cmd xInstToken $iTok 0]
|
||||
set txt [string map [list "\0" "."] $txt]
|
||||
lappend ret $txt
|
||||
}
|
||||
set ret
|
||||
}
|
||||
sqlite3_fts5_create_function db tokens tokens
|
||||
|
||||
do_execsql_test 2.0 {
|
||||
CREATE VIRTUAL TABLE x1 USING fts5(
|
||||
v, tokenize="origintext unicode61", tokendata=1, detail=none
|
||||
);
|
||||
|
||||
INSERT INTO x1 VALUES('xxx Xxx XXX yyy YYY yyy');
|
||||
INSERT INTO x1 VALUES('xxx yyy xxx yyy yyy yyy');
|
||||
}
|
||||
|
||||
do_execsql_test 2.1 {
|
||||
SELECT tokens(x1) FROM x1('xxx');
|
||||
} {
|
||||
{xxx xxx.Xxx xxx.XXX} {xxx xxx}
|
||||
}
|
||||
|
||||
do_execsql_test 2.2 {
|
||||
UPDATE x1_content SET c0 = 'xxx xxX xxx yyy yyy yyy' WHERE id=1;
|
||||
}
|
||||
|
||||
do_execsql_test 2.3 {
|
||||
SELECT tokens(x1) FROM x1('xxx');
|
||||
} {
|
||||
{xxx {} xxx} {xxx xxx}
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
# 2023 November 22
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# Tests focused on phrase queries.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5origintext3
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
foreach_detail_mode $testprefix {
|
||||
reset_db
|
||||
|
||||
sqlite3_fts5_register_origintext db
|
||||
fts5_aux_test_functions db
|
||||
proc insttoken {cmd iIdx iToken} {
|
||||
set txt [$cmd xInstToken $iIdx $iToken]
|
||||
string map [list "\0" "."] $txt
|
||||
}
|
||||
sqlite3_fts5_create_function db insttoken insttoken
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(
|
||||
x, tokenize="origintext unicode61", tokendata=1, detail=%DETAIL%
|
||||
);
|
||||
}
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
INSERT INTO ft VALUES('Hello world HELLO WORLD hello');
|
||||
}
|
||||
|
||||
do_execsql_test 1.2 {
|
||||
SELECT fts5_test_poslist(ft) FROM ft('hello');
|
||||
} {{0.0.0 0.0.2 0.0.4}}
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
SELECT
|
||||
insttoken(ft, 0, 0),
|
||||
insttoken(ft, 1, 0),
|
||||
insttoken(ft, 2, 0)
|
||||
FROM ft('hello');
|
||||
} {hello.Hello hello.HELLO hello}
|
||||
|
||||
do_execsql_test 1.4 {
|
||||
SELECT
|
||||
insttoken(ft, 0, 0),
|
||||
insttoken(ft, 1, 0),
|
||||
insttoken(ft, 2, 0)
|
||||
FROM ft('hello') ORDER BY rank;
|
||||
} {hello.Hello hello.HELLO hello}
|
||||
|
||||
do_execsql_test 1.5 {
|
||||
CREATE VIRTUAL TABLE ft2 USING fts5(
|
||||
x, tokenize="origintext unicode61", tokendata=1, detail=%DETAIL%
|
||||
);
|
||||
INSERT INTO ft2(rowid, x) VALUES(1, 'ONE one two three ONE');
|
||||
INSERT INTO ft2(rowid, x) VALUES(2, 'TWO one two three TWO');
|
||||
INSERT INTO ft2(rowid, x) VALUES(3, 'THREE one two three THREE');
|
||||
}
|
||||
|
||||
do_execsql_test 1.6 {
|
||||
SELECT insttoken(ft2, 0, 0), rowid FROM ft2('three') ORDER BY rank;
|
||||
} {three.THREE 3 three 1 three 2}
|
||||
|
||||
do_execsql_test 1.7 {
|
||||
INSERT INTO ft2(rowid, x) VALUES(10, 'aaa bbb BBB');
|
||||
INSERT INTO ft2(rowid, x) VALUES(12, 'bbb bbb bbb');
|
||||
INSERT INTO ft2(rowid, x) VALUES(13, 'bbb bbb bbb');
|
||||
INSERT INTO ft2(rowid, x) VALUES(14, 'bbb BBB bbb');
|
||||
INSERT INTO ft2(rowid, x) VALUES(15, 'bbb bbb bbb');
|
||||
INSERT INTO ft2(rowid, x) VALUES(16, 'bbb bbb bbb');
|
||||
INSERT INTO ft2(rowid, x) VALUES(17, 'bbb bbb bbb');
|
||||
INSERT INTO ft2(rowid, x) VALUES(18, 'bbb bbb bbb');
|
||||
INSERT INTO ft2(rowid, x) VALUES(19, 'bbb bbb bbb');
|
||||
INSERT INTO ft2(rowid, x) VALUES(20, 'bbb bbb bbb');
|
||||
INSERT INTO ft2(rowid, x) VALUES(21, 'bbb bbb bbb');
|
||||
INSERT INTO ft2(rowid, x) VALUES(22, 'bbb bbb bbb');
|
||||
INSERT INTO ft2(rowid, x) VALUES(23, 'bbb bbb bbb');
|
||||
INSERT INTO ft2(rowid, x) VALUES(24, 'aaa bbb BBB');
|
||||
}
|
||||
|
||||
do_execsql_test 1.8 { SELECT rowid FROM ft2('aaa AND bbb'); } {10 24}
|
||||
do_execsql_test 1.9 { SELECT rowid FROM ft2('bbb AND aaa'); } {10 24}
|
||||
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
# 2023 November 22
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# Tests focused on phrase queries.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5origintext4
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
# The tests below verify that a doclist-index is used to limit the number
|
||||
# of pages loaded into the cache. It does this by querying sqlite3_db_status()
|
||||
# for the amount of memory used by the pager cache.
|
||||
#
|
||||
# memsubsys1 effectively limits the page-cache to 24 pages. Which masks
|
||||
# the effect tested by the tests in this file. And "mmap" prevents the
|
||||
# cache from being used, also preventing these tests from working.
|
||||
#
|
||||
if {[permutation]=="memsubsys1" || [permutation]=="mmap"} {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
sqlite3_fts5_register_origintext db
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA page_size = 4096;
|
||||
CREATE VIRTUAL TABLE ft USING fts5(
|
||||
x, tokenize="origintext unicode61", tokendata=1
|
||||
);
|
||||
}
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
BEGIN;
|
||||
INSERT INTO ft SELECT 'the first thing';
|
||||
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<90000
|
||||
)
|
||||
INSERT INTO ft SELECT 'The second thing' FROM s;
|
||||
|
||||
INSERT INTO ft SELECT 'the first thing';
|
||||
COMMIT;
|
||||
INSERT INTO ft(ft) VALUES('optimize');
|
||||
}
|
||||
|
||||
foreach {tn sql expr} {
|
||||
1 { SELECT rowid FROM ft('the') } {$mem > 250000}
|
||||
2 { SELECT rowid FROM ft('first') } {$mem < 50000}
|
||||
3 { SELECT rowid FROM ft('the first') } {$mem < 50000}
|
||||
} {
|
||||
db close
|
||||
sqlite3 db test.db
|
||||
sqlite3_fts5_register_origintext db
|
||||
|
||||
execsql $sql
|
||||
do_test 1.2.$tn {
|
||||
set mem [lindex [sqlite3_db_status db CACHE_USED 0] 1]
|
||||
expr $expr
|
||||
} 1
|
||||
}
|
||||
|
||||
proc b {x} { string map [list "\0" "."] $x }
|
||||
db func b b
|
||||
# execsql_pp { SELECT segid, b(term), pgno from ft_idx }
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -1,273 +0,0 @@
|
||||
# 2023 Dec 04
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# Tests for tables that use both tokendata=1 and contentless_delete=1.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5origintext
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
# Return a random integer between 0 and n-1.
|
||||
#
|
||||
proc random {n} { expr {abs(int(rand()*$n))} }
|
||||
|
||||
# Select an element of the list passed as the only argument at random and
|
||||
# return it.
|
||||
#
|
||||
proc select_one {list} {
|
||||
set n [llength $list]
|
||||
lindex $list [random $n]
|
||||
}
|
||||
|
||||
# Given a term that consists entirely of alphabet characters, return all
|
||||
# permutations of the term using upper and lower case characters. e.g.
|
||||
#
|
||||
# "abc" -> {CBA cBA CbA cbA CBa cBa Cba cba}
|
||||
#
|
||||
proc casify {term {lRet {{}}}} {
|
||||
if {$term==""} { return $lRet }
|
||||
set t [string range $term 1 end]
|
||||
set f1 [string toupper [string range $term 0 0]]
|
||||
set f2 [string tolower [string range $term 0 0]]
|
||||
set ret [list]
|
||||
foreach x $lRet {
|
||||
lappend ret "$x$f1"
|
||||
lappend ret "$x$f2"
|
||||
}
|
||||
return [casify $t $ret]
|
||||
}
|
||||
|
||||
proc vocab {} {
|
||||
list abc def ghi jkl mno pqr stu vwx yza
|
||||
}
|
||||
|
||||
# Return a random 3 letter term.
|
||||
#
|
||||
proc term {} {
|
||||
if {[info exists ::expanded_vocab]==0} {
|
||||
foreach v [vocab] { lappend ::expanded_vocab {*}[casify $v] }
|
||||
}
|
||||
|
||||
select_one $::expanded_vocab
|
||||
}
|
||||
|
||||
# Return a document - between 3 and 10 terms.
|
||||
#
|
||||
proc document {} {
|
||||
set nTerm [expr [random 3] + 7]
|
||||
set doc ""
|
||||
for {set ii 0} {$ii < $nTerm} {incr ii} {
|
||||
lappend doc [term]
|
||||
}
|
||||
set doc
|
||||
}
|
||||
db func document document
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
expr srand(6)
|
||||
|
||||
set NDOC 200
|
||||
set NLOOP 50
|
||||
|
||||
sqlite3_fts5_register_origintext db
|
||||
|
||||
proc tokens {cmd} {
|
||||
set ret [list]
|
||||
for {set iTok 0} {$iTok < [$cmd xInstCount]} {incr iTok} {
|
||||
set txt [$cmd xInstToken $iTok 0]
|
||||
set txt [string map [list "\0" "."] $txt]
|
||||
lappend ret $txt
|
||||
}
|
||||
set ret
|
||||
}
|
||||
sqlite3_fts5_create_function db tokens tokens
|
||||
|
||||
proc rankfunc {cmd} {
|
||||
$cmd xRowid
|
||||
}
|
||||
sqlite3_fts5_create_function db rankfunc rankfunc
|
||||
|
||||
proc ctrl_tokens {term args} {
|
||||
set ret [list]
|
||||
set term [string tolower $term]
|
||||
foreach doc $args {
|
||||
foreach a $doc {
|
||||
if {[string tolower $a]==$term} {
|
||||
if {$a==$term} {
|
||||
lappend ret $a
|
||||
} else {
|
||||
lappend ret [string tolower $a].$a
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
set ret
|
||||
}
|
||||
db func ctrl_tokens ctrl_tokens
|
||||
|
||||
proc do_all_vocab_test {tn} {
|
||||
foreach ::v [concat [vocab] nnn] {
|
||||
set answer [execsql {
|
||||
SELECT id, ctrl_tokens($::v, x) FROM ctrl WHERE x LIKE '%' || $::v || '%'
|
||||
}]
|
||||
do_execsql_test $tn.$::v.1 {
|
||||
SELECT rowid, tokens(ft) FROM ft($::v)
|
||||
} $answer
|
||||
do_execsql_test $tn.$::v.2 {
|
||||
SELECT rowid, tokens(ft) FROM ft($::v) ORDER BY rank
|
||||
} $answer
|
||||
}
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(
|
||||
x, tokenize="origintext unicode61", content=, contentless_delete=1,
|
||||
tokendata=1
|
||||
);
|
||||
|
||||
CREATE TABLE ctrl(id INTEGER PRIMARY KEY, x TEXT);
|
||||
INSERT INTO ft(ft, rank) VALUES('pgsz', 64);
|
||||
INSERT INTO ft(ft, rank) VALUES('rank', 'rankfunc()');
|
||||
}
|
||||
do_test 1.1 {
|
||||
for {set ii 0} {$ii < $NDOC} {incr ii} {
|
||||
set doc [document]
|
||||
execsql {
|
||||
INSERT INTO ft(rowid, x) VALUES($ii, $doc);
|
||||
INSERT INTO ctrl(id, x) VALUES($ii, $doc);
|
||||
}
|
||||
}
|
||||
} {}
|
||||
|
||||
#execsql_pp { SELECT * FROM ctrl }
|
||||
#execsql_pp { SELECT * FROM ft }
|
||||
#fts5_aux_test_functions db
|
||||
#execsql_pp { SELECT rowid, tokens(ft), fts5_test_poslist(ft) FROM ft('ghi'); }
|
||||
|
||||
do_all_vocab_test 1.2
|
||||
|
||||
for {set ii 0} {$ii < $NLOOP} {incr ii} {
|
||||
set lRowid [execsql { SELECT id FROM ctrl WHERE random() % 2 }]
|
||||
foreach r $lRowid {
|
||||
execsql { DELETE FROM ft WHERE rowid = $r }
|
||||
execsql { DELETE FROM ctrl WHERE rowid = $r }
|
||||
|
||||
set doc [document]
|
||||
execsql { INSERT INTO ft(rowid, x) VALUES($r, $doc) }
|
||||
execsql { INSERT INTO ctrl(id, x) VALUES($r, $doc) }
|
||||
}
|
||||
do_all_vocab_test 1.3.$ii
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
do_execsql_test 2.0 {
|
||||
CREATE VIRTUAL TABLE ft2 USING fts5(
|
||||
x, y, tokenize="origintext unicode61", content=, contentless_delete=1,
|
||||
tokendata=1
|
||||
);
|
||||
|
||||
CREATE TABLE ctrl2(id INTEGER PRIMARY KEY, x TEXT, y TEXT);
|
||||
INSERT INTO ft2(ft2, rank) VALUES('pgsz', 64);
|
||||
INSERT INTO ft2(ft2, rank) VALUES('rank', 'rankfunc()');
|
||||
}
|
||||
do_test 2.1 {
|
||||
for {set ii 0} {$ii < $NDOC} {incr ii} {
|
||||
set doc1 [document]
|
||||
set doc2 [document]
|
||||
execsql {
|
||||
INSERT INTO ft2(rowid, x, y) VALUES($ii, $doc, $doc2);
|
||||
INSERT INTO ctrl2(id, x, y) VALUES($ii, $doc, $doc2);
|
||||
}
|
||||
}
|
||||
} {}
|
||||
|
||||
proc do_all_vocab_test2 {tn} {
|
||||
foreach ::v [vocab] {
|
||||
set answer [execsql {
|
||||
SELECT id, ctrl_tokens($::v, x, y) FROM ctrl2
|
||||
WHERE x LIKE '%' || $::v || '%' OR y LIKE '%' || $::v || '%';
|
||||
}]
|
||||
do_execsql_test $tn.$::v.1 {
|
||||
SELECT rowid, tokens(ft2) FROM ft2($::v)
|
||||
} $answer
|
||||
do_execsql_test $tn.$::v.2 {
|
||||
SELECT rowid, tokens(ft2) FROM ft2($::v) ORDER BY rank
|
||||
} $answer
|
||||
}
|
||||
}
|
||||
|
||||
do_all_vocab_test2 2.2
|
||||
|
||||
for {set ii 0} {$ii < $NLOOP} {incr ii} {
|
||||
set lRowid [execsql { SELECT id FROM ctrl2 WHERE random() % 2 }]
|
||||
foreach r $lRowid {
|
||||
execsql { DELETE FROM ft2 WHERE rowid = $r }
|
||||
execsql { DELETE FROM ctrl2 WHERE rowid = $r }
|
||||
|
||||
set doc1 [document]
|
||||
set doc2 [document]
|
||||
execsql { INSERT INTO ft2(rowid, x, y) VALUES($r, $doc, $doc1) }
|
||||
execsql { INSERT INTO ctrl2(id, x, y) VALUES($r, $doc, $doc2) }
|
||||
}
|
||||
do_all_vocab_test 2.3.$ii
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
unset -nocomplain ::expanded_vocab
|
||||
proc vocab {} {
|
||||
list abcde fghij klmno
|
||||
}
|
||||
|
||||
proc do_all_vocab_test3 {tn} {
|
||||
foreach ::v [concat [vocab] nnn] {
|
||||
set answer [execsql {
|
||||
SELECT rowid, ctrl_tokens($::v, w) FROM ctrl3 WHERE w LIKE '%' || $::v || '%'
|
||||
}]
|
||||
do_execsql_test $tn.$::v.1 {
|
||||
SELECT rowid, tokens(ft3) FROM ft3($::v)
|
||||
} $answer
|
||||
do_execsql_test $tn.$::v.2 {
|
||||
SELECT rowid, tokens(ft3) FROM ft3($::v) ORDER BY rank
|
||||
} $answer
|
||||
}
|
||||
}
|
||||
|
||||
do_execsql_test 3.0 {
|
||||
CREATE VIRTUAL TABLE ft3 USING fts5(
|
||||
w, tokenize="origintext unicode61", content=, contentless_delete=1,
|
||||
tokendata=1
|
||||
);
|
||||
INSERT INTO ft3(ft3, rank) VALUES('rank', 'rankfunc()');
|
||||
CREATE TABLE ctrl3(w);
|
||||
}
|
||||
|
||||
do_execsql_test 3.1 {
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<2
|
||||
)
|
||||
INSERT INTO ctrl3 SELECT document() FROM s;
|
||||
INSERT INTO ft3(rowid, w) SELECT rowid, w FROM ctrl3;
|
||||
}
|
||||
|
||||
do_all_vocab_test3 3.2
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -86,76 +86,71 @@ do_execsql_test 2.8 {
|
||||
# Tests with large/small rowid values.
|
||||
#
|
||||
|
||||
foreach {tn cfg} {
|
||||
1 ""
|
||||
2 "INSERT INTO fff(fff, rank) VALUES('secure-delete', 1)"
|
||||
} {
|
||||
reset_db
|
||||
|
||||
expr srand(0)
|
||||
|
||||
set vocab {
|
||||
Popper Poppins Popsicle Porfirio Porrima Porsche
|
||||
Porter Portia Portland Portsmouth Portugal Portuguese
|
||||
Poseidon Post PostgreSQL Potemkin Potomac Potsdam
|
||||
Pottawatomie Potter Potts Pound Poussin Powell
|
||||
PowerPC PowerPoint Powers Powhatan Poznan Prada
|
||||
Prado Praetorian Prague Praia Prakrit Pratchett
|
||||
Pratt Pravda Praxiteles Preakness Precambrian Preminger
|
||||
Premyslid Prensa Prentice Pres Presbyterian Presbyterianism
|
||||
}
|
||||
proc newdoc {} {
|
||||
for {set i 0} {$i<8} {incr i} {
|
||||
lappend ret [lindex $::vocab [expr int(abs(rand()) * [llength $::vocab])]]
|
||||
}
|
||||
set ret
|
||||
}
|
||||
db func newdoc newdoc
|
||||
|
||||
do_execsql_test 3.$tn.0 {
|
||||
CREATE VIRTUAL TABLE fff USING fts5(y);
|
||||
INSERT INTO fff(fff, rank) VALUES('pgsz', 64);
|
||||
|
||||
WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 )
|
||||
INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s;
|
||||
|
||||
WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 )
|
||||
INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s;
|
||||
|
||||
WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 )
|
||||
INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s;
|
||||
}
|
||||
reset_db
|
||||
|
||||
execsql $cfg
|
||||
|
||||
proc lshuffle {in} {
|
||||
set out [list]
|
||||
while {[llength $in]>0} {
|
||||
set idx [expr int(abs(rand()) * [llength $in])]
|
||||
lappend out [lindex $in $idx]
|
||||
set in [lreplace $in $idx $idx]
|
||||
}
|
||||
set out
|
||||
expr srand(0)
|
||||
|
||||
set vocab {
|
||||
Popper Poppins Popsicle Porfirio Porrima Porsche
|
||||
Porter Portia Portland Portsmouth Portugal Portuguese
|
||||
Poseidon Post PostgreSQL Potemkin Potomac Potsdam
|
||||
Pottawatomie Potter Potts Pound Poussin Powell
|
||||
PowerPC PowerPoint Powers Powhatan Poznan Prada
|
||||
Prado Praetorian Prague Praia Prakrit Pratchett
|
||||
Pratt Pravda Praxiteles Preakness Precambrian Preminger
|
||||
Premyslid Prensa Prentice Pres Presbyterian Presbyterianism
|
||||
}
|
||||
proc newdoc {} {
|
||||
for {set i 0} {$i<8} {incr i} {
|
||||
lappend ret [lindex $::vocab [expr int(abs(rand()) * [llength $::vocab])]]
|
||||
}
|
||||
|
||||
#dump fff
|
||||
|
||||
set iTest 1
|
||||
foreach ii [lshuffle [db eval {SELECT rowid FROM fff}]] {
|
||||
#if {$iTest==1} { dump fff }
|
||||
#if {$iTest==1} { breakpoint }
|
||||
do_execsql_test 3.$tn.1.$iTest.$ii {
|
||||
DELETE FROM fff WHERE rowid=$ii;
|
||||
}
|
||||
#if {$iTest==1} { dump fff }
|
||||
if {($iTest % 20)==0} {
|
||||
do_execsql_test 3.$tn.1.$iTest.$ii.ic {
|
||||
INSERT INTO fff(fff) VALUES('integrity-check');
|
||||
}
|
||||
}
|
||||
#if {$iTest==1} { break }
|
||||
incr iTest
|
||||
set ret
|
||||
}
|
||||
db func newdoc newdoc
|
||||
|
||||
do_execsql_test 3.0 {
|
||||
CREATE VIRTUAL TABLE fff USING fts5(y);
|
||||
INSERT INTO fff(fff, rank) VALUES('pgsz', 64);
|
||||
|
||||
WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 )
|
||||
INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s;
|
||||
|
||||
WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 )
|
||||
INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s;
|
||||
|
||||
WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 )
|
||||
INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s;
|
||||
|
||||
INSERT INTO fff(fff, rank) VALUES('secure-delete', 1);
|
||||
}
|
||||
|
||||
proc lshuffle {in} {
|
||||
set out [list]
|
||||
while {[llength $in]>0} {
|
||||
set idx [expr int(abs(rand()) * [llength $in])]
|
||||
lappend out [lindex $in $idx]
|
||||
set in [lreplace $in $idx $idx]
|
||||
}
|
||||
set out
|
||||
}
|
||||
|
||||
#dump fff
|
||||
|
||||
set iTest 1
|
||||
foreach ii [lshuffle [db eval {SELECT rowid FROM fff}]] {
|
||||
#if {$iTest==1} { dump fff }
|
||||
#if {$iTest==1} { breakpoint }
|
||||
do_execsql_test 3.1.$iTest.$ii {
|
||||
DELETE FROM fff WHERE rowid=$ii;
|
||||
}
|
||||
#if {$iTest==1} { dump fff }
|
||||
if {($iTest % 20)==0} {
|
||||
do_execsql_test 3.1.$iTest.$ii.ic {
|
||||
INSERT INTO fff(fff) VALUES('integrity-check');
|
||||
}
|
||||
}
|
||||
#if {$iTest==1} { break }
|
||||
incr iTest
|
||||
}
|
||||
|
||||
#execsql_pp { SELECT rowid FROM fff('post') ORDER BY rowid ASC }
|
||||
|
||||
@@ -343,9 +343,7 @@ do_execsql_test 17.0 {
|
||||
INSERT INTO t2 VALUES('a aa aaa', 'b bb bbb');
|
||||
COMMIT;
|
||||
}
|
||||
do_execsql_test 17.1 {
|
||||
SELECT * FROM t2('y:a*') WHERE rowid BETWEEN 10 AND 20
|
||||
}
|
||||
do_execsql_test 17.1 { SELECT * FROM t2('y:a*') WHERE rowid BETWEEN 10 AND 20 }
|
||||
do_execsql_test 17.2 {
|
||||
BEGIN;
|
||||
INSERT INTO t2 VALUES('a aa aaa', 'b bb bbb');
|
||||
|
||||
@@ -42,7 +42,7 @@ proc fts5_test_bothlist {cmd} {
|
||||
}
|
||||
sqlite3_fts5_create_function db fts5_test_bothlist fts5_test_bothlist
|
||||
|
||||
proc fts5_rowid {cmd} { expr [$cmd xRowid] }
|
||||
proc fts5_rowid {cmd} { expr [$cmd xColumnText -1] }
|
||||
sqlite3_fts5_create_function db fts5_rowid fts5_rowid
|
||||
|
||||
do_execsql_test 1.$tok.0.1 "
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
# 2023 Nov 03
|
||||
#
|
||||
# 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 fts5tokenizer2
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
sqlite3_fts5_create_tokenizer db tst get_tst_tokenizer
|
||||
proc get_tst_tokenizer {args} {
|
||||
return "tst_tokenizer"
|
||||
}
|
||||
proc tst_tokenizer {flags txt} {
|
||||
set token ""
|
||||
set lTok [list]
|
||||
|
||||
foreach c [split $txt {}] {
|
||||
if {$token==""} {
|
||||
append token $c
|
||||
} else {
|
||||
set t1 [string is upper $token]
|
||||
set t2 [string is upper $c]
|
||||
|
||||
if {$t1!=$t2} {
|
||||
lappend lTok $token
|
||||
set token ""
|
||||
}
|
||||
append token $c
|
||||
}
|
||||
}
|
||||
if {$token!=""} { lappend lTok $token }
|
||||
|
||||
set iOff 0
|
||||
foreach t $lTok {
|
||||
set n [string length $t]
|
||||
sqlite3_fts5_token $t $iOff [expr $iOff+$n]
|
||||
incr iOff $n
|
||||
}
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(t, tokenize=tst);
|
||||
}
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
INSERT INTO t1 VALUES('AAdontBBmess');
|
||||
}
|
||||
|
||||
do_execsql_test 1.2 {
|
||||
SELECT snippet(t1, 0, '>', '<', '...', 4) FROM t1('BB');
|
||||
} {AAdont>BB<mess}
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
SELECT highlight(t1, 0, '>', '<') FROM t1('BB');
|
||||
} {AAdont>BB<mess}
|
||||
|
||||
do_execsql_test 1.4 {
|
||||
SELECT highlight(t1, 0, '>', '<') FROM t1('AA');
|
||||
} {>AA<dontBBmess}
|
||||
|
||||
do_execsql_test 1.5 {
|
||||
SELECT highlight(t1, 0, '>', '<') FROM t1('dont');
|
||||
} {AA>dont<BBmess}
|
||||
|
||||
do_execsql_test 1.6 {
|
||||
SELECT highlight(t1, 0, '>', '<') FROM t1('mess');
|
||||
} {AAdontBB>mess<}
|
||||
|
||||
do_execsql_test 1.7 {
|
||||
SELECT highlight(t1, 0, '>', '<') FROM t1('BB mess');
|
||||
} {AAdont>BBmess<}
|
||||
|
||||
|
||||
finish_test
|
||||
@@ -1,109 +0,0 @@
|
||||
# 2023 October 24
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#*************************************************************************
|
||||
#
|
||||
# Tests for the fts5 "trigram" tokenizer.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
ifcapable !fts5 { finish_test ; return }
|
||||
set ::testprefix fts5trigram2
|
||||
|
||||
do_execsql_test 1.0 "
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(y, tokenize='trigram remove_diacritics 1');
|
||||
INSERT INTO t1 VALUES('abc\u0303defghijklm');
|
||||
INSERT INTO t1 VALUES('a\u0303b\u0303c\u0303defghijklm');
|
||||
"
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
SELECT highlight(t1, 0, '(', ')') FROM t1('abc');
|
||||
} [list \
|
||||
"(abc\u0303)defghijklm" \
|
||||
"(a\u0303b\u0303c\u0303)defghijklm" \
|
||||
]
|
||||
|
||||
do_execsql_test 1.2 {
|
||||
SELECT highlight(t1, 0, '(', ')') FROM t1('bcde');
|
||||
} [list \
|
||||
"a(bc\u0303de)fghijklm" \
|
||||
"a\u0303(b\u0303c\u0303de)fghijklm" \
|
||||
]
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
SELECT highlight(t1, 0, '(', ')') FROM t1('cdef');
|
||||
} [list \
|
||||
"ab(c\u0303def)ghijklm" \
|
||||
"a\u0303b\u0303(c\u0303def)ghijklm" \
|
||||
]
|
||||
|
||||
do_execsql_test 1.4 {
|
||||
SELECT highlight(t1, 0, '(', ')') FROM t1('def');
|
||||
} [list \
|
||||
"abc\u0303(def)ghijklm" \
|
||||
"a\u0303b\u0303c\u0303(def)ghijklm" \
|
||||
]
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
do_catchsql_test 2.0 {
|
||||
CREATE VIRTUAL TABLE t2 USING fts5(
|
||||
z, tokenize='trigram case_sensitive 1 remove_diacritics 1'
|
||||
);
|
||||
} {1 {error in tokenizer constructor}}
|
||||
|
||||
do_execsql_test 2.1 {
|
||||
CREATE VIRTUAL TABLE t2 USING fts5(
|
||||
z, tokenize='trigram case_sensitive 0 remove_diacritics 1'
|
||||
);
|
||||
}
|
||||
do_execsql_test 2.2 "
|
||||
INSERT INTO t2 VALUES('\u00E3bcdef');
|
||||
INSERT INTO t2 VALUES('b\u00E3cdef');
|
||||
INSERT INTO t2 VALUES('bc\u00E3def');
|
||||
INSERT INTO t2 VALUES('bcd\u00E3ef');
|
||||
"
|
||||
|
||||
do_execsql_test 2.3 {
|
||||
SELECT highlight(t2, 0, '(', ')') FROM t2('abc');
|
||||
} "(\u00E3bc)def"
|
||||
do_execsql_test 2.4 {
|
||||
SELECT highlight(t2, 0, '(', ')') FROM t2('bac');
|
||||
} "(b\u00E3c)def"
|
||||
do_execsql_test 2.5 {
|
||||
SELECT highlight(t2, 0, '(', ')') FROM t2('bca');
|
||||
} "(bc\u00E3)def"
|
||||
do_execsql_test 2.6 "
|
||||
SELECT highlight(t2, 0, '(', ')') FROM t2('\u00E3bc');
|
||||
" "(\u00E3bc)def"
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
do_execsql_test 3.0 {
|
||||
CREATE VIRTUAL TABLE t3 USING fts5(
|
||||
z, tokenize='trigram remove_diacritics 1'
|
||||
);
|
||||
} {}
|
||||
do_execsql_test 3.1 "
|
||||
INSERT INTO t3 VALUES ('\u0303abc\u0303');
|
||||
"
|
||||
do_execsql_test 3.2 {
|
||||
SELECT highlight(t3, 0, '(', ')') FROM t3('abc');
|
||||
} "\u0303(abc\u0303)"
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
do_execsql_test 4.0 {
|
||||
CREATE VIRTUAL TABLE t4 USING fts5(z, tokenize=trigram);
|
||||
} {}
|
||||
|
||||
breakpoint
|
||||
do_execsql_test 4.1 {
|
||||
INSERT INTO t4 VALUES('ABCD');
|
||||
} {}
|
||||
|
||||
finish_test
|
||||
@@ -280,30 +280,6 @@ do_catchsql_test 5.2 {
|
||||
INSERT INTO t1 SELECT randomblob(3000) FROM v1
|
||||
} {1 {query aborted}}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
sqlite3_fts5_may_be_corrupt 1
|
||||
|
||||
do_execsql_test 6.0 {
|
||||
BEGIN TRANSACTION;
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(a,b unindexed,c,tokenize="porter ascii",tokendata=1);
|
||||
REPLACE INTO t1_data VALUES(1,X'03090009');
|
||||
REPLACE INTO t1_data VALUES(10,X'000000000103030003010101020101030101');
|
||||
REPLACE INTO t1_data VALUES(137438953473,X'0000002e023061010202010162010203010163010204010167010601020201016801060102030101690106010204040606060808');
|
||||
REPLACE INTO t1_data VALUES(274877906945,X'0000001f013067020802010202010168020803010203010169020804010204040909');
|
||||
REPLACE INTO t1_data VALUES(412316860417,X'0000002e023061030202010162030203010163030204010167030601020201016803060102030101690306010204040606060808');
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
do_execsql_test 6.1 {
|
||||
CREATE VIRTUAL TABLE t3 USING fts5vocab('t1', 'row');
|
||||
}
|
||||
|
||||
do_catchsql_test 6.2 {
|
||||
SELECT * FROM t3;
|
||||
} {1 {database disk image is malformed}}
|
||||
|
||||
sqlite3_fts5_may_be_corrupt 0
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -1,331 +0,0 @@
|
||||
# 2008 Feb 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.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# The focus of this file is testing the incremental integrity check
|
||||
# (intck) extension.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] intck_common.tcl]
|
||||
set testprefix intck1
|
||||
|
||||
foreach {tn sql} {
|
||||
1 "CREATE TABLE t1(a PRIMARY KEY, b)"
|
||||
2 "CREATE TABLE t2(a PRIMARY KEY, b) WITHOUT ROWID "
|
||||
3 "CREATE TABLE t3(a PRIMARY KEY, b) WITHOUT rowID;"
|
||||
4 "CREATE TABLE t4(a PRIMARY KEY, ROWID)"
|
||||
5 {CREATE TABLE t5(a PRIMARY KEY, ROWID) WITHOUT ROWID
|
||||
}
|
||||
} {
|
||||
do_test 1.1.$tn {
|
||||
db eval $sql
|
||||
set {} {}
|
||||
} {}
|
||||
}
|
||||
|
||||
set space " \n\v\t\r\f"
|
||||
|
||||
do_execsql_test 1.2 {
|
||||
SELECT name, (rtrim(sql, $space) LIKE '%rowid')
|
||||
FROM sqlite_schema WHERE type='table'
|
||||
ORDER BY 1
|
||||
} {
|
||||
t1 0
|
||||
t2 1
|
||||
t3 1
|
||||
t4 0
|
||||
t5 1
|
||||
}
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
CREATE TABLE x1(a COLLATE nocase, b INTEGER, c BLOB);
|
||||
INSERT INTO x1 VALUES('lEtTeRs', 1234, 1234);
|
||||
}
|
||||
do_execsql_test 1.3.1 {
|
||||
WITH wrapper(c1, c2, c3) AS (
|
||||
SELECT a, b, c FROM x1
|
||||
)
|
||||
SELECT * FROM wrapper WHERE c1='letters';
|
||||
} {lEtTeRs 1234 1234}
|
||||
do_execsql_test 1.3.2 {
|
||||
WITH wrapper(c1, c2, c3) AS (
|
||||
SELECT a, b, c FROM x1
|
||||
)
|
||||
SELECT * FROM wrapper WHERE c2='1234';
|
||||
} {lEtTeRs 1234 1234}
|
||||
do_execsql_test 1.3.2 {
|
||||
WITH wrapper(c1, c2, c3) AS (
|
||||
SELECT a, b, c FROM x1
|
||||
)
|
||||
SELECT * FROM wrapper WHERE c3='1234';
|
||||
} {}
|
||||
|
||||
do_execsql_test 1.4 {
|
||||
CREATE TABLE z1(a, b);
|
||||
CREATE INDEX z1ab ON z1(a+b COLLATE nocase);
|
||||
}
|
||||
do_execsql_test 1.4.1 {
|
||||
SELECT * FROM z1 INDEXED BY z1ab
|
||||
}
|
||||
|
||||
do_catchsql_test 1.5.1 {
|
||||
CREATE INDEX z1b ON z1(b ASC NULLS LAST);
|
||||
} {1 {unsupported use of NULLS LAST}}
|
||||
do_catchsql_test 1.5.2 {
|
||||
CREATE INDEX z1b ON z1(b DESC NULLS LAST);
|
||||
} {1 {unsupported use of NULLS LAST}}
|
||||
do_catchsql_test 1.5.3 {
|
||||
CREATE INDEX z1b ON z1(b ASC NULLS FIRST);
|
||||
} {1 {unsupported use of NULLS FIRST}}
|
||||
do_catchsql_test 1.5.4 {
|
||||
CREATE INDEX z1b ON z1(b DESC NULLS FIRST);
|
||||
} {1 {unsupported use of NULLS FIRST}}
|
||||
|
||||
|
||||
reset_db
|
||||
do_execsql_test 1.6.1 {
|
||||
CREATE TABLE t1(i INTEGER PRIMARY KEY, b, c);
|
||||
CREATE INDEX i1 ON t1(b);
|
||||
ANALYZE;
|
||||
INSERT INTO sqlite_stat1 VALUES('t1', 'i1', '10000 10000');
|
||||
ANALYZE sqlite_schema;
|
||||
} {}
|
||||
do_eqp_test 1.6.2 {
|
||||
SELECT 1 FROM t1 INDEXED BY i1 WHERE (b, i) IS (?, ?);
|
||||
} {SEARCH}
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
|
||||
do_test 2.0 {
|
||||
set ic [sqlite3_intck db main]
|
||||
$ic close
|
||||
} {}
|
||||
|
||||
do_execsql_test 2.1 {
|
||||
CREATE TABLE t1(a, b);
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
INSERT INTO t1 VALUES(3, 4);
|
||||
|
||||
CREATE INDEX i1 ON t1(a COLLATE nocase);
|
||||
CREATE INDEX i2 ON t1(b, a);
|
||||
CREATE INDEX i3 ON t1(b + a COLLATE nocase) WHERE a!=1;
|
||||
}
|
||||
|
||||
do_intck_test 2.2 {
|
||||
}
|
||||
|
||||
# Delete a row from each of the i1 and i2 indexes using the imposter
|
||||
# table interface.
|
||||
#
|
||||
do_test 2.3 {
|
||||
db eval {SELECT name, rootpage FROM sqlite_schema} {
|
||||
set R($name) $rootpage
|
||||
}
|
||||
sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $R(i1)
|
||||
db eval { CREATE TABLE imp1(a PRIMARY KEY, rowid) WITHOUT ROWID; }
|
||||
sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $R(i2)
|
||||
db eval { CREATE TABLE imp2(b, a, rowid, PRIMARY KEY(b, a)) WITHOUT ROWID; }
|
||||
sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 0 0
|
||||
|
||||
db eval {
|
||||
DELETE FROM imp1 WHERE rowid=1;
|
||||
DELETE FROM imp2 WHERE rowid=2;
|
||||
}
|
||||
|
||||
db close
|
||||
sqlite3 db test.db
|
||||
} {}
|
||||
|
||||
do_intck_test 2.4 {
|
||||
{entry (1,1) missing from index i1}
|
||||
{entry (4,3,2) missing from index i2}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
CREATE TABLE x1(a, b, c, PRIMARY KEY(c, b)) WITHOUT ROWID;
|
||||
CREATE INDEX x1a ON x1(a COLLATE nocase);
|
||||
|
||||
INSERT INTO x1 VALUES(1, 2, 'three');
|
||||
INSERT INTO x1 VALUES(4, 5, 'six');
|
||||
INSERT INTO x1 VALUES(7, 8, 'nine');
|
||||
}
|
||||
|
||||
do_intck_test 3.1 { }
|
||||
|
||||
do_test 3.2 {
|
||||
db eval {SELECT name, rootpage FROM sqlite_schema} {
|
||||
set R($name) $rootpage
|
||||
}
|
||||
sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $R(x1a)
|
||||
db eval { CREATE TABLE imp1(c, b, a, PRIMARY KEY(c, b)) WITHOUT ROWID }
|
||||
sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 0 0
|
||||
|
||||
db eval {
|
||||
DELETE FROM imp1 WHERE a=5;
|
||||
}
|
||||
execsql_pp {
|
||||
}
|
||||
|
||||
db close
|
||||
sqlite3 db test.db
|
||||
} {}
|
||||
|
||||
do_intck_test 3.3 {
|
||||
{entry (4,'six',5) missing from index x1a}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 4.0 {
|
||||
CREATE TABLE www(x, y, z);
|
||||
CREATE INDEX w1 ON www( (x+1), z );
|
||||
INSERT INTO www VALUES(1, 1, 1), (2, 2, 2);
|
||||
}
|
||||
|
||||
do_intck_test 4.1 { }
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 5.0 {
|
||||
CREATE TABLE t1(a, b);
|
||||
CREATE INDEX i1 ON t1(a COLLATE NOCASE);
|
||||
INSERT INTO t1 VALUES(1, 1);
|
||||
INSERT INTO t1 VALUES(2, 2);
|
||||
}
|
||||
|
||||
do_test 5.1 {
|
||||
set ic [sqlite3_intck db nosuchdb]
|
||||
$ic step
|
||||
} {SQLITE_ERROR}
|
||||
|
||||
do_test 5.2 {
|
||||
$ic close
|
||||
set ic [sqlite3_intck db {}]
|
||||
while {[$ic step]=="SQLITE_OK"} {}
|
||||
set res [$ic error]
|
||||
$ic close
|
||||
set res
|
||||
} {SQLITE_OK {}}
|
||||
|
||||
do_test 5.3 { test_do_intck db "main" } {}
|
||||
|
||||
do_test 5.4 {
|
||||
set ret {}
|
||||
set ic [sqlite3_intck db main]
|
||||
db eval [$ic test_sql t1] {
|
||||
if {$error_message!=""} { lappend ret $error_message }
|
||||
}
|
||||
$ic close
|
||||
set ret
|
||||
} {}
|
||||
|
||||
do_test 5.5 {
|
||||
set ret {}
|
||||
set ic [sqlite3_intck db main]
|
||||
db eval [$ic test_sql {}] {
|
||||
if {$error_message!=""} { lappend ret $error_message }
|
||||
}
|
||||
$ic close
|
||||
set ret
|
||||
} {}
|
||||
|
||||
db cache flush
|
||||
|
||||
do_test 5.6 {
|
||||
set ret {}
|
||||
set ic [sqlite3_intck db main]
|
||||
$ic step
|
||||
db eval [$ic test_sql {}] {
|
||||
if {$error_message!=""} { lappend ret $error_message }
|
||||
}
|
||||
$ic close
|
||||
set ret
|
||||
} {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
|
||||
do_execsql_test 6.0 {
|
||||
CREATE TABLE t1(x, y, PRIMARY KEY(x)) WITHOUT ROWID;
|
||||
CREATE INDEX i1 ON t1(y, x);
|
||||
INSERT INTO t1 VALUES(X'0000', X'1111');
|
||||
}
|
||||
|
||||
do_intck_test 6.1 {}
|
||||
|
||||
do_execsql_test 6.2.1 {
|
||||
PRAGMA writable_schema = 1;
|
||||
UPDATE sqlite_schema SET sql = 'CREATE INDEX i1' WHERE name='i1';
|
||||
} {}
|
||||
do_intck_test 6.2.2 {}
|
||||
|
||||
do_execsql_test 6.3.1 {
|
||||
UPDATE sqlite_schema SET sql = 'CREATE INDEX i1(y' WHERE name='i1';
|
||||
} {}
|
||||
do_intck_test 6.3.2 {}
|
||||
|
||||
do_execsql_test 6.4.1 {
|
||||
UPDATE sqlite_schema
|
||||
SET sql = 'CREATE INDEX i1(y) hello world'
|
||||
WHERE name='i1';
|
||||
} {}
|
||||
do_intck_test 6.4.2 {}
|
||||
|
||||
do_execsql_test 6.5.1 {
|
||||
UPDATE sqlite_schema
|
||||
SET sql = 'CREATE INDEX i1(y, x) WHERE 1 '
|
||||
WHERE name='i1';
|
||||
} {}
|
||||
do_intck_test 6.5.2 {}
|
||||
|
||||
do_execsql_test 6.6.1 {
|
||||
UPDATE sqlite_schema
|
||||
SET sql = 'CREATE INDEX i1( , ) WHERE 1 '
|
||||
WHERE name='i1';
|
||||
} {}
|
||||
|
||||
do_test 6.7.2 {
|
||||
set ic [sqlite3_intck db main]
|
||||
$ic step
|
||||
} {SQLITE_ERROR}
|
||||
do_test 6.5.3 {
|
||||
$ic error
|
||||
} {SQLITE_ERROR {near "AS": syntax error}}
|
||||
$ic close
|
||||
|
||||
do_execsql_test 6.6.1 {
|
||||
UPDATE sqlite_schema
|
||||
SET sql = 'CREATE INDEX i1([y'
|
||||
WHERE name='i1';
|
||||
} {}
|
||||
do_intck_test 6.6.2 {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 7.0 {
|
||||
CREATE TABLE x1("1", "22", "3333", four);
|
||||
CREATE INDEX i1 ON x1( "1" , "22", NULL);
|
||||
INSERT INTO x1 VALUES(1, 22, 3333, NULL);
|
||||
INSERT INTO x1 VALUES(1, 22, 3333, NULL);
|
||||
}
|
||||
do_execsql_test 7.1 " CREATE INDEX i2 ON x1( \"1\"\r\n\t ) "
|
||||
do_execsql_test 7.2 { CREATE INDEX i3 ON x1( "22" || 'abc''def' || `1` ) }
|
||||
do_execsql_test 7.3 { CREATE INDEX i4 ON x1( [22] + [1] ) }
|
||||
do_execsql_test 7.4 { CREATE INDEX i5 ON x1( four||'hello' ) }
|
||||
|
||||
do_intck_test 7.5 {}
|
||||
|
||||
|
||||
finish_test
|
||||
@@ -1,176 +0,0 @@
|
||||
# 2024 Feb 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.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# The focus of this file is testing the incremental integrity check
|
||||
# (intck) extension.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] intck_common.tcl]
|
||||
set testprefix intck2
|
||||
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);
|
||||
INSERT INTO t1 VALUES(1, 'one');
|
||||
INSERT INTO t1 VALUES(2, 'two');
|
||||
INSERT INTO t1 VALUES(3, 'three');
|
||||
CREATE INDEX i1 ON t1(b);
|
||||
}
|
||||
|
||||
proc imposter_edit {obj create sql} {
|
||||
sqlite3 xdb test.db
|
||||
set pgno [xdb one {SELECT rootpage FROM sqlite_schema WHERE name=$obj}]
|
||||
|
||||
sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER xdb main 1 $pgno
|
||||
xdb eval $create
|
||||
sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER xdb main 0 0
|
||||
xdb eval $sql
|
||||
xdb close
|
||||
}
|
||||
|
||||
imposter_edit i1 {
|
||||
CREATE TABLE imp(b, a, PRIMARY KEY(b)) WITHOUT ROWID;
|
||||
} {
|
||||
DELETE FROM imp WHERE b='two';
|
||||
INSERT INTO imp(b, a) VALUES('four', 4);
|
||||
}
|
||||
|
||||
do_intck_test 1.1 {
|
||||
{surplus entry ('four',4) in index i1}
|
||||
{entry ('two',2) missing from index i1}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 2.0 {
|
||||
CREATE TABLE x1(a, b, "c d");
|
||||
CREATE INDEX x1a ON x1(a COLLATE nocase DESC , b ASC);
|
||||
CREATE INDEX x1b ON x1( a || b || ' "''" ' COLLATE binary ASC );
|
||||
CREATE INDEX x1c ON x1( format('%s', a)ASC, format('%d', "c d" ) );
|
||||
INSERT INTO x1 VALUES('one', 2, 3);
|
||||
INSERT INTO x1 VALUES('One', 4, 5);
|
||||
INSERT INTO x1 VALUES('ONE', 6, 7);
|
||||
INSERT INTO x1 VALUES(NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
do_intck_test 2.1 {}
|
||||
|
||||
imposter_edit x1 {
|
||||
CREATE TABLE imp(a, b, c);
|
||||
} {
|
||||
DELETE FROM imp WHERE c=7;
|
||||
}
|
||||
do_intck_test 2.2 {
|
||||
{surplus entry ('ONE',6,3) in index x1a}
|
||||
{surplus entry ('ONE6 "''" ',3) in index x1b}
|
||||
{surplus entry ('ONE','7',3) in index x1c}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
CREATE TABLE x1(a, b, c);
|
||||
CREATE INDEX x1all ON x1(a DESC, b ASC, c DESC);
|
||||
INSERT INTO x1 VALUES(2, 1, 2);
|
||||
INSERT INTO x1 VALUES(2, 1, 1);
|
||||
INSERT INTO x1 VALUES(2, 2, 2);
|
||||
INSERT INTO x1 VALUES(2, 2, 1);
|
||||
INSERT INTO x1 VALUES(1, 1, 2);
|
||||
INSERT INTO x1 VALUES(1, 1, 1);
|
||||
INSERT INTO x1 VALUES(1, 2, 2);
|
||||
INSERT INTO x1 VALUES(1, 2, 1);
|
||||
}
|
||||
|
||||
do_intck_test 3.1 {
|
||||
}
|
||||
|
||||
imposter_edit x1 {
|
||||
CREATE TABLE imp(a, b, c);
|
||||
} {
|
||||
DELETE FROM imp WHERE 1;
|
||||
}
|
||||
|
||||
db close
|
||||
sqlite3 db test.db
|
||||
|
||||
do_intck_test 3.2 {
|
||||
{surplus entry (2,1,2,1) in index x1all}
|
||||
{surplus entry (2,1,1,2) in index x1all}
|
||||
{surplus entry (2,2,2,3) in index x1all}
|
||||
{surplus entry (2,2,1,4) in index x1all}
|
||||
{surplus entry (1,1,2,5) in index x1all}
|
||||
{surplus entry (1,1,1,6) in index x1all}
|
||||
{surplus entry (1,2,2,7) in index x1all}
|
||||
{surplus entry (1,2,1,8) in index x1all}
|
||||
}
|
||||
|
||||
do_execsql_test 3.3 {
|
||||
DELETE FROM x1;
|
||||
INSERT INTO x1 VALUES(NULL, NULL, NULL);
|
||||
INSERT INTO x1 VALUES(NULL, NULL, NULL);
|
||||
INSERT INTO x1 VALUES(NULL, NULL, NULL);
|
||||
INSERT INTO x1 VALUES(NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
do_intck_test 3.4 {
|
||||
}
|
||||
|
||||
imposter_edit x1 {
|
||||
CREATE TABLE imp(a, b, c);
|
||||
} {
|
||||
DELETE FROM imp WHERE 1;
|
||||
INSERT INTO imp(rowid) VALUES(-123);
|
||||
INSERT INTO imp(rowid) VALUES(456);
|
||||
}
|
||||
|
||||
db close
|
||||
sqlite3 db test.db
|
||||
|
||||
do_intck_test 3.5 {
|
||||
{entry (NULL,NULL,NULL,-123) missing from index x1all}
|
||||
{entry (NULL,NULL,NULL,456) missing from index x1all}
|
||||
{surplus entry (NULL,NULL,NULL,1) in index x1all}
|
||||
{surplus entry (NULL,NULL,NULL,2) in index x1all}
|
||||
{surplus entry (NULL,NULL,NULL,3) in index x1all}
|
||||
{surplus entry (NULL,NULL,NULL,4) in index x1all}
|
||||
}
|
||||
|
||||
reset_db
|
||||
|
||||
do_execsql_test 3.6 {
|
||||
CREATE TABLE w1(a PRIMARY KEY, b, c);
|
||||
INSERT INTO w1 VALUES(1.0, NULL, NULL);
|
||||
INSERT INTO w1 VALUES(33.0, NULL, NULL);
|
||||
INSERT INTO w1 VALUES(100.0, NULL, NULL);
|
||||
CREATE INDEX w1bc ON w1(b, c);
|
||||
}
|
||||
|
||||
do_intck_test 3.7 {
|
||||
}
|
||||
|
||||
imposter_edit w1 {
|
||||
CREATE TABLE imp(a, b, c);
|
||||
} {
|
||||
DELETE FROM imp WHERE a=33;
|
||||
INSERT INTO imp(a) VALUES(1234.5);
|
||||
INSERT INTO imp(a) VALUES(-1234.5);
|
||||
}
|
||||
|
||||
do_intck_test 3.8 {
|
||||
{surplus entry (33.0,2) in index sqlite_autoindex_w1_1}
|
||||
{entry (1234.5,4) missing from index sqlite_autoindex_w1_1}
|
||||
{entry (NULL,NULL,4) missing from index w1bc}
|
||||
{entry (-1234.5,5) missing from index sqlite_autoindex_w1_1}
|
||||
{entry (NULL,NULL,5) missing from index w1bc}
|
||||
{surplus entry (NULL,NULL,2) in index w1bc}
|
||||
}
|
||||
|
||||
finish_test
|
||||
@@ -1,56 +0,0 @@
|
||||
# 2024 Feb 18
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source $testdir/tester.tcl
|
||||
|
||||
proc do_intck {db {bSuspend 0}} {
|
||||
set ic [sqlite3_intck $db main]
|
||||
|
||||
set ret [list]
|
||||
while {"SQLITE_OK"==[$ic step]} {
|
||||
set msg [$ic message]
|
||||
if {$msg!=""} {
|
||||
lappend ret $msg
|
||||
}
|
||||
if {$bSuspend} {
|
||||
$ic unlock
|
||||
#puts "SQL: [$ic test_sql {}]"
|
||||
#execsql_pp "EXPLAIN query plan [$ic test_sql {}]"
|
||||
#explain_i [$ic test_sql {}]
|
||||
}
|
||||
}
|
||||
|
||||
set err [$ic error]
|
||||
if {[lindex $err 0]!="SQLITE_OK"} {
|
||||
error $err
|
||||
}
|
||||
$ic close
|
||||
|
||||
return $ret
|
||||
}
|
||||
|
||||
proc intck_sql {db tbl} {
|
||||
set ic [sqlite3_intck $db main]
|
||||
set sql [$ic test_sql $tbl]
|
||||
$ic close
|
||||
return $sql
|
||||
}
|
||||
|
||||
proc do_intck_test {tn expect} {
|
||||
uplevel [list do_test $tn.a [list do_intck db] [list {*}$expect]]
|
||||
uplevel [list do_test $tn.b [list do_intck db 1] [list {*}$expect]]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
# 2024 February 24
|
||||
#
|
||||
# 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]] intck_common.tcl]
|
||||
set testprefix intckbusy
|
||||
|
||||
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
|
||||
INSERT INTO t1 VALUES(1, 2, 3);
|
||||
INSERT INTO t1 VALUES(2, 'two', 'three');
|
||||
INSERT INTO t1 VALUES(3, NULL, NULL);
|
||||
CREATE INDEX i1 ON t1(b, c);
|
||||
}
|
||||
|
||||
sqlite3 db2 test.db
|
||||
|
||||
do_execsql_test -db db2 1.1 {
|
||||
BEGIN EXCLUSIVE;
|
||||
INSERT INTO t1 VALUES(4, 5, 6);
|
||||
}
|
||||
|
||||
do_test 1.2 {
|
||||
set ic [sqlite3_intck db main]
|
||||
$ic step
|
||||
} {SQLITE_BUSY}
|
||||
do_test 1.3 {
|
||||
$ic unlock
|
||||
} {SQLITE_BUSY}
|
||||
do_test 1.4 {
|
||||
$ic error
|
||||
} {SQLITE_BUSY {database is locked}}
|
||||
do_test 1.4 {
|
||||
$ic close
|
||||
} {}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -1,235 +0,0 @@
|
||||
# 2024 Feb 21
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# The focus of this file is testing the intck extensions response
|
||||
# to corruption at the b-tree level.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] intck_common.tcl]
|
||||
set testprefix intckcorrupt
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_test 1.0 {
|
||||
sqlite3 db {}
|
||||
db deserialize [decode_hexdb {
|
||||
| size 356352 pagesize 4096 filename crash-acaae0347204ae.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 d0 00 00 00 .....@ ........
|
||||
| 32: 40 00 ea 00 00 00 00 00 00 40 00 00 00 40 00 00 @........@...@..
|
||||
| 96: 00 00 00 00 0d 00 00 00 04 0e 9c 00 0f ad 0f 4f ...............O
|
||||
| 112: 0e fc 0e 9c 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
| 3728: 00 00 00 00 00 00 00 00 00 00 00 00 5e 04 07 17 ............^...
|
||||
| 3744: 1f 1f 01 81 0b 74 61 62 6c 65 74 31 5f 70 61 72 .....tablet1_par
|
||||
| 3760: 65 6e 74 74 31 5f 70 61 72 65 6e 74 04 43 52 45 entt1_parent.CRE
|
||||
| 3776: 41 54 45 20 54 41 42 4c 45 20 22 74 31 5f 70 61 ATE TABLE .t1_pa
|
||||
| 3792: 72 65 6e 74 22 28 6e 6f 64 65 6e 6f 20 49 4e 54 rent.(nodeno INT
|
||||
| 3808: 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 EGER PRIMARY KEY
|
||||
| 3824: 2c 70 61 72 65 6e 74 6e 6f 64 65 29 51 03 06 17 ,parentnode)Q...
|
||||
| 3840: 1b 1b 01 7b 74 61 62 6c 65 74 31 5f 6e 6f 64 65 ....tablet1_node
|
||||
| 3856: 74 31 5f 6e 6f 64 65 03 43 52 45 41 54 45 20 54 t1_node.CREATE T
|
||||
| 3872: 41 42 4c 45 20 22 74 31 5f 6e 6f 64 65 22 28 6e ABLE .t1_node.(n
|
||||
| 3888: 6f 64 65 6e 6f 20 49 4e 54 45 47 45 52 20 50 52 odeno INTEGER PR
|
||||
| 3904: 49 4d 41 52 59 20 4b 45 59 2c 64 61 74 61 29 5c IMARY KEY,data).
|
||||
| 3920: 02 07 17 1d 1d 01 81 0b 74 61 62 6c 65 74 31 5f ........tablet1_
|
||||
| 3936: 72 6f 77 69 64 74 31 5f 72 6f 77 69 64 02 43 52 rowidt1_rowid.CR
|
||||
| 3952: 45 41 54 45 20 54 41 42 4c 45 20 22 74 31 5f 72 EATE TABLE .t1_r
|
||||
| 3968: 6f 77 69 64 22 28 72 6f 77 69 64 20 49 4e 54 45 owid.(rowid INTE
|
||||
| 3984: 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c GER PRIMARY KEY,
|
||||
| 4000: 6e 6f 64 65 6e 6f 2c 61 30 2c 61 31 29 51 01 07 nodeno,a0,a1)Q..
|
||||
| 4016: 17 11 11 08 81 0f 74 61 62 6c 65 74 31 74 31 43 ......tablet1t1C
|
||||
| 4032: 52 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 REATE VIRTUAL TA
|
||||
| 4048: 42 4c 45 20 74 31 20 55 53 49 4e 47 20 72 74 72 BLE t1 USING rtr
|
||||
| 4064: 65 65 28 69 64 2c 78 30 20 50 52 49 4d 41 52 59 ee(id,x0 PRIMARY
|
||||
| 4080: 20 4b 45 59 2c 70 61 72 65 6e 74 6e 6f 64 65 29 KEY,parentnode)
|
||||
| page 2 offset 4096
|
||||
| 0: 51 03 06 17 1b 1b 01 7b 74 61 62 6c 65 74 31 5f Q.......tablet1_
|
||||
| 16: 6e 6f 64 65 74 31 5f 6e 6f 64 65 03 43 52 45 41 nodet1_node.CREA
|
||||
| 32: 54 45 20 54 41 42 4c 45 20 22 74 31 5f 6e 6f 64 TE TABLE .t1_nod
|
||||
| 48: 65 22 28 6e 6f 64 65 6e 6f 20 49 4e 54 45 47 45 e.(nodeno INTEGE
|
||||
| 64: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 64 61 R PRIMARY KEY,da
|
||||
| 80: 74 61 29 5c 02 07 17 1d 1d 01 81 0b 74 61 62 6c ta).........tabl
|
||||
| 96: 65 74 31 5f 72 6f 77 69 64 74 31 5f 72 6f 77 69 et1_rowidt1_rowi
|
||||
| 112: 64 02 43 52 45 41 54 45 20 54 41 42 4c 45 00 00 d.CREATE TABLE..
|
||||
| 128: 01 0a 02 00 00 00 01 0e 0d 00 00 00 00 24 0e 0d .............$..
|
||||
| 144: 0c 1a 06 85 50 46 60 27 70 08 00 00 00 00 00 00 ....PF`'p.......
|
||||
| 3824: 00 00 00 00 00 00 00 0d 0e 05 00 09 1d 00 74 6f ..............to
|
||||
| 3840: 79 20 68 61 6c 66 10 0d 05 00 09 23 00 62 6f 74 y half.....#.bot
|
||||
| 3856: 74 6f 6d 20 68 61 6c 66 0f 0c 05 00 09 21 00 72 tom half.....!.r
|
||||
| 3872: 69 67 68 74 20 68 61 6c 66 0e 0b 05 00 09 1f 00 ight half.......
|
||||
| 3888: 6c 65 66 74 20 43 15 f6 e6 f6 46 50 34 35 24 54 left C....FP45$T
|
||||
| 3904: 15 44 52 05 44 14 24 c4 52 02 27 43 15 f6 e6 f6 .DR.D.$.R.'C....
|
||||
| 3920: 46 52 22 8e 6f 64 65 6e 6f 20 49 4e 54 45 47 45 FR..odeno INTEGE
|
||||
| 3936: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 64 61 R PRIMARY KEY,da
|
||||
| 3952: 74 61 29 5c 02 07 17 1d 1d 01 81 0b 74 61 62 6c ta).........tabl
|
||||
| 3968: 65 74 31 5f 72 6f 74 74 6f 6d 20 65 64 67 65 0f et1_rottom edge.
|
||||
| 3984: 07 05 00 09 21 00 72 69 67 68 74 20 65 64 67 65 ....!.right edge
|
||||
| 4000: 0e 06 05 00 09 1f 00 6c 65 66 74 20 65 64 67 65 .......left edge
|
||||
| 4016: 0b 05 05 00 09 19 00 63 65 6e 74 65 72 17 04 05 .......center...
|
||||
| 4032: 00 09 31 00 75 70 70 65 72 2d 72 69 67 68 74 20 ..1.upper-right
|
||||
| 4048: 63 6f 72 6e 65 72 17 03 05 00 09 31 00 6c 6f 77 corner.....1.low
|
||||
| 4064: 65 72 2d 72 69 67 68 74 20 63 6f 72 6e 65 72 16 er-right corner.
|
||||
| 4080: 02 05 00 09 2f 00 75 70 70 65 72 2d 6c 65 66 74 ..../.upper-left
|
||||
| page 3 offset 8192
|
||||
| 0: 20 63 6f 72 6e 65 72 16 01 05 00 09 2f 01 8c 6f corner...../..o
|
||||
| 16: 77 65 72 2d 6c 53 51 4c 69 74 65 20 66 6f 72 6d wer-lSQLite form
|
||||
| 32: 61 74 20 33 00 10 00 01 01 00 40 20 20 00 00 00 at 3......@ ...
|
||||
| 48: 00 00 00 00 2f 00 00 0d eb 13 00 00 00 03 00 00 ..../...........
|
||||
| 64: 00 04 00 00 00 00 00 00 00 06 00 00 00 01 00 00 ................
|
||||
| 80: 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 ................
|
||||
| page 6 offset 20480
|
||||
| 128: 00 00 00 00 00 00 00 00 97 3d 04 ae 7c 01 00 00 .........=..|...
|
||||
| 624: 00 00 00 00 00 00 21 97 3d 04 ae 7c 01 00 00 00 ......!.=..|....
|
||||
| 1120: 00 00 00 00 00 20 97 3d 04 ae 7c 01 00 00 00 00 ..... .=..|.....
|
||||
| 1616: 00 00 00 00 1f 97 3d 04 ae 7c 01 00 00 00 00 00 ......=..|......
|
||||
| 2112: 00 00 00 1e 97 3d 04 ae 7c 01 00 00 00 00 00 00 .....=..|.......
|
||||
| 2608: 00 00 1d 97 d3 d0 4a e7 c0 00 00 00 00 00 00 00 ......J.........
|
||||
| 3088: 00 00 00 00 00 00 00 00 00 00 00 00 01 f3 00 00 ................
|
||||
| 3600: 23 97 3d 04 ae 7c 01 00 00 00 00 00 00 00 00 00 #.=..|..........
|
||||
| 4080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 26 ...............&
|
||||
| page 8 offset 28672
|
||||
| 0: 0d 00 00 00 01 04 30 00 04 30 00 00 00 00 00 00 ......0..0......
|
||||
| 1072: 97 4d 1e 14 00 ae 7c 00 00 00 00 00 00 00 00 00 .M....|.........
|
||||
| 1088: 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 ................
|
||||
| 4080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 ................
|
||||
| page 10 offset 36864
|
||||
| 0: 0d 00 00 00 01 04 30 00 04 30 00 00 00 00 00 00 ......0..0......
|
||||
| 1072: 9a ee c1 80 fd 78 1f ce 1b ae eb b4 00 00 00 00 .....x..........
|
||||
| 1088: 13 20 ff 20 00 70 00 00 00 60 50 00 00 00 11 e0 . . .p...`P.....
|
||||
| 1104: 00 00 00 70 00 00 00 60 50 05 35 14 c6 97 46 52 ...p...`P.5...FR
|
||||
| 1120: 06 66 f7 26 d6 17 42 03 30 01 00 00 10 10 04 02 .f.&..B.0.......
|
||||
| 1136: 02 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 .........@......
|
||||
| 1152: 00 00 00 00 00 40 00 00 00 40 00 00 00 00 00 00 .....@...@......
|
||||
| 4080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 05 ................
|
||||
| page 12 offset 45056
|
||||
| 0: 0d 00 00 00 01 04 30 00 04 30 e1 b4 30 97 4d 46 ......0..0..0.MF
|
||||
| 16: 14 00 ae 7c 00 00 00 00 00 00 00 03 00 00 43 00 ...|..........C.
|
||||
| page 47 offset 188416
|
||||
| 2512: 00 00 00 00 00 00 00 00 be 00 00 00 00 00 00 00 ................
|
||||
| page 87 offset 352256
|
||||
| 2512: 00 00 00 00 00 00 00 00 aa 00 00 00 00 00 00 00 ................
|
||||
| end crash-acaae0347204ae.db
|
||||
}]} {}
|
||||
|
||||
do_intck_test 1.1 {
|
||||
{corruption found while reading database schema}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_test 2.0 {
|
||||
sqlite3 db {}
|
||||
db deserialize [decode_hexdb {
|
||||
| size 28672 pagesize 4096 filename crash-3afa1ca9e9c1bd.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 07 .....@ ........
|
||||
| 32: 00 00 00 00 00 00 00 00 00 00 00 06 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 06 0e 88 00 0f b8 0f 6d ...............m
|
||||
| 112: 0f 3a 0f 0b 0e d5 0e 88 01 00 00 00 00 00 00 00 .:..............
|
||||
| 3712: 00 00 00 00 00 00 00 00 4b 06 06 17 25 25 01 5b ........K...%%.[
|
||||
| 3728: 74 61 62 6c 65 73 71 6c 69 74 65 5f 73 74 61 74 tablesqlite_stat
|
||||
| 3744: 31 73 71 6c 69 74 65 5f 73 74 61 74 31 07 43 52 1sqlite_stat1.CR
|
||||
| 3760: 45 41 54 45 20 54 41 42 4c 45 20 73 71 6c 69 74 EATE TABLE sqlit
|
||||
| 3776: 65 5f 73 74 61 74 31 28 74 62 6c 2c 69 64 78 2c e_stat1(tbl,idx,
|
||||
| 3792: 73 74 61 74 29 34 05 06 17 13 11 01 53 69 6e 64 stat)4......Sind
|
||||
| 3808: 65 78 63 31 63 63 31 06 43 52 45 41 54 45 20 55 exc1cc1.CREATE U
|
||||
| 3824: 4e 49 51 55 45 20 49 4e 44 45 58 20 63 31 63 20 NIQUE INDEX c1c
|
||||
| 3840: 4f 4e 20 63 31 28 63 2c 20 62 29 2d 04 06 17 13 ON c1(c, b)-....
|
||||
| 3856: 11 01 45 69 6e 64 65 78 63 31 64 63 31 05 43 52 ..Eindexc1dc1.CR
|
||||
| 3872: 45 41 54 45 20 49 4e 44 45 58 20 63 31 64 20 4f EATE INDEX c1d O
|
||||
| 3888: 4e 20 63 31 28 64 2c 20 62 29 31 03 06 17 13 11 N c1(d, b)1.....
|
||||
| 3904: 01 4d 69 6e 64 65 78 62 31 63 62 31 05 43 52 45 .Mindexb1cb1.CRE
|
||||
| 3920: 41 54 45 20 55 4e 49 51 55 45 20 49 4e 44 45 58 ATE UNIQUE INDEX
|
||||
| 3936: 20 62 31 63 20 4f 4e 20 62 31 28 63 29 49 02 06 b1c ON b1(c)I..
|
||||
| 3952: 17 11 11 0f 7f 74 61 62 6c 65 63 31 63 31 03 43 .....tablec1c1.C
|
||||
| 3968: 52 45 41 54 45 20 54 41 42 4c 45 20 63 31 28 61 REATE TABLE c1(a
|
||||
| 3984: 20 49 4e 54 20 50 52 49 4d 41 52 59 20 4b 45 59 INT PRIMARY KEY
|
||||
| 4000: 2c 20 62 2c 20 63 2c 20 64 29 20 57 49 54 48 4f , b, c, d) WITHO
|
||||
| 4016: 55 54 20 52 4f 57 49 44 46 01 06 17 11 11 01 79 UT ROWIDF......y
|
||||
| 4032: 74 61 62 6c 65 62 31 62 31 02 43 52 45 41 54 45 tableb1b1.CREATE
|
||||
| 4048: 20 54 41 42 4c 45 20 62 31 28 61 20 49 4e 54 20 TABLE b1(a INT
|
||||
| 4064: 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 62 2c 20 PRIMARY KEY, b,
|
||||
| 4080: 63 29 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 c) WITHOUT ROWID
|
||||
| page 2 offset 4096
|
||||
| 0: 0a 00 00 00 07 0f ca 00 0f fa 0f f2 0f ea 0f e2 ................
|
||||
| 16: 0f da 00 00 00 01 00 00 00 00 00 00 00 00 00 00 ................
|
||||
| 4032: 00 00 00 00 00 00 00 00 00 00 07 04 01 0f 01 06 ................
|
||||
| 4048: 67 07 07 04 01 0f 01 06 66 06 07 04 01 0f 01 05 g.......f.......
|
||||
| 4064: 65 05 07 04 01 0f 01 04 64 04 07 04 01 0f 01 03 e.......d.......
|
||||
| 4080: 63 03 07 04 01 0f 01 02 62 0f 05 04 09 0f 09 61 c.......b......a
|
||||
| page 3 offset 8192
|
||||
| 0: 0a 00 00 00 07 0f bd 00 0f f9 0f ef 0f e5 0f db ................
|
||||
| 16: 0f d1 0f c7 0f bd 00 00 00 00 01 00 00 00 00 00 ................
|
||||
| 4016: 00 00 00 00 00 00 00 00 00 00 00 00 00 09 05 01 ................
|
||||
| 4032: 0f 01 01 07 61 07 07 09 05 01 0f 01 01 06 61 06 ....a.........a.
|
||||
| 4048: 06 09 05 01 0f 01 01 05 61 05 05 09 05 01 0f 01 ........a.......
|
||||
| 4064: 01 04 61 04 04 09 05 01 0f 01 01 03 61 03 03 09 ..a.........a...
|
||||
| 4080: 05 01 0f 01 01 02 61 0f 02 06 05 09 0f 09 09 61 ......a........a
|
||||
| page 4 offset 12288
|
||||
| 0: 0a 00 00 00 07 0f d8 00 0f fc 0f f0 0f ea 0f e4 ................
|
||||
| 16: 0f de 0f d8 0f f6 00 00 00 00 00 00 00 00 00 00 ................
|
||||
| 4048: 00 00 00 00 00 00 00 00 05 03 01 01 07 07 05 03 ................
|
||||
| 4064: 01 01 06 06 05 03 01 01 05 05 05 03 01 01 04 04 ................
|
||||
| 4080: 05 03 01 01 03 03 05 03 01 01 0f 02 03 03 09 09 ................
|
||||
| page 5 offset 16384
|
||||
| 0: 0a 00 00 00 07 0f ca 00 0f fa 0f f2 0f ea 0f 00 ................
|
||||
| 4032: 00 00 00 00 00 00 00 00 00 00 07 04 01 0f 01 07 ................
|
||||
| 4048: 61 07 07 04 01 0f 01 06 61 06 07 04 01 0f 01 05 a.......a.......
|
||||
| 4064: 61 05 07 04 01 1f 01 04 61 04 07 04 01 0f 01 03 a.......a.......
|
||||
| 4080: 61 03 07 04 01 0f 01 02 61 02 05 04 09 0f 09 61 a.......a......a
|
||||
| page 6 offset 20480
|
||||
| 0: 0a 00 00 00 07 0f ca 00 0f fa 0f ea 0f e2 00 00 ................
|
||||
| 4032: 00 00 00 00 00 00 00 00 00 00 07 04 01 0f 01 07 ................
|
||||
| 4048: 61 07 07 04 01 0f 01 06 61 06 07 04 01 0f 01 05 a.......a.......
|
||||
| 4064: 61 05 07 04 01 0f 01 04 61 04 07 04 01 0f 01 03 a.......a.......
|
||||
| 4080: 61 03 07 04 01 0f 01 0f 61 02 05 04 09 0f 09 61 a.......a......a
|
||||
| page 7 offset 24576
|
||||
| 0: 0d 00 00 00 05 0f 1c 00 0f f0 0f e0 0f d3 0f c5 ................
|
||||
| 16: 0f b8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
| 4016: 00 00 00 00 00 00 00 00 0b 05 04 11 11 13 62 31 ..............b1
|
||||
| 4032: 62 31 37 20 31 0c 04 04 11 13 13 62 31 62 31 63 b17 1......b1b1c
|
||||
| 4048: 37 20 31 0b 03 04 11 11 13 63 31 63 31 37 20 31 7 1......c1c17 1
|
||||
| 4064: 0e 02 04 11 13 07 63 31 63 31 64 37 20 31 20 31 ......c1c1d7 1 1
|
||||
| 4080: 0e 01 04 11 13 17 63 31 63 31 63 37 20 31 00 00 ......c1c1c7 1..
|
||||
| end crash-3afa1ca9e9c1bd.db
|
||||
}]} {}
|
||||
|
||||
do_intck_test 2.1 {
|
||||
{corruption found while reading database schema}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
PRAGMA page_size = 1024;
|
||||
CREATE TABLE t1(a, b);
|
||||
CREATE INDEX i1 ON t1(a);
|
||||
INSERT INTO t1 VALUES(1, 1), (2, 2), (3, 3);
|
||||
}
|
||||
|
||||
do_test 3.1 {
|
||||
set pgno [db one {SELECT rootpage FROM sqlite_schema WHERE name='t1'}]
|
||||
db close
|
||||
hexio_write test.db [expr ($pgno-1)*1024] 0000
|
||||
} {2}
|
||||
|
||||
sqlite3 db test.db
|
||||
do_intck_test 3.2 {
|
||||
{corruption found while scanning database object i1}
|
||||
{corruption found while scanning database object t1}
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# 2024 February 24
|
||||
#
|
||||
# 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]] intck_common.tcl]
|
||||
set testprefix intckfault
|
||||
|
||||
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
|
||||
INSERT INTO t1 VALUES(1, 2, 3);
|
||||
INSERT INTO t1 VALUES(2, 'two', 'three');
|
||||
INSERT INTO t1 VALUES(3, NULL, NULL);
|
||||
CREATE INDEX i1 ON t1(b, c);
|
||||
}
|
||||
|
||||
do_faultsim_test 1 -faults oom-t* -prep {
|
||||
} -body {
|
||||
set ::ic [sqlite3_intck db main]
|
||||
set nStep 0
|
||||
while {"SQLITE_OK"==[$::ic step]} {
|
||||
incr nStep
|
||||
if {$nStep==3} { $::ic unlock }
|
||||
}
|
||||
set res [$::ic error]
|
||||
$::ic close
|
||||
set res
|
||||
} -test {
|
||||
catch { $::ic close }
|
||||
puts $testresult
|
||||
puts $testnfail
|
||||
faultsim_test_result {0 {SQLITE_OK {}}} {0 {SQLITE_NOMEM {}}} {0 {SQLITE_NOMEM {out of memory}}}
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -1,941 +0,0 @@
|
||||
/*
|
||||
** 2024-02-08
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
*/
|
||||
|
||||
#include "sqlite3intck.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/*
|
||||
** nKeyVal:
|
||||
** The number of values that make up the 'key' for the current pCheck
|
||||
** statement.
|
||||
**
|
||||
** rc:
|
||||
** Error code returned by most recent sqlite3_intck_step() or
|
||||
** sqlite3_intck_unlock() call. This is set to SQLITE_DONE when
|
||||
** the integrity-check operation is finished.
|
||||
**
|
||||
** zErr:
|
||||
** If the object has entered the error state, this is the error message.
|
||||
** Is freed using sqlite3_free() when the object is deleted.
|
||||
**
|
||||
** zTestSql:
|
||||
** The value returned by the most recent call to sqlite3_intck_testsql().
|
||||
** Each call to testsql() frees the previous zTestSql value (using
|
||||
** sqlite3_free()) and replaces it with the new value it will return.
|
||||
*/
|
||||
struct sqlite3_intck {
|
||||
sqlite3 *db;
|
||||
const char *zDb; /* Copy of zDb parameter to _open() */
|
||||
char *zObj; /* Current object. Or NULL. */
|
||||
|
||||
sqlite3_stmt *pCheck; /* Current check statement */
|
||||
char *zKey;
|
||||
int nKeyVal;
|
||||
|
||||
char *zMessage;
|
||||
int bCorruptSchema;
|
||||
|
||||
int rc; /* Error code */
|
||||
char *zErr; /* Error message */
|
||||
char *zTestSql; /* Returned by sqlite3_intck_test_sql() */
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
** Some error has occurred while using database p->db. Save the error message
|
||||
** and error code currently held by the database handle in p->rc and p->zErr.
|
||||
*/
|
||||
static void intckSaveErrmsg(sqlite3_intck *p){
|
||||
p->rc = sqlite3_errcode(p->db);
|
||||
sqlite3_free(p->zErr);
|
||||
p->zErr = sqlite3_mprintf("%s", sqlite3_errmsg(p->db));
|
||||
}
|
||||
|
||||
/*
|
||||
** If the handle passed as the first argument is already in the error state,
|
||||
** then this function is a no-op (returns NULL immediately). Otherwise, if an
|
||||
** error occurs within this function, it leaves an error in said handle.
|
||||
**
|
||||
** Otherwise, this function attempts to prepare SQL statement zSql and
|
||||
** return the resulting statement handle to the user.
|
||||
*/
|
||||
static sqlite3_stmt *intckPrepare(sqlite3_intck *p, const char *zSql){
|
||||
sqlite3_stmt *pRet = 0;
|
||||
if( p->rc==SQLITE_OK ){
|
||||
p->rc = sqlite3_prepare_v2(p->db, zSql, -1, &pRet, 0);
|
||||
if( p->rc!=SQLITE_OK ){
|
||||
intckSaveErrmsg(p);
|
||||
assert( pRet==0 );
|
||||
}
|
||||
}
|
||||
return pRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** If the handle passed as the first argument is already in the error state,
|
||||
** then this function is a no-op (returns NULL immediately). Otherwise, if an
|
||||
** error occurs within this function, it leaves an error in said handle.
|
||||
**
|
||||
** Otherwise, this function treats argument zFmt as a printf() style format
|
||||
** string. It formats it according to the trailing arguments and then
|
||||
** attempts to prepare the results and return the resulting prepared
|
||||
** statement.
|
||||
*/
|
||||
static sqlite3_stmt *intckPrepareFmt(sqlite3_intck *p, const char *zFmt, ...){
|
||||
sqlite3_stmt *pRet = 0;
|
||||
va_list ap;
|
||||
char *zSql = 0;
|
||||
va_start(ap, zFmt);
|
||||
zSql = sqlite3_vmprintf(zFmt, ap);
|
||||
if( p->rc==SQLITE_OK && zSql==0 ){
|
||||
p->rc = SQLITE_NOMEM;
|
||||
}
|
||||
pRet = intckPrepare(p, zSql);
|
||||
sqlite3_free(zSql);
|
||||
va_end(ap);
|
||||
return pRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** Finalize SQL statement pStmt. If an error occurs and the handle passed
|
||||
** as the first argument does not already contain an error, store the
|
||||
** error in the handle.
|
||||
*/
|
||||
static void intckFinalize(sqlite3_intck *p, sqlite3_stmt *pStmt){
|
||||
int rc = sqlite3_finalize(pStmt);
|
||||
if( p->rc==SQLITE_OK && rc!=SQLITE_OK ){
|
||||
intckSaveErrmsg(p);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** If there is already an error in handle p, return it. Otherwise, call
|
||||
** sqlite3_step() on the statement handle and return that value.
|
||||
*/
|
||||
static int intckStep(sqlite3_intck *p, sqlite3_stmt *pStmt){
|
||||
if( p->rc ) return p->rc;
|
||||
return sqlite3_step(pStmt);
|
||||
}
|
||||
|
||||
/*
|
||||
** Execute SQL statement zSql. There is no way to obtain any results
|
||||
** returned by the statement. This function uses the sqlite3_intck error
|
||||
** code convention.
|
||||
*/
|
||||
static void intckExec(sqlite3_intck *p, const char *zSql){
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
pStmt = intckPrepare(p, zSql);
|
||||
intckStep(p, pStmt);
|
||||
intckFinalize(p, pStmt);
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around sqlite3_mprintf() that uses the sqlite3_intck error
|
||||
** code convention.
|
||||
*/
|
||||
static char *intckMprintf(sqlite3_intck *p, const char *zFmt, ...){
|
||||
va_list ap;
|
||||
char *zRet = 0;
|
||||
va_start(ap, zFmt);
|
||||
zRet = sqlite3_vmprintf(zFmt, ap);
|
||||
if( p->rc==SQLITE_OK ){
|
||||
if( zRet==0 ){
|
||||
p->rc = SQLITE_NOMEM;
|
||||
}
|
||||
}else{
|
||||
sqlite3_free(zRet);
|
||||
zRet = 0;
|
||||
}
|
||||
return zRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** This is used by sqlite3_intck_unlock() to save the vector key value
|
||||
** required to restart the current pCheck query as a nul-terminated string
|
||||
** in p->zKey.
|
||||
*/
|
||||
static void intckSaveKey(sqlite3_intck *p){
|
||||
int ii;
|
||||
char *zSql = 0;
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
sqlite3_stmt *pXinfo = 0;
|
||||
const char *zDir = 0;
|
||||
|
||||
assert( p->pCheck );
|
||||
assert( p->zKey==0 );
|
||||
|
||||
pXinfo = intckPrepareFmt(p,
|
||||
"SELECT group_concat(desc, '') FROM %Q.sqlite_schema s, "
|
||||
"pragma_index_xinfo(%Q, %Q) "
|
||||
"WHERE s.type='index' AND s.name=%Q",
|
||||
p->zDb, p->zObj, p->zDb, p->zObj
|
||||
);
|
||||
if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXinfo) ){
|
||||
zDir = (const char*)sqlite3_column_text(pXinfo, 0);
|
||||
}
|
||||
|
||||
if( zDir==0 ){
|
||||
/* Object is a table, not an index. This is the easy case,as there are
|
||||
** no DESC columns or NULL values in a primary key. */
|
||||
const char *zSep = "SELECT '(' || ";
|
||||
for(ii=0; ii<p->nKeyVal; ii++){
|
||||
zSql = intckMprintf(p, "%z%squote(?)", zSql, zSep);
|
||||
zSep = " || ', ' || ";
|
||||
}
|
||||
zSql = intckMprintf(p, "%z || ')'", zSql);
|
||||
}else{
|
||||
|
||||
/* Object is an index. */
|
||||
assert( p->nKeyVal>1 );
|
||||
for(ii=p->nKeyVal; ii>0; ii--){
|
||||
int bLastIsDesc = zDir[ii-1]=='1';
|
||||
int bLastIsNull = sqlite3_column_type(p->pCheck, ii)==SQLITE_NULL;
|
||||
const char *zLast = sqlite3_column_name(p->pCheck, ii);
|
||||
char *zLhs = 0;
|
||||
char *zRhs = 0;
|
||||
char *zWhere = 0;
|
||||
|
||||
if( bLastIsNull ){
|
||||
if( bLastIsDesc ) continue;
|
||||
zWhere = intckMprintf(p, "'%s IS NOT NULL'", zLast);
|
||||
}else{
|
||||
const char *zOp = bLastIsDesc ? "<" : ">";
|
||||
zWhere = intckMprintf(p, "'%s %s ' || quote(?%d)", zLast, zOp, ii);
|
||||
}
|
||||
|
||||
if( ii>1 ){
|
||||
const char *zLhsSep = "";
|
||||
const char *zRhsSep = "";
|
||||
int jj;
|
||||
for(jj=0; jj<ii-1; jj++){
|
||||
const char *zAlias = (const char*)sqlite3_column_name(p->pCheck,jj+1);
|
||||
zLhs = intckMprintf(p, "%z%s%s", zLhs, zLhsSep, zAlias);
|
||||
zRhs = intckMprintf(p, "%z%squote(?%d)", zRhs, zRhsSep, jj+1);
|
||||
zLhsSep = ",";
|
||||
zRhsSep = " || ',' || ";
|
||||
}
|
||||
|
||||
zWhere = intckMprintf(p,
|
||||
"'(%z) IS (' || %z || ') AND ' || %z",
|
||||
zLhs, zRhs, zWhere);
|
||||
}
|
||||
zWhere = intckMprintf(p, "'WHERE ' || %z", zWhere);
|
||||
|
||||
zSql = intckMprintf(p, "%z%s(quote( %z ) )",
|
||||
zSql,
|
||||
(zSql==0 ? "VALUES" : ",\n "),
|
||||
zWhere
|
||||
);
|
||||
}
|
||||
zSql = intckMprintf(p,
|
||||
"WITH wc(q) AS (\n%z\n)"
|
||||
"SELECT 'VALUES' || group_concat('(' || q || ')', ',\n ') FROM wc"
|
||||
, zSql
|
||||
);
|
||||
}
|
||||
|
||||
pStmt = intckPrepare(p, zSql);
|
||||
if( p->rc==SQLITE_OK ){
|
||||
for(ii=0; ii<p->nKeyVal; ii++){
|
||||
sqlite3_bind_value(pStmt, ii+1, sqlite3_column_value(p->pCheck, ii+1));
|
||||
}
|
||||
if( SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
p->zKey = intckMprintf(p,"%s",(const char*)sqlite3_column_text(pStmt, 0));
|
||||
}
|
||||
intckFinalize(p, pStmt);
|
||||
}
|
||||
|
||||
sqlite3_free(zSql);
|
||||
intckFinalize(p, pXinfo);
|
||||
}
|
||||
|
||||
/*
|
||||
** Find the next database object (table or index) to check. If successful,
|
||||
** set sqlite3_intck.zObj to point to a nul-terminated buffer containing
|
||||
** the object's name before returning.
|
||||
*/
|
||||
static void intckFindObject(sqlite3_intck *p){
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
char *zPrev = p->zObj;
|
||||
p->zObj = 0;
|
||||
|
||||
assert( p->rc==SQLITE_OK );
|
||||
assert( p->pCheck==0 );
|
||||
|
||||
pStmt = intckPrepareFmt(p,
|
||||
"WITH tables(table_name) AS ("
|
||||
" SELECT name"
|
||||
" FROM %Q.sqlite_schema WHERE (type='table' OR type='index') AND rootpage"
|
||||
" UNION ALL "
|
||||
" SELECT 'sqlite_schema'"
|
||||
")"
|
||||
"SELECT table_name FROM tables "
|
||||
"WHERE ?1 IS NULL OR table_name%s?1 "
|
||||
"ORDER BY 1"
|
||||
, p->zDb, (p->zKey ? ">=" : ">")
|
||||
);
|
||||
|
||||
if( p->rc==SQLITE_OK ){
|
||||
sqlite3_bind_text(pStmt, 1, zPrev, -1, SQLITE_TRANSIENT);
|
||||
if( sqlite3_step(pStmt)==SQLITE_ROW ){
|
||||
p->zObj = intckMprintf(p,"%s",(const char*)sqlite3_column_text(pStmt, 0));
|
||||
}
|
||||
}
|
||||
intckFinalize(p, pStmt);
|
||||
|
||||
/* If this is a new object, ensure the previous key value is cleared. */
|
||||
if( sqlite3_stricmp(p->zObj, zPrev) ){
|
||||
sqlite3_free(p->zKey);
|
||||
p->zKey = 0;
|
||||
}
|
||||
|
||||
sqlite3_free(zPrev);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the size in bytes of the first token in nul-terminated buffer z.
|
||||
** For the purposes of this call, a token is either:
|
||||
**
|
||||
** * a quoted SQL string,
|
||||
* * a contiguous series of ascii alphabet characters, or
|
||||
* * any other single byte.
|
||||
*/
|
||||
static int intckGetToken(const char *z){
|
||||
char c = z[0];
|
||||
int iRet = 1;
|
||||
if( c=='\'' || c=='"' || c=='`' ){
|
||||
while( 1 ){
|
||||
if( z[iRet]==c ){
|
||||
iRet++;
|
||||
if( z[iRet]!=c ) break;
|
||||
}
|
||||
iRet++;
|
||||
}
|
||||
}
|
||||
else if( c=='[' ){
|
||||
while( z[iRet++]!=']' && z[iRet] );
|
||||
}
|
||||
else if( (c>='A' && c<='Z') || (c>='a' && c<='z') ){
|
||||
while( (z[iRet]>='A' && z[iRet]<='Z') || (z[iRet]>='a' && z[iRet]<='z') ){
|
||||
iRet++;
|
||||
}
|
||||
}
|
||||
|
||||
return iRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return true if argument c is an ascii whitespace character.
|
||||
*/
|
||||
static int intckIsSpace(char c){
|
||||
return (c==' ' || c=='\t' || c=='\n' || c=='\r');
|
||||
}
|
||||
|
||||
/*
|
||||
** Argument z points to the text of a CREATE INDEX statement. This function
|
||||
** identifies the part of the text that contains either the index WHERE
|
||||
** clause (if iCol<0) or the iCol'th column of the index.
|
||||
**
|
||||
** If (iCol<0), the identified fragment does not include the "WHERE" keyword,
|
||||
** only the expression that follows it. If (iCol>=0) then the identified
|
||||
** fragment does not include any trailing sort-order keywords - "ASC" or
|
||||
** "DESC".
|
||||
**
|
||||
** If the CREATE INDEX statement does not contain the requested field or
|
||||
** clause, NULL is returned and (*pnByte) is set to 0. Otherwise, a pointer to
|
||||
** the identified fragment is returned and output parameter (*pnByte) set
|
||||
** to its size in bytes.
|
||||
*/
|
||||
static const char *intckParseCreateIndex(const char *z, int iCol, int *pnByte){
|
||||
int iOff = 0;
|
||||
int iThisCol = 0;
|
||||
int iStart = 0;
|
||||
int nOpen = 0;
|
||||
|
||||
const char *zRet = 0;
|
||||
int nRet = 0;
|
||||
|
||||
int iEndOfCol = 0;
|
||||
|
||||
/* Skip forward until the first "(" token */
|
||||
while( z[iOff]!='(' ){
|
||||
iOff += intckGetToken(&z[iOff]);
|
||||
if( z[iOff]=='\0' ) return 0;
|
||||
}
|
||||
assert( z[iOff]=='(' );
|
||||
|
||||
nOpen = 1;
|
||||
iOff++;
|
||||
iStart = iOff;
|
||||
while( z[iOff] ){
|
||||
const char *zToken = &z[iOff];
|
||||
int nToken = 0;
|
||||
|
||||
/* Check if this is the end of the current column - either a "," or ")"
|
||||
** when nOpen==1. */
|
||||
if( nOpen==1 ){
|
||||
if( z[iOff]==',' || z[iOff]==')' ){
|
||||
if( iCol==iThisCol ){
|
||||
int iEnd = iEndOfCol ? iEndOfCol : iOff;
|
||||
nRet = (iEnd - iStart);
|
||||
zRet = &z[iStart];
|
||||
break;
|
||||
}
|
||||
iStart = iOff+1;
|
||||
while( intckIsSpace(z[iStart]) ) iStart++;
|
||||
iThisCol++;
|
||||
}
|
||||
if( z[iOff]==')' ) break;
|
||||
}
|
||||
if( z[iOff]=='(' ) nOpen++;
|
||||
if( z[iOff]==')' ) nOpen--;
|
||||
nToken = intckGetToken(zToken);
|
||||
|
||||
if( (nToken==3 && 0==sqlite3_strnicmp(zToken, "ASC", nToken))
|
||||
|| (nToken==4 && 0==sqlite3_strnicmp(zToken, "DESC", nToken))
|
||||
){
|
||||
iEndOfCol = iOff;
|
||||
}else if( 0==intckIsSpace(zToken[0]) ){
|
||||
iEndOfCol = 0;
|
||||
}
|
||||
|
||||
iOff += nToken;
|
||||
}
|
||||
|
||||
/* iStart is now the byte offset of 1 byte passed the final ')' in the
|
||||
** CREATE INDEX statement. Try to find a WHERE clause to return. */
|
||||
while( zRet==0 && z[iOff] ){
|
||||
int n = intckGetToken(&z[iOff]);
|
||||
if( n==5 && 0==sqlite3_strnicmp(&z[iOff], "where", 5) ){
|
||||
zRet = &z[iOff+5];
|
||||
nRet = strlen(zRet);
|
||||
}
|
||||
iOff += n;
|
||||
}
|
||||
|
||||
/* Trim any whitespace from the start and end of the returned string. */
|
||||
if( zRet ){
|
||||
while( intckIsSpace(zRet[0]) ){
|
||||
nRet--;
|
||||
zRet++;
|
||||
}
|
||||
while( nRet>0 && intckIsSpace(zRet[nRet-1]) ) nRet--;
|
||||
}
|
||||
|
||||
*pnByte = nRet;
|
||||
return zRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** User-defined SQL function wrapper for intckParseCreateIndex():
|
||||
**
|
||||
** SELECT parse_create_index(<sql>, <icol>);
|
||||
*/
|
||||
static void intckParseCreateIndexFunc(
|
||||
sqlite3_context *pCtx,
|
||||
int nVal,
|
||||
sqlite3_value **apVal
|
||||
){
|
||||
const char *zSql = (const char*)sqlite3_value_text(apVal[0]);
|
||||
int idx = sqlite3_value_int(apVal[1]);
|
||||
const char *zRes = 0;
|
||||
int nRes = 0;
|
||||
|
||||
assert( nVal==2 );
|
||||
if( zSql ){
|
||||
zRes = intckParseCreateIndex(zSql, idx, &nRes);
|
||||
}
|
||||
sqlite3_result_text(pCtx, zRes, nRes, SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return true if sqlite3_intck.db has automatic indexes enabled, false
|
||||
** otherwise.
|
||||
*/
|
||||
static int intckGetAutoIndex(sqlite3_intck *p){
|
||||
int bRet = 0;
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
pStmt = intckPrepare(p, "PRAGMA automatic_index");
|
||||
if( SQLITE_ROW==intckStep(p, pStmt) ){
|
||||
bRet = sqlite3_column_int(pStmt, 0);
|
||||
}
|
||||
intckFinalize(p, pStmt);
|
||||
return bRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return true if zObj is an index, or false otherwise.
|
||||
*/
|
||||
static int intckIsIndex(sqlite3_intck *p, const char *zObj){
|
||||
int bRet = 0;
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
pStmt = intckPrepareFmt(p,
|
||||
"SELECT 1 FROM %Q.sqlite_schema WHERE name=%Q AND type='index'",
|
||||
p->zDb, zObj
|
||||
);
|
||||
if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
bRet = 1;
|
||||
}
|
||||
intckFinalize(p, pStmt);
|
||||
return bRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a pointer to a nul-terminated buffer containing the SQL statement
|
||||
** used to check database object zObj (a table or index) for corruption.
|
||||
** If parameter zPrev is not NULL, then it must be a string containing the
|
||||
** vector key required to restart the check where it left off last time.
|
||||
** If pnKeyVal is not NULL, then (*pnKeyVal) is set to the number of
|
||||
** columns in the vector key value for the specified object.
|
||||
**
|
||||
** This function uses the sqlite3_intck error code convention.
|
||||
*/
|
||||
static char *intckCheckObjectSql(
|
||||
sqlite3_intck *p, /* Integrity check object */
|
||||
const char *zObj, /* Object (table or index) to scan */
|
||||
const char *zPrev, /* Restart key vector, if any */
|
||||
int *pnKeyVal /* OUT: Number of key-values for this scan */
|
||||
){
|
||||
char *zRet = 0;
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
int bAutoIndex = 0;
|
||||
int bIsIndex = 0;
|
||||
|
||||
const char *zCommon =
|
||||
/* Relation without_rowid also contains just one row. Column "b" is
|
||||
** set to true if the table being examined is a WITHOUT ROWID table,
|
||||
** or false otherwise. */
|
||||
", without_rowid(b) AS ("
|
||||
" SELECT EXISTS ("
|
||||
" SELECT 1 FROM tabname, pragma_index_list(tab, db) AS l"
|
||||
" WHERE origin='pk' "
|
||||
" AND NOT EXISTS (SELECT 1 FROM sqlite_schema WHERE name=l.name)"
|
||||
" )"
|
||||
")"
|
||||
""
|
||||
/* Table idx_cols contains 1 row for each column in each index on the
|
||||
** table being checked. Columns are:
|
||||
**
|
||||
** idx_name: Name of the index.
|
||||
** idx_ispk: True if this index is the PK of a WITHOUT ROWID table.
|
||||
** col_name: Name of indexed column, or NULL for index on expression.
|
||||
** col_expr: Indexed expression, including COLLATE clause.
|
||||
** col_alias: Alias used for column in 'intck_wrapper' table.
|
||||
*/
|
||||
", idx_cols(idx_name, idx_ispk, col_name, col_expr, col_alias) AS ("
|
||||
" SELECT l.name, (l.origin=='pk' AND w.b), i.name, COALESCE(("
|
||||
" SELECT parse_create_index(sql, i.seqno) FROM "
|
||||
" sqlite_schema WHERE name = l.name"
|
||||
" ), format('\"%w\"', i.name) || ' COLLATE ' || quote(i.coll)),"
|
||||
" 'c' || row_number() OVER ()"
|
||||
" FROM "
|
||||
" tabname t,"
|
||||
" without_rowid w,"
|
||||
" pragma_index_list(t.tab, t.db) l,"
|
||||
" pragma_index_xinfo(l.name) i"
|
||||
" WHERE i.key"
|
||||
" UNION ALL"
|
||||
" SELECT '', 1, '_rowid_', '_rowid_', 'r1' FROM without_rowid WHERE b=0"
|
||||
")"
|
||||
""
|
||||
""
|
||||
/*
|
||||
** For a PK declared as "PRIMARY KEY(a, b) ... WITHOUT ROWID", where
|
||||
** the intck_wrapper aliases of "a" and "b" are "c1" and "c2":
|
||||
**
|
||||
** o_pk: "o.c1, o.c2"
|
||||
** i_pk: "i.'a', i.'b'"
|
||||
** ...
|
||||
** n_pk: 2
|
||||
*/
|
||||
", tabpk(db, tab, idx, o_pk, i_pk, q_pk, eq_pk, ps_pk, pk_pk, n_pk) AS ("
|
||||
" WITH pkfields(f, a) AS ("
|
||||
" SELECT i.col_name, i.col_alias FROM idx_cols i WHERE i.idx_ispk"
|
||||
" )"
|
||||
" SELECT t.db, t.tab, t.idx, "
|
||||
" group_concat(a, ', '), "
|
||||
" group_concat('i.'||quote(f), ', '), "
|
||||
" group_concat('quote(o.'||a||')', ' || '','' || '), "
|
||||
" format('(%s)==(%s)',"
|
||||
" group_concat('o.'||a, ', '), "
|
||||
" group_concat(format('\"%w\"', f), ', ')"
|
||||
" ),"
|
||||
" group_concat('%s', ','),"
|
||||
" group_concat('quote('||a||')', ', '), "
|
||||
" count(*)"
|
||||
" FROM tabname t, pkfields"
|
||||
")"
|
||||
""
|
||||
", idx(name, match_expr, partial, partial_alias, idx_ps, idx_idx) AS ("
|
||||
" SELECT idx_name,"
|
||||
" format('(%s,%s) IS (%s,%s)', "
|
||||
" group_concat(i.col_expr, ', '), i_pk,"
|
||||
" group_concat('o.'||i.col_alias, ', '), o_pk"
|
||||
" ), "
|
||||
" parse_create_index("
|
||||
" (SELECT sql FROM sqlite_schema WHERE name=idx_name), -1"
|
||||
" ),"
|
||||
" 'cond' || row_number() OVER ()"
|
||||
" , group_concat('%s', ',')"
|
||||
" , group_concat('quote('||i.col_alias||')', ', ')"
|
||||
" FROM tabpk t, "
|
||||
" without_rowid w,"
|
||||
" idx_cols i"
|
||||
" WHERE i.idx_ispk==0 "
|
||||
" GROUP BY idx_name"
|
||||
")"
|
||||
""
|
||||
", wrapper_with(s) AS ("
|
||||
" SELECT 'intck_wrapper AS (\n SELECT\n ' || ("
|
||||
" WITH f(a, b) AS ("
|
||||
" SELECT col_expr, col_alias FROM idx_cols"
|
||||
" UNION ALL "
|
||||
" SELECT partial, partial_alias FROM idx WHERE partial IS NOT NULL"
|
||||
" )"
|
||||
" SELECT group_concat(format('%s AS %s', a, b), ',\n ') FROM f"
|
||||
" )"
|
||||
" || format('\n FROM %Q.%Q ', t.db, t.tab)"
|
||||
/* If the object being checked is a table, append "NOT INDEXED".
|
||||
** Otherwise, append "INDEXED BY <index>", and then, if the index
|
||||
** is a partial index " WHERE <condition>". */
|
||||
" || CASE WHEN t.idx IS NULL THEN "
|
||||
" 'NOT INDEXED'"
|
||||
" ELSE"
|
||||
" format('INDEXED BY %Q%s', t.idx, ' WHERE '||i.partial)"
|
||||
" END"
|
||||
" || '\n)'"
|
||||
" FROM tabname t LEFT JOIN idx i ON (i.name=t.idx)"
|
||||
")"
|
||||
""
|
||||
;
|
||||
|
||||
bAutoIndex = intckGetAutoIndex(p);
|
||||
if( bAutoIndex ) intckExec(p, "PRAGMA automatic_index = 0");
|
||||
|
||||
bIsIndex = intckIsIndex(p, zObj);
|
||||
if( bIsIndex ){
|
||||
pStmt = intckPrepareFmt(p,
|
||||
/* Table idxname contains a single row. The first column, "db", contains
|
||||
** the name of the db containing the table (e.g. "main") and the second,
|
||||
** "tab", the name of the table itself. */
|
||||
"WITH tabname(db, tab, idx) AS ("
|
||||
" SELECT %Q, (SELECT tbl_name FROM %Q.sqlite_schema WHERE name=%Q), %Q "
|
||||
")"
|
||||
""
|
||||
", whereclause(w_c) AS (%s)"
|
||||
""
|
||||
"%s" /* zCommon */
|
||||
""
|
||||
", case_statement(c) AS ("
|
||||
" SELECT "
|
||||
" 'CASE WHEN (' || group_concat(col_alias, ', ') || ', 1) IS (\n' "
|
||||
" || ' SELECT ' || group_concat(col_expr, ', ') || ', 1 FROM '"
|
||||
" || format('%%Q.%%Q NOT INDEXED WHERE %%s\n', t.db, t.tab, p.eq_pk)"
|
||||
" || ' )\n THEN NULL\n '"
|
||||
" || 'ELSE format(''surplus entry ('"
|
||||
" || group_concat('%%s', ',') || ',' || p.ps_pk"
|
||||
" || ') in index ' || t.idx || ''', ' "
|
||||
" || group_concat('quote('||i.col_alias||')', ', ') || ', ' || p.pk_pk"
|
||||
" || ')'"
|
||||
" || '\n END AS error_message'"
|
||||
" FROM tabname t, tabpk p, idx_cols i WHERE i.idx_name=t.idx"
|
||||
")"
|
||||
""
|
||||
", thiskey(k, n) AS ("
|
||||
" SELECT group_concat(i.col_alias, ', ') || ', ' || p.o_pk, "
|
||||
" count(*) + p.n_pk "
|
||||
" FROM tabpk p, idx_cols i WHERE i.idx_name=p.idx"
|
||||
")"
|
||||
""
|
||||
", main_select(m, n) AS ("
|
||||
" SELECT format("
|
||||
" 'WITH %%s\n' ||"
|
||||
" ', idx_checker AS (\n' ||"
|
||||
" ' SELECT %%s,\n' ||"
|
||||
" ' %%s\n' || "
|
||||
" ' FROM intck_wrapper AS o\n' ||"
|
||||
" ')\n',"
|
||||
" ww.s, c, t.k"
|
||||
" ), t.n"
|
||||
" FROM case_statement, wrapper_with ww, thiskey t"
|
||||
")"
|
||||
|
||||
"SELECT m || "
|
||||
" group_concat('SELECT * FROM idx_checker ' || w_c, ' UNION ALL '), n"
|
||||
" FROM "
|
||||
"main_select, whereclause "
|
||||
, p->zDb, p->zDb, zObj, zObj
|
||||
, zPrev ? zPrev : "VALUES('')", zCommon
|
||||
);
|
||||
}else{
|
||||
pStmt = intckPrepareFmt(p,
|
||||
/* Table tabname contains a single row. The first column, "db", contains
|
||||
** the name of the db containing the table (e.g. "main") and the second,
|
||||
** "tab", the name of the table itself. */
|
||||
"WITH tabname(db, tab, idx, prev) AS (SELECT %Q, %Q, NULL, %Q)"
|
||||
""
|
||||
"%s" /* zCommon */
|
||||
|
||||
/* expr(e) contains one row for each index on table zObj. Value e
|
||||
** is set to an expression that evaluates to NULL if the required
|
||||
** entry is present in the index, or an error message otherwise. */
|
||||
", expr(e, p) AS ("
|
||||
" SELECT format('CASE WHEN EXISTS \n"
|
||||
" (SELECT 1 FROM %%Q.%%Q AS i INDEXED BY %%Q WHERE %%s%%s)\n"
|
||||
" THEN NULL\n"
|
||||
" ELSE format(''entry (%%s,%%s) missing from index %%s'', %%s, %%s)\n"
|
||||
" END\n'"
|
||||
" , t.db, t.tab, i.name, i.match_expr, ' AND (' || partial || ')',"
|
||||
" i.idx_ps, t.ps_pk, i.name, i.idx_idx, t.pk_pk),"
|
||||
" CASE WHEN partial IS NULL THEN NULL ELSE i.partial_alias END"
|
||||
" FROM tabpk t, idx i"
|
||||
")"
|
||||
|
||||
", numbered(ii, cond, e) AS ("
|
||||
" SELECT 0, 'n.ii=0', 'NULL'"
|
||||
" UNION ALL "
|
||||
" SELECT row_number() OVER (),"
|
||||
" '(n.ii='||row_number() OVER ()||COALESCE(' AND '||p||')', ')'), e"
|
||||
" FROM expr"
|
||||
")"
|
||||
|
||||
", counter_with(w) AS ("
|
||||
" SELECT 'WITH intck_counter(ii) AS (\n ' || "
|
||||
" group_concat('SELECT '||ii, ' UNION ALL\n ') "
|
||||
" || '\n)' FROM numbered"
|
||||
")"
|
||||
""
|
||||
", case_statement(c) AS ("
|
||||
" SELECT 'CASE ' || "
|
||||
" group_concat(format('\n WHEN %%s THEN (%%s)', cond, e), '') ||"
|
||||
" '\nEND AS error_message'"
|
||||
" FROM numbered"
|
||||
")"
|
||||
""
|
||||
|
||||
/* This table contains a single row consisting of a single value -
|
||||
** the text of an SQL expression that may be used by the main SQL
|
||||
** statement to output an SQL literal that can be used to resume
|
||||
** the scan if it is suspended. e.g. for a rowid table, an expression
|
||||
** like:
|
||||
**
|
||||
** format('(%d,%d)', _rowid_, n.ii)
|
||||
*/
|
||||
", thiskey(k, n) AS ("
|
||||
" SELECT o_pk || ', ii', n_pk+1 FROM tabpk"
|
||||
")"
|
||||
""
|
||||
", whereclause(w_c) AS ("
|
||||
" SELECT CASE WHEN prev!='' THEN "
|
||||
" '\nWHERE (' || o_pk ||', n.ii) > ' || prev"
|
||||
" ELSE ''"
|
||||
" END"
|
||||
" FROM tabpk, tabname"
|
||||
")"
|
||||
""
|
||||
", main_select(m, n) AS ("
|
||||
" SELECT format("
|
||||
" '%%s, %%s\nSELECT %%s,\n%%s\nFROM intck_wrapper AS o"
|
||||
", intck_counter AS n%%s\nORDER BY %%s', "
|
||||
" w, ww.s, c, thiskey.k, whereclause.w_c, t.o_pk"
|
||||
" ), thiskey.n"
|
||||
" FROM case_statement, tabpk t, counter_with, "
|
||||
" wrapper_with ww, thiskey, whereclause"
|
||||
")"
|
||||
|
||||
"SELECT m, n FROM main_select",
|
||||
p->zDb, zObj, zPrev, zCommon
|
||||
);
|
||||
}
|
||||
|
||||
while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
zRet = intckMprintf(p, "%s", (const char*)sqlite3_column_text(pStmt, 0));
|
||||
if( pnKeyVal ){
|
||||
*pnKeyVal = sqlite3_column_int(pStmt, 1);
|
||||
}
|
||||
}
|
||||
intckFinalize(p, pStmt);
|
||||
|
||||
if( bAutoIndex ) intckExec(p, "PRAGMA automatic_index = 1");
|
||||
return zRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a new integrity-check object.
|
||||
*/
|
||||
int sqlite3_intck_open(
|
||||
sqlite3 *db, /* Database handle to operate on */
|
||||
const char *zDbArg, /* "main", "temp" etc. */
|
||||
sqlite3_intck **ppOut /* OUT: New integrity-check handle */
|
||||
){
|
||||
sqlite3_intck *pNew = 0;
|
||||
int rc = SQLITE_OK;
|
||||
const char *zDb = zDbArg ? zDbArg : "main";
|
||||
int nDb = strlen(zDb);
|
||||
|
||||
pNew = (sqlite3_intck*)sqlite3_malloc(sizeof(*pNew) + nDb + 1);
|
||||
if( pNew==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
pNew->db = db;
|
||||
pNew->zDb = (const char*)&pNew[1];
|
||||
memcpy(&pNew[1], zDb, nDb+1);
|
||||
rc = sqlite3_create_function(db, "parse_create_index",
|
||||
2, SQLITE_UTF8, 0, intckParseCreateIndexFunc, 0, 0
|
||||
);
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3_intck_close(pNew);
|
||||
pNew = 0;
|
||||
}
|
||||
}
|
||||
|
||||
*ppOut = pNew;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Free the integrity-check object.
|
||||
*/
|
||||
void sqlite3_intck_close(sqlite3_intck *p){
|
||||
if( p ){
|
||||
sqlite3_finalize(p->pCheck);
|
||||
sqlite3_create_function(
|
||||
p->db, "parse_create_index", 1, SQLITE_UTF8, 0, 0, 0, 0
|
||||
);
|
||||
sqlite3_free(p->zObj);
|
||||
sqlite3_free(p->zKey);
|
||||
sqlite3_free(p->zTestSql);
|
||||
sqlite3_free(p->zErr);
|
||||
sqlite3_free(p->zMessage);
|
||||
sqlite3_free(p);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Step the integrity-check object.
|
||||
*/
|
||||
int sqlite3_intck_step(sqlite3_intck *p){
|
||||
if( p->rc==SQLITE_OK ){
|
||||
|
||||
if( p->zMessage ){
|
||||
sqlite3_free(p->zMessage);
|
||||
p->zMessage = 0;
|
||||
}
|
||||
|
||||
if( p->bCorruptSchema ){
|
||||
p->rc = SQLITE_DONE;
|
||||
}else
|
||||
if( p->pCheck==0 ){
|
||||
intckFindObject(p);
|
||||
if( p->rc==SQLITE_OK ){
|
||||
if( p->zObj ){
|
||||
char *zSql = 0;
|
||||
zSql = intckCheckObjectSql(p, p->zObj, p->zKey, &p->nKeyVal);
|
||||
p->pCheck = intckPrepare(p, zSql);
|
||||
sqlite3_free(zSql);
|
||||
sqlite3_free(p->zKey);
|
||||
p->zKey = 0;
|
||||
}else{
|
||||
p->rc = SQLITE_DONE;
|
||||
}
|
||||
}else if( p->rc==SQLITE_CORRUPT ){
|
||||
p->rc = SQLITE_OK;
|
||||
p->zMessage = intckMprintf(p, "%s",
|
||||
"corruption found while reading database schema"
|
||||
);
|
||||
p->bCorruptSchema = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if( p->pCheck ){
|
||||
assert( p->rc==SQLITE_OK );
|
||||
if( sqlite3_step(p->pCheck)==SQLITE_ROW ){
|
||||
/* Normal case, do nothing. */
|
||||
}else{
|
||||
intckFinalize(p, p->pCheck);
|
||||
p->pCheck = 0;
|
||||
p->nKeyVal = 0;
|
||||
if( p->rc==SQLITE_CORRUPT ){
|
||||
p->rc = SQLITE_OK;
|
||||
p->zMessage = intckMprintf(p,
|
||||
"corruption found while scanning database object %s", p->zObj
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return p->rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a message describing the corruption encountered by the most recent
|
||||
** call to sqlite3_intck_step(), or NULL if no corruption was encountered.
|
||||
*/
|
||||
const char *sqlite3_intck_message(sqlite3_intck *p){
|
||||
assert( p->pCheck==0 || p->zMessage==0 );
|
||||
if( p->zMessage ){
|
||||
return p->zMessage;
|
||||
}
|
||||
if( p->pCheck ){
|
||||
return (const char*)sqlite3_column_text(p->pCheck, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the error code and message.
|
||||
*/
|
||||
int sqlite3_intck_error(sqlite3_intck *p, const char **pzErr){
|
||||
if( pzErr ) *pzErr = p->zErr;
|
||||
return (p->rc==SQLITE_DONE ? SQLITE_OK : p->rc);
|
||||
}
|
||||
|
||||
/*
|
||||
** Close any read transaction the integrity-check object is holding open
|
||||
** on the database.
|
||||
*/
|
||||
int sqlite3_intck_unlock(sqlite3_intck *p){
|
||||
if( p->rc==SQLITE_OK && p->pCheck ){
|
||||
assert( p->zKey==0 && p->nKeyVal>0 );
|
||||
intckSaveKey(p);
|
||||
intckFinalize(p, p->pCheck);
|
||||
p->pCheck = 0;
|
||||
}
|
||||
return p->rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the SQL statement used to check object zObj. Or, if zObj is
|
||||
** NULL, the current SQL statement.
|
||||
*/
|
||||
const char *sqlite3_intck_test_sql(sqlite3_intck *p, const char *zObj){
|
||||
sqlite3_free(p->zTestSql);
|
||||
if( zObj ){
|
||||
p->zTestSql = intckCheckObjectSql(p, zObj, 0, 0);
|
||||
}else{
|
||||
if( p->zObj ){
|
||||
p->zTestSql = intckCheckObjectSql(p, p->zObj, p->zKey, 0);
|
||||
}else{
|
||||
sqlite3_free(p->zTestSql);
|
||||
p->zTestSql = 0;
|
||||
}
|
||||
}
|
||||
return p->zTestSql;
|
||||
}
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
/*
|
||||
** 2024-02-08
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
*/
|
||||
|
||||
/*
|
||||
** Incremental Integrity-Check Extension
|
||||
** -------------------------------------
|
||||
**
|
||||
** This module contains code to check whether or not an SQLite database
|
||||
** is well-formed or corrupt. This is the same task as performed by SQLite's
|
||||
** built-in "PRAGMA integrity_check" command. This module differs from
|
||||
** "PRAGMA integrity_check" in that:
|
||||
**
|
||||
** + It is less thorough - this module does not detect certain types
|
||||
** of corruption that are detected by the PRAGMA command. However,
|
||||
** it does detect all kinds of corruption that are likely to cause
|
||||
** errors in SQLite applications.
|
||||
**
|
||||
** + It is slower. Sometimes up to three times slower.
|
||||
**
|
||||
** + It allows integrity-check operations to be split into multiple
|
||||
** transactions, so that the database does not need to be read-locked
|
||||
** for the duration of the integrity-check.
|
||||
**
|
||||
** One way to use the API to run integrity-check on the "main" database
|
||||
** of handle db is:
|
||||
**
|
||||
** int rc = SQLITE_OK;
|
||||
** sqlite3_intck *p = 0;
|
||||
**
|
||||
** sqlite3_intck_open(db, "main", &p);
|
||||
** while( SQLITE_OK==sqlite3_intck_step(p) ){
|
||||
** const char *zMsg = sqlite3_intck_message(p);
|
||||
** if( zMsg ) printf("corruption: %s\n", zMsg);
|
||||
** }
|
||||
** rc = sqlite3_intck_error(p, &zErr);
|
||||
** if( rc!=SQLITE_OK ){
|
||||
** printf("error occured (rc=%d), (errmsg=%s)\n", rc, zErr);
|
||||
** }
|
||||
** sqlite3_intck_close(p);
|
||||
**
|
||||
** Usually, the sqlite3_intck object opens a read transaction within the
|
||||
** first call to sqlite3_intck_step() and holds it open until the
|
||||
** integrity-check is complete. However, if sqlite3_intck_unlock() is
|
||||
** called, the read transaction is ended and a new read transaction opened
|
||||
** by the subsequent call to sqlite3_intck_step().
|
||||
*/
|
||||
|
||||
#ifndef _SQLITE_INTCK_H
|
||||
#define _SQLITE_INTCK_H
|
||||
|
||||
#include "sqlite3.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
** An ongoing incremental integrity-check operation is represented by an
|
||||
** opaque pointer of the following type.
|
||||
*/
|
||||
typedef struct sqlite3_intck sqlite3_intck;
|
||||
|
||||
/*
|
||||
** Open a new incremental integrity-check object. If successful, populate
|
||||
** output variable (*ppOut) with the new object handle and return SQLITE_OK.
|
||||
** Or, if an error occurs, set (*ppOut) to NULL and return an SQLite error
|
||||
** code (e.g. SQLITE_NOMEM).
|
||||
**
|
||||
** The integrity-check will be conducted on database zDb (which must be "main",
|
||||
** "temp", or the name of an attached database) of database handle db. Once
|
||||
** this function has been called successfully, the caller should not use
|
||||
** database handle db until the integrity-check object has been destroyed
|
||||
** using sqlite3_intck_close().
|
||||
*/
|
||||
int sqlite3_intck_open(
|
||||
sqlite3 *db, /* Database handle */
|
||||
const char *zDb, /* Database name ("main", "temp" etc.) */
|
||||
sqlite3_intck **ppOut /* OUT: New sqlite3_intck handle */
|
||||
);
|
||||
|
||||
/*
|
||||
** Close and release all resources associated with a handle opened by an
|
||||
** earlier call to sqlite3_intck_open(). The results of using an
|
||||
** integrity-check handle after it has been passed to this function are
|
||||
** undefined.
|
||||
*/
|
||||
void sqlite3_intck_close(sqlite3_intck *pCk);
|
||||
|
||||
/*
|
||||
** Do the next step of the integrity-check operation specified by the handle
|
||||
** passed as the only argument. This function returns SQLITE_DONE if the
|
||||
** integrity-check operation is finished, or an SQLite error code if
|
||||
** an error occurs, or SQLITE_OK if no error occurs but the integrity-check
|
||||
** is not finished. It is not considered an error if database corruption
|
||||
** is encountered.
|
||||
**
|
||||
** Following a successful call to sqlite3_intck_step() (one that returns
|
||||
** SQLITE_OK), sqlite3_intck_message() returns a non-NULL value if
|
||||
** corruption was detected in the db.
|
||||
**
|
||||
** If an error occurs and a value other than SQLITE_OK or SQLITE_DONE is
|
||||
** returned, then the integrity-check handle is placed in an error state.
|
||||
** In this state all subsequent calls to sqlite3_intck_step() or
|
||||
** sqlite3_intck_unlock() will immediately return the same error. The
|
||||
** sqlite3_intck_error() method may be used to obtain an English language
|
||||
** error message in this case.
|
||||
*/
|
||||
int sqlite3_intck_step(sqlite3_intck *pCk);
|
||||
|
||||
/*
|
||||
** If the previous call to sqlite3_intck_step() encountered corruption
|
||||
** within the database, then this function returns a pointer to a buffer
|
||||
** containing a nul-terminated string describing the corruption in
|
||||
** English. If the previous call to sqlite3_intck_step() did not encounter
|
||||
** corruption, or if there was no previous call, this function returns
|
||||
** NULL.
|
||||
*/
|
||||
const char *sqlite3_intck_message(sqlite3_intck *pCk);
|
||||
|
||||
/*
|
||||
** Close any read-transaction opened by an earlier call to
|
||||
** sqlite3_intck_step(). Any subsequent call to sqlite3_intck_step() will
|
||||
** open a new transaction. Return SQLITE_OK if successful, or an SQLite error
|
||||
** code otherwise.
|
||||
**
|
||||
** If an error occurs, then the integrity-check handle is placed in an error
|
||||
** state. In this state all subsequent calls to sqlite3_intck_step() or
|
||||
** sqlite3_intck_unlock() will immediately return the same error. The
|
||||
** sqlite3_intck_error() method may be used to obtain an English language
|
||||
** error message in this case.
|
||||
*/
|
||||
int sqlite3_intck_unlock(sqlite3_intck *pCk);
|
||||
|
||||
/*
|
||||
** If an error has occurred in an earlier call to sqlite3_intck_step()
|
||||
** or sqlite3_intck_unlock(), then this method returns the associated
|
||||
** SQLite error code. Additionally, if pzErr is not NULL, then (*pzErr)
|
||||
** may be set to point to a nul-terminated string containing an English
|
||||
** language error message. Or, if no error message is available, to
|
||||
** NULL.
|
||||
**
|
||||
** If no error has occurred within sqlite3_intck_step() or
|
||||
** sqlite_intck_unlock() calls on the handle passed as the first argument,
|
||||
** then SQLITE_OK is returned and (*pzErr) set to NULL.
|
||||
*/
|
||||
int sqlite3_intck_error(sqlite3_intck *pCk, const char **pzErr);
|
||||
|
||||
/*
|
||||
** This API is used for testing only. It returns the full-text of an SQL
|
||||
** statement used to test object zObj, which may be a table or index.
|
||||
** The returned buffer is valid until the next call to either this function
|
||||
** or sqlite3_intck_close() on the same sqlite3_intck handle.
|
||||
*/
|
||||
const char *sqlite3_intck_test_sql(sqlite3_intck *pCk, const char *zObj);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* end of the 'extern "C"' block */
|
||||
#endif
|
||||
|
||||
#endif /* ifndef _SQLITE_INTCK_H */
|
||||
@@ -1,238 +0,0 @@
|
||||
/*
|
||||
** 2010 August 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.
|
||||
**
|
||||
*************************************************************************
|
||||
** Code for testing all sorts of SQLite interfaces. This code
|
||||
** is not included in the SQLite library.
|
||||
*/
|
||||
|
||||
#include "sqlite3.h"
|
||||
#include "sqlite3intck.h"
|
||||
|
||||
#if defined(INCLUDE_SQLITE_TCL_H)
|
||||
# include "sqlite_tcl.h"
|
||||
#else
|
||||
# include "tcl.h"
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
/* In test1.c */
|
||||
int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb);
|
||||
const char *sqlite3ErrName(int);
|
||||
|
||||
typedef struct TestIntck TestIntck;
|
||||
struct TestIntck {
|
||||
sqlite3_intck *intck;
|
||||
};
|
||||
|
||||
static int testIntckCmd(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
struct Subcmd {
|
||||
const char *zName;
|
||||
int nArg;
|
||||
const char *zExpect;
|
||||
} aCmd[] = {
|
||||
{"close", 0, ""}, /* 0 */
|
||||
{"step", 0, ""}, /* 1 */
|
||||
{"message", 0, ""}, /* 2 */
|
||||
{"error", 0, ""}, /* 3 */
|
||||
{"unlock", 0, ""}, /* 4 */
|
||||
{"test_sql", 1, ""}, /* 5 */
|
||||
{0 , 0}
|
||||
};
|
||||
int rc = TCL_OK;
|
||||
int iIdx = -1;
|
||||
TestIntck *p = (TestIntck*)clientData;
|
||||
|
||||
if( objc<2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND ...");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
rc = Tcl_GetIndexFromObjStruct(
|
||||
interp, objv[1], aCmd, sizeof(aCmd[0]), "SUB-COMMAND", 0, &iIdx
|
||||
);
|
||||
if( rc ) return rc;
|
||||
|
||||
if( objc!=2+aCmd[iIdx].nArg ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, aCmd[iIdx].zExpect);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
switch( iIdx ){
|
||||
case 0: assert( 0==strcmp("close", aCmd[iIdx].zName) ); {
|
||||
Tcl_DeleteCommand(interp, Tcl_GetStringFromObj(objv[0], 0));
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: assert( 0==strcmp("step", aCmd[iIdx].zName) ); {
|
||||
int rc = sqlite3_intck_step(p->intck);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
|
||||
break;
|
||||
}
|
||||
|
||||
case 2: assert( 0==strcmp("message", aCmd[iIdx].zName) ); {
|
||||
const char *z = sqlite3_intck_message(p->intck);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(z ? z : "", -1));
|
||||
break;
|
||||
}
|
||||
|
||||
case 3: assert( 0==strcmp("error", aCmd[iIdx].zName) ); {
|
||||
const char *zErr = 0;
|
||||
int rc = sqlite3_intck_error(p->intck, 0);
|
||||
Tcl_Obj *pRes = Tcl_NewObj();
|
||||
Tcl_ListObjAppendElement(
|
||||
interp, pRes, Tcl_NewStringObj(sqlite3ErrName(rc), -1)
|
||||
);
|
||||
sqlite3_intck_error(p->intck, &zErr);
|
||||
Tcl_ListObjAppendElement(
|
||||
interp, pRes, Tcl_NewStringObj(zErr ? zErr : 0, -1)
|
||||
);
|
||||
Tcl_SetObjResult(interp, pRes);
|
||||
break;
|
||||
}
|
||||
|
||||
case 4: assert( 0==strcmp("unlock", aCmd[iIdx].zName) ); {
|
||||
int rc = sqlite3_intck_unlock(p->intck);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
|
||||
break;
|
||||
}
|
||||
|
||||
case 5: assert( 0==strcmp("test_sql", aCmd[iIdx].zName) ); {
|
||||
const char *zObj = Tcl_GetString(objv[2]);
|
||||
const char *zSql = sqlite3_intck_test_sql(p->intck, zObj[0] ? zObj : 0);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(zSql, -1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Destructor for commands created by test_sqlite3_intck().
|
||||
*/
|
||||
static void testIntckFree(void *clientData){
|
||||
TestIntck *p = (TestIntck*)clientData;
|
||||
sqlite3_intck_close(p->intck);
|
||||
ckfree(p);
|
||||
}
|
||||
|
||||
/*
|
||||
** tclcmd: sqlite3_intck DB DBNAME
|
||||
*/
|
||||
static int test_sqlite3_intck(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
char zName[64];
|
||||
int iName = 0;
|
||||
Tcl_CmdInfo info;
|
||||
TestIntck *p = 0;
|
||||
sqlite3 *db = 0;
|
||||
const char *zDb = 0;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
if( objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
p = (TestIntck*)ckalloc(sizeof(TestIntck));
|
||||
memset(p, 0, sizeof(TestIntck));
|
||||
|
||||
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
zDb = Tcl_GetString(objv[2]);
|
||||
if( zDb[0]=='\0' ) zDb = 0;
|
||||
|
||||
rc = sqlite3_intck_open(db, zDb, &p->intck);
|
||||
if( rc!=SQLITE_OK ){
|
||||
ckfree(p);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3_errstr(rc), -1));
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
do {
|
||||
sprintf(zName, "intck%d", iName++);
|
||||
}while( Tcl_GetCommandInfo(interp, zName, &info)!=0 );
|
||||
Tcl_CreateObjCommand(interp, zName, testIntckCmd, (void*)p, testIntckFree);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(zName, -1));
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** tclcmd: test_do_intck DB DBNAME
|
||||
*/
|
||||
static int test_do_intck(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
sqlite3 *db = 0;
|
||||
const char *zDb = 0;
|
||||
int rc = SQLITE_OK;
|
||||
sqlite3_intck *pCk = 0;
|
||||
Tcl_Obj *pRet = 0;
|
||||
const char *zErr = 0;
|
||||
|
||||
if( objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
zDb = Tcl_GetString(objv[2]);
|
||||
|
||||
pRet = Tcl_NewObj();
|
||||
Tcl_IncrRefCount(pRet);
|
||||
|
||||
rc = sqlite3_intck_open(db, zDb, &pCk);
|
||||
if( rc==SQLITE_OK ){
|
||||
while( sqlite3_intck_step(pCk)==SQLITE_OK ){
|
||||
const char *zMsg = sqlite3_intck_message(pCk);
|
||||
if( zMsg ){
|
||||
Tcl_ListObjAppendElement(interp, pRet, Tcl_NewStringObj(zMsg, -1));
|
||||
}
|
||||
}
|
||||
rc = sqlite3_intck_error(pCk, &zErr);
|
||||
}
|
||||
if( rc!=SQLITE_OK ){
|
||||
if( zErr ){
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1));
|
||||
}else{
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
|
||||
}
|
||||
}else{
|
||||
Tcl_SetObjResult(interp, pRet);
|
||||
}
|
||||
Tcl_DecrRefCount(pRet);
|
||||
sqlite3_intck_close(pCk);
|
||||
sqlite3_intck_close(0);
|
||||
return rc ? TCL_ERROR : TCL_OK;
|
||||
}
|
||||
|
||||
int Sqlitetestintck_Init(Tcl_Interp *interp){
|
||||
Tcl_CreateObjCommand(interp, "sqlite3_intck", test_sqlite3_intck, 0, 0);
|
||||
Tcl_CreateObjCommand(interp, "test_do_intck", test_do_intck, 0, 0);
|
||||
return TCL_OK;
|
||||
}
|
||||
+4
-11
@@ -35,8 +35,6 @@ $(dir.bld.c):
|
||||
|
||||
javac.flags ?= -Xlint:unchecked -Xlint:deprecation
|
||||
java.flags ?=
|
||||
javac.flags += -encoding utf8
|
||||
# -------------^^^^^^^^^^^^^^ required for Windows builds
|
||||
jnicheck ?= 1
|
||||
ifeq (1,$(jnicheck))
|
||||
java.flags += -Xcheck:jni
|
||||
@@ -81,7 +79,6 @@ $(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile
|
||||
# Be explicit about which Java files to compile so that we can work on
|
||||
# in-progress files without requiring them to be in a compilable statae.
|
||||
JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/annotation/%,\
|
||||
Experimental.java \
|
||||
NotNull.java \
|
||||
Nullable.java \
|
||||
) $(patsubst %,$(dir.src.capi)/%,\
|
||||
@@ -94,7 +91,7 @@ JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/annotation/%,\
|
||||
CollationNeededCallback.java \
|
||||
CommitHookCallback.java \
|
||||
ConfigLogCallback.java \
|
||||
ConfigSqlLogCallback.java \
|
||||
ConfigSqllogCallback.java \
|
||||
NativePointerHolder.java \
|
||||
OutputPointer.java \
|
||||
PrepareMultiCallback.java \
|
||||
@@ -113,7 +110,6 @@ JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/annotation/%,\
|
||||
WindowFunction.java \
|
||||
XDestroyCallback.java \
|
||||
sqlite3.java \
|
||||
sqlite3_blob.java \
|
||||
sqlite3_context.java \
|
||||
sqlite3_stmt.java \
|
||||
sqlite3_value.java \
|
||||
@@ -124,7 +120,6 @@ JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/annotation/%,\
|
||||
Sqlite.java \
|
||||
SqliteException.java \
|
||||
ValueHolder.java \
|
||||
WindowFunction.java \
|
||||
)
|
||||
|
||||
JAVA_FILES.unittest := $(patsubst %,$(dir.src.jni)/%,\
|
||||
@@ -164,13 +159,12 @@ endif
|
||||
CLASS_FILES :=
|
||||
define CLASSFILE_DEPS
|
||||
all: $(1).class
|
||||
$(1).class: $(1).java
|
||||
CLASS_FILES += $(1).class
|
||||
endef
|
||||
$(foreach B,$(basename \
|
||||
$(JAVA_FILES.main) $(JAVA_FILES.unittest) $(JAVA_FILES.tester)),\
|
||||
$(eval $(call CLASSFILE_DEPS,$(B))))
|
||||
$(CLASS_FILES): $(MAKEFILE)
|
||||
$(CLASS_FILES): $(JAVA_FILES) $(MAKEFILE)
|
||||
$(bin.javac) $(javac.flags) -h $(dir.bld.c) -cp $(classpath) $(JAVA_FILES)
|
||||
|
||||
#.PHONY: classfiles
|
||||
@@ -232,8 +226,7 @@ SQLITE_OPT += -DSQLITE_ENABLE_RTREE \
|
||||
-DSQLITE_ENABLE_OFFSET_SQL_FUNC \
|
||||
-DSQLITE_ENABLE_PREUPDATE_HOOK \
|
||||
-DSQLITE_ENABLE_NORMALIZE \
|
||||
-DSQLITE_ENABLE_SQLLOG \
|
||||
-DSQLITE_ENABLE_COLUMN_METADATA
|
||||
-DSQLITE_ENABLE_SQLLOG
|
||||
endif
|
||||
|
||||
ifeq (1,$(opt.debug))
|
||||
@@ -323,7 +316,7 @@ test-one: $(test.deps)
|
||||
$(bin.java) $(test.flags.jvm) org.sqlite.jni.wrapper1.Tester2 $(Tester2.flags)
|
||||
test-sqllog: $(test.deps)
|
||||
@echo "Testing with -sqllog..."
|
||||
$(bin.java) $(test.flags.jvm) org.sqlite.jni.capi.Tester1 $(Tester1.flags) -sqllog
|
||||
$(bin.java) $(test.flags.jvm) -sqllog
|
||||
test-mt: $(test.deps)
|
||||
@echo "Testing in multi-threaded mode:";
|
||||
$(bin.java) $(test.flags.jvm) org.sqlite.jni.capi.Tester1 \
|
||||
|
||||
+19
-22
@@ -16,8 +16,9 @@ Technical support is available in the forum:
|
||||
> **FOREWARNING:** this subproject is very much in development and
|
||||
subject to any number of changes. Please do not rely on any
|
||||
information about its API until this disclaimer is removed. The JNI
|
||||
bindings released with version 3.43 are a "tech preview." Once
|
||||
finalized, strong backward compatibility guarantees will apply.
|
||||
bindings released with version 3.43 are a "tech preview" and 3.44
|
||||
will be "final," at which point strong backward compatibility
|
||||
guarantees will apply.
|
||||
|
||||
Project goals/requirements:
|
||||
|
||||
@@ -42,13 +43,11 @@ Non-goals:
|
||||
- Creation of high-level OO wrapper APIs. Clients are free to create
|
||||
them off of the C-style API.
|
||||
|
||||
- Virtual tables are unlikely to be supported due to the amount of
|
||||
glue code needed to fit them into Java.
|
||||
|
||||
- Support for mixed-mode operation, where client code accesses SQLite
|
||||
both via the Java-side API and the C API via their own native
|
||||
code. Such cases would be a minefield of potential mis-interactions
|
||||
between this project's JNI bindings and mixed-mode client code.
|
||||
code. In such cases, proxy functionalities (primarily callback
|
||||
handler wrappers of all sorts) may fail because the C-side use of
|
||||
the SQLite APIs will bypass those proxies.
|
||||
|
||||
|
||||
Hello World
|
||||
@@ -124,13 +123,15 @@ sensible default argument values. In all such cases they are thin
|
||||
proxies around the corresponding C APIs and do not introduce new
|
||||
semantics.
|
||||
|
||||
In a few cases, Java-specific capabilities have been added in
|
||||
In some very few cases, Java-specific capabilities have been added in
|
||||
new APIs, all of which have "_java" somewhere in their names.
|
||||
Examples include:
|
||||
|
||||
- `sqlite3_result_java_object()`
|
||||
- `sqlite3_column_java_object()`
|
||||
- `sqlite3_column_java_casted()`
|
||||
- `sqlite3_value_java_object()`
|
||||
- `sqlite3_value_java_casted()`
|
||||
|
||||
which, as one might surmise, collectively enable the passing of
|
||||
arbitrary Java objects from user-defined SQL functions through to the
|
||||
@@ -149,9 +150,6 @@ pending statements have been closed. Be aware that Java garbage
|
||||
collection _cannot_ close a database or finalize a prepared statement.
|
||||
Those things require explicit API calls.
|
||||
|
||||
Classes for which it is sensible support Java's `AutoCloseable`
|
||||
interface so can be used with try-with-resources constructs.
|
||||
|
||||
|
||||
Golden Rule #2: _Never_ Throw from Callbacks (Unless...)
|
||||
------------------------------------------------------------------------
|
||||
@@ -161,15 +159,14 @@ retain C-like semantics. For example, they are not permitted to throw
|
||||
or propagate exceptions and must return error information (if any) via
|
||||
result codes or `null`. The only cases where the C-style APIs may
|
||||
throw is through client-side misuse, e.g. passing in a null where it
|
||||
may cause a `NullPointerException`. The APIs clearly mark function
|
||||
parameters which should not be null, but does not generally actively
|
||||
defend itself against such misuse. Some C-style APIs explicitly accept
|
||||
`null` as a no-op for usability's sake, and some of the JNI APIs
|
||||
deliberately return an error code, instead of segfaulting, when passed
|
||||
a `null`.
|
||||
shouldn't be used. The APIs clearly mark function parameters which
|
||||
should not be null, but does not actively defend itself against such
|
||||
misuse. Some C-style APIs explicitly accept `null` as a no-op for
|
||||
usability's sake, and some of the JNI APIs deliberately return an
|
||||
error code, instead of segfaulting, when passed a `null`.
|
||||
|
||||
Client-defined callbacks _must never throw exceptions_ unless _very
|
||||
explitly documented_ as being throw-safe. Exceptions are generally
|
||||
explicitly documented_ as being throw-safe. Exceptions are generally
|
||||
reserved for higher-level bindings which are constructed to
|
||||
specifically deal with them and ensure that they do not leak C-level
|
||||
resources. In some cases, callback handlers are permitted to throw, in
|
||||
@@ -295,14 +292,14 @@ int sqlite3_create_function(sqlite3 db, String funcName, int nArgs,
|
||||
`SQLFunction` is not used directly, but is instead instantiated via
|
||||
one of its three subclasses:
|
||||
|
||||
- `ScalarFunction` implements simple scalar functions using but a
|
||||
- `SQLFunction.Scalar` implements simple scalar functions using but a
|
||||
single callback.
|
||||
- `AggregateFunction` implements aggregate functions using two
|
||||
- `SQLFunction.Aggregate` implements aggregate functions using two
|
||||
callbacks.
|
||||
- `WindowFunction` implements window functions using four
|
||||
- `SQLFunction.Window` implements window functions using four
|
||||
callbacks.
|
||||
|
||||
Search [`Tester1.java`](/file/ext/jni/src/org/sqlite/jni/capi/Tester1.java) for
|
||||
Search [`Tester1.java`](/file/ext/jni/src/org/sqlite/jni/Tester1.java) for
|
||||
`SQLFunction` for how it's used.
|
||||
|
||||
Reminder: see the disclaimer at the top of this document regarding the
|
||||
|
||||
+141
-585
File diff suppressed because it is too large
Load Diff
+26
-108
@@ -427,6 +427,8 @@ extern "C" {
|
||||
#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_EXRESCODE 33554432L
|
||||
#undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_PERSISTENT
|
||||
#define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_PERSISTENT 1L
|
||||
#undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NORMALIZE
|
||||
#define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NORMALIZE 2L
|
||||
#undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NO_VTAB
|
||||
#define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NO_VTAB 4L
|
||||
#undef org_sqlite_jni_capi_CApi_SQLITE_OK
|
||||
@@ -705,12 +707,8 @@ extern "C" {
|
||||
#define org_sqlite_jni_capi_CApi_SQLITE_DETERMINISTIC 2048L
|
||||
#undef org_sqlite_jni_capi_CApi_SQLITE_DIRECTONLY
|
||||
#define org_sqlite_jni_capi_CApi_SQLITE_DIRECTONLY 524288L
|
||||
#undef org_sqlite_jni_capi_CApi_SQLITE_SUBTYPE
|
||||
#define org_sqlite_jni_capi_CApi_SQLITE_SUBTYPE 1048576L
|
||||
#undef org_sqlite_jni_capi_CApi_SQLITE_INNOCUOUS
|
||||
#define org_sqlite_jni_capi_CApi_SQLITE_INNOCUOUS 2097152L
|
||||
#undef org_sqlite_jni_capi_CApi_SQLITE_RESULT_SUBTYPE
|
||||
#define org_sqlite_jni_capi_CApi_SQLITE_RESULT_SUBTYPE 16777216L
|
||||
#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_SCAN_UNIQUE
|
||||
#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_SCAN_UNIQUE 1L
|
||||
#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_EQ
|
||||
@@ -777,22 +775,6 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_init
|
||||
JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1java_1uncache_1thread
|
||||
(JNIEnv *, jclass);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_jni_supports_nio
|
||||
* Signature: ()Z
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1supports_1nio
|
||||
(JNIEnv *, jclass);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_jni_db_error
|
||||
* Signature: (Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1db_1error
|
||||
(JNIEnv *, jclass, jobject, jint, jstring);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_aggregate_context
|
||||
@@ -889,14 +871,6 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1int64
|
||||
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1java_1object
|
||||
(JNIEnv *, jclass, jlong, jint, jobject);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_bind_nio_buffer
|
||||
* Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;ILjava/nio/ByteBuffer;II)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1nio_1buffer
|
||||
(JNIEnv *, jclass, jobject, jint, jobject, jint, jint);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_bind_null
|
||||
@@ -1001,14 +975,6 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1open
|
||||
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1read
|
||||
(JNIEnv *, jclass, jlong, jbyteArray, jint);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_blob_read_nio_buffer
|
||||
* Signature: (JILjava/nio/ByteBuffer;II)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1read_1nio_1buffer
|
||||
(JNIEnv *, jclass, jlong, jint, jobject, jint, jint);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_blob_reopen
|
||||
@@ -1025,14 +991,6 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1reopen
|
||||
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1write
|
||||
(JNIEnv *, jclass, jlong, jbyteArray, jint);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_blob_write_nio_buffer
|
||||
* Signature: (JILjava/nio/ByteBuffer;II)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1write_1nio_1buffer
|
||||
(JNIEnv *, jclass, jlong, jint, jobject, jint, jint);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_busy_handler
|
||||
@@ -1129,14 +1087,6 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1bytes16
|
||||
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1count
|
||||
(JNIEnv *, jclass, jlong);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_column_database_name
|
||||
* Signature: (JI)Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1database_1name
|
||||
(JNIEnv *, jclass, jlong, jint);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_column_decltype
|
||||
@@ -1169,14 +1119,6 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1int
|
||||
JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1int64
|
||||
(JNIEnv *, jclass, jobject, jint);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_column_java_object
|
||||
* Signature: (JI)Ljava/lang/Object;
|
||||
*/
|
||||
JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1java_1object
|
||||
(JNIEnv *, jclass, jlong, jint);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_column_name
|
||||
@@ -1187,11 +1129,11 @@ JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1name
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_column_nio_buffer
|
||||
* Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)Ljava/nio/ByteBuffer;
|
||||
* Method: sqlite3_column_database_name
|
||||
* Signature: (JI)Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1nio_1buffer
|
||||
(JNIEnv *, jclass, jobject, jint);
|
||||
JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1database_1name
|
||||
(JNIEnv *, jclass, jlong, jint);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
@@ -1283,26 +1225,26 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1complete
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_config__enable
|
||||
* Method: sqlite3_config
|
||||
* Signature: (I)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config_1_1enable
|
||||
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__I
|
||||
(JNIEnv *, jclass, jint);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_config__CONFIG_LOG
|
||||
* Signature: (Lorg/sqlite/jni/capi/ConfigLogCallback;)I
|
||||
* Method: sqlite3_config
|
||||
* Signature: (Lorg/sqlite/jni/capi/ConfigSqllogCallback;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config_1_1CONFIG_1LOG
|
||||
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__Lorg_sqlite_jni_capi_ConfigSqllogCallback_2
|
||||
(JNIEnv *, jclass, jobject);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_config__SQLLOG
|
||||
* Signature: (Lorg/sqlite/jni/capi/ConfigSqlLogCallback;)I
|
||||
* Method: sqlite3_config
|
||||
* Signature: (Lorg/sqlite/jni/capi/ConfigLogCallback;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config_1_1SQLLOG
|
||||
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__Lorg_sqlite_jni_capi_ConfigLogCallback_2
|
||||
(JNIEnv *, jclass, jobject);
|
||||
|
||||
/*
|
||||
@@ -1452,9 +1394,9 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1errcode
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_extended_result_codes
|
||||
* Signature: (Lorg/sqlite/jni/capi/sqlite3;Z)I
|
||||
* Signature: (Lorg/sqlite/jni/capi/sqlite3;Z)Z
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1result_1codes
|
||||
JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1result_1codes
|
||||
(JNIEnv *, jclass, jobject, jboolean);
|
||||
|
||||
/*
|
||||
@@ -1737,6 +1679,14 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error_1nom
|
||||
JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error_1code
|
||||
(JNIEnv *, jclass, jobject, jint);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_result_null
|
||||
* Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1null
|
||||
(JNIEnv *, jclass, jobject);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_result_int
|
||||
@@ -1761,30 +1711,6 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1int64
|
||||
JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1java_1object
|
||||
(JNIEnv *, jclass, jobject, jobject);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_result_nio_buffer
|
||||
* Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Ljava/nio/ByteBuffer;II)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1nio_1buffer
|
||||
(JNIEnv *, jclass, jobject, jobject, jint, jint);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_result_null
|
||||
* Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1null
|
||||
(JNIEnv *, jclass, jobject);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_result_subtype
|
||||
* Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1subtype
|
||||
(JNIEnv *, jclass, jobject, jint);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_result_value
|
||||
@@ -1924,10 +1850,10 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1status64
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_step
|
||||
* Signature: (J)I
|
||||
* Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1step
|
||||
(JNIEnv *, jclass, jlong);
|
||||
(JNIEnv *, jclass, jobject);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
@@ -2137,14 +2063,6 @@ JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1int64
|
||||
JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1java_1object
|
||||
(JNIEnv *, jclass, jlong);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_value_nio_buffer
|
||||
* Signature: (Lorg/sqlite/jni/capi/sqlite3_value;)Ljava/nio/ByteBuffer;
|
||||
*/
|
||||
JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1nio_1buffer
|
||||
(JNIEnv *, jclass, jobject);
|
||||
|
||||
/*
|
||||
* Class: org_sqlite_jni_capi_CApi
|
||||
* Method: sqlite3_value_nochange
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
** 2023-09-27
|
||||
**
|
||||
** 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 houses the Experimental annotation for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni.annotation;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
This annotation is for flagging methods, constructors, and types
|
||||
which are expressly experimental and subject to any amount of
|
||||
change or outright removal. Client code should not rely on such
|
||||
features.
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({
|
||||
ElementType.METHOD,
|
||||
ElementType.CONSTRUCTOR,
|
||||
ElementType.TYPE
|
||||
})
|
||||
public @interface Experimental{}
|
||||
@@ -9,22 +9,21 @@
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file houses the NotNull annotation for the sqlite3 C API.
|
||||
** This file houses the NotNull annotaion for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni.annotation;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
This annotation is for flagging parameters which may not legally be
|
||||
null or point to closed/finalized C-side resources.
|
||||
|
||||
<p>In the case of Java types which map directly to C struct types
|
||||
(e.g. {@link org.sqlite.jni.capi.sqlite3}, {@link
|
||||
org.sqlite.jni.capi.sqlite3_stmt}, and {@link
|
||||
org.sqlite.jni.capi.sqlite3_context}), a closed/finalized resource
|
||||
is also considered to be null for purposes this annotation because
|
||||
the C-side effect of passing such a handle is the same as if null
|
||||
is passed.</p>
|
||||
(e.g. {@link org.sqlite.jni.sqlite3}, {@link
|
||||
org.sqlite.jni.sqlite3_stmt}, and {@link
|
||||
org.sqlite.jni.sqlite3_context}), a closed/finalized resource is
|
||||
also considered to be null for purposes this annotation because the
|
||||
C-side effect of passing such a handle is the same as if null is
|
||||
passed.</p>
|
||||
|
||||
<p>When used in the context of Java interfaces which are called
|
||||
from the C APIs, this annotation communicates that the C API will
|
||||
@@ -32,23 +31,12 @@ import java.lang.annotation.*;
|
||||
|
||||
<p>Passing a null, for this annotation's definition of null, for
|
||||
any parameter marked with this annoation specifically invokes
|
||||
undefined behavior (see below).</p>
|
||||
undefined behavior.</p>
|
||||
|
||||
<p>Passing 0 (i.e. C NULL) or a negative value for any long-type
|
||||
parameter marked with this annoation specifically invokes undefined
|
||||
behavior (see below). Such values are treated as C pointers in the
|
||||
JNI layer.</p>
|
||||
|
||||
<p><b>Undefined behaviour:</b> the JNI build uses the {@code
|
||||
SQLITE_ENABLE_API_ARMOR} build flag, meaning that the C code
|
||||
invoked with invalid NULL pointers and the like will not invoke
|
||||
undefined behavior in the conventional C sense, but may, for
|
||||
example, return result codes which are not documented for the
|
||||
affected APIs or may otherwise behave unpredictably. In no known
|
||||
cases will such arguments result in C-level code dereferencing a
|
||||
NULL pointer or accessing out-of-bounds (or otherwise invalid)
|
||||
memory. In other words, they may cause unexpected behavior but
|
||||
should never cause an outright crash or security issue.</p>
|
||||
behavior. Such values are treated as C pointers in the JNI
|
||||
layer.</p>
|
||||
|
||||
<p>Note that the C-style API does not throw any exceptions on its
|
||||
own because it has a no-throw policy in order to retain its C-style
|
||||
@@ -60,12 +48,12 @@ import java.lang.annotation.*;
|
||||
code.</p>
|
||||
|
||||
<p>This annotation is solely for the use by the classes in the
|
||||
org.sqlite.jni package and subpackages, but is made public so that
|
||||
org.sqlite package and subpackages, but is made public so that
|
||||
javadoc will link to it from the annotated functions. It is not
|
||||
part of the public API and client-level code must not rely on
|
||||
it.</p>
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.PARAMETER)
|
||||
@java.lang.annotation.Documented
|
||||
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
|
||||
@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER)
|
||||
public @interface NotNull{}
|
||||
|
||||
@@ -9,10 +9,9 @@
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file houses the Nullable annotation for the sqlite3 C API.
|
||||
** This file houses the Nullable annotaion for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni.annotation;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
This annotation is for flagging parameters which may legally be
|
||||
@@ -27,7 +26,7 @@ import java.lang.annotation.*;
|
||||
annotated functions. It is not part of the public API and
|
||||
client-level code must not rely on it.
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.PARAMETER)
|
||||
@java.lang.annotation.Documented
|
||||
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
|
||||
@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER)
|
||||
public @interface Nullable{}
|
||||
|
||||
@@ -42,75 +42,9 @@ public abstract class AggregateFunction<T> implements SQLFunction {
|
||||
*/
|
||||
public void xDestroy() {}
|
||||
|
||||
/**
|
||||
PerContextState assists aggregate and window functions in
|
||||
managing their accumulator state across calls to the UDF's
|
||||
callbacks.
|
||||
|
||||
<p>T must be of a type which can be legally stored as a value in
|
||||
java.util.HashMap<KeyType,T>.
|
||||
|
||||
<p>If a given aggregate or window function is called multiple times
|
||||
in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)...,
|
||||
then the clients need some way of knowing which call is which so
|
||||
that they can map their state between their various UDF callbacks
|
||||
and reset it via xFinal(). This class takes care of such
|
||||
mappings.
|
||||
|
||||
<p>This class works by mapping
|
||||
sqlite3_context.getAggregateContext() to a single piece of
|
||||
state, of a client-defined type (the T part of this class), which
|
||||
persists across a "matching set" of the UDF's callbacks.
|
||||
|
||||
<p>This class is a helper providing commonly-needed functionality
|
||||
- it is not required for use with aggregate or window functions.
|
||||
Client UDFs are free to perform such mappings using custom
|
||||
approaches. The provided {@link AggregateFunction} and {@link
|
||||
WindowFunction} classes use this.
|
||||
*/
|
||||
public static final class PerContextState<T> {
|
||||
private final java.util.Map<Long,ValueHolder<T>> map
|
||||
= new java.util.HashMap<>();
|
||||
|
||||
/**
|
||||
Should be called from a UDF's xStep(), xValue(), and xInverse()
|
||||
methods, passing it that method's first argument and an initial
|
||||
value for the persistent state. If there is currently no
|
||||
mapping for the given context within the map, one is created
|
||||
using the given initial value, else the existing one is used
|
||||
and the 2nd argument is ignored. It returns a ValueHolder<T>
|
||||
which can be used to modify that state directly without
|
||||
requiring that the client update the underlying map's entry.
|
||||
|
||||
<p>The caller is obligated to eventually call
|
||||
takeAggregateState() to clear the mapping.
|
||||
*/
|
||||
public ValueHolder<T> getAggregateState(sqlite3_context cx, T initialValue){
|
||||
final Long key = cx.getAggregateContext(true);
|
||||
ValueHolder<T> rc = null==key ? null : map.get(key);
|
||||
if( null==rc ){
|
||||
map.put(key, rc = new ValueHolder<>(initialValue));
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
Should be called from a UDF's xFinal() method and passed that
|
||||
method's first argument. This function removes the value
|
||||
associated with cx.getAggregateContext() from the map and
|
||||
returns it, returning null if no other UDF method has been
|
||||
called to set up such a mapping. The latter condition will be
|
||||
the case if a UDF is used in a statement which has no result
|
||||
rows.
|
||||
*/
|
||||
public T takeAggregateState(sqlite3_context cx){
|
||||
final ValueHolder<T> h = map.remove(cx.getAggregateContext(false));
|
||||
return null==h ? null : h.value;
|
||||
}
|
||||
}
|
||||
|
||||
/** Per-invocation state for the UDF. */
|
||||
private final PerContextState<T> map = new PerContextState<>();
|
||||
private final SQLFunction.PerContextState<T> map =
|
||||
new SQLFunction.PerContextState<>();
|
||||
|
||||
/**
|
||||
To be called from the implementation's xStep() method, as well
|
||||
|
||||
@@ -20,8 +20,7 @@ import org.sqlite.jni.annotation.*;
|
||||
public interface AuthorizerCallback extends CallbackProxy {
|
||||
/**
|
||||
Must function as described for the C-level
|
||||
sqlite3_set_authorizer() callback. If it throws, the error is
|
||||
converted to a db-level error and the exception is suppressed.
|
||||
sqlite3_set_authorizer() callback.
|
||||
*/
|
||||
int call(int opId, @Nullable String s1, @Nullable String s2,
|
||||
@Nullable String s3, @Nullable String s4);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,9 +24,8 @@ package org.sqlite.jni.capi;
|
||||
propagated. For callback interfaces which support returning error
|
||||
info to the core, the JNI binding will convert any exceptions to
|
||||
C-level error information. For callback interfaces which do not
|
||||
support returning error information, all exceptions will
|
||||
necessarily be suppressed in order to retain the C-style no-throw
|
||||
semantics and avoid invoking undefined behavior in the C layer.
|
||||
support, all exceptions will necessarily be suppressed in order to
|
||||
retain the C-style no-throw semantics.
|
||||
|
||||
<p>Callbacks of this style follow a common naming convention:
|
||||
|
||||
|
||||
@@ -21,9 +21,8 @@ public interface CollationNeededCallback extends CallbackProxy {
|
||||
Has the same semantics as the C-level sqlite3_create_collation()
|
||||
callback.
|
||||
|
||||
<p>Because the C API has no mechanism for reporting errors
|
||||
from this callbacks, any exceptions thrown by this callback
|
||||
are suppressed.
|
||||
<p>If it throws, the exception message is passed on to the db and
|
||||
the exception is suppressed.
|
||||
*/
|
||||
void call(sqlite3 db, int eTextRep, String collationName);
|
||||
int call(sqlite3 db, int eTextRep, String collationName);
|
||||
}
|
||||
|
||||
@@ -19,8 +19,7 @@ package org.sqlite.jni.capi;
|
||||
public interface CommitHookCallback extends CallbackProxy {
|
||||
/**
|
||||
Works as documented for the C-level sqlite3_commit_hook()
|
||||
callback. If it throws, the exception is translated into
|
||||
a db-level error.
|
||||
callback. Must not throw.
|
||||
*/
|
||||
int call();
|
||||
}
|
||||
|
||||
+2
-2
@@ -16,10 +16,10 @@ package org.sqlite.jni.capi;
|
||||
/**
|
||||
A callback for use with sqlite3_config().
|
||||
*/
|
||||
public interface ConfigSqlLogCallback {
|
||||
public interface ConfigSqllogCallback {
|
||||
/**
|
||||
Must function as described for a C-level callback for
|
||||
{@link CApi#sqlite3_config(ConfigSqlLogCallback)}, with the slight signature change.
|
||||
{@link CApi#sqlite3_config(ConfigSqllogCallback)}, with the slight signature change.
|
||||
*/
|
||||
void call(sqlite3 db, String msg, int msgType );
|
||||
}
|
||||
@@ -228,26 +228,4 @@ public final class OutputPointer {
|
||||
/** Sets the current value. */
|
||||
public final void set(byte[] v){value = v;}
|
||||
}
|
||||
|
||||
/**
|
||||
Output pointer for use with native routines which return
|
||||
blobs via java.nio.ByteBuffer.
|
||||
|
||||
See {@link org.sqlite.jni.capi.CApi#sqlite3_jni_supports_nio}
|
||||
*/
|
||||
public static final class ByteBuffer {
|
||||
/**
|
||||
This is public for ease of use. Accessors are provided for
|
||||
consistency with the higher-level types.
|
||||
*/
|
||||
public java.nio.ByteBuffer value;
|
||||
/** Initializes with the value null. */
|
||||
public ByteBuffer(){this(null);}
|
||||
/** Initializes with the value v. */
|
||||
public ByteBuffer(java.nio.ByteBuffer v){value = v;}
|
||||
/** Returns the current value. */
|
||||
public final java.nio.ByteBuffer get(){return value;}
|
||||
/** Sets the current value. */
|
||||
public final void set(java.nio.ByteBuffer v){value = v;}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,7 @@ public interface PrepareMultiCallback extends CallbackProxy {
|
||||
sqlite3_prepare_multi() will _not_ finalize st - it is up
|
||||
to the call() implementation how st is handled.
|
||||
|
||||
Must return 0 on success or an SQLITE_... code on error. If it
|
||||
throws, sqlite3_prepare_multi() will transform the exception into
|
||||
a db-level error in order to retain the C-style error semantics
|
||||
of the API.
|
||||
Must return 0 on success or an SQLITE_... code on error.
|
||||
|
||||
See the {@link Finalize} class for a wrapper which finalizes the
|
||||
statement after calling a proxy PrepareMultiCallback.
|
||||
@@ -40,7 +37,7 @@ public interface PrepareMultiCallback extends CallbackProxy {
|
||||
any sqlite3_stmt passed to its callback.
|
||||
*/
|
||||
public static final class Finalize implements PrepareMultiCallback {
|
||||
private final PrepareMultiCallback p;
|
||||
private PrepareMultiCallback p;
|
||||
/**
|
||||
p is the proxy to call() when this.call() is called.
|
||||
*/
|
||||
|
||||
@@ -19,8 +19,7 @@ package org.sqlite.jni.capi;
|
||||
public interface PreupdateHookCallback extends CallbackProxy {
|
||||
/**
|
||||
Must function as described for the C-level sqlite3_preupdate_hook()
|
||||
callback. If it throws, the exception is translated to a
|
||||
db-level error and the exception is suppressed.
|
||||
callback.
|
||||
*/
|
||||
void call(sqlite3 db, int op, String dbName, String dbTable,
|
||||
long iKey1, long iKey2 );
|
||||
|
||||
@@ -18,9 +18,8 @@ package org.sqlite.jni.capi;
|
||||
*/
|
||||
public interface RollbackHookCallback extends CallbackProxy {
|
||||
/**
|
||||
Must function as documented for the C-level sqlite3_rollback_hook()
|
||||
callback. If it throws, the exception is translated into
|
||||
a db-level error.
|
||||
Works as documented for the C-level sqlite3_rollback_hook()
|
||||
callback.
|
||||
*/
|
||||
void call();
|
||||
}
|
||||
|
||||
@@ -33,4 +33,71 @@ package org.sqlite.jni.capi;
|
||||
*/
|
||||
public interface SQLFunction {
|
||||
|
||||
/**
|
||||
PerContextState assists aggregate and window functions in
|
||||
managing their accumulator state across calls to the UDF's
|
||||
callbacks.
|
||||
|
||||
<p>T must be of a type which can be legally stored as a value in
|
||||
java.util.HashMap<KeyType,T>.
|
||||
|
||||
<p>If a given aggregate or window function is called multiple times
|
||||
in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)...,
|
||||
then the clients need some way of knowing which call is which so
|
||||
that they can map their state between their various UDF callbacks
|
||||
and reset it via xFinal(). This class takes care of such
|
||||
mappings.
|
||||
|
||||
<p>This class works by mapping
|
||||
sqlite3_context.getAggregateContext() to a single piece of
|
||||
state, of a client-defined type (the T part of this class), which
|
||||
persists across a "matching set" of the UDF's callbacks.
|
||||
|
||||
<p>This class is a helper providing commonly-needed functionality
|
||||
- it is not required for use with aggregate or window functions.
|
||||
Client UDFs are free to perform such mappings using custom
|
||||
approaches. The provided {@link AggregateFunction} and {@link
|
||||
WindowFunction} classes use this.
|
||||
*/
|
||||
public static final class PerContextState<T> {
|
||||
private final java.util.Map<Long,ValueHolder<T>> map
|
||||
= new java.util.HashMap<>();
|
||||
|
||||
/**
|
||||
Should be called from a UDF's xStep(), xValue(), and xInverse()
|
||||
methods, passing it that method's first argument and an initial
|
||||
value for the persistent state. If there is currently no
|
||||
mapping for the given context within the map, one is created
|
||||
using the given initial value, else the existing one is used
|
||||
and the 2nd argument is ignored. It returns a ValueHolder<T>
|
||||
which can be used to modify that state directly without
|
||||
requiring that the client update the underlying map's entry.
|
||||
|
||||
<p>The caller is obligated to eventually call
|
||||
takeAggregateState() to clear the mapping.
|
||||
*/
|
||||
public ValueHolder<T> getAggregateState(sqlite3_context cx, T initialValue){
|
||||
final Long key = cx.getAggregateContext(true);
|
||||
ValueHolder<T> rc = null==key ? null : map.get(key);
|
||||
if( null==rc ){
|
||||
map.put(key, rc = new ValueHolder<>(initialValue));
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
Should be called from a UDF's xFinal() method and passed that
|
||||
method's first argument. This function removes the value
|
||||
associated with cx.getAggregateContext() from the map and
|
||||
returns it, returning null if no other UDF method has been
|
||||
called to set up such a mapping. The latter condition will be
|
||||
the case if a UDF is used in a statement which has no result
|
||||
rows.
|
||||
*/
|
||||
public T takeAggregateState(sqlite3_context cx){
|
||||
final ValueHolder<T> h = map.remove(cx.getAggregateContext(false));
|
||||
return null==h ? null : h.value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,14 +38,6 @@ import java.util.concurrent.Future;
|
||||
@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
|
||||
@interface SingleThreadOnly{}
|
||||
|
||||
/**
|
||||
Annotation for Tester1 tests which must only be run if
|
||||
sqlite3_jni_supports_nio() is true.
|
||||
*/
|
||||
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
|
||||
@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
|
||||
@interface RequiresJniNio{}
|
||||
|
||||
public class Tester1 implements Runnable {
|
||||
//! True when running in multi-threaded mode.
|
||||
private static boolean mtMode = false;
|
||||
@@ -54,7 +46,7 @@ public class Tester1 implements Runnable {
|
||||
//! True to shuffle the order of the tests.
|
||||
private static boolean shuffle = false;
|
||||
//! True to dump the list of to-run tests to stdout.
|
||||
private static int listRunTests = 0;
|
||||
private static boolean listRunTests = false;
|
||||
//! True to squelch all out() and outln() output.
|
||||
private static boolean quietMode = false;
|
||||
//! Total number of runTests() calls.
|
||||
@@ -335,7 +327,7 @@ public class Tester1 implements Runnable {
|
||||
|
||||
|
||||
rc = sqlite3_prepare_v3(db, "INSERT INTO t2(a) VALUES(1),(2),(3)",
|
||||
0, outStmt);
|
||||
SQLITE_PREPARE_NORMALIZE, outStmt);
|
||||
affirm(0 == rc);
|
||||
stmt = outStmt.get();
|
||||
affirm(0 != stmt.getNativePointer());
|
||||
@@ -390,15 +382,6 @@ public class Tester1 implements Runnable {
|
||||
stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
|
||||
affirm( sqlite3_stmt_readonly(stmt) );
|
||||
affirm( !sqlite3_stmt_busy(stmt) );
|
||||
if( sqlite3_compileoption_used("ENABLE_COLUMN_METADATA") ){
|
||||
/* Unlike in native C code, JNI won't trigger an
|
||||
UnsatisfiedLinkError until these are called (on Linux, at
|
||||
least). */
|
||||
affirm("t".equals(sqlite3_column_table_name(stmt,0)));
|
||||
affirm("main".equals(sqlite3_column_database_name(stmt,0)));
|
||||
affirm("a".equals(sqlite3_column_origin_name(stmt,0)));
|
||||
}
|
||||
|
||||
int total2 = 0;
|
||||
while( SQLITE_ROW == sqlite3_step(stmt) ){
|
||||
affirm( sqlite3_stmt_busy(stmt) );
|
||||
@@ -494,7 +477,6 @@ public class Tester1 implements Runnable {
|
||||
stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
|
||||
StringBuilder sbuf = new StringBuilder();
|
||||
n = 0;
|
||||
final boolean tryNio = sqlite3_jni_supports_nio();
|
||||
while( SQLITE_ROW == sqlite3_step(stmt) ){
|
||||
final sqlite3_value sv = sqlite3_value_dup(sqlite3_column_value(stmt,0));
|
||||
final String txt = sqlite3_column_text16(stmt, 0);
|
||||
@@ -509,15 +491,6 @@ public class Tester1 implements Runnable {
|
||||
StandardCharsets.UTF_8)) );
|
||||
affirm( txt.length() == sqlite3_value_bytes16(sv)/2 );
|
||||
affirm( txt.equals(sqlite3_value_text16(sv)) );
|
||||
if( tryNio ){
|
||||
java.nio.ByteBuffer bu = sqlite3_value_nio_buffer(sv);
|
||||
byte ba[] = sqlite3_value_blob(sv);
|
||||
affirm( ba.length == bu.capacity() );
|
||||
int i = 0;
|
||||
for( byte b : ba ){
|
||||
affirm( b == bu.get(i++) );
|
||||
}
|
||||
}
|
||||
sqlite3_value_free(sv);
|
||||
++n;
|
||||
}
|
||||
@@ -575,79 +548,6 @@ public class Tester1 implements Runnable {
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
@RequiresJniNio
|
||||
private void testBindByteBuffer(){
|
||||
/* TODO: these tests need to be much more extensive to check the
|
||||
begin/end range handling. */
|
||||
|
||||
java.nio.ByteBuffer zeroCheck =
|
||||
java.nio.ByteBuffer.allocateDirect(0);
|
||||
affirm( null != zeroCheck );
|
||||
zeroCheck = null;
|
||||
sqlite3 db = createNewDb();
|
||||
execSql(db, "CREATE TABLE t(a)");
|
||||
|
||||
final java.nio.ByteBuffer buf = java.nio.ByteBuffer.allocateDirect(10);
|
||||
buf.put((byte)0x31)/*note that we'll skip this one*/
|
||||
.put((byte)0x32)
|
||||
.put((byte)0x33)
|
||||
.put((byte)0x34)
|
||||
.put((byte)0x35)/*we'll skip this one too*/;
|
||||
|
||||
final int expectTotal = buf.get(1) + buf.get(2) + buf.get(3);
|
||||
sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
|
||||
affirm( SQLITE_ERROR == sqlite3_bind_blob(stmt, 1, buf, -1, 0),
|
||||
"Buffer offset may not be negative." );
|
||||
affirm( 0 == sqlite3_bind_blob(stmt, 1, buf, 1, 3) );
|
||||
affirm( SQLITE_DONE == sqlite3_step(stmt) );
|
||||
sqlite3_finalize(stmt);
|
||||
stmt = prepare(db, "SELECT a FROM t;");
|
||||
int total = 0;
|
||||
affirm( SQLITE_ROW == sqlite3_step(stmt) );
|
||||
byte blob[] = sqlite3_column_blob(stmt, 0);
|
||||
java.nio.ByteBuffer nioBlob =
|
||||
sqlite3_column_nio_buffer(stmt, 0);
|
||||
affirm(3 == blob.length);
|
||||
affirm(blob.length == nioBlob.capacity());
|
||||
affirm(blob.length == nioBlob.limit());
|
||||
int i = 0;
|
||||
for(byte b : blob){
|
||||
affirm( i<=3 );
|
||||
affirm(b == buf.get(1 + i));
|
||||
affirm(b == nioBlob.get(i));
|
||||
++i;
|
||||
total += b;
|
||||
}
|
||||
affirm( SQLITE_DONE == sqlite3_step(stmt) );
|
||||
sqlite3_finalize(stmt);
|
||||
affirm(total == expectTotal);
|
||||
|
||||
SQLFunction func =
|
||||
new ScalarFunction(){
|
||||
public void xFunc(sqlite3_context cx, sqlite3_value[] args){
|
||||
sqlite3_result_blob(cx, buf, 1, 3);
|
||||
}
|
||||
};
|
||||
|
||||
affirm( 0 == sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func) );
|
||||
stmt = prepare(db, "SELECT myfunc()");
|
||||
affirm( SQLITE_ROW == sqlite3_step(stmt) );
|
||||
blob = sqlite3_column_blob(stmt, 0);
|
||||
affirm(3 == blob.length);
|
||||
i = 0;
|
||||
total = 0;
|
||||
for(byte b : blob){
|
||||
affirm( i<=3 );
|
||||
affirm(b == buf.get(1 + i++));
|
||||
total += b;
|
||||
}
|
||||
affirm( SQLITE_DONE == sqlite3_step(stmt) );
|
||||
sqlite3_finalize(stmt);
|
||||
affirm(total == expectTotal);
|
||||
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
private void testSql(){
|
||||
sqlite3 db = createNewDb();
|
||||
sqlite3_stmt stmt = prepare(db, "SELECT 1");
|
||||
@@ -693,9 +593,9 @@ public class Tester1 implements Runnable {
|
||||
};
|
||||
final CollationNeededCallback collLoader = new CollationNeededCallback(){
|
||||
@Override
|
||||
public void call(sqlite3 dbArg, int eTextRep, String collationName){
|
||||
public int call(sqlite3 dbArg, int eTextRep, String collationName){
|
||||
affirm(dbArg == db/* as opposed to a temporary object*/);
|
||||
sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation);
|
||||
return sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation);
|
||||
}
|
||||
};
|
||||
int rc = sqlite3_collation_needed(db, collLoader);
|
||||
@@ -903,17 +803,13 @@ public class Tester1 implements Runnable {
|
||||
affirm( 0==rc );
|
||||
int n = 0;
|
||||
if( SQLITE_ROW == sqlite3_step(stmt) ){
|
||||
affirm( testResult.value == sqlite3_column_java_object(stmt, 0) );
|
||||
affirm( testResult.value == sqlite3_column_java_object(stmt, 0, sqlite3.class) );
|
||||
affirm( null == sqlite3_column_java_object(stmt, 0, sqlite3_stmt.class) );
|
||||
affirm( null == sqlite3_column_java_object(stmt,1) );
|
||||
final sqlite3_value v = sqlite3_column_value(stmt, 0);
|
||||
affirm( testResult.value == sqlite3_value_java_object(v) );
|
||||
affirm( testResult.value == sqlite3_value_java_object(v, sqlite3.class) );
|
||||
affirm( testResult.value == sqlite3_value_java_casted(v, sqlite3.class) );
|
||||
affirm( testResult.value ==
|
||||
sqlite3_value_java_object(v, testResult.value.getClass()) );
|
||||
affirm( testResult.value == sqlite3_value_java_object(v, Object.class) );
|
||||
affirm( null == sqlite3_value_java_object(v, String.class) );
|
||||
sqlite3_value_java_casted(v, testResult.value.getClass()) );
|
||||
affirm( testResult.value == sqlite3_value_java_casted(v, Object.class) );
|
||||
affirm( null == sqlite3_value_java_casted(v, String.class) );
|
||||
++n;
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
@@ -928,28 +824,15 @@ public class Tester1 implements Runnable {
|
||||
// To confirm that xFinal() is called with no aggregate state
|
||||
// when the corresponding result set is empty.
|
||||
new ValueHolder<>(false);
|
||||
final ValueHolder<sqlite3_value[]> neverEverDoThisInClientCode = new ValueHolder<>(null);
|
||||
final ValueHolder<sqlite3_context> neverEverDoThisInClientCode2 = new ValueHolder<>(null);
|
||||
SQLFunction func = new AggregateFunction<Integer>(){
|
||||
@Override
|
||||
public void xStep(sqlite3_context cx, sqlite3_value[] args){
|
||||
if( null==neverEverDoThisInClientCode.value ){
|
||||
/* !!!NEVER!!! hold a reference to an sqlite3_value or
|
||||
sqlite3_context object like this in client code! They
|
||||
are ONLY legal for the duration of their single
|
||||
call. We do it here ONLY to test that the defenses
|
||||
against clients doing this are working. */
|
||||
neverEverDoThisInClientCode.value = args;
|
||||
}
|
||||
final ValueHolder<Integer> agg = this.getAggregateState(cx, 0);
|
||||
agg.value += sqlite3_value_int(args[0]);
|
||||
affirm( agg == this.getAggregateState(cx, 0) );
|
||||
}
|
||||
@Override
|
||||
public void xFinal(sqlite3_context cx){
|
||||
if( null==neverEverDoThisInClientCode2.value ){
|
||||
neverEverDoThisInClientCode2.value = cx;
|
||||
}
|
||||
final Integer v = this.takeAggregateState(cx);
|
||||
if(null == v){
|
||||
xFinalNull.value = true;
|
||||
@@ -974,10 +857,6 @@ public class Tester1 implements Runnable {
|
||||
}
|
||||
affirm( 1==n );
|
||||
affirm(!xFinalNull.value);
|
||||
affirm( null!=neverEverDoThisInClientCode.value );
|
||||
affirm( null!=neverEverDoThisInClientCode2.value );
|
||||
affirm( 0<neverEverDoThisInClientCode.value.length );
|
||||
affirm( 0==neverEverDoThisInClientCode2.value.getNativePointer() );
|
||||
sqlite3_reset(stmt);
|
||||
affirm( 1==sqlite3_stmt_status(stmt, SQLITE_STMTSTATUS_RUN, false) );
|
||||
// Ensure that the accumulator is reset on subsequent calls...
|
||||
@@ -1046,6 +925,7 @@ public class Tester1 implements Runnable {
|
||||
"ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING"+
|
||||
") AS sum_y "+
|
||||
"FROM twin ORDER BY x;");
|
||||
affirm( 0 == rc );
|
||||
int n = 0;
|
||||
while( SQLITE_ROW == sqlite3_step(stmt) ){
|
||||
final String s = sqlite3_column_text16(stmt, 0);
|
||||
@@ -1148,48 +1028,48 @@ public class Tester1 implements Runnable {
|
||||
@SingleThreadOnly /* because threads inherently break this test */
|
||||
private static void testBusy(){
|
||||
final String dbName = "_busy-handler.db";
|
||||
final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3();
|
||||
final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
|
||||
|
||||
int rc = sqlite3_open(dbName, outDb);
|
||||
++metrics.dbOpen;
|
||||
affirm( 0 == rc );
|
||||
final sqlite3 db1 = outDb.get();
|
||||
execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)");
|
||||
rc = sqlite3_open(dbName, outDb);
|
||||
++metrics.dbOpen;
|
||||
affirm( 0 == rc );
|
||||
affirm( outDb.get() != db1 );
|
||||
final sqlite3 db2 = outDb.get();
|
||||
|
||||
affirm( "main".equals( sqlite3_db_name(db1, 0) ) );
|
||||
rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo");
|
||||
affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) );
|
||||
affirm( "foo".equals( sqlite3_db_name(db1, 0) ) );
|
||||
|
||||
final ValueHolder<Integer> xBusyCalled = new ValueHolder<>(0);
|
||||
BusyHandlerCallback handler = new BusyHandlerCallback(){
|
||||
@Override public int call(int n){
|
||||
//outln("busy handler #"+n);
|
||||
return n > 2 ? 0 : ++xBusyCalled.value;
|
||||
}
|
||||
};
|
||||
rc = sqlite3_busy_handler(db2, handler);
|
||||
affirm(0 == rc);
|
||||
|
||||
// Force a locked condition...
|
||||
execSql(db1, "BEGIN EXCLUSIVE");
|
||||
rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt);
|
||||
affirm( SQLITE_BUSY == rc);
|
||||
affirm( null == outStmt.get() );
|
||||
affirm( 3 == xBusyCalled.value );
|
||||
sqlite3_close_v2(db1);
|
||||
sqlite3_close_v2(db2);
|
||||
try{
|
||||
final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3();
|
||||
final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
|
||||
|
||||
int rc = sqlite3_open(dbName, outDb);
|
||||
++metrics.dbOpen;
|
||||
affirm( 0 == rc );
|
||||
final sqlite3 db1 = outDb.get();
|
||||
execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)");
|
||||
rc = sqlite3_open(dbName, outDb);
|
||||
++metrics.dbOpen;
|
||||
affirm( 0 == rc );
|
||||
affirm( outDb.get() != db1 );
|
||||
final sqlite3 db2 = outDb.get();
|
||||
|
||||
affirm( "main".equals( sqlite3_db_name(db1, 0) ) );
|
||||
rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo");
|
||||
affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) );
|
||||
affirm( "foo".equals( sqlite3_db_name(db1, 0) ) );
|
||||
affirm( SQLITE_MISUSE == sqlite3_db_config(db1, 0, 0, null) );
|
||||
|
||||
final ValueHolder<Integer> xBusyCalled = new ValueHolder<>(0);
|
||||
BusyHandlerCallback handler = new BusyHandlerCallback(){
|
||||
@Override public int call(int n){
|
||||
//outln("busy handler #"+n);
|
||||
return n > 2 ? 0 : ++xBusyCalled.value;
|
||||
}
|
||||
};
|
||||
rc = sqlite3_busy_handler(db2, handler);
|
||||
affirm(0 == rc);
|
||||
|
||||
// Force a locked condition...
|
||||
execSql(db1, "BEGIN EXCLUSIVE");
|
||||
rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt);
|
||||
affirm( SQLITE_BUSY == rc);
|
||||
affirm( null == outStmt.get() );
|
||||
affirm( 3 == xBusyCalled.value );
|
||||
sqlite3_close_v2(db1);
|
||||
sqlite3_close_v2(db2);
|
||||
}finally{
|
||||
try{(new java.io.File(dbName)).delete();}
|
||||
catch(Exception e){/* ignore */}
|
||||
final java.io.File f = new java.io.File(dbName);
|
||||
f.delete();
|
||||
}catch(Exception e){
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1213,7 +1093,6 @@ public class Tester1 implements Runnable {
|
||||
|
||||
private void testCommitHook(){
|
||||
final sqlite3 db = createNewDb();
|
||||
sqlite3_extended_result_codes(db, true);
|
||||
final ValueHolder<Integer> counter = new ValueHolder<>(0);
|
||||
final ValueHolder<Integer> hookResult = new ValueHolder<>(0);
|
||||
final CommitHookCallback theHook = new CommitHookCallback(){
|
||||
@@ -1256,7 +1135,7 @@ public class Tester1 implements Runnable {
|
||||
affirm( 5 == counter.value );
|
||||
hookResult.value = SQLITE_ERROR;
|
||||
int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;");
|
||||
affirm( SQLITE_CONSTRAINT_COMMITHOOK == rc );
|
||||
affirm( SQLITE_CONSTRAINT == rc );
|
||||
affirm( 6 == counter.value );
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
@@ -1475,9 +1354,6 @@ public class Tester1 implements Runnable {
|
||||
authRc.value = SQLITE_DENY;
|
||||
int rc = execSql(db, false, "UPDATE t SET a=2");
|
||||
affirm( SQLITE_AUTH==rc );
|
||||
sqlite3_set_authorizer(db, null);
|
||||
rc = execSql(db, false, "UPDATE t SET a=2");
|
||||
affirm( 0==rc );
|
||||
// TODO: expand these tests considerably
|
||||
sqlite3_close(db);
|
||||
}
|
||||
@@ -1539,7 +1415,7 @@ public class Tester1 implements Runnable {
|
||||
|
||||
val.value = 0;
|
||||
final AutoExtensionCallback ax2 = new AutoExtensionCallback(){
|
||||
@Override public int call(sqlite3 db){
|
||||
@Override public synchronized int call(sqlite3 db){
|
||||
++val.value;
|
||||
return 0;
|
||||
}
|
||||
@@ -1750,7 +1626,7 @@ public class Tester1 implements Runnable {
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
b = sqlite3_blob_open(db, "main", "t", "a",
|
||||
sqlite3_last_insert_rowid(db), 0);
|
||||
sqlite3_last_insert_rowid(db), 1);
|
||||
affirm( null!=b );
|
||||
rc = sqlite3_blob_reopen(b, 2);
|
||||
affirm( 0==rc );
|
||||
@@ -1760,86 +1636,6 @@ public class Tester1 implements Runnable {
|
||||
affirm( 100==tgt[0] && 101==tgt[1] && 102==tgt[2], "DEF" );
|
||||
rc = sqlite3_blob_close(b);
|
||||
affirm( 0==rc );
|
||||
|
||||
if( !sqlite3_jni_supports_nio() ){
|
||||
outln("WARNING: skipping tests for ByteBuffer-using sqlite3_blob APIs ",
|
||||
"because this platform lacks that support.");
|
||||
sqlite3_close_v2(db);
|
||||
return;
|
||||
}
|
||||
/* Sanity checks for the java.nio.ByteBuffer-taking overloads of
|
||||
sqlite3_blob_read/write(). */
|
||||
execSql(db, "UPDATE t SET a=zeroblob(10)");
|
||||
b = sqlite3_blob_open(db, "main", "t", "a", 1, 1);
|
||||
affirm( null!=b );
|
||||
java.nio.ByteBuffer bb = java.nio.ByteBuffer.allocateDirect(10);
|
||||
for( byte i = 0; i < 10; ++i ){
|
||||
bb.put((int)i, (byte)(48+i & 0xff));
|
||||
}
|
||||
rc = sqlite3_blob_write(b, 1, bb, 1, 10);
|
||||
affirm( rc==SQLITE_ERROR, "b length < (srcOffset + bb length)" );
|
||||
rc = sqlite3_blob_write(b, -1, bb);
|
||||
affirm( rc==SQLITE_ERROR, "Target offset may not be negative" );
|
||||
rc = sqlite3_blob_write(b, 0, bb, -1, -1);
|
||||
affirm( rc==SQLITE_ERROR, "Source offset may not be negative" );
|
||||
rc = sqlite3_blob_write(b, 1, bb, 1, 8);
|
||||
affirm( rc==0 );
|
||||
// b's contents: 0 49 50 51 52 53 54 55 56 0
|
||||
// ascii: 0 '1' '2' '3' '4' '5' '6' '7' '8' 0
|
||||
byte br[] = new byte[10];
|
||||
java.nio.ByteBuffer bbr =
|
||||
java.nio.ByteBuffer.allocateDirect(bb.limit());
|
||||
rc = sqlite3_blob_read( b, br, 0 );
|
||||
affirm( rc==0 );
|
||||
rc = sqlite3_blob_read( b, bbr );
|
||||
affirm( rc==0 );
|
||||
java.nio.ByteBuffer bbr2 = sqlite3_blob_read_nio_buffer(b, 0, 12);
|
||||
affirm( null==bbr2, "Read size is too big");
|
||||
bbr2 = sqlite3_blob_read_nio_buffer(b, -1, 3);
|
||||
affirm( null==bbr2, "Source offset is negative");
|
||||
bbr2 = sqlite3_blob_read_nio_buffer(b, 5, 6);
|
||||
affirm( null==bbr2, "Read pos+size is too big");
|
||||
bbr2 = sqlite3_blob_read_nio_buffer(b, 4, 7);
|
||||
affirm( null==bbr2, "Read pos+size is too big");
|
||||
bbr2 = sqlite3_blob_read_nio_buffer(b, 4, 6);
|
||||
affirm( null!=bbr2 );
|
||||
java.nio.ByteBuffer bbr3 =
|
||||
java.nio.ByteBuffer.allocateDirect(2 * bb.limit());
|
||||
java.nio.ByteBuffer bbr4 =
|
||||
java.nio.ByteBuffer.allocateDirect(5);
|
||||
rc = sqlite3_blob_read( b, bbr3 );
|
||||
affirm( rc==0 );
|
||||
rc = sqlite3_blob_read( b, bbr4 );
|
||||
affirm( rc==0 );
|
||||
affirm( sqlite3_blob_bytes(b)==bbr3.limit() );
|
||||
affirm( 5==bbr4.limit() );
|
||||
sqlite3_blob_close(b);
|
||||
affirm( 0==br[0] );
|
||||
affirm( 0==br[9] );
|
||||
affirm( 0==bbr.get(0) );
|
||||
affirm( 0==bbr.get(9) );
|
||||
affirm( bbr2.limit() == 6 );
|
||||
affirm( 0==bbr3.get(0) );
|
||||
{
|
||||
Exception ex = null;
|
||||
try{ bbr3.get(11); }
|
||||
catch(Exception e){ex = e;}
|
||||
affirm( ex instanceof IndexOutOfBoundsException,
|
||||
"bbr3.limit() was reset by read()" );
|
||||
ex = null;
|
||||
}
|
||||
affirm( 0==bbr4.get(0) );
|
||||
for( int i = 1; i < 9; ++i ){
|
||||
affirm( br[i] == 48 + i );
|
||||
affirm( br[i] == bbr.get(i) );
|
||||
affirm( br[i] == bbr3.get(i) );
|
||||
if( i>3 ){
|
||||
affirm( br[i] == bbr2.get(i-4) );
|
||||
}
|
||||
if( i < bbr4.limit() ){
|
||||
affirm( br[i] == bbr4.get(i) );
|
||||
}
|
||||
}
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
@@ -1852,13 +1648,9 @@ public class Tester1 implements Runnable {
|
||||
};
|
||||
final List<sqlite3_stmt> liStmt = new ArrayList<sqlite3_stmt>();
|
||||
final PrepareMultiCallback proxy = new PrepareMultiCallback.StepAll();
|
||||
final ValueHolder<String> toss = new ValueHolder<>(null);
|
||||
PrepareMultiCallback m = new PrepareMultiCallback() {
|
||||
@Override public int call(sqlite3_stmt st){
|
||||
liStmt.add(st);
|
||||
if( null!=toss.value ){
|
||||
throw new RuntimeException(toss.value);
|
||||
}
|
||||
return proxy.call(st);
|
||||
}
|
||||
};
|
||||
@@ -1868,10 +1660,6 @@ public class Tester1 implements Runnable {
|
||||
for( sqlite3_stmt st : liStmt ){
|
||||
sqlite3_finalize(st);
|
||||
}
|
||||
toss.value = "This is an exception.";
|
||||
rc = sqlite3_prepare_multi(db, "SELECT 1", m);
|
||||
affirm( SQLITE_ERROR==rc );
|
||||
affirm( sqlite3_errmsg(db).indexOf(toss.value)>0 );
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
@@ -1910,7 +1698,7 @@ public class Tester1 implements Runnable {
|
||||
mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) );
|
||||
java.util.Collections.shuffle(mlist);
|
||||
}
|
||||
if( (!fromThread && listRunTests>0) || listRunTests>1 ){
|
||||
if( listRunTests ){
|
||||
synchronized(this.getClass()){
|
||||
if( !fromThread ){
|
||||
out("Initial test"," list: ");
|
||||
@@ -1972,11 +1760,8 @@ public class Tester1 implements Runnable {
|
||||
-naps: sleep small random intervals between tests in order to add
|
||||
some chaos for cross-thread contention.
|
||||
|
||||
|
||||
-list-tests: outputs the list of tests being run, minus some
|
||||
which are hard-coded. In multi-threaded mode, use this twice to
|
||||
to emit the list run by each thread (which may differ from the initial
|
||||
list, in particular if -shuffle is used).
|
||||
which are hard-coded. This is noisy in multi-threaded mode.
|
||||
|
||||
-fail: forces an exception to be thrown during the test run. Use
|
||||
with -shuffle to make its appearance unpredictable.
|
||||
@@ -2005,7 +1790,7 @@ public class Tester1 implements Runnable {
|
||||
}else if(arg.equals("shuffle")){
|
||||
shuffle = true;
|
||||
}else if(arg.equals("list-tests")){
|
||||
++listRunTests;
|
||||
listRunTests = true;
|
||||
}else if(arg.equals("fail")){
|
||||
forceFail = true;
|
||||
}else if(arg.equals("sqllog")){
|
||||
@@ -2024,7 +1809,7 @@ public class Tester1 implements Runnable {
|
||||
|
||||
if( sqlLog ){
|
||||
if( sqlite3_compileoption_used("ENABLE_SQLLOG") ){
|
||||
final ConfigSqlLogCallback log = new ConfigSqlLogCallback() {
|
||||
final ConfigSqllogCallback log = new ConfigSqllogCallback() {
|
||||
@Override public void call(sqlite3 db, String msg, int op){
|
||||
switch(op){
|
||||
case 0: outln("Opening db: ",db); break;
|
||||
@@ -2035,7 +1820,7 @@ public class Tester1 implements Runnable {
|
||||
};
|
||||
int rc = sqlite3_config( log );
|
||||
affirm( 0==rc );
|
||||
rc = sqlite3_config( (ConfigSqlLogCallback)null );
|
||||
rc = sqlite3_config( (ConfigSqllogCallback)null );
|
||||
affirm( 0==rc );
|
||||
rc = sqlite3_config( log );
|
||||
affirm( 0==rc );
|
||||
@@ -2073,20 +1858,18 @@ public class Tester1 implements Runnable {
|
||||
if( forceFail ){
|
||||
testMethods.add(m);
|
||||
}
|
||||
}else if( m.isAnnotationPresent( RequiresJniNio.class )
|
||||
&& !sqlite3_jni_supports_nio() ){
|
||||
outln("Skipping test for lack of JNI java.nio.ByteBuffer support: ",
|
||||
name,"()\n");
|
||||
++nSkipped;
|
||||
}else if( !m.isAnnotationPresent( ManualTest.class ) ){
|
||||
if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){
|
||||
out("Skipping test in multi-thread mode: ",name,"()\n");
|
||||
++nSkipped;
|
||||
if( 0==nSkipped++ ){
|
||||
out("Skipping tests in multi-thread mode:");
|
||||
}
|
||||
out(" "+name+"()");
|
||||
}else if( name.startsWith("test") ){
|
||||
testMethods.add(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
if( nSkipped>0 ) out("\n");
|
||||
}
|
||||
|
||||
final long timeStart = System.currentTimeMillis();
|
||||
@@ -2118,7 +1901,6 @@ public class Tester1 implements Runnable {
|
||||
sqlite3_libversion_number(),"\n",
|
||||
sqlite3_libversion(),"\n",SQLITE_SOURCE_ID,"\n",
|
||||
"SQLITE_THREADSAFE=",sqlite3_threadsafe());
|
||||
outln("JVM NIO support? ",sqlite3_jni_supports_nio() ? "YES" : "NO");
|
||||
final boolean showLoopCount = (nRepeat>1 && nThread>1);
|
||||
if( showLoopCount ){
|
||||
outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each.");
|
||||
|
||||
@@ -19,8 +19,7 @@ package org.sqlite.jni.capi;
|
||||
public interface UpdateHookCallback extends CallbackProxy {
|
||||
/**
|
||||
Must function as described for the C-level sqlite3_update_hook()
|
||||
callback. If it throws, the exception is translated into
|
||||
a db-level error.
|
||||
callback.
|
||||
*/
|
||||
void call(int opId, String dbName, String tableName, long rowId);
|
||||
}
|
||||
|
||||
@@ -9,16 +9,14 @@
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file contains the ValueHolder utility class for the sqlite3
|
||||
** JNI bindings.
|
||||
** This file contains a set of tests for the sqlite3 JNI bindings.
|
||||
*/
|
||||
package org.sqlite.jni.capi;
|
||||
|
||||
/**
|
||||
A helper class which simply holds a single value. Its primary use
|
||||
is for communicating values out of anonymous classes, as doing so
|
||||
requires a "final" reference, as well as communicating aggregate
|
||||
SQL function state across calls to such functions.
|
||||
requires a "final" reference.
|
||||
*/
|
||||
public class ValueHolder<T> {
|
||||
public T value;
|
||||
|
||||
@@ -38,6 +38,6 @@ public final class sqlite3 extends NativePointerHolder<sqlite3>
|
||||
}
|
||||
|
||||
@Override public void close(){
|
||||
CApi.sqlite3_close_v2(this);
|
||||
CApi.sqlite3_close_v2(this.clearNativePointer());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ public final class sqlite3_blob extends NativePointerHolder<sqlite3_blob>
|
||||
private sqlite3_blob(){}
|
||||
|
||||
@Override public void close(){
|
||||
CApi.sqlite3_blob_close(this);
|
||||
CApi.sqlite3_blob_close(this.clearNativePointer());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,6 @@ public final class sqlite3_stmt extends NativePointerHolder<sqlite3_stmt>
|
||||
private sqlite3_stmt(){}
|
||||
|
||||
@Override public void close(){
|
||||
CApi.sqlite3_finalize(this);
|
||||
CApi.sqlite3_finalize(this.clearNativePointer());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,20 +625,10 @@ public class TesterFts5 {
|
||||
"SELECT fts5_columntext(ft, 1) FROM ft('x') ORDER BY rowid",
|
||||
"[x, x, x y z, x z, x y z, x]"
|
||||
);
|
||||
boolean threw = false;
|
||||
try{
|
||||
/* columntext() used to return NULLs when given an out-of bounds column
|
||||
but now results in a range error. */
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_columntext(ft, 2) FROM ft('x') ORDER BY rowid",
|
||||
"[null, null, null, null, null, null]"
|
||||
);
|
||||
}catch(Exception e){
|
||||
threw = true;
|
||||
affirm( e.getMessage().matches(".*column index out of range") );
|
||||
}
|
||||
affirm( threw );
|
||||
threw = false;
|
||||
do_execsql_test(db,
|
||||
"SELECT fts5_columntext(ft, 2) FROM ft('x') ORDER BY rowid",
|
||||
"[null, null, null, null, null, null]"
|
||||
);
|
||||
|
||||
/* Test fts5_columntotalsize() */
|
||||
do_execsql_test(db,
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
** This file is part of the wrapper1 interface for sqlite3.
|
||||
*/
|
||||
package org.sqlite.jni.wrapper1;
|
||||
import org.sqlite.jni.capi.CApi;
|
||||
import org.sqlite.jni.annotation.*;
|
||||
import org.sqlite.jni.capi.sqlite3_context;
|
||||
import org.sqlite.jni.capi.sqlite3_value;
|
||||
|
||||
/**
|
||||
EXPERIMENTAL/INCOMPLETE/UNTESTED
|
||||
@@ -47,75 +51,9 @@ public abstract class AggregateFunction<T> implements SqlFunction {
|
||||
*/
|
||||
public void xDestroy() {}
|
||||
|
||||
/**
|
||||
PerContextState assists aggregate and window functions in
|
||||
managing their accumulator state across calls to the UDF's
|
||||
callbacks.
|
||||
|
||||
<p>T must be of a type which can be legally stored as a value in
|
||||
java.util.HashMap<KeyType,T>.
|
||||
|
||||
<p>If a given aggregate or window function is called multiple times
|
||||
in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)...,
|
||||
then the clients need some way of knowing which call is which so
|
||||
that they can map their state between their various UDF callbacks
|
||||
and reset it via xFinal(). This class takes care of such
|
||||
mappings.
|
||||
|
||||
<p>This class works by mapping
|
||||
sqlite3_context.getAggregateContext() to a single piece of
|
||||
state, of a client-defined type (the T part of this class), which
|
||||
persists across a "matching set" of the UDF's callbacks.
|
||||
|
||||
<p>This class is a helper providing commonly-needed functionality
|
||||
- it is not required for use with aggregate or window functions.
|
||||
Client UDFs are free to perform such mappings using custom
|
||||
approaches. The provided {@link AggregateFunction} and {@link
|
||||
WindowFunction} classes use this.
|
||||
*/
|
||||
public static final class PerContextState<T> {
|
||||
private final java.util.Map<Long,ValueHolder<T>> map
|
||||
= new java.util.HashMap<>();
|
||||
|
||||
/**
|
||||
Should be called from a UDF's xStep(), xValue(), and xInverse()
|
||||
methods, passing it that method's first argument and an initial
|
||||
value for the persistent state. If there is currently no
|
||||
mapping for the given context within the map, one is created
|
||||
using the given initial value, else the existing one is used
|
||||
and the 2nd argument is ignored. It returns a ValueHolder<T>
|
||||
which can be used to modify that state directly without
|
||||
requiring that the client update the underlying map's entry.
|
||||
|
||||
<p>The caller is obligated to eventually call
|
||||
takeAggregateState() to clear the mapping.
|
||||
*/
|
||||
public ValueHolder<T> getAggregateState(SqlFunction.Arguments args, T initialValue){
|
||||
final Long key = args.getContext().getAggregateContext(true);
|
||||
ValueHolder<T> rc = null==key ? null : map.get(key);
|
||||
if( null==rc ){
|
||||
map.put(key, rc = new ValueHolder<>(initialValue));
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
Should be called from a UDF's xFinal() method and passed that
|
||||
method's first argument. This function removes the value
|
||||
associated with with the arguments' aggregate context from the
|
||||
map and returns it, returning null if no other UDF method has
|
||||
been called to set up such a mapping. The latter condition will
|
||||
be the case if a UDF is used in a statement which has no result
|
||||
rows.
|
||||
*/
|
||||
public T takeAggregateState(SqlFunction.Arguments args){
|
||||
final ValueHolder<T> h = map.remove(args.getContext().getAggregateContext(false));
|
||||
return null==h ? null : h.value;
|
||||
}
|
||||
}
|
||||
|
||||
/** Per-invocation state for the UDF. */
|
||||
private final PerContextState<T> map = new PerContextState<>();
|
||||
private final SqlFunction.PerContextState<T> map =
|
||||
new SqlFunction.PerContextState<>();
|
||||
|
||||
/**
|
||||
To be called from the implementation's xStep() method, as well
|
||||
|
||||
@@ -22,14 +22,6 @@ import org.sqlite.jni.capi.sqlite3_value;
|
||||
*/
|
||||
public interface SqlFunction {
|
||||
|
||||
public static final int DETERMINISTIC = CApi.SQLITE_DETERMINISTIC;
|
||||
public static final int INNOCUOUS = CApi.SQLITE_INNOCUOUS;
|
||||
public static final int DIRECTONLY = CApi.SQLITE_DIRECTONLY;
|
||||
public static final int SUBTYPE = CApi.SQLITE_SUBTYPE;
|
||||
public static final int RESULT_SUBTYPE = CApi.SQLITE_RESULT_SUBTYPE;
|
||||
public static final int UTF8 = CApi.SQLITE_UTF8;
|
||||
public static final int UTF16 = CApi.SQLITE_UTF16;
|
||||
|
||||
/**
|
||||
The Arguments type is an abstraction on top of the lower-level
|
||||
UDF function argument types. It provides _most_ of the functionality
|
||||
@@ -53,118 +45,12 @@ public interface SqlFunction {
|
||||
*/
|
||||
Arguments(sqlite3_context cx, sqlite3_value args[]){
|
||||
this.cx = cx;
|
||||
this.args = args==null ? new sqlite3_value[0] : args;
|
||||
this.args = args==null ? new sqlite3_value[0] : args;;
|
||||
this.length = this.args.length;
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the sqlite3_value at the given argument index or throws
|
||||
an IllegalArgumentException exception if ndx is out of range.
|
||||
*/
|
||||
private sqlite3_value valueAt(int ndx){
|
||||
if(ndx<0 || ndx>=args.length){
|
||||
throw new IllegalArgumentException(
|
||||
"SQL function argument index "+ndx+" is out of range."
|
||||
);
|
||||
}
|
||||
return args[ndx];
|
||||
}
|
||||
|
||||
//! Returns the underlying sqlite3_context for these arguments.
|
||||
sqlite3_context getContext(){return cx;}
|
||||
|
||||
/**
|
||||
Returns the Sqlite (db) object associated with this UDF call,
|
||||
or null if the UDF is somehow called without such an object or
|
||||
the db has been closed in an untimely manner (e.g. closed by a
|
||||
UDF call).
|
||||
*/
|
||||
public Sqlite getDb(){
|
||||
return Sqlite.fromNative( CApi.sqlite3_context_db_handle(cx) );
|
||||
}
|
||||
|
||||
public int getArgCount(){ return args.length; }
|
||||
|
||||
public int getInt(int argNdx){return CApi.sqlite3_value_int(valueAt(argNdx));}
|
||||
public long getInt64(int argNdx){return CApi.sqlite3_value_int64(valueAt(argNdx));}
|
||||
public double getDouble(int argNdx){return CApi.sqlite3_value_double(valueAt(argNdx));}
|
||||
public byte[] getBlob(int argNdx){return CApi.sqlite3_value_blob(valueAt(argNdx));}
|
||||
public byte[] getText(int argNdx){return CApi.sqlite3_value_text(valueAt(argNdx));}
|
||||
public String getText16(int argNdx){return CApi.sqlite3_value_text16(valueAt(argNdx));}
|
||||
public int getBytes(int argNdx){return CApi.sqlite3_value_bytes(valueAt(argNdx));}
|
||||
public int getBytes16(int argNdx){return CApi.sqlite3_value_bytes16(valueAt(argNdx));}
|
||||
public Object getObject(int argNdx){return CApi.sqlite3_value_java_object(valueAt(argNdx));}
|
||||
public <T> T getObject(int argNdx, Class<T> type){
|
||||
return CApi.sqlite3_value_java_object(valueAt(argNdx), type);
|
||||
}
|
||||
|
||||
public int getType(int argNdx){return CApi.sqlite3_value_type(valueAt(argNdx));}
|
||||
public int getSubtype(int argNdx){return CApi.sqlite3_value_subtype(valueAt(argNdx));}
|
||||
public int getNumericType(int argNdx){return CApi.sqlite3_value_numeric_type(valueAt(argNdx));}
|
||||
public int getNoChange(int argNdx){return CApi.sqlite3_value_nochange(valueAt(argNdx));}
|
||||
public boolean getFromBind(int argNdx){return CApi.sqlite3_value_frombind(valueAt(argNdx));}
|
||||
public int getEncoding(int argNdx){return CApi.sqlite3_value_encoding(valueAt(argNdx));}
|
||||
|
||||
public void resultInt(int v){ CApi.sqlite3_result_int(cx, v); }
|
||||
public void resultInt64(long v){ CApi.sqlite3_result_int64(cx, v); }
|
||||
public void resultDouble(double v){ CApi.sqlite3_result_double(cx, v); }
|
||||
public void resultError(String msg){CApi.sqlite3_result_error(cx, msg);}
|
||||
public void resultError(Exception e){CApi.sqlite3_result_error(cx, e);}
|
||||
public void resultErrorTooBig(){CApi.sqlite3_result_error_toobig(cx);}
|
||||
public void resultErrorCode(int rc){CApi.sqlite3_result_error_code(cx, rc);}
|
||||
public void resultObject(Object o){CApi.sqlite3_result_java_object(cx, o);}
|
||||
public void resultNull(){CApi.sqlite3_result_null(cx);}
|
||||
/**
|
||||
Analog to sqlite3_result_value(), using the Value object at the
|
||||
given argument index.
|
||||
*/
|
||||
public void resultArg(int argNdx){CApi.sqlite3_result_value(cx, valueAt(argNdx));}
|
||||
public void resultSubtype(int subtype){CApi.sqlite3_result_subtype(cx, subtype);}
|
||||
public void resultZeroBlob(long n){
|
||||
// Throw on error? If n is too big,
|
||||
// sqlite3_result_error_toobig() is automatically called.
|
||||
CApi.sqlite3_result_zeroblob64(cx, n);
|
||||
}
|
||||
|
||||
public void resultBlob(byte[] blob){CApi.sqlite3_result_blob(cx, blob);}
|
||||
public void resultText(byte[] utf8){CApi.sqlite3_result_text(cx, utf8);}
|
||||
public void resultText(String txt){CApi.sqlite3_result_text(cx, txt);}
|
||||
public void resultText16(byte[] utf16){CApi.sqlite3_result_text16(cx, utf16);}
|
||||
public void resultText16(String txt){CApi.sqlite3_result_text16(cx, txt);}
|
||||
|
||||
/**
|
||||
Callbacks should invoke this on OOM errors, instead of throwing
|
||||
OutOfMemoryError, because the latter cannot be propagated
|
||||
through the C API.
|
||||
*/
|
||||
public void resultNoMem(){CApi.sqlite3_result_error_nomem(cx);}
|
||||
|
||||
/**
|
||||
Analog to sqlite3_set_auxdata() but throws if argNdx is out of
|
||||
range.
|
||||
*/
|
||||
public void setAuxData(int argNdx, Object o){
|
||||
/* From the API docs: https://www.sqlite.org/c3ref/get_auxdata.html
|
||||
|
||||
The value of the N parameter to these interfaces should be
|
||||
non-negative. Future enhancements may make use of negative N
|
||||
values to define new kinds of function caching behavior.
|
||||
*/
|
||||
valueAt(argNdx);
|
||||
CApi.sqlite3_set_auxdata(cx, argNdx, o);
|
||||
}
|
||||
|
||||
/**
|
||||
Analog to sqlite3_get_auxdata() but throws if argNdx is out of
|
||||
range.
|
||||
*/
|
||||
public Object getAuxData(int argNdx){
|
||||
valueAt(argNdx);
|
||||
return CApi.sqlite3_get_auxdata(cx, argNdx);
|
||||
}
|
||||
|
||||
/**
|
||||
Represents a single SqlFunction argument. Primarily intended
|
||||
Wrapper for a single SqlFunction argument. Primarily intended
|
||||
for use with the Arguments class's Iterable interface.
|
||||
*/
|
||||
public final static class Arg {
|
||||
@@ -186,7 +72,7 @@ public interface SqlFunction {
|
||||
public int getBytes(){return a.getBytes(ndx);}
|
||||
public int getBytes16(){return a.getBytes16(ndx);}
|
||||
public Object getObject(){return a.getObject(ndx);}
|
||||
public <T> T getObject(Class<T> type){ return a.getObject(ndx, type); }
|
||||
public <T> T getObjectCasted(Class<T> type){ return a.getObjectCasted(ndx, type); }
|
||||
public int getType(){return a.getType(ndx);}
|
||||
public Object getAuxData(){return a.getAuxData(ndx);}
|
||||
public void setAuxData(Object o){a.setAuxData(ndx, o);}
|
||||
@@ -201,6 +87,147 @@ public interface SqlFunction {
|
||||
return java.util.Arrays.stream(proxies).iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the sqlite3_value at the given argument index or throws
|
||||
an IllegalArgumentException exception if ndx is out of range.
|
||||
*/
|
||||
private sqlite3_value valueAt(int ndx){
|
||||
if(ndx<0 || ndx>=args.length){
|
||||
throw new IllegalArgumentException(
|
||||
"SQL function argument index "+ndx+" is out of range."
|
||||
);
|
||||
}
|
||||
return args[ndx];
|
||||
}
|
||||
|
||||
sqlite3_context getContext(){return cx;}
|
||||
|
||||
public int getArgCount(){ return args.length; }
|
||||
|
||||
public int getInt(int arg){return CApi.sqlite3_value_int(valueAt(arg));}
|
||||
public long getInt64(int arg){return CApi.sqlite3_value_int64(valueAt(arg));}
|
||||
public double getDouble(int arg){return CApi.sqlite3_value_double(valueAt(arg));}
|
||||
public byte[] getBlob(int arg){return CApi.sqlite3_value_blob(valueAt(arg));}
|
||||
public byte[] getText(int arg){return CApi.sqlite3_value_text(valueAt(arg));}
|
||||
public String getText16(int arg){return CApi.sqlite3_value_text16(valueAt(arg));}
|
||||
public int getBytes(int arg){return CApi.sqlite3_value_bytes(valueAt(arg));}
|
||||
public int getBytes16(int arg){return CApi.sqlite3_value_bytes16(valueAt(arg));}
|
||||
public Object getObject(int arg){return CApi.sqlite3_value_java_object(valueAt(arg));}
|
||||
public <T> T getObjectCasted(int arg, Class<T> type){
|
||||
return CApi.sqlite3_value_java_casted(valueAt(arg), type);
|
||||
}
|
||||
|
||||
public int getType(int arg){return CApi.sqlite3_value_type(valueAt(arg));}
|
||||
public int getSubtype(int arg){return CApi.sqlite3_value_subtype(valueAt(arg));}
|
||||
public int getNumericType(int arg){return CApi.sqlite3_value_numeric_type(valueAt(arg));}
|
||||
public int getNoChange(int arg){return CApi.sqlite3_value_nochange(valueAt(arg));}
|
||||
public boolean getFromBind(int arg){return CApi.sqlite3_value_frombind(valueAt(arg));}
|
||||
public int getEncoding(int arg){return CApi.sqlite3_value_encoding(valueAt(arg));}
|
||||
|
||||
public void resultInt(int v){ CApi.sqlite3_result_int(cx, v); }
|
||||
public void resultInt64(long v){ CApi.sqlite3_result_int64(cx, v); }
|
||||
public void resultDouble(double v){ CApi.sqlite3_result_double(cx, v); }
|
||||
public void resultError(String msg){CApi.sqlite3_result_error(cx, msg);}
|
||||
public void resultError(Exception e){CApi.sqlite3_result_error(cx, e);}
|
||||
public void resultErrorTooBig(){CApi.sqlite3_result_error_toobig(cx);}
|
||||
public void resultErrorCode(int rc){CApi.sqlite3_result_error_code(cx, rc);}
|
||||
public void resultObject(Object o){CApi.sqlite3_result_java_object(cx, o);}
|
||||
public void resultNull(){CApi.sqlite3_result_null(cx);}
|
||||
public void resultArg(int argNdx){CApi.sqlite3_result_value(cx, valueAt(argNdx));}
|
||||
public void resultZeroBlob(long n){
|
||||
// Throw on error? If n is too big,
|
||||
// sqlite3_result_error_toobig() is automatically called.
|
||||
CApi.sqlite3_result_zeroblob64(cx, n);
|
||||
}
|
||||
|
||||
public void resultBlob(byte[] blob){CApi.sqlite3_result_blob(cx, blob);}
|
||||
public void resultText(byte[] utf8){CApi.sqlite3_result_text(cx, utf8);}
|
||||
public void resultText(String txt){CApi.sqlite3_result_text(cx, txt);}
|
||||
public void resultText16(byte[] utf16){CApi.sqlite3_result_text16(cx, utf16);}
|
||||
public void resultText16(String txt){CApi.sqlite3_result_text16(cx, txt);}
|
||||
|
||||
public void setAuxData(int arg, Object o){
|
||||
/* From the API docs: https://www.sqlite.org/c3ref/get_auxdata.html
|
||||
|
||||
The value of the N parameter to these interfaces should be
|
||||
non-negative. Future enhancements may make use of negative N
|
||||
values to define new kinds of function caching behavior.
|
||||
*/
|
||||
valueAt(arg);
|
||||
CApi.sqlite3_set_auxdata(cx, arg, o);
|
||||
}
|
||||
|
||||
public Object getAuxData(int arg){
|
||||
valueAt(arg);
|
||||
return CApi.sqlite3_get_auxdata(cx, arg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
PerContextState assists aggregate and window functions in
|
||||
managing their accumulator state across calls to the UDF's
|
||||
callbacks.
|
||||
|
||||
<p>T must be of a type which can be legally stored as a value in
|
||||
java.util.HashMap<KeyType,T>.
|
||||
|
||||
<p>If a given aggregate or window function is called multiple times
|
||||
in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)...,
|
||||
then the clients need some way of knowing which call is which so
|
||||
that they can map their state between their various UDF callbacks
|
||||
and reset it via xFinal(). This class takes care of such
|
||||
mappings.
|
||||
|
||||
<p>This class works by mapping
|
||||
sqlite3_context.getAggregateContext() to a single piece of
|
||||
state, of a client-defined type (the T part of this class), which
|
||||
persists across a "matching set" of the UDF's callbacks.
|
||||
|
||||
<p>This class is a helper providing commonly-needed functionality
|
||||
- it is not required for use with aggregate or window functions.
|
||||
Client UDFs are free to perform such mappings using custom
|
||||
approaches. The provided {@link AggregateFunction} and {@link
|
||||
WindowFunction} classes use this.
|
||||
*/
|
||||
public static final class PerContextState<T> {
|
||||
private final java.util.Map<Long,ValueHolder<T>> map
|
||||
= new java.util.HashMap<>();
|
||||
|
||||
/**
|
||||
Should be called from a UDF's xStep(), xValue(), and xInverse()
|
||||
methods, passing it that method's first argument and an initial
|
||||
value for the persistent state. If there is currently no
|
||||
mapping for the given context within the map, one is created
|
||||
using the given initial value, else the existing one is used
|
||||
and the 2nd argument is ignored. It returns a ValueHolder<T>
|
||||
which can be used to modify that state directly without
|
||||
requiring that the client update the underlying map's entry.
|
||||
|
||||
<p>The caller is obligated to eventually call
|
||||
takeAggregateState() to clear the mapping.
|
||||
*/
|
||||
public ValueHolder<T> getAggregateState(SqlFunction.Arguments args, T initialValue){
|
||||
final Long key = args.getContext().getAggregateContext(true);
|
||||
ValueHolder<T> rc = null==key ? null : map.get(key);
|
||||
if( null==rc ){
|
||||
map.put(key, rc = new ValueHolder<>(initialValue));
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
Should be called from a UDF's xFinal() method and passed that
|
||||
method's first argument. This function removes the value
|
||||
associated with with the arguments' aggregate context from the
|
||||
map and returns it, returning null if no other UDF method has
|
||||
been called to set up such a mapping. The latter condition will
|
||||
be the case if a UDF is used in a statement which has no result
|
||||
rows.
|
||||
*/
|
||||
public T takeAggregateState(SqlFunction.Arguments args){
|
||||
final ValueHolder<T> h = map.remove(args.getContext().getAggregateContext(false));
|
||||
return null==h ? null : h.value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,7 +235,7 @@ public interface SqlFunction {
|
||||
for use with the org.sqlite.jni.capi.ScalarFunction interface.
|
||||
*/
|
||||
static final class ScalarAdapter extends org.sqlite.jni.capi.ScalarFunction {
|
||||
private final ScalarFunction impl;
|
||||
final ScalarFunction impl;
|
||||
ScalarAdapter(ScalarFunction impl){
|
||||
this.impl = impl;
|
||||
}
|
||||
@@ -234,9 +261,8 @@ public interface SqlFunction {
|
||||
Internal-use adapter for wrapping this package's AggregateFunction
|
||||
for use with the org.sqlite.jni.capi.AggregateFunction interface.
|
||||
*/
|
||||
static /*cannot be final without duplicating the whole body in WindowAdapter*/
|
||||
class AggregateAdapter extends org.sqlite.jni.capi.AggregateFunction {
|
||||
private final AggregateFunction impl;
|
||||
static final class AggregateAdapter extends org.sqlite.jni.capi.AggregateFunction {
|
||||
final AggregateFunction impl;
|
||||
AggregateAdapter(AggregateFunction impl){
|
||||
this.impl = impl;
|
||||
}
|
||||
@@ -256,9 +282,8 @@ public interface SqlFunction {
|
||||
}
|
||||
|
||||
/**
|
||||
As for the xFinal() argument of the C API's
|
||||
sqlite3_create_function(). If the proxied function throws, it
|
||||
is translated into a sqlite3_result_error().
|
||||
As for the xFinal() argument of the C API's sqlite3_create_function().
|
||||
If the proxied function throws, it is translated into a sqlite3_result_error().
|
||||
*/
|
||||
public void xFinal(sqlite3_context cx){
|
||||
try{
|
||||
@@ -273,46 +298,4 @@ public interface SqlFunction {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Internal-use adapter for wrapping this package's WindowFunction
|
||||
for use with the org.sqlite.jni.capi.WindowFunction interface.
|
||||
*/
|
||||
static final class WindowAdapter extends AggregateAdapter {
|
||||
private final WindowFunction impl;
|
||||
WindowAdapter(WindowFunction impl){
|
||||
super(impl);
|
||||
this.impl = impl;
|
||||
}
|
||||
|
||||
/**
|
||||
Proxies this.impl.xInverse(), adapting the call arguments to that
|
||||
function's signature. If the proxied function throws, it is
|
||||
translated to sqlite_result_error() with the exception's
|
||||
message.
|
||||
*/
|
||||
public void xInverse(sqlite3_context cx, sqlite3_value[] args){
|
||||
try{
|
||||
impl.xInverse( new SqlFunction.Arguments(cx, args) );
|
||||
}catch(Exception e){
|
||||
CApi.sqlite3_result_error(cx, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
As for the xValue() argument of the C API's sqlite3_create_window_function().
|
||||
If the proxied function throws, it is translated into a sqlite3_result_error().
|
||||
*/
|
||||
public void xValue(sqlite3_context cx){
|
||||
try{
|
||||
impl.xValue( new SqlFunction.Arguments(cx, null) );
|
||||
}catch(Exception e){
|
||||
CApi.sqlite3_result_error(cx, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void xDestroy(){
|
||||
impl.xDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@
|
||||
** This file is part of the wrapper1 interface for sqlite3.
|
||||
*/
|
||||
package org.sqlite.jni.wrapper1;
|
||||
import org.sqlite.jni.capi.CApi;
|
||||
import static org.sqlite.jni.capi.CApi.*;
|
||||
import org.sqlite.jni.capi.sqlite3;
|
||||
|
||||
/**
|
||||
@@ -22,10 +22,10 @@ import org.sqlite.jni.capi.sqlite3;
|
||||
and C via JNI.
|
||||
*/
|
||||
public final class SqliteException extends java.lang.RuntimeException {
|
||||
private int errCode = CApi.SQLITE_ERROR;
|
||||
private int xerrCode = CApi.SQLITE_ERROR;
|
||||
private int errOffset = -1;
|
||||
private int sysErrno = 0;
|
||||
int errCode = SQLITE_ERROR;
|
||||
int xerrCode = SQLITE_ERROR;
|
||||
int errOffset = -1;
|
||||
int sysErrno = 0;
|
||||
|
||||
/**
|
||||
Records the given error string and uses SQLITE_ERROR for both the
|
||||
@@ -38,31 +38,28 @@ public final class SqliteException extends java.lang.RuntimeException {
|
||||
/**
|
||||
Uses sqlite3_errstr(sqlite3ResultCode) for the error string and
|
||||
sets both the error code and extended error code to the given
|
||||
value. This approach includes no database-level information and
|
||||
systemErrno() will be 0, so is intended only for use with sqlite3
|
||||
APIs for which a result code is not an error but which the
|
||||
higher-level wrapper should treat as one.
|
||||
value.
|
||||
*/
|
||||
public SqliteException(int sqlite3ResultCode){
|
||||
super(CApi.sqlite3_errstr(sqlite3ResultCode));
|
||||
super(sqlite3_errstr(sqlite3ResultCode));
|
||||
errCode = xerrCode = sqlite3ResultCode;
|
||||
}
|
||||
|
||||
/**
|
||||
Records the current error state of db (which must not be null and
|
||||
must refer to an opened db object). Note that this does not close
|
||||
must refer to an opened db object). Note that this does NOT close
|
||||
the db.
|
||||
|
||||
Design note: closing the db on error is really only useful during
|
||||
Design note: closing the db on error is likely only useful during
|
||||
a failed db-open operation, and the place(s) where that can
|
||||
happen are inside this library, not client-level code.
|
||||
*/
|
||||
SqliteException(sqlite3 db){
|
||||
super(CApi.sqlite3_errmsg(db));
|
||||
errCode = CApi.sqlite3_errcode(db);
|
||||
xerrCode = CApi.sqlite3_extended_errcode(db);
|
||||
errOffset = CApi.sqlite3_error_offset(db);
|
||||
sysErrno = CApi.sqlite3_system_errno(db);
|
||||
super(sqlite3_errmsg(db));
|
||||
errCode = sqlite3_errcode(db);
|
||||
xerrCode = sqlite3_extended_errcode(db);
|
||||
errOffset = sqlite3_error_offset(db);
|
||||
sysErrno = sqlite3_system_errno(db);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,7 +71,7 @@ public final class SqliteException extends java.lang.RuntimeException {
|
||||
}
|
||||
|
||||
public SqliteException(Sqlite.Stmt stmt){
|
||||
this(stmt.getDb());
|
||||
this( stmt.db() );
|
||||
}
|
||||
|
||||
public int errcode(){ return errCode; }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,13 +9,13 @@
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file contains the ValueHolder utility class.
|
||||
** This file contains a set of tests for the sqlite3 JNI bindings.
|
||||
*/
|
||||
package org.sqlite.jni.wrapper1;
|
||||
|
||||
/**
|
||||
A helper class which simply holds a single value. Its primary use
|
||||
is for communicating values out of anonymous callbacks, as doing so
|
||||
is for communicating values out of anonymous classes, as doing so
|
||||
requires a "final" reference.
|
||||
*/
|
||||
public class ValueHolder<T> {
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
** 2023-10-16
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the wrapper1 interface for sqlite3.
|
||||
*/
|
||||
package org.sqlite.jni.wrapper1;
|
||||
|
||||
/**
|
||||
A SqlFunction implementation for window functions. The T type
|
||||
represents the type of data accumulated by this function while it
|
||||
works. e.g. a SUM()-like UDF might use Integer or Long and a
|
||||
CONCAT()-like UDF might use a StringBuilder or a List<String>.
|
||||
*/
|
||||
public abstract class WindowFunction<T> extends AggregateFunction<T> {
|
||||
|
||||
/**
|
||||
As for the xInverse() argument of the C API's
|
||||
sqlite3_create_window_function(). If this function throws, the
|
||||
exception is reported via sqlite3_result_error().
|
||||
*/
|
||||
public abstract void xInverse(SqlFunction.Arguments args);
|
||||
|
||||
/**
|
||||
As for the xValue() argument of the C API's
|
||||
sqlite3_create_window_function(). If this function throws, it is
|
||||
translated into sqlite3_result_error().
|
||||
|
||||
Note that the passed-in object will not actually contain any
|
||||
arguments for xValue() but will contain the context object needed
|
||||
for setting the call's result or error state.
|
||||
*/
|
||||
public abstract void xValue(SqlFunction.Arguments args);
|
||||
|
||||
}
|
||||
@@ -38,24 +38,6 @@ static void noopfunc(
|
||||
sqlite3_result_value(context, argv[0]);
|
||||
}
|
||||
|
||||
/*
|
||||
** Implementation of the multitype_text() function.
|
||||
**
|
||||
** The function returns its argument. The result will always have a
|
||||
** TEXT value. But if the original input is numeric, it will also
|
||||
** have that numeric value.
|
||||
*/
|
||||
static void multitypeTextFunc(
|
||||
sqlite3_context *context,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
assert( argc==1 );
|
||||
(void)argc;
|
||||
(void)sqlite3_value_text(argv[0]);
|
||||
sqlite3_result_value(context, argv[0]);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
@@ -82,9 +64,5 @@ int sqlite3_noop_init(
|
||||
rc = sqlite3_create_function(db, "noop_nd", 1,
|
||||
SQLITE_UTF8,
|
||||
0, noopfunc, 0, 0);
|
||||
if( rc ) return rc;
|
||||
rc = sqlite3_create_function(db, "multitype_text", 1,
|
||||
SQLITE_UTF8,
|
||||
0, multitypeTextFunc, 0, 0);
|
||||
return rc;
|
||||
}
|
||||
|
||||
+21
-59
@@ -26,14 +26,9 @@
|
||||
**
|
||||
** .load ./randomjson
|
||||
** SELECT random_json(1);
|
||||
** SELECT random_json5(1);
|
||||
*/
|
||||
#ifdef SQLITE_STATIC_RANDOMJSON
|
||||
# include "sqlite3.h"
|
||||
#else
|
||||
# include "sqlite3ext.h"
|
||||
SQLITE_EXTENSION_INIT1
|
||||
#endif
|
||||
#include "sqlite3ext.h"
|
||||
SQLITE_EXTENSION_INIT1
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@@ -56,18 +51,17 @@ static unsigned int prngInt(Prng *p){
|
||||
return p->x ^ p->y;
|
||||
}
|
||||
|
||||
static char *azJsonAtoms[] = {
|
||||
/* JSON JSON-5 */
|
||||
static const char *azJsonAtoms[] = {
|
||||
/* JSON JSON-5 */
|
||||
"0", "0",
|
||||
"1", "1",
|
||||
"-1", "-1",
|
||||
"2", "+2",
|
||||
"3DDDD", "3DDDD",
|
||||
"2.5DD", "2.5DD",
|
||||
"3", "3",
|
||||
"2.5", "2.5",
|
||||
"0.75", ".75",
|
||||
"-4.0e2", "-4.e2",
|
||||
"5.0e-3", "+5e-3",
|
||||
"6.DDe+0DD", "6.DDe+0DD",
|
||||
"0", "0x0",
|
||||
"512", "0x200",
|
||||
"256", "+0x100",
|
||||
@@ -79,14 +73,12 @@ static char *azJsonAtoms[] = {
|
||||
"-9.0e999", "-Infinity",
|
||||
"9.0e999", "+Infinity",
|
||||
"null", "NaN",
|
||||
"-0.0005DD", "-0.0005DD",
|
||||
"-0.0005123", "-0.0005123",
|
||||
"4.35e-3", "+4.35e-3",
|
||||
"\"gem\\\"hay\"", "\"gem\\\"hay\"",
|
||||
"\"icy'joy\"", "'icy\\'joy\'",
|
||||
"\"keylog\"", "\"key\\\nlog\"",
|
||||
"\"mix\\\\\\tnet\"", "\"mix\\\\\\tnet\"",
|
||||
"\"oat\\r\\n\"", "\"oat\\r\\n\"",
|
||||
"\"\\fpan\\b\"", "\"\\fpan\\b\"",
|
||||
"{}", "{}",
|
||||
"[]", "[]",
|
||||
"[]", "[/*empty*/]",
|
||||
@@ -97,20 +89,19 @@ static char *azJsonAtoms[] = {
|
||||
"\"day\"", "\"day\"",
|
||||
"\"end\"", "'end'",
|
||||
"\"fly\"", "\"fly\"",
|
||||
"\"\\u00XX\\u00XX\"", "\"\\xXX\\xXX\"",
|
||||
"\"y\\uXXXXz\"", "\"y\\uXXXXz\"",
|
||||
"\"\"", "\"\"",
|
||||
};
|
||||
static char *azJsonTemplate[] = {
|
||||
static const char *azJsonTemplate[] = {
|
||||
/* JSON JSON-5 */
|
||||
"{\"a\":%,\"b\":%,\"cDD\":%}", "{a:%,b:%,cDD:%}",
|
||||
"{\"a\":%,\"b\":%,\"c\":%}", "{a:%,b:%,c:%}",
|
||||
"{\"a\":%,\"b\":%,\"c\":%,\"d\":%,\"e\":%}", "{a:%,b:%,c:%,d:%,e:%}",
|
||||
"{\"a\":%,\"b\":%,\"c\":%,\"d\":%,\"\":%}", "{a:%,b:%,c:%,d:%,'':%}",
|
||||
"{\"a\":%,\"b\":%,\"c\":%,\"d\":%,\"\":%}", "{a:%,b:%,c:%,d:%,\"\":%}",
|
||||
"{\"d\":%}", "{d:%}",
|
||||
"{\"eeee\":%, \"ffff\":%}", "{eeee:% /*and*/, ffff:%}",
|
||||
"{\"$g\":%,\"_h_\":%,\"a b c d\":%}", "{$g:%,_h_:%,\"a b c d\":%}",
|
||||
"{\"$g\":%,\"_h_\":%}", "{$g:%,_h_:%,}",
|
||||
"{\"x\":%,\n \"y\":%}", "{\"x\":%,\n \"y\":%}",
|
||||
"{\"\\u00XX\":%,\"\\uXXXX\":%}", "{\"\\xXX\":%,\"\\uXXXX\":%}",
|
||||
"{\"a b c d\":%,\"e\":%,\"f\":%,\"x\":%,\"y\":%}",
|
||||
"{\"a b c d\":%,e:%,f:%,x:%,y:%}",
|
||||
"{\"Z\":%}", "{Z:%,}",
|
||||
"[%]", "[%,]",
|
||||
"[%,%]", "[%,%]",
|
||||
@@ -131,13 +122,15 @@ static void jsonExpand(
|
||||
unsigned int r /* Growth probability 0..1000. 0 means no growth */
|
||||
){
|
||||
unsigned int i, j, k;
|
||||
char *z;
|
||||
char *zX;
|
||||
const char *z;
|
||||
size_t n;
|
||||
char zBuf[200];
|
||||
|
||||
j = 0;
|
||||
if( zSrc==0 ) zSrc = "%";
|
||||
if( zSrc==0 ){
|
||||
k = prngInt(p)%(count(azJsonTemplate)/2);
|
||||
k = k*2 + eType;
|
||||
zSrc = azJsonTemplate[k];
|
||||
}
|
||||
if( strlen(zSrc)>=STRSZ/10 ) r = 0;
|
||||
for(i=0; zSrc[i]; i++){
|
||||
if( zSrc[i]!='%' ){
|
||||
@@ -156,36 +149,9 @@ static void jsonExpand(
|
||||
z = azJsonTemplate[k];
|
||||
}
|
||||
n = strlen(z);
|
||||
if( (zX = strstr(z,"XX"))!=0 ){
|
||||
unsigned int y = prngInt(p);
|
||||
if( (y&0xff)==((y>>8)&0xff) ) y += 0x100;
|
||||
while( (y&0xff)==((y>>16)&0xff) || ((y>>8)&0xff)==((y>>16)&0xff) ){
|
||||
y += 0x10000;
|
||||
}
|
||||
memcpy(zBuf, z, n+1);
|
||||
z = zBuf;
|
||||
zX = strstr(z,"XX");
|
||||
while( zX!=0 ){
|
||||
zX[0] = "0123456789abcdef"[y%16]; y /= 16;
|
||||
zX[1] = "0123456789abcdef"[y%16]; y /= 16;
|
||||
zX = strstr(zX, "XX");
|
||||
}
|
||||
}else if( (zX = strstr(z,"DD"))!=0 ){
|
||||
unsigned int y = prngInt(p);
|
||||
memcpy(zBuf, z, n+1);
|
||||
z = zBuf;
|
||||
zX = strstr(z,"DD");
|
||||
while( zX!=0 ){
|
||||
zX[0] = "0123456789"[y%10]; y /= 10;
|
||||
zX[1] = "0123456789"[y%10]; y /= 10;
|
||||
zX = strstr(zX, "DD");
|
||||
}
|
||||
}
|
||||
assert( strstr(z, "XX")==0 );
|
||||
assert( strstr(z, "DD")==0 );
|
||||
if( j+n<STRSZ ){
|
||||
memcpy(&zDest[j], z, n);
|
||||
j += (int)n;
|
||||
j += n;
|
||||
}
|
||||
}
|
||||
zDest[STRSZ-1] = 0;
|
||||
@@ -212,7 +178,7 @@ static void randJsonFunc(
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
int sqlite3_randomjson_init(
|
||||
sqlite3 *db,
|
||||
@@ -222,11 +188,7 @@ int sqlite3_randomjson_init(
|
||||
static int cOne = 1;
|
||||
static int cZero = 0;
|
||||
int rc = SQLITE_OK;
|
||||
#ifdef SQLITE_STATIC_RANDOMJSON
|
||||
(void)pApi; /* Unused parameter */
|
||||
#else
|
||||
SQLITE_EXTENSION_INIT2(pApi);
|
||||
#endif
|
||||
(void)pzErrMsg; /* Unused parameter */
|
||||
rc = sqlite3_create_function(db, "random_json", 1,
|
||||
SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC,
|
||||
|
||||
+7
-11
@@ -103,20 +103,16 @@ SQLITE_EXTENSION_INIT1
|
||||
** index is ix. The 0th member is given by smBase. The sequence members
|
||||
** progress per ix increment by smStep.
|
||||
*/
|
||||
static sqlite3_int64 genSeqMember(
|
||||
sqlite3_int64 smBase,
|
||||
sqlite3_int64 smStep,
|
||||
sqlite3_uint64 ix
|
||||
){
|
||||
static const sqlite3_uint64 mxI64 =
|
||||
((sqlite3_uint64)0x7fffffff)<<32 | 0xffffffff;
|
||||
if( ix>=mxI64 ){
|
||||
static sqlite3_int64 genSeqMember(sqlite3_int64 smBase,
|
||||
sqlite3_int64 smStep,
|
||||
sqlite3_uint64 ix){
|
||||
if( ix>=(sqlite3_uint64)LLONG_MAX ){
|
||||
/* Get ix into signed i64 range. */
|
||||
ix -= mxI64;
|
||||
ix -= (sqlite3_uint64)LLONG_MAX;
|
||||
/* With 2's complement ALU, this next can be 1 step, but is split into
|
||||
* 2 for UBSAN's satisfaction (and hypothetical 1's complement ALUs.) */
|
||||
smBase += (mxI64/2) * smStep;
|
||||
smBase += (mxI64 - mxI64/2) * smStep;
|
||||
smBase += (LLONG_MAX/2) * smStep;
|
||||
smBase += (LLONG_MAX - LLONG_MAX/2) * smStep;
|
||||
}
|
||||
/* Under UBSAN (or on 1's complement machines), must do this last term
|
||||
* in steps to avoid the dreaded (and harmless) signed multiply overlow. */
|
||||
|
||||
+2
-16
@@ -350,20 +350,6 @@ totype_atof_calc:
|
||||
return z>=zEnd && nDigits>0 && eValid && nonNum==0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Convert a floating point value to an integer. Or, if this cannot be
|
||||
** done in a way that avoids 'outside the range of representable values'
|
||||
** warnings from UBSAN, return 0.
|
||||
**
|
||||
** This function is a modified copy of internal SQLite function
|
||||
** sqlite3RealToI64().
|
||||
*/
|
||||
static sqlite3_int64 totypeDoubleToInt(double r){
|
||||
if( r<-9223372036854774784.0 ) return 0;
|
||||
if( r>+9223372036854774784.0 ) return 0;
|
||||
return (sqlite3_int64)r;
|
||||
}
|
||||
|
||||
/*
|
||||
** tointeger(X): If X is any value (integer, double, blob, or string) that
|
||||
** can be losslessly converted into an integer, then make the conversion and
|
||||
@@ -379,7 +365,7 @@ static void tointegerFunc(
|
||||
switch( sqlite3_value_type(argv[0]) ){
|
||||
case SQLITE_FLOAT: {
|
||||
double rVal = sqlite3_value_double(argv[0]);
|
||||
sqlite3_int64 iVal = totypeDoubleToInt(rVal);
|
||||
sqlite3_int64 iVal = (sqlite3_int64)rVal;
|
||||
if( rVal==(double)iVal ){
|
||||
sqlite3_result_int64(context, iVal);
|
||||
}
|
||||
@@ -454,7 +440,7 @@ static void torealFunc(
|
||||
case SQLITE_INTEGER: {
|
||||
sqlite3_int64 iVal = sqlite3_value_int64(argv[0]);
|
||||
double rVal = (double)iVal;
|
||||
if( iVal==totypeDoubleToInt(rVal) ){
|
||||
if( iVal==(sqlite3_int64)rVal ){
|
||||
sqlite3_result_double(context, rVal);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -582,7 +582,6 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
|
||||
bNextPage = 1;
|
||||
}else{
|
||||
iOff += dbdataGetVarintU32(&pCsr->aPage[iOff], &nPayload);
|
||||
if( nPayload>0x7fffff00 ) nPayload &= 0x3fff;
|
||||
}
|
||||
|
||||
/* If this is a leaf intkey cell, load the rowid */
|
||||
|
||||
@@ -236,7 +236,7 @@ static int test_sqlite3_recover_init(
|
||||
zDb = Tcl_GetString(objv[2]);
|
||||
if( zDb[0]=='\0' ) zDb = 0;
|
||||
|
||||
pNew = (TestRecover*)ckalloc(sizeof(TestRecover));
|
||||
pNew = ckalloc(sizeof(TestRecover));
|
||||
if( bSql==0 ){
|
||||
zUri = Tcl_GetString(objv[3]);
|
||||
pNew->p = sqlite3_recover_init(db, zDb, zUri);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user