diff -Nru sqlite3-3.41.0-0/Makefile.in sqlite3-3.44.0-0/Makefile.in
--- sqlite3-3.41.0-0/Makefile.in 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/Makefile.in 2023-11-04 14:24:27.000000000 +0000
@@ -57,6 +57,7 @@
#
READLINE_FLAGS = -DHAVE_READLINE=@TARGET_HAVE_READLINE@ @TARGET_READLINE_INC@
READLINE_FLAGS += -DHAVE_EDITLINE=@TARGET_HAVE_EDITLINE@
+READLINE_FLAGS += -DHAVE_LINENOISE=@TARGET_HAVE_LINENOISE@
# The library that programs using readline() must link against.
#
@@ -599,20 +600,38 @@
SHELL_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB
SHELL_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB
SHELL_OPT += -DSQLITE_ENABLE_OFFSET_SQL_FUNC
-FUZZERSHELL_OPT =
+FUZZERSHELL_OPT =
FUZZCHECK_OPT += -I$(TOP)/test
FUZZCHECK_OPT += -I$(TOP)/ext/recover
-FUZZCHECK_OPT += -DSQLITE_OMIT_LOAD_EXTENSION
-FUZZCHECK_OPT += -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_OSS_FUZZ
-FUZZCHECK_OPT += -DSQLITE_MAX_MEMORY=50000000
-FUZZCHECK_OPT += -DSQLITE_PRINTF_PRECISION_LIMIT=1000
-FUZZCHECK_OPT += -DSQLITE_ENABLE_FTS4
-FUZZCHECK_OPT += -DSQLITE_ENABLE_FTS3_PARENTHESIS
-FUZZCHECK_OPT += -DSQLITE_ENABLE_FTS5
-FUZZCHECK_OPT += -DSQLITE_ENABLE_RTREE
-FUZZCHECK_OPT += -DSQLITE_ENABLE_GEOPOLY
-FUZZCHECK_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB
-FUZZCHECK_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB
+FUZZCHECK_OPT += \
+ -DSQLITE_OSS_FUZZ \
+ -DSQLITE_ENABLE_BYTECODE_VTAB \
+ -DSQLITE_ENABLE_DBPAGE_VTAB \
+ -DSQLITE_ENABLE_DBSTAT_VTAB \
+ -DSQLITE_ENABLE_BYTECODE_VTAB \
+ -DSQLITE_ENABLE_DESERIALIZE \
+ -DSQLITE_ENABLE_EXPLAIN_COMMENTS \
+ -DSQLITE_ENABLE_FTS3_PARENTHESIS \
+ -DSQLITE_ENABLE_FTS4 \
+ -DSQLITE_ENABLE_FTS5 \
+ -DSQLITE_ENABLE_GEOPOLY \
+ -DSQLITE_ENABLE_MATH_FUNCTIONS \
+ -DSQLITE_ENABLE_MEMSYS5 \
+ -DSQLITE_ENABLE_NORMALIZE \
+ -DSQLITE_ENABLE_OFFSET_SQL_FUNC \
+ -DSQLITE_ENABLE_PREUPDATE_HOOK \
+ -DSQLITE_ENABLE_RTREE \
+ -DSQLITE_ENABLE_SESSION \
+ -DSQLITE_ENABLE_STMTVTAB \
+ -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION \
+ -DSQLITE_ENABLE_STAT4 \
+ -DSQLITE_ENABLE_STMT_SCANSTATUS \
+ -DSQLITE_MAX_MEMORY=50000000 \
+ -DSQLITE_MAX_MMAP_SIZE=0 \
+ -DSQLITE_OMIT_LOAD_EXTENSION \
+ -DSQLITE_PRINTF_PRECISION_LIMIT=1000 \
+ -DSQLITE_PRIVATE=""
+
FUZZCHECK_SRC += $(TOP)/test/fuzzcheck.c
FUZZCHECK_SRC += $(TOP)/test/ossfuzz.c
FUZZCHECK_SRC += $(TOP)/test/fuzzinvariants.c
@@ -672,6 +691,12 @@
sourcetest: srcck1$(BEXE) sqlite3.c
./srcck1 sqlite3.c
+src-verify: $(TOP)/tool/src-verify.c
+ $(BCC) -o src-verify$(BEXE) $(TOP)/tool/src-verify.c
+
+verify-source: ./src-verify
+ ./src-verify $(TOP)
+
fuzzershell$(TEXE): $(TOP)/tool/fuzzershell.c sqlite3.c sqlite3.h
$(LTLINK) -o $@ $(FUZZERSHELL_OPT) \
$(TOP)/tool/fuzzershell.c sqlite3.c $(TLIBS)
@@ -679,6 +704,12 @@
fuzzcheck$(TEXE): $(FUZZCHECK_SRC) sqlite3.c sqlite3.h $(FUZZCHECK_DEP)
$(LTLINK) -o $@ $(FUZZCHECK_OPT) $(FUZZCHECK_SRC) sqlite3.c $(TLIBS)
+fuzzcheck-asan$(TEXE): $(FUZZCHECK_SRC) sqlite3.c sqlite3.h $(FUZZCHECK_DEP)
+ $(LTLINK) -o $@ -fsanitize=address $(FUZZCHECK_OPT) $(FUZZCHECK_SRC) sqlite3.c $(TLIBS)
+
+fuzzcheck-ubsan$(TEXE): $(FUZZCHECK_SRC) sqlite3.c sqlite3.h $(FUZZCHECK_DEP)
+ $(LTLINK) -o $@ -fsanitize=undefined $(FUZZCHECK_OPT) $(FUZZCHECK_SRC) sqlite3.c $(TLIBS)
+
ossshell$(TEXE): $(TOP)/test/ossfuzz.c $(TOP)/test/ossshell.c sqlite3.c sqlite3.h
$(LTLINK) -o $@ $(FUZZCHECK_OPT) $(TOP)/test/ossshell.c \
$(TOP)/test/ossfuzz.c sqlite3.c $(TLIBS)
@@ -738,13 +769,22 @@
$(MPTEST2) --journalmode DELETE
+has_tclsh84:
+ sh $(TOP)/tool/cktclsh.sh 8.4 $(TCLSH_CMD)
+ touch has_tclsh84
+
+has_tclsh85:
+ sh $(TOP)/tool/cktclsh.sh 8.5 $(TCLSH_CMD)
+ touch has_tclsh85
+
+
# This target creates a directory named "tsrc" and fills it with
# copies of all of the C source code and header files needed to
# build on the target system. Some of the C source code and header
# files are automatically generated. This target takes care of
# all that automatic generation.
#
-.target_source: $(SRC) $(TOP)/tool/vdbe-compress.tcl fts5.c
+.target_source: $(SRC) $(TOP)/tool/vdbe-compress.tcl has_tclsh84 fts5.c
rm -rf tsrc
mkdir tsrc
cp -f $(SRC) tsrc
@@ -754,15 +794,15 @@
cp fts5.c fts5.h tsrc
touch .target_source
-sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl
+sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl src-verify has_tclsh84
$(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_LINE_MACROS)
cp tsrc/sqlite3ext.h .
cp $(TOP)/ext/session/sqlite3session.h .
-sqlite3r.h: sqlite3.h
+sqlite3r.h: sqlite3.h has_tclsh84
$(TCLSH_CMD) $(TOP)/tool/mksqlite3h.tcl $(TOP) --enable-recover >sqlite3r.h
-sqlite3r.c: sqlite3.c sqlite3r.h
+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/
@@ -777,7 +817,7 @@
echo '#endif /* USE_SYSTEM_SQLITE */' >>tclsqlite3.c
cat $(TOP)/src/tclsqlite.c >>tclsqlite3.c
-sqlite3-all.c: sqlite3.c $(TOP)/tool/split-sqlite3c.tcl
+sqlite3-all.c: sqlite3.c $(TOP)/tool/split-sqlite3c.tcl has_tclsh84
$(TCLSH_CMD) $(TOP)/tool/split-sqlite3c.tcl
# Rule to build the amalgamation
@@ -1065,10 +1105,10 @@
# Rules to build opcodes.c and opcodes.h
#
-opcodes.c: opcodes.h $(TOP)/tool/mkopcodec.tcl
+opcodes.c: opcodes.h $(TOP)/tool/mkopcodec.tcl has_tclsh84
$(TCLSH_CMD) $(TOP)/tool/mkopcodec.tcl opcodes.h >opcodes.c
-opcodes.h: parse.h $(TOP)/src/vdbe.c $(TOP)/tool/mkopcodeh.tcl
+opcodes.h: parse.h $(TOP)/src/vdbe.c $(TOP)/tool/mkopcodeh.tcl has_tclsh84
cat parse.h $(TOP)/src/vdbe.c | $(TCLSH_CMD) $(TOP)/tool/mkopcodeh.tcl >opcodes.h
# Rules to build parse.c and parse.h - the outputs of lemon.
@@ -1079,10 +1119,10 @@
cp $(TOP)/src/parse.y .
./lemon$(BEXE) $(OPT_FEATURE_FLAGS) $(OPTS) -S parse.y
-sqlite3.h: $(TOP)/src/sqlite.h.in $(TOP)/manifest mksourceid$(BEXE) $(TOP)/VERSION
+sqlite3.h: $(TOP)/src/sqlite.h.in $(TOP)/manifest mksourceid$(BEXE) $(TOP)/VERSION has_tclsh84
$(TCLSH_CMD) $(TOP)/tool/mksqlite3h.tcl $(TOP) >sqlite3.h
-sqlite3rc.h: $(TOP)/src/sqlite3.rc $(TOP)/VERSION
+sqlite3rc.h: $(TOP)/src/sqlite3.rc $(TOP)/VERSION has_tclsh84
echo '#ifndef SQLITE_RESOURCE_VERSION' >$@
echo -n '#define SQLITE_RESOURCE_VERSION ' >>$@
cat $(TOP)/VERSION | $(TCLSH_CMD) $(TOP)/tool/replace.tcl exact . , >>$@
@@ -1112,12 +1152,13 @@
$(TOP)/ext/expert/sqlite3expert.h \
$(TOP)/ext/misc/zipfile.c \
$(TOP)/ext/misc/memtrace.c \
+ $(TOP)/ext/misc/pcachetrace.c \
$(TOP)/ext/recover/dbdata.c \
$(TOP)/ext/recover/sqlite3recover.c \
$(TOP)/ext/recover/sqlite3recover.h \
$(TOP)/src/test_windirent.c
-shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl
+shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl has_tclsh84
$(TCLSH_CMD) $(TOP)/tool/mkshellc.tcl >shell.c
@@ -1205,7 +1246,7 @@
fts5parse.h: fts5parse.c
-fts5.c: $(FTS5_SRC)
+fts5.c: $(FTS5_SRC) has_tclsh84
$(TCLSH_CMD) $(TOP)/ext/fts5/tool/mkfts5c.tcl
cp $(TOP)/ext/fts5/fts5.h .
@@ -1239,7 +1280,7 @@
TESTFIXTURE_SRC = $(TESTSRC) $(TOP)/src/tclsqlite.c
TESTFIXTURE_SRC += $(TESTFIXTURE_SRC$(USE_AMALGAMATION))
-testfixture$(TEXE): $(TESTFIXTURE_SRC)
+testfixture$(TEXE): has_tclsh85 $(TESTFIXTURE_SRC)
$(LTLINK) -DSQLITE_NO_SYNC=1 $(TEMP_STORE) $(TESTFIXTURE_FLAGS) \
-o $@ $(TESTFIXTURE_SRC) $(LIBTCL) $(TLIBS)
@@ -1263,11 +1304,17 @@
./testfixture$(TEXE) $(TOP)/test/full.test
# Fuzz testing
-fuzztest: fuzzcheck$(TEXE) $(FUZZDATA) sessionfuzz$(TEXE) $(TOP)/test/sessionfuzz-data1.db
+#
+# WARNING: When the "fuzztest" target is run by the testrunner.tcl script,
+# it does not actually run this code. Instead, it schedules equivalent
+# commands. Therefore, if this target is updated, then code in
+# testrunner_data.tcl (search for "trd_fuzztest_data") must also be updated.
+#
+fuzztest: fuzzcheck$(TEXE) $(FUZZDATA) sessionfuzz$(TEXE)
./fuzzcheck$(TEXE) $(FUZZDATA)
./sessionfuzz$(TEXE) run $(TOP)/test/sessionfuzz-data1.db
-valgrindfuzz: fuzzcheck$(TEXT) $(FUZZDATA) sessionfuzz$(TEXE) $(TOP)/test/sessionfuzz-data1.db
+valgrindfuzz: fuzzcheck$(TEXT) $(FUZZDATA) sessionfuzz$(TEXE)
valgrind ./fuzzcheck$(TEXE) --cell-size-check --limit-mem 10M $(FUZZDATA)
valgrind ./sessionfuzz$(TEXE) run $(TOP)/test/sessionfuzz-data1.db
@@ -1284,11 +1331,23 @@
# Runs both fuzztest and testrunner, consecutively.
#
-devtest: testfixture$(TEXE) fuzztest testrunner
+devtest: srctree-check testfixture$(TEXE) fuzztest testrunner
+
+mdevtest: srctree-check has_tclsh85
+ $(TCLSH_CMD) $(TOP)/test/testrunner.tcl mdevtest
+
+sdevtest: has_tclsh85
+ $(TCLSH_CMD) $(TOP)/test/testrunner.tcl sdevtest
+
+# Validate that various generated files in the source tree
+# are up-to-date.
+#
+srctree-check: $(TOP)/tool/srctree-check.tcl
+ $(TCLSH_CMD) $(TOP)/tool/srctree-check.tcl
# Testing for a release
#
-releasetest: testfixture$(TEXE)
+releasetest: srctree-check testfixture$(TEXE)
./testfixture$(TEXE) $(TOP)/test/testrunner.tcl release
# Minimal testing that runs in less than 3 minutes
@@ -1299,7 +1358,7 @@
# This is the common case. Run many tests that do not take too long,
# including fuzzcheck, sqlite3_analyzer, and sqldiff tests.
#
-test: fuzztest sourcetest $(TESTPROGS) tcltest
+test: srctree-check fuzztest sourcetest $(TESTPROGS) tcltest
# Run a test using valgrind. This can take a really long time
# because valgrind is so much slower than a native machine.
@@ -1317,13 +1376,13 @@
shelltest: $(TESTPROGS)
./testfixture$(TEXT) $(TOP)/test/permutations.test shell
-sqlite3_analyzer.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/spaceanal.tcl $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqlite3_analyzer.c.in
+sqlite3_analyzer.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/spaceanal.tcl $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqlite3_analyzer.c.in has_tclsh85
$(TCLSH_CMD) $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqlite3_analyzer.c.in >sqlite3_analyzer.c
sqlite3_analyzer$(TEXE): sqlite3_analyzer.c
$(LTLINK) sqlite3_analyzer.c -o $@ $(LIBTCL) $(TLIBS)
-sqltclsh.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/sqltclsh.tcl $(TOP)/ext/misc/appendvfs.c $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqltclsh.c.in
+sqltclsh.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/sqltclsh.tcl $(TOP)/ext/misc/appendvfs.c $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqltclsh.c.in has_tclsh85
$(TCLSH_CMD) $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqltclsh.c.in >sqltclsh.c
sqltclsh$(TEXE): sqltclsh.c
@@ -1342,7 +1401,7 @@
$(TOP)/ext/misc/btreeinfo.c \
$(TOP)/ext/repair/sqlite3_checker.c.in
-sqlite3_checker.c: $(CHECKER_DEPS)
+sqlite3_checker.c: $(CHECKER_DEPS) has_tclsh85
$(TCLSH_CMD) $(TOP)/tool/mkccode.tcl $(TOP)/ext/repair/sqlite3_checker.c.in >$@
sqlite3_checker$(TEXE): sqlite3_checker.c
@@ -1428,6 +1487,11 @@
snapshot-tarball: sqlite3.c sqlite3rc.h
TOP=$(TOP) sh $(TOP)/tool/mkautoconfamal.sh --snapshot
+# Build a ZIP archive containing various command-line tools.
+#
+tool-zip: testfixture sqlite3 sqldiff sqlite3_analyzer $(TOP)/tool/mktoolzip.tcl
+ ./testfixture $(TOP)/tool/mktoolzip.tcl
+
# The next two rules are used to support the "threadtest" target. Building
# threadtest runs a few thread-safety tests that are implemented in C. This
# target is invoked by the releasetest.tcl script.
@@ -1485,6 +1549,7 @@
rm -f LogEst$(TEXE) fts3view$(TEXE) rollback-test$(TEXE) showdb$(TEXE)
rm -f showjournal$(TEXE) showstat4$(TEXE) showwal$(TEXE) speedtest1$(TEXE)
rm -f wordcount$(TEXE) changeset$(TEXE)
+ rm -f version-info$(TEXT)
rm -f sqlite3.dll sqlite3.lib sqlite3.exp sqlite3.def
rm -f sqlite3.c
rm -f sqlite3rc.h
@@ -1500,6 +1565,9 @@
rm -f dbhash dbhash.exe
rm -f fts5.* fts5parse.*
rm -f threadtest5
+ rm -f src-verify
+ rm -f custom.rws
+ rm -f has_tclsh84 has_tclsh85
distclean: clean
rm -f sqlite_cfg.h config.log config.status libtool Makefile sqlite3.pc \
@@ -1528,3 +1596,28 @@
#
fiddle: sqlite3.c shell.c
make -C ext/wasm fiddle emcc_opt=-Os
+
+#
+# Spell-checking for source comments
+# The sources checked are either C sources or C source templates.
+# Their comments are extracted and processed through aspell using
+# a custom dictionary that contains scads of odd identifiers that
+# find their way into the comments.
+#
+# Currently, this target is setup to be "made" in-tree only.
+# The output is ephemeral. Redirect it to guide spelling fixups,
+# either to correct spelling or add words to tool/custom.txt.
+#
+./custom.rws: ./tool/custom.txt
+ @echo 'Updating custom dictionary from tool/custom.txt'
+ aspell --lang=en create master ./custom.rws < $<
+
+misspell: ./custom.rws has_tclsh84
+ $(TCLSH_CMD) ./tool/spellsift.tcl ./src/*.c ./src/*.h ./src/*.in
+
+#
+# tool/version-info: a utility for emitting sqlite3 version info
+# in various forms.
+#
+version-info$(TEXE): $(TOP)/tool/version-info.c Makefile sqlite3.h
+ $(LTLINK) $(ST_OPT) -o $@ $(TOP)/tool/version-info.c
diff -Nru sqlite3-3.41.0-0/Makefile.linux-gcc sqlite3-3.44.0-0/Makefile.linux-gcc
--- sqlite3-3.41.0-0/Makefile.linux-gcc 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/Makefile.linux-gcc 2023-11-04 14:24:27.000000000 +0000
@@ -22,12 +22,6 @@
BCC = gcc -g -O0
#BCC = /opt/ancic/bin/c89 -0
-#### If the target operating system supports the "usleep()" system
-# call, then define the HAVE_USLEEP macro for all C modules.
-#
-#USLEEP =
-USLEEP = -DHAVE_USLEEP=1
-
#### If you want the SQLite library to be safe for use within a
# multi-threaded program, then define the following macro
# appropriately:
diff -Nru sqlite3-3.41.0-0/Makefile.msc sqlite3-3.44.0-0/Makefile.msc
--- sqlite3-3.41.0-0/Makefile.msc 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/Makefile.msc 2023-11-04 14:24:27.000000000 +0000
@@ -52,6 +52,13 @@
USE_STDCALL = 0
!ENDIF
+# Use the USE_SEH=0 option on the nmake command line to omit structured
+# exception handling (SEH) support. SEH is on by default.
+#
+!IFNDEF USE_SEH
+USE_SEH = 1
+!ENDIF
+
# Set this non-0 to have the shell executable link against the core dynamic
# link library.
#
@@ -218,6 +225,12 @@
OSTRACE = 0
!ENDIF
+# enable address sanitizer using ASAN=1 on the command-line.
+#
+!IFNDEF ASAN
+ASAN = 0
+!ENDIF
+
# Set this to one of the following values to enable various debugging
# features. Each level includes the debugging options from the previous
# levels. Currently, the recognized values for DEBUG are:
@@ -361,6 +374,7 @@
!IFNDEF OPT_FEATURE_FLAGS
!IF $(MINIMAL_AMALGAMATION)==0
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS3=1
+OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS5=1
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RTREE=1
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_GEOPOLY=1
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_STMTVTAB=1
@@ -389,6 +403,14 @@
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RBU=1
!ENDIF
+# Should structured exception handling (SEH) be enabled for WAL mode in
+# the core library? It is on by default. Only omit it if the
+# USE_SEH=0 option is provided on the nmake command-line.
+#
+!IF $(USE_SEH)==0
+OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_OMIT_SEH=1
+!ENDIF
+
# These are the "extended" SQLite compilation options used when compiling for
# the Windows 10 platform.
#
@@ -877,6 +899,13 @@
!ENDIF
!ENDIF
+
+# Address sanitizer if ASAN=1
+#
+!IF $(ASAN)>0
+TCC = $(TCC) /fsanitize=address
+!ENDIF
+
# <>
# The locations of the Tcl header and library files. Also, the library that
# non-stubs enabled programs using Tcl must link against. These variables
@@ -1526,7 +1555,7 @@
$(TOP)\ext\fts3\fts3_term.c \
$(TOP)\ext\fts3\fts3_test.c \
$(TOP)\ext\rbu\test_rbu.c \
- $(TOP)\ext\session\test_session.c
+ $(TOP)\ext\session\test_session.c
# Statically linked extensions.
#
@@ -1565,8 +1594,7 @@
$(TOP)\ext\rtree\test_rtreedoc.c \
$(TOP)\ext\recover\sqlite3recover.c \
$(TOP)\ext\recover\test_recover.c \
- $(TOP)\ext\recover\dbdata.c \
- fts5.c
+ $(TOP)\ext\recover\dbdata.c
# If use of zlib is enabled, add the "zipfile.c" source file.
#
@@ -1582,7 +1610,8 @@
$(SRC01) \
$(SRC07) \
$(SRC10) \
- $(TOP)\ext\async\sqlite3async.c
+ $(TOP)\ext\async\sqlite3async.c \
+ fts5.c
# Header files used by all library source files.
#
@@ -1661,6 +1690,8 @@
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_FTS4=1
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
!ENDIF
# <>
@@ -1671,6 +1702,33 @@
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -I$(TOP)\test -I$(TOP)\ext\recover
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_MEMSYS5
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_OSS_FUZZ
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_BYTECODE_VTAB
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_DBPAGE_VTAB
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_DBSTAT_VTAB
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_BYTECODE_VTAB
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_DESERIALIZE
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_FTS3_PARENTHESIS
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_FTS4
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_FTS5
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_GEOPOLY
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_MATH_FUNCTIONS
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_MEMSYS5
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_NORMALIZE
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_PREUPDATE_HOOK
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_RTREE
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_SESSION
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_STMTVTAB
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_STAT4
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_STMT_SCANSTATUS
+FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_MAX_MEMORY=50000000
+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_MAX_MEMORY=50000000
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_PRINTF_PRECISION_LIMIT=1000
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_OMIT_LOAD_EXTENSION
@@ -1765,8 +1823,8 @@
/link $(SQLITE3EXEPDB) $(LDFLAGS) $(LTLINKOPTS) $(SHELL_LINK_OPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS)
# <>
-sqldiff.exe: $(TOP)\tool\sqldiff.c $(SQLITE3C) $(SQLITE3H)
- $(LTLINK) $(NO_WARN) $(TOP)\tool\sqldiff.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
+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)
@@ -1780,6 +1838,12 @@
sourcetest: srcck1.exe $(SQLITE3C)
srcck1.exe $(SQLITE3C)
+src-verify.exe: $(TOP)\tool\src-verify.c
+ $(LTLINK) $(NO_WARN) $(TOP)\tool\src-verify.c
+
+verify-source: src-verify.exe
+ src-verify.exe $(TOP)
+
fuzzershell.exe: $(TOP)\tool\fuzzershell.c $(SQLITE3C) $(SQLITE3H)
$(LTLINK) $(NO_WARN) $(FUZZERSHELL_COMPILE_OPTS) $(TOP)\tool\fuzzershell.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
@@ -1787,7 +1851,10 @@
$(LTLINK) $(NO_WARN) $(DBFUZZ_COMPILE_OPTS) $(TOP)\test\dbfuzz.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
fuzzcheck.exe: $(FUZZCHECK_SRC) $(SQLITE3C) $(SQLITE3H)
- $(LTLINK) $(NO_WARN) $(FUZZCHECK_OPTS) $(FUZZCHECK_SRC) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
+ $(LTLINK) /F 8388608 $(NO_WARN) $(FUZZCHECK_OPTS) $(FUZZCHECK_SRC) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
+
+fuzzcheck-asan.exe: $(FUZZCHECK_SRC) $(SQLITE3C) $(SQLITE3H)
+ $(LTLINK) $(NO_WARN) /fsanitize=address $(FUZZCHECK_OPTS) $(FUZZCHECK_SRC) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
ossshell.exe: $(OSSSHELL_SRC) $(SQLITE3C) $(SQLITE3H)
$(LTLINK) $(NO_WARN) $(FUZZCHECK_OPTS) $(OSSSHELL_SRC) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
@@ -1840,7 +1907,7 @@
move vdbe.new tsrc\vdbe.c
echo > .target_source
-sqlite3.c: .target_source sqlite3ext.h sqlite3session.h $(MKSQLITE3C_TOOL)
+sqlite3.c: .target_source sqlite3ext.h sqlite3session.h $(MKSQLITE3C_TOOL) src-verify.exe
$(TCLSH_CMD) $(MKSQLITE3C_TOOL) $(MKSQLITE3C_ARGS)
sqlite3-all.c: sqlite3.c $(TOP)\tool\split-sqlite3c.tcl
@@ -2196,8 +2263,8 @@
$(TOP)\src\shell.c.in \
$(TOP)\ext\misc\appendvfs.c \
$(TOP)\ext\misc\completion.c \
- $(TOP)\ext\misc\base64.c \
- $(TOP)\ext\misc\base85.c \
+ $(TOP)\ext\misc\base64.c \
+ $(TOP)\ext\misc\base85.c \
$(TOP)\ext\misc\decimal.c \
$(TOP)\ext\misc\fileio.c \
$(TOP)\ext\misc\ieee754.c \
@@ -2208,9 +2275,10 @@
$(TOP)\ext\expert\sqlite3expert.c \
$(TOP)\ext\expert\sqlite3expert.h \
$(TOP)\ext\misc\memtrace.c \
- $(TOP)/ext/recover/dbdata.c \
- $(TOP)/ext/recover/sqlite3recover.c \
- $(TOP)/ext/recover/sqlite3recover.h \
+ $(TOP)\ext\misc\pcachetrace.c \
+ $(TOP)\ext\recover\dbdata.c \
+ $(TOP)\ext\recover\sqlite3recover.c \
+ $(TOP)\ext\recover\sqlite3recover.h \
$(TOP)\src\test_windirent.c
# If use of zlib is enabled, add the "zipfile.c" source file.
@@ -2402,6 +2470,9 @@
@set PATH=$(LIBTCLPATH);$(PATH)
.\testfixture.exe $(TOP)\test\loadext.test $(TESTOPTS)
+tool-zip: testfixture.exe sqlite3.exe sqldiff.exe sqlite3_analyzer.exe $(TOP)\tool\mktoolzip.tcl
+ .\testfixture.exe $(TOP)\tool\mktoolzip.tcl
+
coretestprogs: $(TESTPROGS)
testprogs: coretestprogs srcck1.exe fuzzcheck.exe sessionfuzz.exe
@@ -2454,10 +2525,13 @@
#
devtest: testfixture.exe fuzztest testrunner
+mdevtest:
+ $(TCLSH_CMD) $(TOP)\test\testrunner.tcl mdevtest
+
# Testing for a release
#
releasetest: testfixture.exe fuzztest
- testfixture.exe $(TOP)/test/testrunner.tcl release
+ testfixture.exe $(TOP)\test\testrunner.tcl release
smoketest: $(TESTPROGS)
@@ -2485,14 +2559,14 @@
$(LTLINK) $(NO_WARN) $(TOP)\ext\expert\sqlite3expert.c $(TOP)\ext\expert\expert.c $(SQLITE3C) $(TLIBS)
CHECKER_DEPS =\
- $(TOP)/tool/mkccode.tcl \
+ $(TOP)\tool\mkccode.tcl \
sqlite3.c \
- $(TOP)/src/tclsqlite.c \
- $(TOP)/ext/repair/sqlite3_checker.tcl \
- $(TOP)/ext/repair/checkindex.c \
- $(TOP)/ext/repair/checkfreelist.c \
- $(TOP)/ext/misc/btreeinfo.c \
- $(TOP)/ext/repair/sqlite3_checker.c.in
+ $(TOP)\src\tclsqlite.c \
+ $(TOP)\ext\repair\sqlite3_checker.tcl \
+ $(TOP)\ext\repair\checkindex.c \
+ $(TOP)\ext\repair\checkfreelist.c \
+ $(TOP)\ext\misc\btreeinfo.c \
+ $(TOP)\ext\repair\sqlite3_checker.c.in
sqlite3_checker.c: $(CHECKER_DEPS)
$(TCLSH_CMD) $(TOP)\tool\mkccode.tcl $(TOP)\ext\repair\sqlite3_checker.c.in > $@
@@ -2631,4 +2705,5 @@
del /Q showshm.exe sqlite3_checker.* sqlite3_expert.exe 2>NUL
del /Q fts5.* fts5parse.* 2>NUL
del /Q lsm.h lsm1.c 2>NUL
+ del /q src-verify.exe 2>NUL
# <>
diff -Nru sqlite3-3.41.0-0/README.md sqlite3-3.44.0-0/README.md
--- sqlite3-3.41.0-0/README.md 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/README.md 2023-11-04 14:24:27.000000000 +0000
@@ -7,7 +7,7 @@
## Version Control
-SQLite sources are managed using the
+SQLite sources are managed using
[Fossil](https://www.fossil-scm.org/), a distributed version control system
that was specifically designed and written to support SQLite development.
The [Fossil repository](https://sqlite.org/src/timeline) contains the urtext.
@@ -25,7 +25,28 @@
verify its integrity, there are hints on how to do that in the
[Verifying Code Authenticity](#vauth) section below.
-## Obtaining The Code
+## Contacting The SQLite Developers
+
+The preferred way to ask questions or make comments about SQLite or to
+report bugs against SQLite is to visit the
+[SQLite Forum](https://sqlite.org/forum) at .
+Anonymous postings are permitted.
+
+If you think you have found a bug that has security implications and
+you do not want to report it on the public forum, you can send a private
+email to drh at sqlite dot org.
+
+## Public Domain
+
+The SQLite source code is in the public domain. See
+ for details.
+
+Because SQLite is in the public domain, we do not normally accept pull
+requests, because if we did take a pull request, the changes in that
+pull request might carry a copyright and the SQLite source code would
+then no longer be fully in the public domain.
+
+## Obtaining The SQLite Source Code
If you do not want to use Fossil, you can download tarballs or ZIP
archives or [SQLite archives](https://sqlite.org/cli.html#sqlar) as follows:
@@ -82,9 +103,9 @@
mkdir bld ;# Build will occur in a sibling directory
cd bld ;# Change to the build directory
../sqlite/configure ;# Run the configure script
- make ;# Run the makefile.
+ make ;# Builds the "sqlite3" command-line tool
make sqlite3.c ;# Build the "amalgamation" source file
- make test ;# Run some tests (requires Tcl)
+ make devtest ;# Run some tests (requires Tcl)
See the makefile for additional targets.
@@ -94,32 +115,33 @@
can copy and edit to suit your needs. Comments on the generic makefile
show what changes are needed.
-## Using MSVC for Windows systems
+## Compiling for Windows Using MSVC
On Windows, all applicable build products can be compiled with MSVC.
-First open the command prompt window associated with the desired compiler
-version (e.g. "Developer Command Prompt for VS2013"). Next, use NMAKE
-with the provided "Makefile.msc" to build one of the supported targets.
-
-For example, from the parent directory of the source subtree named "sqlite":
-
- mkdir bld
- cd bld
- nmake /f ..\sqlite\Makefile.msc TOP=..\sqlite
- nmake /f ..\sqlite\Makefile.msc sqlite3.c TOP=..\sqlite
- nmake /f ..\sqlite\Makefile.msc sqlite3.dll TOP=..\sqlite
- nmake /f ..\sqlite\Makefile.msc sqlite3.exe TOP=..\sqlite
- nmake /f ..\sqlite\Makefile.msc test TOP=..\sqlite
-
-There are several build options that can be set via the NMAKE command
-line. For example, to build for WinRT, simply add "FOR_WINRT=1" argument
-to the "sqlite3.dll" command line above. When debugging into the SQLite
-code, adding the "DEBUG=1" argument to one of the above command lines is
-recommended.
-
-SQLite does not require [Tcl](http://www.tcl.tk/) to run, but a Tcl installation
-is required by the makefiles (including those for MSVC). SQLite contains
-a lot of generated code and Tcl is used to do much of that code generation.
+You will also need a working installation of TCL.
+See the [compile-for-windows.md](doc/compile-for-windows.md) document for
+additional information about how to install MSVC and TCL and configure your
+build environment.
+
+If you want to run tests, you need to let SQLite know the location of your
+TCL library, using a command like this:
+
+ set TCLDIR=c:\Tcl
+
+SQLite uses "tclsh.exe" as part of the build process, and so that utility
+program will need to be somewhere on your %PATH%. The finished SQLite library
+does not contain any TCL code, but it does use TCL to help with the build process
+and to run tests.
+
+Build using Makefile.msc. Example:
+
+ nmake /f Makefile.msc
+ nmake /f Makefile.msc sqlite3.c
+ nmake /f Makefile.msc devtest
+ nmake /f Makefile.msc releasetest
+
+There are many other makefile targets. See comments in Makefile.msc for
+details.
## Source Code Tour
@@ -290,16 +312,13 @@
is not part of the core SQLite library. But as most of the tests in this
repository are written in Tcl, the Tcl language bindings are important.
- * **test*.c** - Files in the src/ folder that begin with "test" go into
+ * **test\*.c** - Files in the src/ folder that begin with "test" go into
building the "testfixture.exe" program. The testfixture.exe program is
an enhanced Tcl shell. The testfixture.exe program runs scripts in the
test/ folder to validate the core SQLite code. The testfixture program
(and some other test programs too) is built and run when you type
"make test".
- * **ext/misc/json1.c** - This file implements the various JSON functions
- that are built into SQLite.
-
There are many other source files. Each has a succinct header comment that
describes its purpose and role within the larger system.
@@ -307,8 +326,8 @@
## Verifying Code Authenticity
The `manifest` file at the root directory of the source tree
-contains either a SHA3-256 hash (for newer files) or a SHA1 hash (for
-older files) for every source file in the repository.
+contains either a SHA3-256 hash or a SHA1 hash
+for every source file in the repository.
The name of the version of the entire source tree is just the
SHA3-256 hash of the `manifest` file itself, possibly with the
last line of that file omitted if the last line begins with
@@ -316,14 +335,25 @@
The `manifest.uuid` file should contain the SHA3-256 hash of the
`manifest` file. If all of the above hash comparisons are correct, then
you can be confident that your source tree is authentic and unadulterated.
+Details on the format for the `manifest` files are available
+[on the Fossil website](https://fossil-scm.org/fossil/doc/trunk/www/fileformat.wiki#manifest).
+
+The process of checking source code authenticity is automated by the
+makefile:
+
+> make verify-source
+
+Or on windows:
+
+> nmake /f Makefile.msc verify-source
-The format of the `manifest` file should be mostly self-explanatory, but
-if you want details, they are available
-[here](https://fossil-scm.org/fossil/doc/trunk/www/fileformat.wiki#manifest).
+Using the makefile to verify source integrity is good for detecting
+accidental changes to the source tree, but malicious changes could be
+hidden by also modifying the makefiles.
## Contacts
-The main SQLite website is [http://www.sqlite.org/](http://www.sqlite.org/)
+The main SQLite website is [http:/sqlite.org/](http://sqlite.org/)
with geographically distributed backups at
[http://www2.sqlite.org/](http://www2.sqlite.org) and
[http://www3.sqlite.org/](http://www3.sqlite.org).
diff -Nru sqlite3-3.41.0-0/VERSION sqlite3-3.44.0-0/VERSION
--- sqlite3-3.41.0-0/VERSION 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/VERSION 2023-11-04 14:24:27.000000000 +0000
@@ -1 +1 @@
-3.41.2
+3.44.1
diff -Nru sqlite3-3.41.0-0/autoconf/Makefile.am sqlite3-3.44.0-0/autoconf/Makefile.am
--- sqlite3-3.41.0-0/autoconf/Makefile.am 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/autoconf/Makefile.am 2023-11-04 14:24:27.000000000 +0000
@@ -9,7 +9,7 @@
EXTRA_sqlite3_SOURCES = sqlite3.c
sqlite3_LDADD = @EXTRA_SHELL_OBJ@ @READLINE_LIBS@
sqlite3_DEPENDENCIES = @EXTRA_SHELL_OBJ@
-sqlite3_CFLAGS = $(AM_CFLAGS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ENABLE_STMTVTAB -DSQLITE_ENABLE_DBSTAT_VTAB $(SHELL_CFLAGS)
+sqlite3_CFLAGS = $(AM_CFLAGS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_DQS=0 -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ENABLE_STMTVTAB -DSQLITE_ENABLE_DBSTAT_VTAB $(SHELL_CFLAGS)
include_HEADERS = sqlite3.h sqlite3ext.h
diff -Nru sqlite3-3.41.0-0/autoconf/Makefile.msc sqlite3-3.44.0-0/autoconf/Makefile.msc
--- sqlite3-3.41.0-0/autoconf/Makefile.msc 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/autoconf/Makefile.msc 2023-11-04 14:24:27.000000000 +0000
@@ -52,6 +52,13 @@
USE_STDCALL = 0
!ENDIF
+# Use the USE_SEH=0 option on the nmake command line to omit structured
+# exception handling (SEH) support. SEH is on by default.
+#
+!IFNDEF USE_SEH
+USE_SEH = 1
+!ENDIF
+
# Set this non-0 to have the shell executable link against the core dynamic
# link library.
#
@@ -180,6 +187,12 @@
OSTRACE = 0
!ENDIF
+# enable address sanitizer using ASAN=1 on the command-line.
+#
+!IFNDEF ASAN
+ASAN = 0
+!ENDIF
+
# Set this to one of the following values to enable various debugging
# features. Each level includes the debugging options from the previous
# levels. Currently, the recognized values for DEBUG are:
@@ -283,6 +296,7 @@
!IFNDEF OPT_FEATURE_FLAGS
!IF $(MINIMAL_AMALGAMATION)==0
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS3=1
+OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS5=1
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RTREE=1
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_GEOPOLY=1
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_STMTVTAB=1
@@ -311,6 +325,14 @@
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RBU=1
!ENDIF
+# Should structured exception handling (SEH) be enabled for WAL mode in
+# the core library? It is on by default. Only omit it if the
+# USE_SEH=0 option is provided on the nmake command-line.
+#
+!IF $(USE_SEH)==0
+OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_OMIT_SEH=1
+!ENDIF
+
# These are the "extended" SQLite compilation options used when compiling for
# the Windows 10 platform.
#
@@ -718,6 +740,13 @@
!ENDIF
+# Address sanitizer if ASAN=1
+#
+!IF $(ASAN)>0
+TCC = $(TCC) /fsanitize=address
+!ENDIF
+
+
# Compiler options needed for programs that use the readline() library.
#
!IFNDEF READLINE_FLAGS
@@ -959,6 +988,8 @@
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_FTS4=1
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
!ENDIF
diff -Nru sqlite3-3.41.0-0/autoconf/tea/configure.ac sqlite3-3.44.0-0/autoconf/tea/configure.ac
--- sqlite3-3.41.0-0/autoconf/tea/configure.ac 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/autoconf/tea/configure.ac 2023-11-04 14:24:27.000000000 +0000
@@ -19,7 +19,7 @@
# so that we create the export library with the dll.
#-----------------------------------------------------------------------
-AC_INIT([sqlite],[3.41.0])
+AC_INIT([sqlite],[3.44.1])
#--------------------------------------------------------------------
# Call TEA_INIT as the first TEA_ macro to set up initial vars.
diff -Nru sqlite3-3.41.0-0/configure.ac sqlite3-3.44.0-0/configure.ac
--- sqlite3-3.41.0-0/configure.ac 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/configure.ac 2023-11-04 14:24:27.000000000 +0000
@@ -598,11 +598,28 @@
TARGET_HAVE_READLINE=1
fi
fi
+AC_ARG_WITH([linenoise],
+ [AS_HELP_STRING([--with-linenoise=DIR],[source directory for linenoise library])],
+ [with_linenoise=$withval],
+ [with_linenoise="no"])
+if test "x$with_linenoise" != "xno"; then
+ TARGET_HAVE_READLINE=0
+ TARGET_HAVE_EDITLINE=0
+ TARGET_HAVE_LINENOISE=1
+ TARGET_READLINE_INC="-I${with_linenoise}"
+ TARGET_READLINE_LIBS="${with_linenoise}/linenoise.c"
+ echo "using linenoise source code at ${with_linenoise}"
+else
+ TARGET_HAVE_LINENOISE=0
+ echo "not using linenoise"
+fi
AC_SUBST(TARGET_READLINE_LIBS)
AC_SUBST(TARGET_READLINE_INC)
AC_SUBST(TARGET_HAVE_READLINE)
AC_SUBST(TARGET_HAVE_EDITLINE)
+AC_SUBST(TARGET_HAVE_LINENOISE)
+
##########
# Figure out what C libraries are required to compile programs
@@ -615,7 +632,7 @@
AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug],[enable debugging & verbose explain]))
AC_MSG_CHECKING([build type])
if test "${enable_debug}" = "yes" ; then
- TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0"
+ TARGET_DEBUG="-DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0 -Wall"
AC_MSG_RESULT([debug])
else
TARGET_DEBUG="-DNDEBUG"
diff -Nru sqlite3-3.41.0-0/debian/changelog sqlite3-3.44.0-0/debian/changelog
--- sqlite3-3.41.0-0/debian/changelog 2023-03-17 22:50:46.000000000 +0000
+++ sqlite3-3.44.0-0/debian/changelog 2023-11-04 14:24:45.000000000 +0000
@@ -1,8 +1,8 @@
-sqlite3 (3.41.0-0-19468~202303171422~202303172250~ubuntu22.04.1) jammy; urgency=low
+sqlite3 (3.44.0-0-20359~202311021114~202311041424~ubuntu22.04.1) jammy; urgency=low
* Auto build.
- -- Gajj GNDU Fri, 17 Mar 2023 22:50:46 +0000
+ -- Gajj GNDU Sat, 04 Nov 2023 14:24:45 +0000
sqlite3 (3.36.0-3) UNRELEASED; urgency=high
diff -Nru sqlite3-3.41.0-0/debian/git-build-recipe.manifest sqlite3-3.44.0-0/debian/git-build-recipe.manifest
--- sqlite3-3.41.0-0/debian/git-build-recipe.manifest 2023-03-17 22:50:46.000000000 +0000
+++ sqlite3-3.44.0-0/debian/git-build-recipe.manifest 2023-11-04 14:24:45.000000000 +0000
@@ -1,4 +1,4 @@
-# git-build-recipe format 0.4 deb-version 3.41.0-0-19468~202303171422~202303172250
-lp:~linuxgndu/sqlite-recipe/+git/sqlite-recipe git-commit:32ccccf7abd910c9868ffbf9baf3b9b370e96ac7
+# git-build-recipe format 0.4 deb-version 3.44.0-0-20359~202311021114~202311041424
+lp:~linuxgndu/sqlite-recipe/+git/sqlite-recipe git-commit:11379c080966df5c39db7a606c2cb33032819e00
nest-part packaging lp:sqlite-recipe debian debian git-commit:827d8530756db3776fd03e0573bcbfd2b5a75539
nest-part www lp:sqlite-recipe www www git-commit:827d8530756db3776fd03e0573bcbfd2b5a75539
diff -Nru sqlite3-3.41.0-0/doc/compile-for-windows.md sqlite3-3.44.0-0/doc/compile-for-windows.md
--- sqlite3-3.41.0-0/doc/compile-for-windows.md 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/doc/compile-for-windows.md 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,136 @@
+# 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-08-16:
+
+ 1. Install Microsoft Visual Studio. The free "community edition"
+ will work fine. Do a standard install for C++ development.
+ SQLite only needs the
+ "cl" compiler and the "nmake" build tool.
+
+ 2. Under the "Start" menu, find "All Apps" then go to "Visual Studio 20XX"
+ and find "x64 Native Tools Command Prompt for VS 20XX". Pin that
+ application to your task bar, as you will use it a lot. Bring up
+ an instance of this command prompt and do all of the subsequent steps
+ in that "x64 Native Tools" command prompt. (Or use "x86" if you want
+ a 32-bit build.) The subsequent steps will not work in a vanilla
+ DOS prompt. Nor will they work in PowerShell.
+
+ 3. Install TCL development libraries. This note assumes that you will
+ install the TCL development libraries in the "`c:\Tcl`" directory.
+ Make adjustments
+ if you want TCL installed somewhere else. SQLite needs both the
+ "tclsh.exe" command-line tool as part of the build process, and
+ the "tcl86.lib" library in order to run tests. You will need
+ TCL version 8.6 or later.
+
+ - Get the TCL source archive, perhaps from
+ [https://www.tcl.tk/software/tcltk/download.html](https://www.tcl.tk/software/tcltk/download.html).
+
- Untar or unzip the source archive. CD into the "win/" subfolder
+ of the source tree.
+
- Run: `nmake /f makefile.vc release`
+
- Run: `nmake /f makefile.vc INSTALLDIR=c:\Tcl install`
+
- CD to `c:\Tcl\lib`. In that subfolder make a copy of the
+ "`tcl86t.lib`" file to the alternative name "`tcl86.lib`"
+ (omitting the second 't'). Leave the copy in the same directory
+ as the original.
+
- CD to `c:\Tcl\bin`. Make a copy of the "`tclsh86t.exe`"
+ file into "`tclsh.exe`" (without the "86t") in the same directory.
+
- Add `c:\Tcl\bin` to your %PATH%. To do this, go to Settings
+ and search for "path". Select "edit environment variables for
+ your account" and modify your default PATH accordingly.
+ You will need to close and reopen your command prompts after
+ making this change.
+
+
+ 4. Download the SQLite source tree and unpack it. CD into the
+ toplevel directory of the source tree.
+
+ 5. Set the TCLDIR environment variable to point to your TCL installation.
+ Like this:
+
+
+ 6. Run the "`Makefile.msc`" makefile with an appropriate target.
+ Examples:
+
+ - `nmake /f makefile.msc`
+
- `nmake /f makefile.msc sqlite3.c`
+
- `nmake /f makefile.msc devtest`
+
- `nmake /f makefile.msc releasetest`
+
+
+## 32-bit Builds
+
+Doing a 32-bit build is just like doing a 64-bit build with the
+following minor changes:
+
+ 1. Use the "x86 Native Tools Command Prompt" instead of
+ "x64 Native Tools Command Prompt". "**x86**" instead of "**x64**".
+
+ 2. Use a different installation directory for TCL.
+ The recommended directory is `c:\tcl32`. Thus you end up
+ with two TCL builds:
+
+ - `c:\tcl` ← 64-bit (the default)
+
- `c:\tcl32` ← 32-bit
+
+
+ 3. Ensure that `c:\tcl32\bin` comes before `c:\tcl\bin` on
+ your PATH environment variable. You can achieve this using
+ a command like:
+
+ - `set PATH=c:\tcl32\bin;%PATH%`
+
+
+## Statically Linking The TCL Library
+
+Some utility programs associated with SQLite need to be linked
+with TCL in order to function. The [sqlite3_analyzer.exe program](https://sqlite.org/sqlanalyze.html)
+is an example. You can build as described above, and then
+enter:
+
+> ~~~~
+nmake /f Makefile.msc sqlite3_analyzer.exe
+~~~~
+
+And you will end up with a working executable. However, that executable
+will depend on having the "tcl86.dll" library somewhere on your %PATH%.
+Use the following steps to build an executable that has the TCL library
+statically linked so that it does not depend on separate DLL:
+
+ 1. Use the appropriate "Command Prompt" window - either x86 or
+ x64, depending on whether you want a 32-bit or 64-bit executable.
+
+ 2. Untar the TCL source tarball into a fresh directory. CD into
+ the "win/" subfolder.
+
+ 3. Run: `nmake /f makefile.vc OPTS=nothreads,static shell`
+
+
+ 4. CD into the "Release*" subfolder that is created (note the
+ wildcard - the full name of the directory might vary). There
+ you will find the "tcl86s.lib" file. Copy this file into the
+ same directory that you put the "tcl86.lib" on your initial
+ installation. (In this document, that directory is
+ "`C:\Tcl32\lib`" for 32-bit builds and
+ "`C:\Tcl\lib`" for 64-bit builds.)
+
+ 5. CD into your SQLite source code directory and build the desired
+ utility program, but add the following extra arguments to the
+ nmake command line:
+
+ CCOPTS="-DSTATIC_BUILD" LIBTCL="tcl86s.lib netapi32.lib user32.lib"
+
+ So, for example, to build a statically linked version of
+ sqlite3_analyzer.exe, you might type:
+
+ nmake /f Makefile.msc CCOPTS="-DSTATIC_BUILD" LIBTCL="tcl86s.lib netapi32.lib user32.lib" sqlite3_analyzer.exe
+
+
+ 6. After your executable is built, you can verify that it does not
+ depend on the TCL DLL by running:
+
+ dumpbin /dependents sqlite3_analyzer.exe
+
diff -Nru sqlite3-3.41.0-0/doc/lemon.html sqlite3-3.44.0-0/doc/lemon.html
--- sqlite3-3.41.0-0/doc/lemon.html 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/doc/lemon.html 2023-11-04 14:24:27.000000000 +0000
@@ -322,7 +322,7 @@
- Declare a local variable of type "yyParser"
- Initialize the variable using ParseInit()
-
- Pass a pointer to the variable in calls ot Parse()
+
- Pass a pointer to the variable in calls to Parse()
- Deallocate substructure in the parse variable using ParseFinalize().
@@ -1229,8 +1229,8 @@
Lemon was originally written by Richard Hipp sometime in the late
1980s on a Sun4 Workstation using K&R C.
-There was a companion LL(1) parser generator program named "Lime", the
-source code to which as been lost.
+There was a companion LL(1) parser generator program named "Lime".
+The Lime source code has been lost.
The lemon.c source file was originally many separate files that were
compiled together to generate the "lemon" executable. Sometime in the
diff -Nru sqlite3-3.41.0-0/doc/testrunner.md sqlite3-3.44.0-0/doc/testrunner.md
--- sqlite3-3.41.0-0/doc/testrunner.md 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/doc/testrunner.md 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,284 @@
+
+
+# The testrunner.tcl Script
+
+# 1. Overview
+
+testrunner.tcl is a Tcl script used to run multiple SQLite tests using
+multiple jobs. It supports the following types of tests:
+
+ * Tcl test scripts.
+
+ * Tests run with [make] commands. Specifically, at time of writing,
+ [make fuzztest], [make mptest], [make sourcetest] and [make threadtest].
+
+testrunner.tcl pipes the output of all tests and builds run into log file
+**testrunner.log**, created in the cwd directory. Searching this file for
+"failed" is a good way to find the output of a failed test.
+
+testrunner.tcl also populates SQLite database **testrunner.db**. This database
+contains details of all tests run, running and to be run. A useful query
+might be:
+
+```
+ SELECT * FROM script WHERE state='failed'
+```
+
+Running the command:
+
+```
+ ./testfixture $(TESTDIR)/testrunner.tcl status
+```
+
+in the directory containing the testrunner.db database runs various queries
+to produce a succinct report on the state of a running testrunner.tcl script.
+Running:
+
+```
+ watch ./testfixture $(TESTDIR)/testrunner.tcl status
+```
+
+in another terminal is a good way to keep an eye on a long running test.
+
+Sometimes testrunner.tcl uses the [testfixture] binary that it is run with
+to run tests (see "Binary Tests" below). Sometimes it builds testfixture and
+other binaries in specific configurations to test (see "Source Tests").
+
+# 2. Binary Tests
+
+The commands described in this section all run various combinations of the Tcl
+test scripts using the [testfixture] binary used to run the testrunner.tcl
+script (i.e. they do not invoke the compiler to build new binaries, or the
+[make] command to run tests that are not Tcl scripts). The procedure to run
+these tests is therefore:
+
+ 1. Build the "testfixture" (or "testfixture.exe" for windows) binary using
+ whatever method seems convenient.
+
+ 2. Test the binary built in step 1 by running testrunner.tcl with it,
+ perhaps with various options.
+
+The following sub-sections describe the various options that can be
+passed to testrunner.tcl to test binary testfixture builds.
+
+## 2.1. Organization of Tcl Tests
+
+Tcl tests are stored in files that match the pattern *\*.test*. They are
+found in both the $TOP/test/ directory, and in the various sub-directories
+of the $TOP/ext/ directory of the source tree. Not all *\*.test* files
+contain Tcl tests - a handful are Tcl scripts designed to invoke other
+*\*.test* files.
+
+The **veryquick** set of tests is a subset of all Tcl test scripts in the
+source tree. In includes most tests, but excludes some that are very slow.
+Almost all fault-injection tests (those that test the response of the library
+to OOM or IO errors) are excluded. It is defined in source file
+*test/permutations.test*.
+
+The **full** set of tests includes all Tcl test scripts in the source tree.
+To run a "full" test is to run all Tcl test scripts that can be found in the
+source tree.
+
+File *permutations.test* defines various test "permutations". A permutation
+consists of:
+
+ * A subset of Tcl test scripts, and
+
+ * Runtime configuration to apply before running each test script
+ (e.g. enabling auto-vacuum, or disable lookaside).
+
+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*.
+
+## 2.2. Commands to Run Tests
+
+To run the "veryquick" test set, use either of the following:
+
+```
+ ./testfixture $TESTDIR/testrunner.tcl
+ ./testfixture $TESTDIR/testrunner.tcl veryquick
+```
+
+To run the "full" test suite:
+
+```
+ ./testfixture $TESTDIR/testrunner.tcl full
+```
+
+To run the subset of the "full" test suite for which the test file name matches
+a specified pattern (e.g. all tests that start with "fts5"), either of:
+
+```
+ ./testfixture $TESTDIR/testrunner.tcl fts5%
+ ./testfixture $TESTDIR/testrunner.tcl 'fts5*'
+```
+
+To run "all" tests (full + permutations):
+
+```
+ ./testfixture $TESTDIR/testrunner.tcl all
+```
+
+
+## 2.3. Investigating Binary Test Failures
+
+If a test fails, testrunner.tcl reports name of the Tcl test script and, if
+applicable, the name of the permutation, to stdout. This information can also
+be retrieved from either *testrunner.log* or *testrunner.db*.
+
+If there is no permutation, the individual test script may be run with:
+
+```
+ ./testfixture $PATH_TO_SCRIPT
+```
+
+Or, if the failure occured as part of a permutation:
+
+```
+ ./testfixture $TESTDIR/testrunner.tcl $PERMUTATION $PATH_TO_SCRIPT
+```
+
+TODO: An example instead of "$PERMUTATION" and $PATH\_TO\_SCRIPT?
+
+# 3. Source Code Tests
+
+The commands described in this section invoke the C compiler to build
+binaries from the source tree, then use those binaries to run Tcl and
+other tests. The advantages of this are that:
+
+ * it is possible to test multiple build configurations with a single
+ command, and
+
+ * it ensures that tests are always run using binaries created with the
+ same set of compiler options.
+
+The testrunner.tcl commands described in this section may be run using
+either a *testfixture* (or testfixture.exe) build, or with any other Tcl
+shell that supports SQLite 3.31.1 or newer via "package require sqlite3".
+
+TODO: ./configure + Makefile.msc build systems.
+
+## 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
+with debugging enabled and one without:
+
+```
+ tclsh $TESTDIR/testrunner.tcl mdevtest
+```
+
+In other words, it is equivalent to running:
+
+```
+ $TOP/configure --enable-all --enable-debug
+ make fuzztest
+ make testfixture
+ ./testfixture $TOP/test/testrunner.tcl veryquick
+
+ # Then, after removing files created by the tests above:
+ $TOP/configure --enable-all OPTS="-O0"
+ make fuzztest
+ make testfixture
+ ./testfixture $TOP/test/testrunner.tcl veryquick
+```
+
+The **sdevtest** command is identical to the mdevtest command, except that the
+second of the two builds is a sanitizer build. Specifically, this means that
+OPTS="-fsanitize=address,undefined" is specified instead of OPTS="-O0":
+
+```
+ tclsh $TESTDIR/testrunner.tcl sdevtest
+```
+
+The **release** command runs lots of tests under lots of builds. It runs
+different combinations of builds and tests depending on whether it is run
+on Linux, Windows or OSX. Refer to *testrunner\_data.tcl* for the details
+of the specific tests run.
+
+```
+ tclsh $TESTDIR/testrunner.tcl release
+```
+
+## 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:
+
+```
+ tclsh $TESTDIR/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS
+```
+
+This can be combined with any of "mdevtest", "sdevtest" or "release" to
+test both SQLite and Zipvfs with a single command:
+
+```
+ tclsh $TESTDIR/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS mdevtest
+```
+
+## Investigating Source Code Test Failures
+
+Investigating a test failure that occurs during source code testing is a
+two step process:
+
+ 1. Recreating the build configuration in which the test failed, and
+
+ 2. Re-running the actual test.
+
+To recreate a build configuration, use the testrunner.tcl **script** command
+to create a build script. A build script is a bash script on Linux or OSX, or
+a dos \*.bat file on windows. For example:
+
+```
+ # Create a script that recreates build configuration "Device-One" on
+ # Linux or OSX:
+ tclsh $TESTDIR/testrunner.tcl script Device-One > make.sh
+
+ # Create a script that recreates build configuration "Have-Not" on Windows:
+ tclsh $TESTDIR/testrunner.tcl script Have-Not > make.bat
+```
+
+The generated bash or \*.bat file script accepts a single argument - a makefile
+target to build. This may be used either to run a [make] command test directly,
+or else to build a testfixture (or testfixture.exe) binary with which to
+run a Tcl test script, as described above.
+
+
+
+# 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.
+
+```
+ $ ./testfixture $TESTDIR/testrunner.tcl
+ splitting work across 16 jobs
+ ... more output ...
+```
+
+By default, testfixture.tcl attempts to set the number of jobs to the number
+of real cores on the machine. This can be overridden using the "--jobs" (or -j)
+switch:
+
+```
+ $ ./testfixture $TESTDIR/testrunner.tcl --jobs 8
+ splitting work across 8 jobs
+ ... more output ...
+```
+
+The number of jobs may also be changed while an instance of testrunner.tcl is
+running by exucuting the following command from the directory containing the
+testrunner.log and testrunner.db files:
+
+```
+ $ ./testfixture $TESTDIR/testrunner.tcl njob $NEW_NUMBER_OF_JOBS
+```
+
+
+
+
+
+
+
+
diff -Nru sqlite3-3.41.0-0/ext/expert/expert1.test sqlite3-3.44.0-0/ext/expert/expert1.test
--- sqlite3-3.41.0-0/ext/expert/expert1.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/expert/expert1.test 2023-11-04 14:24:27.000000000 +0000
@@ -464,4 +464,23 @@
t2 t2_idx_0001295b {100 20 5}
}
+if 0 {
+do_test expert1-6.0 {
+ catchcmd :memory: {
+.expert
+select base64('');
+.expert
+select name from pragma_collation_list order by name collate uint;
+}
+} {0 {(no new indexes)
+
+SCAN CONSTANT ROW
+
+(no new indexes)
+
+SCAN pragma_collation_list VIRTUAL TABLE INDEX 0:
+USE TEMP B-TREE FOR ORDER BY
+}}
+}
+
finish_test
diff -Nru sqlite3-3.41.0-0/ext/expert/sqlite3expert.c sqlite3-3.44.0-0/ext/expert/sqlite3expert.c
--- sqlite3-3.41.0-0/ext/expert/sqlite3expert.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/expert/sqlite3expert.c 2023-11-04 14:24:27.000000000 +0000
@@ -32,7 +32,7 @@
#endif /* !defined(SQLITE_AMALGAMATION) */
-#ifndef SQLITE_OMIT_VIRTUALTABLE
+#ifndef SQLITE_OMIT_VIRTUALTABLE
typedef sqlite3_int64 i64;
typedef sqlite3_uint64 u64;
@@ -662,6 +662,7 @@
0, /* xRelease */
0, /* xRollbackTo */
0, /* xShadowName */
+ 0, /* xIntegrity */
};
return sqlite3_create_module(p->dbv, "expert", &expertModule, (void*)p);
@@ -1819,6 +1820,88 @@
}
/*
+** Define and possibly pretend to use a useless collation sequence.
+** This pretense allows expert to accept SQL using custom collations.
+*/
+int dummyCompare(void *up1, int up2, const void *up3, int up4, const void *up5){
+ (void)up1;
+ (void)up2;
+ (void)up3;
+ (void)up4;
+ (void)up5;
+ assert(0); /* VDBE should never be run. */
+ return 0;
+}
+/* And a callback to register above upon actual need */
+void useDummyCS(void *up1, sqlite3 *db, int etr, const char *zName){
+ (void)up1;
+ sqlite3_create_collation_v2(db, zName, etr, 0, dummyCompare, 0);
+}
+
+#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) \
+ && !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS)
+/*
+** dummy functions for no-op implementation of UDFs during expert's work
+*/
+void dummyUDF(sqlite3_context *up1, int up2, sqlite3_value **up3){
+ (void)up1;
+ (void)up2;
+ (void)up3;
+ assert(0); /* VDBE should never be run. */
+}
+void dummyUDFvalue(sqlite3_context *up1){
+ (void)up1;
+ assert(0); /* VDBE should never be run. */
+}
+
+/*
+** Register UDFs from user database with another.
+*/
+int registerUDFs(sqlite3 *dbSrc, sqlite3 *dbDst){
+ sqlite3_stmt *pStmt;
+ int rc = sqlite3_prepare_v2(dbSrc,
+ "SELECT name,type,enc,narg,flags "
+ "FROM pragma_function_list() "
+ "WHERE builtin==0", -1, &pStmt, 0);
+ if( rc==SQLITE_OK ){
+ while( SQLITE_ROW==(rc = sqlite3_step(pStmt)) ){
+ int nargs = sqlite3_column_int(pStmt,3);
+ int flags = sqlite3_column_int(pStmt,4);
+ const char *name = (char*)sqlite3_column_text(pStmt,0);
+ const char *type = (char*)sqlite3_column_text(pStmt,1);
+ const char *enc = (char*)sqlite3_column_text(pStmt,2);
+ if( name==0 || type==0 || enc==0 ){
+ /* no-op. Only happens on OOM */
+ }else{
+ int ienc = SQLITE_UTF8;
+ int rcf = SQLITE_ERROR;
+ if( strcmp(enc,"utf16le")==0 ) ienc = SQLITE_UTF16LE;
+ else if( strcmp(enc,"utf16be")==0 ) ienc = SQLITE_UTF16BE;
+ ienc |= (flags & (SQLITE_DETERMINISTIC|SQLITE_DIRECTONLY));
+ if( strcmp(type,"w")==0 ){
+ rcf = sqlite3_create_window_function(dbDst,name,nargs,ienc,0,
+ dummyUDF,dummyUDFvalue,0,0,0);
+ }else if( strcmp(type,"a")==0 ){
+ rcf = sqlite3_create_function(dbDst,name,nargs,ienc,0,
+ 0,dummyUDF,dummyUDFvalue);
+ }else if( strcmp(type,"s")==0 ){
+ rcf = sqlite3_create_function(dbDst,name,nargs,ienc,0,
+ dummyUDF,0,0);
+ }
+ if( rcf!=SQLITE_OK ){
+ rc = rcf;
+ break;
+ }
+ }
+ }
+ sqlite3_finalize(pStmt);
+ if( rc==SQLITE_DONE ) rc = SQLITE_OK;
+ }
+ return rc;
+}
+#endif
+
+/*
** Allocate a new sqlite3expert object.
*/
sqlite3expert *sqlite3_expert_new(sqlite3 *db, char **pzErrmsg){
@@ -1844,7 +1927,21 @@
sqlite3_db_config(pNew->dbm, SQLITE_DBCONFIG_TRIGGER_EQP, 1, (int*)0);
}
}
-
+
+ /* Allow custom collations to be dealt with through prepare. */
+ if( rc==SQLITE_OK ) rc = sqlite3_collation_needed(pNew->dbm,0,useDummyCS);
+ if( rc==SQLITE_OK ) rc = sqlite3_collation_needed(pNew->dbv,0,useDummyCS);
+
+#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) \
+ && !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS)
+ /* Register UDFs from database [db] with [dbm] and [dbv]. */
+ if( rc==SQLITE_OK ){
+ rc = registerUDFs(pNew->db, pNew->dbm);
+ }
+ if( rc==SQLITE_OK ){
+ rc = registerUDFs(pNew->db, pNew->dbv);
+ }
+#endif
/* Copy the entire schema of database [db] into [dbm]. */
if( rc==SQLITE_OK ){
@@ -1920,6 +2017,10 @@
while( rc==SQLITE_OK && zStmt && zStmt[0] ){
sqlite3_stmt *pStmt = 0;
+ /* Ensure that the provided statement compiles against user's DB. */
+ rc = idxPrepareStmt(p->db, &pStmt, pzErr, zStmt);
+ if( rc!=SQLITE_OK ) break;
+ sqlite3_finalize(pStmt);
rc = sqlite3_prepare_v2(p->dbv, zStmt, -1, &pStmt, &zStmt);
if( rc==SQLITE_OK ){
if( pStmt ){
diff -Nru sqlite3-3.41.0-0/ext/fts3/fts3.c sqlite3-3.44.0-0/ext/fts3/fts3.c
--- sqlite3-3.41.0-0/ext/fts3/fts3.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts3/fts3.c 2023-11-04 14:24:27.000000000 +0000
@@ -640,6 +640,7 @@
zLanguageid = (p->zLanguageid ? p->zLanguageid : "__langid");
sqlite3_vtab_config(p->db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1);
+ sqlite3_vtab_config(p->db, SQLITE_VTAB_INNOCUOUS);
/* Create a list of user columns for the virtual table */
zCols = sqlite3_mprintf("%Q, ", p->azColumn[0]);
@@ -3889,6 +3890,8 @@
rc = sqlite3Fts3PendingTermsFlush(p);
}
+ p->bIgnoreSavepoint = 1;
+
if( p->zContentTbl==0 ){
fts3DbExec(&rc, db,
"ALTER TABLE %Q.'%q_content' RENAME TO '%q_content';",
@@ -3916,6 +3919,8 @@
"ALTER TABLE %Q.'%q_segdir' RENAME TO '%q_segdir';",
p->zDb, p->zName, zName
);
+
+ p->bIgnoreSavepoint = 0;
return rc;
}
@@ -3926,12 +3931,28 @@
*/
static int fts3SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){
int rc = SQLITE_OK;
- UNUSED_PARAMETER(iSavepoint);
- assert( ((Fts3Table *)pVtab)->inTransaction );
- assert( ((Fts3Table *)pVtab)->mxSavepoint <= iSavepoint );
- TESTONLY( ((Fts3Table *)pVtab)->mxSavepoint = iSavepoint );
- if( ((Fts3Table *)pVtab)->bIgnoreSavepoint==0 ){
- rc = fts3SyncMethod(pVtab);
+ Fts3Table *pTab = (Fts3Table*)pVtab;
+ assert( pTab->inTransaction );
+ assert( pTab->mxSavepoint<=iSavepoint );
+ TESTONLY( pTab->mxSavepoint = iSavepoint );
+
+ if( pTab->bIgnoreSavepoint==0 ){
+ if( fts3HashCount(&pTab->aIndex[0].hPending)>0 ){
+ char *zSql = sqlite3_mprintf("INSERT INTO %Q.%Q(%Q) VALUES('flush')",
+ pTab->zDb, pTab->zName, pTab->zName
+ );
+ if( zSql ){
+ pTab->bIgnoreSavepoint = 1;
+ rc = sqlite3_exec(pTab->db, zSql, 0, 0, 0);
+ pTab->bIgnoreSavepoint = 0;
+ sqlite3_free(zSql);
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ }
+ if( rc==SQLITE_OK ){
+ pTab->iSavepoint = iSavepoint+1;
+ }
}
return rc;
}
@@ -3942,12 +3963,11 @@
** This is a no-op.
*/
static int fts3ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){
- TESTONLY( Fts3Table *p = (Fts3Table*)pVtab );
- UNUSED_PARAMETER(iSavepoint);
- UNUSED_PARAMETER(pVtab);
- assert( p->inTransaction );
- assert( p->mxSavepoint >= iSavepoint );
- TESTONLY( p->mxSavepoint = iSavepoint-1 );
+ Fts3Table *pTab = (Fts3Table*)pVtab;
+ assert( pTab->inTransaction );
+ assert( pTab->mxSavepoint >= iSavepoint );
+ TESTONLY( pTab->mxSavepoint = iSavepoint-1 );
+ pTab->iSavepoint = iSavepoint;
return SQLITE_OK;
}
@@ -3957,11 +3977,13 @@
** Discard the contents of the pending terms table.
*/
static int fts3RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){
- Fts3Table *p = (Fts3Table*)pVtab;
+ Fts3Table *pTab = (Fts3Table*)pVtab;
UNUSED_PARAMETER(iSavepoint);
- assert( p->inTransaction );
- TESTONLY( p->mxSavepoint = iSavepoint );
- sqlite3Fts3PendingTermsClear(p);
+ assert( pTab->inTransaction );
+ TESTONLY( pTab->mxSavepoint = iSavepoint );
+ if( (iSavepoint+1)<=pTab->iSavepoint ){
+ sqlite3Fts3PendingTermsClear(pTab);
+ }
return SQLITE_OK;
}
@@ -3980,8 +4002,49 @@
return 0;
}
+/*
+** Implementation of the xIntegrity() method on the FTS3/FTS4 virtual
+** table.
+*/
+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 */
+ int isQuick, /* True if this is a quick_check */
+ char **pzErr /* Write error message here */
+){
+ Fts3Table *p = (Fts3Table*)pVtab;
+ char *zSql;
+ int rc;
+ char *zErr = 0;
+
+ assert( pzErr!=0 );
+ assert( *pzErr==0 );
+ UNUSED_PARAMETER(isQuick);
+ 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);
+ }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);
+ }
+ sqlite3_free(zErr);
+ return SQLITE_OK;
+}
+
+
+
static const sqlite3_module fts3Module = {
- /* iVersion */ 3,
+ /* iVersion */ 4,
/* xCreate */ fts3CreateMethod,
/* xConnect */ fts3ConnectMethod,
/* xBestIndex */ fts3BestIndexMethod,
@@ -4005,6 +4068,7 @@
/* xRelease */ fts3ReleaseMethod,
/* xRollbackTo */ fts3RollbackToMethod,
/* xShadowName */ fts3ShadowName,
+ /* xIntegrity */ fts3Integrity,
};
/*
diff -Nru sqlite3-3.41.0-0/ext/fts3/fts3Int.h sqlite3-3.44.0-0/ext/fts3/fts3Int.h
--- sqlite3-3.41.0-0/ext/fts3/fts3Int.h 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts3/fts3Int.h 2023-11-04 14:24:27.000000000 +0000
@@ -265,6 +265,7 @@
int nPgsz; /* Page size for host database */
char *zSegmentsTbl; /* Name of %_segments table */
sqlite3_blob *pSegments; /* Blob handle open on %_segments table */
+ int iSavepoint;
/*
** The following array of hash tables is used to buffer pending index
diff -Nru sqlite3-3.41.0-0/ext/fts3/fts3_aux.c sqlite3-3.44.0-0/ext/fts3/fts3_aux.c
--- sqlite3-3.41.0-0/ext/fts3/fts3_aux.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts3/fts3_aux.c 2023-11-04 14:24:27.000000000 +0000
@@ -545,7 +545,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
int rc; /* Return code */
diff -Nru sqlite3-3.41.0-0/ext/fts3/fts3_term.c sqlite3-3.44.0-0/ext/fts3/fts3_term.c
--- sqlite3-3.41.0-0/ext/fts3/fts3_term.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts3/fts3_term.c 2023-11-04 14:24:27.000000000 +0000
@@ -362,7 +362,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
int rc; /* Return code */
diff -Nru sqlite3-3.41.0-0/ext/fts3/fts3_tokenize_vtab.c sqlite3-3.44.0-0/ext/fts3/fts3_tokenize_vtab.c
--- sqlite3-3.41.0-0/ext/fts3/fts3_tokenize_vtab.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts3/fts3_tokenize_vtab.c 2023-11-04 14:24:27.000000000 +0000
@@ -445,7 +445,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
int rc; /* Return code */
diff -Nru sqlite3-3.41.0-0/ext/fts3/fts3_write.c sqlite3-3.44.0-0/ext/fts3/fts3_write.c
--- sqlite3-3.41.0-0/ext/fts3/fts3_write.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts3/fts3_write.c 2023-11-04 14:24:27.000000000 +0000
@@ -2667,16 +2667,18 @@
char *pList,
i64 nList
){
- if( nList>pMsr->nBuffer ){
+ if( (nList+FTS3_NODE_PADDING)>pMsr->nBuffer ){
char *pNew;
- pMsr->nBuffer = nList*2;
- pNew = (char *)sqlite3_realloc64(pMsr->aBuffer, pMsr->nBuffer);
+ int nNew = nList*2 + FTS3_NODE_PADDING;
+ pNew = (char *)sqlite3_realloc64(pMsr->aBuffer, nNew);
if( !pNew ) return SQLITE_NOMEM;
pMsr->aBuffer = pNew;
+ pMsr->nBuffer = nNew;
}
assert( nList>0 );
memcpy(pMsr->aBuffer, pList, nList);
+ memset(&pMsr->aBuffer[nList], 0, FTS3_NODE_PADDING);
return SQLITE_OK;
}
@@ -3323,7 +3325,6 @@
rc = fts3SegmentMerge(p, p->iPrevLangid, i, FTS3_SEGCURSOR_PENDING);
if( rc==SQLITE_DONE ) rc = SQLITE_OK;
}
- sqlite3Fts3PendingTermsClear(p);
/* Determine the auto-incr-merge setting if unknown. If enabled,
** estimate the number of leaf blocks of content to be written
@@ -3345,6 +3346,10 @@
rc = sqlite3_reset(pStmt);
}
}
+
+ if( rc==SQLITE_OK ){
+ sqlite3Fts3PendingTermsClear(p);
+ }
return rc;
}
@@ -3976,6 +3981,8 @@
blobGrowBuffer(pPrev, nTerm, &rc);
if( rc!=SQLITE_OK ) return rc;
+ assert( pPrev!=0 );
+ assert( pPrev->a!=0 );
nPrefix = fts3PrefixCompress(pPrev->a, pPrev->n, zTerm, nTerm);
nSuffix = nTerm - nPrefix;
@@ -4032,9 +4039,13 @@
nSpace += sqlite3Fts3VarintLen(nDoclist) + nDoclist;
/* If the current block is not empty, and if adding this term/doclist
- ** to the current block would make it larger than Fts3Table.nNodeSize
- ** bytes, write this block out to the database. */
- if( pLeaf->block.n>0 && (pLeaf->block.n + nSpace)>p->nNodeSize ){
+ ** to the current block would make it larger than Fts3Table.nNodeSize bytes,
+ ** and if there is still room for another leaf page, write this block out to
+ ** the database. */
+ if( pLeaf->block.n>0
+ && (pLeaf->block.n + nSpace)>p->nNodeSize
+ && pLeaf->iBlock < (pWriter->iStart + pWriter->nLeafEst)
+ ){
rc = fts3WriteSegment(p, pLeaf->iBlock, pLeaf->block.a, pLeaf->block.n);
pWriter->nWork++;
@@ -4345,6 +4356,7 @@
for(i=nHeight; i>=0 && rc==SQLITE_OK; i--){
NodeReader reader;
+ memset(&reader, 0, sizeof(reader));
pNode = &pWriter->aNodeWriter[i];
if( pNode->block.a){
@@ -4365,7 +4377,7 @@
rc = sqlite3Fts3ReadBlock(p, reader.iChild, &aBlock, &nBlock,0);
blobGrowBuffer(&pNode->block,
MAX(nBlock, p->nNodeSize)+FTS3_NODE_PADDING, &rc
- );
+ );
if( rc==SQLITE_OK ){
memcpy(pNode->block.a, aBlock, nBlock);
pNode->block.n = nBlock;
@@ -5215,7 +5227,7 @@
int rc;
u64 cksum = 0;
- assert( *pRc==SQLITE_OK );
+ if( *pRc ) return 0;
memset(&filter, 0, sizeof(filter));
memset(&csr, 0, sizeof(csr));
@@ -5430,8 +5442,11 @@
rc = fts3DoIncrmerge(p, &zVal[6]);
}else if( nVal>10 && 0==sqlite3_strnicmp(zVal, "automerge=", 10) ){
rc = fts3DoAutoincrmerge(p, &zVal[10]);
+ }else if( nVal==5 && 0==sqlite3_strnicmp(zVal, "flush", 5) ){
+ rc = sqlite3Fts3PendingTermsFlush(p);
+ }
#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
- }else{
+ else{
int v;
if( nVal>9 && 0==sqlite3_strnicmp(zVal, "nodesize=", 9) ){
v = atoi(&zVal[9]);
@@ -5449,8 +5464,8 @@
if( v>=4 && v<=FTS3_MERGE_COUNT && (v&1)==0 ) p->nMergeCount = v;
rc = SQLITE_OK;
}
-#endif
}
+#endif
return rc;
}
diff -Nru sqlite3-3.41.0-0/ext/fts5/fts5.h sqlite3-3.44.0-0/ext/fts5/fts5.h
--- sqlite3-3.41.0-0/ext/fts5/fts5.h 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5.h 2023-11-04 14:24:27.000000000 +0000
@@ -263,7 +263,7 @@
** See xPhraseFirstColumn above.
*/
struct Fts5ExtensionApi {
- int iVersion; /* Currently always set to 3 */
+ int iVersion; /* Currently always set to 2 */
void *(*xUserData)(Fts5Context*);
@@ -492,8 +492,8 @@
** as separate queries of the FTS index are required for each synonym.
**
** When using methods (2) or (3), it is important that the tokenizer only
-** provide synonyms when tokenizing document text (method (2)) or query
-** text (method (3)), not both. Doing so will not cause any errors, but is
+** provide synonyms when tokenizing document text (method (3)) or query
+** text (method (2)), not both. Doing so will not cause any errors, but is
** inefficient.
*/
typedef struct Fts5Tokenizer Fts5Tokenizer;
@@ -541,7 +541,7 @@
int (*xCreateTokenizer)(
fts5_api *pApi,
const char *zName,
- void *pContext,
+ void *pUserData,
fts5_tokenizer *pTokenizer,
void (*xDestroy)(void*)
);
@@ -550,7 +550,7 @@
int (*xFindTokenizer)(
fts5_api *pApi,
const char *zName,
- void **ppContext,
+ void **ppUserData,
fts5_tokenizer *pTokenizer
);
@@ -558,7 +558,7 @@
int (*xCreateFunction)(
fts5_api *pApi,
const char *zName,
- void *pContext,
+ void *pUserData,
fts5_extension_function xFunction,
void (*xDestroy)(void*)
);
diff -Nru sqlite3-3.41.0-0/ext/fts5/fts5Int.h sqlite3-3.44.0-0/ext/fts5/fts5Int.h
--- sqlite3-3.41.0-0/ext/fts5/fts5Int.h 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5Int.h 2023-11-04 14:24:27.000000000 +0000
@@ -154,6 +154,10 @@
** attempt to merge together. A value of 1 sets the object to use the
** compile time default. Zero disables auto-merge altogether.
**
+** bContentlessDelete:
+** True if the contentless_delete option was present in the CREATE
+** VIRTUAL TABLE statement.
+**
** zContent:
**
** zContentRowid:
@@ -188,6 +192,7 @@
int nPrefix; /* Number of prefix indexes */
int *aPrefix; /* Sizes in bytes of nPrefix prefix indexes */
int eContent; /* An FTS5_CONTENT value */
+ int bContentlessDelete; /* "contentless_delete=" option (dflt==0) */
char *zContent; /* content table */
char *zContentRowid; /* "content_rowid=" option value */
int bColumnsize; /* "columnsize=" option value (dflt==1) */
@@ -199,6 +204,7 @@
int ePattern; /* FTS_PATTERN_XXX constant */
/* Values loaded from the %_config table */
+ int iVersion; /* fts5 file format 'version' */
int iCookie; /* Incremented when %_config is modified */
int pgsz; /* Approximate page size used in %_data */
int nAutomerge; /* 'automerge' setting */
@@ -207,6 +213,8 @@
int nHashSize; /* Bytes of memory for in-memory hash */
char *zRank; /* Name of rank function */
char *zRankArgs; /* Arguments to rank function */
+ int bSecureDelete; /* 'secure-delete' */
+ int nDeleteMerge; /* 'deletemerge' */
/* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */
char **pzErrmsg;
@@ -216,8 +224,11 @@
#endif
};
-/* Current expected value of %_config table 'version' field */
-#define FTS5_CURRENT_VERSION 4
+/* Current expected value of %_config table 'version' field. And
+** the expected version if the 'secure-delete' option has ever been
+** set on the table. */
+#define FTS5_CURRENT_VERSION 4
+#define FTS5_CURRENT_VERSION_SECUREDELETE 5
#define FTS5_CONTENT_NORMAL 0
#define FTS5_CONTENT_NONE 1
@@ -383,6 +394,7 @@
** above. */
#define FTS5INDEX_QUERY_SKIPEMPTY 0x0010
#define FTS5INDEX_QUERY_NOOUTPUT 0x0020
+#define FTS5INDEX_QUERY_SKIPHASH 0x0040
/*
** Create/destroy an Fts5Index object.
@@ -525,6 +537,9 @@
int sqlite3Fts5IndexLoadConfig(Fts5Index *p);
+int sqlite3Fts5IndexGetOrigin(Fts5Index *p, i64 *piOrigin);
+int sqlite3Fts5IndexContentlessDelete(Fts5Index *p, i64 iOrigin, i64 iRowid);
+
/*
** End of interface to code in fts5_index.c.
**************************************************************************/
@@ -537,7 +552,7 @@
u8 sqlite3Fts5GetVarint(const unsigned char*, u64*);
int sqlite3Fts5PutVarint(unsigned char *p, u64 v);
-#define fts5GetVarint32(a,b) sqlite3Fts5GetVarint32(a,(u32*)&b)
+#define fts5GetVarint32(a,b) sqlite3Fts5GetVarint32(a,(u32*)&(b))
#define fts5GetVarint sqlite3Fts5GetVarint
#define fts5FastGetVarint32(a, iOff, nVal) { \
@@ -609,6 +624,11 @@
*/
void sqlite3Fts5HashClear(Fts5Hash*);
+/*
+** Return true if the hash is empty, false otherwise.
+*/
+int sqlite3Fts5HashIsEmpty(Fts5Hash*);
+
int sqlite3Fts5HashQuery(
Fts5Hash*, /* Hash table to query */
int nPre,
@@ -630,6 +650,7 @@
);
+
/*
** End of interface to code in fts5_hash.c.
**************************************************************************/
diff -Nru sqlite3-3.41.0-0/ext/fts5/fts5_aux.c sqlite3-3.44.0-0/ext/fts5/fts5_aux.c
--- sqlite3-3.41.0-0/ext/fts5/fts5_aux.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5_aux.c 2023-11-04 14:24:27.000000000 +0000
@@ -110,15 +110,19 @@
*/
typedef struct HighlightContext HighlightContext;
struct HighlightContext {
- CInstIter iter; /* Coalesced Instance Iterator */
- int iPos; /* Current token offset in zIn[] */
+ /* Constant parameters to fts5HighlightCb() */
int iRangeStart; /* First token to include */
int iRangeEnd; /* If non-zero, last token to include */
const char *zOpen; /* Opening highlight */
const char *zClose; /* Closing highlight */
const char *zIn; /* Input text */
int nIn; /* Size of input text in bytes */
- int iOff; /* Current offset within zIn[] */
+
+ /* Variables modified by fts5HighlightCb() */
+ CInstIter iter; /* Coalesced Instance Iterator */
+ int iPos; /* Current token offset in zIn[] */
+ int iOff; /* Have copied up to this offset in zIn[] */
+ int bOpen; /* True if highlight is open */
char *zOut; /* Output value */
};
@@ -151,8 +155,8 @@
int tflags, /* Mask of FTS5_TOKEN_* flags */
const char *pToken, /* Buffer containing token */
int nToken, /* Size of token in bytes */
- int iStartOff, /* Start offset of token */
- int iEndOff /* End offset of token */
+ int iStartOff, /* Start byte offset of token */
+ int iEndOff /* End byte offset of token */
){
HighlightContext *p = (HighlightContext*)pContext;
int rc = SQLITE_OK;
@@ -163,35 +167,52 @@
if( tflags & FTS5_TOKEN_COLOCATED ) return SQLITE_OK;
iPos = p->iPos++;
- if( p->iRangeEnd>0 ){
+ if( p->iRangeEnd>=0 ){
if( iPosiRangeStart || iPos>p->iRangeEnd ) return SQLITE_OK;
if( p->iRangeStart && iPos==p->iRangeStart ) p->iOff = iStartOff;
}
- if( iPos==p->iter.iStart ){
+ /* If the parenthesis is open, and this token is not part of the current
+ ** phrase, and the starting byte offset of this token is past the point
+ ** that has currently been copied into the output buffer, close the
+ ** parenthesis. */
+ if( p->bOpen
+ && (iPos<=p->iter.iStart || p->iter.iStart<0)
+ && iStartOff>p->iOff
+ ){
+ fts5HighlightAppend(&rc, p, p->zClose, -1);
+ p->bOpen = 0;
+ }
+
+ /* If this is the start of a new phrase, and the highlight is not open:
+ **
+ ** * copy text from the input up to the start of the phrase, and
+ ** * open the highlight.
+ */
+ if( iPos==p->iter.iStart && p->bOpen==0 ){
fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iStartOff - p->iOff);
fts5HighlightAppend(&rc, p, p->zOpen, -1);
p->iOff = iStartOff;
+ p->bOpen = 1;
}
if( iPos==p->iter.iEnd ){
- if( p->iRangeEnd && p->iter.iStartiRangeStart ){
+ if( p->bOpen==0 ){
+ assert( p->iRangeEnd>=0 );
fts5HighlightAppend(&rc, p, p->zOpen, -1);
+ p->bOpen = 1;
}
fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff);
- fts5HighlightAppend(&rc, p, p->zClose, -1);
p->iOff = iEndOff;
+
if( rc==SQLITE_OK ){
rc = fts5CInstIterNext(&p->iter);
}
}
- if( p->iRangeEnd>0 && iPos==p->iRangeEnd ){
+ if( iPos==p->iRangeEnd ){
fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff);
p->iOff = iEndOff;
- if( iPos>=p->iter.iStart && iPositer.iEnd ){
- fts5HighlightAppend(&rc, p, p->zClose, -1);
- }
}
return rc;
@@ -221,6 +242,7 @@
memset(&ctx, 0, sizeof(HighlightContext));
ctx.zOpen = (const char*)sqlite3_value_text(apVal[1]);
ctx.zClose = (const char*)sqlite3_value_text(apVal[2]);
+ ctx.iRangeEnd = -1;
rc = pApi->xColumnText(pFts, iCol, &ctx.zIn, &ctx.nIn);
if( ctx.zIn ){
@@ -231,6 +253,9 @@
if( rc==SQLITE_OK ){
rc = pApi->xTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb);
}
+ if( ctx.bOpen ){
+ fts5HighlightAppend(&rc, &ctx, ctx.zClose, -1);
+ }
fts5HighlightAppend(&rc, &ctx, &ctx.zIn[ctx.iOff], ctx.nIn - ctx.iOff);
if( rc==SQLITE_OK ){
@@ -406,6 +431,7 @@
iCol = sqlite3_value_int(apVal[0]);
ctx.zOpen = fts5ValueToText(apVal[1]);
ctx.zClose = fts5ValueToText(apVal[2]);
+ ctx.iRangeEnd = -1;
zEllips = fts5ValueToText(apVal[3]);
nToken = sqlite3_value_int(apVal[4]);
@@ -508,6 +534,9 @@
if( rc==SQLITE_OK ){
rc = pApi->xTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb);
}
+ if( ctx.bOpen ){
+ fts5HighlightAppend(&rc, &ctx, ctx.zClose, -1);
+ }
if( ctx.iRangeEnd>=(nColSize-1) ){
fts5HighlightAppend(&rc, &ctx, &ctx.zIn[ctx.iOff], ctx.nIn - ctx.iOff);
}else{
diff -Nru sqlite3-3.41.0-0/ext/fts5/fts5_config.c sqlite3-3.44.0-0/ext/fts5/fts5_config.c
--- sqlite3-3.41.0-0/ext/fts5/fts5_config.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5_config.c 2023-11-04 14:24:27.000000000 +0000
@@ -22,6 +22,8 @@
#define FTS5_DEFAULT_CRISISMERGE 16
#define FTS5_DEFAULT_HASHSIZE (1024*1024)
+#define FTS5_DEFAULT_DELETE_AUTOMERGE 10 /* default 10% */
+
/* Maximum allowed page size */
#define FTS5_MAX_PAGE_SIZE (64*1024)
@@ -352,6 +354,16 @@
return rc;
}
+ if( sqlite3_strnicmp("contentless_delete", zCmd, nCmd)==0 ){
+ if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){
+ *pzErr = sqlite3_mprintf("malformed contentless_delete=... directive");
+ rc = SQLITE_ERROR;
+ }else{
+ pConfig->bContentlessDelete = (zArg[0]=='1');
+ }
+ return rc;
+ }
+
if( sqlite3_strnicmp("content_rowid", zCmd, nCmd)==0 ){
if( pConfig->zContentRowid ){
*pzErr = sqlite3_mprintf("multiple content_rowid=... directives");
@@ -550,6 +562,7 @@
rc = SQLITE_ERROR;
}
+ assert( (pRet->abUnindexed && pRet->azCol) || rc!=SQLITE_OK );
for(i=3; rc==SQLITE_OK && ibContentlessDelete
+ && pRet->eContent!=FTS5_CONTENT_NONE
+ ){
+ *pzErr = sqlite3_mprintf(
+ "contentless_delete=1 requires a contentless table"
+ );
+ rc = SQLITE_ERROR;
+ }
+
+ /* We only allow contentless_delete=1 if columnsize=0 is not present.
+ **
+ ** This restriction may be removed at some point.
+ */
+ if( rc==SQLITE_OK && pRet->bContentlessDelete && pRet->bColumnsize==0 ){
+ *pzErr = sqlite3_mprintf(
+ "contentless_delete=1 is incompatible with columnsize=0"
+ );
+ rc = SQLITE_ERROR;
+ }
+
/* If a tokenizer= option was successfully parsed, the tokenizer has
** already been allocated. Otherwise, allocate an instance of the default
** tokenizer (unicode61) now. */
@@ -889,6 +924,18 @@
}
}
+ else if( 0==sqlite3_stricmp(zKey, "deletemerge") ){
+ int nVal = -1;
+ if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
+ nVal = sqlite3_value_int(pVal);
+ }else{
+ *pbBadkey = 1;
+ }
+ if( nVal<0 ) nVal = FTS5_DEFAULT_DELETE_AUTOMERGE;
+ if( nVal>100 ) nVal = 0;
+ pConfig->nDeleteMerge = nVal;
+ }
+
else if( 0==sqlite3_stricmp(zKey, "rank") ){
const char *zIn = (const char*)sqlite3_value_text(pVal);
char *zRank;
@@ -903,6 +950,18 @@
rc = SQLITE_OK;
*pbBadkey = 1;
}
+ }
+
+ else if( 0==sqlite3_stricmp(zKey, "secure-delete") ){
+ int bVal = -1;
+ if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
+ bVal = sqlite3_value_int(pVal);
+ }
+ if( bVal<0 ){
+ *pbBadkey = 1;
+ }else{
+ pConfig->bSecureDelete = (bVal ? 1 : 0);
+ }
}else{
*pbBadkey = 1;
}
@@ -925,6 +984,7 @@
pConfig->nUsermerge = FTS5_DEFAULT_USERMERGE;
pConfig->nCrisisMerge = FTS5_DEFAULT_CRISISMERGE;
pConfig->nHashSize = FTS5_DEFAULT_HASHSIZE;
+ pConfig->nDeleteMerge = FTS5_DEFAULT_DELETE_AUTOMERGE;
zSql = sqlite3Fts5Mprintf(&rc, zSelect, pConfig->zDb, pConfig->zName);
if( zSql ){
@@ -947,15 +1007,20 @@
rc = sqlite3_finalize(p);
}
- if( rc==SQLITE_OK && iVersion!=FTS5_CURRENT_VERSION ){
+ if( rc==SQLITE_OK
+ && iVersion!=FTS5_CURRENT_VERSION
+ && iVersion!=FTS5_CURRENT_VERSION_SECUREDELETE
+ ){
rc = SQLITE_ERROR;
if( pConfig->pzErrmsg ){
assert( 0==*pConfig->pzErrmsg );
- *pConfig->pzErrmsg = sqlite3_mprintf(
- "invalid fts5 file format (found %d, expected %d) - run 'rebuild'",
- iVersion, FTS5_CURRENT_VERSION
+ *pConfig->pzErrmsg = sqlite3_mprintf("invalid fts5 file format "
+ "(found %d, expected %d or %d) - run 'rebuild'",
+ iVersion, FTS5_CURRENT_VERSION, FTS5_CURRENT_VERSION_SECUREDELETE
);
}
+ }else{
+ pConfig->iVersion = iVersion;
}
if( rc==SQLITE_OK ){
diff -Nru sqlite3-3.41.0-0/ext/fts5/fts5_expr.c sqlite3-3.44.0-0/ext/fts5/fts5_expr.c
--- sqlite3-3.41.0-0/ext/fts5/fts5_expr.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5_expr.c 2023-11-04 14:24:27.000000000 +0000
@@ -17,6 +17,10 @@
#include "fts5Int.h"
#include "fts5parse.h"
+#ifndef SQLITE_FTS5_MAX_EXPR_DEPTH
+# define SQLITE_FTS5_MAX_EXPR_DEPTH 256
+#endif
+
/*
** All token types in the generated fts5parse.h file are greater than 0.
*/
@@ -57,11 +61,17 @@
** FTS5_NOT (nChild, apChild valid)
** FTS5_STRING (pNear valid)
** FTS5_TERM (pNear valid)
+**
+** iHeight:
+** Distance from this node to furthest leaf. This is always 0 for nodes
+** of type FTS5_STRING and FTS5_TERM. For all other nodes it is one
+** greater than the largest child value.
*/
struct Fts5ExprNode {
int eType; /* Node type */
int bEof; /* True at EOF */
int bNomatch; /* True if entry is not a match */
+ int iHeight; /* Distance to tree leaf nodes */
/* Next method for this node. */
int (*xNext)(Fts5Expr*, Fts5ExprNode*, int, i64);
@@ -131,6 +141,31 @@
int bPhraseToAnd; /* Convert "a+b" to "a AND b" */
};
+/*
+** Check that the Fts5ExprNode.iHeight variables are set correctly in
+** the expression tree passed as the only argument.
+*/
+#ifndef NDEBUG
+static void assert_expr_depth_ok(int rc, Fts5ExprNode *p){
+ if( rc==SQLITE_OK ){
+ if( p->eType==FTS5_TERM || p->eType==FTS5_STRING || p->eType==0 ){
+ assert( p->iHeight==0 );
+ }else{
+ int ii;
+ int iMaxChild = 0;
+ for(ii=0; iinChild; ii++){
+ Fts5ExprNode *pChild = p->apChild[ii];
+ iMaxChild = MAX(iMaxChild, pChild->iHeight);
+ assert_expr_depth_ok(SQLITE_OK, pChild);
+ }
+ assert( p->iHeight==iMaxChild+1 );
+ }
+ }
+}
+#else
+# define assert_expr_depth_ok(rc, p)
+#endif
+
void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...){
va_list ap;
va_start(ap, zFmt);
@@ -245,6 +280,8 @@
}while( sParse.rc==SQLITE_OK && t!=FTS5_EOF );
sqlite3Fts5ParserFree(pEngine, fts5ParseFree);
+ assert_expr_depth_ok(sParse.rc, sParse.pExpr);
+
/* If the LHS of the MATCH expression was a user column, apply the
** implicit column-filter. */
if( iColnCol && sParse.pExpr && sParse.rc==SQLITE_OK ){
@@ -407,7 +444,7 @@
Fts5Parse sParse;
memset(&sParse, 0, sizeof(sParse));
- if( *pp1 ){
+ if( *pp1 && p2 ){
Fts5Expr *p1 = *pp1;
int nPhrase = p1->nPhrase + p2->nPhrase;
@@ -432,7 +469,7 @@
}
sqlite3_free(p2->apExprPhrase);
sqlite3_free(p2);
- }else{
+ }else if( p2 ){
*pp1 = p2;
}
@@ -2206,6 +2243,7 @@
}
static void fts5ExprAddChildren(Fts5ExprNode *p, Fts5ExprNode *pSub){
+ int ii = p->nChild;
if( p->eType!=FTS5_NOT && pSub->eType==p->eType ){
int nByte = sizeof(Fts5ExprNode*) * pSub->nChild;
memcpy(&p->apChild[p->nChild], pSub->apChild, nByte);
@@ -2214,6 +2252,9 @@
}else{
p->apChild[p->nChild++] = pSub;
}
+ for( ; iinChild; ii++){
+ p->iHeight = MAX(p->iHeight, p->apChild[ii]->iHeight + 1);
+ }
}
/*
@@ -2244,6 +2285,7 @@
if( pRet ){
pRet->eType = FTS5_AND;
pRet->nChild = nTerm;
+ pRet->iHeight = 1;
fts5ExprAssignXNext(pRet);
pParse->nPhrase--;
for(ii=0; iiiHeight>SQLITE_FTS5_MAX_EXPR_DEPTH ){
+ sqlite3Fts5ParseError(pParse,
+ "fts5 expression tree is too large (maximum depth %d)",
+ SQLITE_FTS5_MAX_EXPR_DEPTH
+ );
+ sqlite3_free(pRet);
+ pRet = 0;
+ }
}
}
}
@@ -2427,7 +2477,7 @@
return pRet;
}
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){
sqlite3_int64 nByte = 0;
Fts5ExprTerm *p;
@@ -2533,6 +2583,8 @@
if( zRet==0 ) return 0;
}
+ }else if( pExpr->eType==0 ){
+ zRet = sqlite3_mprintf("{}");
}else{
char const *zOp = 0;
int i;
@@ -2794,14 +2846,14 @@
sqlite3_result_int(pCtx, sqlite3Fts5UnicodeFold(iCode, bRemoveDiacritics));
}
}
-#endif /* ifdef SQLITE_TEST */
+#endif /* if SQLITE_TEST || SQLITE_FTS5_DEBUG */
/*
** This is called during initialization to register the fts5_expr() scalar
** UDF with the SQLite handle passed as the only argument.
*/
int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
struct Fts5ExprFunc {
const char *z;
void (*x)(sqlite3_context*,int,sqlite3_value**);
diff -Nru sqlite3-3.41.0-0/ext/fts5/fts5_hash.c sqlite3-3.44.0-0/ext/fts5/fts5_hash.c
--- sqlite3-3.41.0-0/ext/fts5/fts5_hash.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5_hash.c 2023-11-04 14:24:27.000000000 +0000
@@ -475,7 +475,6 @@
pList = fts5HashEntryMerge(pList, ap[i]);
}
- pHash->nEntry = 0;
sqlite3_free(ap);
*ppSorted = pList;
return SQLITE_OK;
@@ -529,6 +528,28 @@
return fts5HashEntrySort(p, pTerm, nTerm, &p->pScan);
}
+#ifdef SQLITE_DEBUG
+static int fts5HashCount(Fts5Hash *pHash){
+ int nEntry = 0;
+ int ii;
+ for(ii=0; iinSlot; ii++){
+ Fts5HashEntry *p = 0;
+ for(p=pHash->aSlot[ii]; p; p=p->pHashNext){
+ nEntry++;
+ }
+ }
+ return nEntry;
+}
+#endif
+
+/*
+** Return true if the hash table is empty, false otherwise.
+*/
+int sqlite3Fts5HashIsEmpty(Fts5Hash *pHash){
+ assert( pHash->nEntry==fts5HashCount(pHash) );
+ return pHash->nEntry==0;
+}
+
void sqlite3Fts5HashScanNext(Fts5Hash *p){
assert( !sqlite3Fts5HashScanEof(p) );
p->pScan = p->pScan->pScanNext;
diff -Nru sqlite3-3.41.0-0/ext/fts5/fts5_index.c sqlite3-3.44.0-0/ext/fts5/fts5_index.c
--- sqlite3-3.41.0-0/ext/fts5/fts5_index.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5_index.c 2023-11-04 14:24:27.000000000 +0000
@@ -57,13 +57,31 @@
#define FTS5_MAX_LEVEL 64
/*
+** There are two versions of the format used for the structure record:
+**
+** 1. the legacy format, that may be read by all fts5 versions, and
+**
+** 2. the V2 format, which is used by contentless_delete=1 databases.
+**
+** Both begin with a 4-byte "configuration cookie" value. Then, a legacy
+** format structure record contains a varint - the number of levels in
+** the structure. Whereas a V2 structure record contains the constant
+** 4 bytes [0xff 0x00 0x00 0x01]. This is unambiguous as the value of a
+** varint has to be at least 16256 to begin with "0xFF". And the default
+** maximum number of levels is 64.
+**
+** See below for more on structure record formats.
+*/
+#define FTS5_STRUCTURE_V2 "\xFF\x00\x00\x01"
+
+/*
** Details:
**
** The %_data table managed by this module,
**
** CREATE TABLE %_data(id INTEGER PRIMARY KEY, block BLOB);
**
-** , contains the following 5 types of records. See the comments surrounding
+** , contains the following 6 types of records. See the comments surrounding
** the FTS5_*_ROWID macros below for a description of how %_data rowids are
** assigned to each fo them.
**
@@ -71,13 +89,13 @@
**
** The set of segments that make up an index - the index structure - are
** recorded in a single record within the %_data table. The record consists
-** of a single 32-bit configuration cookie value followed by a list of
-** SQLite varints. If the FTS table features more than one index (because
-** there are one or more prefix indexes), it is guaranteed that all share
-** the same cookie value.
+** of a single 32-bit configuration cookie value followed by a list of
+** SQLite varints.
**
-** Immediately following the configuration cookie, the record begins with
-** three varints:
+** If the structure record is a V2 record, the configuration cookie is
+** followed by the following 4 bytes: [0xFF 0x00 0x00 0x01].
+**
+** Next, the record continues with three varints:
**
** + number of levels,
** + total number of segments on all levels,
@@ -92,6 +110,12 @@
** + first leaf page number (often 1, always greater than 0)
** + final leaf page number
**
+** Then, for V2 structures only:
+**
+** + lower origin counter value,
+** + upper origin counter value,
+** + the number of tombstone hash pages.
+**
** 2. The Averages Record:
**
** A single record within the %_data table. The data is a list of varints.
@@ -207,6 +231,38 @@
** * A list of delta-encoded varints - the first rowid on each subsequent
** child page.
**
+** 6. Tombstone Hash Page
+**
+** These records are only ever present in contentless_delete=1 tables.
+** There are zero or more of these associated with each segment. They
+** are used to store the tombstone rowids for rows contained in the
+** associated segments.
+**
+** The set of nHashPg tombstone hash pages associated with a single
+** segment together form a single hash table containing tombstone rowids.
+** To find the page of the hash on which a key might be stored:
+**
+** iPg = (rowid % nHashPg)
+**
+** Then, within page iPg, which has nSlot slots:
+**
+** iSlot = (rowid / nHashPg) % nSlot
+**
+** Each tombstone hash page begins with an 8 byte header:
+**
+** 1-byte: Key-size (the size in bytes of each slot). Either 4 or 8.
+** 1-byte: rowid-0-tombstone flag. This flag is only valid on the
+** first tombstone hash page for each segment (iPg=0). If set,
+** the hash table contains rowid 0. If clear, it does not.
+** Rowid 0 is handled specially.
+** 2-bytes: unused.
+** 4-bytes: Big-endian integer containing number of entries on page.
+**
+** Following this are nSlot 4 or 8 byte slots (depending on the key-size
+** in the first byte of the page header). The number of slots may be
+** determined based on the size of the page record and the key-size:
+**
+** nSlot = (nByte - 8) / key-size
*/
/*
@@ -240,6 +296,7 @@
#define FTS5_SEGMENT_ROWID(segid, pgno) fts5_dri(segid, 0, 0, pgno)
#define FTS5_DLIDX_ROWID(segid, height, pgno) fts5_dri(segid, 1, height, pgno)
+#define FTS5_TOMBSTONE_ROWID(segid,ipg) fts5_dri(segid+(1<<16), 0, 0, ipg)
#ifdef SQLITE_DEBUG
int sqlite3Fts5Corrupt() { return SQLITE_CORRUPT_VTAB; }
@@ -275,6 +332,12 @@
/*
** One object per %_data table.
+**
+** nContentlessDelete:
+** The number of contentless delete operations since the most recent
+** call to fts5IndexFlush() or fts5IndexDiscardData(). This is tracked
+** so that extra auto-merge work can be done by fts5IndexFlush() to
+** account for the delete operations.
*/
struct Fts5Index {
Fts5Config *pConfig; /* Virtual table configuration */
@@ -289,6 +352,8 @@
int nPendingData; /* Current bytes of pending data */
i64 iWriteRowid; /* Rowid for current doc being written */
int bDelete; /* Current write is a delete */
+ int nContentlessDelete; /* Number of contentless delete ops */
+ int nPendingRow; /* Number of INSERT in hash table */
/* Error state. */
int rc; /* Current error code */
@@ -302,6 +367,8 @@
sqlite3_stmt *pIdxSelect;
int nRead; /* Total number of blocks read */
+ sqlite3_stmt *pDeleteFromIdx;
+
sqlite3_stmt *pDataVersion;
i64 iStructVersion; /* data_version when pStruct read */
Fts5Structure *pStruct; /* Current db structure (or NULL) */
@@ -321,11 +388,23 @@
** The contents of the "structure" record for each index are represented
** using an Fts5Structure record in memory. Which uses instances of the
** other Fts5StructureXXX types as components.
+**
+** nOriginCntr:
+** This value is set to non-zero for structure records created for
+** contentlessdelete=1 tables only. In that case it represents the
+** origin value to apply to the next top-level segment created.
*/
struct Fts5StructureSegment {
int iSegid; /* Segment id */
int pgnoFirst; /* First leaf page number in segment */
int pgnoLast; /* Last leaf page number in segment */
+
+ /* contentlessdelete=1 tables only: */
+ u64 iOrigin1;
+ u64 iOrigin2;
+ int nPgTombstone; /* Number of tombstone hash table pages */
+ u64 nEntryTombstone; /* Number of tombstone entries that "count" */
+ u64 nEntry; /* Number of rows in this segment */
};
struct Fts5StructureLevel {
int nMerge; /* Number of segments in incr-merge */
@@ -335,6 +414,7 @@
struct Fts5Structure {
int nRef; /* Object reference count */
u64 nWriteCounter; /* Total leaves written to level 0 */
+ u64 nOriginCntr; /* Origin value for next top-level segment */
int nSegment; /* Total segments in this structure */
int nLevel; /* Number of levels in this index */
Fts5StructureLevel aLevel[1]; /* Array of nLevel level objects */
@@ -394,9 +474,6 @@
** iLeafOffset:
** Byte offset within the current leaf that is the first byte of the
** position list data (one byte passed the position-list size field).
-** rowid field of the current entry. Usually this is the size field of the
-** position list data. The exception is if the rowid for the current entry
-** is the last thing on the leaf page.
**
** pLeaf:
** Buffer containing current leaf page data. Set to NULL at EOF.
@@ -426,6 +503,13 @@
**
** iTermIdx:
** Index of current term on iTermLeafPgno.
+**
+** apTombstone/nTombstone:
+** These are used for contentless_delete=1 tables only. When the cursor
+** is first allocated, the apTombstone[] array is allocated so that it
+** is large enough for all tombstones hash pages associated with the
+** segment. The pages themselves are loaded lazily from the database as
+** they are required.
*/
struct Fts5SegIter {
Fts5StructureSegment *pSeg; /* Segment to iterate through */
@@ -434,6 +518,8 @@
Fts5Data *pLeaf; /* Current leaf data */
Fts5Data *pNextLeaf; /* Leaf page (iLeafPgno+1) */
i64 iLeafOffset; /* Byte offset within current leaf */
+ Fts5Data **apTombstone; /* Array of tombstone pages */
+ int nTombstone;
/* Next method */
void (*xNext)(Fts5Index*, Fts5SegIter*, int*);
@@ -564,6 +650,60 @@
}
/*
+** The only argument points to a buffer at least 8 bytes in size. This
+** function interprets the first 8 bytes of the buffer as a 64-bit big-endian
+** unsigned integer and returns the result.
+*/
+static u64 fts5GetU64(u8 *a){
+ return ((u64)a[0] << 56)
+ + ((u64)a[1] << 48)
+ + ((u64)a[2] << 40)
+ + ((u64)a[3] << 32)
+ + ((u64)a[4] << 24)
+ + ((u64)a[5] << 16)
+ + ((u64)a[6] << 8)
+ + ((u64)a[7] << 0);
+}
+
+/*
+** The only argument points to a buffer at least 4 bytes in size. This
+** function interprets the first 4 bytes of the buffer as a 32-bit big-endian
+** unsigned integer and returns the result.
+*/
+static u32 fts5GetU32(const u8 *a){
+ return ((u32)a[0] << 24)
+ + ((u32)a[1] << 16)
+ + ((u32)a[2] << 8)
+ + ((u32)a[3] << 0);
+}
+
+/*
+** Write iVal, formated as a 64-bit big-endian unsigned integer, to the
+** buffer indicated by the first argument.
+*/
+static void fts5PutU64(u8 *a, u64 iVal){
+ a[0] = ((iVal >> 56) & 0xFF);
+ a[1] = ((iVal >> 48) & 0xFF);
+ a[2] = ((iVal >> 40) & 0xFF);
+ a[3] = ((iVal >> 32) & 0xFF);
+ a[4] = ((iVal >> 24) & 0xFF);
+ a[5] = ((iVal >> 16) & 0xFF);
+ a[6] = ((iVal >> 8) & 0xFF);
+ a[7] = ((iVal >> 0) & 0xFF);
+}
+
+/*
+** Write iVal, formated as a 32-bit big-endian unsigned integer, to the
+** buffer indicated by the first argument.
+*/
+static void fts5PutU32(u8 *a, u32 iVal){
+ a[0] = ((iVal >> 24) & 0xFF);
+ a[1] = ((iVal >> 16) & 0xFF);
+ a[2] = ((iVal >> 8) & 0xFF);
+ a[3] = ((iVal >> 0) & 0xFF);
+}
+
+/*
** Allocate and return a buffer at least nByte bytes in size.
**
** If an OOM error is encountered, return NULL and set the error code in
@@ -790,10 +930,17 @@
/*
** Remove all records associated with segment iSegid.
*/
-static void fts5DataRemoveSegment(Fts5Index *p, int iSegid){
+static void fts5DataRemoveSegment(Fts5Index *p, Fts5StructureSegment *pSeg){
+ int iSegid = pSeg->iSegid;
i64 iFirst = FTS5_SEGMENT_ROWID(iSegid, 0);
i64 iLast = FTS5_SEGMENT_ROWID(iSegid+1, 0)-1;
fts5DataDelete(p, iFirst, iLast);
+
+ if( pSeg->nPgTombstone ){
+ i64 iTomb1 = FTS5_TOMBSTONE_ROWID(iSegid, 0);
+ i64 iTomb2 = FTS5_TOMBSTONE_ROWID(iSegid, pSeg->nPgTombstone-1);
+ fts5DataDelete(p, iTomb1, iTomb2);
+ }
if( p->pIdxDeleter==0 ){
Fts5Config *pConfig = p->pConfig;
fts5IndexPrepareStmt(p, &p->pIdxDeleter, sqlite3_mprintf(
@@ -904,11 +1051,19 @@
int nSegment = 0;
sqlite3_int64 nByte; /* Bytes of space to allocate at pRet */
Fts5Structure *pRet = 0; /* Structure object to return */
+ int bStructureV2 = 0; /* True for FTS5_STRUCTURE_V2 */
+ u64 nOriginCntr = 0; /* Largest origin value seen so far */
/* Grab the cookie value */
if( piCookie ) *piCookie = sqlite3Fts5Get32(pData);
i = 4;
+ /* Check if this is a V2 structure record. Set bStructureV2 if it is. */
+ if( 0==memcmp(&pData[i], FTS5_STRUCTURE_V2, 4) ){
+ i += 4;
+ bStructureV2 = 1;
+ }
+
/* Read the total number of levels and segments from the start of the
** structure record. */
i += fts5GetVarint32(&pData[i], nLevel);
@@ -955,9 +1110,18 @@
rc = FTS5_CORRUPT;
break;
}
+ assert( pSeg!=0 );
i += fts5GetVarint32(&pData[i], pSeg->iSegid);
i += fts5GetVarint32(&pData[i], pSeg->pgnoFirst);
i += fts5GetVarint32(&pData[i], pSeg->pgnoLast);
+ if( bStructureV2 ){
+ i += fts5GetVarint(&pData[i], &pSeg->iOrigin1);
+ i += fts5GetVarint(&pData[i], &pSeg->iOrigin2);
+ i += fts5GetVarint32(&pData[i], pSeg->nPgTombstone);
+ i += fts5GetVarint(&pData[i], &pSeg->nEntryTombstone);
+ i += fts5GetVarint(&pData[i], &pSeg->nEntry);
+ nOriginCntr = MAX(nOriginCntr, pSeg->iOrigin2);
+ }
if( pSeg->pgnoLastpgnoFirst ){
rc = FTS5_CORRUPT;
break;
@@ -968,6 +1132,9 @@
}
}
if( nSegment!=0 && rc==SQLITE_OK ) rc = FTS5_CORRUPT;
+ if( bStructureV2 ){
+ pRet->nOriginCntr = nOriginCntr+1;
+ }
if( rc!=SQLITE_OK ){
fts5StructureRelease(pRet);
@@ -985,6 +1152,7 @@
*/
static void fts5StructureAddLevel(int *pRc, Fts5Structure **ppStruct){
fts5StructureMakeWritable(pRc, ppStruct);
+ assert( (ppStruct!=0 && (*ppStruct)!=0) || (*pRc)!=SQLITE_OK );
if( *pRc==SQLITE_OK ){
Fts5Structure *pStruct = *ppStruct;
int nLevel = pStruct->nLevel;
@@ -1179,6 +1347,7 @@
Fts5Buffer buf; /* Buffer to serialize record into */
int iLvl; /* Used to iterate through levels */
int iCookie; /* Cookie value to store */
+ int nHdr = (pStruct->nOriginCntr>0 ? (4+4+9+9+9) : (4+9+9));
assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) );
memset(&buf, 0, sizeof(Fts5Buffer));
@@ -1187,9 +1356,12 @@
iCookie = p->pConfig->iCookie;
if( iCookie<0 ) iCookie = 0;
- if( 0==sqlite3Fts5BufferSize(&p->rc, &buf, 4+9+9+9) ){
+ if( 0==sqlite3Fts5BufferSize(&p->rc, &buf, nHdr) ){
sqlite3Fts5Put32(buf.p, iCookie);
buf.n = 4;
+ if( pStruct->nOriginCntr>0 ){
+ fts5BufferSafeAppendBlob(&buf, FTS5_STRUCTURE_V2, 4);
+ }
fts5BufferSafeAppendVarint(&buf, pStruct->nLevel);
fts5BufferSafeAppendVarint(&buf, pStruct->nSegment);
fts5BufferSafeAppendVarint(&buf, (i64)pStruct->nWriteCounter);
@@ -1203,9 +1375,17 @@
assert( pLvl->nMerge<=pLvl->nSeg );
for(iSeg=0; iSegnSeg; iSeg++){
- fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].iSegid);
- fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].pgnoFirst);
- fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].pgnoLast);
+ Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg];
+ fts5BufferAppendVarint(&p->rc, &buf, pSeg->iSegid);
+ fts5BufferAppendVarint(&p->rc, &buf, pSeg->pgnoFirst);
+ fts5BufferAppendVarint(&p->rc, &buf, pSeg->pgnoLast);
+ if( pStruct->nOriginCntr>0 ){
+ fts5BufferAppendVarint(&p->rc, &buf, pSeg->iOrigin1);
+ fts5BufferAppendVarint(&p->rc, &buf, pSeg->iOrigin2);
+ fts5BufferAppendVarint(&p->rc, &buf, pSeg->nPgTombstone);
+ fts5BufferAppendVarint(&p->rc, &buf, pSeg->nEntryTombstone);
+ fts5BufferAppendVarint(&p->rc, &buf, pSeg->nEntry);
+ }
}
}
@@ -1443,42 +1623,25 @@
pLvl->bEof = 1;
}else{
u8 *a = pLvl->pData->p;
- i64 iVal;
- int iLimit;
- int ii;
- int nZero = 0;
- /* Currently iOff points to the first byte of a varint. This block
- ** decrements iOff until it points to the first byte of the previous
- ** varint. Taking care not to read any memory locations that occur
- ** before the buffer in memory. */
- iLimit = (iOff>9 ? iOff-9 : 0);
- for(iOff--; iOff>iLimit; iOff--){
- if( (a[iOff-1] & 0x80)==0 ) break;
- }
-
- fts5GetVarint(&a[iOff], (u64*)&iVal);
- pLvl->iRowid -= iVal;
- pLvl->iLeafPgno--;
-
- /* Skip backwards past any 0x00 varints. */
- for(ii=iOff-1; ii>=pLvl->iFirstOff && a[ii]==0x00; ii--){
- nZero++;
- }
- if( ii>=pLvl->iFirstOff && (a[ii] & 0x80) ){
- /* The byte immediately before the last 0x00 byte has the 0x80 bit
- ** set. So the last 0x00 is only a varint 0 if there are 8 more 0x80
- ** bytes before a[ii]. */
- int bZero = 0; /* True if last 0x00 counts */
- if( (ii-8)>=pLvl->iFirstOff ){
- int j;
- for(j=1; j<=8 && (a[ii-j] & 0x80); j++);
- bZero = (j>8);
- }
- if( bZero==0 ) nZero--;
+ pLvl->iOff = 0;
+ fts5DlidxLvlNext(pLvl);
+ while( 1 ){
+ int nZero = 0;
+ int ii = pLvl->iOff;
+ u64 delta = 0;
+
+ while( a[ii]==0 ){
+ nZero++;
+ ii++;
+ }
+ ii += sqlite3Fts5GetVarint(&a[ii], &delta);
+
+ if( ii>=iOff ) break;
+ pLvl->iLeafPgno += nZero+1;
+ pLvl->iRowid += delta;
+ pLvl->iOff = ii;
}
- pLvl->iLeafPgno -= nZero;
- pLvl->iOff = iOff - nZero;
}
return pLvl->bEof;
@@ -1674,7 +1837,7 @@
i64 iOff = pIter->iLeafOffset;
ASSERT_SZLEAF_OK(pIter->pLeaf);
- if( iOff>=pIter->pLeaf->szLeaf ){
+ while( iOff>=pIter->pLeaf->szLeaf ){
fts5SegIterNextPage(p, pIter);
if( pIter->pLeaf==0 ){
if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT;
@@ -1746,6 +1909,23 @@
}
/*
+** Allocate a tombstone hash page array (pIter->apTombstone) for the
+** iterator passed as the second argument. If an OOM error occurs, leave
+** an error in the Fts5Index object.
+*/
+static void fts5SegIterAllocTombstone(Fts5Index *p, Fts5SegIter *pIter){
+ const int nTomb = pIter->pSeg->nPgTombstone;
+ if( nTomb>0 ){
+ Fts5Data **apTomb = 0;
+ apTomb = (Fts5Data**)sqlite3Fts5MallocZero(&p->rc, sizeof(Fts5Data)*nTomb);
+ if( apTomb ){
+ pIter->apTombstone = apTomb;
+ pIter->nTombstone = nTomb;
+ }
+ }
+}
+
+/*
** Initialize the iterator object pIter to iterate through the entries in
** segment pSeg. The iterator is left pointing to the first entry when
** this function returns.
@@ -1773,10 +1953,12 @@
fts5SegIterSetNext(p, pIter);
pIter->pSeg = pSeg;
pIter->iLeafPgno = pSeg->pgnoFirst-1;
- fts5SegIterNextPage(p, pIter);
+ do {
+ fts5SegIterNextPage(p, pIter);
+ }while( p->rc==SQLITE_OK && pIter->pLeaf && pIter->pLeaf->nn==4 );
}
- if( p->rc==SQLITE_OK ){
+ if( p->rc==SQLITE_OK && pIter->pLeaf ){
pIter->iLeafOffset = 4;
assert( pIter->pLeaf!=0 );
assert_nc( pIter->pLeaf->nn>4 );
@@ -1784,6 +1966,7 @@
pIter->iPgidxOff = pIter->pLeaf->szLeaf+1;
fts5SegIterLoadTerm(p, pIter, 0);
fts5SegIterLoadNPos(p, pIter);
+ fts5SegIterAllocTombstone(p, pIter);
}
}
@@ -1970,7 +2153,7 @@
iOff = pIter->iLeafOffset;
/* Next entry is on the next page */
- if( pIter->pSeg && iOff>=pIter->pLeaf->szLeaf ){
+ while( pIter->pSeg && iOff>=pIter->pLeaf->szLeaf ){
fts5SegIterNextPage(p, pIter);
if( p->rc || pIter->pLeaf==0 ) return;
pIter->iRowid = 0;
@@ -2163,7 +2346,7 @@
Fts5Data *pLast = 0;
int pgnoLast = 0;
- if( pDlidx ){
+ if( pDlidx && p->pConfig->iVersion==FTS5_CURRENT_VERSION ){
int iSegid = pIter->pSeg->iSegid;
pgnoLast = fts5DlidxIterPgno(pDlidx);
pLast = fts5LeafRead(p, FTS5_SEGMENT_ROWID(iSegid, pgnoLast));
@@ -2485,6 +2668,7 @@
}
fts5SegIterSetNext(p, pIter);
+ fts5SegIterAllocTombstone(p, pIter);
/* Either:
**
@@ -2566,12 +2750,27 @@
}
/*
+** Array ap[] contains n elements. Release each of these elements using
+** fts5DataRelease(). Then free the array itself using sqlite3_free().
+*/
+static void fts5IndexFreeArray(Fts5Data **ap, int n){
+ if( ap ){
+ int ii;
+ for(ii=0; iiterm);
fts5DataRelease(pIter->pLeaf);
fts5DataRelease(pIter->pNextLeaf);
+ fts5IndexFreeArray(pIter->apTombstone, pIter->nTombstone);
fts5DlidxIterFree(pIter->pDlidx);
sqlite3_free(pIter->aRowidOffset);
memset(pIter, 0, sizeof(Fts5SegIter));
@@ -2705,7 +2904,6 @@
assert_nc( i2!=0 );
pRes->bTermEq = 1;
if( p1->iRowid==p2->iRowid ){
- p1->bDel = p2->bDel;
return i2;
}
res = ((p1->iRowid > p2->iRowid)==pIter->bRev) ? -1 : +1;
@@ -2724,7 +2922,8 @@
/*
** Move the seg-iter so that it points to the first rowid on page iLeafPgno.
-** It is an error if leaf iLeafPgno does not exist or contains no rowids.
+** It is an error if leaf iLeafPgno does not exist. Unless the db is
+** a 'secure-delete' db, if it contains no rowids then this is also an error.
*/
static void fts5SegIterGotoPage(
Fts5Index *p, /* FTS5 backend object */
@@ -2739,21 +2938,23 @@
fts5DataRelease(pIter->pNextLeaf);
pIter->pNextLeaf = 0;
pIter->iLeafPgno = iLeafPgno-1;
- fts5SegIterNextPage(p, pIter);
- assert( p->rc!=SQLITE_OK || pIter->iLeafPgno==iLeafPgno );
- if( p->rc==SQLITE_OK && ALWAYS(pIter->pLeaf!=0) ){
+ while( p->rc==SQLITE_OK ){
int iOff;
- u8 *a = pIter->pLeaf->p;
- int n = pIter->pLeaf->szLeaf;
-
+ fts5SegIterNextPage(p, pIter);
+ if( pIter->pLeaf==0 ) break;
iOff = fts5LeafFirstRowidOff(pIter->pLeaf);
- if( iOff<4 || iOff>=n ){
- p->rc = FTS5_CORRUPT;
- }else{
- iOff += fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid);
- pIter->iLeafOffset = iOff;
- fts5SegIterLoadNPos(p, pIter);
+ if( iOff>0 ){
+ u8 *a = pIter->pLeaf->p;
+ int n = pIter->pLeaf->szLeaf;
+ if( iOff<4 || iOff>=n ){
+ p->rc = FTS5_CORRUPT;
+ }else{
+ iOff += fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid);
+ pIter->iLeafOffset = iOff;
+ fts5SegIterLoadNPos(p, pIter);
+ }
+ break;
}
}
}
@@ -2907,6 +3108,84 @@
}
/*
+** The argument to this macro must be an Fts5Data structure containing a
+** tombstone hash page. This macro returns the key-size of the hash-page.
+*/
+#define TOMBSTONE_KEYSIZE(pPg) (pPg->p[0]==4 ? 4 : 8)
+
+#define TOMBSTONE_NSLOT(pPg) \
+ ((pPg->nn > 16) ? ((pPg->nn-8) / TOMBSTONE_KEYSIZE(pPg)) : 1)
+
+/*
+** Query a single tombstone hash table for rowid iRowid. Return true if
+** it is found or false otherwise. The tombstone hash table is one of
+** nHashTable tables.
+*/
+static int fts5IndexTombstoneQuery(
+ Fts5Data *pHash, /* Hash table page to query */
+ int nHashTable, /* Number of pages attached to segment */
+ u64 iRowid /* Rowid to query hash for */
+){
+ const int szKey = TOMBSTONE_KEYSIZE(pHash);
+ const int nSlot = TOMBSTONE_NSLOT(pHash);
+ int iSlot = (iRowid / nHashTable) % nSlot;
+ int nCollide = nSlot;
+
+ if( iRowid==0 ){
+ return pHash->p[1];
+ }else if( szKey==4 ){
+ u32 *aSlot = (u32*)&pHash->p[8];
+ while( aSlot[iSlot] ){
+ if( fts5GetU32((u8*)&aSlot[iSlot])==iRowid ) return 1;
+ if( nCollide--==0 ) break;
+ iSlot = (iSlot+1)%nSlot;
+ }
+ }else{
+ u64 *aSlot = (u64*)&pHash->p[8];
+ while( aSlot[iSlot] ){
+ if( fts5GetU64((u8*)&aSlot[iSlot])==iRowid ) return 1;
+ if( nCollide--==0 ) break;
+ iSlot = (iSlot+1)%nSlot;
+ }
+ }
+
+ return 0;
+}
+
+/*
+** Return true if the iterator passed as the only argument points
+** to an segment entry for which there is a tombstone. Return false
+** if there is no tombstone or if the iterator is already at EOF.
+*/
+static int fts5MultiIterIsDeleted(Fts5Iter *pIter){
+ int iFirst = pIter->aFirst[1].iFirst;
+ Fts5SegIter *pSeg = &pIter->aSeg[iFirst];
+
+ if( pSeg->pLeaf && pSeg->nTombstone ){
+ /* Figure out which page the rowid might be present on. */
+ int iPg = ((u64)pSeg->iRowid) % pSeg->nTombstone;
+ assert( iPg>=0 );
+
+ /* If tombstone hash page iPg has not yet been loaded from the
+ ** database, load it now. */
+ if( pSeg->apTombstone[iPg]==0 ){
+ pSeg->apTombstone[iPg] = fts5DataRead(pIter->pIndex,
+ FTS5_TOMBSTONE_ROWID(pSeg->pSeg->iSegid, iPg)
+ );
+ if( pSeg->apTombstone[iPg]==0 ) return 0;
+ }
+
+ return fts5IndexTombstoneQuery(
+ pSeg->apTombstone[iPg],
+ pSeg->nTombstone,
+ pSeg->iRowid
+ );
+ }
+
+ return 0;
+}
+
+/*
** Move the iterator to the next entry.
**
** If an error occurs, an error code is left in Fts5Index.rc. It is not
@@ -2943,7 +3222,9 @@
fts5AssertMultiIterSetup(p, pIter);
assert( pSeg==&pIter->aSeg[pIter->aFirst[1].iFirst] && pSeg->pLeaf );
- if( pIter->bSkipEmpty==0 || pSeg->nPos ){
+ if( (pIter->bSkipEmpty==0 || pSeg->nPos)
+ && 0==fts5MultiIterIsDeleted(pIter)
+ ){
pIter->xSetOutputs(pIter, pSeg);
return;
}
@@ -2975,7 +3256,9 @@
}
fts5AssertMultiIterSetup(p, pIter);
- }while( fts5MultiIterIsEmpty(p, pIter) );
+ }while( (fts5MultiIterIsEmpty(p, pIter) || fts5MultiIterIsDeleted(pIter))
+ && (p->rc==SQLITE_OK)
+ );
}
}
@@ -2988,7 +3271,7 @@
int nSeg
){
Fts5Iter *pNew;
- int nSlot; /* Power of two >= nSeg */
+ i64 nSlot; /* Power of two >= nSeg */
for(nSlot=2; nSlotnSegment==fts5StructureCountSegments(pStruct) );
nSeg = pStruct->nSegment;
- nSeg += (p->pHash ? 1 : 0);
+ nSeg += (p->pHash && 0==(flags & FTS5INDEX_QUERY_SKIPHASH));
}else{
nSeg = MIN(pStruct->aLevel[iLevel].nSeg, nSegment);
}
@@ -3489,7 +3772,7 @@
if( p->rc==SQLITE_OK ){
if( iLevel<0 ){
Fts5StructureLevel *pEnd = &pStruct->aLevel[pStruct->nLevel];
- if( p->pHash ){
+ if( p->pHash && 0==(flags & FTS5INDEX_QUERY_SKIPHASH) ){
/* Add a segment iterator for the current contents of the hash table. */
Fts5SegIter *pIter = &pNew->aSeg[iIter++];
fts5SegIterHashInit(p, pTerm, nTerm, flags, pIter);
@@ -3530,7 +3813,9 @@
fts5MultiIterSetEof(pNew);
fts5AssertMultiIterSetup(p, pNew);
- if( pNew->bSkipEmpty && fts5MultiIterIsEmpty(p, pNew) ){
+ if( (pNew->bSkipEmpty && fts5MultiIterIsEmpty(p, pNew))
+ || fts5MultiIterIsDeleted(pNew)
+ ){
fts5MultiIterNext(p, pNew, 0, 0);
}else if( pNew->base.bEof==0 ){
Fts5SegIter *pSeg = &pNew->aSeg[pNew->aFirst[1].iFirst];
@@ -3708,7 +3993,9 @@
if( p->pHash ){
sqlite3Fts5HashClear(p->pHash);
p->nPendingData = 0;
+ p->nPendingRow = 0;
}
+ p->nContentlessDelete = 0;
}
/*
@@ -4244,7 +4531,7 @@
fts5BufferAppendBlob(&p->rc, &buf, sizeof(aHdr), aHdr);
fts5BufferAppendVarint(&p->rc, &buf, pSeg->term.n);
fts5BufferAppendBlob(&p->rc, &buf, pSeg->term.n, pSeg->term.p);
- fts5BufferAppendBlob(&p->rc, &buf, pData->szLeaf-iOff,&pData->p[iOff]);
+ fts5BufferAppendBlob(&p->rc, &buf,pData->szLeaf-iOff,&pData->p[iOff]);
if( p->rc==SQLITE_OK ){
/* Set the szLeaf field */
fts5PutU16(&buf.p[2], (u16)buf.n);
@@ -4345,6 +4632,12 @@
/* Read input from all segments in the input level */
nInput = pLvl->nSeg;
+
+ /* Set the range of origins that will go into the output segment. */
+ if( pStruct->nOriginCntr>0 ){
+ pSeg->iOrigin1 = pLvl->aSeg[0].iOrigin1;
+ pSeg->iOrigin2 = pLvl->aSeg[pLvl->nSeg-1].iOrigin2;
+ }
}
bOldest = (pLvlOut->nSeg==1 && pStruct->nLevel==iLvl+2);
@@ -4404,8 +4697,11 @@
int i;
/* Remove the redundant segments from the %_data table */
+ assert( pSeg->nEntry==0 );
for(i=0; iaSeg[i].iSegid);
+ Fts5StructureSegment *pOld = &pLvl->aSeg[i];
+ pSeg->nEntry += (pOld->nEntry - pOld->nEntryTombstone);
+ fts5DataRemoveSegment(p, pOld);
}
/* Remove the redundant segments from the input level */
@@ -4432,6 +4728,43 @@
}
/*
+** If this is not a contentless_delete=1 table, or if the 'deletemerge'
+** configuration option is set to 0, then this function always returns -1.
+** Otherwise, it searches the structure object passed as the second argument
+** for a level suitable for merging due to having a large number of
+** tombstones in the tombstone hash. If one is found, its index is returned.
+** Otherwise, if there is no suitable level, -1.
+*/
+static int fts5IndexFindDeleteMerge(Fts5Index *p, Fts5Structure *pStruct){
+ Fts5Config *pConfig = p->pConfig;
+ int iRet = -1;
+ if( pConfig->bContentlessDelete && pConfig->nDeleteMerge>0 ){
+ int ii;
+ int nBest = 0;
+
+ for(ii=0; iinLevel; ii++){
+ Fts5StructureLevel *pLvl = &pStruct->aLevel[ii];
+ i64 nEntry = 0;
+ i64 nTomb = 0;
+ int iSeg;
+ for(iSeg=0; iSegnSeg; iSeg++){
+ nEntry += pLvl->aSeg[iSeg].nEntry;
+ nTomb += pLvl->aSeg[iSeg].nEntryTombstone;
+ }
+ assert_nc( nEntry>0 || pLvl->nSeg==0 );
+ if( nEntry>0 ){
+ int nPercent = (nTomb * 100) / nEntry;
+ if( nPercent>=pConfig->nDeleteMerge && nPercent>nBest ){
+ iRet = ii;
+ nBest = nPercent;
+ }
+ }
+ }
+ }
+ return iRet;
+}
+
+/*
** Do up to nPg pages of automerge work on the index.
**
** Return true if any changes were actually made, or false otherwise.
@@ -4450,14 +4783,15 @@
int iBestLvl = 0; /* Level offering the most input segments */
int nBest = 0; /* Number of input segments on best level */
- /* Set iBestLvl to the level to read input segments from. */
+ /* Set iBestLvl to the level to read input segments from. Or to -1 if
+ ** there is no level suitable to merge segments from. */
assert( pStruct->nLevel>0 );
for(iLvl=0; iLvlnLevel; iLvl++){
Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl];
if( pLvl->nMerge ){
if( pLvl->nMerge>nBest ){
iBestLvl = iLvl;
- nBest = pLvl->nMerge;
+ nBest = nMin;
}
break;
}
@@ -4466,22 +4800,18 @@
iBestLvl = iLvl;
}
}
-
- /* If nBest is still 0, then the index must be empty. */
-#ifdef SQLITE_DEBUG
- for(iLvl=0; nBest==0 && iLvlnLevel; iLvl++){
- assert( pStruct->aLevel[iLvl].nSeg==0 );
+ if( nBestaLevel[iBestLvl].nMerge==0 ){
- break;
- }
+ if( iBestLvl<0 ) break;
bRet = 1;
fts5IndexMergeLevel(p, &pStruct, iBestLvl, &nRem);
if( p->rc==SQLITE_OK && pStruct->aLevel[iBestLvl].nMerge==0 ){
fts5StructurePromote(p, iBestLvl+1, pStruct);
}
+
+ if( nMin==1 ) nMin = 2;
}
*ppStruct = pStruct;
return bRet;
@@ -4522,16 +4852,16 @@
){
const int nCrisis = p->pConfig->nCrisisMerge;
Fts5Structure *pStruct = *ppStruct;
- int iLvl = 0;
-
- assert( p->rc!=SQLITE_OK || pStruct->nLevel>0 );
- while( p->rc==SQLITE_OK && pStruct->aLevel[iLvl].nSeg>=nCrisis ){
- fts5IndexMergeLevel(p, &pStruct, iLvl, 0);
- assert( p->rc!=SQLITE_OK || pStruct->nLevel>(iLvl+1) );
- fts5StructurePromote(p, iLvl+1, pStruct);
- iLvl++;
+ if( pStruct && pStruct->nLevel>0 ){
+ int iLvl = 0;
+ while( p->rc==SQLITE_OK && pStruct->aLevel[iLvl].nSeg>=nCrisis ){
+ fts5IndexMergeLevel(p, &pStruct, iLvl, 0);
+ assert( p->rc!=SQLITE_OK || pStruct->nLevel>(iLvl+1) );
+ fts5StructurePromote(p, iLvl+1, pStruct);
+ iLvl++;
+ }
+ *ppStruct = pStruct;
}
- *ppStruct = pStruct;
}
static int fts5IndexReturn(Fts5Index *p){
@@ -4566,6 +4896,463 @@
}
/*
+** Execute the SQL statement:
+**
+** DELETE FROM %_idx WHERE (segid, (pgno/2)) = ($iSegid, $iPgno);
+**
+** This is used when a secure-delete operation removes the last term
+** from a segment leaf page. In that case the %_idx entry is removed
+** too. This is done to ensure that if all instances of a token are
+** removed from an fts5 database in secure-delete mode, no trace of
+** the token itself remains in the database.
+*/
+static void fts5SecureDeleteIdxEntry(
+ Fts5Index *p, /* FTS5 backend object */
+ int iSegid, /* Id of segment to delete entry for */
+ int iPgno /* Page number within segment */
+){
+ if( iPgno!=1 ){
+ assert( p->pConfig->iVersion==FTS5_CURRENT_VERSION_SECUREDELETE );
+ if( p->pDeleteFromIdx==0 ){
+ fts5IndexPrepareStmt(p, &p->pDeleteFromIdx, sqlite3_mprintf(
+ "DELETE FROM '%q'.'%q_idx' WHERE (segid, (pgno/2)) = (?1, ?2)",
+ p->pConfig->zDb, p->pConfig->zName
+ ));
+ }
+ if( p->rc==SQLITE_OK ){
+ sqlite3_bind_int(p->pDeleteFromIdx, 1, iSegid);
+ sqlite3_bind_int(p->pDeleteFromIdx, 2, iPgno);
+ sqlite3_step(p->pDeleteFromIdx);
+ p->rc = sqlite3_reset(p->pDeleteFromIdx);
+ }
+ }
+}
+
+/*
+** This is called when a secure-delete operation removes a position-list
+** that overflows onto segment page iPgno of segment pSeg. This function
+** rewrites node iPgno, and possibly one or more of its right-hand peers,
+** to remove this portion of the position list.
+**
+** Output variable (*pbLastInDoclist) is set to true if the position-list
+** removed is followed by a new term or the end-of-segment, or false if
+** it is followed by another rowid/position list.
+*/
+static void fts5SecureDeleteOverflow(
+ Fts5Index *p,
+ Fts5StructureSegment *pSeg,
+ int iPgno,
+ int *pbLastInDoclist
+){
+ const int bDetailNone = (p->pConfig->eDetail==FTS5_DETAIL_NONE);
+ int pgno;
+ Fts5Data *pLeaf = 0;
+ assert( iPgno!=1 );
+
+ *pbLastInDoclist = 1;
+ for(pgno=iPgno; p->rc==SQLITE_OK && pgno<=pSeg->pgnoLast; pgno++){
+ i64 iRowid = FTS5_SEGMENT_ROWID(pSeg->iSegid, pgno);
+ int iNext = 0;
+ u8 *aPg = 0;
+
+ pLeaf = fts5DataRead(p, iRowid);
+ if( pLeaf==0 ) break;
+ aPg = pLeaf->p;
+
+ iNext = fts5GetU16(&aPg[0]);
+ if( iNext!=0 ){
+ *pbLastInDoclist = 0;
+ }
+ if( iNext==0 && pLeaf->szLeaf!=pLeaf->nn ){
+ fts5GetVarint32(&aPg[pLeaf->szLeaf], iNext);
+ }
+
+ if( iNext==0 ){
+ /* The page contains no terms or rowids. Replace it with an empty
+ ** page and move on to the right-hand peer. */
+ const u8 aEmpty[] = {0x00, 0x00, 0x00, 0x04};
+ assert_nc( bDetailNone==0 || pLeaf->nn==4 );
+ if( bDetailNone==0 ) fts5DataWrite(p, iRowid, aEmpty, sizeof(aEmpty));
+ fts5DataRelease(pLeaf);
+ pLeaf = 0;
+ }else if( bDetailNone ){
+ break;
+ }else if( iNext>=pLeaf->szLeaf || pLeaf->nnszLeaf || iNext<4 ){
+ p->rc = FTS5_CORRUPT;
+ break;
+ }else{
+ int nShift = iNext - 4;
+ int nPg;
+
+ int nIdx = 0;
+ u8 *aIdx = 0;
+
+ /* Unless the current page footer is 0 bytes in size (in which case
+ ** the new page footer will be as well), allocate and populate a
+ ** buffer containing the new page footer. Set stack variables aIdx
+ ** and nIdx accordingly. */
+ if( pLeaf->nn>pLeaf->szLeaf ){
+ int iFirst = 0;
+ int i1 = pLeaf->szLeaf;
+ int i2 = 0;
+
+ i1 += fts5GetVarint32(&aPg[i1], iFirst);
+ if( iFirstrc = FTS5_CORRUPT;
+ break;
+ }
+ aIdx = sqlite3Fts5MallocZero(&p->rc, (pLeaf->nn-pLeaf->szLeaf)+2);
+ if( aIdx==0 ) break;
+ i2 = sqlite3Fts5PutVarint(aIdx, iFirst-nShift);
+ if( i1nn ){
+ memcpy(&aIdx[i2], &aPg[i1], pLeaf->nn-i1);
+ i2 += (pLeaf->nn-i1);
+ }
+ nIdx = i2;
+ }
+
+ /* Modify the contents of buffer aPg[]. Set nPg to the new size
+ ** in bytes. The new page is always smaller than the old. */
+ nPg = pLeaf->szLeaf - nShift;
+ memmove(&aPg[4], &aPg[4+nShift], nPg-4);
+ fts5PutU16(&aPg[2], nPg);
+ if( fts5GetU16(&aPg[0]) ) fts5PutU16(&aPg[0], 4);
+ if( nIdx>0 ){
+ memcpy(&aPg[nPg], aIdx, nIdx);
+ nPg += nIdx;
+ }
+ sqlite3_free(aIdx);
+
+ /* Write the new page to disk and exit the loop */
+ assert( nPg>4 || fts5GetU16(aPg)==0 );
+ fts5DataWrite(p, iRowid, aPg, nPg);
+ break;
+ }
+ }
+ fts5DataRelease(pLeaf);
+}
+
+/*
+** Completely remove the entry that pSeg currently points to from
+** the database.
+*/
+static void fts5DoSecureDelete(
+ Fts5Index *p,
+ Fts5SegIter *pSeg
+){
+ const int bDetailNone = (p->pConfig->eDetail==FTS5_DETAIL_NONE);
+ int iSegid = pSeg->pSeg->iSegid;
+ u8 *aPg = pSeg->pLeaf->p;
+ int nPg = pSeg->pLeaf->nn;
+ int iPgIdx = pSeg->pLeaf->szLeaf;
+
+ u64 iDelta = 0;
+ int iNextOff = 0;
+ int iOff = 0;
+ int nIdx = 0;
+ u8 *aIdx = 0;
+ int bLastInDoclist = 0;
+ int iIdx = 0;
+ int iStart = 0;
+ int iDelKeyOff = 0; /* Offset of deleted key, if any */
+
+ nIdx = nPg-iPgIdx;
+ aIdx = sqlite3Fts5MallocZero(&p->rc, nIdx+16);
+ if( p->rc ) return;
+ memcpy(aIdx, &aPg[iPgIdx], nIdx);
+
+ /* At this point segment iterator pSeg points to the entry
+ ** this function should remove from the b-tree segment.
+ **
+ ** In detail=full or detail=column mode, pSeg->iLeafOffset is the
+ ** offset of the first byte in the position-list for the entry to
+ ** remove. Immediately before this comes two varints that will also
+ ** need to be removed:
+ **
+ ** + the rowid or delta rowid value for the entry, and
+ ** + the size of the position list in bytes.
+ **
+ ** Or, in detail=none mode, there is a single varint prior to
+ ** pSeg->iLeafOffset - the rowid or delta rowid value.
+ **
+ ** This block sets the following variables:
+ **
+ ** iStart:
+ ** The offset of the first byte of the rowid or delta-rowid
+ ** value for the doclist entry being removed.
+ **
+ ** iDelta:
+ ** The value of the rowid or delta-rowid value for the doclist
+ ** entry being removed.
+ **
+ ** iNextOff:
+ ** The offset of the next entry following the position list
+ ** for the one being removed. If the position list for this
+ ** entry overflows onto the next leaf page, this value will be
+ ** greater than pLeaf->szLeaf.
+ */
+ {
+ int iSOP; /* Start-Of-Position-list */
+ if( pSeg->iLeafPgno==pSeg->iTermLeafPgno ){
+ iStart = pSeg->iTermLeafOffset;
+ }else{
+ iStart = fts5GetU16(&aPg[0]);
+ }
+
+ iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta);
+ assert_nc( iSOP<=pSeg->iLeafOffset );
+
+ if( bDetailNone ){
+ while( iSOPiLeafOffset ){
+ if( aPg[iSOP]==0x00 ) iSOP++;
+ if( aPg[iSOP]==0x00 ) iSOP++;
+ iStart = iSOP;
+ iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta);
+ }
+
+ iNextOff = iSOP;
+ if( iNextOffiEndofDoclist && aPg[iNextOff]==0x00 ) iNextOff++;
+ if( iNextOffiEndofDoclist && aPg[iNextOff]==0x00 ) iNextOff++;
+
+ }else{
+ int nPos = 0;
+ iSOP += fts5GetVarint32(&aPg[iSOP], nPos);
+ while( iSOPiLeafOffset ){
+ iStart = iSOP + (nPos/2);
+ iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta);
+ iSOP += fts5GetVarint32(&aPg[iSOP], nPos);
+ }
+ assert_nc( iSOP==pSeg->iLeafOffset );
+ iNextOff = pSeg->iLeafOffset + pSeg->nPos;
+ }
+ }
+
+ iOff = iStart;
+
+ /* Set variable bLastInDoclist to true if this entry happens to be
+ ** the last rowid in the doclist for its term. */
+ if( pSeg->bDel==0 ){
+ if( iNextOff>=iPgIdx ){
+ int pgno = pSeg->iLeafPgno+1;
+ fts5SecureDeleteOverflow(p, pSeg->pSeg, pgno, &bLastInDoclist);
+ iNextOff = iPgIdx;
+ }else{
+ /* Loop through the page-footer. If iNextOff (offset of the
+ ** entry following the one we are removing) is equal to the
+ ** offset of a key on this page, then the entry is the last
+ ** in its doclist. */
+ int iKeyOff = 0;
+ for(iIdx=0; iIdxbDel ){
+ iOff += sqlite3Fts5PutVarint(&aPg[iOff], iDelta);
+ aPg[iOff++] = 0x01;
+ }else if( bLastInDoclist==0 ){
+ if( iNextOff!=iPgIdx ){
+ u64 iNextDelta = 0;
+ iNextOff += fts5GetVarint(&aPg[iNextOff], &iNextDelta);
+ iOff += sqlite3Fts5PutVarint(&aPg[iOff], iDelta + iNextDelta);
+ }
+ }else if(
+ pSeg->iLeafPgno==pSeg->iTermLeafPgno
+ && iStart==pSeg->iTermLeafOffset
+ ){
+ /* The entry being removed was the only position list in its
+ ** doclist. Therefore the term needs to be removed as well. */
+ int iKey = 0;
+ int iKeyOff = 0;
+
+ /* Set iKeyOff to the offset of the term that will be removed - the
+ ** last offset in the footer that is not greater than iStart. */
+ for(iIdx=0; iIdx(u32)iStart ) break;
+ iKeyOff += iVal;
+ }
+ assert_nc( iKey>=1 );
+
+ /* Set iDelKeyOff to the value of the footer entry to remove from
+ ** the page. */
+ iDelKeyOff = iOff = iKeyOff;
+
+ if( iNextOff!=iPgIdx ){
+ /* This is the only position-list associated with the term, and there
+ ** is another term following it on this page. So the subsequent term
+ ** needs to be moved to replace the term associated with the entry
+ ** being removed. */
+ int nPrefix = 0;
+ int nSuffix = 0;
+ int nPrefix2 = 0;
+ int nSuffix2 = 0;
+
+ iDelKeyOff = iNextOff;
+ iNextOff += fts5GetVarint32(&aPg[iNextOff], nPrefix2);
+ iNextOff += fts5GetVarint32(&aPg[iNextOff], nSuffix2);
+
+ if( iKey!=1 ){
+ iKeyOff += fts5GetVarint32(&aPg[iKeyOff], nPrefix);
+ }
+ iKeyOff += fts5GetVarint32(&aPg[iKeyOff], nSuffix);
+
+ nPrefix = MIN(nPrefix, nPrefix2);
+ nSuffix = (nPrefix2 + nSuffix2) - nPrefix;
+
+ if( (iKeyOff+nSuffix)>iPgIdx || (iNextOff+nSuffix2)>iPgIdx ){
+ p->rc = FTS5_CORRUPT;
+ }else{
+ if( iKey!=1 ){
+ iOff += sqlite3Fts5PutVarint(&aPg[iOff], nPrefix);
+ }
+ iOff += sqlite3Fts5PutVarint(&aPg[iOff], nSuffix);
+ if( nPrefix2>pSeg->term.n ){
+ p->rc = FTS5_CORRUPT;
+ }else if( nPrefix2>nPrefix ){
+ memcpy(&aPg[iOff], &pSeg->term.p[nPrefix], nPrefix2-nPrefix);
+ iOff += (nPrefix2-nPrefix);
+ }
+ memmove(&aPg[iOff], &aPg[iNextOff], nSuffix2);
+ iOff += nSuffix2;
+ iNextOff += nSuffix2;
+ }
+ }
+ }else if( iStart==4 ){
+ int iPgno;
+
+ assert_nc( pSeg->iLeafPgno>pSeg->iTermLeafPgno );
+ /* The entry being removed may be the only position list in
+ ** its doclist. */
+ for(iPgno=pSeg->iLeafPgno-1; iPgno>pSeg->iTermLeafPgno; iPgno-- ){
+ Fts5Data *pPg = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno));
+ int bEmpty = (pPg && pPg->nn==4);
+ fts5DataRelease(pPg);
+ if( bEmpty==0 ) break;
+ }
+
+ if( iPgno==pSeg->iTermLeafPgno ){
+ i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno);
+ Fts5Data *pTerm = fts5DataRead(p, iId);
+ if( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){
+ u8 *aTermIdx = &pTerm->p[pTerm->szLeaf];
+ int nTermIdx = pTerm->nn - pTerm->szLeaf;
+ int iTermIdx = 0;
+ int iTermOff = 0;
+
+ while( 1 ){
+ u32 iVal = 0;
+ int nByte = fts5GetVarint32(&aTermIdx[iTermIdx], iVal);
+ iTermOff += iVal;
+ if( (iTermIdx+nByte)>=nTermIdx ) break;
+ iTermIdx += nByte;
+ }
+ nTermIdx = iTermIdx;
+
+ memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx);
+ fts5PutU16(&pTerm->p[2], iTermOff);
+
+ fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx);
+ if( nTermIdx==0 ){
+ fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno);
+ }
+ }
+ fts5DataRelease(pTerm);
+ }
+ }
+
+ /* Assuming no error has occurred, this block does final edits to the
+ ** leaf page before writing it back to disk. Input variables are:
+ **
+ ** nPg: Total initial size of leaf page.
+ ** iPgIdx: Initial offset of page footer.
+ **
+ ** iOff: Offset to move data to
+ ** iNextOff: Offset to move data from
+ */
+ if( p->rc==SQLITE_OK ){
+ const int nMove = nPg - iNextOff; /* Number of bytes to move */
+ int nShift = iNextOff - iOff; /* Distance to move them */
+
+ int iPrevKeyOut = 0;
+ int iKeyIn = 0;
+
+ memmove(&aPg[iOff], &aPg[iNextOff], nMove);
+ iPgIdx -= nShift;
+ nPg = iPgIdx;
+ fts5PutU16(&aPg[2], iPgIdx);
+
+ for(iIdx=0; iIdxiOff ? nShift : 0));
+ nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOut - iPrevKeyOut);
+ iPrevKeyOut = iKeyOut;
+ }
+ }
+
+ if( iPgIdx==nPg && nIdx>0 && pSeg->iLeafPgno!=1 ){
+ fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iLeafPgno);
+ }
+
+ assert_nc( nPg>4 || fts5GetU16(aPg)==0 );
+ fts5DataWrite(p, FTS5_SEGMENT_ROWID(iSegid,pSeg->iLeafPgno), aPg, nPg);
+ }
+ sqlite3_free(aIdx);
+}
+
+/*
+** This is called as part of flushing a delete to disk in 'secure-delete'
+** mode. It edits the segments within the database described by argument
+** pStruct to remove the entries for term zTerm, rowid iRowid.
+*/
+static void fts5FlushSecureDelete(
+ Fts5Index *p,
+ Fts5Structure *pStruct,
+ const char *zTerm,
+ i64 iRowid
+){
+ const int f = FTS5INDEX_QUERY_SKIPHASH;
+ int nTerm = (int)strlen(zTerm);
+ Fts5Iter *pIter = 0; /* Used to find term instance */
+
+ fts5MultiIterNew(p, pStruct, f, 0, (const u8*)zTerm, nTerm, -1, 0, &pIter);
+ if( fts5MultiIterEof(p, pIter)==0 ){
+ i64 iThis = fts5MultiIterRowid(pIter);
+ if( iThisrc==SQLITE_OK
+ && fts5MultiIterEof(p, pIter)==0
+ && iRowid==fts5MultiIterRowid(pIter)
+ ){
+ Fts5SegIter *pSeg = &pIter->aSeg[pIter->aFirst[1].iFirst];
+ fts5DoSecureDelete(p, pSeg);
+ }
+ }
+
+ fts5MultiIterFree(pIter);
+}
+
+
+/*
** Flush the contents of in-memory hash table iHash to a new level-0
** segment on disk. Also update the corresponding structure record.
**
@@ -4581,143 +5368,198 @@
/* Obtain a reference to the index structure and allocate a new segment-id
** for the new level-0 segment. */
pStruct = fts5StructureRead(p);
- iSegid = fts5AllocateSegid(p, pStruct);
fts5StructureInvalidate(p);
- if( iSegid ){
- const int pgsz = p->pConfig->pgsz;
- int eDetail = p->pConfig->eDetail;
- Fts5StructureSegment *pSeg; /* New segment within pStruct */
- Fts5Buffer *pBuf; /* Buffer in which to assemble leaf page */
- Fts5Buffer *pPgidx; /* Buffer in which to assemble pgidx */
-
- Fts5SegWriter writer;
- fts5WriteInit(p, &writer, iSegid);
-
- pBuf = &writer.writer.buf;
- pPgidx = &writer.writer.pgidx;
-
- /* fts5WriteInit() should have initialized the buffers to (most likely)
- ** the maximum space required. */
- assert( p->rc || pBuf->nSpace>=(pgsz + FTS5_DATA_PADDING) );
- assert( p->rc || pPgidx->nSpace>=(pgsz + FTS5_DATA_PADDING) );
-
- /* Begin scanning through hash table entries. This loop runs once for each
- ** term/doclist currently stored within the hash table. */
- if( p->rc==SQLITE_OK ){
- p->rc = sqlite3Fts5HashScanInit(pHash, 0, 0);
- }
- while( p->rc==SQLITE_OK && 0==sqlite3Fts5HashScanEof(pHash) ){
- const char *zTerm; /* Buffer containing term */
- const u8 *pDoclist; /* Pointer to doclist for this term */
- int nDoclist; /* Size of doclist in bytes */
-
- /* Write the term for this entry to disk. */
- sqlite3Fts5HashScanEntry(pHash, &zTerm, &pDoclist, &nDoclist);
- fts5WriteAppendTerm(p, &writer, (int)strlen(zTerm), (const u8*)zTerm);
- if( p->rc!=SQLITE_OK ) break;
-
- assert( writer.bFirstRowidInPage==0 );
- if( pgsz>=(pBuf->n + pPgidx->n + nDoclist + 1) ){
- /* The entire doclist will fit on the current leaf. */
- fts5BufferSafeAppendBlob(pBuf, pDoclist, nDoclist);
- }else{
- i64 iRowid = 0;
- u64 iDelta = 0;
- int iOff = 0;
-
- /* The entire doclist will not fit on this leaf. The following
- ** loop iterates through the poslists that make up the current
- ** doclist. */
- while( p->rc==SQLITE_OK && iOffp[0], (u16)pBuf->n); /* first rowid on page */
- pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid);
- writer.bFirstRowidInPage = 0;
- fts5WriteDlidxAppend(p, &writer, iRowid);
+ if( sqlite3Fts5HashIsEmpty(pHash)==0 ){
+ iSegid = fts5AllocateSegid(p, pStruct);
+ if( iSegid ){
+ const int pgsz = p->pConfig->pgsz;
+ int eDetail = p->pConfig->eDetail;
+ int bSecureDelete = p->pConfig->bSecureDelete;
+ Fts5StructureSegment *pSeg; /* New segment within pStruct */
+ Fts5Buffer *pBuf; /* Buffer in which to assemble leaf page */
+ Fts5Buffer *pPgidx; /* Buffer in which to assemble pgidx */
+
+ Fts5SegWriter writer;
+ fts5WriteInit(p, &writer, iSegid);
+
+ pBuf = &writer.writer.buf;
+ pPgidx = &writer.writer.pgidx;
+
+ /* fts5WriteInit() should have initialized the buffers to (most likely)
+ ** the maximum space required. */
+ assert( p->rc || pBuf->nSpace>=(pgsz + FTS5_DATA_PADDING) );
+ assert( p->rc || pPgidx->nSpace>=(pgsz + FTS5_DATA_PADDING) );
+
+ /* Begin scanning through hash table entries. This loop runs once for each
+ ** term/doclist currently stored within the hash table. */
+ if( p->rc==SQLITE_OK ){
+ p->rc = sqlite3Fts5HashScanInit(pHash, 0, 0);
+ }
+ while( p->rc==SQLITE_OK && 0==sqlite3Fts5HashScanEof(pHash) ){
+ const char *zTerm; /* Buffer containing term */
+ int nTerm; /* Size of zTerm in bytes */
+ const u8 *pDoclist; /* Pointer to doclist for this term */
+ int nDoclist; /* Size of doclist in bytes */
+
+ /* Get the term and doclist for this entry. */
+ sqlite3Fts5HashScanEntry(pHash, &zTerm, &pDoclist, &nDoclist);
+ nTerm = (int)strlen(zTerm);
+ if( bSecureDelete==0 ){
+ fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm);
+ if( p->rc!=SQLITE_OK ) break;
+ assert( writer.bFirstRowidInPage==0 );
+ }
+
+ if( !bSecureDelete && pgsz>=(pBuf->n + pPgidx->n + nDoclist + 1) ){
+ /* The entire doclist will fit on the current leaf. */
+ fts5BufferSafeAppendBlob(pBuf, pDoclist, nDoclist);
+ }else{
+ int bTermWritten = !bSecureDelete;
+ i64 iRowid = 0;
+ i64 iPrev = 0;
+ int iOff = 0;
+
+ /* The entire doclist will not fit on this leaf. The following
+ ** loop iterates through the poslists that make up the current
+ ** doclist. */
+ while( p->rc==SQLITE_OK && iOffrc!=SQLITE_OK || pDoclist[iOff]==0x01 ){
+ iOff++;
+ continue;
+ }
+ }
+ }
+
+ if( p->rc==SQLITE_OK && bTermWritten==0 ){
+ fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm);
+ bTermWritten = 1;
+ assert( p->rc!=SQLITE_OK || writer.bFirstRowidInPage==0 );
+ }
+
+ if( writer.bFirstRowidInPage ){
+ fts5PutU16(&pBuf->p[0], (u16)pBuf->n); /* first rowid on page */
+ pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid);
+ writer.bFirstRowidInPage = 0;
+ fts5WriteDlidxAppend(p, &writer, iRowid);
+ }else{
+ u64 iRowidDelta = (u64)iRowid - (u64)iPrev;
+ pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowidDelta);
+ }
if( p->rc!=SQLITE_OK ) break;
- }else{
- pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iDelta);
- }
- assert( pBuf->n<=pBuf->nSpace );
-
- if( eDetail==FTS5_DETAIL_NONE ){
- if( iOffp[pBuf->n++] = 0;
- iOff++;
+ assert( pBuf->n<=pBuf->nSpace );
+ iPrev = iRowid;
+
+ if( eDetail==FTS5_DETAIL_NONE ){
if( iOffp[pBuf->n++] = 0;
iOff++;
+ if( iOffp[pBuf->n++] = 0;
+ iOff++;
+ }
+ }
+ if( (pBuf->n + pPgidx->n)>=pgsz ){
+ fts5WriteFlushLeaf(p, &writer);
}
- }
- if( (pBuf->n + pPgidx->n)>=pgsz ){
- fts5WriteFlushLeaf(p, &writer);
- }
- }else{
- int bDummy;
- int nPos;
- int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDummy);
- nCopy += nPos;
- if( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){
- /* The entire poslist will fit on the current leaf. So copy
- ** it in one go. */
- fts5BufferSafeAppendBlob(pBuf, &pDoclist[iOff], nCopy);
}else{
- /* The entire poslist will not fit on this leaf. So it needs
- ** to be broken into sections. The only qualification being
- ** that each varint must be stored contiguously. */
- const u8 *pPoslist = &pDoclist[iOff];
- int iPos = 0;
- while( p->rc==SQLITE_OK ){
- int nSpace = pgsz - pBuf->n - pPgidx->n;
- int n = 0;
- if( (nCopy - iPos)<=nSpace ){
- n = nCopy - iPos;
- }else{
- n = fts5PoslistPrefix(&pPoslist[iPos], nSpace);
- }
- assert( n>0 );
- fts5BufferSafeAppendBlob(pBuf, &pPoslist[iPos], n);
- iPos += n;
- if( (pBuf->n + pPgidx->n)>=pgsz ){
- fts5WriteFlushLeaf(p, &writer);
+ int bDel = 0;
+ int nPos = 0;
+ int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDel);
+ if( bDel && bSecureDelete ){
+ fts5BufferAppendVarint(&p->rc, pBuf, nPos*2);
+ iOff += nCopy;
+ nCopy = nPos;
+ }else{
+ nCopy += nPos;
+ }
+ if( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){
+ /* The entire poslist will fit on the current leaf. So copy
+ ** it in one go. */
+ fts5BufferSafeAppendBlob(pBuf, &pDoclist[iOff], nCopy);
+ }else{
+ /* The entire poslist will not fit on this leaf. So it needs
+ ** to be broken into sections. The only qualification being
+ ** that each varint must be stored contiguously. */
+ const u8 *pPoslist = &pDoclist[iOff];
+ int iPos = 0;
+ while( p->rc==SQLITE_OK ){
+ int nSpace = pgsz - pBuf->n - pPgidx->n;
+ int n = 0;
+ if( (nCopy - iPos)<=nSpace ){
+ n = nCopy - iPos;
+ }else{
+ n = fts5PoslistPrefix(&pPoslist[iPos], nSpace);
+ }
+ assert( n>0 );
+ fts5BufferSafeAppendBlob(pBuf, &pPoslist[iPos], n);
+ iPos += n;
+ if( (pBuf->n + pPgidx->n)>=pgsz ){
+ fts5WriteFlushLeaf(p, &writer);
+ }
+ if( iPos>=nCopy ) break;
}
- if( iPos>=nCopy ) break;
}
+ iOff += nCopy;
}
- iOff += nCopy;
}
}
+
+ /* TODO2: Doclist terminator written here. */
+ /* pBuf->p[pBuf->n++] = '\0'; */
+ assert( pBuf->n<=pBuf->nSpace );
+ if( p->rc==SQLITE_OK ) sqlite3Fts5HashScanNext(pHash);
+ }
+ fts5WriteFinish(p, &writer, &pgnoLast);
+
+ assert( p->rc!=SQLITE_OK || bSecureDelete || pgnoLast>0 );
+ if( pgnoLast>0 ){
+ /* Update the Fts5Structure. It is written back to the database by the
+ ** fts5StructureRelease() call below. */
+ if( pStruct->nLevel==0 ){
+ fts5StructureAddLevel(&p->rc, &pStruct);
+ }
+ fts5StructureExtendLevel(&p->rc, pStruct, 0, 1, 0);
+ if( p->rc==SQLITE_OK ){
+ pSeg = &pStruct->aLevel[0].aSeg[ pStruct->aLevel[0].nSeg++ ];
+ pSeg->iSegid = iSegid;
+ pSeg->pgnoFirst = 1;
+ pSeg->pgnoLast = pgnoLast;
+ if( pStruct->nOriginCntr>0 ){
+ pSeg->iOrigin1 = pStruct->nOriginCntr;
+ pSeg->iOrigin2 = pStruct->nOriginCntr;
+ pSeg->nEntry = p->nPendingRow;
+ pStruct->nOriginCntr++;
+ }
+ pStruct->nSegment++;
+ }
+ fts5StructurePromote(p, 0, pStruct);
}
-
- /* TODO2: Doclist terminator written here. */
- /* pBuf->p[pBuf->n++] = '\0'; */
- assert( pBuf->n<=pBuf->nSpace );
- if( p->rc==SQLITE_OK ) sqlite3Fts5HashScanNext(pHash);
- }
- sqlite3Fts5HashClear(pHash);
- fts5WriteFinish(p, &writer, &pgnoLast);
-
- /* Update the Fts5Structure. It is written back to the database by the
- ** fts5StructureRelease() call below. */
- if( pStruct->nLevel==0 ){
- fts5StructureAddLevel(&p->rc, &pStruct);
- }
- fts5StructureExtendLevel(&p->rc, pStruct, 0, 1, 0);
- if( p->rc==SQLITE_OK ){
- pSeg = &pStruct->aLevel[0].aSeg[ pStruct->aLevel[0].nSeg++ ];
- pSeg->iSegid = iSegid;
- pSeg->pgnoFirst = 1;
- pSeg->pgnoLast = pgnoLast;
- pStruct->nSegment++;
}
- fts5StructurePromote(p, 0, pStruct);
}
- fts5IndexAutomerge(p, &pStruct, pgnoLast);
+ fts5IndexAutomerge(p, &pStruct, pgnoLast + p->nContentlessDelete);
fts5IndexCrisismerge(p, &pStruct);
fts5StructureWrite(p, pStruct);
fts5StructureRelease(pStruct);
@@ -4728,10 +5570,15 @@
*/
static void fts5IndexFlush(Fts5Index *p){
/* Unless it is empty, flush the hash table to disk */
- if( p->nPendingData ){
+ if( p->nPendingData || p->nContentlessDelete ){
assert( p->pHash );
- p->nPendingData = 0;
fts5FlushOneHash(p);
+ if( p->rc==SQLITE_OK ){
+ sqlite3Fts5HashClear(p->pHash);
+ p->nPendingData = 0;
+ p->nPendingRow = 0;
+ p->nContentlessDelete = 0;
+ }
}
}
@@ -4747,17 +5594,22 @@
/* Figure out if this structure requires optimization. A structure does
** not require optimization if either:
**
- ** + it consists of fewer than two segments, or
- ** + all segments are on the same level, or
- ** + all segments except one are currently inputs to a merge operation.
+ ** 1. it consists of fewer than two segments, or
+ ** 2. all segments are on the same level, or
+ ** 3. all segments except one are currently inputs to a merge operation.
**
- ** In the first case, return NULL. In the second, increment the ref-count
- ** on *pStruct and return a copy of the pointer to it.
+ ** In the first case, if there are no tombstone hash pages, return NULL. In
+ ** the second, increment the ref-count on *pStruct and return a copy of the
+ ** pointer to it.
*/
- if( nSeg<2 ) return 0;
+ if( nSeg==0 ) return 0;
for(i=0; inLevel; i++){
int nThis = pStruct->aLevel[i].nSeg;
- if( nThis==nSeg || (nThis==nSeg-1 && pStruct->aLevel[i].nMerge==nThis) ){
+ int nMerge = pStruct->aLevel[i].nMerge;
+ if( nThis>0 && (nThis==nSeg || (nThis==nSeg-1 && nMerge==nThis)) ){
+ if( nSeg==1 && nThis==1 && pStruct->aLevel[i].aSeg[0].nPgTombstone==0 ){
+ return 0;
+ }
fts5StructureRef(pStruct);
return pStruct;
}
@@ -4773,6 +5625,7 @@
pNew->nLevel = MIN(pStruct->nLevel+1, FTS5_MAX_LEVEL);
pNew->nRef = 1;
pNew->nWriteCounter = pStruct->nWriteCounter;
+ pNew->nOriginCntr = pStruct->nOriginCntr;
pLvl = &pNew->aLevel[pNew->nLevel-1];
pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&p->rc, nByte);
if( pLvl->aSeg ){
@@ -4803,6 +5656,7 @@
assert( p->rc==SQLITE_OK );
fts5IndexFlush(p);
+ assert( p->nContentlessDelete==0 );
pStruct = fts5StructureRead(p);
fts5StructureInvalidate(p);
@@ -4832,7 +5686,10 @@
** INSERT command.
*/
int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){
- Fts5Structure *pStruct = fts5StructureRead(p);
+ Fts5Structure *pStruct = 0;
+
+ fts5IndexFlush(p);
+ pStruct = fts5StructureRead(p);
if( pStruct ){
int nMin = p->pConfig->nUsermerge;
fts5StructureInvalidate(p);
@@ -4840,7 +5697,7 @@
Fts5Structure *pNew = fts5IndexOptimizeStruct(p, pStruct);
fts5StructureRelease(pStruct);
pStruct = pNew;
- nMin = 2;
+ nMin = 1;
nMerge = nMerge*-1;
}
if( pStruct && pStruct->nLevel ){
@@ -5354,6 +6211,9 @@
p->iWriteRowid = iRowid;
p->bDelete = bDelete;
+ if( bDelete==0 ){
+ p->nPendingRow++;
+ }
return fts5IndexReturn(p);
}
@@ -5391,6 +6251,9 @@
fts5StructureInvalidate(p);
fts5IndexDiscardData(p);
memset(&s, 0, sizeof(Fts5Structure));
+ if( p->pConfig->bContentlessDelete ){
+ s.nOriginCntr = 1;
+ }
fts5DataWrite(p, FTS5_AVERAGES_ROWID, (const u8*)"", 0);
fts5StructureWrite(p, &s);
return fts5IndexReturn(p);
@@ -5455,6 +6318,7 @@
sqlite3_finalize(p->pIdxDeleter);
sqlite3_finalize(p->pIdxSelect);
sqlite3_finalize(p->pDataVersion);
+ sqlite3_finalize(p->pDeleteFromIdx);
sqlite3Fts5HashFree(p->pHash);
sqlite3_free(p->zDataTbl);
sqlite3_free(p);
@@ -5781,6 +6645,347 @@
return fts5IndexReturn(p);
}
+/*
+** Retrieve the origin value that will be used for the segment currently
+** being accumulated in the in-memory hash table when it is flushed to
+** disk. If successful, SQLITE_OK is returned and (*piOrigin) set to
+** the queried value. Or, if an error occurs, an error code is returned
+** and the final value of (*piOrigin) is undefined.
+*/
+int sqlite3Fts5IndexGetOrigin(Fts5Index *p, i64 *piOrigin){
+ Fts5Structure *pStruct;
+ pStruct = fts5StructureRead(p);
+ if( pStruct ){
+ *piOrigin = pStruct->nOriginCntr;
+ fts5StructureRelease(pStruct);
+ }
+ return fts5IndexReturn(p);
+}
+
+/*
+** Buffer pPg contains a page of a tombstone hash table - one of nPg pages
+** associated with the same segment. This function adds rowid iRowid to
+** the hash table. The caller is required to guarantee that there is at
+** least one free slot on the page.
+**
+** If parameter bForce is false and the hash table is deemed to be full
+** (more than half of the slots are occupied), then non-zero is returned
+** and iRowid not inserted. Or, if bForce is true or if the hash table page
+** is not full, iRowid is inserted and zero returned.
+*/
+static int fts5IndexTombstoneAddToPage(
+ Fts5Data *pPg,
+ int bForce,
+ int nPg,
+ u64 iRowid
+){
+ const int szKey = TOMBSTONE_KEYSIZE(pPg);
+ const int nSlot = TOMBSTONE_NSLOT(pPg);
+ const int nElem = fts5GetU32(&pPg->p[4]);
+ int iSlot = (iRowid / nPg) % nSlot;
+ int nCollide = nSlot;
+
+ if( szKey==4 && iRowid>0xFFFFFFFF ) return 2;
+ if( iRowid==0 ){
+ pPg->p[1] = 0x01;
+ return 0;
+ }
+
+ if( bForce==0 && nElem>=(nSlot/2) ){
+ return 1;
+ }
+
+ fts5PutU32(&pPg->p[4], nElem+1);
+ if( szKey==4 ){
+ u32 *aSlot = (u32*)&pPg->p[8];
+ while( aSlot[iSlot] ){
+ iSlot = (iSlot + 1) % nSlot;
+ if( nCollide--==0 ) return 0;
+ }
+ fts5PutU32((u8*)&aSlot[iSlot], (u32)iRowid);
+ }else{
+ u64 *aSlot = (u64*)&pPg->p[8];
+ while( aSlot[iSlot] ){
+ iSlot = (iSlot + 1) % nSlot;
+ if( nCollide--==0 ) return 0;
+ }
+ fts5PutU64((u8*)&aSlot[iSlot], iRowid);
+ }
+
+ return 0;
+}
+
+/*
+** This function attempts to build a new hash containing all the keys
+** currently in the tombstone hash table for segment pSeg. The new
+** hash will be stored in the nOut buffers passed in array apOut[].
+** All pages of the new hash use key-size szKey (4 or 8).
+**
+** Return 0 if the hash is successfully rebuilt into the nOut pages.
+** Or non-zero if it is not (because one page became overfull). In this
+** case the caller should retry with a larger nOut parameter.
+**
+** Parameter pData1 is page iPg1 of the hash table being rebuilt.
+*/
+static int fts5IndexTombstoneRehash(
+ Fts5Index *p,
+ Fts5StructureSegment *pSeg, /* Segment to rebuild hash of */
+ Fts5Data *pData1, /* One page of current hash - or NULL */
+ int iPg1, /* Which page of the current hash is pData1 */
+ int szKey, /* 4 or 8, the keysize */
+ int nOut, /* Number of output pages */
+ Fts5Data **apOut /* Array of output hash pages */
+){
+ int ii;
+ int res = 0;
+
+ /* Initialize the headers of all the output pages */
+ for(ii=0; iip[0] = szKey;
+ fts5PutU32(&apOut[ii]->p[4], 0);
+ }
+
+ /* Loop through the current pages of the hash table. */
+ for(ii=0; res==0 && iinPgTombstone; ii++){
+ Fts5Data *pData = 0; /* Page ii of the current hash table */
+ Fts5Data *pFree = 0; /* Free this at the end of the loop */
+
+ if( iPg1==ii ){
+ pData = pData1;
+ }else{
+ pFree = pData = fts5DataRead(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid, ii));
+ }
+
+ if( pData ){
+ int szKeyIn = TOMBSTONE_KEYSIZE(pData);
+ int nSlotIn = (pData->nn - 8) / szKeyIn;
+ int iIn;
+ for(iIn=0; iInp[8];
+ if( aSlot[iIn] ) iVal = fts5GetU32((u8*)&aSlot[iIn]);
+ }else{
+ u64 *aSlot = (u64*)&pData->p[8];
+ if( aSlot[iIn] ) iVal = fts5GetU64((u8*)&aSlot[iIn]);
+ }
+
+ /* If iVal is not 0 at this point, insert it into the new hash table */
+ if( iVal ){
+ Fts5Data *pPg = apOut[(iVal % nOut)];
+ res = fts5IndexTombstoneAddToPage(pPg, 0, nOut, iVal);
+ if( res ) break;
+ }
+ }
+
+ /* If this is page 0 of the old hash, copy the rowid-0-flag from the
+ ** old hash to the new. */
+ if( ii==0 ){
+ apOut[0]->p[1] = pData->p[1];
+ }
+ }
+ fts5DataRelease(pFree);
+ }
+
+ return res;
+}
+
+/*
+** This is called to rebuild the hash table belonging to segment pSeg.
+** If parameter pData1 is not NULL, then one page of the existing hash table
+** has already been loaded - pData1, which is page iPg1. The key-size for
+** the new hash table is szKey (4 or 8).
+**
+** If successful, the new hash table is not written to disk. Instead,
+** output parameter (*pnOut) is set to the number of pages in the new
+** hash table, and (*papOut) to point to an array of buffers containing
+** the new page data.
+**
+** If an error occurs, an error code is left in the Fts5Index object and
+** both output parameters set to 0 before returning.
+*/
+static void fts5IndexTombstoneRebuild(
+ Fts5Index *p,
+ Fts5StructureSegment *pSeg, /* Segment to rebuild hash of */
+ Fts5Data *pData1, /* One page of current hash - or NULL */
+ int iPg1, /* Which page of the current hash is pData1 */
+ int szKey, /* 4 or 8, the keysize */
+ int *pnOut, /* OUT: Number of output pages */
+ Fts5Data ***papOut /* OUT: Output hash pages */
+){
+ const int MINSLOT = 32;
+ int nSlotPerPage = MAX(MINSLOT, (p->pConfig->pgsz - 8) / szKey);
+ int nSlot = 0; /* Number of slots in each output page */
+ int nOut = 0;
+
+ /* Figure out how many output pages (nOut) and how many slots per
+ ** page (nSlot). There are three possibilities:
+ **
+ ** 1. The hash table does not yet exist. In this case the new hash
+ ** table will consist of a single page with MINSLOT slots.
+ **
+ ** 2. The hash table exists but is currently a single page. In this
+ ** case an attempt is made to grow the page to accommodate the new
+ ** entry. The page is allowed to grow up to nSlotPerPage (see above)
+ ** slots.
+ **
+ ** 3. The hash table already consists of more than one page, or of
+ ** a single page already so large that it cannot be grown. In this
+ ** case the new hash consists of (nPg*2+1) pages of nSlotPerPage
+ ** slots each, where nPg is the current number of pages in the
+ ** hash table.
+ */
+ if( pSeg->nPgTombstone==0 ){
+ /* Case 1. */
+ nOut = 1;
+ nSlot = MINSLOT;
+ }else if( pSeg->nPgTombstone==1 ){
+ /* Case 2. */
+ int nElem = (int)fts5GetU32(&pData1->p[4]);
+ assert( pData1 && iPg1==0 );
+ nOut = 1;
+ nSlot = MAX(nElem*4, MINSLOT);
+ if( nSlot>nSlotPerPage ) nOut = 0;
+ }
+ if( nOut==0 ){
+ /* Case 3. */
+ nOut = (pSeg->nPgTombstone * 2 + 1);
+ nSlot = nSlotPerPage;
+ }
+
+ /* Allocate the required array and output pages */
+ while( 1 ){
+ int res = 0;
+ int ii = 0;
+ int szPage = 0;
+ Fts5Data **apOut = 0;
+
+ /* Allocate space for the new hash table */
+ assert( nSlot>=MINSLOT );
+ apOut = (Fts5Data**)sqlite3Fts5MallocZero(&p->rc, sizeof(Fts5Data*) * nOut);
+ szPage = 8 + nSlot*szKey;
+ for(ii=0; iirc,
+ sizeof(Fts5Data)+szPage
+ );
+ if( pNew ){
+ pNew->nn = szPage;
+ pNew->p = (u8*)&pNew[1];
+ apOut[ii] = pNew;
+ }
+ }
+
+ /* Rebuild the hash table. */
+ if( p->rc==SQLITE_OK ){
+ res = fts5IndexTombstoneRehash(p, pSeg, pData1, iPg1, szKey, nOut, apOut);
+ }
+ if( res==0 ){
+ if( p->rc ){
+ fts5IndexFreeArray(apOut, nOut);
+ apOut = 0;
+ nOut = 0;
+ }
+ *pnOut = nOut;
+ *papOut = apOut;
+ break;
+ }
+
+ /* If control flows to here, it was not possible to rebuild the hash
+ ** table. Free all buffers and then try again with more pages. */
+ assert( p->rc==SQLITE_OK );
+ fts5IndexFreeArray(apOut, nOut);
+ nSlot = nSlotPerPage;
+ nOut = nOut*2 + 1;
+ }
+}
+
+
+/*
+** Add a tombstone for rowid iRowid to segment pSeg.
+*/
+static void fts5IndexTombstoneAdd(
+ Fts5Index *p,
+ Fts5StructureSegment *pSeg,
+ u64 iRowid
+){
+ Fts5Data *pPg = 0;
+ int iPg = -1;
+ int szKey = 0;
+ int nHash = 0;
+ Fts5Data **apHash = 0;
+
+ p->nContentlessDelete++;
+
+ if( pSeg->nPgTombstone>0 ){
+ iPg = iRowid % pSeg->nPgTombstone;
+ pPg = fts5DataRead(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid,iPg));
+ if( pPg==0 ){
+ assert( p->rc!=SQLITE_OK );
+ return;
+ }
+
+ if( 0==fts5IndexTombstoneAddToPage(pPg, 0, pSeg->nPgTombstone, iRowid) ){
+ fts5DataWrite(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid,iPg), pPg->p, pPg->nn);
+ fts5DataRelease(pPg);
+ return;
+ }
+ }
+
+ /* Have to rebuild the hash table. First figure out the key-size (4 or 8). */
+ szKey = pPg ? TOMBSTONE_KEYSIZE(pPg) : 4;
+ if( iRowid>0xFFFFFFFF ) szKey = 8;
+
+ /* Rebuild the hash table */
+ fts5IndexTombstoneRebuild(p, pSeg, pPg, iPg, szKey, &nHash, &apHash);
+ assert( p->rc==SQLITE_OK || (nHash==0 && apHash==0) );
+
+ /* If all has succeeded, write the new rowid into one of the new hash
+ ** table pages, then write them all out to disk. */
+ if( nHash ){
+ int ii = 0;
+ fts5IndexTombstoneAddToPage(apHash[iRowid % nHash], 1, nHash, iRowid);
+ for(ii=0; iiiSegid, ii);
+ fts5DataWrite(p, iTombstoneRowid, apHash[ii]->p, apHash[ii]->nn);
+ }
+ pSeg->nPgTombstone = nHash;
+ fts5StructureWrite(p, p->pStruct);
+ }
+
+ fts5DataRelease(pPg);
+ fts5IndexFreeArray(apHash, nHash);
+}
+
+/*
+** Add iRowid to the tombstone list of the segment or segments that contain
+** rows from origin iOrigin. Return SQLITE_OK if successful, or an SQLite
+** error code otherwise.
+*/
+int sqlite3Fts5IndexContentlessDelete(Fts5Index *p, i64 iOrigin, i64 iRowid){
+ Fts5Structure *pStruct;
+ pStruct = fts5StructureRead(p);
+ if( pStruct ){
+ int bFound = 0; /* True after pSeg->nEntryTombstone incr. */
+ int iLvl;
+ for(iLvl=pStruct->nLevel-1; iLvl>=0; iLvl--){
+ int iSeg;
+ for(iSeg=pStruct->aLevel[iLvl].nSeg-1; iSeg>=0; iSeg--){
+ Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg];
+ if( pSeg->iOrigin1<=(u64)iOrigin && pSeg->iOrigin2>=(u64)iOrigin ){
+ if( bFound==0 ){
+ pSeg->nEntryTombstone++;
+ bFound = 1;
+ }
+ fts5IndexTombstoneAdd(p, pSeg, iRowid);
+ }
+ }
+ }
+ fts5StructureRelease(pStruct);
+ }
+ return fts5IndexReturn(p);
+}
/*************************************************************************
**************************************************************************
@@ -6085,6 +7290,7 @@
Fts5StructureSegment *pSeg /* Segment to check internal consistency */
){
Fts5Config *pConfig = p->pConfig;
+ int bSecureDelete = (pConfig->iVersion==FTS5_CURRENT_VERSION_SECUREDELETE);
sqlite3_stmt *pStmt = 0;
int rc2;
int iIdxPrevLeaf = pSeg->pgnoFirst-1;
@@ -6120,7 +7326,19 @@
** is also a rowid pointer within the leaf page header, it points to a
** location before the term. */
if( pLeaf->nn<=pLeaf->szLeaf ){
- p->rc = FTS5_CORRUPT;
+
+ if( nIdxTerm==0
+ && pConfig->iVersion==FTS5_CURRENT_VERSION_SECUREDELETE
+ && pLeaf->nn==pLeaf->szLeaf
+ && pLeaf->nn==4
+ ){
+ /* special case - the very first page in a segment keeps its %_idx
+ ** entry even if all the terms are removed from it by secure-delete
+ ** operations. */
+ }else{
+ p->rc = FTS5_CORRUPT;
+ }
+
}else{
int iOff; /* Offset of first term on leaf */
int iRowidOff; /* Offset of first rowid on leaf */
@@ -6184,9 +7402,12 @@
ASSERT_SZLEAF_OK(pLeaf);
if( iRowidOff>=pLeaf->szLeaf ){
p->rc = FTS5_CORRUPT;
- }else{
+ }else if( bSecureDelete==0 || iRowidOff>0 ){
+ i64 iDlRowid = fts5DlidxIterRowid(pDlidx);
fts5GetVarint(&pLeaf->p[iRowidOff], (u64*)&iRowid);
- if( iRowid!=fts5DlidxIterRowid(pDlidx) ) p->rc = FTS5_CORRUPT;
+ if( iRowidrc = FTS5_CORRUPT;
+ }
}
fts5DataRelease(pLeaf);
}
@@ -6316,13 +7537,14 @@
** function only.
*/
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
/*
** Decode a segment-data rowid from the %_data table. This function is
** the opposite of macro FTS5_SEGMENT_ROWID().
*/
static void fts5DecodeRowid(
i64 iRowid, /* Rowid from %_data table */
+ int *pbTombstone, /* OUT: Tombstone hash flag */
int *piSegid, /* OUT: Segment id */
int *pbDlidx, /* OUT: Dlidx flag */
int *piHeight, /* OUT: Height */
@@ -6338,13 +7560,16 @@
iRowid >>= FTS5_DATA_DLI_B;
*piSegid = (int)(iRowid & (((i64)1 << FTS5_DATA_ID_B) - 1));
+ iRowid >>= FTS5_DATA_ID_B;
+
+ *pbTombstone = (int)(iRowid & 0x0001);
}
-#endif /* SQLITE_TEST */
+#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){
- int iSegid, iHeight, iPgno, bDlidx; /* Rowid compenents */
- fts5DecodeRowid(iKey, &iSegid, &bDlidx, &iHeight, &iPgno);
+ int iSegid, iHeight, iPgno, bDlidx, bTomb; /* Rowid compenents */
+ fts5DecodeRowid(iKey, &bTomb, &iSegid, &bDlidx, &iHeight, &iPgno);
if( iSegid==0 ){
if( iKey==FTS5_AVERAGES_ROWID ){
@@ -6354,14 +7579,16 @@
}
}
else{
- sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{%ssegid=%d h=%d pgno=%d}",
- bDlidx ? "dlidx " : "", iSegid, iHeight, iPgno
+ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{%s%ssegid=%d h=%d pgno=%d}",
+ bDlidx ? "dlidx " : "",
+ bTomb ? "tombstone " : "",
+ iSegid, iHeight, iPgno
);
}
}
-#endif /* SQLITE_TEST */
+#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
static void fts5DebugStructure(
int *pRc, /* IN/OUT: error code */
Fts5Buffer *pBuf,
@@ -6376,16 +7603,22 @@
);
for(iSeg=0; iSegnSeg; iSeg++){
Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg];
- sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " {id=%d leaves=%d..%d}",
+ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " {id=%d leaves=%d..%d",
pSeg->iSegid, pSeg->pgnoFirst, pSeg->pgnoLast
);
+ if( pSeg->iOrigin1>0 ){
+ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " origin=%lld..%lld",
+ pSeg->iOrigin1, pSeg->iOrigin2
+ );
+ }
+ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}");
}
sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}");
}
}
-#endif /* SQLITE_TEST */
+#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
/*
** This is part of the fts5_decode() debugging aid.
**
@@ -6410,9 +7643,9 @@
fts5DebugStructure(pRc, pBuf, p);
fts5StructureRelease(p);
}
-#endif /* SQLITE_TEST */
+#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
/*
** This is part of the fts5_decode() debugging aid.
**
@@ -6435,9 +7668,9 @@
zSpace = " ";
}
}
-#endif /* SQLITE_TEST */
+#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
/*
** Buffer (a/n) is assumed to contain a list of serialized varints. Read
** each varint and append its string representation to buffer pBuf. Return
@@ -6454,9 +7687,9 @@
}
return iOff;
}
-#endif /* SQLITE_TEST */
+#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
/*
** The start of buffer (a/n) contains the start of a doclist. The doclist
** may or may not finish within the buffer. This function appends a text
@@ -6489,9 +7722,9 @@
return iOff;
}
-#endif /* SQLITE_TEST */
+#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
/*
** This function is part of the fts5_decode() debugging function. It is
** only ever used with detail=none tables.
@@ -6532,9 +7765,9 @@
sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %lld%s", iRowid, zApp);
}
}
-#endif /* SQLITE_TEST */
+#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
/*
** The implementation of user-defined scalar function fts5_decode().
*/
@@ -6545,6 +7778,7 @@
){
i64 iRowid; /* Rowid for record being decoded */
int iSegid,iHeight,iPgno,bDlidx;/* Rowid components */
+ int bTomb;
const u8 *aBlob; int n; /* Record to decode */
u8 *a = 0;
Fts5Buffer s; /* Build up text to return here */
@@ -6567,7 +7801,7 @@
if( a==0 ) goto decode_out;
if( n>0 ) memcpy(a, aBlob, n);
- fts5DecodeRowid(iRowid, &iSegid, &bDlidx, &iHeight, &iPgno);
+ fts5DecodeRowid(iRowid, &bTomb, &iSegid, &bDlidx, &iHeight, &iPgno);
fts5DebugRowid(&rc, &s, iRowid);
if( bDlidx ){
@@ -6586,6 +7820,28 @@
" %d(%lld)", lvl.iLeafPgno, lvl.iRowid
);
}
+ }else if( bTomb ){
+ u32 nElem = fts5GetU32(&a[4]);
+ int szKey = (aBlob[0]==4 || aBlob[0]==8) ? aBlob[0] : 8;
+ int nSlot = (n - 8) / szKey;
+ int ii;
+ sqlite3Fts5BufferAppendPrintf(&rc, &s, " nElem=%d", (int)nElem);
+ if( aBlob[1] ){
+ sqlite3Fts5BufferAppendPrintf(&rc, &s, " 0");
+ }
+ for(ii=0; iiszLeaf ){
+ rc = FTS5_CORRUPT;
+ }else{
+ fts5DecodeRowidList(&rc, &s, &a[iOff], iTermOff-iOff);
+ }
iOff = iTermOff;
if( iOffestimatedCost = (double)100;
+ pIdxInfo->estimatedRows = 100;
+ pIdxInfo->idxNum = 0;
+ for(i=0, p=pIdxInfo->aConstraint; inConstraint; i++, p++){
+ if( p->usable==0 ) continue;
+ if( p->op==SQLITE_INDEX_CONSTRAINT_EQ && p->iColumn==11 ){
+ rc = SQLITE_OK;
+ pIdxInfo->aConstraintUsage[i].omit = 1;
+ pIdxInfo->aConstraintUsage[i].argvIndex = 1;
+ break;
+ }
+ }
+ return rc;
+}
+
+/*
+** This method is the destructor for bytecodevtab objects.
+*/
+static int fts5structDisconnectMethod(sqlite3_vtab *pVtab){
+ Fts5StructVtab *p = (Fts5StructVtab*)pVtab;
+ sqlite3_free(p);
+ return SQLITE_OK;
+}
+
+/*
+** Constructor for a new bytecodevtab_cursor object.
+*/
+static int fts5structOpenMethod(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCsr){
+ int rc = SQLITE_OK;
+ Fts5StructVcsr *pNew = 0;
+
+ pNew = sqlite3Fts5MallocZero(&rc, sizeof(*pNew));
+ *ppCsr = (sqlite3_vtab_cursor*)pNew;
+
+ return SQLITE_OK;
+}
+
+/*
+** Destructor for a bytecodevtab_cursor.
+*/
+static int fts5structCloseMethod(sqlite3_vtab_cursor *cur){
+ Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur;
+ fts5StructureRelease(pCsr->pStruct);
+ sqlite3_free(pCsr);
+ return SQLITE_OK;
+}
+
+
+/*
+** Advance a bytecodevtab_cursor to its next row of output.
+*/
+static int fts5structNextMethod(sqlite3_vtab_cursor *cur){
+ Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur;
+ Fts5Structure *p = pCsr->pStruct;
+
+ assert( pCsr->pStruct );
+ pCsr->iSeg++;
+ pCsr->iRowid++;
+ while( pCsr->iLevelnLevel && pCsr->iSeg>=p->aLevel[pCsr->iLevel].nSeg ){
+ pCsr->iLevel++;
+ pCsr->iSeg = 0;
+ }
+ if( pCsr->iLevel>=p->nLevel ){
+ fts5StructureRelease(pCsr->pStruct);
+ pCsr->pStruct = 0;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Return TRUE if the cursor has been moved off of the last
+** row of output.
+*/
+static int fts5structEofMethod(sqlite3_vtab_cursor *cur){
+ Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur;
+ return pCsr->pStruct==0;
+}
+
+static int fts5structRowidMethod(
+ sqlite3_vtab_cursor *cur,
+ sqlite_int64 *piRowid
+){
+ Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur;
+ *piRowid = pCsr->iRowid;
+ return SQLITE_OK;
+}
+
+/*
+** Return values of columns for the row at which the bytecodevtab_cursor
+** is currently pointing.
+*/
+static int fts5structColumnMethod(
+ sqlite3_vtab_cursor *cur, /* The cursor */
+ sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
+ int i /* Which column to return */
+){
+ Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur;
+ Fts5Structure *p = pCsr->pStruct;
+ Fts5StructureSegment *pSeg = &p->aLevel[pCsr->iLevel].aSeg[pCsr->iSeg];
+
+ switch( i ){
+ case 0: /* level */
+ sqlite3_result_int(ctx, pCsr->iLevel);
+ break;
+ case 1: /* segment */
+ sqlite3_result_int(ctx, pCsr->iSeg);
+ break;
+ case 2: /* merge */
+ sqlite3_result_int(ctx, pCsr->iSeg < p->aLevel[pCsr->iLevel].nMerge);
+ break;
+ case 3: /* segid */
+ sqlite3_result_int(ctx, pSeg->iSegid);
+ break;
+ case 4: /* leaf1 */
+ sqlite3_result_int(ctx, pSeg->pgnoFirst);
+ break;
+ case 5: /* leaf2 */
+ sqlite3_result_int(ctx, pSeg->pgnoLast);
+ break;
+ case 6: /* origin1 */
+ sqlite3_result_int64(ctx, pSeg->iOrigin1);
+ break;
+ case 7: /* origin2 */
+ sqlite3_result_int64(ctx, pSeg->iOrigin2);
+ break;
+ case 8: /* npgtombstone */
+ sqlite3_result_int(ctx, pSeg->nPgTombstone);
+ break;
+ case 9: /* nentrytombstone */
+ sqlite3_result_int64(ctx, pSeg->nEntryTombstone);
+ break;
+ case 10: /* nentry */
+ sqlite3_result_int64(ctx, pSeg->nEntry);
+ break;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Initialize a cursor.
+**
+** idxNum==0 means show all subprograms
+** idxNum==1 means show only the main bytecode and omit subprograms.
+*/
+static int fts5structFilterMethod(
+ sqlite3_vtab_cursor *pVtabCursor,
+ int idxNum, const char *idxStr,
+ int argc, sqlite3_value **argv
+){
+ Fts5StructVcsr *pCsr = (Fts5StructVcsr *)pVtabCursor;
+ int rc = SQLITE_OK;
+
+ const u8 *aBlob = 0;
+ int nBlob = 0;
+
+ assert( argc==1 );
+ fts5StructureRelease(pCsr->pStruct);
+ pCsr->pStruct = 0;
+
+ nBlob = sqlite3_value_bytes(argv[0]);
+ aBlob = (const u8*)sqlite3_value_blob(argv[0]);
+ rc = fts5StructureDecode(aBlob, nBlob, 0, &pCsr->pStruct);
+ if( rc==SQLITE_OK ){
+ pCsr->iLevel = 0;
+ pCsr->iRowid = 0;
+ pCsr->iSeg = -1;
+ rc = fts5structNextMethod(pVtabCursor);
+ }
+
+ return rc;
+}
+
+#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */
/*
** This is called as part of registering the FTS5 module with database
@@ -6790,7 +8277,7 @@
** SQLite error code is returned instead.
*/
int sqlite3Fts5IndexInit(sqlite3 *db){
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
int rc = sqlite3_create_function(
db, "fts5_decode", 2, SQLITE_UTF8, 0, fts5DecodeFunction, 0, 0
);
@@ -6807,6 +8294,37 @@
db, "fts5_rowid", -1, SQLITE_UTF8, 0, fts5RowidFunction, 0, 0
);
}
+
+ if( rc==SQLITE_OK ){
+ static const sqlite3_module fts5structure_module = {
+ 0, /* iVersion */
+ 0, /* xCreate */
+ fts5structConnectMethod, /* xConnect */
+ fts5structBestIndexMethod, /* xBestIndex */
+ fts5structDisconnectMethod, /* xDisconnect */
+ 0, /* xDestroy */
+ fts5structOpenMethod, /* xOpen */
+ fts5structCloseMethod, /* xClose */
+ fts5structFilterMethod, /* xFilter */
+ fts5structNextMethod, /* xNext */
+ fts5structEofMethod, /* xEof */
+ fts5structColumnMethod, /* xColumn */
+ fts5structRowidMethod, /* xRowid */
+ 0, /* xUpdate */
+ 0, /* xBegin */
+ 0, /* xSync */
+ 0, /* xCommit */
+ 0, /* xRollback */
+ 0, /* xFindFunction */
+ 0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
+ };
+ rc = sqlite3_create_module(db, "fts5_structure", &fts5structure_module, 0);
+ }
return rc;
#else
return SQLITE_OK;
diff -Nru sqlite3-3.41.0-0/ext/fts5/fts5_main.c sqlite3-3.44.0-0/ext/fts5/fts5_main.c
--- sqlite3-3.41.0-0/ext/fts5/fts5_main.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5_main.c 2023-11-04 14:24:27.000000000 +0000
@@ -117,6 +117,8 @@
Fts5Storage *pStorage; /* Document store */
Fts5Global *pGlobal; /* Global (connection wide) data */
Fts5Cursor *pSortCsr; /* Sort data from this cursor */
+ int iSavepoint; /* Successful xSavepoint()+1 */
+ int bInSavepoint;
#ifdef SQLITE_DEBUG
struct Fts5TransactionState ts;
#endif
@@ -405,6 +407,13 @@
pConfig->pzErrmsg = 0;
}
+ if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){
+ rc = sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, (int)1);
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS);
+ }
+
if( rc!=SQLITE_OK ){
fts5FreeVtab(pTab);
pTab = 0;
@@ -1329,6 +1338,9 @@
pCsr->iFirstRowid = fts5GetRowidLimit(pRowidGe, SMALLEST_INT64);
}
+ rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex);
+ if( rc!=SQLITE_OK ) goto filter_out;
+
if( pTab->pSortCsr ){
/* If pSortCsr is non-NULL, then this call is being made as part of
** processing for a "... MATCH ORDER BY rank" query (ePlan is
@@ -1351,6 +1363,7 @@
pCsr->pExpr = pTab->pSortCsr->pExpr;
rc = fts5CursorFirst(pTab, pCsr, bDesc);
}else if( pCsr->pExpr ){
+ assert( rc==SQLITE_OK );
rc = fts5CursorParseRank(pConfig, pCsr, pRank);
if( rc==SQLITE_OK ){
if( bOrderByRank ){
@@ -1522,6 +1535,7 @@
Fts5Config *pConfig = pTab->p.pConfig;
int rc = SQLITE_OK;
int bError = 0;
+ int bLoadConfig = 0;
if( 0==sqlite3_stricmp("delete-all", zCmd) ){
if( pConfig->eContent==FTS5_CONTENT_NORMAL ){
@@ -1533,6 +1547,7 @@
}else{
rc = sqlite3Fts5StorageDeleteAll(pTab->pStorage);
}
+ bLoadConfig = 1;
}else if( 0==sqlite3_stricmp("rebuild", zCmd) ){
if( pConfig->eContent==FTS5_CONTENT_NONE ){
fts5SetVtabError(pTab,
@@ -1542,6 +1557,7 @@
}else{
rc = sqlite3Fts5StorageRebuild(pTab->pStorage);
}
+ bLoadConfig = 1;
}else if( 0==sqlite3_stricmp("optimize", zCmd) ){
rc = sqlite3Fts5StorageOptimize(pTab->pStorage);
}else if( 0==sqlite3_stricmp("merge", zCmd) ){
@@ -1554,6 +1570,8 @@
}else if( 0==sqlite3_stricmp("prefix-index", zCmd) ){
pConfig->bPrefixIndex = sqlite3_value_int(pVal);
#endif
+ }else if( 0==sqlite3_stricmp("flush", zCmd) ){
+ rc = sqlite3Fts5FlushToDisk(&pTab->p);
}else{
rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex);
if( rc==SQLITE_OK ){
@@ -1567,6 +1585,12 @@
}
}
}
+
+ if( rc==SQLITE_OK && bLoadConfig ){
+ pTab->p.pConfig->iCookie--;
+ rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex);
+ }
+
return rc;
}
@@ -1623,6 +1647,7 @@
Fts5Config *pConfig = pTab->p.pConfig;
int eType0; /* value_type() of apVal[0] */
int rc = SQLITE_OK; /* Return code */
+ int bUpdateOrDelete = 0;
/* A transaction must be open when this is called. */
assert( pTab->ts.eState==1 || pTab->ts.eState==2 );
@@ -1633,6 +1658,11 @@
|| sqlite3_value_type(apVal[0])==SQLITE_NULL
);
assert( pTab->p.pConfig->pzErrmsg==0 );
+ if( pConfig->pgsz==0 ){
+ rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+
pTab->p.pConfig->pzErrmsg = &pTab->p.base.zErrMsg;
/* Put any active cursors into REQUIRE_SEEK state. */
@@ -1647,7 +1677,14 @@
if( pConfig->eContent!=FTS5_CONTENT_NORMAL
&& 0==sqlite3_stricmp("delete", z)
){
- rc = fts5SpecialDelete(pTab, apVal);
+ if( pConfig->bContentlessDelete ){
+ fts5SetVtabError(pTab,
+ "'delete' may not be used with a contentless_delete=1 table"
+ );
+ rc = SQLITE_ERROR;
+ }else{
+ rc = fts5SpecialDelete(pTab, apVal);
+ }
}else{
rc = fts5SpecialInsert(pTab, z, apVal[2 + pConfig->nCol + 1]);
}
@@ -1664,7 +1701,7 @@
** Cases 3 and 4 may violate the rowid constraint.
*/
int eConflict = SQLITE_ABORT;
- if( pConfig->eContent==FTS5_CONTENT_NORMAL ){
+ if( pConfig->eContent==FTS5_CONTENT_NORMAL || pConfig->bContentlessDelete ){
eConflict = sqlite3_vtab_on_conflict(pConfig->db);
}
@@ -1672,8 +1709,12 @@
assert( nArg!=1 || eType0==SQLITE_INTEGER );
/* Filter out attempts to run UPDATE or DELETE on contentless tables.
- ** This is not suported. */
- if( eType0==SQLITE_INTEGER && fts5IsContentless(pTab) ){
+ ** This is not suported. Except - they are both supported if the CREATE
+ ** VIRTUAL TABLE statement contained "contentless_delete=1". */
+ if( eType0==SQLITE_INTEGER
+ && pConfig->eContent==FTS5_CONTENT_NONE
+ && pConfig->bContentlessDelete==0
+ ){
pTab->p.base.zErrMsg = sqlite3_mprintf(
"cannot %s contentless fts5 table: %s",
(nArg>1 ? "UPDATE" : "DELETE from"), pConfig->zName
@@ -1685,6 +1726,7 @@
else if( nArg==1 ){
i64 iDel = sqlite3_value_int64(apVal[0]); /* Rowid to delete */
rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, 0);
+ bUpdateOrDelete = 1;
}
/* INSERT or UPDATE */
@@ -1696,10 +1738,12 @@
}
else if( eType0!=SQLITE_INTEGER ){
- /* If this is a REPLACE, first remove the current entry (if any) */
+ /* An INSERT statement. If the conflict-mode is REPLACE, first remove
+ ** the current entry (if any). */
if( eConflict==SQLITE_REPLACE && eType1==SQLITE_INTEGER ){
i64 iNew = sqlite3_value_int64(apVal[1]); /* Rowid to delete */
rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0);
+ bUpdateOrDelete = 1;
}
fts5StorageInsert(&rc, pTab, apVal, pRowid);
}
@@ -1728,10 +1772,24 @@
rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0);
fts5StorageInsert(&rc, pTab, apVal, pRowid);
}
+ bUpdateOrDelete = 1;
}
}
}
+ if( rc==SQLITE_OK
+ && bUpdateOrDelete
+ && pConfig->bSecureDelete
+ && pConfig->iVersion==FTS5_CURRENT_VERSION
+ ){
+ rc = sqlite3Fts5StorageConfigValue(
+ pTab->pStorage, "version", 0, FTS5_CURRENT_VERSION_SECUREDELETE
+ );
+ if( rc==SQLITE_OK ){
+ pConfig->iVersion = FTS5_CURRENT_VERSION_SECUREDELETE;
+ }
+ }
+
pTab->p.pConfig->pzErrmsg = 0;
return rc;
}
@@ -1744,8 +1802,7 @@
Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
fts5CheckTransactionState(pTab, FTS5_SYNC, 0);
pTab->p.pConfig->pzErrmsg = &pTab->p.base.zErrMsg;
- fts5TripCursors(pTab);
- rc = sqlite3Fts5StorageSync(pTab->pStorage);
+ rc = sqlite3Fts5FlushToDisk(&pTab->p);
pTab->p.pConfig->pzErrmsg = 0;
return rc;
}
@@ -2512,6 +2569,12 @@
sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1));
}
pConfig->pzErrmsg = 0;
+ }else if( pConfig->bContentlessDelete && sqlite3_vtab_nochange(pCtx) ){
+ char *zErr = sqlite3_mprintf("cannot UPDATE a subset of "
+ "columns on fts5 contentless-delete table: %s", pConfig->zName
+ );
+ sqlite3_result_error(pCtx, zErr, -1);
+ sqlite3_free(zErr);
}
return rc;
}
@@ -2550,8 +2613,12 @@
sqlite3_vtab *pVtab, /* Virtual table handle */
const char *zName /* New name of table */
){
+ int rc;
Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
- return sqlite3Fts5StorageRename(pTab->pStorage, zName);
+ pTab->bInSavepoint = 1;
+ rc = sqlite3Fts5StorageRename(pTab->pStorage, zName);
+ pTab->bInSavepoint = 0;
+ return rc;
}
int sqlite3Fts5FlushToDisk(Fts5Table *pTab){
@@ -2565,9 +2632,29 @@
** Flush the contents of the pending-terms table to disk.
*/
static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){
- UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */
- fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_SAVEPOINT, iSavepoint);
- return sqlite3Fts5FlushToDisk((Fts5Table*)pVtab);
+ Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
+ int rc = SQLITE_OK;
+ char *zSql = 0;
+ fts5CheckTransactionState(pTab, FTS5_SAVEPOINT, iSavepoint);
+
+ if( pTab->bInSavepoint==0 ){
+ zSql = sqlite3_mprintf("INSERT INTO %Q.%Q(%Q) VALUES('flush')",
+ pTab->p.pConfig->zDb, pTab->p.pConfig->zName, pTab->p.pConfig->zName
+ );
+ if( zSql ){
+ pTab->bInSavepoint = 1;
+ rc = sqlite3_exec(pTab->p.pConfig->db, zSql, 0, 0, 0);
+ pTab->bInSavepoint = 0;
+ sqlite3_free(zSql);
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ if( rc==SQLITE_OK ){
+ pTab->iSavepoint = iSavepoint+1;
+ }
+ }
+
+ return rc;
}
/*
@@ -2576,9 +2663,16 @@
** This is a no-op.
*/
static int fts5ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){
- UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */
- fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_RELEASE, iSavepoint);
- return sqlite3Fts5FlushToDisk((Fts5Table*)pVtab);
+ Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
+ int rc = SQLITE_OK;
+ fts5CheckTransactionState(pTab, FTS5_RELEASE, iSavepoint);
+ if( (iSavepoint+1)iSavepoint ){
+ rc = sqlite3Fts5FlushToDisk(&pTab->p);
+ if( rc==SQLITE_OK ){
+ pTab->iSavepoint = iSavepoint;
+ }
+ }
+ return rc;
}
/*
@@ -2588,10 +2682,14 @@
*/
static int fts5RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){
Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
- UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */
+ int rc = SQLITE_OK;
fts5CheckTransactionState(pTab, FTS5_ROLLBACKTO, iSavepoint);
fts5TripCursors(pTab);
- return sqlite3Fts5StorageRollback(pTab->pStorage);
+ pTab->p.pConfig->pgsz = 0;
+ if( (iSavepoint+1)<=pTab->iSavepoint ){
+ rc = sqlite3Fts5StorageRollback(pTab->pStorage);
+ }
+ return rc;
}
/*
@@ -2811,9 +2909,46 @@
return 0;
}
+/*
+** Run an integrity check on the FTS5 data structures. Return a string
+** if anything is found amiss. Return a NULL pointer if everything is
+** OK.
+*/
+static int fts5Integrity(
+ sqlite3_vtab *pVtab, /* the FTS5 virtual table to check */
+ const char *zSchema, /* Name of schema in which this table lives */
+ const char *zTabname, /* Name of the table itself */
+ int isQuick, /* True if this is a quick-check */
+ 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);
+ 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);
+ }else if( rc!=SQLITE_OK ){
+ *pzErr = sqlite3_mprintf("unable to validate the inverted index for"
+ " FTS5 table %s.%s: %s",
+ zSchema, zTabname, zErr);
+ }
+ sqlite3_free(zErr);
+ return SQLITE_OK;
+}
+
static int fts5Init(sqlite3 *db){
static const sqlite3_module fts5Mod = {
- /* iVersion */ 3,
+ /* iVersion */ 4,
/* xCreate */ fts5CreateMethod,
/* xConnect */ fts5ConnectMethod,
/* xBestIndex */ fts5BestIndexMethod,
@@ -2836,7 +2971,8 @@
/* xSavepoint */ fts5SavepointMethod,
/* xRelease */ fts5ReleaseMethod,
/* xRollbackTo */ fts5RollbackToMethod,
- /* xShadowName */ fts5ShadowName
+ /* xShadowName */ fts5ShadowName,
+ /* xIntegrity */ fts5Integrity
};
int rc;
diff -Nru sqlite3-3.41.0-0/ext/fts5/fts5_storage.c sqlite3-3.44.0-0/ext/fts5/fts5_storage.c
--- sqlite3-3.41.0-0/ext/fts5/fts5_storage.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5_storage.c 2023-11-04 14:24:27.000000000 +0000
@@ -77,10 +77,10 @@
"INSERT INTO %Q.'%q_content' VALUES(%s)", /* INSERT_CONTENT */
"REPLACE INTO %Q.'%q_content' VALUES(%s)", /* REPLACE_CONTENT */
"DELETE FROM %Q.'%q_content' WHERE id=?", /* DELETE_CONTENT */
- "REPLACE INTO %Q.'%q_docsize' VALUES(?,?)", /* REPLACE_DOCSIZE */
+ "REPLACE INTO %Q.'%q_docsize' VALUES(?,?%s)", /* REPLACE_DOCSIZE */
"DELETE FROM %Q.'%q_docsize' WHERE id=?", /* DELETE_DOCSIZE */
- "SELECT sz FROM %Q.'%q_docsize' WHERE id=?", /* LOOKUP_DOCSIZE */
+ "SELECT sz%s FROM %Q.'%q_docsize' WHERE id=?", /* LOOKUP_DOCSIZE */
"REPLACE INTO %Q.'%q_config' VALUES(?,?)", /* REPLACE_CONFIG */
"SELECT %s FROM %s AS T", /* SCAN */
@@ -128,6 +128,19 @@
break;
}
+ case FTS5_STMT_REPLACE_DOCSIZE:
+ zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName,
+ (pC->bContentlessDelete ? ",?" : "")
+ );
+ break;
+
+ case FTS5_STMT_LOOKUP_DOCSIZE:
+ zSql = sqlite3_mprintf(azStmt[eStmt],
+ (pC->bContentlessDelete ? ",origin" : ""),
+ pC->zDb, pC->zName
+ );
+ break;
+
default:
zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName);
break;
@@ -317,9 +330,11 @@
}
if( rc==SQLITE_OK && pConfig->bColumnsize ){
- rc = sqlite3Fts5CreateTable(
- pConfig, "docsize", "id INTEGER PRIMARY KEY, sz BLOB", 0, pzErr
- );
+ const char *zCols = "id INTEGER PRIMARY KEY, sz BLOB";
+ if( pConfig->bContentlessDelete ){
+ zCols = "id INTEGER PRIMARY KEY, sz BLOB, origin INTEGER";
+ }
+ rc = sqlite3Fts5CreateTable(pConfig, "docsize", zCols, 0, pzErr);
}
if( rc==SQLITE_OK ){
rc = sqlite3Fts5CreateTable(
@@ -396,7 +411,7 @@
){
Fts5Config *pConfig = p->pConfig;
sqlite3_stmt *pSeek = 0; /* SELECT to read row iDel from %_data */
- int rc; /* Return code */
+ int rc = SQLITE_OK; /* Return code */
int rc2; /* sqlite3_reset() return code */
int iCol;
Fts5InsertCtx ctx;
@@ -412,7 +427,6 @@
ctx.pStorage = p;
ctx.iCol = -1;
- rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel);
for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){
if( pConfig->abUnindexed[iCol-1]==0 ){
const char *zText;
@@ -449,6 +463,37 @@
return rc;
}
+/*
+** This function is called to process a DELETE on a contentless_delete=1
+** table. It adds the tombstone required to delete the entry with rowid
+** iDel. If successful, SQLITE_OK is returned. Or, if an error occurs,
+** an SQLite error code.
+*/
+static int fts5StorageContentlessDelete(Fts5Storage *p, i64 iDel){
+ i64 iOrigin = 0;
+ sqlite3_stmt *pLookup = 0;
+ int rc = SQLITE_OK;
+
+ assert( p->pConfig->bContentlessDelete );
+ assert( p->pConfig->eContent==FTS5_CONTENT_NONE );
+
+ /* Look up the origin of the document in the %_docsize table. Store
+ ** this in stack variable iOrigin. */
+ rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0);
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_int64(pLookup, 1, iDel);
+ if( SQLITE_ROW==sqlite3_step(pLookup) ){
+ iOrigin = sqlite3_column_int64(pLookup, 1);
+ }
+ rc = sqlite3_reset(pLookup);
+ }
+
+ if( rc==SQLITE_OK && iOrigin!=0 ){
+ rc = sqlite3Fts5IndexContentlessDelete(p->pIndex, iOrigin, iDel);
+ }
+
+ return rc;
+}
/*
** Insert a record into the %_docsize table. Specifically, do:
@@ -469,10 +514,17 @@
rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0);
if( rc==SQLITE_OK ){
sqlite3_bind_int64(pReplace, 1, iRowid);
- sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC);
- sqlite3_step(pReplace);
- rc = sqlite3_reset(pReplace);
- sqlite3_bind_null(pReplace, 2);
+ if( p->pConfig->bContentlessDelete ){
+ i64 iOrigin = 0;
+ rc = sqlite3Fts5IndexGetOrigin(p->pIndex, &iOrigin);
+ sqlite3_bind_int64(pReplace, 3, iOrigin);
+ }
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC);
+ sqlite3_step(pReplace);
+ rc = sqlite3_reset(pReplace);
+ sqlite3_bind_null(pReplace, 2);
+ }
}
}
return rc;
@@ -536,7 +588,15 @@
/* Delete the index records */
if( rc==SQLITE_OK ){
- rc = fts5StorageDeleteFromIndex(p, iDel, apVal);
+ rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel);
+ }
+
+ if( rc==SQLITE_OK ){
+ if( p->pConfig->bContentlessDelete ){
+ rc = fts5StorageContentlessDelete(p, iDel);
+ }else{
+ rc = fts5StorageDeleteFromIndex(p, iDel, apVal);
+ }
}
/* Delete the %_docsize record */
@@ -1124,7 +1184,9 @@
i64 iLastRowid = sqlite3_last_insert_rowid(p->pConfig->db);
if( p->bTotalsValid ){
rc = fts5StorageSaveTotals(p);
- p->bTotalsValid = 0;
+ if( rc==SQLITE_OK ){
+ p->bTotalsValid = 0;
+ }
}
if( rc==SQLITE_OK ){
rc = sqlite3Fts5IndexSync(p->pIndex);
diff -Nru sqlite3-3.41.0-0/ext/fts5/fts5_test_tok.c sqlite3-3.44.0-0/ext/fts5/fts5_test_tok.c
--- sqlite3-3.41.0-0/ext/fts5/fts5_test_tok.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5_test_tok.c 2023-11-04 14:24:27.000000000 +0000
@@ -472,7 +472,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
int rc; /* Return code */
diff -Nru sqlite3-3.41.0-0/ext/fts5/fts5_vocab.c sqlite3-3.44.0-0/ext/fts5/fts5_vocab.c
--- sqlite3-3.41.0-0/ext/fts5/fts5_vocab.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5_vocab.c 2023-11-04 14:24:27.000000000 +0000
@@ -783,7 +783,8 @@
/* xSavepoint */ 0,
/* xRelease */ 0,
/* xRollbackTo */ 0,
- /* xShadowName */ 0
+ /* xShadowName */ 0,
+ /* xIntegrity */ 0
};
void *p = (void*)pGlobal;
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5_common.tcl sqlite3-3.44.0-0/ext/fts5/test/fts5_common.tcl
--- sqlite3-3.41.0-0/ext/fts5/test/fts5_common.tcl 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5_common.tcl 2023-11-04 14:24:27.000000000 +0000
@@ -594,6 +594,10 @@
list
}
+proc dump {tname} {
+ execsql_pp "SELECT * FROM ${tname}_idx"
+ execsql_pp "SELECT id, quote(block), fts5_decode(id,block) FROM ${tname}_data"
+}
#-------------------------------------------------------------------------
# Code for a simple Tcl tokenizer that supports synonyms at query time.
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5aa.test sqlite3-3.44.0-0/ext/fts5/test/fts5aa.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5aa.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5aa.test 2023-11-04 14:24:27.000000000 +0000
@@ -65,7 +65,9 @@
do_execsql_test 2.4 {
INSERT INTO t1(t1) VALUES('integrity-check');
-}
+ PRAGMA integrity_check;
+ PRAGMA integrity_check(t1);
+} {ok ok}
#-------------------------------------------------------------------------
@@ -88,6 +90,7 @@
} {
do_execsql_test 3.$i.1 { INSERT INTO t1 VALUES($x, $y) }
do_execsql_test 3.$i.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
+ do_execsql_test 3.$i.3 { PRAGMA integrity_check(t1) } ok
if {[set_test_counter errors]} break
}
@@ -135,7 +138,7 @@
10 {ddd abcde dddd dd c} {dddd c c d abcde}
} {
do_execsql_test 5.$i.1 { INSERT INTO t1 VALUES($x, $y) }
- do_execsql_test 5.$i.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
+ do_execsql_test 5.$i.2 { PRAGMA integrity_check(t1) } ok
if {[set_test_counter errors]} break
}
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5ab.test sqlite3-3.44.0-0/ext/fts5/test/fts5ab.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5ab.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5ab.test 2023-11-04 14:24:27.000000000 +0000
@@ -180,7 +180,11 @@
} {1 2}
}
-do_execsql_test 4.5 {
+do_execsql_test 4.5.1 {
+ SELECT rowid FROM s1 WHERE s1 MATCH 'a AND x'
+} {1 2}
+
+do_execsql_test 4.5.2 {
SELECT rowid FROM s1 WHERE s1 MATCH 'a x'
} {1 2}
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5af.test sqlite3-3.44.0-0/ext/fts5/test/fts5af.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5af.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5af.test 2023-11-04 14:24:27.000000000 +0000
@@ -193,4 +193,34 @@
} ;# foreach_detail_mode
+reset_db
+do_execsql_test 6.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(colA, colB);
+ INSERT INTO t1 VALUES('A B C', 'D E F');
+}
+
+do_execsql_test 6.1 {
+ SELECT colA, colB, snippet(t1,0,'[', ']','...',1) FROM t1 WHERE t1 MATCH 'B';
+} {{A B C} {D E F} ...[B]...}
+breakpoint
+do_execsql_test 6.2 {
+ SELECT colA, colB, snippet(t1, 1,'[',']','...',2) FROM t1 WHERE t1 MATCH 'B';
+} {{A B C} {D E F} {D E...}}
+do_execsql_test 6.3 {
+ SELECT colA, colB, snippet(t1, 1,'[',']','...',1) FROM t1 WHERE t1 MATCH 'B';
+} {{A B C} {D E F} {D...}}
+
+do_execsql_test 6.1 {
+ SELECT colA, colB, snippet(t1,0,'[', ']','...',1) FROM t1 WHERE t1 MATCH 'A';
+} {{A B C} {D E F} [A]...}
+breakpoint
+do_execsql_test 6.2 {
+ SELECT colA, colB, snippet(t1, 1,'[',']','...',2) FROM t1 WHERE t1 MATCH 'A';
+} {{A B C} {D E F} {D E...}}
+do_execsql_test 6.3 {
+ SELECT colA, colB, snippet(t1, 1,'[',']','...',1) FROM t1 WHERE t1 MATCH 'A';
+} {{A B C} {D E F} {D...}}
+
+
+
finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5ak.test sqlite3-3.44.0-0/ext/fts5/test/fts5ak.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5ak.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5ak.test 2023-11-04 14:24:27.000000000 +0000
@@ -154,4 +154,30 @@
}
+# 2023-04-06 https://sqlite.org/forum/forumpost/cae4367d9b
+#
+# This is not a test of FTS5, but rather a test of the of what happens to
+# prepared statements that encounter SQLITE_SCHEMA while other prepared
+# statements are running. The original problem POC used FTS5, and so
+# is seems reasonable to put the test here.
+#
+# The vdbeaux24.test module in TH3 also tests this same behavior but
+# without requiring FTS5 or an other extension.
+#
+reset_db
+db null NULL
+do_execsql_test 4.0 {
+ CREATE TABLE t5(a PRIMARY KEY);
+ INSERT INTO t5 VALUES(0);
+ CREATE VIRTUAL TABLE t6 USING fts5(0);
+ DELETE FROM t6;
+ CREATE TABLE t7(x);
+ WITH cte(a) AS (
+ SELECT a FROM t5
+ WHERE ((0,0) IN (SELECT 0, LAG(0) OVER (PARTITION BY 0) FROM t6), 0)
+ < (a,0)
+ )
+ SELECT max(a) FROM cte;
+} NULL
+
finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5aux.test sqlite3-3.44.0-0/ext/fts5/test/fts5aux.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5aux.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5aux.test 2023-11-04 14:24:27.000000000 +0000
@@ -307,5 +307,31 @@
SELECT group_concat(firstcol(t1), '.') FROM t1 GROUP BY rowid
} {1 {unable to use function firstcol in the requested context}}
-finish_test
+#-------------------------------------------------------------------------
+# Test that xInstCount() works from within an xPhraseQuery() callback.
+#
+reset_db
+
+proc xCallback {cmd} {
+ incr ::hitcount [$cmd xInstCount]
+ return SQLITE_OK
+}
+proc fts5_hitcount {cmd} {
+ set ::hitcount 0
+ $cmd xQueryPhrase 0 xCallback
+ return $::hitcount
+}
+sqlite3_fts5_create_function db fts5_hitcount fts5_hitcount
+
+do_execsql_test 11.1 {
+ CREATE VIRTUAL TABLE x1 USING fts5(z);
+ INSERT INTO x1 VALUES('one two three');
+ INSERT INTO x1 VALUES('one two one three one');
+ INSERT INTO x1 VALUES('one two three');
+}
+do_execsql_test 11.2 {
+ SELECT fts5_hitcount(x1) FROM x1('one') LIMIT 1;
+} {5}
+
+finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5bigid.test sqlite3-3.44.0-0/ext/fts5/test/fts5bigid.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5bigid.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5bigid.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,62 @@
+# 2023 May 28
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5bigid
+
+# If SQLITE_ENABLE_FTS5 is not defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+set nRow 20000
+
+proc do_ascdesc_test {tn query} {
+ set ::lAsc [db eval { SELECT rowid FROM x1($query) }]
+ set ::lDesc [db eval { SELECT rowid FROM x1($query) ORDER BY rowid DESC }]
+ do_test $tn.1 { lsort -integer $::lAsc } $::lAsc
+ do_test $tn.2 { lsort -integer -decr $::lDesc } $::lDesc
+ do_test $tn.3 { lsort -integer $::lDesc } $::lAsc
+}
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE x1 USING fts5(a);
+}
+
+do_test 1.1 {
+ for {set ii 0} {$ii < $nRow} {incr ii} {
+ db eval {
+ REPLACE INTO x1(rowid, a) VALUES(random(), 'movement at the station');
+ }
+ }
+} {}
+
+do_ascdesc_test 1.2 "the"
+
+do_execsql_test 1.3 {
+ DELETE FROM x1
+}
+
+do_test 1.4 {
+ for {set ii 0} {$ii < $nRow} {incr ii} {
+ db eval {
+ INSERT INTO x1(rowid, a) VALUES(
+ $ii + 0x6FFFFFFFFFFFFFFF, 'movement at the station'
+ );
+ }
+ }
+} {}
+
+do_ascdesc_test 1.5 "movement"
+
+finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5conflict.test sqlite3-3.44.0-0/ext/fts5/test/fts5conflict.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5conflict.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5conflict.test 2023-11-04 14:24:27.000000000 +0000
@@ -65,4 +65,44 @@
INSERT INTO fts_idx(fts_idx) VALUES('integrity-check');
}
+#-------------------------------------------------------------------------
+# Tests for OR IGNORE conflict handling.
+#
+reset_db
+foreach_detail_mode $::testprefix {
+
+ do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(xyz, detail=%DETAIL%);
+
+ BEGIN;
+ INSERT INTO t1(rowid, xyz) VALUES(13, 'thirteen documents');
+ INSERT INTO t1(rowid, xyz) VALUES(14, 'fourteen documents');
+ INSERT INTO t1(rowid, xyz) VALUES(15, 'fifteen documents');
+ COMMIT;
+ }
+
+ set db_cksum [cksum]
+ foreach {tn sql} {
+ 1 {
+ INSERT OR IGNORE INTO t1(rowid, xyz) VALUES(14, 'new text');
+ }
+ 2 {
+ UPDATE OR IGNORE t1 SET rowid=13 WHERE rowid=15;
+ }
+ 3 {
+ INSERT OR IGNORE INTO t1(rowid, xyz)
+ SELECT 13, 'some text'
+ UNION ALL
+ SELECT 14, 'some text'
+ UNION ALL
+ SELECT 15, 'some text'
+ }
+ } {
+ do_execsql_test 3.1.$tn.1 $sql
+ do_test 3.1.$tn.2 { cksum } $db_cksum
+ }
+
+}
+
+
finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5content.test sqlite3-3.44.0-0/ext/fts5/test/fts5content.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5content.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5content.test 2023-11-04 14:24:27.000000000 +0000
@@ -294,4 +294,3 @@
} {1 {recursively defined fts5 content table}}
finish_test
-
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5contentless.test sqlite3-3.44.0-0/ext/fts5/test/fts5contentless.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5contentless.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5contentless.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,270 @@
+# 2014 Dec 20
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# This file contains tests for the content= and content_rowid= options.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5contentless
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+# Check that it is not possible to specify "contentless_delete=1" for
+# anything other than a contentless table.
+#
+set res(0) {0 {}}
+set res(1) {1 {contentless_delete=1 requires a contentless table}}
+foreach {tn sql bError} {
+ 1 "(a, b, contentless_delete=1)" 1
+ 2 "(a, b, contentless_delete=1, content=abc)" 1
+ 3 "(a, b, contentless_delete=1, content=)" 0
+ 4 "(content=, contentless_delete=1, a)" 0
+ 5 "(content='', contentless_delete=1, hello)" 0
+} {
+ execsql { BEGIN }
+ do_catchsql_test 1.$tn "CREATE VIRTUAL TABLE t1 USING fts5 $sql" $res($bError)
+ execsql { ROLLBACK }
+}
+
+# Check that it is not possible to specify "contentless_delete=1"
+# along with columnsize=1.
+#
+set res(0) {0 {}}
+set res(1) {1 {contentless_delete=1 is incompatible with columnsize=0}}
+foreach {tn sql bError} {
+ 2 "(a, b, content='', contentless_delete=1, columnsize=0)" 1
+} {
+ execsql { BEGIN }
+ do_catchsql_test 1.$tn "CREATE VIRTUAL TABLE t1 USING fts5 $sql" $res($bError)
+ execsql { ROLLBACK }
+}
+
+# Check that if contentless_delete=1 is specified, then the "origin"
+# column is added to the %_docsize table.
+reset_db
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE x1 USING fts5(c, content='');
+ CREATE VIRTUAL TABLE x2 USING fts5(c, content='', contentless_delete=1);
+}
+do_execsql_test 3.1 {
+ SELECT sql FROM sqlite_schema WHERE name IN ('x1_docsize', 'x2_docsize');
+} {
+ {CREATE TABLE 'x1_docsize'(id INTEGER PRIMARY KEY, sz BLOB)}
+ {CREATE TABLE 'x2_docsize'(id INTEGER PRIMARY KEY, sz BLOB, origin INTEGER)}
+}
+
+do_execsql_test 3.2.1 {
+ SELECT hex(block) FROM x1_data WHERE id=10
+} {00000000000000}
+do_execsql_test 3.2.2 {
+ SELECT hex(block) FROM x2_data WHERE id=10
+} {00000000FF000001000000}
+
+do_execsql_test 3.3 {
+ INSERT INTO x2 VALUES('first text');
+ INSERT INTO x2 VALUES('second text');
+}
+do_execsql_test 3.4 {
+ SELECT id, origin FROM x2_docsize
+} {1 1 2 2}
+do_execsql_test 3.5 {
+ SELECT level, segment, loc1, loc2 FROM fts5_structure(
+ (SELECT block FROM x2_data WHERE id=10)
+ )
+} {
+ 0 0 1 1
+ 0 1 2 2
+}
+do_execsql_test 3.6 {
+ INSERT INTO x2(x2) VALUES('optimize');
+}
+do_execsql_test 3.7 {
+ SELECT level, segment, loc1, loc2 FROM fts5_structure(
+ (SELECT block FROM x2_data WHERE id=10)
+ )
+} {
+ 1 0 1 2
+}
+
+do_execsql_test 3.8 {
+ DELETE FROM x2 WHERE rowid=2;
+}
+
+do_execsql_test 3.9 {
+ SELECT rowid FROM x2('text')
+} {1}
+
+#--------------------------------------------------------------------------
+reset_db
+proc document {n} {
+ set vocab [list 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 ret [list]
+ for {set ii 0} {$ii < $n} {incr ii} {
+ lappend ret [lindex $vocab [expr int(rand()*[llength $vocab])]]
+ }
+ set ret
+}
+
+set nRow 1000
+
+do_execsql_test 4.0 {
+ CREATE TABLE t1(x);
+ CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
+ INSERT INTO ft(ft, rank) VALUES('pgsz', 100);
+}
+do_test 4.1 {
+ for {set ii 0} {$ii < $nRow} {incr ii} {
+ set doc [document 6]
+ execsql {
+ INSERT INTO t1 VALUES($doc);
+ INSERT INTO ft VALUES($doc);
+ }
+ }
+} {}
+
+foreach v {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 L1 [execsql {SELECT rowid FROM t1 WHERE x LIKE '%'||$v||'%'}]
+ set L2 [execsql {SELECT rowid FROM ft($v)}]
+ do_test 4.2.$v { set L1 } $L2
+}
+
+do_test 4.3 {
+ for {set ii 1} {$ii < $nRow} {incr ii 2} {
+ execsql {
+ DELETE FROM ft WHERE rowid=$ii;
+ DELETE FROM t1 WHERE rowid=$ii;
+ }
+ }
+} {}
+
+foreach v {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 L1 [execsql {SELECT rowid FROM t1 WHERE x LIKE '%'||$v||'%'}]
+ set L2 [execsql {SELECT rowid FROM ft($v)}]
+ do_test 4.4.$v { set L1 } $L2
+}
+
+do_execsql_test 4.5 {
+ INSERT INTO ft(ft) VALUES('optimize');
+} {}
+
+foreach v {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 L1 [execsql {SELECT rowid FROM t1 WHERE x LIKE '%'||$v||'%'}]
+ set L2 [execsql {SELECT rowid FROM ft($v)}]
+ do_test 4.6.$v { set L1 } $L2
+}
+
+#execsql_pp { SELECT fts5_decode(id, block) FROM ft_data }
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 5.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
+ INSERT INTO ft(rowid, x) VALUES(1, 'one two three');
+ INSERT INTO ft(rowid, x) VALUES(2, 'one two four');
+ INSERT INTO ft(rowid, x) VALUES(3, 'one two five');
+ INSERT INTO ft(rowid, x) VALUES(4, 'one two seven');
+ INSERT INTO ft(rowid, x) VALUES(5, 'one two eight');
+}
+
+do_execsql_test 5.1 {
+ DELETE FROM ft WHERE rowid=2
+}
+
+do_execsql_test 5.2 {
+ SELECT rowid FROM ft
+} {1 3 4 5}
+
+do_catchsql_test 5.3 {
+ UPDATE ft SET x='four six' WHERE rowid=3
+} {0 {}}
+
+do_execsql_test 5.4 {
+ SELECT rowid FROM ft('one');
+} {1 4 5}
+
+do_execsql_test 5.5 {
+ REPLACE INTO ft(rowid, x) VALUES(3, 'four six');
+ SELECT rowid FROM ft('one');
+} {1 4 5}
+
+do_execsql_test 5.6 {
+ REPLACE INTO ft(rowid, x) VALUES(6, 'one two eleven');
+ SELECT rowid FROM ft('one');
+} {1 4 5 6}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 6.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
+ INSERT INTO ft(rowid, x) VALUES(1, 'one two three');
+ INSERT INTO ft(rowid, x) VALUES(2, 'one two four');
+}
+
+do_test 6.1 {
+ db eval { SELECT rowid FROM ft('one two') } {
+ if {$rowid==1} {
+ db eval { INSERT INTO ft(rowid, x) VALUES(3, 'one two four') }
+ }
+ }
+} {}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 7.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
+}
+
+set lRowid [list -450 0 1 2 42]
+
+do_test 7.1 {
+ execsql BEGIN
+ foreach r $lRowid {
+ execsql { INSERT INTO ft(rowid, x) VALUES($r, 'one one one'); }
+ }
+ execsql COMMIT
+} {}
+
+do_test 7.2 {
+ execsql BEGIN
+ foreach r $lRowid {
+ execsql { REPLACE INTO ft(rowid, x) VALUES($r, 'two two two'); }
+ }
+ execsql COMMIT
+} {}
+
+do_execsql_test 7.3 { SELECT rowid FROM ft('one'); } {}
+do_execsql_test 7.4 { SELECT rowid FROM ft('two'); } $lRowid
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 8.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
+ INSERT INTO ft VALUES('hello world');
+ INSERT INTO ft VALUES('one two three');
+}
+
+do_catchsql_test 8.1 {
+ INSERT INTO ft(ft, rowid, x) VALUES('delete', 1, 'hello world');
+} {1 {'delete' may not be used with a contentless_delete=1 table}}
+
+do_execsql_test 8.2 {
+ BEGIN;
+ INSERT INTO ft(rowid, x) VALUES(3, 'four four four');
+ DELETE FROM ft WHERE rowid=3;
+ COMMIT;
+ SELECT rowid FROM ft('four');
+} {}
+
+finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5contentless2.test sqlite3-3.44.0-0/ext/fts5/test/fts5contentless2.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5contentless2.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5contentless2.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,207 @@
+# 2023 July 19
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# This file contains tests for the content= and content_rowid= options.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5contentless2
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+proc vocab {} {
+ list aaa bbb ccc ddd eee fff ggg hhh iii jjj kkk lll mmm nnn ooo ppp
+}
+
+proc document {nToken} {
+ set doc [list]
+ set vocab [vocab]
+ for {set ii 0} {$ii < $nToken} {incr ii} {
+ lappend doc [lindex $vocab [expr int(rand()*[llength $vocab])]]
+ }
+ set doc
+}
+db func document document
+
+proc contains {doc token} {
+ expr {[lsearch $doc $token]>=0}
+}
+db func contains contains
+
+proc do_compare_tables_test {tn} {
+ uplevel [list do_test $tn {
+ foreach v [vocab] {
+ set l1 [execsql { SELECT rowid FROM t1 WHERE contains(doc, $v) }]
+ set l2 [execsql { SELECT rowid FROM t2($v) }]
+ if {$l1!=$l2} { error "1: query mismatch ($l1) ($l2)" }
+
+ set w "[string range $v 0 1]*"
+ set l1 [execsql { SELECT rowid FROM t1 WHERE contains(doc, $w) }]
+ set l2 [execsql { SELECT rowid FROM t2($w) }]
+ if {$l1!=$l2} { error "2: query mismatch ($l1) ($l2)" }
+
+ set w "[string range $v 0 0]*"
+ set l1 [execsql { SELECT rowid FROM t1 WHERE contains(doc, $w) }]
+ set l2 [execsql { SELECT rowid FROM t2($w) }]
+ if {$l1!=$l2} { error "2: query mismatch ($l1) ($l2)" }
+
+ set l1 [execsql {
+ SELECT rowid FROM t1 WHERE contains(doc, $v) ORDER BY rowid DESC
+ }]
+ set l2 [execsql { SELECT rowid FROM t2($v) ORDER BY rowid DESC }]
+ if {$l1!=$l2} { error "1: query mismatch ($l1) ($l2)" }
+ }
+ set {} {}
+ } {}]
+}
+
+proc lshuffle {in} {
+ set L [list]
+ set ret [list]
+ foreach elem $in { lappend L [list [expr rand()] $elem] }
+ foreach pair [lsort -index 0 $L] { lappend ret [lindex $pair 1] }
+ set ret
+}
+
+expr srand(0)
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t2 USING fts5(
+ doc, prefix=2, content=, contentless_delete=1
+ );
+
+ CREATE TABLE t1(doc);
+ CREATE TRIGGER tr1 AFTER DELETE ON t1 BEGIN
+ DELETE FROM t2 WHERE rowid = old.rowid;
+ END;
+}
+
+set SMALLEST64 -9223372036854775808
+set LARGEST64 9223372036854775807
+
+foreach {tn r1 r2} {
+ 1 0 50
+ 2 $SMALLEST64 $SMALLEST64+50
+ 3 $LARGEST64-50 $LARGEST64
+ 4 -50 -1
+} {
+ set r1 [expr $r1]
+ set r2 [expr $r2]
+
+ do_test 1.1.$tn {
+ execsql BEGIN
+ for {set ii $r1} {$ii <= $r2} {incr ii} {
+ execsql { INSERT INTO t1(rowid, doc) VALUES ($ii, document(8)); }
+ }
+ execsql COMMIT
+ } {}
+}
+do_test 1.2 {
+ db eval { SELECT rowid, doc FROM t1 } {
+ execsql { INSERT INTO t2(rowid, doc) VALUES($rowid, $doc) }
+ }
+} {}
+
+foreach {tn rowid} {
+ 1 $SMALLEST64
+ 2 0
+ 3 -5
+ 4 -30
+ 5 $LARGEST64
+ 6 $LARGEST64-1
+} {
+ set rowid [expr $rowid]
+ do_execsql_test 1.3.$tn.1 {
+ DELETE FROM t1 WHERE rowid=$rowid
+ }
+ do_compare_tables_test 1.3.$tn.2
+}
+
+set iTest 1
+foreach r [lshuffle [execsql {SELECT rowid FROM t1}]] {
+ if {($iTest % 50)==0} {
+ execsql { INSERT INTO t2(t2) VALUES('optimize') }
+ }
+ if {($iTest % 5)==0} {
+ execsql { INSERT INTO t2(t2, rank) VALUES('merge', 5) }
+ }
+ do_execsql_test 1.4.$iTest.1($r) {
+ DELETE FROM t1 WHERE rowid=$r
+ }
+ do_compare_tables_test 1.4.$iTest.2
+ incr iTest
+}
+
+do_execsql_test 1.5 {
+ SELECT * FROM t1
+} {}
+
+#-------------------------------------------------------------------------
+reset_db
+db func document document
+
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t2 USING fts5(doc, content=, contentless_delete=1);
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000
+ )
+ INSERT INTO t2(rowid, doc) SELECT i, i || ' ' || i FROM s;
+}
+
+do_execsql_test 2.1 {
+ BEGIN;
+ DELETE FROM t2 WHERE rowid=32;
+ DELETE FROM t2 WHERE rowid=64;
+ DELETE FROM t2 WHERE rowid=96;
+ DELETE FROM t2 WHERE rowid=128;
+ DELETE FROM t2 WHERE rowid=160;
+ DELETE FROM t2 WHERE rowid=192;
+ COMMIT;
+}
+
+do_execsql_test 2.2 {
+ SELECT * FROM t2('128');
+} {}
+
+#-------------------------------------------------------------------------
+
+foreach {tn step} {
+ 1 3
+ 2 7
+ 3 15
+} {
+ set step [expr $step]
+
+ reset_db
+ db func document document
+ do_execsql_test 3.$tn.0 {
+ CREATE VIRTUAL TABLE t2 USING fts5(doc, content=, contentless_delete=1);
+ INSERT INTO t2(t2, rank) VALUES('pgsz', 100);
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000
+ )
+ INSERT INTO t2(rowid, doc) SELECT i, i || ' ' || i FROM s;
+ }
+ do_execsql_test 3.$tn.1 {
+ DELETE FROM t2 WHERE (rowid % $step)==0
+ }
+ do_execsql_test 3.$tn.2 {
+ SELECT * FROM t2( $step * 5 )
+ } {}
+}
+
+
+
+finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5contentless3.test sqlite3-3.44.0-0/ext/fts5/test/fts5contentless3.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5contentless3.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5contentless3.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,195 @@
+# 2023 July 21
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# This file contains tests for the content= and content_rowid= options.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5contentless3
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content=, contentless_delete=1);
+ BEGIN;
+ INSERT INTO ft VALUES('one one one');
+ INSERT INTO ft VALUES('two two two');
+ INSERT INTO ft VALUES('three three three');
+ INSERT INTO ft VALUES('four four four');
+ INSERT INTO ft VALUES('five five five');
+ INSERT INTO ft VALUES('six six six');
+ INSERT INTO ft VALUES('seven seven seven');
+ INSERT INTO ft VALUES('eight eight eight');
+ INSERT INTO ft VALUES('nine nine nine');
+ COMMIT;
+
+ DELETE FROM ft WHERE rowid=3;
+}
+
+proc myhex {hex} { binary decode hex $hex }
+db func myhex myhex
+
+do_execsql_test 1.1 {
+ UPDATE ft_data SET block =
+ myhex('04000000 00000001' ||
+ '01020304 01020304 01020304 01020304' ||
+ '01020304 01020304 01020304 01020304'
+ )
+ WHERE id = (SELECT max(id) FROM ft_data);
+}
+
+do_execsql_test 1.2 {
+ DELETE FROM ft WHERE rowid=1
+}
+
+do_execsql_test 1.3 {
+ SELECT rowid FROM ft('two');
+} {2}
+
+do_execsql_test 1.3 {
+ UPDATE ft_data SET block =
+ myhex('08000000 00000001' ||
+ '0000000001020304 0000000001020304 0000000001020304 0000000001020304' ||
+ '0000000001020304 0000000001020304 0000000001020304 0000000001020304'
+ )
+ WHERE id = (SELECT max(id) FROM ft_data);
+}
+
+do_execsql_test 1.4 {
+ SELECT rowid FROM ft('two');
+} {2}
+
+do_execsql_test 1.5 {
+ DELETE FROM ft WHERE rowid=4
+}
+
+do_execsql_test 1.6 {
+ UPDATE ft_data SET block = myhex('04000000 00000000')
+ WHERE id = (SELECT max(id) FROM ft_data);
+}
+do_execsql_test 1.7 {
+ SELECT rowid FROM ft('two');
+} {2}
+
+do_execsql_test 1.8 {
+ UPDATE ft_data SET block = myhex('04000000 00000000')
+ WHERE id = (SELECT max(id) FROM ft_data);
+}
+do_execsql_test 1.9 {
+ DELETE FROM ft WHERE rowid=8
+} {}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content=, contentless_delete=1);
+ INSERT INTO ft VALUES('one one one');
+ INSERT INTO ft VALUES('two two two');
+ INSERT INTO ft VALUES('three three three');
+ INSERT INTO ft VALUES('four four four');
+ INSERT INTO ft VALUES('five five five');
+ INSERT INTO ft VALUES('six six six');
+ INSERT INTO ft VALUES('seven seven seven');
+ INSERT INTO ft VALUES('eight eight eight');
+ INSERT INTO ft VALUES('nine nine nine');
+}
+
+do_execsql_test 2.1 {
+ INSERT INTO ft(ft) VALUES('optimize');
+}
+do_execsql_test 2.2 {
+ SELECT count(*) FROM ft_data
+} {3}
+do_execsql_test 2.3 {
+ DELETE FROM ft WHERE rowid=5
+}
+do_execsql_test 2.4 {
+ SELECT count(*) FROM ft_data
+} {4}
+
+# Check that an 'optimize' works (rewrites the index) if there is a single
+# segment with one or more tombstone hash pages.
+do_execsql_test 2.5 {
+ INSERT INTO ft(ft) VALUES('optimize');
+}
+do_execsql_test 2.6 {
+ SELECT count(*) FROM ft_data
+} {3}
+
+# Check that an 'optimize' is a no-op if there is a single segment
+# and no tombstone hash pages.
+do_execsql_test 2.7 {
+ INSERT INTO ft(ft) VALUES('optimize');
+ SELECT rowid FROM ft_data;
+} [db eval {SELECT rowid FROM ft_data}]
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content=, contentless_delete=1);
+ INSERT INTO ft(ft, rank) VALUES('pgsz', 64);
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000
+ )
+ INSERT INTO ft(rowid, x) SELECT i, i||' '||i||' '||i||' '||i FROM s;
+ INSERT INTO ft(ft) VALUES('optimize');
+}
+
+do_execsql_test 3.1 {
+ SELECT count(*) FROM ft_data
+} {200}
+
+do_execsql_test 3.2 {
+ DELETE FROM ft WHERE (rowid % 50)==0;
+ SELECT count(*) FROM ft_data;
+} {203}
+
+do_execsql_test 3.3 {
+ INSERT INTO ft(ft, rank) VALUES('merge', 500);
+ SELECT rowid FROM ft_data;
+} [db eval {SELECT rowid FROM ft_data}]
+
+do_execsql_test 3.4 {
+ INSERT INTO ft(ft, rank) VALUES('merge', -1000);
+ SELECT count(*) FROM ft_data;
+} {197}
+
+do_execsql_test 3.5 {
+ DELETE FROM ft WHERE (rowid % 50)==1;
+ SELECT count(*) FROM ft_data;
+} {200}
+
+do_execsql_test 3.6 {
+ SELECT level, segment, npgtombstone FROM fts5_structure(
+ (SELECT block FROM ft_data WHERE id=10)
+ )
+} {1 0 3}
+
+do_test 3.6 {
+ while 1 {
+ set nChange [db total_changes]
+ execsql { INSERT INTO ft(ft, rank) VALUES('merge', -5) }
+ if {([db total_changes] - $nChange)<2} break
+ }
+} {}
+
+do_execsql_test 3.7 {
+ SELECT level, segment, npgtombstone FROM fts5_structure(
+ (SELECT block FROM ft_data WHERE id=10)
+ )
+} {2 0 0}
+
+
+finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5contentless4.test sqlite3-3.44.0-0/ext/fts5/test/fts5contentless4.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5contentless4.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5contentless4.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,247 @@
+# 2023 July 21
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# This file contains tests for the content= and content_rowid= options.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5contentless4
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+proc document {n} {
+ set vocab [list 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 ret [list]
+ for {set ii 0} {$ii < $n} {incr ii} {
+ lappend ret [lindex $vocab [expr int(rand()*[llength $vocab])]]
+ }
+ set ret
+}
+db func document document
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
+ INSERT INTO ft(ft, rank) VALUES('pgsz', 240);
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000
+ )
+ INSERT INTO ft SELECT document(12) FROM s;
+}
+
+do_execsql_test 1.1 {
+ INSERT INTO ft(ft) VALUES('optimize');
+}
+
+do_execsql_test 1.2 {
+ SELECT level, segment, nentry, nentrytombstone FROM fts5_structure((
+ SELECT block FROM ft_data WHERE id=10
+ ))
+} {0 0 1000 0}
+
+do_execsql_test 1.3 {
+ DELETE FROM ft WHERE rowid < 50
+}
+
+do_execsql_test 1.4 {
+ SELECT level, segment, nentry, nentrytombstone FROM fts5_structure((
+ SELECT block FROM ft_data WHERE id=10
+ ))
+} {0 0 1000 49}
+
+do_execsql_test 1.5 {
+ DELETE FROM ft WHERE rowid < 1000
+}
+
+do_execsql_test 1.6 {
+ SELECT level, segment, nentry, nentrytombstone FROM fts5_structure((
+ SELECT block FROM ft_data WHERE id=10
+ ))
+} {1 0 1 0}
+
+#--------------------------------------------------------------------------
+reset_db
+db func document document
+
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
+}
+
+do_test 2.1 {
+ for {set ii 0} {$ii < 5000} {incr ii} {
+ execsql { INSERT INTO ft VALUES( document(12) ) }
+ }
+} {}
+
+do_execsql_test 2.2 {
+ SELECT sum(nentry) - sum(nentrytombstone) FROM fts5_structure((
+ SELECT block FROM ft_data WHERE id=10
+ ))
+} {5000}
+
+for {set ii 5000} {$ii >= 0} {incr ii -100} {
+ do_execsql_test 2.3.$ii {
+ DELETE FROM ft WHERE rowid > $ii
+ }
+ do_execsql_test 2.3.$ii.2 {
+ SELECT
+ CAST((total(nentry) - total(nentrytombstone)) AS integer)
+ FROM
+ fts5_structure( (SELECT block FROM ft_data WHERE id=10) )
+ } $ii
+}
+
+execsql_pp {
+ SELECT * FROM fts5_structure((
+ SELECT block FROM ft_data WHERE id=10
+ ))
+}
+
+do_test 2.4 {
+ for {set ii 0} {$ii < 5000} {incr ii} {
+ execsql { INSERT INTO ft VALUES( document(12) ) }
+ }
+} {}
+
+for {set ii 1} {$ii <= 5000} {incr ii 10} {
+ do_execsql_test 2.3.$ii {
+ DELETE FROM ft WHERE rowid = $ii;
+ INSERT INTO ft VALUES( document(12) );
+ INSERT INTO ft(ft, rank) VALUES('merge', -10);
+ }
+
+ do_execsql_test 2.3.$ii.2 {
+ SELECT
+ CAST((total(nentry) - total(nentrytombstone)) AS integer)
+ FROM
+ fts5_structure( (SELECT block FROM ft_data WHERE id=10) )
+ } 5000
+}
+
+#-------------------------------------------------------------------------
+reset_db
+db func document document
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100
+ )
+ INSERT INTO ft SELECT document(12) FROM s;
+}
+
+do_catchsql_test 3.1 {
+ INSERT INTO ft(ft, rank) VALUES('deletemerge', 'text');
+} {1 {SQL logic error}}
+do_catchsql_test 3.2 {
+ INSERT INTO ft(ft, rank) VALUES('deletemerge', 50);
+} {0 {}}
+do_execsql_test 3.3 {
+ SELECT * FROM ft_config WHERE k='deletemerge'
+} {deletemerge 50}
+do_catchsql_test 3.4 {
+ INSERT INTO ft(ft, rank) VALUES('deletemerge', 101);
+} {0 {}}
+do_execsql_test 3.5 {
+ SELECT * FROM ft_config WHERE k='deletemerge'
+} {deletemerge 101}
+
+do_execsql_test 3.6 {
+ DELETE FROM ft WHERE rowid<95
+}
+
+do_execsql_test 3.7 {
+ SELECT nentrytombstone, nentry FROM fts5_structure((
+ SELECT block FROM ft_data WHERE id=10
+ ))
+} {94 100}
+
+do_execsql_test 3.8 {
+ DELETE FROM ft WHERE rowid=95
+}
+
+do_execsql_test 3.9 {
+ SELECT nentrytombstone, nentry FROM fts5_structure((
+ SELECT block FROM ft_data WHERE id=10
+ ))
+} {95 100}
+
+do_execsql_test 3.10 {
+ DELETE FROM ft;
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100
+ )
+ INSERT INTO ft SELECT document(12) FROM s;
+ INSERT INTO ft(ft, rank) VALUES('deletemerge', 50);
+}
+
+do_execsql_test 3.11 {
+ DELETE FROM ft WHERE rowid<95
+}
+
+do_execsql_test 3.12 {
+ SELECT nentrytombstone, nentry FROM fts5_structure((
+ SELECT block FROM ft_data WHERE id=10
+ ))
+} {0 6}
+
+#-------------------------------------------------------------------------
+reset_db
+db func document document
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE x1 USING fts5(x, content='', contentless_delete=1);
+ INSERT INTO x1(x1, rank) VALUES('usermerge', 16);
+ INSERT INTO x1(x1, rank) VALUES('deletemerge', 40);
+ INSERT INTO x1 VALUES('one');
+ INSERT INTO x1 VALUES('two');
+ INSERT INTO x1 VALUES('three');
+ INSERT INTO x1 VALUES('four');
+ INSERT INTO x1 VALUES('five');
+ INSERT INTO x1 VALUES('six');
+ INSERT INTO x1 VALUES('seven');
+ INSERT INTO x1 VALUES('eight');
+ INSERT INTO x1 VALUES('nine');
+ INSERT INTO x1 VALUES('ten');
+}
+
+do_execsql_test 4.1 {
+ SELECT level, segment FROM fts5_structure((
+ SELECT block FROM x1_data WHERE id=10
+ ))
+} {
+ 0 0 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9
+}
+
+for {set ii 1} {$ii < 4} {incr ii} {
+ do_execsql_test 4.2.$ii {
+ DELETE FROM x1 WHERE rowid = $ii;
+ INSERT INTO x1(x1, rank) VALUES('merge', 5);
+ SELECT level, segment FROM fts5_structure((
+ SELECT block FROM x1_data WHERE id=10
+ ))
+ } {
+ 0 0 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9
+ }
+}
+
+do_execsql_test 4.3 {
+ DELETE FROM x1 WHERE rowid = $ii;
+ INSERT INTO x1(x1, rank) VALUES('merge', 5);
+ SELECT level, segment, nentry FROM fts5_structure((
+ SELECT block FROM x1_data WHERE id=10
+ ))
+} {
+ 1 0 6
+}
+
+finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5contentless5.test sqlite3-3.44.0-0/ext/fts5/test/fts5contentless5.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5contentless5.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5contentless5.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,58 @@
+# 2023 August 7
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# This file contains tests for the content= and content_rowid= options.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5contentless5
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a, b, c, content='', contentless_delete=1);
+ INSERT INTO t1 VALUES('A', 'B', 'C');
+ INSERT INTO t1 VALUES('D', 'E', 'F');
+ INSERT INTO t1 VALUES('G', 'H', 'I');
+}
+
+do_execsql_test 1.01 {
+ CREATE TABLE t2(x, y);
+ INSERT INTO t2 VALUES('x', 'y');
+}
+
+# explain_i "UPDATE t1 SET a='a' WHERE t1.rowid=1"
+breakpoint
+explain_i "UPDATE t1 SET a='a' FROM t2 WHERE t1.rowid=1 AND b IS NULL"
+
+#breakpoint
+#explain_i "UPDATE t1 SET a='a' WHERE b IS NULL AND rowid=?"
+
+foreach {tn up err} {
+ 1 "UPDATE t1 SET a='a', b='b', c='c' WHERE rowid=1" 0
+ 2 "UPDATE t1 SET a='a', b='b' WHERE rowid=1" 1
+ 3 "UPDATE t1 SET b='b', c='c' WHERE rowid=1" 1
+ 4 "UPDATE t1 SET a='a', c='c' WHERE rowid=1" 1
+ 5 "UPDATE t1 SET a='a', c='c' WHERE t1.rowid=1 AND b IS NULL" 1
+ 6 "UPDATE t1 SET a='a' FROM t2 WHERE t1.rowid=1" 1
+ 7 "UPDATE t1 SET a='a', b='b', c='c' FROM t2 WHERE t1.rowid=1" 0
+} {
+
+ set res(0) {0 {}}
+ set res(1) {1 {cannot UPDATE a subset of columns on fts5 contentless-delete table: t1}}
+ do_catchsql_test 1.$tn $up $res($err)
+}
+
+finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5corrupt.test sqlite3-3.44.0-0/ext/fts5/test/fts5corrupt.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5corrupt.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5corrupt.test 2023-11-04 14:24:27.000000000 +0000
@@ -48,6 +48,10 @@
}
catchsql { INSERT INTO t1(t1) VALUES('integrity-check') }
} {1 {database disk image is malformed}}
+do_execsql_test 1.3b {
+ PRAGMA integrity_check(t1);
+} {{malformed inverted index for FTS5 table main.t1}}
+
do_test 1.4 {
db_restore_and_reopen
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5corrupt2.test sqlite3-3.44.0-0/ext/fts5/test/fts5corrupt2.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5corrupt2.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5corrupt2.test 2023-11-04 14:24:27.000000000 +0000
@@ -167,6 +167,9 @@
do_test 3.$tn.$tn2.2 {
catchsql { INSERT INTO x3(x3) VALUES('integrity-check') }
} {1 {database disk image is malformed}}
+ do_execsql_test 3.$tn.$tn2.3 {
+ PRAGMA integrity_check(x3);
+ } {{malformed inverted index for FTS5 table main.x3}}
}
execsql ROLLBACK
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5corrupt5.test sqlite3-3.44.0-0/ext/fts5/test/fts5corrupt5.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5corrupt5.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5corrupt5.test 2023-11-04 14:24:27.000000000 +0000
@@ -15,7 +15,7 @@
#
source [file join [file dirname [info script]] fts5_common.tcl]
-set testprefix fts5corrupt3
+set testprefix fts5corrupt5
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
ifcapable !fts5 {
@@ -793,6 +793,94 @@
REPLACE INTO t1(rowid,a,b,rowid) VALUES(200,1,2,3);
} {1 {database disk image is malformed}}
+#-------------------------------------------------------------------------
+reset_db
+do_test 5.0 {
+ sqlite3 db {}
+ db deserialize [decode_hexdb {
+.open --hexdb
+| size 28672 pagesize 4096 filename crash-0c6d3451d11597.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 07 00 00 00 04 ................
+| 96: 00 00 00 00 0d 00 00 00 07 0d d2 00 0f c4 0f 6d ...............m
+| 112: 0f 02 0e ab 0e 4e 0d f6 0d d2 00 00 00 00 00 00 .....N..........
+| 3536: 00 00 22 07 06 17 11 11 01 31 74 61 62 6c 65 74 .........1tablet
+| 3552: 32 74 32 07 43 52 45 41 54 45 20 54 41 42 4c 45 2t2.CREATE TABLE
+| 3568: 20 74 32 28 78 29 56 06 06 17 1f 1f 01 7d 74 61 t2(x)V.......ta
+| 3584: 62 6c 65 74 31 5f 63 6f 6e 66 69 67 74 31 5f 63 blet1_configt1_c
+| 3600: 6f 6e 66 69 67 06 43 52 45 41 54 45 20 54 41 42 onfig.CREATE TAB
+| 3616: 4c 45 20 27 74 31 5f 63 6f 6e 66 69 67 27 28 6b LE 't1_config'(k
+| 3632: 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 76 29 PRIMARY KEY, v)
+| 3648: 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 5b 05 WITHOUT ROWID[.
+| 3664: 07 17 21 21 01 81 01 74 61 62 6c 65 74 31 5f 64 ..!!...tablet1_d
+| 3680: 6f 63 73 69 7a 65 74 31 5f 64 6f 63 73 69 7a 65 ocsizet1_docsize
+| 3696: 05 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 .CREATE TABLE 't
+| 3712: 31 5f 64 6f 63 73 69 7a 65 27 28 69 64 20 49 4e 1_docsize'(id IN
+| 3728: 54 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 TEGER PRIMARY KE
+| 3744: 59 2c 20 73 7a 20 42 4c 4f 42 29 55 04 06 17 21 Y, sz BLOB)U...!
+| 3760: 21 01 77 74 61 62 6c 65 74 31 5f 63 6f 6e 74 65 !.wtablet1_conte
+| 3776: 6e 74 74 31 5f 63 6f 6e 74 65 6e 74 04 43 52 45 ntt1_content.CRE
+| 3792: 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 63 6f ATE TABLE 't1_co
+| 3808: 6e 74 65 6e 74 27 28 69 64 20 49 4e 54 45 47 45 ntent'(id INTEGE
+| 3824: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 63 R PRIMARY KEY, c
+| 3840: 30 29 69 03 07 17 19 19 01 81 2d 74 61 62 6c 65 0)i.......-table
+| 3856: 74 31 5f 69 64 78 74 31 5f 69 64 78 03 43 52 45 t1_idxt1_idx.CRE
+| 3872: 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 69 64 ATE TABLE 't1_id
+| 3888: 78 27 28 73 65 67 69 64 2c 20 74 65 72 6d 2c 20 x'(segid, term,
+| 3904: 70 67 6e 6f 2c 20 50 52 49 4d 41 52 59 20 4b 45 pgno, PRIMARY KE
+| 3920: 59 28 73 65 67 69 64 2c 20 74 65 72 6d 29 29 20 Y(segid, term))
+| 3936: 57 49 54 48 4f 55 54 20 52 4f 57 49 44 55 02 07 WITHOUT ROWIDU..
+| 3952: 17 1b 1b 01 81 01 74 61 62 6c 65 74 31 5f 64 61 ......tablet1_da
+| 3968: 74 61 74 31 5f 64 61 74 61 02 43 52 45 41 54 45 tat1_data.CREATE
+| 3984: 20 54 41 42 4c 45 20 27 74 31 5f 64 61 74 61 27 TABLE 't1_data'
+| 4000: 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d (id INTEGER PRIM
+| 4016: 41 52 b9 20 4b 45 59 2c 20 62 6c 6f 63 6b 20 42 AR. KEY, block B
+| 4032: 4c 4f 42 29 3a 01 06 17 11 11 08 63 74 61 62 6c LOB):......ctabl
+| 4048: 65 74 31 74 31 43 52 45 41 54 45 20 56 49 52 54 et1t1CREATE VIRT
+| 4064: 55 41 4c 20 54 41 42 4c 45 20 74 31 20 55 53 49 UAL TABLE t1 USI
+| 4080: 4e 47 20 66 74 73 35 28 63 6f 6e 74 65 6e 74 29 NG fts5(content)
+| page 2 offset 4096
+| 0: 0d 00 00 00 03 0f bd 00 0f e8 0f ef 0f bd 00 00 ................
+| 16: 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
+| 4016: 00 00 00 00 00 00 00 00 00 00 00 00 00 24 84 80 .............$..
+| 4032: 80 80 80 01 03 00 4e 00 00 00 1e 06 30 61 62 61 ......N.....0aba
+| 4048: 63 6b 01 02 02 04 02 66 74 02 02 02 04 04 6e 64 ck.....ft.....nd
+| 4064: 6f 6e 03 02 02 04 0a 07 05 01 03 00 10 03 03 0f on..............
+| 4080: 0a 03 00 24 00 00 00 00 01 01 01 00 01 01 01 11 ...$............
+| page 3 offset 8192
+| 0: 0a 00 00 00 01 0f 00 00 00 00 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 03 0f e0 00 0f f6 0f ec 0f e0 00 00 ................
+| 4064: 0a 03 03 00 1b 61 62 61 6e 64 6f 6e 08 02 03 00 .....abandon....
+| 4080: 17 61 62 61 66 74 08 01 03 00 17 61 62 61 63 6b .abaft.....aback
+| page 5 offset 16384
+| 0: 0d 00 00 00 03 0f ee 00 0f fa 0f f4 0f ee 00 00 ................
+| 16: 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
+| 4064: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 03 ................
+| 4080: 03 00 0e 01 04 02 03 00 0e 01 04 01 03 00 0e 01 ................
+| 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 10 03 0f d6 00 0f f4 10 e1 0f d6 00 00 ................
+| 16: 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 ................
+| 4048: 00 00 00 00 00 00 09 03 02 1b 72 65 62 75 69 6c ..........rebuil
+| 4064: 64 11 02 02 2b 69 6e 74 65 67 72 69 74 79 2d 63 d...+integrity-c
+| 4080: 68 65 63 6b 0a 01 02 1d 6f 70 74 69 6d 00 00 00 heck....optim...
+| end crash-0c6d3451d11597.db
+}]} {}
+
+do_execsql_test 5.1 {
+ INSERT INTO t1(t1,rank) VALUES('secure-delete',1);
+}
+do_catchsql_test 5.4 {
+ UPDATE t1 SET content=randomblob(500);
+} {1 {database disk image is malformed}}
+
+
sqlite3_fts5_may_be_corrupt 0
finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5corrupt7.test sqlite3-3.44.0-0/ext/fts5/test/fts5corrupt7.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5corrupt7.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5corrupt7.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,128 @@
+# 2023 April 30
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5corrupt7
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+sqlite3_fts5_may_be_corrupt 1
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+}
+
+set doc [string repeat "a b " 30]
+
+do_execsql_test 1.1 {
+ BEGIN;
+ INSERT INTO t1(rowid, x) VALUES(123, $doc);
+ INSERT INTO t1(rowid, x) VALUES(124, $doc);
+ COMMIT;
+}
+
+execsql_pp {
+ SELECT id, fts5_decode(id, block), quote(block) FROM t1_data
+}
+
+set rows [db eval { SELECT rowid FROM t1_data }]
+db_save_and_close
+
+foreach r $rows {
+ db_restore_and_reopen
+
+ proc edit_block {b} {
+ binary scan $b c* in
+ set out [lreplace $in 0 1 255 255]
+ binary format c* $out
+ }
+ db func edit_block edit_block
+
+ do_execsql_test 1.2.$r.1 {
+ UPDATE t1_data SET block = edit_block(block) WHERE rowid=$r;
+ }
+
+ do_execsql_test 1.2.$r.2 {
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+ }
+
+ do_test 1.2.$r.3 {
+ catchsql { DELETE FROM t1 WHERE rowid=123; }
+ catchsql { DELETE FROM t1 WHERE rowid=124; }
+ set {} {}
+ } {}
+
+ db close
+}
+
+foreach r $rows {
+set r 137438953475
+ db_restore_and_reopen
+
+ proc edit_block {b} {
+ binary scan $b c* in
+ set out [lreplace $in end end 127]
+ binary format c* $out
+ }
+ db func edit_block edit_block
+
+ do_execsql_test 1.2.$r.1 {
+ UPDATE t1_data SET block = edit_block(block) WHERE rowid=$r;
+ }
+
+ do_execsql_test 1.2.$r.2 {
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+ }
+
+ do_test 1.2.$r.3 {
+ catchsql { DELETE FROM t1 WHERE rowid=124; }
+ catchsql { DELETE FROM t1 WHERE rowid=123; }
+ set {} {}
+ } {}
+
+ db close
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ BEGIN;
+ INSERT INTO t1 VALUES('abc');
+ INSERT INTO t1 VALUES('b d d d');
+ COMMIT;
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+}
+
+execsql_pp {
+ SELECT id, quote(block) FROM t1_data
+}
+
+do_execsql_test 2.1 {
+ SELECT quote(block) FROM t1_data WHERE id > 10;
+} {X'0000001A04306162630102020101620202020101640206030303040806'}
+
+do_execsql_test 2.2 {
+ UPDATE t1_data SET
+ block=X'0000001A04306162630102025501620202020101640206030303040806'
+ WHERE id>10
+}
+
+do_catchsql_test 2.3 {
+ DELETE FROM t1 WHERE rowid = 1
+} {1 {database disk image is malformed}}
+
+finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5eb.test sqlite3-3.44.0-0/ext/fts5/test/fts5eb.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5eb.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5eb.test 2023-11-04 14:24:27.000000000 +0000
@@ -95,6 +95,9 @@
SELECT rowid, bm25(e1) FROM e1 WHERE e1 MATCH '"/" OR "just"' ORDER BY rank;
} {1 -1e-06}
+do_execsql_test 3.4 "
+ SELECT fts5_expr_tcl('e AND \" \"');
+" {{AND [nearset -- {e}] [{}]}}
finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5faultF.test sqlite3-3.44.0-0/ext/fts5/test/fts5faultF.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5faultF.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5faultF.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,111 @@
+# 2023 July 20
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+#
+# This file is focused on OOM errors. Particularly those that may occur
+# when using contentless_delete=1 databases.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+source $testdir/malloc_common.tcl
+set testprefix fts5faultF
+
+# If SQLITE_ENABLE_FTS5 is not defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+faultsim_save_and_close
+do_faultsim_test 1 -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql {
+ CREATE VIRTUAL TABLE t1 USING fts5(x, y, content=, contentless_delete=1)
+ }
+} -test {
+ faultsim_test_result {0 {}} {1 {vtable constructor failed: t1}}
+}
+
+reset_db
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(doc, content=, contentless_delete=1);
+ BEGIN;
+ INSERT INTO t1(rowid, doc) VALUES(1, 'a b c d');
+ INSERT INTO t1(rowid, doc) VALUES(2, 'a b c d');
+ INSERT INTO t1(rowid, doc) VALUES(3, 'a b c d');
+ INSERT INTO t1(rowid, doc) VALUES(4, 'a b c d');
+ COMMIT;
+ DELETE FROM t1 WHERE rowid IN (2, 4);
+}
+
+do_faultsim_test 2 -prep {
+ sqlite3 db test.db
+ execsql { SELECT rowid FROM t1 }
+} -body {
+ execsql {
+ SELECT rowid FROM t1('b c');
+ }
+} -test {
+ faultsim_test_result {0 {1 3}}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(doc, content=, contentless_delete=1);
+ BEGIN;
+ INSERT INTO t1(rowid, doc) VALUES(1, 'a b c d');
+ INSERT INTO t1(rowid, doc) VALUES(2, 'a b c d');
+ INSERT INTO t1(rowid, doc) VALUES(3, 'a b c d');
+ INSERT INTO t1(rowid, doc) VALUES(4, 'a b c d');
+ COMMIT;
+}
+
+faultsim_save_and_close
+do_faultsim_test 3 -prep {
+ faultsim_restore_and_reopen
+ execsql { SELECT rowid FROM t1 }
+} -body {
+ execsql {
+ INSERT INTO t1(rowid, doc) VALUES(5, 'a b c d');
+ }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(doc, content=, contentless_delete=1);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000
+ )
+ INSERT INTO t1(rowid, doc) SELECT i, 'a b c d' FROM s;
+}
+
+do_execsql_test 4.1 { DELETE FROM t1 WHERE rowid <= 25 }
+
+faultsim_save_and_close
+do_faultsim_test 4 -faults oom-t* -prep {
+ faultsim_restore_and_reopen
+ execsql { SELECT rowid FROM t1 }
+} -body {
+ execsql {
+ DELETE FROM t1 WHERE rowid < 100
+ }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+
+finish_test
+
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5faultG.test sqlite3-3.44.0-0/ext/fts5/test/fts5faultG.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5faultG.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5faultG.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,50 @@
+# 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 fts5faultG
+
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a);
+ INSERT INTO t1 VALUES('test renaming the table');
+ INSERT INTO t1 VALUES(' after it has been written');
+ INSERT INTO t1 VALUES(' actually other stuff instead');
+}
+faultsim_save_and_close
+do_faultsim_test 1 -faults oom* -prep {
+ faultsim_restore_and_reopen
+ execsql {
+ BEGIN;
+ DELETE FROM t1 WHERE rowid=2;
+ }
+} -body {
+ execsql {
+ DELETE FROM t1;
+ }
+} -test {
+ catchsql { COMMIT }
+ faultsim_integrity_check
+ faultsim_test_result {0 {}}
+}
+
+
+finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5integrity.test sqlite3-3.44.0-0/ext/fts5/test/fts5integrity.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5integrity.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5integrity.test 2023-11-04 14:24:27.000000000 +0000
@@ -77,6 +77,9 @@
UPDATE aa_docsize SET sz = X'44' WHERE rowid = 3;
INSERT INTO aa(aa) VALUES('integrity-check');
} {1 {database disk image is malformed}}
+do_execsql_test 4.2.1 {
+ PRAGMA integrity_check(aa);
+} {{malformed inverted index for FTS5 table main.aa}}
do_catchsql_test 4.3 {
ROLLBACK;
@@ -317,4 +320,39 @@
INSERT INTO vt0(vt0) VALUES('integrity-check');
} {0 {}}
+reset_db
+proc slang {in} {return [string map {th d e eh} $in]}
+db function slang -deterministic -innocuous slang
+do_execsql_test 11.0 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT, c TEXT AS (slang(b)));
+ INSERT INTO t1(b) VALUES('the quick fox jumps over the lazy brown dog');
+ SELECT c FROM t1;
+} {{deh quick fox jumps ovehr deh lazy brown dog}}
+
+do_execsql_test 11.1 {
+ CREATE VIRTUAL TABLE t2 USING fts5(content="t1", c);
+ INSERT INTO t2(t2) VALUES('rebuild');
+ SELECT rowid FROM t2 WHERE t2 MATCH 'deh';
+} {1}
+
+do_execsql_test 11.2 {
+ PRAGMA integrity_check(t2);
+} {ok}
+db close
+sqlite3 db test.db
+
+# FIX ME?
+#
+# FTS5 integrity-check does not care if the content table is unreadable or
+# does not exist. It only looks for internal inconsistencies in the
+# inverted index.
+#
+do_execsql_test 11.3 {
+ PRAGMA integrity_check(t2);
+} {ok}
+do_execsql_test 11.4 {
+ DROP TABLE t1;
+ PRAGMA integrity_check(t2);
+} {ok}
+
finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5limits.test sqlite3-3.44.0-0/ext/fts5/test/fts5limits.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5limits.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5limits.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,47 @@
+# 2023 May 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.
+#
+#*************************************************************************
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5limits
+return_if_no_fts5
+
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x);
+}
+
+# Default limit for expression depth is 256
+#
+foreach {tn nRepeat op bErr} {
+ 1 200 AND 0
+ 2 200 NOT 0
+ 3 200 OR 0
+
+ 4 260 AND 0
+ 5 260 NOT 1
+ 6 260 OR 0
+} {
+ set L [string repeat "abc " $nRepeat]
+ set Q [join $L " $op "]
+
+ set res {0 {}}
+ if {$bErr} {
+ set res "1 {fts5 expression tree is too large (maximum depth 256)}"
+ }
+
+ do_catchsql_test 1.$tn {
+ SELECT * FROM ft($Q)
+ } $res
+}
+
+finish_test
+
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5misc.test sqlite3-3.44.0-0/ext/fts5/test/fts5misc.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5misc.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5misc.test 2023-11-04 14:24:27.000000000 +0000
@@ -44,12 +44,12 @@
do_catchsql_test 1.3.1 {
SELECT highlight(t1, 4, '', '') FROM t1('*reads');
-} {1 {no such cursor: 1}}
+} {1 {no such cursor: 2}}
do_catchsql_test 1.3.2 {
SELECT a FROM t1
WHERE rank = (SELECT highlight(t1, 4, '', '') FROM t1('*reads'));
-} {1 {no such cursor: 1}}
+} {1 {no such cursor: 2}}
db close
sqlite3 db test.db
@@ -329,7 +329,7 @@
reset_db
sqlite3_db_config db DEFENSIVE 1
-do_execsql_test 13.0 {
+do_execsql_test 13.1.0 {
CREATE TABLE a (id INTEGER PRIMARY KEY, name TEXT);
CREATE VIRTUAL TABLE b USING fts5(name);
CREATE TRIGGER a_trigger AFTER INSERT ON a BEGIN
@@ -337,18 +337,44 @@
END;
}
-do_test 13.1 {
+do_test 13.1.1 {
set ::STMT [
sqlite3_prepare db "INSERT INTO a VALUES (1, 'foo') RETURNING id;" -1 dummy
]
sqlite3_step $::STMT
} {SQLITE_ROW}
-do_test 13.2 {
+do_test 13.1.2 {
sqlite3_finalize $::STMT
} {SQLITE_OK}
-do_test 13.3 {
+do_test 13.1.3 {
+ sqlite3_errmsg db
+} {not an error}
+
+reset_db
+sqlite3_db_config db DEFENSIVE 1
+do_execsql_test 13.2.0 {
+ BEGIN;
+ CREATE TABLE a (id INTEGER PRIMARY KEY, name TEXT);
+ CREATE VIRTUAL TABLE b USING fts5(name);
+ CREATE TRIGGER a_trigger AFTER INSERT ON a BEGIN
+ INSERT INTO b (name) VALUES ('foo');
+ END;
+}
+
+do_test 13.2.1 {
+ set ::STMT [
+ sqlite3_prepare db "INSERT INTO a VALUES (1, 'foo') RETURNING id;" -1 dummy
+ ]
+ sqlite3_step $::STMT
+} {SQLITE_ROW}
+
+do_test 13.2.2 {
+ sqlite3_finalize $::STMT
+} {SQLITE_OK}
+
+do_test 13.2.3 {
sqlite3_errmsg db
} {not an error}
@@ -398,10 +424,12 @@
SAVEPOINT one;
} {}
do_execsql_test 15.4 END
-do_test 15.4 {
+do_test 15.5 {
list [catch { db2 eval COMMIT } msg] $msg
} {0 {}}
+db2 close
+
#-------------------------------------------------------------------------
reset_db
forcedelete test.db2
@@ -443,5 +471,35 @@
SELECT * FROM x1
} {abc def}
+db2 close
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 17.1 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, tokenize="unicode61 separators 'X'");
+}
+do_execsql_test 17.2 {
+ SELECT 0 FROM ft WHERE ft MATCH 'X' AND ft MATCH 'X'
+}
+do_execsql_test 17.3 {
+ SELECT 0 FROM ft('X')
+}
+
+do_execsql_test 17.4 {
+ CREATE VIRTUAL TABLE t0 USING fts5(c0, t="trigram");
+ INSERT INTO t0 VALUES('assertionfaultproblem');
+}
+do_execsql_test 17.5 {
+ SELECT 0 FROM t0(0) WHERE c0 GLOB 0;
+} {}
+
+do_execsql_test 17.5 {
+ SELECT c0 FROM t0 WHERE c0 GLOB '*f*';
+} {assertionfaultproblem}
+do_execsql_test 17.5 {
+ SELECT c0 FROM t0 WHERE c0 GLOB '*faul*';
+} {assertionfaultproblem}
+
+
finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5optimize2.test sqlite3-3.44.0-0/ext/fts5/test/fts5optimize2.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5optimize2.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5optimize2.test 2023-11-04 14:24:27.000000000 +0000
@@ -9,7 +9,7 @@
#
#***********************************************************************
#
-# TESTRUNNER: slow
+# TESTRUNNER: superslow
#
source [file join [file dirname [info script]] fts5_common.tcl]
@@ -42,23 +42,4 @@
SELECT count(*) FROM t1('mno')
} $nLoop
-do_execsql_test 2.0 {
- CREATE VIRTUAL TABLE t2 USING fts5(x);
- INSERT INTO t2(t2, rank) VALUES('pgsz', 32);
-}
-
-do_test 2.1 {
- for {set ii 0} {$ii < $nLoop} {incr ii} {
- execsql {
- INSERT INTO t2 VALUES('abc def ghi');
- INSERT INTO t2 VALUES('jkl mno pqr');
- INSERT INTO t2(t2, rank) VALUES('merge', -1);
- }
- }
-} {}
-
-do_execsql_test 2.2 {
- SELECT count(*) FROM t2('mno')
-} $nLoop
-
finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5optimize3.test sqlite3-3.44.0-0/ext/fts5/test/fts5optimize3.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5optimize3.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5optimize3.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,45 @@
+# 2023 Aug 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.
+#
+#***********************************************************************
+#
+# TESTRUNNER: superslow
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5optimize2
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+set nLoop 2500
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t2 USING fts5(x);
+ INSERT INTO t2(t2, rank) VALUES('pgsz', 32);
+}
+
+do_test 1.1 {
+ for {set ii 0} {$ii < $nLoop} {incr ii} {
+ execsql {
+ INSERT INTO t2 VALUES('abc def ghi');
+ INSERT INTO t2 VALUES('jkl mno pqr');
+ INSERT INTO t2(t2, rank) VALUES('merge', -1);
+ }
+ }
+} {}
+
+do_execsql_test 1.2 {
+ SELECT count(*) FROM t2('mno')
+} $nLoop
+
+finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5rank.test sqlite3-3.44.0-0/ext/fts5/test/fts5rank.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5rank.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5rank.test 2023-11-04 14:24:27.000000000 +0000
@@ -180,4 +180,28 @@
{table table table} {the table names.} {rank on an fts5 table}
}
+
+#-------------------------------------------------------------------------
+# forum post: https://sqlite.org/forum/forumpost/a2dd636330
+#
+reset_db
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t USING fts5 (a, b);
+ INSERT INTO t (a, b) VALUES ('data1', 'sentence1'), ('data2', 'sentence2');
+ INSERT INTO t(t, rank) VALUES ('rank', 'bm25(10.0,1.0)');
+}
+
+sqlite3 db2 test.db
+do_execsql_test -db db2 1.1 {
+ SELECT *, rank<0.0 FROM t('data*') ORDER BY RANK;
+} {data1 sentence1 1 data2 sentence2 1}
+
+do_execsql_test 1.2 {
+ INSERT INTO t(t, rank) VALUES ('rank', 'bm25(10.0,1.0)');
+}
+do_execsql_test -db db2 1.3 {
+ SELECT *, rank<0.0 FROM t('data*') ORDER BY RANK;
+} {data1 sentence1 1 data2 sentence2 1}
+db2 close
+
finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5savepoint.test sqlite3-3.44.0-0/ext/fts5/test/fts5savepoint.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5savepoint.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5savepoint.test 2023-11-04 14:24:27.000000000 +0000
@@ -71,7 +71,7 @@
do_catchsql_test 3.2 {
DROP TABLE vt1;
- } {1 {SQL logic error}}
+ } {0 {}}
do_execsql_test 3.3 {
SAVEPOINT x;
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5secure.test sqlite3-3.44.0-0/ext/fts5/test/fts5secure.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5secure.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5secure.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,348 @@
+# 2023 Feb 17
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+ifcapable !fts5 { finish_test ; return }
+set ::testprefix fts5secure
+
+proc dump {tname} {
+ execsql_pp "SELECT * FROM ${tname}_idx"
+ execsql_pp "SELECT id, quote(block), fts5_decode(id,block) FROM ${tname}_data"
+}
+
+
+do_execsql_test 0.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(ab);
+ CREATE VIRTUAL TABLE v1 USING fts5vocab('t1', 'instance');
+ INSERT INTO t1(rowid, ab) VALUES
+ (0,'abc'), (1,'abc'), (2,'abc'), (3,'abc'), (4,'def');
+}
+
+do_execsql_test 0.1 {
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+}
+
+do_execsql_test 0.2 {
+ DELETE FROM t1 WHERE rowid=2;
+}
+
+do_execsql_test 0.3 {
+ SELECT count(*) FROM t1_data
+} 3
+
+do_execsql_test 0.4 {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+}
+
+do_execsql_test 0.5 {
+ DELETE FROM t1 WHERE rowid=3;
+}
+
+do_execsql_test 0.6 {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+}
+
+do_execsql_test 0.7 {
+ DELETE FROM t1 WHERE rowid=0;
+}
+
+do_execsql_test 0.8 {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+}
+
+#----------------------------------
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t2 USING fts5(ab);
+ INSERT INTO t2(rowid, ab) VALUES (5, 'key'), (6, 'value');
+ INSERT INTO t2(t2, rank) VALUES('secure-delete', 1);
+}
+
+#execsql_pp { SELECT id, quote(block) FROM t1_data }
+#execsql_pp { SELECT segid, quote(term), pgno FROM t1_idx }
+
+do_execsql_test 1.1 {
+ DELETE FROM t2 WHERE rowid = 5;
+}
+
+do_execsql_test 1.2 {
+ INSERT INTO t2(t2) VALUES('integrity-check');
+}
+
+do_execsql_test 1.3 {
+ DELETE FROM t2 WHERE rowid = 6;
+}
+
+do_execsql_test 1.4 {
+ INSERT INTO t2(t2) VALUES('integrity-check');
+}
+
+do_execsql_test 1.5 {
+ SELECT * FROM t2('value');
+ SELECT * FROM t2('v*');
+}
+
+do_execsql_test 1.6 {
+ SELECT * FROM t2('value') ORDER BY rowid DESC;
+ SELECT * FROM t2('v*') ORDER BY rowid DESC;
+}
+execsql_pp {
+ SELECT id, quote(block) FROM t2_data;
+}
+
+#----------------------------------
+
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(ab);
+ CREATE VIRTUAL TABLE vocab USING fts5vocab('ft', 'instance');
+ INSERT INTO ft(rowid, ab) VALUES
+ (1, 'one'),
+ (2, 'two'),
+ (3, 'three'),
+ (4, 'four'),
+ (5, 'one one'),
+ (6, 'one two'),
+ (7, 'one three'),
+ (8, 'one four'),
+ (9, 'two one'),
+ (10, 'two two'),
+ (11, 'two three'),
+ (12, 'two four'),
+ (13, 'three one'),
+ (14, 'three two'),
+ (15, 'three three'),
+ (16, 'three four');
+}
+
+do_execsql_test 2.1 {
+ SELECT count(*) FROM ft_data;
+} {3}
+
+do_execsql_test 2.2 {
+ INSERT INTO ft(ft, rank) VALUES('secure-delete', 1);
+}
+
+do_execsql_test 2.3 {
+ DELETE FROM ft WHERE rowid=9;
+}
+
+do_execsql_test 2.4 {
+ INSERT INTO ft(ft) VALUES('integrity-check');
+}
+
+do_execsql_test 2.5 {
+ DELETE FROM ft WHERE ab LIKE '%two%'
+}
+
+do_execsql_test 2.6 {
+ INSERT INTO ft(ft) VALUES('integrity-check');
+}
+
+do_execsql_test 2.7 {
+ SELECT count(*) FROM ft_data;
+} {3}
+
+#----------------------------------
+reset_db
+
+set ::vocab {
+ one two three four five six seven eight nine ten
+ eleven twelve thirteen fourteen fifteen sixteen
+ seventeen eighteen nineteen twenty
+}
+proc rnddoc {} {
+ set nVocab [llength $::vocab]
+ set ret [list]
+ for {set ii 0} {$ii < 8} {incr ii} {
+ lappend ret [lindex $::vocab [expr int(abs(rand()) * $nVocab)]]
+ }
+ set ret
+}
+
+proc contains {list val} {
+ expr {[lsearch $list $val]>=0}
+}
+
+foreach {tn pgsz} {
+ 2 64
+ 1 1000
+} {
+ reset_db
+ db function rnddoc rnddoc
+ db function contains contains
+
+ expr srand(1)
+
+ do_execsql_test 3.$tn.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', $pgsz);
+ WITH s(i) AS (
+ VALUES(1) UNION SELECT i+1 FROM s WHERE i<20
+ )
+ INSERT INTO t1 SELECT rnddoc() FROM s;
+ }
+
+ do_execsql_test 3.$tn.1 {
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+ }
+
+ foreach {rowid} {
+ 6 16 3 4 9 14 13 7 20 15 19 10 11 2 5 18 17 1 12 8
+ } {
+
+ do_execsql_test 3.$tn.2.$rowid {
+ DELETE FROM t1 WHERE rowid=$rowid;
+ }
+ do_execsql_test 3.$tn.2.$rowid.ic {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+ }
+
+ foreach v $::vocab {
+ do_execsql_test 3.$tn.2.$rowid.q.$v {
+ SELECT rowid FROM t1($v)
+ } [db eval {SELECT rowid FROM t1 WHERE contains(x, $v)}]
+
+ do_execsql_test 3.$tn.2.$rowid.q.$v.DESC {
+ SELECT rowid FROM t1($v) ORDER BY 1 DESC
+ } [db eval {SELECT rowid FROM t1 WHERE contains(x, $v) ORDER BY 1 DESC}]
+ }
+ }
+}
+
+do_execsql_test 3.3 {
+ INSERT INTO t1(x) VALUES('optimize');
+ INSERT INTO t1(t1) VALUES('optimize');
+ SELECT count(*) FROM t1_data;
+} {3}
+
+#----------------------------------
+reset_db
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+}
+
+set L1 [string repeat abcdefghij 10]
+set L2 [string repeat 1234567890 10]
+
+do_execsql_test 4.1 {
+ INSERT INTO t1 VALUES('aa' || $L1 || ' ' || $L2);
+}
+do_execsql_test 4.2 {
+ DELETE FROM t1 WHERE rowid=1
+}
+do_execsql_test 4.3 {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+}
+
+#----------------------------------
+reset_db
+do_execsql_test 5.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+}
+
+set doc "aa [string repeat {abc } 60]"
+
+do_execsql_test 5.1 {
+ BEGIN;
+ INSERT INTO t1 VALUES($doc);
+ INSERT INTO t1 VALUES('aa abc');
+ COMMIT;
+}
+
+do_execsql_test 5.2 {
+ DELETE FROM t1 WHERE rowid = 1;
+}
+
+do_execsql_test 5.3 {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+}
+
+do_execsql_test 5.4 { SELECT rowid FROM t1('abc'); } 2
+do_execsql_test 5.5 { SELECT rowid FROM t1('aa'); } 2
+
+#-------------------------------------------------------------------------
+# Tests for the bug fixed by https://sqlite.org/src/info/4b60a1c3
+#
+reset_db
+do_execsql_test 6.0 {
+ CREATE VIRTUAL TABLE fts USING fts5(content);
+ INSERT INTO fts(fts, rank) VALUES ('secure-delete', 1);
+ INSERT INTO fts(rowid, content) VALUES
+ (3407, 'profile profile profile profile profile profile profile profile pull pulling pulling really');
+ DELETE FROM fts WHERE rowid IS 3407;
+ INSERT INTO fts(fts) VALUES ('integrity-check');
+}
+
+foreach {tn detail} {
+ 1 full
+ 2 column
+ 3 none
+} {
+ do_execsql_test 6.1.$detail "
+ DROP TABLE IF EXISTS t1;
+ CREATE VIRTUAL TABLE t1 USING fts5(x, detail=$detail);
+ "
+
+ do_execsql_test 6.2.$detail {
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+ }
+
+ for {set ii 1} {$ii < 100} {incr ii} {
+ do_execsql_test 6.3.$detail.$ii.1 {
+ BEGIN;
+ INSERT INTO t1(rowid, x) VALUES(10, 'word1');
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i0} {
+ 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 }
+#breakpoint
+#execsql_pp {
+# SELECT rowid FROM fff('post') ORDER BY rowid DESC
+#}
+#
+#dump fff
+
+
+finish_test
+
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5secure4.test sqlite3-3.44.0-0/ext/fts5/test/fts5secure4.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5secure4.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5secure4.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,170 @@
+# 2023 April 14
+#
+# 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]
+return_if_no_fts5
+set ::testprefix fts5secure4
+
+#-------------------------------------------------------------------------
+# Test using the 'delete' command to attempt to delete a token that
+# is not present in the index in secure-delete mode.
+#
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a, b, content=x1);
+
+ CREATE TABLE x1(rowid INTEGER PRIMARY KEY, a, b);
+ INSERT INTO x1 VALUES
+ (1, 'hello world', 'today xyz'),
+ (2, 'not the day', 'crunch crumble and chomp'),
+ (3, 'one', 'two');
+ INSERT INTO t1(t1) VALUES('rebuild');
+}
+
+do_execsql_test 1.1 {
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+}
+
+do_execsql_test 1.2 {
+ INSERT INTO t1(t1, rowid, a, b) VALUES('delete', 4, 'nosuchtoken', '');
+}
+
+do_execsql_test 1.3 {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+}
+
+do_execsql_test 1.4 {
+ INSERT INTO t1(t1, rowid, a, b) VALUES('delete', 1, 'crunch', '');
+}
+
+do_execsql_test 1.5 {
+ INSERT INTO t1(t1, rowid, a, b) VALUES('delete', 3, 'crunch', '');
+}
+
+do_execsql_test 1.6 {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+}
+
+do_execsql_test 1.7 {
+CREATE VIRTUAL TABLE y1 USING fts5(xx, prefix='1,2');
+INSERT INTO y1(y1, rank) VALUES('pgsz', 64);
+INSERT INTO y1(y1, rank) VALUES('secure-delete', 1);
+}
+do_execsql_test 1.8 {
+ BEGIN;
+ INSERT INTO y1(rowid, xx) VALUES(1, 'abc def');
+ INSERT INTO y1(rowid, xx) VALUES(2, 'reallyreallylongtoken');
+ COMMIT;
+}
+do_execsql_test 1.9 {
+ DELETE FROM y1 WHERE rowid=1;
+ INSERT INTO y1(y1) VALUES('integrity-check');
+}
+
+do_execsql_test 1.10 {
+ CREATE VIRTUAL TABLE w1 USING fts5(ww, content="");
+ INSERT INTO w1(rowid, ww) VALUES(123, '');
+}
+do_catchsql_test 1.11 {
+ INSERT INTO w1(w1, rowid, ww) VALUES('delete', 123, 'xyz');
+} {1 {database disk image is malformed}}
+do_catchsql_test 1.12 {
+ DROP TABLE w1;
+ CREATE VIRTUAL TABLE w1 USING fts5(ww, content="");
+ INSERT INTO w1(rowid, ww) VALUES(123, '');
+ DELETE FROM w1_data WHERE id>10;
+ INSERT INTO w1(w1, rowid, ww) VALUES('delete', 123, 'xyz');
+} {1 {database disk image is malformed}}
+
+#-------------------------------------------------------------------------
+# Test using secure-delete with detail=none or detail=col.
+#
+foreach {tn d} {1 full 2 none 3 column} {
+ reset_db
+ do_execsql_test 2.$tn.1 "
+ CREATE VIRTUAL TABLE x1 USING fts5(xx, yy, zz, detail=$d, prefix='10,20');
+ INSERT INTO x1(x1, rank) VALUES('pgsz', 64);
+ INSERT INTO x1(x1, rank) VALUES('secure-delete', 1);
+ "
+
+ do_execsql_test 2.$tn.2 {
+ BEGIN;
+ INSERT INTO x1(xx, yy, zz) VALUES('a b c', 'd e f', 'a b c');
+ INSERT INTO x1(xx, yy, zz) VALUES('a b c', 'd e f', 'a b c');
+ INSERT INTO x1(xx, yy, zz) VALUES('a b c', 'd e f', 'a b c');
+ INSERT INTO x1(xx, yy, zz) VALUES('a b c', 'd e f', 'a b c');
+ INSERT INTO x1(xx, yy, zz) VALUES('a b c', 'd e f', 'a b c');
+ COMMIT;
+ INSERT INTO x1(x1) VALUES('integrity-check');
+ }
+
+ do_execsql_test 2.$tn.3 {
+ DELETE FROM x1 WHERE rowid IN (2, 4, 6);
+ INSERT INTO x1(x1) VALUES('integrity-check');
+ }
+
+ do_execsql_test 2.$tn.4 {
+ DELETE FROM x1 WHERE rowid IN (1, 3, 5);
+ INSERT INTO x1(x1) VALUES('integrity-check');
+ }
+
+ do_execsql_test 2.$tn.5 {
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100
+ )
+ INSERT INTO x1
+ SELECT 'seems to be', 'used brew to', 'everything is working' FROM s
+ UNION ALL
+ SELECT 'used brew to', 'everything is working', 'seems to be' FROM s
+ UNION ALL
+ SELECT 'everything is working', 'seems to be', 'used brew to' FROM s
+ UNION ALL
+ SELECT 'abc', 'zzz', 'a b c d'
+ UNION ALL
+ SELECT 'z', 'z', 'z' FROM s
+ }
+
+ do_test 2.$tn.6 {
+ for {set i 300} {$i > 200} {incr i -1} {
+ execsql {
+ DELETE FROM x1 WHERE rowid=$i;
+ INSERT INTO x1(x1) VALUES('integrity-check');
+ }
+ }
+ } {}
+
+ do_test 2.$tn.7 {
+ for {set i 1} {$i < 100} {incr i} {
+ execsql {
+ DELETE FROM x1 WHERE rowid=$i;
+ INSERT INTO x1(x1) VALUES('integrity-check');
+ }
+ }
+ } {}
+
+ do_test 2.$tn.8 {
+ foreach i [db eval {SELECT rowid FROM x1}] {
+ execsql {
+ DELETE FROM x1 WHERE rowid=$i;
+ INSERT INTO x1(x1) VALUES('integrity-check');
+ }
+ }
+ } {}
+
+ do_execsql_test 2.$tn.9 {
+ SELECT * FROM x1
+ } {}
+}
+
+
+
+finish_test
+
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5secure5.test sqlite3-3.44.0-0/ext/fts5/test/fts5secure5.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5secure5.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5secure5.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,129 @@
+# 2023 April 14
+#
+# 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]
+return_if_no_fts5
+set ::testprefix fts5secure5
+return_if_no_fts5
+
+proc dump {} {
+ execsql_pp {
+ SELECT id, quote(block), fts5_decode_none(id, block) FROM ft1_data
+ }
+}
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE ft1 USING fts5(a, detail=none);
+ INSERT INTO ft1(ft1, rank) VALUES('secure-delete', 1);
+}
+
+do_execsql_test 1.1 {
+ BEGIN;
+ INSERT INTO ft1(rowid, a) VALUES(1, 'abcd');
+ INSERT INTO ft1(rowid, a) VALUES(2, 'abcd');
+ INSERT INTO ft1(rowid, a) VALUES(3, 'abcd');
+ COMMIT;
+}
+do_execsql_test 1.2 {
+ DELETE FROM ft1 WHERE rowid=1;
+}
+do_execsql_test 1.3 {
+ INSERT INTO ft1(ft1) VALUES('integrity-check');
+}
+do_execsql_test 1.4 {
+ DELETE FROM ft1 WHERE rowid=3;
+}
+do_execsql_test 1.5 {
+ INSERT INTO ft1(ft1) VALUES('integrity-check');
+}
+do_execsql_test 1.6 {
+ DELETE FROM ft1 WHERE rowid=3;
+}
+do_execsql_test 1.7 {
+ INSERT INTO ft1(ft1) VALUES('integrity-check');
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE ft1 USING fts5(a, detail=none);
+ INSERT INTO ft1(ft1, rank) VALUES('secure-delete', 1);
+}
+
+do_execsql_test 2.1 {
+ BEGIN;
+ INSERT INTO ft1(rowid, a) VALUES(1, 'abcd one');
+ INSERT INTO ft1(rowid, a) VALUES(2, 'abcd two');
+ INSERT INTO ft1(rowid, a) VALUES(3, 'abcd two');
+ INSERT INTO ft1(rowid, a) VALUES(4, 'abcd two');
+ INSERT INTO ft1(rowid, a) VALUES(5, 'abcd three');
+ COMMIT;
+}
+
+do_execsql_test 2.2a {
+ DELETE FROM ft1 WHERE rowid=3;
+}
+do_execsql_test 2.2b {
+ INSERT INTO ft1(ft1) VALUES('integrity-check');
+}
+do_execsql_test 2.3a {
+ DELETE FROM ft1 WHERE rowid=2;
+}
+do_execsql_test 2.3b {
+ INSERT INTO ft1(ft1) VALUES('integrity-check');
+}
+do_execsql_test 2.4a {
+ DELETE FROM ft1 WHERE rowid=4;
+}
+do_execsql_test 2.4b {
+ INSERT INTO ft1(ft1) VALUES('integrity-check');
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE ft1 USING fts5(a, detail=none, prefix=1);
+ INSERT INTO ft1(ft1, rank) VALUES('secure-delete', 1);
+ INSERT INTO ft1(ft1, rank) VALUES('pgsz', 64);
+}
+do_execsql_test 3.1 {
+ BEGIN;
+ INSERT INTO ft1(a) VALUES('c');
+ COMMIT;
+}
+do_execsql_test 3.2 {
+ DELETE FROM ft1 WHERE rowid IN (1);
+ INSERT INTO ft1(ft1) VALUES('integrity-check');
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE ft1 USING fts5(a, detail=none);
+ INSERT INTO ft1(ft1, rank) VALUES('secure-delete', 1);
+ INSERT INTO ft1(ft1, rank) VALUES('pgsz', 64);
+
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<500
+ )
+ INSERT INTO ft1 SELECT 'abcdefg' FROM s;
+}
+
+do_test 4.1 {
+ for {set i 500} {$i > 0} {incr i -1} {
+ execsql { DELETE FROM ft1 WHERE rowid=$i }
+ execsql { INSERT INTO ft1(ft1) VALUES('integrity-check') }
+ }
+} {}
+
+finish_test
+
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5secure6.test sqlite3-3.44.0-0/ext/fts5/test/fts5secure6.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5secure6.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5secure6.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,141 @@
+# 2023 Feb 17
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+ifcapable !fts5 { finish_test ; return }
+set ::testprefix fts5secure6
+
+db progress 1 progress_handler
+set ::PHC 0
+proc progress_handler {args} {
+ incr ::PHC
+ # if {($::PHC % 100000)==0} breakpoint
+ return 0
+}
+
+proc setup {} {
+ db eval {
+ DROP TABLE IF EXISTS t1;
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ WITH s(i) AS (
+ VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<1000
+ )
+ INSERT INTO t1 SELECT 'a b c d e f g h i j k' FROM s;
+ }
+}
+
+foreach {tn sd} {
+ 1 0
+ 2 1
+} {
+ setup
+ do_execsql_test 1.$tn.0 {
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', $sd)
+ }
+ set PHC 0
+ do_execsql_test 1.$tn.1 { DELETE FROM t1; }
+ set phc($tn) $PHC
+}
+
+do_test 1.3 {
+ expr $phc(1)*5 < $phc(2)
+} {1}
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', $sd)
+}
+
+do_execsql_test 2.1 {
+ BEGIN;
+ INSERT INTO t1(rowid, x) VALUES(-100000, 'abc def ghi');
+ INSERT INTO t1(rowid, x) VALUES(-99999, 'abc def ghi');
+ INSERT INTO t1(rowid, x) VALUES(9223372036854775800, 'abc def ghi');
+ COMMIT;
+}
+
+do_execsql_test 2.2 {
+ SELECT rowid FROM t1('def')
+} {-100000 -99999 9223372036854775800}
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', $sd)
+}
+
+do_execsql_test 3.1 {
+ BEGIN;
+ INSERT INTO t1(rowid, x)
+ VALUES(51869, 'when whenever where weress what turn'),
+ (51871, 'to were');
+ COMMIT;
+}
+
+do_execsql_test 3.2 {
+ DELETE FROM t1 WHERE rowid=51871;
+ INSERT INTO t1(t1) VALUES('integrity-check');
+}
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ INSERT INTO t1(rowid, x) VALUES(10, 'one two');
+}
+do_execsql_test 4.1 {
+ UPDATE t1 SET x = 'one three' WHERE rowid=10;
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+}
+do_execsql_test 4.2 {
+ DELETE FROM t1 WHERE rowid=10;
+}
+do_execsql_test 4.3 {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+}
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 5.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(content);
+
+ INSERT INTO t1(t1,rank) VALUES('secure-delete',1);
+ INSERT INTO t1 VALUES('active'),('boomer'),('atom'),('atomic'),
+ ('alpha channel backup abandon test aback boomer atom alpha active');
+ DELETE FROM t1 WHERE t1 MATCH 'abandon';
+}
+
+do_execsql_test 5.1 {
+ INSERT INTO t1(t1) VALUES('rebuild');
+}
+
+do_execsql_test 5.2 {
+ DELETE FROM t1 WHERE rowid NOTNULL<5;
+}
+
+db close
+sqlite3 db test.db
+
+do_execsql_test 5.3 {
+ PRAGMA integrity_check;
+} {ok}
+
+
+finish_test
+
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5secure7.test sqlite3-3.44.0-0/ext/fts5/test/fts5secure7.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5secure7.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5secure7.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,116 @@
+# 2023 Feb 17
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+#
+# TESTRUNNER: slow
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+ifcapable !fts5 { finish_test ; return }
+set ::testprefix fts5secure7
+
+
+set NVOCAB 500
+set NDOC [expr 1000]
+
+set NREP 100
+set nDeletePerRep [expr 5]
+
+set VOCAB [list]
+
+proc select_one {list} {
+ set n [llength $list]
+ lindex $list [expr {abs(int(rand()*$n))}]
+}
+
+proc init_vocab {} {
+ set L [split "abcdefghijklmnopqrstuvwxyz" {}]
+ set nL [llength $L]
+ for {set i 0} {$i < $::NVOCAB} {incr i} {
+ set n [expr {6 + int(rand()*8)}]
+ set word ""
+ for {set j 0} {$j < $n} {incr j} {
+ append word [select_one $L]
+ }
+ lappend ::VOCAB $word
+ }
+}
+
+proc get_word {} {
+ select_one $::VOCAB
+}
+
+proc get_document {nWord} {
+ set ret [list]
+ for {set i 0} {$i < $nWord} {incr i} {
+ lappend ret [get_word]
+ }
+ return $ret
+}
+
+init_vocab
+
+db func document [list get_document 12]
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(body);
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+}
+do_execsql_test 1.1 {
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<$NDOC
+ )
+ INSERT INTO t1 SELECT document() FROM s;
+}
+
+for {set iRep 0} {$iRep < $NREP} {incr iRep} {
+ set lRowid [db eval {SELECT rowid FROM t1}]
+ for {set iDel 0} {$iDel < $nDeletePerRep} {incr iDel} {
+ set idx [select_one $lRowid]
+ db eval {
+ DELETE FROM t1 WHERE rowid=$idx
+ }
+ }
+ db eval {
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<$nDeletePerRep
+ )
+ INSERT INTO t1 SELECT document() FROM s;
+ }
+ do_execsql_test 1.2.$iRep {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+ }
+}
+
+reset_db
+db func document [list get_document 12]
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(body);
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 128);
+}
+do_execsql_test 2.1 {
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<$NDOC
+ )
+ INSERT INTO t1 SELECT document() FROM s;
+}
+for {set ii 0} {$ii < $NDOC} {incr ii} {
+ set lRowid [db eval {SELECT rowid FROM t1}]
+ set idx [select_one $lRowid]
+ db eval { DELETE FROM t1 WHERE rowid=$idx }
+ do_execsql_test 2.2.$ii {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+ }
+}
+
+finish_test
+
+
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5securefault.test sqlite3-3.44.0-0/ext/fts5/test/fts5securefault.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5securefault.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5securefault.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,225 @@
+# 2023 April 14
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this script is testing the FTS5 module.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+source $testdir/malloc_common.tcl
+set testprefix fts5securefault
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+return_if_no_fts5
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(ab);
+ INSERT INTO t1(rowid, ab) VALUES
+ (0, 'abc'), (1, 'abc'), (2, 'abc'), (3, 'abc'), (4, 'def');
+}
+faultsim_save_and_close
+
+do_faultsim_test 1.1 -faults oom* -prep {
+ faultsim_restore_and_reopen
+ execsql {
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+ }
+} -body {
+ execsql { DELETE FROM t1 WHERE rowid=2 }
+} -test {
+ faultsim_test_result {0 {}}
+}
+do_faultsim_test 1.2 -faults oom* -prep {
+ faultsim_restore_and_reopen
+ execsql {
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+ }
+} -body {
+ execsql { DELETE FROM t1 WHERE rowid IN(0, 1, 2, 3, 4) }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+set big [string repeat abcdefghij 5]
+set big2 [string repeat klmnopqrst 5]
+set doc "$big $big2"
+
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(ab);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<4
+ )
+ INSERT INTO t1(rowid, ab) SELECT i, $doc FROM s;
+}
+faultsim_save_and_close
+
+do_faultsim_test 2.1 -faults oom* -prep {
+ faultsim_restore_and_reopen
+ execsql {
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+ }
+} -body {
+ execsql { DELETE FROM t1 WHERE rowid = 3 }
+ execsql { DELETE FROM t1 WHERE rowid = 4 }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+set big [string repeat abcdefghij 5]
+set big2 [string repeat klmnopqrst 5]
+set doc "$big $big2"
+
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(ab);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<25
+ )
+ INSERT INTO t1(rowid, ab) SELECT i, $doc FROM s;
+
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+ DELETE FROM t1 WHERE rowid BETWEEN 3 AND 23;
+}
+faultsim_save_and_close
+
+do_faultsim_test 3.1 -faults oom* -prep {
+ faultsim_restore_and_reopen
+ execsql {
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+ }
+} -body {
+ execsql { DELETE FROM t1 WHERE rowid = 24 }
+ execsql { DELETE FROM t1 WHERE rowid = 25 }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+set doc [string repeat "tok " 400]
+
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(ab);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
+ INSERT INTO t1(rowid, ab) VALUES(1, $doc), (2, $doc), (3, $doc);
+}
+faultsim_save_and_close
+
+do_faultsim_test 4.1 -faults oom* -prep {
+ faultsim_restore_and_reopen
+ execsql {
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+ }
+} -body {
+ execsql { DELETE FROM t1 WHERE rowid = 2 }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+
+set doc1 [string repeat "abc " 10]
+set doc2 [string repeat "def " 10]
+
+do_test 5.0 {
+ execsql {
+ CREATE VIRTUAL TABLE t1 USING fts5(ab);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
+ BEGIN;
+ }
+ for {set i 0} {$i < 50} {incr i} {
+ execsql {
+ INSERT INTO t1(rowid, ab) VALUES($i, 'abcdefg');
+ }
+ }
+ execsql {
+ INSERT INTO t1(rowid, ab) VALUES(105, 'def');
+ COMMIT;
+ }
+} {}
+faultsim_save_and_close
+
+do_faultsim_test 5.1 -faults oom* -prep {
+ faultsim_restore_and_reopen
+ execsql {
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+ }
+} -body {
+ execsql { DELETE FROM t1 WHERE rowid = 105 }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+do_test 6.0 {
+ execsql {
+ CREATE VIRTUAL TABLE t1 USING fts5(ab);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
+ BEGIN;
+ INSERT INTO t1(rowid, ab) VALUES(1, 'abcdefg');
+ INSERT INTO t1(rowid, ab) VALUES(2, 'abcdefg');
+ INSERT INTO t1(rowid, ab) VALUES(3, 'abcdefg');
+ COMMIT;
+ }
+} {}
+faultsim_save_and_close
+
+do_faultsim_test 6.1 -faults oom* -prep {
+ faultsim_restore_and_reopen
+ execsql {
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+ }
+} -body {
+ execsql {
+ UPDATE t1 SET ab='abcdefg' WHERE rowid=2;
+ }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+do_test 7.0 {
+ execsql {
+ CREATE VIRTUAL TABLE t1 USING fts5(ab);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+ }
+} {}
+faultsim_save_and_close
+
+do_faultsim_test 7.1 -faults oom* -prep {
+ faultsim_restore_and_reopen
+ set big1 "[string repeat x 50] [string repeat y 50] [string repeat z 50]"
+ execsql {
+ BEGIN;
+ INSERT INTO t1 VALUES($big1);
+ }
+} -body {
+ execsql { COMMIT }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+
+finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5synonym2.test sqlite3-3.44.0-0/ext/fts5/test/fts5synonym2.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5synonym2.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5synonym2.test 2023-11-04 14:24:27.000000000 +0000
@@ -122,6 +122,9 @@
4.1 "NEAR(one two, 2)"
4.2 "NEAR(one two three, 2)"
4.3 "NEAR(eight nine, 1) OR NEAR(six seven, 1)"
+
+ 5.1 "one + two"
+ 5.2 "1 + two"
} {
if {[fts5_expr_ok $expr ss]==0} {
do_test 1.$tok.$tn.OMITTED { list } [list]
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5trigram.test sqlite3-3.44.0-0/ext/fts5/test/fts5trigram.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5trigram.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5trigram.test 2023-11-04 14:24:27.000000000 +0000
@@ -215,4 +215,42 @@
SELECT rowid FROM f WHERE filename GLOB '*ир*';
} {20}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 8.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(y, tokenize=trigram);
+ INSERT INTO t1 VALUES('abcdefghijklm');
+}
+
+foreach {tn match res} {
+ 1 "abc ghi" "(abc)def(ghi)jklm"
+ 2 "def ghi" "abc(defghi)jklm"
+ 3 "efg ghi" "abcd(efghi)jklm"
+ 4 "efghi" "abcd(efghi)jklm"
+ 5 "abcd jklm" "(abcd)efghi(jklm)"
+ 6 "ijkl jklm" "abcdefgh(ijklm)"
+ 7 "ijk ijkl hijk" "abcdefg(hijkl)m"
+
+} {
+ do_execsql_test 8.1.$tn {
+ SELECT highlight(t1, 0, '(', ')') FROM t1($match)
+ } $res
+}
+
+do_execsql_test 8.2 {
+ CREATE VIRTUAL TABLE ft2 USING fts5(a, tokenize="trigram");
+ INSERT INTO ft2 VALUES('abc x cde');
+ INSERT INTO ft2 VALUES('abc cde');
+ INSERT INTO ft2 VALUES('abcde');
+}
+
+do_execsql_test 8.3 {
+ SELECT highlight(ft2, 0, '[', ']') FROM ft2 WHERE ft2 MATCH 'abc AND cde';
+} {
+ {[abc] x [cde]}
+ {[abc] [cde]}
+ {[abcde]}
+}
+
finish_test
diff -Nru sqlite3-3.41.0-0/ext/fts5/test/fts5version.test sqlite3-3.44.0-0/ext/fts5/test/fts5version.test
--- sqlite3-3.41.0-0/ext/fts5/test/fts5version.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5version.test 2023-11-04 14:24:27.000000000 +0000
@@ -38,20 +38,20 @@
sqlite3_db_config db DEFENSIVE 0
do_execsql_test 1.4 {
- UPDATE t1_config set v=5 WHERE k='version';
+ UPDATE t1_config set v=6 WHERE k='version';
}
do_test 1.5 {
db close
sqlite3 db test.db
catchsql { SELECT * FROM t1 WHERE t1 MATCH 'a' }
-} {1 {invalid fts5 file format (found 5, expected 4) - run 'rebuild'}}
+} {1 {invalid fts5 file format (found 6, expected 4 or 5) - run 'rebuild'}}
do_test 1.6 {
db close
sqlite3 db test.db
catchsql { INSERT INTO t1 VALUES('x y z') }
-} {1 {invalid fts5 file format (found 5, expected 4) - run 'rebuild'}}
+} {1 {invalid fts5 file format (found 6, expected 4 or 5) - run 'rebuild'}}
do_test 1.7 {
sqlite3_db_config db DEFENSIVE 0
@@ -59,7 +59,75 @@
db close
sqlite3 db test.db
catchsql { SELECT * FROM t1 WHERE t1 MATCH 'a' }
-} {1 {invalid fts5 file format (found 0, expected 4) - run 'rebuild'}}
+} {1 {invalid fts5 file format (found 0, expected 4 or 5) - run 'rebuild'}}
+
+do_test 1.8 {
+ sqlite3_db_config db DEFENSIVE 0
+ execsql { INSERT INTO t1_config VALUES('version', 4) }
+ execsql { INSERT INTO t1(t1, rank) VALUES('secure-delete', 1) }
+} {}
+
+do_execsql_test 1.10 {
+ SELECT * FROM t1_config
+} {secure-delete 1 version 4}
+
+do_execsql_test 1.11 {
+ INSERT INTO t1(rowid, one) VALUES(123, 'one two three');
+ DELETE FROM t1 WHERE rowid=123;
+ SELECT * FROM t1_config
+} {secure-delete 1 version 5}
+
+do_execsql_test 1.11 {
+ INSERT INTO t1(t1) VALUES('rebuild');
+ SELECT * FROM t1_config
+} {secure-delete 1 version 4}
+
+do_execsql_test 1.12 {
+ SELECT * FROM t1_config
+} {secure-delete 1 version 4}
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE xyz USING fts5(x);
+ INSERT INTO xyz(rowid, x) VALUES
+ (1, 'one document'),
+ (2, 'two document'),
+ (3, 'three document'),
+ (4, 'four document'),
+ (5, 'five document'),
+ (6, 'six document');
+
+ INSERT INTO xyz(xyz, rank) VALUES('secure-delete', 1);
+ SELECT v FROM xyz_config WHERE k='version';
+} {4}
+
+do_execsql_test 2.1 {
+ BEGIN;
+ INSERT INTO xyz(rowid, x) VALUES(7, 'seven document');
+ SAVEPOINT one;
+ DELETE FROM xyz WHERE rowid = 4;
+}
+
+do_execsql_test 2.2 {
+ SELECT v FROM xyz_config WHERE k='version';
+} {5}
+
+do_execsql_test 2.3 {
+ ROLLBACK TO one;
+ SELECT v FROM xyz_config WHERE k='version';
+} {4}
+
+
+do_execsql_test 2.4 {
+ DELETE FROM xyz WHERE rowid = 3;
+ COMMIT;
+ SELECT v FROM xyz_config WHERE k='version';
+} {5}
+
+
finish_test
+
diff -Nru sqlite3-3.41.0-0/ext/jni/GNUmakefile sqlite3-3.44.0-0/ext/jni/GNUmakefile
--- sqlite3-3.41.0-0/ext/jni/GNUmakefile 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/GNUmakefile 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,498 @@
+# Quick-and-dirty makefile to bootstrap the sqlite3-jni project. This
+# build assumes a Linux-like system.
+default: all
+
+JAVA_HOME ?= $(HOME)/jdk/current
+# e.g. /usr/lib/jvm/default-javajava-19-openjdk-amd64
+JDK_HOME ?= $(JAVA_HOME)
+# ^^^ JDK_HOME is not as widely used as JAVA_HOME
+bin.jar := $(JDK_HOME)/bin/jar
+bin.java := $(JDK_HOME)/bin/java
+bin.javac := $(JDK_HOME)/bin/javac
+bin.javadoc := $(JDK_HOME)/bin/javadoc
+ifeq (,$(wildcard $(JDK_HOME)))
+$(error set JDK_HOME to the top-most dir of your JDK installation.)
+endif
+MAKEFILE := $(lastword $(MAKEFILE_LIST))
+$(MAKEFILE):
+
+package.jar := sqlite3-jni.jar
+
+dir.top := ../..
+dir.tool := ../../tool
+dir.jni := $(patsubst %/,%,$(dir $(MAKEFILE)))
+dir.src := $(dir.jni)/src
+dir.src.c := $(dir.src)/c
+dir.bld := $(dir.jni)/bld
+dir.bld.c := $(dir.bld)
+dir.src.jni := $(dir.src)/org/sqlite/jni
+dir.src.capi := $(dir.src.jni)/capi
+dir.src.fts5 := $(dir.src.jni)/fts5
+dir.tests := $(dir.src)/tests
+mkdir ?= mkdir -p
+$(dir.bld.c):
+ $(mkdir) $@
+
+javac.flags ?= -Xlint:unchecked -Xlint:deprecation
+java.flags ?=
+jnicheck ?= 1
+ifeq (1,$(jnicheck))
+ java.flags += -Xcheck:jni
+endif
+
+classpath := $(dir.src)
+CLEAN_FILES := $(package.jar)
+DISTCLEAN_FILES := $(dir.jni)/*~ $(dir.src.c)/*~ $(dir.src.jni)/*~
+
+sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h
+.NOTPARALLEL: $(sqlite3-jni.h)
+CApi.java := $(dir.src.capi)/CApi.java
+SQLTester.java := $(dir.src.capi)/SQLTester.java
+CApi.class := $(CApi.java:.java=.class)
+SQLTester.class := $(SQLTester.java:.java=.class)
+
+########################################################################
+# The future of FTS5 customization in this API is as yet unclear.
+# The pieces are all in place, and are all thin proxies so not much
+# complexity, but some semantic changes were required in porting
+# which are largely untested.
+#
+# Reminder: this flag influences the contents of $(sqlite3-jni.h),
+# which is checked in. Please do not check in changes to that file in
+# which the fts5 APIs have been stripped unless that feature is
+# intended to be stripped for good.
+enable.fts5 ?= 1
+
+ifeq (,$(wildcard $(dir.tests)/*))
+ enable.tester := 0
+else
+ enable.tester := 1
+endif
+
+# bin.version-info = binary to output various sqlite3 version info
+# building the distribution zip file.
+bin.version-info := $(dir.top)/version-info
+.NOTPARALLEL: $(bin.version-info)
+$(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile
+ $(MAKE) -C $(dir.top) version-info
+
+# 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/%,\
+ NotNull.java \
+ Nullable.java \
+) $(patsubst %,$(dir.src.capi)/%,\
+ AbstractCollationCallback.java \
+ AggregateFunction.java \
+ AuthorizerCallback.java \
+ AutoExtensionCallback.java \
+ BusyHandlerCallback.java \
+ CollationCallback.java \
+ CollationNeededCallback.java \
+ CommitHookCallback.java \
+ ConfigLogCallback.java \
+ ConfigSqllogCallback.java \
+ NativePointerHolder.java \
+ OutputPointer.java \
+ PrepareMultiCallback.java \
+ PreupdateHookCallback.java \
+ ProgressHandlerCallback.java \
+ ResultCode.java \
+ RollbackHookCallback.java \
+ ScalarFunction.java \
+ SQLFunction.java \
+ CallbackProxy.java \
+ CApi.java \
+ TableColumnMetadata.java \
+ TraceV2Callback.java \
+ UpdateHookCallback.java \
+ ValueHolder.java \
+ WindowFunction.java \
+ XDestroyCallback.java \
+ sqlite3.java \
+ sqlite3_context.java \
+ sqlite3_stmt.java \
+ sqlite3_value.java \
+) $(patsubst %,$(dir.src.jni)/wrapper1/%,\
+ AggregateFunction.java \
+ ScalarFunction.java \
+ SqlFunction.java \
+ Sqlite.java \
+ SqliteException.java \
+ ValueHolder.java \
+)
+
+JAVA_FILES.unittest := $(patsubst %,$(dir.src.jni)/%,\
+ capi/Tester1.java \
+ wrapper1/Tester2.java \
+)
+ifeq (1,$(enable.fts5))
+ JAVA_FILES.unittest += $(patsubst %,$(dir.src.fts5)/%,\
+ TesterFts5.java \
+ )
+ JAVA_FILES.main += $(patsubst %,$(dir.src.fts5)/%,\
+ fts5_api.java \
+ fts5_extension_function.java \
+ fts5_tokenizer.java \
+ Fts5.java \
+ Fts5Context.java \
+ Fts5ExtensionApi.java \
+ Fts5PhraseIter.java \
+ Fts5Tokenizer.java \
+ XTokenizeCallback.java \
+ )
+endif
+JAVA_FILES.tester := $(SQLTester.java)
+JAVA_FILES.package.info := \
+ $(dir.src.jni)/package-info.java \
+ $(dir.src.jni)/annotation/package-info.java
+
+CLASS_FILES.main := $(JAVA_FILES.main:.java=.class)
+CLASS_FILES.unittest := $(JAVA_FILES.unittest:.java=.class)
+CLASS_FILES.tester := $(JAVA_FILES.tester:.java=.class)
+
+JAVA_FILES += $(JAVA_FILES.main) $(JAVA_FILES.unittest)
+ifeq (1,$(enable.tester))
+ JAVA_FILES += $(JAVA_FILES.tester)
+endif
+
+CLASS_FILES :=
+define CLASSFILE_DEPS
+all: $(1).class
+CLASS_FILES += $(1).class
+endef
+$(foreach B,$(basename \
+ $(JAVA_FILES.main) $(JAVA_FILES.unittest) $(JAVA_FILES.tester)),\
+ $(eval $(call CLASSFILE_DEPS,$(B))))
+$(CLASS_FILES): $(JAVA_FILES) $(MAKEFILE)
+ $(bin.javac) $(javac.flags) -h $(dir.bld.c) -cp $(classpath) $(JAVA_FILES)
+
+#.PHONY: classfiles
+
+########################################################################
+# Set up sqlite3.c and sqlite3.h...
+#
+# To build with SEE (https://sqlite.org/see), either put sqlite3-see.c
+# in the top of this build tree or pass
+# sqlite3.c=PATH_TO_sqlite3-see.c to the build. Note that only
+# encryption modules with no 3rd-party dependencies will currently
+# work here: AES256-OFB, AES128-OFB, and AES128-CCM. Not
+# coincidentally, those 3 modules are included in the sqlite3-see.c
+# bundle.
+#
+# A custom sqlite3.c must not have any spaces in its name.
+# $(sqlite3.canonical.c) must point to the sqlite3.c in
+# the sqlite3 canonical source tree, as that source file
+# is required for certain utility and test code.
+sqlite3.canonical.c := $(firstword $(wildcard $(dir.src.c)/sqlite3.c) $(dir.top)/sqlite3.c)
+sqlite3.canonical.h := $(firstword $(wildcard $(dir.src.c)/sqlite3.h) $(dir.top)/sqlite3.h)
+sqlite3.c := $(sqlite3.canonical.c)
+sqlite3.h := $(sqlite3.canonical.h)
+#ifeq (,$(shell grep sqlite3_activate_see $(sqlite3.c) 2>/dev/null))
+# SQLITE_C_IS_SEE := 0
+#else
+# SQLITE_C_IS_SEE := 1
+# $(info This is an SEE build.)
+#endif
+
+.NOTPARALLEL: $(sqlite3.h)
+$(sqlite3.h):
+ $(MAKE) -C $(dir.top) sqlite3.c
+$(sqlite3.c): $(sqlite3.h)
+
+opt.threadsafe ?= 1
+opt.fatal-oom ?= 1
+opt.debug ?= 1
+opt.metrics ?= 1
+SQLITE_OPT = \
+ -DSQLITE_THREADSAFE=$(opt.threadsafe) \
+ -DSQLITE_TEMP_STORE=2 \
+ -DSQLITE_USE_URI=1 \
+ -DSQLITE_OMIT_LOAD_EXTENSION \
+ -DSQLITE_OMIT_DEPRECATED \
+ -DSQLITE_OMIT_SHARED_CACHE \
+ -DSQLITE_C=$(sqlite3.c) \
+ -DSQLITE_JNI_FATAL_OOM=$(opt.fatal-oom) \
+ -DSQLITE_JNI_ENABLE_METRICS=$(opt.metrics)
+
+opt.extras ?= 1
+ifeq (1,$(opt.extras))
+SQLITE_OPT += -DSQLITE_ENABLE_RTREE \
+ -DSQLITE_ENABLE_EXPLAIN_COMMENTS \
+ -DSQLITE_ENABLE_STMTVTAB \
+ -DSQLITE_ENABLE_DBPAGE_VTAB \
+ -DSQLITE_ENABLE_DBSTAT_VTAB \
+ -DSQLITE_ENABLE_BYTECODE_VTAB \
+ -DSQLITE_ENABLE_OFFSET_SQL_FUNC \
+ -DSQLITE_ENABLE_PREUPDATE_HOOK \
+ -DSQLITE_ENABLE_NORMALIZE \
+ -DSQLITE_ENABLE_SQLLOG
+endif
+
+ifeq (1,$(opt.debug))
+ SQLITE_OPT += -DSQLITE_DEBUG -g -DDEBUG -UNDEBUG
+else
+ SQLITE_OPT += -Os
+endif
+
+ifeq (1,$(enable.fts5))
+ SQLITE_OPT += -DSQLITE_ENABLE_FTS5
+endif
+
+sqlite3-jni.c := $(dir.src.c)/sqlite3-jni.c
+sqlite3-jni.o := $(dir.bld.c)/sqlite3-jni.o
+sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h
+package.dll := $(dir.bld.c)/libsqlite3-jni.so
+# All javac-generated .h files must be listed in $(sqlite3-jni.h.in):
+sqlite3-jni.h.in :=
+# $(java.with.jni) lists all Java files which contain JNI decls:
+java.with.jni :=
+define ADD_JNI_H
+sqlite3-jni.h.in += $$(dir.bld.c)/org_sqlite_jni$(3)_$(2).h
+java.with.jni += $(1)/$(2).java
+$$(dir.bld.c)/org_sqlite_jni$(3)_$(2).h: $(1)/$(2).java
+endef
+# Invoke ADD_JNI_H once for each Java file which includes JNI
+# declarations:
+$(eval $(call ADD_JNI_H,$(dir.src.capi),CApi,_capi))
+$(eval $(call ADD_JNI_H,$(dir.src.capi),SQLTester,_capi))
+ifeq (1,$(enable.fts5))
+ $(eval $(call ADD_JNI_H,$(dir.src.fts5),Fts5ExtensionApi,_fts5))
+ $(eval $(call ADD_JNI_H,$(dir.src.fts5),fts5_api,_fts5))
+ $(eval $(call ADD_JNI_H,$(dir.src.fts5),fts5_tokenizer,_fts5))
+endif
+$(sqlite3-jni.h.in): $(dir.bld.c)
+
+#package.dll.cfiles :=
+package.dll.cflags = \
+ -std=c99 \
+ -fPIC \
+ -I. \
+ -I$(dir $(sqlite3.h)) \
+ -I$(dir.src.c) \
+ -I$(JDK_HOME)/include \
+ $(patsubst %,-I%,$(patsubst %.h,,$(wildcard $(JDK_HOME)/include/*))) \
+ -Wall
+# The gross $(patsubst...) above is to include the platform-specific
+# subdir which lives under $(JDK_HOME)/include and is a required
+# include path for client-level code.
+#
+# Using (-Wall -Wextra) triggers an untennable number of
+# gcc warnings from sqlite3.c for mundane things like
+# unused parameters.
+########################################################################
+ifeq (1,$(enable.tester))
+ package.dll.cflags += -DSQLITE_JNI_ENABLE_SQLTester
+endif
+
+$(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE)
+ @cat $(sqlite3-jni.h.in) > $@.tmp
+ @if cmp $@ $@.tmp >/dev/null; then \
+ rm -f $@.tmp; \
+ echo "$@ not modified"; \
+ else \
+ mv $@.tmp $@; \
+ echo "Updated $@"; \
+ fi
+ @if [ x1 != x$(enable.fts5) ]; then \
+ echo "*** REMINDER:"; \
+ echo "*** enable.fts5=0, so please do not check in changes to $@."; \
+ fi
+
+$(package.dll): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h)
+$(package.dll): $(sqlite3-jni.c) $(MAKEFILE)
+ $(CC) $(package.dll.cflags) $(SQLITE_OPT) \
+ $(sqlite3-jni.c) -shared -o $@
+all: $(package.dll)
+
+.PHONY: test test-one
+Tester1.flags ?=
+Tester2.flags ?=
+test.flags.jvm = -ea -Djava.library.path=$(dir.bld.c) \
+ $(java.flags) -cp $(classpath)
+test.deps := $(CLASS_FILES) $(package.dll)
+test-one: $(test.deps)
+ $(bin.java) $(test.flags.jvm) org.sqlite.jni.capi.Tester1 $(Tester1.flags)
+ $(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) -sqllog
+test-mt: $(test.deps)
+ @echo "Testing in multi-threaded mode:";
+ $(bin.java) $(test.flags.jvm) org.sqlite.jni.capi.Tester1 \
+ -t 7 -r 50 -shuffle $(Tester1.flags)
+ $(bin.java) $(test.flags.jvm) org.sqlite.jni.wrapper1.Tester2 \
+ -t 7 -r 50 -shuffle $(Tester2.flags)
+
+test: test-one test-mt
+tests: test test-sqllog
+
+tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test))
+tester.flags ?= # --verbose
+.PHONY: tester tester-local tester-ext
+ifeq (1,$(enable.tester))
+tester-local: $(CLASS_FILES.tester) $(package.dll)
+ $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
+ $(java.flags) -cp $(classpath) \
+ org.sqlite.jni.capi.SQLTester $(tester.flags) $(tester.scripts)
+tester: tester-local
+else
+tester:
+ @echo "SQLTester support is disabled."
+endif
+
+tester.extdir.default := $(dir.tests)/ext
+tester.extdir ?= $(tester.extdir.default)
+tester.extern-scripts := $(wildcard $(tester.extdir)/*.test)
+ifneq (,$(tester.extern-scripts))
+tester-ext:
+ $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
+ $(java.flags) -cp $(classpath) \
+ org.sqlite.jni.capi.SQLTester $(tester.flags) $(tester.extern-scripts)
+else
+tester-ext:
+ @echo "******************************************************"; \
+ echo "*** Include the out-of-tree test suite in the 'tester'"; \
+ echo "*** target by either symlinking its directory to"; \
+ echo "*** $(tester.extdir.default) or passing it to make"; \
+ echo "*** as tester.extdir=/path/to/that/dir."; \
+ echo "******************************************************";
+endif
+
+tester-ext: tester-local
+tester: tester-ext
+tests: tester
+########################################################################
+# Build each SQLITE_THREADMODE variant and run all tests against them.
+multitest: clean
+define MULTIOPT
+multitest: multitest-$(1)
+multitest-$(1):
+ $$(MAKE) opt.debug=$$(opt.debug) $(patsubst %,opt.%,$(2)) \
+ tests clean enable.fts5=1
+endef
+
+$(eval $(call MULTIOPT,01,threadsafe=0 oom=1))
+$(eval $(call MULTIOPT,00,threadsafe=0 oom=0))
+$(eval $(call MULTIOPT,11,threadsafe=1 oom=1))
+$(eval $(call MULTIOPT,10,threadsafe=1 oom=0))
+$(eval $(call MULTIOPT,21,threadsafe=2 oom=1))
+$(eval $(call MULTIOPT,20,threadsafe=2 oom=0))
+
+
+########################################################################
+# jar bundle...
+package.jar.in := $(abspath $(dir.src)/jar.in)
+CLEAN_FILES += $(package.jar.in)
+JAVA_FILES.jar := $(JAVA_FILES.main) $(JAVA_FILES.unittest) $(JAVA_FILES.package.info)
+CLASS_FILES.jar := $(filter-out %/package-info.class,$(JAVA_FILES.jar:.java=.class))
+$(package.jar.in): $(package.dll) $(MAKEFILE)
+ ls -1 \
+ $(dir.src.jni)/*/*.java $(dir.src.jni)/*/*.class \
+ | sed -e 's,^$(dir.src)/,,' | sort > $@
+
+$(package.jar): $(CLASS_FILES.jar) $(MAKEFILE) $(package.jar.in)
+ @rm -f $(dir.src)/c/*~ $(dir.src.jni)/*~
+ cd $(dir.src); $(bin.jar) -cfe ../$@ org.sqlite.jni.capi.Tester1 @$(package.jar.in)
+ @ls -la $@
+ @echo "To use this jar you will need the -Djava.library.path=DIR/CONTAINING/libsqlite3-jni.so flag."
+ @echo "e.g. java -Djava.library.path=bld -jar $@"
+
+jar: $(package.jar)
+run-jar: $(package.jar) $(package.dll)
+ $(bin.java) -Djava.library.path=$(dir.bld) -jar $(package.jar) $(run-jar.flags)
+
+########################################################################
+# javadoc...
+dir.doc := $(dir.jni)/javadoc
+doc.index := $(dir.doc)/index.html
+javadoc.exclude := -exclude org.sqlite.jni.fts5
+# ^^^^ 2023-09-13: elide the fts5 parts from the public docs for
+# the time being, as it's not clear where the Java bindings for
+# those bits are going.
+# javadoc.exclude += -exclude org.sqlite.jni.capi
+# ^^^^ exclude the capi API only for certain builds (TBD)
+$(doc.index): $(JAVA_FILES.main) $(MAKEFILE)
+ @if [ -d $(dir.doc) ]; then rm -fr $(dir.doc)/*; fi
+ $(bin.javadoc) -cp $(classpath) -d $(dir.doc) -quiet \
+ -subpackages org.sqlite.jni $(javadoc.exclude)
+ @echo "javadoc output is in $@"
+
+.PHONY: doc javadoc docserve
+.FORCE: doc
+doc: $(doc.index)
+javadoc: $(doc.index)
+# Force rebild of docs
+redoc:
+ @rm -f $(doc.index)
+ @$(MAKE) doc
+docserve: $(doc.index)
+ cd $(dir.doc) && althttpd -max-age 1 -page index.html
+########################################################################
+# Clean up...
+CLEAN_FILES += $(dir.bld.c)/* \
+ $(dir.src.jni)/*.class \
+ $(dir.src.jni)/*/*.class \
+ $(package.dll) \
+ hs_err_pid*.log
+
+.PHONY: clean distclean
+clean:
+ -rm -f $(CLEAN_FILES)
+distclean: clean
+ -rm -f $(DISTCLEAN_FILES)
+ -rm -fr $(dir.bld.c) $(dir.doc)
+
+########################################################################
+# disttribution bundle rules...
+
+ifeq (,$(filter snapshot,$(MAKECMDGOALS)))
+dist-name-prefix := sqlite-jni
+else
+dist-name-prefix := sqlite-jni-snapshot-$(shell /usr/bin/date +%Y%m%d)
+endif
+dist-name := $(dist-name-prefix)-TEMP
+
+
+dist-dir.top := $(dist-name)
+dist-dir.src := $(dist-dir.top)/src
+dist.top.extras := \
+ README.md
+
+.PHONY: dist snapshot
+
+dist: \
+ $(bin.version-info) $(sqlite3.canonical.c) \
+ $(package.jar) $(MAKEFILE)
+ @echo "Making end-user deliverables..."
+ @echo "****************************************************************************"; \
+ echo "*** WARNING: be sure to build this with JDK8 (javac 1.8) for compatibility."; \
+ echo "*** reasons!"; $$($(bin.javac) -version); \
+ echo "****************************************************************************"
+ @rm -fr $(dist-dir.top)
+ @mkdir -p $(dist-dir.src)
+ @cp -p $(dist.top.extras) $(dist-dir.top)/.
+ @cp -p jar-dist.make $(dist-dir.top)/Makefile
+ @cp -p $(dir.src.c)/*.[ch] $(dist-dir.src)/.
+ @cp -p $(sqlite3.canonical.c) $(sqlite3.canonical.h) $(dist-dir.src)/.
+ @set -e; \
+ vnum=$$($(bin.version-info) --download-version); \
+ vjar=$$($(bin.version-info) --version); \
+ vdir=$(dist-name-prefix)-$$vnum; \
+ arczip=$$vdir.zip; \
+ cp -p $(package.jar) $(dist-dir.top)/sqlite3-jni-$${vjar}.jar; \
+ echo "Making $$arczip ..."; \
+ rm -fr $$arczip $$vdir; \
+ mv $(dist-dir.top) $$vdir; \
+ zip -qr $$arczip $$vdir; \
+ rm -fr $$vdir; \
+ ls -la $$arczip; \
+ set +e; \
+ unzip -lv $$arczip || echo "Missing unzip app? Not fatal."
+
+snapshot: dist
+
+.PHONY: dist-clean
+clean: dist-clean
+dist-clean:
+ rm -fr $(dist-name) $(wildcard sqlite-jni-*.zip)
diff -Nru sqlite3-3.41.0-0/ext/jni/README.md sqlite3-3.44.0-0/ext/jni/README.md
--- sqlite3-3.41.0-0/ext/jni/README.md 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/README.md 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,313 @@
+SQLite3 via JNI
+========================================================================
+
+This directory houses a Java Native Interface (JNI) binding for the
+sqlite3 API. If you are reading this from the distribution ZIP file,
+links to resources in the canonical source tree will note work. The
+canonical copy of this file can be browsed at:
+
+
+
+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" and 3.44
+ will be "final," at which point strong backward compatibility
+ guarantees will apply.
+
+Project goals/requirements:
+
+- A [1-to-1(-ish) mapping of the C API](#1to1ish) to Java via JNI,
+ insofar as cross-language semantics allow for. A closely-related
+ goal is that [the C documentation](https://sqlite.org/c3ref/intro.html)
+ should be usable as-is, insofar as possible, for the JNI binding.
+
+- Support Java as far back as version 8 (2014).
+
+- Environment-independent. Should work everywhere both Java
+ and SQLite3 do.
+
+- No 3rd-party dependencies beyond the JDK. That includes no
+ build-level dependencies for specific IDEs and toolchains. We
+ welcome the addition of build files for arbitrary environments
+ insofar as they neither interfere with each other nor become
+ a maintenance burden for the sqlite developers.
+
+Non-goals:
+
+- Creation of high-level OO wrapper APIs. Clients are free to create
+ them off of the C-style API.
+
+- 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. 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
+-----------------------------------------------------------------------
+
+```java
+import org.sqlite.jni.*;
+import static org.sqlite.jni.CApi.*;
+
+...
+
+final sqlite3 db = sqlite3_open(":memory:");
+try {
+ final int rc = sqlite3_errcode(db);
+ if( 0 != rc ){
+ if( null != db ){
+ System.out.print("Error opening db: "+sqlite3_errmsg(db));
+ }else{
+ System.out.print("Error opening db: rc="+rc);
+ }
+ ... handle error ...
+ }
+ // ... else use the db ...
+}finally{
+ // ALWAYS close databases using sqlite3_close() or sqlite3_close_v2()
+ // when done with them. All of their active statement handles must
+ // first have been passed to sqlite3_finalize().
+ sqlite3_close_v2(db);
+}
+```
+
+
+Building
+========================================================================
+
+The canonical builds assumes a Linux-like environment and requires:
+
+- GNU Make
+- A JDK supporting Java 8 or higher
+- A modern C compiler. gcc and clang should both work.
+
+Put simply:
+
+```console
+$ export JAVA_HOME=/path/to/jdk/root
+$ make
+$ make test
+$ make clean
+```
+
+The jar distribution can be created with `make jar`, but note that it
+does not contain the binary DLL file. A different DLL is needed for
+each target platform.
+
+
+
+One-to-One(-ish) Mapping to C
+========================================================================
+
+This JNI binding aims to provide as close to a 1-to-1 experience with
+the C API as cross-language semantics allow. Interface changes are
+necessarily made where cross-language semantics do not allow a 1-to-1,
+and judiciously made where a 1-to-1 mapping would be unduly cumbersome
+to use in Java. In all cases, this binding makes every effort to
+provide semantics compatible with the C API documentation even if the
+interface to those semantics is slightly different. Any cases which
+deviate from those semantics (either removing or adding semantics) are
+clearly documented.
+
+Where it makes sense to do so for usability, Java-side overloads are
+provided which accept or return data in alternative forms or provide
+sensible default argument values. In all such cases they are thin
+proxies around the corresponding C APIs and do not introduce new
+semantics.
+
+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
+caller.
+
+
+Golden Rule: Garbage Collection Cannot Free SQLite Resources
+------------------------------------------------------------------------
+
+It is important that all databases and prepared statement handles get
+cleaned up by client code. A database cannot be closed if it has open
+statement handles. `sqlite3_close()` fails if the db cannot be closed
+whereas `sqlite3_close_v2()` recognizes that case and marks the db as
+a "zombie," pending finalization when the library detects that all
+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.
+
+
+Golden Rule #2: _Never_ Throw from Callbacks (Unless...)
+------------------------------------------------------------------------
+
+All routines in this API, barring explicitly documented exceptions,
+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
+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
+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
+which cases they get translated to C-level result codes and/or
+messages. If a callback which is not permitted to throw throws, its
+exception may trigger debug output but will otherwise be suppressed.
+
+The reason some callbacks are permitted to throw and others not is
+because all such callbacks act as proxies for C function callback
+interfaces and some of those interfaces have no error-reporting
+mechanism. Those which are capable of propagating errors back through
+the library convert exceptions from callbacks into corresponding
+C-level error information. Those which cannot propagate errors
+necessarily suppress any exceptions in order to maintain the C-style
+semantics of the APIs.
+
+
+Unwieldy Constructs are Re-mapped
+------------------------------------------------------------------------
+
+Some constructs, when modelled 1-to-1 from C to Java, are unduly
+clumsy to work with in Java because they try to shoehorn C's way of
+doing certain things into Java's wildly different ways. The following
+subsections cover those, starting with a verbose explanation and
+demonstration of where such changes are "really necessary"...
+
+### Custom Collations
+
+A prime example of where interface changes for Java are necessary for
+usability is [registration of a custom
+collation](https://sqlite.org/c3ref/create_collation.html):
+
+```c
+// C:
+int sqlite3_create_collation(sqlite3 * db, const char * name, int eTextRep,
+ void *pUserData,
+ int (*xCompare)(void*,int,void const *,int,void const *));
+
+int sqlite3_create_collation_v2(sqlite3 * db, const char * name, int eTextRep,
+ void *pUserData,
+ int (*xCompare)(void*,int,void const *,int,void const *),
+ void (*xDestroy)(void*));
+```
+
+The `pUserData` object is optional client-defined state for the
+`xCompare()` and/or `xDestroy()` callback functions, both of which are
+passed that object as their first argument. That data is passed around
+"externally" in C because that's how C models the world. If we were to
+bind that part as-is to Java, the result would be awkward to use (^Yes,
+we tried this.):
+
+```java
+// Java:
+int sqlite3_create_collation(sqlite3 db, String name, int eTextRep,
+ Object pUserData, xCompareType xCompare);
+
+int sqlite3_create_collation_v2(sqlite3 db, String name, int eTextRep,
+ Object pUserData,
+ xCompareType xCompare, xDestroyType xDestroy);
+```
+
+The awkwardness comes from (A) having two distinctly different objects
+for callbacks and (B) having their internal state provided separately,
+which is ill-fitting in Java. For the sake of usability, C APIs which
+follow that pattern use a slightly different Java interface:
+
+```java
+int sqlite3_create_collation(sqlite3 db, String name, int eTextRep,
+ SomeCallbackType collation);
+```
+
+Where the `Collation` class has an abstract `call()` method and
+no-op `xDestroy()` method which can be overridden if needed, leading to
+a much more Java-esque usage:
+
+```java
+int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new SomeCallbackType(){
+
+ // Required comparison function:
+ @Override public int call(byte[] lhs, byte[] rhs){ ... }
+
+ // Optional finalizer function:
+ @Override public void xDestroy(){ ... }
+
+ // Optional local state:
+ private String localState1 =
+ "This is local state. There are many like it, but this one is mine.";
+ private MyStateType localState2 = new MyStateType();
+ ...
+});
+```
+
+Noting that:
+
+- It is possible to bind in call-scope-local state via closures, if
+ desired, as opposed to packing it into the Collation object.
+
+- No capabilities of the C API are lost or unduly obscured via the
+ above API reshaping, so power users need not make any compromises.
+
+- In the specific example above, `sqlite3_create_collation_v2()`
+ becomes superfluous because the provided interface effectively
+ provides both the v1 and v2 interfaces, the difference being that
+ overriding the `xDestroy()` method effectively gives it v2
+ semantics.
+
+
+### User-defined SQL Functions (a.k.a. UDFs)
+
+The [`sqlite3_create_function()`](https://sqlite.org/c3ref/create_function.html)
+family of APIs make heavy use of function pointers to provide
+client-defined callbacks, necessitating interface changes in the JNI
+binding. The Java API has only one core function-registration function:
+
+```java
+int sqlite3_create_function(sqlite3 db, String funcName, int nArgs,
+ int encoding, SQLFunction func);
+```
+
+> Design question: does the encoding argument serve any purpose in
+ Java? That's as-yet undetermined. If not, it will be removed.
+
+`SQLFunction` is not used directly, but is instead instantiated via
+one of its three subclasses:
+
+- `SQLFunction.Scalar` implements simple scalar functions using but a
+ single callback.
+- `SQLFunction.Aggregate` implements aggregate functions using two
+ callbacks.
+- `SQLFunction.Window` implements window functions using four
+ callbacks.
+
+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
+in-flux nature of this API.
+
+### And so on...
+
+Various APIs which accept callbacks, e.g. `sqlite3_trace_v2()` and
+`sqlite3_update_hook()`, use interfaces similar to those shown above.
+Despite the changes in signature, the JNI layer makes every effort to
+provide the same semantics as the C API documentation suggests.
diff -Nru sqlite3-3.41.0-0/ext/jni/jar-dist.make sqlite3-3.44.0-0/ext/jni/jar-dist.make
--- sqlite3-3.41.0-0/ext/jni/jar-dist.make 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/jar-dist.make 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,60 @@
+#!/this/is/make
+#^^^^ help emacs out
+#
+# This is a POSIX-make-compatible makefile for building the sqlite3
+# JNI library from "dist" zip file. It must be edited to set the
+# proper top-level JDK directory and, depending on the platform, add a
+# platform-specific -I directory. It should build as-is with any
+# 2020s-era version of gcc or clang. It requires JDK version 8 or
+# higher and that JAVA_HOME points to the top-most installation
+# directory of that JDK. On Ubuntu-style systems the JDK is typically
+# installed under /usr/lib/jvm/java-VERSION-PLATFORM.
+
+default: all
+
+JAVA_HOME = /usr/lib/jvm/java-1.8.0-openjdk-amd64
+CFLAGS = \
+ -fPIC \
+ -Isrc \
+ -I$(JAVA_HOME)/include \
+ -I$(JAVA_HOME)/include/linux \
+ -I$(JAVA_HOME)/include/apple \
+ -I$(JAVA_HOME)/include/bsd \
+ -Wall
+
+SQLITE_OPT = \
+ -DSQLITE_ENABLE_RTREE \
+ -DSQLITE_ENABLE_EXPLAIN_COMMENTS \
+ -DSQLITE_ENABLE_STMTVTAB \
+ -DSQLITE_ENABLE_DBPAGE_VTAB \
+ -DSQLITE_ENABLE_DBSTAT_VTAB \
+ -DSQLITE_ENABLE_BYTECODE_VTAB \
+ -DSQLITE_ENABLE_OFFSET_SQL_FUNC \
+ -DSQLITE_OMIT_LOAD_EXTENSION \
+ -DSQLITE_OMIT_DEPRECATED \
+ -DSQLITE_OMIT_SHARED_CACHE \
+ -DSQLITE_THREADSAFE=1 \
+ -DSQLITE_TEMP_STORE=2 \
+ -DSQLITE_USE_URI=1 \
+ -DSQLITE_ENABLE_FTS5 \
+ -DSQLITE_DEBUG
+
+sqlite3-jni.dll = libsqlite3-jni.so
+$(sqlite3-jni.dll):
+ @echo "************************************************************************"; \
+ echo "*** If this fails to build, be sure to edit this makefile ***"; \
+ echo "*** to configure it for your system. ***"; \
+ echo "************************************************************************"
+ $(CC) $(CFLAGS) $(SQLITE_OPT) \
+ src/sqlite3-jni.c -shared -o $@
+ @echo "Now try running it with: make test"
+
+test.flags = -Djava.library.path=. sqlite3-jni-*.jar
+test: $(sqlite3-jni.dll)
+ java -jar $(test.flags)
+ java -jar $(test.flags) -t 7 -r 10 -shuffle
+
+clean:
+ -rm -f $(sqlite3-jni.dll)
+
+all: $(sqlite3-jni.dll)
diff -Nru sqlite3-3.41.0-0/ext/jni/src/c/sqlite3-jni.c sqlite3-3.44.0-0/ext/jni/src/c/sqlite3-jni.c
--- sqlite3-3.41.0-0/ext/jni/src/c/sqlite3-jni.c 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/c/sqlite3-jni.c 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,5899 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file implements the JNI bindings declared in
+** org.sqlite.jni.capi.CApi (from which sqlite3-jni.h is generated).
+*/
+
+/*
+** If you found this comment by searching the code for
+** CallStaticObjectMethod then you're the victim of an OpenJDK bug:
+**
+** https://bugs.openjdk.org/browse/JDK-8130659
+**
+** It's known to happen with OpenJDK v8 but not with v19.
+**
+** This code does not use JNI's CallStaticObjectMethod().
+*/
+
+/*
+** Define any SQLITE_... config defaults we want if they aren't
+** overridden by the builder. Please keep these alphabetized.
+*/
+
+/**********************************************************************/
+/* SQLITE_D... */
+#ifndef SQLITE_DEFAULT_CACHE_SIZE
+# define SQLITE_DEFAULT_CACHE_SIZE -16384
+#endif
+#if !defined(SQLITE_DEFAULT_PAGE_SIZE)
+# define SQLITE_DEFAULT_PAGE_SIZE 8192
+#endif
+#ifndef SQLITE_DQS
+# define SQLITE_DQS 0
+#endif
+
+/**********************************************************************/
+/* SQLITE_ENABLE_... */
+/*
+** Unconditionally enable API_ARMOR in the JNI build. It ensures that
+** public APIs behave predictable in the face of passing illegal NULLs
+** or ranges which might otherwise invoke undefined behavior.
+*/
+#undef SQLITE_ENABLE_API_ARMOR
+#define SQLITE_ENABLE_API_ARMOR 1
+
+#ifndef SQLITE_ENABLE_BYTECODE_VTAB
+# define SQLITE_ENABLE_BYTECODE_VTAB 1
+#endif
+#ifndef SQLITE_ENABLE_DBPAGE_VTAB
+# define SQLITE_ENABLE_DBPAGE_VTAB 1
+#endif
+#ifndef SQLITE_ENABLE_DBSTAT_VTAB
+# define SQLITE_ENABLE_DBSTAT_VTAB 1
+#endif
+#ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS
+# define SQLITE_ENABLE_EXPLAIN_COMMENTS 1
+#endif
+#ifndef SQLITE_ENABLE_MATH_FUNCTIONS
+# define SQLITE_ENABLE_MATH_FUNCTIONS 1
+#endif
+#ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC
+# define SQLITE_ENABLE_OFFSET_SQL_FUNC 1
+#endif
+#ifndef SQLITE_ENABLE_RTREE
+# define SQLITE_ENABLE_RTREE 1
+#endif
+//#ifndef SQLITE_ENABLE_SESSION
+//# define SQLITE_ENABLE_SESSION 1
+//#endif
+#ifndef SQLITE_ENABLE_STMTVTAB
+# define SQLITE_ENABLE_STMTVTAB 1
+#endif
+//#ifndef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
+//# define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
+//#endif
+
+/**********************************************************************/
+/* SQLITE_J... */
+#ifdef SQLITE_JNI_FATAL_OOM
+#if !SQLITE_JNI_FATAL_OOM
+#undef SQLITE_JNI_FATAL_OOM
+#endif
+#endif
+
+/**********************************************************************/
+/* SQLITE_M... */
+#ifndef SQLITE_MAX_ALLOCATION_SIZE
+# define SQLITE_MAX_ALLOCATION_SIZE 0x1fffffff
+#endif
+
+/**********************************************************************/
+/* SQLITE_O... */
+#ifndef SQLITE_OMIT_DEPRECATED
+# define SQLITE_OMIT_DEPRECATED 1
+#endif
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+# define SQLITE_OMIT_LOAD_EXTENSION 1
+#endif
+#ifndef SQLITE_OMIT_SHARED_CACHE
+# define SQLITE_OMIT_SHARED_CACHE 1
+#endif
+#ifdef SQLITE_OMIT_UTF16
+/* UTF16 is required for java */
+# undef SQLITE_OMIT_UTF16 1
+#endif
+
+/**********************************************************************/
+/* SQLITE_T... */
+#ifndef SQLITE_TEMP_STORE
+# define SQLITE_TEMP_STORE 2
+#endif
+#ifndef SQLITE_THREADSAFE
+# define SQLITE_THREADSAFE 1
+#endif
+
+/**********************************************************************/
+/* SQLITE_USE_... */
+#ifndef SQLITE_USE_URI
+# define SQLITE_USE_URI 1
+#endif
+
+
+/*
+** Which sqlite3.c we're using needs to be configurable to enable
+** building against a custom copy, e.g. the SEE variant. We have to
+** include sqlite3.c, as opposed to sqlite3.h, in order to get access
+** to some interal details like SQLITE_MAX_... and friends. This
+** increases the rebuild time considerably but we need this in order
+** to access some internal functionality and keep the to-Java-exported
+** values of SQLITE_MAX_... and SQLITE_LIMIT_... in sync with the C
+** build.
+*/
+#ifndef SQLITE_C
+# define SQLITE_C sqlite3.c
+#endif
+#define INC__STRINGIFY_(f) #f
+#define INC__STRINGIFY(f) INC__STRINGIFY_(f)
+#include INC__STRINGIFY(SQLITE_C)
+#undef INC__STRINGIFY_
+#undef INC__STRINGIFY
+#undef SQLITE_C
+
+/*
+** End of the sqlite3 lib setup. What follows is JNI-specific.
+*/
+
+#include "sqlite3-jni.h"
+#include
+#include /* only for testing/debugging */
+#include /* intptr_t for 32-bit builds */
+
+/* Only for debugging */
+#define MARKER(pfexp) \
+ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \
+ printf pfexp; \
+ } while(0)
+
+/*
+** Creates a verbose JNI function name. Suffix must be
+** the JNI-mangled form of the function's name, minus the
+** prefix seen in this macro.
+*/
+#define JniFuncName(Suffix) \
+ Java_org_sqlite_jni_capi_CApi_sqlite3_ ## Suffix
+
+/* Prologue for JNI function declarations and definitions. */
+#define JniDecl(ReturnType,Suffix) \
+ JNIEXPORT ReturnType JNICALL JniFuncName(Suffix)
+
+/*
+** S3JniApi's intent is that CFunc be the C API func(s) the
+** being-declared JNI function is wrapping, making it easier to find
+** that function's JNI-side entry point. The other args are for JniDecl.
+** See the many examples in this file.
+*/
+#define S3JniApi(CFunc,ReturnType,Suffix) JniDecl(ReturnType,Suffix)
+
+/*
+** S3JniCast_L2P and P2L cast jlong (64-bit) to/from pointers. This is
+** required for casting warning-free on 32-bit builds, where we
+** otherwise get complaints that we're casting between different-sized
+** int types.
+**
+** This use of intptr_t is the _only_ reason we require
+** which, in turn, requires building with -std=c99 (or later).
+*/
+#define S3JniCast_L2P(JLongAsPtr) (void*)((intptr_t)(JLongAsPtr))
+#define S3JniCast_P2L(PTR) (jlong)((intptr_t)(PTR))
+
+/*
+** Shortcuts for the first 2 parameters to all JNI bindings.
+**
+** The type of the jSelf arg differs, but no docs seem to mention
+** this: for static methods it's of type jclass and for non-static
+** it's jobject. jobject actually works for all funcs, in the sense
+** that it compiles and runs so long as we don't use jSelf (which is
+** only rarely needed in this code), but to be pedantically correct we
+** need the proper type in the signature.
+**
+** https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers
+*/
+#define JniArgsEnvObj JNIEnv * const env, jobject jSelf
+#define JniArgsEnvClass JNIEnv * const env, jclass jKlazz
+/*
+** Helpers to account for -Xcheck:jni warnings about not having
+** checked for exceptions.
+*/
+#define S3JniIfThrew if( (*env)->ExceptionCheck(env) )
+#define S3JniExceptionClear (*env)->ExceptionClear(env)
+#define S3JniExceptionReport (*env)->ExceptionDescribe(env)
+#define S3JniExceptionIgnore S3JniIfThrew S3JniExceptionClear
+#define S3JniExceptionWarnIgnore \
+ S3JniIfThrew {S3JniExceptionReport; S3JniExceptionClear;}(void)0
+#define S3JniExceptionWarnCallbackThrew(STR) \
+ MARKER(("WARNING: " STR " MUST NOT THROW.\n")); \
+ (*env)->ExceptionDescribe(env)
+
+/** To be used for cases where we're _really_ not expecting an
+ exception, e.g. looking up well-defined Java class members. */
+#define S3JniExceptionIsFatal(MSG) S3JniIfThrew {\
+ S3JniExceptionReport; S3JniExceptionClear; \
+ (*env)->FatalError(env, MSG); \
+ }
+
+/*
+** Declares local var env = s3jni_env(). All JNI calls involve a
+** JNIEnv somewhere, always named env, and many of our macros assume
+** env is in scope. Where it's not, but should be, use this to make it
+** so.
+*/
+#define S3JniDeclLocal_env JNIEnv * const env = s3jni_env()
+
+/* Fail fatally with an OOM message. */
+static inline void s3jni_oom(JNIEnv * const env){
+ (*env)->FatalError(env, "SQLite3 JNI is out of memory.") /* does not return */;
+}
+
+/*
+** sqlite3_malloc() proxy which fails fatally on OOM. This should
+** only be used for routines which manage global state and have no
+** recovery strategy for OOM. For sqlite3 API which can reasonably
+** return SQLITE_NOMEM, s3jni_malloc() should be used instead.
+*/
+static void * s3jni_malloc_or_die(JNIEnv * const env, size_t n){
+ void * const rv = sqlite3_malloc(n);
+ if( n && !rv ) s3jni_oom(env);
+ return rv;
+}
+
+/*
+** Works like sqlite3_malloc() unless built with SQLITE_JNI_FATAL_OOM,
+** in which case it calls s3jni_oom() on OOM.
+*/
+#ifdef SQLITE_JNI_FATAL_OOM
+#define s3jni_malloc(SIZE) s3jni_malloc_or_die(env, SIZE)
+#else
+#define s3jni_malloc(SIZE) sqlite3_malloc(((void)env,(SIZE)))
+/* the ((void)env) trickery here is to avoid ^^^^^^ an otherwise
+ unused arg in at least one place. */
+#endif
+
+/*
+** Works like sqlite3_realloc() unless built with SQLITE_JNI_FATAL_OOM,
+** in which case it calls s3jni_oom() on OOM.
+*/
+#ifdef SQLITE_JNI_FATAL_OOM
+static void * s3jni_realloc_or_die(JNIEnv * const env, void * p, size_t n){
+ void * const rv = sqlite3_realloc(p, (int)n);
+ if( n && !rv ) s3jni_oom(env);
+ return rv;
+}
+#define s3jni_realloc(MEM,SIZE) s3jni_realloc_or_die(env, (MEM), (SIZE))
+#else
+#define s3jni_realloc(MEM,SIZE) sqlite3_realloc((MEM), ((void)env, (SIZE)))
+#endif
+
+/* Fail fatally if !EXPR. */
+#define s3jni_oom_fatal(EXPR) if( !(EXPR) ) s3jni_oom(env)
+/* Maybe fail fatally if !EXPR. */
+#ifdef SQLITE_JNI_FATAL_OOM
+#define s3jni_oom_check s3jni_oom_fatal
+#else
+#define s3jni_oom_check(EXPR)
+#endif
+//#define S3JniDb_oom(pDb,EXPR) ((EXPR) ? sqlite3OomFault(pDb) : 0)
+
+#define s3jni_db_oom(pDb) (void)((pDb) ? ((pDb)->mallocFailed=1) : 0)
+
+/* Helpers for Java value reference management. */
+static jobject s3jni_ref_global(JNIEnv * const env, jobject const v){
+ jobject const rv = v ? (*env)->NewGlobalRef(env, v) : NULL;
+ s3jni_oom_fatal( v ? !!rv : 1 );
+ return rv;
+}
+static jobject s3jni_ref_local(JNIEnv * const env, jobject const v){
+ jobject const rv = v ? (*env)->NewLocalRef(env, v) : NULL;
+ s3jni_oom_fatal( v ? !!rv : 1 );
+ return rv;
+}
+static inline void s3jni_unref_global(JNIEnv * const env, jobject const v){
+ if( v ) (*env)->DeleteGlobalRef(env, v);
+}
+static inline void s3jni_unref_local(JNIEnv * const env, jobject const v){
+ if( v ) (*env)->DeleteLocalRef(env, v);
+}
+#define S3JniRefGlobal(VAR) s3jni_ref_global(env, (VAR))
+#define S3JniRefLocal(VAR) s3jni_ref_local(env, (VAR))
+#define S3JniUnrefGlobal(VAR) s3jni_unref_global(env, (VAR))
+#define S3JniUnrefLocal(VAR) s3jni_unref_local(env, (VAR))
+
+/*
+** Lookup key type for use with s3jni_nphop() and a cache of a
+** frequently-needed Java-side class reference and one or two Java
+** class member IDs.
+*/
+typedef struct S3JniNphOp S3JniNphOp;
+struct S3JniNphOp {
+ const int index /* index into S3JniGlobal.nph[] */;
+ const char * const zName /* Full Java name of the class */;
+ const char * const zMember /* Name of member property */;
+ const char * const zTypeSig /* JNI type signature of zMember */;
+ /*
+ ** klazz is a global ref to the class represented by pRef.
+ **
+ ** According to:
+ **
+ ** https://developer.ibm.com/articles/j-jni/
+ **
+ ** > ... the IDs returned for a given class don't change for the
+ ** lifetime of the JVM process. But the call to get the field or
+ ** method can require significant work in the JVM, because fields
+ ** and methods might have been inherited from superclasses, making
+ ** the JVM walk up the class hierarchy to find them. Because the
+ ** IDs are the same for a given class, you should look them up
+ ** once and then reuse them. Similarly, looking up class objects
+ ** can be expensive, so they should be cached as well.
+ */
+ jclass klazz;
+ volatile jfieldID fidValue /* NativePointerHolder.nativePointer or
+ ** OutputPointer.T.value */;
+ volatile jmethodID midCtor /* klazz's no-arg constructor. Used by
+ ** NativePointerHolder_new(). */;
+};
+
+/*
+** Cache keys for each concrete NativePointerHolder subclasses and
+** OutputPointer.T types. The members are to be used with s3jni_nphop()
+** and friends, and each one's member->index corresponds to its index
+** in the S3JniGlobal.nph[] array.
+*/
+static const struct {
+ const S3JniNphOp sqlite3;
+ const S3JniNphOp sqlite3_backup;
+ const S3JniNphOp sqlite3_blob;
+ const S3JniNphOp sqlite3_context;
+ const S3JniNphOp sqlite3_stmt;
+ const S3JniNphOp sqlite3_value;
+ const S3JniNphOp OutputPointer_Bool;
+ const S3JniNphOp OutputPointer_Int32;
+ const S3JniNphOp OutputPointer_Int64;
+ const S3JniNphOp OutputPointer_sqlite3;
+ const S3JniNphOp OutputPointer_sqlite3_blob;
+ const S3JniNphOp OutputPointer_sqlite3_stmt;
+ const S3JniNphOp OutputPointer_sqlite3_value;
+ const S3JniNphOp OutputPointer_String;
+#ifdef SQLITE_ENABLE_FTS5
+ const S3JniNphOp OutputPointer_ByteArray;
+ const S3JniNphOp Fts5Context;
+ const S3JniNphOp Fts5ExtensionApi;
+ const S3JniNphOp fts5_api;
+ const S3JniNphOp fts5_tokenizer;
+ const S3JniNphOp Fts5Tokenizer;
+#endif
+} S3JniNphOps = {
+#define MkRef(INDEX, KLAZZ, MEMBER, SIG) \
+ { INDEX, "org/sqlite/jni/" KLAZZ, MEMBER, SIG }
+/* NativePointerHolder ref */
+#define RefN(INDEX, KLAZZ) MkRef(INDEX, KLAZZ, "nativePointer", "J")
+/* OutputPointer.T ref */
+#define RefO(INDEX, KLAZZ, SIG) MkRef(INDEX, KLAZZ, "value", SIG)
+ RefN(0, "capi/sqlite3"),
+ RefN(1, "capi/sqlite3_backup"),
+ RefN(2, "capi/sqlite3_blob"),
+ RefN(3, "capi/sqlite3_context"),
+ RefN(4, "capi/sqlite3_stmt"),
+ RefN(5, "capi/sqlite3_value"),
+ RefO(6, "capi/OutputPointer$Bool", "Z"),
+ RefO(7, "capi/OutputPointer$Int32", "I"),
+ RefO(8, "capi/OutputPointer$Int64", "J"),
+ RefO(9, "capi/OutputPointer$sqlite3",
+ "Lorg/sqlite/jni/capi/sqlite3;"),
+ RefO(10, "capi/OutputPointer$sqlite3_blob",
+ "Lorg/sqlite/jni/capi/sqlite3_blob;"),
+ RefO(11, "capi/OutputPointer$sqlite3_stmt",
+ "Lorg/sqlite/jni/capi/sqlite3_stmt;"),
+ RefO(12, "capi/OutputPointer$sqlite3_value",
+ "Lorg/sqlite/jni/capi/sqlite3_value;"),
+ RefO(13, "capi/OutputPointer$String", "Ljava/lang/String;"),
+#ifdef SQLITE_ENABLE_FTS5
+ RefO(14, "capi/OutputPointer$ByteArray", "[B"),
+ RefN(15, "fts5/Fts5Context"),
+ RefN(16, "fts5/Fts5ExtensionApi"),
+ RefN(17, "fts5/fts5_api"),
+ RefN(18, "fts5/fts5_tokenizer"),
+ RefN(19, "fts5/Fts5Tokenizer")
+#endif
+#undef MkRef
+#undef RefN
+#undef RefO
+};
+
+#define S3JniNph(T) &S3JniNphOps.T
+
+enum {
+ /*
+ ** Size of the NativePointerHolder cache. Need enough space for
+ ** (only) the library's NativePointerHolder and OutputPointer types,
+ ** a fixed count known at build-time. This value needs to be
+ ** exactly the number of S3JniNphOp entries in the S3JniNphOps
+ ** object.
+ */
+ S3Jni_NphCache_size = sizeof(S3JniNphOps) / sizeof(S3JniNphOp)
+};
+
+/*
+** State for binding C callbacks to Java methods.
+*/
+typedef struct S3JniHook S3JniHook;
+struct S3JniHook{
+ jobject jObj /* global ref to Java instance */;
+ jmethodID midCallback /* callback method. Signature depends on
+ ** jObj's type */;
+ /* We lookup the jObj.xDestroy() method as-needed for contexts which
+ ** support custom finalizers. Fundamentally we can support them for
+ ** any Java type, but we only want to expose support for them where
+ ** the C API does. */
+ jobject jExtra /* Global ref to a per-hook-type value */;
+ int doXDestroy /* If true then S3JniHook_unref() will call
+ jObj->xDestroy() if it's available. */;
+ S3JniHook * pNext /* Next entry in S3Global.hooks.aFree */;
+};
+/* For clean bitwise-copy init of local instances. */
+static const S3JniHook S3JniHook_empty = {0,0,0,0,0};
+
+/*
+** Per-(sqlite3*) state for various JNI bindings. This state is
+** allocated as needed, cleaned up in sqlite3_close(_v2)(), and
+** recycled when possible.
+**
+** Trivia: vars and parameters of this type are often named "ps"
+** because this class used to have a name for which that abbreviation
+** made sense.
+*/
+typedef struct S3JniDb S3JniDb;
+struct S3JniDb {
+ sqlite3 *pDb /* The associated db handle */;
+ jobject jDb /* A global ref of the output object which gets
+ returned from sqlite3_open(_v2)(). We need this in
+ order to have an object to pass to routines like
+ sqlite3_collation_needed()'s callback, or else we
+ have to dynamically create one for that purpose,
+ which would be fine except that it would be a
+ different instance (and maybe even a different
+ class) than the one the user may expect to
+ receive. */;
+ char * zMainDbName /* Holds the string allocated on behalf of
+ SQLITE_DBCONFIG_MAINDBNAME. */;
+ struct {
+ S3JniHook busyHandler;
+ S3JniHook collationNeeded;
+ S3JniHook commit;
+ S3JniHook progress;
+ S3JniHook rollback;
+ S3JniHook trace;
+ S3JniHook update;
+ S3JniHook auth;
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ S3JniHook preUpdate;
+#endif
+ } hooks;
+#ifdef SQLITE_ENABLE_FTS5
+ /* FTS5-specific state */
+ struct {
+ jobject jApi /* global ref to s3jni_fts5_api_from_db() */;
+ } fts;
+#endif
+ S3JniDb * pNext /* Next entry in SJG.perDb.aFree */;
+};
+
+static const char * const S3JniDb_clientdata_key = "S3JniDb";
+#define S3JniDb_from_clientdata(pDb) \
+ (pDb ? sqlite3_get_clientdata(pDb, S3JniDb_clientdata_key) : 0)
+
+/*
+** Cache for per-JNIEnv (i.e. per-thread) data.
+**
+** Trivia: vars and parameters of this type are often named "jc"
+** because this class used to have a name for which that abbreviation
+** made sense.
+*/
+typedef struct S3JniEnv S3JniEnv;
+struct S3JniEnv {
+ JNIEnv *env /* JNIEnv in which this cache entry was created */;
+ /*
+ ** pdbOpening is used to coordinate the Java/DB connection of a
+ ** being-open()'d db in the face of auto-extensions.
+ ** Auto-extensions run before we can bind the C db to its Java
+ ** representation, but auto-extensions require that binding to pass
+ ** on to their Java-side callbacks. We handle this as follows:
+ **
+ ** - In the JNI side of sqlite3_open(), allocate the Java side of
+ ** that connection and set pdbOpening to point to that
+ ** object.
+ **
+ ** - Call sqlite3_open(), which triggers the auto-extension
+ ** handler. That handler uses pdbOpening to connect the native
+ ** db handle which it receives with pdbOpening.
+ **
+ ** - When sqlite3_open() returns, check whether pdbOpening->pDb is
+ ** NULL. If it isn't, auto-extension handling set it up. If it
+ ** is, complete the Java/C binding unless sqlite3_open() returns
+ ** a NULL db, in which case free pdbOpening.
+ */
+ S3JniDb * pdbOpening;
+ S3JniEnv * pNext /* Next entry in SJG.envCache.aHead or
+ SJG.envCache.aFree */;
+};
+
+/*
+** State for proxying sqlite3_auto_extension() in Java. This was
+** initially a separate class from S3JniHook and now the older name is
+** retained for readability in the APIs which use this, as well as for
+** its better code-searchability.
+*/
+typedef S3JniHook S3JniAutoExtension;
+
+/*
+** Type IDs for SQL function categories.
+*/
+enum UDFType {
+ UDF_UNKNOWN_TYPE = 0/*for error propagation*/,
+ UDF_SCALAR,
+ UDF_AGGREGATE,
+ UDF_WINDOW
+};
+
+/*
+** State for binding Java-side UDFs.
+*/
+typedef struct S3JniUdf S3JniUdf;
+struct S3JniUdf {
+ jobject jObj /* SQLFunction instance */;
+ char * zFuncName /* Only for error reporting and debug logging */;
+ enum UDFType type /* UDF type */;
+ /** Method IDs for the various UDF methods. */
+ jmethodID jmidxFunc /* xFunc method (scalar) */;
+ jmethodID jmidxStep /* xStep method (aggregate/window) */;
+ jmethodID jmidxFinal /* xFinal method (aggregate/window) */;
+ jmethodID jmidxValue /* xValue method (window) */;
+ jmethodID jmidxInverse /* xInverse method (window) */;
+ S3JniUdf * pNext /* Next entry in SJG.udf.aFree. */;
+};
+
+#if defined(SQLITE_JNI_ENABLE_METRICS) && 0==SQLITE_JNI_ENABLE_METRICS
+# undef SQLITE_JNI_ENABLE_METRICS
+#endif
+
+/*
+** If true, modifying S3JniGlobal.metrics is protected by a mutex,
+** else it isn't.
+*/
+#ifdef SQLITE_DEBUG
+# define S3JNI_METRICS_MUTEX SQLITE_THREADSAFE
+#else
+# define S3JNI_METRICS_MUTEX 0
+#endif
+#ifndef SQLITE_JNI_ENABLE_METRICS
+# undef S3JNI_METRICS_MUTEX
+# define S3JNI_METRICS_MUTEX 0
+#endif
+
+/*
+** Global state, e.g. caches and metrics.
+*/
+typedef struct S3JniGlobalType S3JniGlobalType;
+struct S3JniGlobalType {
+ /*
+ ** According to: https://developer.ibm.com/articles/j-jni/
+ **
+ ** > A thread can get a JNIEnv by calling GetEnv() using the JNI
+ ** invocation interface through a JavaVM object. The JavaVM object
+ ** itself can be obtained by calling the JNI GetJavaVM() method
+ ** using a JNIEnv object and can be cached and shared across
+ ** threads. Caching a copy of the JavaVM object enables any thread
+ ** with access to the cached object to get access to its own
+ ** JNIEnv when necessary.
+ */
+ JavaVM * jvm;
+ /*
+ ** Global mutex. It must not be used for anything which might call
+ ** back into the JNI layer.
+ */
+ sqlite3_mutex * mutex;
+ /*
+ ** Cache of references to Java classes and method IDs for
+ ** NativePointerHolder subclasses and OutputPointer.T types.
+ */
+ struct {
+ S3JniNphOp list[S3Jni_NphCache_size];
+ sqlite3_mutex * mutex; /* mutex for this->list */
+ volatile void const * locker; /* sanity-checking-only context object
+ for this->mutex */
+ } nph;
+ /*
+ ** Cache of per-thread state.
+ */
+ struct {
+ S3JniEnv * aHead /* Linked list of in-use instances */;
+ S3JniEnv * aFree /* Linked list of free instances */;
+ sqlite3_mutex * mutex /* mutex for aHead and aFree. */;
+ volatile void const * locker /* env mutex is held on this
+ object's behalf. Used only for
+ sanity checking. */;
+ } envCache;
+ /*
+ ** Per-db state. This can move into the core library once we can tie
+ ** client-defined state to db handles there.
+ */
+ struct {
+ S3JniDb * aFree /* Linked list of free instances */;
+ sqlite3_mutex * mutex /* mutex for aHead and aFree */;
+ volatile void const * locker
+ /* perDb mutex is held on this object's behalf. Used only for
+ sanity checking. Note that the mutex is at the class level, not
+ instance level. */;
+ } perDb;
+ struct {
+ S3JniUdf * aFree /* Head of the free-item list. Guarded by global
+ mutex. */;
+ } udf;
+ /*
+ ** Refs to global classes and methods. Obtained during static init
+ ** and never released.
+ */
+ struct {
+ jclass cLong /* global ref to java.lang.Long */;
+ jclass cString /* global ref to java.lang.String */;
+ jobject oCharsetUtf8 /* global ref to StandardCharset.UTF_8 */;
+ jmethodID ctorLong1 /* the Long(long) constructor */;
+ jmethodID ctorStringBA /* the String(byte[],Charset) constructor */;
+ jmethodID stringGetBytes /* the String.getBytes(Charset) method */;
+ } g;
+ /*
+ ** The list of Java-side auto-extensions
+ ** (org.sqlite.jni.capi.AutoExtensionCallback objects).
+ */
+ struct {
+ S3JniAutoExtension *aExt /* The auto-extension list. It is
+ maintained such that all active
+ entries are in the first contiguous
+ nExt array elements. */;
+ int nAlloc /* number of entries allocated for aExt,
+ as distinct from the number of active
+ entries. */;
+ int nExt /* number of active entries in aExt, all in the
+ first nExt'th array elements. */;
+ sqlite3_mutex * mutex /* mutex for manipulation/traversal of aExt */;
+ volatile const void * locker /* object on whose behalf the mutex
+ is held. Only for sanity checking
+ in debug builds. */;
+ } autoExt;
+#ifdef SQLITE_ENABLE_FTS5
+ struct {
+ volatile jobject jExt /* Global ref to Java singleton for the
+ Fts5ExtensionApi instance. */;
+ struct {
+ jfieldID fidA /* Fts5Phrase::a member */;
+ jfieldID fidB /* Fts5Phrase::b member */;
+ } jPhraseIter;
+ } fts5;
+#endif
+ struct {
+#ifdef SQLITE_ENABLE_SQLLOG
+ S3JniHook sqllog /* sqlite3_config(SQLITE_CONFIG_SQLLOG) callback */;
+#endif
+ S3JniHook configlog /* sqlite3_config(SQLITE_CONFIG_LOG) callback */;
+ S3JniHook * aFree /* free-item list, for recycling. */;
+ sqlite3_mutex * mutex /* mutex for aFree */;
+ volatile const void * locker /* object on whose behalf the mutex
+ is held. Only for sanity checking
+ in debug builds. */;
+ } hook;
+#ifdef SQLITE_JNI_ENABLE_METRICS
+ /* Internal metrics. */
+ struct {
+ volatile unsigned nEnvHit;
+ volatile unsigned nEnvMiss;
+ volatile unsigned nEnvAlloc;
+ volatile unsigned nMutexEnv /* number of times envCache.mutex was entered for
+ a S3JniEnv operation. */;
+ volatile unsigned nMutexNph /* number of times SJG.mutex was entered */;
+ volatile unsigned nMutexHook /* number of times SJG.mutex hooks.was entered */;
+ volatile unsigned nMutexPerDb /* number of times perDb.mutex was entered */;
+ volatile unsigned nMutexAutoExt /* number of times autoExt.mutex was entered */;
+ volatile unsigned nMutexGlobal /* number of times global mutex was entered. */;
+ volatile unsigned nMutexUdf /* number of times global mutex was entered
+ for UDFs. */;
+ volatile unsigned nDestroy /* xDestroy() calls across all types */;
+ volatile unsigned nPdbAlloc /* Number of S3JniDb alloced. */;
+ volatile unsigned nPdbRecycled /* Number of S3JniDb reused. */;
+ volatile unsigned nUdfAlloc /* Number of S3JniUdf alloced. */;
+ volatile unsigned nUdfRecycled /* Number of S3JniUdf reused. */;
+ volatile unsigned nHookAlloc /* Number of S3JniHook alloced. */;
+ volatile unsigned nHookRecycled /* Number of S3JniHook reused. */;
+ struct {
+ /* Number of calls for each type of UDF callback. */
+ volatile unsigned nFunc;
+ volatile unsigned nStep;
+ volatile unsigned nFinal;
+ volatile unsigned nValue;
+ volatile unsigned nInverse;
+ } udf;
+ unsigned nMetrics /* Total number of mutex-locked
+ metrics increments. */;
+#if S3JNI_METRICS_MUTEX
+ sqlite3_mutex * mutex;
+#endif
+ } metrics;
+#endif /* SQLITE_JNI_ENABLE_METRICS */
+};
+static S3JniGlobalType S3JniGlobal = {};
+#define SJG S3JniGlobal
+
+/* Increments *p, possibly protected by a mutex. */
+#ifndef SQLITE_JNI_ENABLE_METRICS
+#define s3jni_incr(PTR)
+#elif S3JNI_METRICS_MUTEX
+static void s3jni_incr( volatile unsigned int * const p ){
+ sqlite3_mutex_enter(SJG.metrics.mutex);
+ ++SJG.metrics.nMetrics;
+ ++(*p);
+ sqlite3_mutex_leave(SJG.metrics.mutex);
+}
+#else
+#define s3jni_incr(PTR) ++(*(PTR))
+#endif
+
+/* Helpers for working with specific mutexes. */
+#if SQLITE_THREADSAFE
+#define s3jni_mutex_enter2(M, Metric) \
+ sqlite3_mutex_enter( M ); \
+ s3jni_incr( &SJG.metrics.Metric )
+#define s3jni_mutex_leave2(M) \
+ sqlite3_mutex_leave( M )
+
+#define s3jni_mutex_enter(M, L, Metric) \
+ assert( (void*)env != (void*)L && "Invalid use of " #L); \
+ s3jni_mutex_enter2( M, Metric ); \
+ L = env
+#define s3jni_mutex_leave(M, L) \
+ assert( (void*)env == (void*)L && "Invalid use of " #L); \
+ L = 0; \
+ s3jni_mutex_leave2( M )
+
+#define S3JniEnv_mutex_assertLocked \
+ assert( 0 != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
+#define S3JniEnv_mutex_assertLocker \
+ assert( (env) == SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
+#define S3JniEnv_mutex_assertNotLocker \
+ assert( (env) != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
+
+#define S3JniEnv_mutex_enter \
+ s3jni_mutex_enter( SJG.envCache.mutex, SJG.envCache.locker, nMutexEnv )
+#define S3JniEnv_mutex_leave \
+ s3jni_mutex_leave( SJG.envCache.mutex, SJG.envCache.locker )
+
+#define S3JniAutoExt_mutex_enter \
+ s3jni_mutex_enter( SJG.autoExt.mutex, SJG.autoExt.locker, nMutexAutoExt )
+#define S3JniAutoExt_mutex_leave \
+ s3jni_mutex_leave( SJG.autoExt.mutex, SJG.autoExt.locker )
+#define S3JniAutoExt_mutex_assertLocker \
+ assert( env == SJG.autoExt.locker && "Misuse of S3JniGlobal.autoExt.mutex" )
+
+#define S3JniGlobal_mutex_enter \
+ s3jni_mutex_enter2( SJG.mutex, nMutexGlobal )
+#define S3JniGlobal_mutex_leave \
+ s3jni_mutex_leave2( SJG.mutex )
+
+#define S3JniHook_mutex_enter \
+ s3jni_mutex_enter( SJG.hook.mutex, SJG.hook.locker, nMutexHook )
+#define S3JniHook_mutex_leave \
+ s3jni_mutex_leave( SJG.hook.mutex, SJG.hook.locker )
+
+#define S3JniNph_mutex_enter \
+ s3jni_mutex_enter( SJG.nph.mutex, SJG.nph.locker, nMutexNph )
+#define S3JniNph_mutex_leave \
+ s3jni_mutex_leave( SJG.nph.mutex, SJG.nph.locker )
+
+#define S3JniDb_mutex_assertLocker \
+ assert( (env) == SJG.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" )
+#define S3JniDb_mutex_enter \
+ s3jni_mutex_enter( SJG.perDb.mutex, SJG.perDb.locker, nMutexPerDb )
+#define S3JniDb_mutex_leave \
+ s3jni_mutex_leave( SJG.perDb.mutex, SJG.perDb.locker )
+
+#else /* SQLITE_THREADSAFE==0 */
+#define S3JniAutoExt_mutex_assertLocker
+#define S3JniAutoExt_mutex_enter
+#define S3JniAutoExt_mutex_leave
+#define S3JniDb_mutex_assertLocker
+#define S3JniDb_mutex_enter
+#define S3JniDb_mutex_leave
+#define S3JniEnv_mutex_assertLocked
+#define S3JniEnv_mutex_assertLocker
+#define S3JniEnv_mutex_assertNotLocker
+#define S3JniEnv_mutex_enter
+#define S3JniEnv_mutex_leave
+#define S3JniGlobal_mutex_enter
+#define S3JniGlobal_mutex_leave
+#define S3JniHook_mutex_enter
+#define S3JniHook_mutex_leave
+#define S3JniNph_mutex_enter
+#define S3JniNph_mutex_leave
+#endif
+
+/* Helpers for jstring and jbyteArray. */
+static const char * s3jni__jstring_to_mutf8(JNIEnv * const env, jstring v ){
+ const char *z = v ? (*env)->GetStringUTFChars(env, v, NULL) : 0;
+ s3jni_oom_check( v ? !!z : !z );
+ return z;
+}
+
+#define s3jni_jstring_to_mutf8(ARG) s3jni__jstring_to_mutf8(env, (ARG))
+#define s3jni_mutf8_release(ARG,VAR) if( VAR ) (*env)->ReleaseStringUTFChars(env, ARG, VAR)
+
+/*
+** If jBA is not NULL then its GetByteArrayElements() value is
+** returned. If jBA is not NULL and nBA is not NULL then *nBA is set
+** to the GetArrayLength() of jBA. If GetByteArrayElements() requires
+** an allocation and that allocation fails then this function either
+** fails fatally or returns 0, depending on build-time options.
+ */
+static jbyte * s3jni__jbyteArray_bytes2(JNIEnv * const env, jbyteArray jBA, jsize * nBA ){
+ jbyte * const rv = jBA ? (*env)->GetByteArrayElements(env, jBA, NULL) : 0;
+ s3jni_oom_check( jBA ? !!rv : 1 );
+ if( jBA && nBA ) *nBA = (*env)->GetArrayLength(env, jBA);
+ return rv;
+}
+
+#define s3jni_jbyteArray_bytes2(jByteArray,ptrToSz) \
+ s3jni__jbyteArray_bytes2(env, (jByteArray), (ptrToSz))
+#define s3jni_jbyteArray_bytes(jByteArray) s3jni__jbyteArray_bytes2(env, (jByteArray), 0)
+#define s3jni_jbyteArray_release(jByteArray,jBytes) \
+ if( jBytes ) (*env)->ReleaseByteArrayElements(env, jByteArray, jBytes, JNI_ABORT)
+#define s3jni_jbyteArray_commit(jByteArray,jBytes) \
+ if( jBytes ) (*env)->ReleaseByteArrayElements(env, jByteArray, jBytes, JNI_COMMIT)
+
+/*
+** Returns the current JNIEnv object. Fails fatally if it cannot find
+** the object.
+*/
+static JNIEnv * s3jni_env(void){
+ JNIEnv * env = 0;
+ if( (*SJG.jvm)->GetEnv(SJG.jvm, (void **)&env,
+ JNI_VERSION_1_8) ){
+ fprintf(stderr, "Fatal error: cannot get current JNIEnv.\n");
+ abort();
+ }
+ return env;
+}
+
+/*
+** Fetches the S3JniGlobal.envCache row for the given env, allocing a
+** row if needed. When a row is allocated, its state is initialized
+** insofar as possible. Calls (*env)->FatalError() if allocation of an
+** entry fails. That's hypothetically possible but "shouldn't happen."
+*/
+static S3JniEnv * S3JniEnv__get(JNIEnv * const env){
+ struct S3JniEnv * row;
+ S3JniEnv_mutex_enter;
+ row = SJG.envCache.aHead;
+ for( ; row; row = row->pNext ){
+ if( row->env == env ){
+ s3jni_incr( &SJG.metrics.nEnvHit );
+ S3JniEnv_mutex_leave;
+ return row;
+ }
+ }
+ s3jni_incr( &SJG.metrics.nEnvMiss );
+ row = SJG.envCache.aFree;
+ if( row ){
+ SJG.envCache.aFree = row->pNext;
+ }else{
+ row = s3jni_malloc_or_die(env, sizeof(*row));
+ s3jni_incr( &SJG.metrics.nEnvAlloc );
+ }
+ memset(row, 0, sizeof(*row));
+ row->pNext = SJG.envCache.aHead;
+ SJG.envCache.aHead = row;
+ row->env = env;
+
+ S3JniEnv_mutex_leave;
+ return row;
+}
+
+#define S3JniEnv_get() S3JniEnv__get(env)
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own Java/JNI bindings.
+**
+** For purposes of certain hand-crafted JNI function bindings, we
+** need a way of reporting errors which is consistent with the rest of
+** the C API, as opposed to throwing Java exceptions. To that end, this
+** internal-use-only function is a thin proxy around
+** sqlite3ErrorWithMessage(). The intent is that it only be used from
+** JNI bindings such as sqlite3_prepare_v2/v3(), and definitely not
+** from client code.
+**
+** Returns err_code.
+*/
+static int s3jni_db_error(sqlite3* const db, int err_code,
+ const char * const zMsg){
+ if( db!=0 ){
+ if( 0==zMsg ){
+ sqlite3Error(db, err_code);
+ }else{
+ const int nMsg = sqlite3Strlen30(zMsg);
+ sqlite3_mutex_enter(sqlite3_db_mutex(db));
+ sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg);
+ sqlite3_mutex_leave(sqlite3_db_mutex(db));
+ }
+ }
+ return err_code;
+}
+
+/*
+** Creates a new jByteArray of length nP, copies p's contents into it,
+** and returns that byte array (NULL on OOM unless fail-fast alloc
+** errors are enabled). p may be NULL, in which case the array is
+** created but no bytes are filled.
+*/
+static jbyteArray s3jni__new_jbyteArray(JNIEnv * const env,
+ const void * const p, int nP){
+ jbyteArray jba = (*env)->NewByteArray(env, (jint)nP);
+
+ s3jni_oom_check( jba );
+ if( jba && p ){
+ (*env)->SetByteArrayRegion(env, jba, 0, (jint)nP, (const jbyte*)p);
+ }
+ return jba;
+}
+
+#define s3jni_new_jbyteArray(P,n) s3jni__new_jbyteArray(env, P, n)
+
+
+/*
+** Uses the java.lang.String(byte[],Charset) constructor to create a
+** new String from UTF-8 string z. n is the number of bytes to
+** copy. If n<0 then sqlite3Strlen30() is used to calculate it.
+**
+** Returns NULL if z is NULL or on OOM, else returns a new jstring
+** owned by the caller.
+**
+** Sidebar: this is a painfully inefficient way to convert from
+** standard UTF-8 to a Java string, but JNI offers only algorithms for
+** working with MUTF-8, not UTF-8.
+*/
+static jstring s3jni__utf8_to_jstring(JNIEnv * const env,
+ const char * const z, int n){
+ jstring rv = NULL;
+ if( 0==n || (n<0 && z && !z[0]) ){
+ /* Fast-track the empty-string case via the MUTF-8 API. We could
+ hypothetically do this for any strings where n<4 and z is
+ NUL-terminated and none of z[0..3] are NUL bytes. */
+ rv = (*env)->NewStringUTF(env, "");
+ s3jni_oom_check( rv );
+ }else if( z ){
+ jbyteArray jba;
+ if( n<0 ) n = sqlite3Strlen30(z);
+ jba = s3jni_new_jbyteArray((unsigned const char *)z, n);
+ if( jba ){
+ rv = (*env)->NewObject(env, SJG.g.cString, SJG.g.ctorStringBA,
+ jba, SJG.g.oCharsetUtf8);
+ S3JniIfThrew{
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ }
+ S3JniUnrefLocal(jba);
+ }
+ s3jni_oom_check( rv );
+ }
+ return rv;
+}
+#define s3jni_utf8_to_jstring(CStr,n) s3jni__utf8_to_jstring(env, CStr, n)
+
+/*
+** Converts the given java.lang.String object into a NUL-terminated
+** UTF-8 C-string by calling jstr.getBytes(StandardCharset.UTF_8).
+** Returns NULL if jstr is NULL or on allocation error. If jstr is not
+** NULL and nLen is not NULL then nLen is set to the length of the
+** returned string, not including the terminating NUL. If jstr is not
+** NULL and it returns NULL, this indicates an allocation error. In
+** that case, if nLen is not NULL then it is either set to 0 (if
+** fetching of jstr's bytes fails to allocate) or set to what would
+** have been the length of the string had C-string allocation
+** succeeded.
+**
+** The returned memory is allocated from sqlite3_malloc() and
+** ownership is transferred to the caller.
+*/
+static char * s3jni__jstring_to_utf8(JNIEnv * const env,
+ jstring jstr, int *nLen){
+ jbyteArray jba;
+ jsize nBA;
+ char *rv;
+
+ if( !jstr ) return 0;
+ jba = (*env)->CallObjectMethod(env, jstr, SJG.g.stringGetBytes,
+ SJG.g.oCharsetUtf8);
+
+ if( (*env)->ExceptionCheck(env) || !jba
+ /* order of these checks is significant for -Xlint:jni */ ) {
+ S3JniExceptionReport;
+ s3jni_oom_check( jba );
+ if( nLen ) *nLen = 0;
+ return 0;
+ }
+ nBA = (*env)->GetArrayLength(env, jba);
+ if( nLen ) *nLen = (int)nBA;
+ rv = s3jni_malloc( nBA + 1 );
+ if( rv ){
+ (*env)->GetByteArrayRegion(env, jba, 0, nBA, (jbyte*)rv);
+ rv[nBA] = 0;
+ }
+ S3JniUnrefLocal(jba);
+ return rv;
+}
+#define s3jni_jstring_to_utf8(JStr,n) s3jni__jstring_to_utf8(env, JStr, n)
+
+/*
+** Expects to be passed a pointer from sqlite3_column_text16() or
+** sqlite3_value_text16() and a byte-length value from
+** sqlite3_column_bytes16() or sqlite3_value_bytes16(). It creates a
+** Java String of exactly half that character length, returning NULL
+** if !p or (*env)->NewString() fails.
+*/
+static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, int nP){
+ jstring const rv = p
+ ? (*env)->NewString(env, (const jchar *)p, (jsize)(nP/2))
+ : NULL;
+ s3jni_oom_check( p ? !!rv : 1 );
+ return rv;
+}
+
+/*
+** Requires jx to be a Throwable. Calls its toString() method and
+** returns its value converted to a UTF-8 string. The caller owns the
+** returned string and must eventually sqlite3_free() it. Returns 0
+** if there is a problem fetching the info or on OOM.
+**
+** Design note: we use toString() instead of getMessage() because the
+** former includes the exception type's name:
+**
+** Exception e = new RuntimeException("Hi");
+** System.out.println(e.toString()); // java.lang.RuntimeException: Hi
+** System.out.println(e.getMessage()); // Hi
+*/
+static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx){
+ jmethodID mid;
+ jstring msg;
+ char * zMsg;
+ jclass const klazz = (*env)->GetObjectClass(env, jx);
+ mid = (*env)->GetMethodID(env, klazz, "toString", "()Ljava/lang/String;");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew{
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ return 0;
+ }
+ msg = (*env)->CallObjectMethod(env, jx, mid);
+ S3JniIfThrew{
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ return 0;
+ }
+ zMsg = s3jni_jstring_to_utf8( msg, 0);
+ S3JniUnrefLocal(msg);
+ return zMsg;
+}
+
+/*
+** Extracts env's current exception, sets ps->pDb's error message to
+** its message string, and clears the exception. If errCode is non-0,
+** it is used as-is, else SQLITE_ERROR is assumed. If there's a
+** problem extracting the exception's message, it's treated as
+** non-fatal and zDfltMsg is used in its place.
+**
+** Locks the global S3JniDb mutex.
+**
+** This must only be called if a JNI exception is pending.
+**
+** Returns errCode unless it is 0, in which case SQLITE_ERROR is
+** returned.
+*/
+static int s3jni__db_exception(JNIEnv * const env, sqlite3 * const pDb,
+ int errCode, const char *zDfltMsg){
+ jthrowable const ex = (*env)->ExceptionOccurred(env);
+
+ if( 0==errCode ) errCode = SQLITE_ERROR;
+ if( ex ){
+ char * zMsg;
+ S3JniExceptionClear;
+ zMsg = s3jni_exception_error_msg(env, ex);
+ s3jni_db_error(pDb, errCode, zMsg ? zMsg : zDfltMsg);
+ sqlite3_free(zMsg);
+ S3JniUnrefLocal(ex);
+ }else if( zDfltMsg ){
+ s3jni_db_error(pDb, errCode, zDfltMsg);
+ }
+ return errCode;
+}
+#define s3jni_db_exception(pDb,ERRCODE,DFLTMSG) \
+ s3jni__db_exception(env, (pDb), (ERRCODE), (DFLTMSG) )
+
+/*
+** Extracts the (void xDestroy()) method from jObj and applies it to
+** jObj. If jObj is NULL, this is a no-op. The lack of an xDestroy()
+** method is silently ignored. Any exceptions thrown by xDestroy()
+** trigger a warning to stdout or stderr and then the exception is
+** suppressed.
+*/
+static void s3jni__call_xDestroy(JNIEnv * const env, jobject jObj){
+ if( jObj ){
+ jclass const klazz = (*env)->GetObjectClass(env, jObj);
+ jmethodID method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V");
+
+ S3JniUnrefLocal(klazz);
+ if( method ){
+ s3jni_incr( &SJG.metrics.nDestroy );
+ (*env)->CallVoidMethod(env, jObj, method);
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("xDestroy() callback");
+ S3JniExceptionClear;
+ }
+ }else{
+ /* Non-fatal. */
+ S3JniExceptionClear;
+ }
+ }
+}
+#define s3jni_call_xDestroy(JOBJ) s3jni__call_xDestroy(env, (JOBJ))
+
+/*
+** Internal helper for many hook callback impls. Locks the S3JniDb
+** mutex, makes a copy of src into dest, with a some differences: (1)
+** if src->jObj or src->jExtra are not NULL then dest will be a new
+** LOCAL ref to it instead of a copy of the prior GLOBAL ref. (2)
+** dest->doXDestroy is always false.
+**
+** If dest->jObj is not NULL when this returns then the caller is
+** obligated to eventually free the new ref by passing *dest to
+** S3JniHook_localundup(). The dest pointer must NOT be passed to
+** S3JniHook_unref(), as that routine assumes that dest->jObj/jExtra
+** are GLOBAL refs (it's illegal to try to unref the wrong ref type).
+**
+** Background: when running a hook we need a call-local copy lest
+** another thread modify the hook while we're running it. That copy
+** has to have its own Java reference, but it need only be call-local.
+*/
+static void S3JniHook__localdup( JNIEnv * const env, S3JniHook const * const src,
+ S3JniHook * const dest ){
+ S3JniHook_mutex_enter;
+ *dest = *src;
+ if(src->jObj) dest->jObj = S3JniRefLocal(src->jObj);
+ if(src->jExtra) dest->jExtra = S3JniRefLocal(src->jExtra);
+ dest->doXDestroy = 0;
+ S3JniHook_mutex_leave;
+}
+#define S3JniHook_localdup(src,dest) S3JniHook__localdup(env,src,dest)
+
+static void S3JniHook__localundup( JNIEnv * const env, S3JniHook * const h ){
+ S3JniUnrefLocal(h->jObj);
+ S3JniUnrefLocal(h->jExtra);
+ *h = S3JniHook_empty;
+}
+#define S3JniHook_localundup(HOOK) S3JniHook__localundup(env, &(HOOK))
+
+/*
+** Removes any Java references from s and clears its state. If
+** doXDestroy is true and s->jObj is not NULL, s->jObj
+** is passed to s3jni_call_xDestroy() before any references are
+** cleared. It is legal to call this when the object has no Java
+** references. s must not be NULL.
+*/
+static void S3JniHook__unref(JNIEnv * const env, S3JniHook * const s){
+ if( s->jObj ){
+ if( s->doXDestroy ){
+ s3jni_call_xDestroy(s->jObj);
+ }
+ S3JniUnrefGlobal(s->jObj);
+ S3JniUnrefGlobal(s->jExtra);
+ }else{
+ assert( !s->jExtra );
+ }
+ *s = S3JniHook_empty;
+}
+#define S3JniHook_unref(hook) S3JniHook__unref(env, (hook))
+
+/*
+** Allocates one blank S3JniHook object from the recycling bin, if
+** available, else from the heap. Returns NULL or dies on OOM,
+** depending on build options. Locks on SJG.hooks.mutex.
+*/
+static S3JniHook *S3JniHook__alloc(JNIEnv * const env){
+ S3JniHook * p = 0;
+ S3JniHook_mutex_enter;
+ if( SJG.hook.aFree ){
+ p = SJG.hook.aFree;
+ SJG.hook.aFree = p->pNext;
+ p->pNext = 0;
+ s3jni_incr(&SJG.metrics.nHookRecycled);
+ }
+ S3JniHook_mutex_leave;
+ if( 0==p ){
+ p = s3jni_malloc(sizeof(S3JniHook));
+ if( p ){
+ s3jni_incr(&SJG.metrics.nHookAlloc);
+ }
+ }
+ if( p ){
+ *p = S3JniHook_empty;
+ }
+ return p;
+}
+#define S3JniHook_alloc() S3JniHook__alloc(env)
+
+/*
+** The rightful fate of all results from S3JniHook_alloc(). Locks on
+** SJG.hook.mutex.
+*/
+static void S3JniHook__free(JNIEnv * const env, S3JniHook * const p){
+ if(p){
+ assert( !p->pNext );
+ S3JniHook_unref(p);
+ S3JniHook_mutex_enter;
+ p->pNext = SJG.hook.aFree;
+ SJG.hook.aFree = p;
+ S3JniHook_mutex_leave;
+ }
+}
+#define S3JniHook_free(hook) S3JniHook__free(env, hook)
+
+#if 0
+/* S3JniHook__free() without the lock: caller must hold the global mutex */
+static void S3JniHook__free_unlocked(JNIEnv * const env, S3JniHook * const p){
+ if(p){
+ assert( !p->pNext );
+ assert( p->pNext != SJG.hook.aFree );
+ S3JniHook_unref(p);
+ p->pNext = SJG.hook.aFree;
+ SJG.hook.aFree = p;
+ }
+}
+#define S3JniHook_free_unlocked(hook) S3JniHook__free_unlocked(env, hook)
+#endif
+
+/*
+** Clears all of s's state. Requires that that the caller has locked
+** S3JniGlobal.perDb.mutex. Make sure to do anything needed with
+** s->pNext and s->pPrev before calling this, as this clears them.
+*/
+static void S3JniDb_clear(JNIEnv * const env, S3JniDb * const s){
+ S3JniDb_mutex_assertLocker;
+ sqlite3_free( s->zMainDbName );
+#define UNHOOK(MEMBER) \
+ S3JniHook_unref(&s->hooks.MEMBER)
+ UNHOOK(auth);
+ UNHOOK(busyHandler);
+ UNHOOK(collationNeeded);
+ UNHOOK(commit);
+ UNHOOK(progress);
+ UNHOOK(rollback);
+ UNHOOK(trace);
+ UNHOOK(update);
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ UNHOOK(preUpdate);
+#endif
+#undef UNHOOK
+ S3JniUnrefGlobal(s->jDb);
+ memset(s, 0, sizeof(S3JniDb));
+}
+
+/*
+** Clears s's state and moves it to the free-list. Requires that
+** S3JniGlobal.perDb.mutex is locked.
+*/
+static void S3JniDb__set_aside_unlocked(JNIEnv * const env, S3JniDb * const s){
+ assert( s );
+ S3JniDb_mutex_assertLocker;
+ if( s ){
+ S3JniDb_clear(env, s);
+ s->pNext = SJG.perDb.aFree;
+ SJG.perDb.aFree = s;
+ }
+}
+#define S3JniDb_set_aside_unlocked(JniDb) S3JniDb__set_aside_unlocked(env, JniDb)
+
+static void S3JniDb__set_aside(JNIEnv * const env, S3JniDb * const s){
+ S3JniDb_mutex_enter;
+ S3JniDb_set_aside_unlocked(s);
+ S3JniDb_mutex_leave;
+}
+#define S3JniDb_set_aside(JNIDB) S3JniDb__set_aside(env, JNIDB)
+
+/*
+** Uncache any state for the given JNIEnv, clearing all Java
+** references the cache owns. Returns true if env was cached and false
+** if it was not found in the cache. Ownership of the S3JniEnv object
+** associated with the given argument is transferred to this function,
+** which makes it free for re-use.
+**
+** Requires that the env mutex be locked.
+*/
+static int S3JniEnv_uncache(JNIEnv * const env){
+ struct S3JniEnv * row;
+ struct S3JniEnv * pPrev = 0;
+
+ S3JniEnv_mutex_assertLocked;
+ row = SJG.envCache.aHead;
+ for( ; row; pPrev = row, row = row->pNext ){
+ if( row->env == env ){
+ break;
+ }
+ }
+ if( !row ){
+ return 0;
+ }
+ if( pPrev) pPrev->pNext = row->pNext;
+ else{
+ assert( SJG.envCache.aHead == row );
+ SJG.envCache.aHead = row->pNext;
+ }
+ memset(row, 0, sizeof(S3JniEnv));
+ row->pNext = SJG.envCache.aFree;
+ SJG.envCache.aFree = row;
+ return 1;
+}
+
+/*
+** Fetches the given nph-ref from cache the cache and returns the
+** object with its klazz member set. This is an O(1) operation except
+** on the first call for a given pRef, during which pRef->klazz and
+** pRef->pRef are initialized thread-safely. In the latter case it's
+** still effectively O(1), but with a much longer 1.
+**
+** It is up to the caller to populate the other members of the
+** returned object if needed, taking care to lock the modification
+** with S3JniNph_mutex_enter/leave.
+**
+** This simple cache catches >99% of searches in the current
+** (2023-07-31) tests.
+*/
+static S3JniNphOp * s3jni__nphop(JNIEnv * const env, S3JniNphOp const* pRef){
+ S3JniNphOp * const pNC = &SJG.nph.list[pRef->index];
+
+ assert( (void*)pRef>=(void*)&S3JniNphOps && (void*)pRef<(void*)(&S3JniNphOps + 1)
+ && "pRef is out of range" );
+ assert( pRef->index>=0
+ && (pRef->index < (sizeof(S3JniNphOps) / sizeof(S3JniNphOp)))
+ && "pRef->index is out of range" );
+ if( !pNC->klazz ){
+ S3JniNph_mutex_enter;
+ if( !pNC->klazz ){
+ jclass const klazz = (*env)->FindClass(env, pRef->zName);
+ //printf("FindClass %s\n", pRef->zName);
+ S3JniExceptionIsFatal("FindClass() unexpectedly threw");
+ pNC->klazz = S3JniRefGlobal(klazz);
+ }
+ S3JniNph_mutex_leave;
+ }
+ assert( pNC->klazz );
+ return pNC;
+}
+
+#define s3jni_nphop(PRef) s3jni__nphop(env, PRef)
+
+/*
+** Common code for accessor functions for NativePointerHolder and
+** OutputPointer types. pRef must be a pointer from S3JniNphOps. jOut
+** must be an instance of that class (Java's type safety takes care of
+** that requirement). If necessary, this fetches the jfieldID for
+** jOut's pRef->zMember, which must be of the type represented by the
+** JNI type signature pRef->zTypeSig, and stores it in
+** S3JniGlobal.nph.list[pRef->index]. Fails fatally if the pRef->zMember
+** property is not found, as that presents a serious internal misuse.
+**
+** Property lookups are cached on a per-pRef basis.
+*/
+static jfieldID s3jni_nphop_field(JNIEnv * const env, S3JniNphOp const* pRef){
+ S3JniNphOp * const pNC = s3jni_nphop(pRef);
+
+ if( !pNC->fidValue ){
+ S3JniNph_mutex_enter;
+ if( !pNC->fidValue ){
+ pNC->fidValue = (*env)->GetFieldID(env, pNC->klazz,
+ pRef->zMember, pRef->zTypeSig);
+ S3JniExceptionIsFatal("Code maintenance required: missing "
+ "required S3JniNphOp::fidValue.");
+ }
+ S3JniNph_mutex_leave;
+ }
+ assert( pNC->fidValue );
+ return pNC->fidValue;
+}
+
+/*
+** Sets a native ptr value in NativePointerHolder object jNph,
+** which must be of the native type described by pRef. jNph
+** may not be NULL.
+*/
+static void NativePointerHolder__set(JNIEnv * const env, S3JniNphOp const* pRef,
+ jobject jNph, const void * p){
+ assert( jNph );
+ (*env)->SetLongField(env, jNph, s3jni_nphop_field(env, pRef),
+ S3JniCast_P2L(p));
+ S3JniExceptionIsFatal("Could not set NativePointerHolder.nativePointer.");
+}
+
+#define NativePointerHolder_set(PREF,JNPH,P) \
+ NativePointerHolder__set(env, PREF, JNPH, P)
+
+/*
+** Fetches a native ptr value from NativePointerHolder object jNph,
+** which must be of the native type described by pRef. This is a
+** no-op if jNph is NULL.
+*/
+static void * NativePointerHolder__get(JNIEnv * env, jobject jNph,
+ S3JniNphOp const* pRef){
+ void * rv = 0;
+ if( jNph ){
+ rv = S3JniCast_L2P(
+ (*env)->GetLongField(env, jNph, s3jni_nphop_field(env, pRef))
+ );
+ S3JniExceptionIsFatal("Cannot fetch NativePointerHolder.nativePointer.");
+ }
+ return rv;
+}
+
+#define NativePointerHolder_get(JOBJ,NPHREF) \
+ NativePointerHolder__get(env, (JOBJ), (NPHREF))
+
+/*
+** Helpers for extracting pointers from jobjects, noting that we rely
+** on the corresponding Java interfaces having already done the
+** type-checking. OBJ must be a jobject referring to a
+** NativePointerHolder, where T matches PtrGet_T. Don't use these
+** in contexts where that's not the case. Note that these aren't
+** type-safe in the strictest sense:
+**
+** sqlite3 * s = PtrGet_sqlite3_stmt(...)
+**
+** will work, despite the incorrect macro name, so long as the
+** argument is a Java sqlite3 object, as this operation only has void
+** pointers to work with.
+*/
+#define PtrGet_T(T,OBJ) (T*)NativePointerHolder_get(OBJ, S3JniNph(T))
+#define PtrGet_sqlite3(OBJ) PtrGet_T(sqlite3, OBJ)
+#define PtrGet_sqlite3_backup(OBJ) PtrGet_T(sqlite3_backup, OBJ)
+#define PtrGet_sqlite3_blob(OBJ) PtrGet_T(sqlite3_blob, OBJ)
+#define PtrGet_sqlite3_context(OBJ) PtrGet_T(sqlite3_context, OBJ)
+#define PtrGet_sqlite3_stmt(OBJ) PtrGet_T(sqlite3_stmt, OBJ)
+#define PtrGet_sqlite3_value(OBJ) PtrGet_T(sqlite3_value, OBJ)
+/*
+** S3JniLongPtr_T(X,Y) expects X to be an unqualified sqlite3 struct
+** type name and Y to be a native pointer to such an object in the
+** form of a jlong value. The jlong is simply cast to (X*). This
+** approach is, as of 2023-09-27, supplanting the former approach. We
+** now do the native pointer extraction in the Java side, rather than
+** the C side, because it's reportedly significantly faster. The
+** intptr_t part here is necessary for compatibility with (at least)
+** ARM32.
+*/
+#define S3JniLongPtr_T(T,JLongAsPtr) (T*)((intptr_t)(JLongAsPtr))
+#define S3JniLongPtr_sqlite3(JLongAsPtr) S3JniLongPtr_T(sqlite3,JLongAsPtr)
+#define S3JniLongPtr_sqlite3_backup(JLongAsPtr) S3JniLongPtr_T(sqlite3_backup,JLongAsPtr)
+#define S3JniLongPtr_sqlite3_blob(JLongAsPtr) S3JniLongPtr_T(sqlite3_blob,JLongAsPtr)
+#define S3JniLongPtr_sqlite3_stmt(JLongAsPtr) S3JniLongPtr_T(sqlite3_stmt,JLongAsPtr)
+#define S3JniLongPtr_sqlite3_value(JLongAsPtr) S3JniLongPtr_T(sqlite3_value,JLongAsPtr)
+/*
+** Extracts the new S3JniDb instance from the free-list, or allocates
+** one if needed, associates it with pDb, and returns. Returns NULL
+** on OOM. The returned object MUST, on success of the calling
+** operation, subsequently be associated with jDb via
+** NativePointerHolder_set() or freed using S3JniDb_set_aside().
+*/
+static S3JniDb * S3JniDb_alloc(JNIEnv * const env, jobject jDb){
+ S3JniDb * rv = 0;
+ S3JniDb_mutex_enter;
+ if( SJG.perDb.aFree ){
+ rv = SJG.perDb.aFree;
+ SJG.perDb.aFree = rv->pNext;
+ rv->pNext = 0;
+ s3jni_incr( &SJG.metrics.nPdbRecycled );
+ }
+ S3JniDb_mutex_leave;
+ if( 0==rv ){
+ rv = s3jni_malloc(sizeof(S3JniDb));
+ if( rv ){
+ s3jni_incr( &SJG.metrics.nPdbAlloc );
+ }
+ }
+ if( rv ){
+ memset(rv, 0, sizeof(S3JniDb));
+ rv->jDb = S3JniRefGlobal(jDb);
+ }
+ return rv;
+}
+
+/*
+** Returns the S3JniDb object for the given org.sqlite.jni.capi.sqlite3
+** object, or NULL if jDb is NULL, no pointer can be extracted
+** from it, or no matching entry can be found.
+*/
+static S3JniDb * S3JniDb__from_java(JNIEnv * const env, jobject jDb){
+ sqlite3 * const pDb = jDb ? PtrGet_sqlite3(jDb) : 0;
+ return pDb ? S3JniDb_from_clientdata(pDb) : 0;
+}
+#define S3JniDb_from_java(jObject) S3JniDb__from_java(env,(jObject))
+
+/*
+** S3JniDb finalizer for use with sqlite3_set_clientdata().
+*/
+static void S3JniDb_xDestroy(void *p){
+ S3JniDeclLocal_env;
+ S3JniDb * const ps = p;
+ assert( !ps->pNext && "Else ps is already in the free-list.");
+ S3JniDb_set_aside(ps);
+}
+
+/*
+** Evaluates to the S3JniDb object for the given sqlite3 object, or
+** NULL if pDb is NULL or was not initialized via the JNI interfaces.
+*/
+#define S3JniDb_from_c(sqlite3Ptr) \
+ ((sqlite3Ptr) ? S3JniDb_from_clientdata(sqlite3Ptr) : 0)
+#define S3JniDb_from_jlong(sqlite3PtrAsLong) \
+ S3JniDb_from_c(S3JniLongPtr_T(sqlite3,sqlite3PtrAsLong))
+
+/*
+** Unref any Java-side state in (S3JniAutoExtension*) AX and zero out
+** AX.
+*/
+#define S3JniAutoExtension_clear(AX) S3JniHook_unref(AX);
+
+/*
+** Initializes a pre-allocated S3JniAutoExtension object. Returns
+** non-0 if there is an error collecting the required state from
+** jAutoExt (which must be an AutoExtensionCallback object). On error,
+** it passes ax to S3JniAutoExtension_clear().
+*/
+static int S3JniAutoExtension_init(JNIEnv *const env,
+ S3JniAutoExtension * const ax,
+ jobject const jAutoExt){
+ jclass const klazz = (*env)->GetObjectClass(env, jAutoExt);
+
+ S3JniAutoExt_mutex_assertLocker;
+ *ax = S3JniHook_empty;
+ ax->midCallback = (*env)->GetMethodID(env, klazz, "call",
+ "(Lorg/sqlite/jni/capi/sqlite3;)I");
+ S3JniUnrefLocal(klazz);
+ S3JniExceptionWarnIgnore;
+ if( !ax->midCallback ){
+ S3JniAutoExtension_clear(ax);
+ return SQLITE_ERROR;
+ }
+ ax->jObj = S3JniRefGlobal(jAutoExt);
+ return 0;
+}
+
+/*
+** Sets the value property of the OutputPointer.Bool jOut object to
+** v.
+*/
+static void OutputPointer_set_Bool(JNIEnv * const env, jobject const jOut,
+ int v){
+ (*env)->SetBooleanField(env, jOut, s3jni_nphop_field(
+ env, S3JniNph(OutputPointer_Bool)
+ ), v ? JNI_TRUE : JNI_FALSE );
+ S3JniExceptionIsFatal("Cannot set OutputPointer.Bool.value");
+}
+
+/*
+** Sets the value property of the OutputPointer.Int32 jOut object to
+** v.
+*/
+static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut,
+ int v){
+ (*env)->SetIntField(env, jOut, s3jni_nphop_field(
+ env, S3JniNph(OutputPointer_Int32)
+ ), (jint)v);
+ S3JniExceptionIsFatal("Cannot set OutputPointer.Int32.value");
+}
+
+/*
+** Sets the value property of the OutputPointer.Int64 jOut object to
+** v.
+*/
+static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut,
+ jlong v){
+ (*env)->SetLongField(env, jOut, s3jni_nphop_field(
+ env, S3JniNph(OutputPointer_Int64)
+ ), v);
+ S3JniExceptionIsFatal("Cannot set OutputPointer.Int64.value");
+}
+
+/*
+** Internal helper for OutputPointer_set_TYPE() where TYPE is an
+** Object type.
+*/
+static void OutputPointer_set_obj(JNIEnv * const env,
+ S3JniNphOp const * const pRef,
+ jobject const jOut,
+ jobject v){
+ (*env)->SetObjectField(env, jOut, s3jni_nphop_field(env, pRef), v);
+ S3JniExceptionIsFatal("Cannot set OutputPointer.T.value");
+}
+
+#ifdef SQLITE_ENABLE_FTS5
+#if 0
+/*
+** Sets the value property of the OutputPointer.ByteArray jOut object
+** to v.
+*/
+static void OutputPointer_set_ByteArray(JNIEnv * const env, jobject const jOut,
+ jbyteArray const v){
+ OutputPointer_set_obj(env, S3JniNph(OutputPointer_ByteArray), jOut, v);
+}
+#endif
+#endif /* SQLITE_ENABLE_FTS5 */
+
+/*
+** Sets the value property of the OutputPointer.String jOut object to
+** v.
+*/
+static void OutputPointer_set_String(JNIEnv * const env, jobject const jOut,
+ jstring const v){
+ OutputPointer_set_obj(env, S3JniNph(OutputPointer_String), jOut, v);
+}
+
+/*
+** Returns true if eTextRep is a valid sqlite3 encoding constant, else
+** returns false.
+*/
+static int encodingTypeIsValid(int eTextRep){
+ switch( eTextRep ){
+ case SQLITE_UTF8: case SQLITE_UTF16:
+ case SQLITE_UTF16LE: case SQLITE_UTF16BE:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/* For use with sqlite3_result/value_pointer() */
+static const char * const ResultJavaValuePtrStr = "org.sqlite.jni.capi.ResultJavaVal";
+
+/*
+** If v is not NULL, it must be a jobject global reference. Its
+** reference is relinquished.
+*/
+static void S3Jni_jobject_finalizer(void *v){
+ if( v ){
+ S3JniDeclLocal_env;
+ S3JniUnrefGlobal((jobject)v);
+ }
+}
+
+/*
+** Returns a new Java instance of the class referred to by pRef, which
+** MUST be interface-compatible with NativePointerHolder and MUST have
+** a no-arg constructor. The NativePointerHolder_set() method is
+** passed the new Java object (which must not be NULL) and pNative
+** (which may be NULL). Hypothetically returns NULL if Java fails to
+** allocate, but the JNI docs are not entirely clear on that detail.
+**
+** Always use a static pointer from the S3JniNphOps struct for the
+** 2nd argument.
+*/
+static jobject NativePointerHolder_new(JNIEnv * const env,
+ S3JniNphOp const * pRef,
+ const void * pNative){
+ jobject rv = 0;
+ S3JniNphOp * const pNC = s3jni_nphop(pRef);
+ if( !pNC->midCtor ){
+ S3JniNph_mutex_enter;
+ if( !pNC->midCtor ){
+ pNC->midCtor = (*env)->GetMethodID(env, pNC->klazz, "", "()V");
+ S3JniExceptionIsFatal("Cannot find constructor for class.");
+ }
+ S3JniNph_mutex_leave;
+ }
+ rv = (*env)->NewObject(env, pNC->klazz, pNC->midCtor);
+ S3JniExceptionIsFatal("No-arg constructor threw.");
+ s3jni_oom_check(rv);
+ if( rv ) NativePointerHolder_set(pRef, rv, pNative);
+ return rv;
+}
+
+static inline jobject new_java_sqlite3(JNIEnv * const env, sqlite3 *sv){
+ return NativePointerHolder_new(env, S3JniNph(sqlite3), sv);
+}
+static inline jobject new_java_sqlite3_backup(JNIEnv * const env, sqlite3_backup *sv){
+ return NativePointerHolder_new(env, S3JniNph(sqlite3_backup), sv);
+}
+static inline jobject new_java_sqlite3_blob(JNIEnv * const env, sqlite3_blob *sv){
+ return NativePointerHolder_new(env, S3JniNph(sqlite3_blob), sv);
+}
+static inline jobject new_java_sqlite3_context(JNIEnv * const env, sqlite3_context *sv){
+ return NativePointerHolder_new(env, S3JniNph(sqlite3_context), sv);
+}
+static inline jobject new_java_sqlite3_stmt(JNIEnv * const env, sqlite3_stmt *sv){
+ return NativePointerHolder_new(env, S3JniNph(sqlite3_stmt), sv);
+}
+static inline jobject new_java_sqlite3_value(JNIEnv * const env, sqlite3_value *sv){
+ return NativePointerHolder_new(env, S3JniNph(sqlite3_value), sv);
+}
+
+/* Helper typedefs for UDF callback types. */
+typedef void (*udf_xFunc_f)(sqlite3_context*,int,sqlite3_value**);
+typedef void (*udf_xStep_f)(sqlite3_context*,int,sqlite3_value**);
+typedef void (*udf_xFinal_f)(sqlite3_context*);
+/*typedef void (*udf_xValue_f)(sqlite3_context*);*/
+/*typedef void (*udf_xInverse_f)(sqlite3_context*,int,sqlite3_value**);*/
+
+/*
+** Allocate a new S3JniUdf (User-defined Function) and associate it
+** with the SQLFunction-type jObj. Returns NULL on OOM. If the
+** returned object's type==UDF_UNKNOWN_TYPE then the type of UDF was
+** not unambiguously detected based on which callback members it has,
+** which falls into the category of user error.
+**
+** The caller must arrange for the returned object to eventually be
+** passed to S3JniUdf_free().
+*/
+static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){
+ S3JniUdf * s = 0;
+
+ S3JniGlobal_mutex_enter;
+ s3jni_incr(&SJG.metrics.nMutexUdf);
+ if( SJG.udf.aFree ){
+ s = SJG.udf.aFree;
+ SJG.udf.aFree = s->pNext;
+ s->pNext = 0;
+ s3jni_incr(&SJG.metrics.nUdfRecycled);
+ }
+ S3JniGlobal_mutex_leave;
+ if( !s ){
+ s = s3jni_malloc( sizeof(*s));
+ s3jni_incr(&SJG.metrics.nUdfAlloc);
+ }
+ if( s ){
+ const char * zFSI = /* signature for xFunc, xStep, xInverse */
+ "(Lorg/sqlite/jni/capi/sqlite3_context;[Lorg/sqlite/jni/capi/sqlite3_value;)V";
+ const char * zFV = /* signature for xFinal, xValue */
+ "(Lorg/sqlite/jni/capi/sqlite3_context;)V";
+ jclass const klazz = (*env)->GetObjectClass(env, jObj);
+
+ memset(s, 0, sizeof(*s));
+ s->jObj = S3JniRefGlobal(jObj);
+
+#define FGET(FuncName,FuncSig,Field) \
+ s->Field = (*env)->GetMethodID(env, klazz, FuncName, FuncSig); \
+ if( !s->Field ) (*env)->ExceptionClear(env)
+
+ FGET("xFunc", zFSI, jmidxFunc);
+ FGET("xStep", zFSI, jmidxStep);
+ FGET("xFinal", zFV, jmidxFinal);
+ FGET("xValue", zFV, jmidxValue);
+ FGET("xInverse", zFSI, jmidxInverse);
+#undef FGET
+
+ S3JniUnrefLocal(klazz);
+ if( s->jmidxFunc ) s->type = UDF_SCALAR;
+ else if( s->jmidxStep && s->jmidxFinal ){
+ s->type = (s->jmidxValue && s->jmidxInverse)
+ ? UDF_WINDOW : UDF_AGGREGATE;
+ }else{
+ s->type = UDF_UNKNOWN_TYPE;
+ }
+ }
+ return s;
+}
+
+/*
+** Frees up all resources owned by s, clears its state, then either
+** caches it for reuse (if cacheIt is true) or frees it. The former
+** requires locking the global mutex, so it must not be held when this
+** is called.
+*/
+static void S3JniUdf_free(JNIEnv * const env, S3JniUdf * const s,
+ int cacheIt){
+ assert( !s->pNext );
+ if( s->jObj ){
+ s3jni_call_xDestroy(s->jObj);
+ S3JniUnrefGlobal(s->jObj);
+ sqlite3_free(s->zFuncName);
+ assert( !s->pNext );
+ memset(s, 0, sizeof(*s));
+ }
+ if( cacheIt ){
+ S3JniGlobal_mutex_enter;
+ s->pNext = S3JniGlobal.udf.aFree;
+ S3JniGlobal.udf.aFree = s;
+ S3JniGlobal_mutex_leave;
+ }else{
+ sqlite3_free( s );
+ }
+}
+
+/* Finalizer for sqlite3_create_function() and friends. */
+static void S3JniUdf_finalizer(void * s){
+ S3JniUdf_free(s3jni_env(), (S3JniUdf*)s, 1);
+}
+
+/*
+** Helper for processing args to UDF handlers with signature
+** (sqlite3_context*,int,sqlite3_value**).
+*/
+typedef struct {
+ jobject jcx /* sqlite3_context */;
+ jobjectArray jargv /* sqlite3_value[] */;
+} udf_jargs;
+
+/*
+** Converts the given (cx, argc, argv) into arguments for the given
+** UDF, writing the result (Java wrappers for cx and argv) in the
+** final 2 arguments. Returns 0 on success, SQLITE_NOMEM on allocation
+** error. On error *jCx and *jArgv will be set to 0. The output
+** objects are of type org.sqlite.jni.capi.sqlite3_context and
+** array-of-org.sqlite.jni.capi.sqlite3_value, respectively.
+*/
+static int udf_args(JNIEnv *env,
+ sqlite3_context * const cx,
+ int argc, sqlite3_value**argv,
+ jobject * jCx, jobjectArray *jArgv){
+ jobjectArray ja = 0;
+ jobject jcx = new_java_sqlite3_context(env, cx);
+ jint i;
+ *jCx = 0;
+ *jArgv = 0;
+ if( !jcx ) goto error_oom;
+ ja = (*env)->NewObjectArray(
+ env, argc, s3jni_nphop(S3JniNph(sqlite3_value))->klazz,
+ NULL);
+ s3jni_oom_check( ja );
+ if( !ja ) goto error_oom;
+ for(i = 0; i < argc; ++i){
+ jobject jsv = new_java_sqlite3_value(env, argv[i]);
+ if( !jsv ) goto error_oom;
+ (*env)->SetObjectArrayElement(env, ja, i, jsv);
+ S3JniUnrefLocal(jsv)/*ja has a ref*/;
+ }
+ *jCx = jcx;
+ *jArgv = ja;
+ return 0;
+error_oom:
+ S3JniUnrefLocal(jcx);
+ S3JniUnrefLocal(ja);
+ return SQLITE_NOMEM;
+}
+
+/*
+** Requires that jCx and jArgv are sqlite3_context
+** resp. array-of-sqlite3_value values initialized by udf_args(). This
+** function zeroes out the nativePointer member of jCx and each entry
+** in jArgv. This is a safety-net precaution to avoid undefined
+** behavior if a Java-side UDF holds a reference to one of its
+** arguments. This MUST be called from any function which successfully
+** calls udf_args(), after calling the corresponding UDF and checking
+** its exception status. It MUST NOT be called in any other case.
+*/
+static void udf_unargs(JNIEnv *env, jobject jCx, int argc, jobjectArray jArgv){
+ int i = 0;
+ assert(jCx);
+ NativePointerHolder_set(S3JniNph(sqlite3_context), jCx, 0);
+ for( ; i < argc; ++i ){
+ jobject jsv = (*env)->GetObjectArrayElement(env, jArgv, i);
+ assert(jsv);
+ NativePointerHolder_set(S3JniNph(sqlite3_value), jsv, 0);
+ }
+}
+
+
+/*
+** Must be called immediately after a Java-side UDF callback throws.
+** If translateToErr is true then it sets the exception's message in
+** the result error using sqlite3_result_error(). If translateToErr is
+** false then it emits a warning that the function threw but should
+** not do so. In either case, it clears the exception state.
+**
+** Returns SQLITE_NOMEM if an allocation fails, else SQLITE_ERROR. In
+** the former case it calls sqlite3_result_error_nomem().
+*/
+static int udf_report_exception(JNIEnv * const env, int translateToErr,
+ sqlite3_context * cx,
+ const char *zFuncName, const char *zFuncType ){
+ jthrowable const ex = (*env)->ExceptionOccurred(env);
+ int rc = SQLITE_ERROR;
+
+ assert(ex && "This must only be called when a Java exception is pending.");
+ if( translateToErr ){
+ char * zMsg;
+ char * z;
+
+ S3JniExceptionClear;
+ zMsg = s3jni_exception_error_msg(env, ex);
+ z = sqlite3_mprintf("Client-defined SQL function %s.%s() threw: %s",
+ zFuncName ? zFuncName : "", zFuncType,
+ zMsg ? zMsg : "Unknown exception" );
+ sqlite3_free(zMsg);
+ if( z ){
+ sqlite3_result_error(cx, z, -1);
+ sqlite3_free(z);
+ }else{
+ sqlite3_result_error_nomem(cx);
+ rc = SQLITE_NOMEM;
+ }
+ }else{
+ S3JniExceptionWarnCallbackThrew("client-defined SQL function");
+ S3JniExceptionClear;
+ }
+ S3JniUnrefLocal(ex);
+ return rc;
+}
+
+/*
+** Sets up the state for calling a Java-side xFunc/xStep/xInverse()
+** UDF, calls it, and returns 0 on success.
+*/
+static int udf_xFSI(sqlite3_context* const pCx, int argc,
+ sqlite3_value** const argv, S3JniUdf * const s,
+ jmethodID xMethodID, const char * const zFuncType){
+ S3JniDeclLocal_env;
+ udf_jargs args = {0,0};
+ int rc = udf_args(env, pCx, argc, argv, &args.jcx, &args.jargv);
+
+ if( 0 == rc ){
+ (*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv);
+ S3JniIfThrew{
+ rc = udf_report_exception(env, 'F'==zFuncType[1]/*xFunc*/, pCx,
+ s->zFuncName, zFuncType);
+ }
+ udf_unargs(env, args.jcx, argc, args.jargv);
+ }
+ S3JniUnrefLocal(args.jcx);
+ S3JniUnrefLocal(args.jargv);
+ return rc;
+}
+
+/*
+** Sets up the state for calling a Java-side xFinal/xValue() UDF,
+** calls it, and returns 0 on success.
+*/
+static int udf_xFV(sqlite3_context* cx, S3JniUdf * s,
+ jmethodID xMethodID,
+ const char *zFuncType){
+ S3JniDeclLocal_env;
+ jobject jcx = new_java_sqlite3_context(env, cx);
+ int rc = 0;
+ int const isFinal = 'F'==zFuncType[1]/*xFinal*/;
+
+ if( jcx ){
+ (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx);
+ S3JniIfThrew{
+ rc = udf_report_exception(env, isFinal, cx, s->zFuncName,
+ zFuncType);
+ }
+ S3JniUnrefLocal(jcx);
+ }else{
+ if( isFinal ) sqlite3_result_error_nomem(cx);
+ rc = SQLITE_NOMEM;
+ }
+ return rc;
+}
+
+/* Proxy for C-to-Java xFunc. */
+static void udf_xFunc(sqlite3_context* cx, int argc,
+ sqlite3_value** argv){
+ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
+ s3jni_incr( &SJG.metrics.udf.nFunc );
+ udf_xFSI(cx, argc, argv, s, s->jmidxFunc, "xFunc");
+}
+/* Proxy for C-to-Java xStep. */
+static void udf_xStep(sqlite3_context* cx, int argc,
+ sqlite3_value** argv){
+ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
+ s3jni_incr( &SJG.metrics.udf.nStep );
+ udf_xFSI(cx, argc, argv, s, s->jmidxStep, "xStep");
+}
+/* Proxy for C-to-Java xFinal. */
+static void udf_xFinal(sqlite3_context* cx){
+ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
+ s3jni_incr( &SJG.metrics.udf.nFinal );
+ udf_xFV(cx, s, s->jmidxFinal, "xFinal");
+}
+/* Proxy for C-to-Java xValue. */
+static void udf_xValue(sqlite3_context* cx){
+ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
+ s3jni_incr( &SJG.metrics.udf.nValue );
+ udf_xFV(cx, s, s->jmidxValue, "xValue");
+}
+/* Proxy for C-to-Java xInverse. */
+static void udf_xInverse(sqlite3_context* cx, int argc,
+ sqlite3_value** argv){
+ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
+ s3jni_incr( &SJG.metrics.udf.nInverse );
+ udf_xFSI(cx, argc, argv, s, s->jmidxInverse, "xInverse");
+}
+
+
+////////////////////////////////////////////////////////////////////////
+// What follows is the JNI/C bindings. They are in alphabetical order
+// except for this macro-generated subset which are kept together
+// (alphabetized) here at the front...
+////////////////////////////////////////////////////////////////////////
+
+/** Create a trivial JNI wrapper for (int CName(void)). */
+#define WRAP_INT_VOID(JniNameSuffix,CName) \
+ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass){ \
+ return (jint)CName(); \
+ }
+/** Create a trivial JNI wrapper for (int CName(int)). */
+#define WRAP_INT_INT(JniNameSuffix,CName) \
+ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jint arg){ \
+ return (jint)CName((int)arg); \
+ }
+/*
+** Create a trivial JNI wrapper for (const mutf8_string *
+** CName(void)). This is only valid for functions which are known to
+** return ASCII or text which is equivalent in UTF-8 and MUTF-8.
+*/
+#define WRAP_MUTF8_VOID(JniNameSuffix,CName) \
+ JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass){ \
+ jstring const rv = (*env)->NewStringUTF( env, CName() ); \
+ s3jni_oom_check(rv); \
+ return rv; \
+ }
+/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*)). */
+#define WRAP_INT_STMT(JniNameSuffix,CName) \
+ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt){ \
+ return (jint)CName(S3JniLongPtr_sqlite3_stmt(jpStmt)); \
+ }
+/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*,int)). */
+#define WRAP_INT_STMT_INT(JniNameSuffix,CName) \
+ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt, jint n){ \
+ return (jint)CName(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)n); \
+ }
+/** Create a trivial JNI wrapper for (boolean CName(sqlite3_stmt*)). */
+#define WRAP_BOOL_STMT(JniNameSuffix,CName) \
+ JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jobject jStmt){ \
+ return CName(PtrGet_sqlite3_stmt(jStmt)) ? JNI_TRUE : JNI_FALSE; \
+ }
+/** Create a trivial JNI wrapper for (jstring CName(sqlite3_stmt*,int)). */
+#define WRAP_STR_STMT_INT(JniNameSuffix,CName) \
+ JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt, jint ndx){ \
+ return s3jni_utf8_to_jstring( \
+ CName(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx), \
+ -1); \
+ }
+/** Create a trivial JNI wrapper for (boolean CName(sqlite3*)). */
+#define WRAP_BOOL_DB(JniNameSuffix,CName) \
+ JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \
+ return CName(S3JniLongPtr_sqlite3(jpDb)) ? JNI_TRUE : JNI_FALSE; \
+ }
+/** Create a trivial JNI wrapper for (int CName(sqlite3*)). */
+#define WRAP_INT_DB(JniNameSuffix,CName) \
+ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \
+ return (jint)CName(S3JniLongPtr_sqlite3(jpDb)); \
+ }
+/** Create a trivial JNI wrapper for (int64 CName(sqlite3*)). */
+#define WRAP_INT64_DB(JniNameSuffix,CName) \
+ JniDecl(jlong,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \
+ return (jlong)CName(S3JniLongPtr_sqlite3(jpDb)); \
+ }
+/** Create a trivial JNI wrapper for (jstring CName(sqlite3*,int)). */
+#define WRAP_STR_DB_INT(JniNameSuffix,CName) \
+ JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass, jlong jpDb, jint ndx){ \
+ return s3jni_utf8_to_jstring( \
+ CName(S3JniLongPtr_sqlite3(jpDb), (int)ndx), \
+ -1); \
+ }
+/** Create a trivial JNI wrapper for (int CName(sqlite3_value*)). */
+#define WRAP_INT_SVALUE(JniNameSuffix,CName,DfltOnNull) \
+ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpSValue){ \
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSValue); \
+ return (jint)(sv ? CName(sv): DfltOnNull); \
+ }
+/** Create a trivial JNI wrapper for (boolean CName(sqlite3_value*)). */
+#define WRAP_BOOL_SVALUE(JniNameSuffix,CName,DfltOnNull) \
+ JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jlong jpSValue){ \
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSValue); \
+ return (jint)(sv ? CName(sv) : DfltOnNull) \
+ ? JNI_TRUE : JNI_FALSE; \
+ }
+
+WRAP_INT_DB(1changes, sqlite3_changes)
+WRAP_INT64_DB(1changes64, sqlite3_changes64)
+WRAP_INT_STMT(1clear_1bindings, sqlite3_clear_bindings)
+WRAP_INT_STMT_INT(1column_1bytes, sqlite3_column_bytes)
+WRAP_INT_STMT_INT(1column_1bytes16, sqlite3_column_bytes16)
+WRAP_INT_STMT(1column_1count, sqlite3_column_count)
+WRAP_STR_STMT_INT(1column_1decltype, sqlite3_column_decltype)
+WRAP_STR_STMT_INT(1column_1name, sqlite3_column_name)
+WRAP_STR_STMT_INT(1column_1database_1name, sqlite3_column_database_name)
+WRAP_STR_STMT_INT(1column_1origin_1name, sqlite3_column_origin_name)
+WRAP_STR_STMT_INT(1column_1table_1name, sqlite3_column_table_name)
+WRAP_INT_STMT_INT(1column_1type, sqlite3_column_type)
+WRAP_INT_STMT(1data_1count, sqlite3_data_count)
+WRAP_STR_DB_INT(1db_1name, sqlite3_db_name)
+WRAP_INT_DB(1error_1offset, sqlite3_error_offset)
+WRAP_INT_DB(1extended_1errcode, sqlite3_extended_errcode)
+WRAP_BOOL_DB(1get_1autocommit, sqlite3_get_autocommit)
+WRAP_MUTF8_VOID(1libversion, sqlite3_libversion)
+WRAP_INT_VOID(1libversion_1number, sqlite3_libversion_number)
+WRAP_INT_VOID(1keyword_1count, sqlite3_keyword_count)
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+WRAP_INT_DB(1preupdate_1blobwrite, sqlite3_preupdate_blobwrite)
+WRAP_INT_DB(1preupdate_1count, sqlite3_preupdate_count)
+WRAP_INT_DB(1preupdate_1depth, sqlite3_preupdate_depth)
+#endif
+WRAP_INT_INT(1release_1memory, sqlite3_release_memory)
+WRAP_INT_INT(1sleep, sqlite3_sleep)
+WRAP_MUTF8_VOID(1sourceid, sqlite3_sourceid)
+WRAP_BOOL_STMT(1stmt_1busy, sqlite3_stmt_busy)
+WRAP_INT_STMT_INT(1stmt_1explain, sqlite3_stmt_explain)
+WRAP_INT_STMT(1stmt_1isexplain, sqlite3_stmt_isexplain)
+WRAP_BOOL_STMT(1stmt_1readonly, sqlite3_stmt_readonly)
+WRAP_INT_DB(1system_1errno, sqlite3_system_errno)
+WRAP_INT_VOID(1threadsafe, sqlite3_threadsafe)
+WRAP_INT_DB(1total_1changes, sqlite3_total_changes)
+WRAP_INT64_DB(1total_1changes64, sqlite3_total_changes64)
+WRAP_INT_SVALUE(1value_1encoding, sqlite3_value_encoding,SQLITE_UTF8)
+WRAP_BOOL_SVALUE(1value_1frombind, sqlite3_value_frombind,0)
+WRAP_INT_SVALUE(1value_1nochange, sqlite3_value_nochange,0)
+WRAP_INT_SVALUE(1value_1numeric_1type, sqlite3_value_numeric_type,SQLITE_NULL)
+WRAP_INT_SVALUE(1value_1subtype, sqlite3_value_subtype,0)
+WRAP_INT_SVALUE(1value_1type, sqlite3_value_type,SQLITE_NULL)
+
+#undef WRAP_BOOL_DB
+#undef WRAP_BOOL_STMT
+#undef WRAP_BOOL_SVALUE
+#undef WRAP_INT64_DB
+#undef WRAP_INT_DB
+#undef WRAP_INT_INT
+#undef WRAP_INT_STMT
+#undef WRAP_INT_STMT_INT
+#undef WRAP_INT_SVALUE
+#undef WRAP_INT_VOID
+#undef WRAP_MUTF8_VOID
+#undef WRAP_STR_STMT_INT
+#undef WRAP_STR_DB_INT
+
+S3JniApi(sqlite3_aggregate_context(),jlong,1aggregate_1context)(
+ JniArgsEnvClass, jobject jCx, jboolean initialize
+){
+ sqlite3_context * const pCx = PtrGet_sqlite3_context(jCx);
+ void * const p = pCx
+ ? sqlite3_aggregate_context(pCx, (int)(initialize
+ ? (int)sizeof(void*)
+ : 0))
+ : 0;
+ return S3JniCast_P2L(p);
+}
+
+/* Central auto-extension handler. */
+static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr,
+ const struct sqlite3_api_routines *ignored){
+ int rc = 0;
+ unsigned i, go = 1;
+ JNIEnv * env = 0;
+ S3JniDb * ps;
+ S3JniEnv * jc;
+
+ if( 0==SJG.autoExt.nExt ) return 0;
+ env = s3jni_env();
+ jc = S3JniEnv_get();
+ S3JniDb_mutex_enter;
+ ps = jc->pdbOpening ? jc->pdbOpening : S3JniDb_from_c(pDb);
+ if( !ps ){
+ *pzErr = sqlite3_mprintf("Unexpected arrival of null S3JniDb in "
+ "auto-extension runner.");
+ S3JniDb_mutex_leave;
+ return SQLITE_ERROR;
+ }
+ assert( ps->jDb );
+ if( !ps->pDb ){
+ assert( jc->pdbOpening == ps );
+ rc = sqlite3_set_clientdata(pDb, S3JniDb_clientdata_key,
+ ps, 0/* we'll re-set this after open()
+ completes. */);
+ if( rc ){
+ S3JniDb_mutex_leave;
+ return rc;
+ }
+ }
+ else{
+ assert( ps == jc->pdbOpening );
+ jc->pdbOpening = 0;
+ }
+ S3JniDb_mutex_leave;
+ NativePointerHolder_set(S3JniNph(sqlite3), ps->jDb, pDb)
+ /* As of here, the Java/C connection is complete except for the
+ (temporary) lack of finalizer for the ps object. */;
+ ps->pDb = pDb;
+ for( i = 0; go && 0==rc; ++i ){
+ S3JniAutoExtension ax = S3JniHook_empty
+ /* We need a copy of the auto-extension object, with our own
+ ** local reference to it, to avoid a race condition with another
+ ** thread manipulating the list during the call and invaliding
+ ** what ax references. */;
+ S3JniAutoExt_mutex_enter;
+ if( i >= SJG.autoExt.nExt ){
+ go = 0;
+ }else{
+ S3JniHook_localdup(&SJG.autoExt.aExt[i], &ax);
+ }
+ S3JniAutoExt_mutex_leave;
+ if( ax.jObj ){
+ rc = (*env)->CallIntMethod(env, ax.jObj, ax.midCallback, ps->jDb);
+ S3JniHook_localundup(ax);
+ S3JniIfThrew {
+ jthrowable const ex = (*env)->ExceptionOccurred(env);
+ char * zMsg;
+ S3JniExceptionClear;
+ zMsg = s3jni_exception_error_msg(env, ex);
+ S3JniUnrefLocal(ex);
+ *pzErr = sqlite3_mprintf("auto-extension threw: %s", zMsg);
+ sqlite3_free(zMsg);
+ rc = SQLITE_ERROR;
+ }
+ }
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_auto_extension(),jint,1auto_1extension)(
+ JniArgsEnvClass, jobject jAutoExt
+){
+ int i;
+ S3JniAutoExtension * ax = 0;
+ int rc = 0;
+
+ if( !jAutoExt ) return SQLITE_MISUSE;
+ S3JniAutoExt_mutex_enter;
+ for( i = 0; i < SJG.autoExt.nExt; ++i ){
+ /* Look for a match. */
+ ax = &SJG.autoExt.aExt[i];
+ if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
+ /* same object, so this is a no-op. */
+ S3JniAutoExt_mutex_leave;
+ return 0;
+ }
+ }
+ if( i == SJG.autoExt.nExt ){
+ assert( SJG.autoExt.nExt <= SJG.autoExt.nAlloc );
+ if( SJG.autoExt.nExt == SJG.autoExt.nAlloc ){
+ /* Allocate another slot. */
+ unsigned n = 1 + SJG.autoExt.nAlloc;
+ S3JniAutoExtension * const aNew =
+ s3jni_realloc( SJG.autoExt.aExt, n * sizeof(*ax) );
+ if( !aNew ){
+ rc = SQLITE_NOMEM;
+ }else{
+ SJG.autoExt.aExt = aNew;
+ ++SJG.autoExt.nAlloc;
+ }
+ }
+ if( 0==rc ){
+ ax = &SJG.autoExt.aExt[SJG.autoExt.nExt];
+ rc = S3JniAutoExtension_init(env, ax, jAutoExt);
+ assert( rc ? (0==ax->jObj && 0==ax->midCallback)
+ : (0!=ax->jObj && 0!=ax->midCallback) );
+ }
+ }
+ if( 0==rc ){
+ static int once = 0;
+ if( 0==once && ++once ){
+ rc = sqlite3_auto_extension(
+ (void(*)(void))s3jni_run_java_auto_extensions
+ /* Reminder: the JNI binding of sqlite3_reset_auto_extension()
+ ** does not call the core-lib impl. It only clears Java-side
+ ** auto-extensions. */
+ );
+ if( rc ){
+ assert( ax );
+ S3JniAutoExtension_clear(ax);
+ }
+ }
+ if( 0==rc ){
+ ++SJG.autoExt.nExt;
+ }
+ }
+ S3JniAutoExt_mutex_leave;
+ return rc;
+}
+
+S3JniApi(sqlite3_backup_finish(),jint,1backup_1finish)(
+ JniArgsEnvClass, jlong jpBack
+){
+ int rc = 0;
+ if( jpBack!=0 ){
+ rc = sqlite3_backup_finish( S3JniLongPtr_sqlite3_backup(jpBack) );
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_backup_init(),jobject,1backup_1init)(
+ JniArgsEnvClass, jlong jpDbDest, jstring jTDest,
+ jlong jpDbSrc, jstring jTSrc
+){
+ sqlite3 * const pDest = S3JniLongPtr_sqlite3(jpDbDest);
+ sqlite3 * const pSrc = S3JniLongPtr_sqlite3(jpDbSrc);
+ char * const zDest = s3jni_jstring_to_utf8(jTDest, 0);
+ char * const zSrc = s3jni_jstring_to_utf8(jTSrc, 0);
+ jobject rv = 0;
+
+ if( pDest && pSrc && zDest && zSrc ){
+ sqlite3_backup * const pB =
+ sqlite3_backup_init(pDest, zDest, pSrc, zSrc);
+ if( pB ){
+ rv = new_java_sqlite3_backup(env, pB);
+ if( !rv ){
+ sqlite3_backup_finish( pB );
+ }
+ }
+ }
+ sqlite3_free(zDest);
+ sqlite3_free(zSrc);
+ return rv;
+}
+
+S3JniApi(sqlite3_backup_pagecount(),jint,1backup_1pagecount)(
+ JniArgsEnvClass, jlong jpBack
+){
+ return sqlite3_backup_pagecount(S3JniLongPtr_sqlite3_backup(jpBack));
+}
+
+S3JniApi(sqlite3_backup_remaining(),jint,1backup_1remaining)(
+ JniArgsEnvClass, jlong jpBack
+){
+ return sqlite3_backup_remaining(S3JniLongPtr_sqlite3_backup(jpBack));
+}
+
+S3JniApi(sqlite3_backup_step(),jint,1backup_1step)(
+ JniArgsEnvClass, jlong jpBack, jint nPage
+){
+ return sqlite3_backup_step(S3JniLongPtr_sqlite3_backup(jpBack), (int)nPage);
+}
+
+S3JniApi(sqlite3_bind_blob(),jint,1bind_1blob)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jbyteArray baData, jint nMax
+){
+ jsize nBA = 0;
+ jbyte * const pBuf = baData ? s3jni_jbyteArray_bytes2(baData, &nBA) : 0;
+ int rc;
+ if( pBuf ){
+ if( nMax>nBA ){
+ nMax = nBA;
+ }
+ rc = sqlite3_bind_blob(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx,
+ pBuf, (int)nMax, SQLITE_TRANSIENT);
+ s3jni_jbyteArray_release(baData, pBuf);
+ }else{
+ rc = baData
+ ? SQLITE_NOMEM
+ : sqlite3_bind_null( S3JniLongPtr_sqlite3_stmt(jpStmt), ndx );
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_bind_double(),jint,1bind_1double)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jdouble val
+){
+ return (jint)sqlite3_bind_double(S3JniLongPtr_sqlite3_stmt(jpStmt),
+ (int)ndx, (double)val);
+}
+
+S3JniApi(sqlite3_bind_int(),jint,1bind_1int)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jint val
+){
+ return (jint)sqlite3_bind_int(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, (int)val);
+}
+
+S3JniApi(sqlite3_bind_int64(),jint,1bind_1int64)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jlong val
+){
+ return (jint)sqlite3_bind_int64(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val);
+}
+
+/*
+** Bind a new global ref to Object `val` using sqlite3_bind_pointer().
+*/
+S3JniApi(sqlite3_bind_java_object(),jint,1bind_1java_1object)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jobject val
+){
+ sqlite3_stmt * const pStmt = S3JniLongPtr_sqlite3_stmt(jpStmt);
+ int rc = SQLITE_MISUSE;
+
+ if(pStmt){
+ jobject const rv = S3JniRefGlobal(val);
+ if( rv ){
+ rc = sqlite3_bind_pointer(pStmt, ndx, rv, ResultJavaValuePtrStr,
+ S3Jni_jobject_finalizer);
+ }else if(val){
+ rc = SQLITE_NOMEM;
+ }else{
+ rc = sqlite3_bind_null(pStmt, ndx);
+ }
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_bind_null(),jint,1bind_1null)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx
+){
+ return (jint)sqlite3_bind_null(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx);
+}
+
+S3JniApi(sqlite3_bind_parameter_count(),jint,1bind_1parameter_1count)(
+ JniArgsEnvClass, jlong jpStmt
+){
+ return (jint)sqlite3_bind_parameter_count(S3JniLongPtr_sqlite3_stmt(jpStmt));
+}
+
+S3JniApi(sqlite3_bind_parameter_index(),jint,1bind_1parameter_1index)(
+ JniArgsEnvClass, jlong jpStmt, jbyteArray jName
+){
+ int rc = 0;
+ jbyte * const pBuf = s3jni_jbyteArray_bytes(jName);
+ if( pBuf ){
+ rc = sqlite3_bind_parameter_index(S3JniLongPtr_sqlite3_stmt(jpStmt),
+ (const char *)pBuf);
+ s3jni_jbyteArray_release(jName, pBuf);
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_bind_parameter_name(),jstring,1bind_1parameter_1name)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx
+){
+ const char *z =
+ sqlite3_bind_parameter_name(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx);
+ return z ? s3jni_utf8_to_jstring(z, -1) : 0;
+}
+
+/*
+** Impl of sqlite3_bind_text/text16().
+*/
+static int s3jni__bind_text(int is16, JNIEnv *env, jlong jpStmt, jint ndx,
+ jbyteArray baData, jint nMax){
+ jsize nBA = 0;
+ jbyte * const pBuf =
+ baData ? s3jni_jbyteArray_bytes2(baData, &nBA) : 0;
+ int rc;
+ if( pBuf ){
+ if( nMax>nBA ){
+ nMax = nBA;
+ }
+ /* Note that we rely on the Java layer having assured that baData
+ is NUL-terminated if nMax is negative. In order to avoid UB for
+ such cases, we do not expose the byte-limit arguments in the
+ public API. */
+ rc = is16
+ ? sqlite3_bind_text16(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx,
+ pBuf, (int)nMax, SQLITE_TRANSIENT)
+ : sqlite3_bind_text(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx,
+ (const char *)pBuf,
+ (int)nMax, SQLITE_TRANSIENT);
+ }else{
+ rc = baData
+ ? sqlite3_bind_null(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx)
+ : SQLITE_NOMEM;
+ }
+ s3jni_jbyteArray_release(baData, pBuf);
+ return (jint)rc;
+
+}
+
+S3JniApi(sqlite3_bind_text(),jint,1bind_1text)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jbyteArray baData, jint nMax
+){
+ return s3jni__bind_text(0, env, jpStmt, ndx, baData, nMax);
+}
+
+S3JniApi(sqlite3_bind_text16(),jint,1bind_1text16)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jbyteArray baData, jint nMax
+){
+ return s3jni__bind_text(1, env, jpStmt, ndx, baData, nMax);
+}
+
+S3JniApi(sqlite3_bind_value(),jint,1bind_1value)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jlong jpValue
+){
+ int rc = 0;
+ sqlite3_stmt * pStmt = S3JniLongPtr_sqlite3_stmt(jpStmt);
+ if( pStmt ){
+ sqlite3_value *v = S3JniLongPtr_sqlite3_value(jpValue);
+ if( v ){
+ rc = sqlite3_bind_value(pStmt, (int)ndx, v);
+ }else{
+ rc = sqlite3_bind_null(pStmt, (int)ndx);
+ }
+ }else{
+ rc = SQLITE_MISUSE;
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_bind_zeroblob(),jint,1bind_1zeroblob)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jint n
+){
+ return (jint)sqlite3_bind_zeroblob(S3JniLongPtr_sqlite3_stmt(jpStmt),
+ (int)ndx, (int)n);
+}
+
+S3JniApi(sqlite3_bind_zeroblob64(),jint,1bind_1zeroblob64)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jlong n
+){
+ return (jint)sqlite3_bind_zeroblob64(S3JniLongPtr_sqlite3_stmt(jpStmt),
+ (int)ndx, (sqlite3_uint64)n);
+}
+
+S3JniApi(sqlite3_blob_bytes(),jint,1blob_1bytes)(
+ JniArgsEnvClass, jlong jpBlob
+){
+ return sqlite3_blob_bytes(S3JniLongPtr_sqlite3_blob(jpBlob));
+}
+
+S3JniApi(sqlite3_blob_close(),jint,1blob_1close)(
+ JniArgsEnvClass, jlong jpBlob
+){
+ sqlite3_blob * const b = S3JniLongPtr_sqlite3_blob(jpBlob);
+ return b ? (jint)sqlite3_blob_close(b) : SQLITE_MISUSE;
+}
+
+S3JniApi(sqlite3_blob_open(),jint,1blob_1open)(
+ JniArgsEnvClass, jlong jpDb, jstring jDbName, jstring jTbl, jstring jCol,
+ jlong jRowId, jint flags, jobject jOut
+){
+ sqlite3 * const db = S3JniLongPtr_sqlite3(jpDb);
+ sqlite3_blob * pBlob = 0;
+ char * zDbName = 0, * zTableName = 0, * zColumnName = 0;
+ int rc;
+
+ if( !db || !jDbName || !jTbl || !jCol ) return SQLITE_MISUSE;
+ zDbName = s3jni_jstring_to_utf8(jDbName,0);
+ zTableName = zDbName ? s3jni_jstring_to_utf8(jTbl,0) : 0;
+ zColumnName = zTableName ? s3jni_jstring_to_utf8(jCol,0) : 0;
+ rc = zColumnName
+ ? sqlite3_blob_open(db, zDbName, zTableName, zColumnName,
+ (sqlite3_int64)jRowId, (int)flags, &pBlob)
+ : SQLITE_NOMEM;
+ if( 0==rc ){
+ jobject rv = new_java_sqlite3_blob(env, pBlob);
+ if( !rv ){
+ sqlite3_blob_close(pBlob);
+ rc = SQLITE_NOMEM;
+ }
+ OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_blob), jOut, rv);
+ }
+ sqlite3_free(zDbName);
+ sqlite3_free(zTableName);
+ sqlite3_free(zColumnName);
+ return rc;
+}
+
+S3JniApi(sqlite3_blob_read(),jint,1blob_1read)(
+ JniArgsEnvClass, jlong jpBlob, jbyteArray jTgt, jint iOffset
+){
+ jbyte * const pBa = s3jni_jbyteArray_bytes(jTgt);
+ int rc = jTgt ? (pBa ? SQLITE_MISUSE : SQLITE_NOMEM) : SQLITE_MISUSE;
+ if( pBa ){
+ jsize const nTgt = (*env)->GetArrayLength(env, jTgt);
+ rc = sqlite3_blob_read(S3JniLongPtr_sqlite3_blob(jpBlob), pBa,
+ (int)nTgt, (int)iOffset);
+ if( 0==rc ){
+ s3jni_jbyteArray_commit(jTgt, pBa);
+ }else{
+ s3jni_jbyteArray_release(jTgt, pBa);
+ }
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_blob_reopen(),jint,1blob_1reopen)(
+ JniArgsEnvClass, jlong jpBlob, jlong iNewRowId
+){
+ return (jint)sqlite3_blob_reopen(S3JniLongPtr_sqlite3_blob(jpBlob),
+ (sqlite3_int64)iNewRowId);
+}
+
+S3JniApi(sqlite3_blob_write(),jint,1blob_1write)(
+ JniArgsEnvClass, jlong jpBlob, jbyteArray jBa, jint iOffset
+){
+ sqlite3_blob * const b = S3JniLongPtr_sqlite3_blob(jpBlob);
+ jbyte * const pBuf = b ? s3jni_jbyteArray_bytes(jBa) : 0;
+ const jsize nBA = pBuf ? (*env)->GetArrayLength(env, jBa) : 0;
+ int rc = SQLITE_MISUSE;
+ if(b && pBuf){
+ rc = sqlite3_blob_write( b, pBuf, (int)nBA, (int)iOffset );
+ }
+ s3jni_jbyteArray_release(jBa, pBuf);
+ return (jint)rc;
+}
+
+/* Central C-to-Java busy handler proxy. */
+static int s3jni_busy_handler(void* pState, int n){
+ S3JniDb * const ps = (S3JniDb *)pState;
+ int rc = 0;
+ S3JniDeclLocal_env;
+ S3JniHook hook;
+
+ S3JniHook_localdup(&ps->hooks.busyHandler, &hook);
+ if( hook.jObj ){
+ rc = (*env)->CallIntMethod(env, hook.jObj,
+ hook.midCallback, (jint)n);
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("sqlite3_busy_handler() callback");
+ rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR,
+ "sqlite3_busy_handler() callback threw.");
+ }
+ S3JniHook_localundup(hook);
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_busy_handler(),jint,1busy_1handler)(
+ JniArgsEnvClass, jlong jpDb, jobject jBusy
+){
+ S3JniDb * const ps = S3JniDb_from_jlong(jpDb);
+ S3JniHook * const pHook = ps ? &ps->hooks.busyHandler : 0;
+ S3JniHook hook = S3JniHook_empty;
+ int rc = 0;
+
+ if( !ps ) return (jint)SQLITE_MISUSE;
+ S3JniDb_mutex_enter;
+ if( jBusy ){
+ if( pHook->jObj && (*env)->IsSameObject(env, pHook->jObj, jBusy) ){
+ /* Same object - this is a no-op. */
+ }else{
+ jclass const klazz = (*env)->GetObjectClass(env, jBusy);
+ hook.jObj = S3JniRefGlobal(jBusy);
+ hook.midCallback = (*env)->GetMethodID(env, klazz, "call", "(I)I");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ rc = SQLITE_ERROR;
+ }
+ }
+ }
+ if( 0==rc ){
+ if( jBusy ){
+ if( hook.jObj ){ /* Replace handler */
+ rc = sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps);
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ *pHook = hook /* transfer Java ref ownership */;
+ hook = S3JniHook_empty;
+ }
+ }/* else no-op */
+ }else{ /* Clear handler */
+ rc = sqlite3_busy_handler(ps->pDb, 0, 0);
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ }
+ }
+ }
+ S3JniHook_unref(&hook);
+ S3JniDb_mutex_leave;
+ return rc;
+}
+
+S3JniApi(sqlite3_busy_timeout(),jint,1busy_1timeout)(
+ JniArgsEnvClass, jlong jpDb, jint ms
+){
+ S3JniDb * const ps = S3JniDb_from_jlong(jpDb);
+ int rc = SQLITE_MISUSE;
+ if( ps ){
+ S3JniDb_mutex_enter;
+ S3JniHook_unref(&ps->hooks.busyHandler);
+ rc = sqlite3_busy_timeout(ps->pDb, (int)ms);
+ S3JniDb_mutex_leave;
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_cancel_auto_extension(),jboolean,1cancel_1auto_1extension)(
+ JniArgsEnvClass, jobject jAutoExt
+){
+ S3JniAutoExtension * ax;
+ jboolean rc = JNI_FALSE;
+ int i;
+
+ if( !jAutoExt ){
+ return rc;
+ }
+ S3JniAutoExt_mutex_enter;
+ /* This algo corresponds to the one in the core. */
+ for( i = SJG.autoExt.nExt-1; i >= 0; --i ){
+ ax = &SJG.autoExt.aExt[i];
+ if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
+ S3JniAutoExtension_clear(ax);
+ /* Move final entry into this slot. */
+ --SJG.autoExt.nExt;
+ *ax = SJG.autoExt.aExt[SJG.autoExt.nExt];
+ SJG.autoExt.aExt[SJG.autoExt.nExt] = S3JniHook_empty;
+ assert( !SJG.autoExt.aExt[SJG.autoExt.nExt].jObj );
+ rc = JNI_TRUE;
+ break;
+ }
+ }
+ S3JniAutoExt_mutex_leave;
+ return rc;
+}
+
+/* Wrapper for sqlite3_close(_v2)(). */
+static jint s3jni_close_db(JNIEnv * const env, jlong jpDb, int version){
+ int rc = 0;
+ S3JniDb * const ps = S3JniDb_from_jlong(jpDb);
+
+ assert(version == 1 || version == 2);
+ if( ps ){
+ rc = 1==version
+ ? (jint)sqlite3_close(ps->pDb)
+ : (jint)sqlite3_close_v2(ps->pDb);
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_close(),jint,1close)(JniArgsEnvClass, jlong pDb){
+ return s3jni_close_db(env, pDb, 1);
+}
+
+S3JniApi(sqlite3_close_v2(),jint,1close_1v2)(JniArgsEnvClass, jlong pDb){
+ return s3jni_close_db(env, pDb, 2);
+}
+
+/*
+** Assumes z is an array of unsigned short and returns the index in
+** that array of the first element with the value 0.
+*/
+static unsigned int s3jni_utf16_strlen(void const * z){
+ unsigned int i = 0;
+ const unsigned short * p = z;
+ while( p[i] ) ++i;
+ return i;
+}
+
+/* Descriptive alias for use with sqlite3_collation_needed(). */
+typedef S3JniHook S3JniCollationNeeded;
+
+/* Central C-to-Java sqlite3_collation_needed16() hook impl. */
+static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb,
+ int eTextRep, const void * z16Name){
+ S3JniCollationNeeded * const pHook = pState;
+ S3JniDeclLocal_env;
+ S3JniHook hook;
+
+ S3JniHook_localdup(pHook, &hook);
+ if( hook.jObj ){
+ unsigned int const nName = s3jni_utf16_strlen(z16Name);
+ jstring jName = (*env)->NewString(env, (jchar const *)z16Name, nName);
+
+ s3jni_oom_check( jName );
+ assert( hook.jExtra );
+ S3JniIfThrew{
+ S3JniExceptionClear;
+ }else if( hook.jExtra ){
+ (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback,
+ hook.jExtra, (jint)eTextRep, jName);
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("sqlite3_collation_needed() callback");
+ }
+ }
+ S3JniUnrefLocal(jName);
+ S3JniHook_localundup(hook);
+ }
+}
+
+S3JniApi(sqlite3_collation_needed(),jint,1collation_1needed)(
+ JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+ S3JniDb * ps;
+ S3JniCollationNeeded * pHook;
+ int rc = 0;
+
+ S3JniDb_mutex_enter;
+ ps = S3JniDb_from_jlong(jpDb);
+ if( !ps ){
+ S3JniDb_mutex_leave;
+ return SQLITE_MISUSE;
+ }
+ pHook = &ps->hooks.collationNeeded;
+ if( pHook->jObj && jHook &&
+ (*env)->IsSameObject(env, pHook->jObj, jHook) ){
+ /* no-op */
+ }else if( !jHook ){
+ rc = sqlite3_collation_needed(ps->pDb, 0, 0);
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ }
+ }else{
+ jclass const klazz = (*env)->GetObjectClass(env, jHook);
+ jmethodID const xCallback = (*env)->GetMethodID(
+ env, klazz, "call", "(Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)I"
+ );
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ rc = s3jni_db_exception(ps->pDb, SQLITE_MISUSE,
+ "Cannot not find matching call() in "
+ "CollationNeededCallback object.");
+ }else{
+ rc = sqlite3_collation_needed16(ps->pDb, pHook,
+ s3jni_collation_needed_impl16);
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ pHook->midCallback = xCallback;
+ pHook->jObj = S3JniRefGlobal(jHook);
+ pHook->jExtra = S3JniRefGlobal(ps->jDb);
+ }
+ }
+ }
+ S3JniDb_mutex_leave;
+ return rc;
+}
+
+S3JniApi(sqlite3_column_blob(),jbyteArray,1column_1blob)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+ void const * const p = sqlite3_column_blob(pStmt, (int)ndx);
+ int const n = p ? sqlite3_column_bytes(pStmt, (int)ndx) : 0;
+
+ return p ? s3jni_new_jbyteArray(p, n) : 0;
+}
+
+S3JniApi(sqlite3_column_double(),jdouble,1column_1double)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ return (jdouble)sqlite3_column_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
+}
+
+S3JniApi(sqlite3_column_int(),jint,1column_1int)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ return (jint)sqlite3_column_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
+}
+
+S3JniApi(sqlite3_column_int64(),jlong,1column_1int64)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
+}
+
+S3JniApi(sqlite3_column_text(),jbyteArray,1column_1text)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt);
+ const unsigned char * const p = stmt ? sqlite3_column_text(stmt, (int)ndx) : 0;
+ const int n = p ? sqlite3_column_bytes(stmt, (int)ndx) : 0;
+ return p ? s3jni_new_jbyteArray(p, n) : NULL;
+}
+
+#if 0
+// this impl might prove useful.
+S3JniApi(sqlite3_column_text(),jstring,1column_1text)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt);
+ const unsigned char * const p = stmt ? sqlite3_column_text(stmt, (int)ndx) : 0;
+ const int n = p ? sqlite3_column_bytes(stmt, (int)ndx) : 0;
+ return p ? s3jni_utf8_to_jstring( (const char *)p, n) : 0;
+}
+#endif
+
+S3JniApi(sqlite3_column_text16(),jstring,1column_1text16)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt);
+ const void * const p = stmt ? sqlite3_column_text16(stmt, (int)ndx) : 0;
+ const int n = p ? sqlite3_column_bytes16(stmt, (int)ndx) : 0;
+ return s3jni_text16_to_jstring(env, p, n);
+}
+
+S3JniApi(sqlite3_column_value(),jobject,1column_1value)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ sqlite3_value * const sv =
+ sqlite3_column_value(PtrGet_sqlite3_stmt(jpStmt), (int)ndx)
+ /* reminder: returns an SQL NULL if jpStmt==NULL */;
+ return new_java_sqlite3_value(env, sv);
+}
+
+/*
+** Impl for commit hooks (if isCommit is true) or rollback hooks.
+*/
+static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){
+ S3JniDeclLocal_env;
+ int rc = 0;
+ S3JniHook hook;
+
+ S3JniHook_localdup(isCommit
+ ? &ps->hooks.commit : &ps->hooks.rollback,
+ &hook);
+ if( hook.jObj ){
+ rc = isCommit
+ ? (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback)
+ : (int)((*env)->CallVoidMethod(env, hook.jObj, hook.midCallback), 0);
+ S3JniIfThrew{
+ rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR, "hook callback threw");
+ }
+ S3JniHook_localundup(hook);
+ }
+ return rc;
+}
+
+/* C-to-Java commit hook wrapper. */
+static int s3jni_commit_hook_impl(void *pP){
+ return s3jni_commit_rollback_hook_impl(1, pP);
+}
+
+/* C-to-Java rollback hook wrapper. */
+static void s3jni_rollback_hook_impl(void *pP){
+ (void)s3jni_commit_rollback_hook_impl(0, pP);
+}
+
+/*
+** Proxy for sqlite3_commit_hook() (if isCommit is true) or
+** sqlite3_rollback_hook().
+*/
+static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,
+ jlong jpDb, jobject jHook){
+ S3JniDb * ps;
+ jobject pOld = 0; /* previous hoook */
+ S3JniHook * pHook; /* ps->hooks.commit|rollback */
+
+ S3JniDb_mutex_enter;
+ ps = S3JniDb_from_jlong(jpDb);
+ if( !ps ){
+ s3jni_db_error(ps->pDb, SQLITE_MISUSE, 0);
+ S3JniDb_mutex_leave;
+ return 0;
+ }
+ pHook = isCommit ? &ps->hooks.commit : &ps->hooks.rollback;
+ pOld = pHook->jObj;
+ if( pOld && jHook &&
+ (*env)->IsSameObject(env, pOld, jHook) ){
+ /* No-op. */
+ }else if( !jHook ){
+ if( pOld ){
+ jobject tmp = S3JniRefLocal(pOld);
+ S3JniUnrefGlobal(pOld);
+ pOld = tmp;
+ }
+ *pHook = S3JniHook_empty;
+ if( isCommit ) sqlite3_commit_hook(ps->pDb, 0, 0);
+ else sqlite3_rollback_hook(ps->pDb, 0, 0);
+ }else{
+ jclass const klazz = (*env)->GetObjectClass(env, jHook);
+ jmethodID const xCallback = (*env)->GetMethodID(env, klazz, "call",
+ isCommit ? "()I" : "()V");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ "Cannot not find matching call() method in"
+ "hook object.");
+ }else{
+ pHook->midCallback = xCallback;
+ pHook->jObj = S3JniRefGlobal(jHook);
+ if( isCommit ) sqlite3_commit_hook(ps->pDb, s3jni_commit_hook_impl, ps);
+ else sqlite3_rollback_hook(ps->pDb, s3jni_rollback_hook_impl, ps);
+ if( pOld ){
+ jobject tmp = S3JniRefLocal(pOld);
+ S3JniUnrefGlobal(pOld);
+ pOld = tmp;
+ }
+ }
+ }
+ S3JniDb_mutex_leave;
+ return pOld;
+}
+
+S3JniApi(sqlite3_commit_hook(),jobject,1commit_1hook)(
+ JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+ return s3jni_commit_rollback_hook(1, env, jpDb, jHook);
+}
+
+S3JniApi(sqlite3_compileoption_get(),jstring,1compileoption_1get)(
+ JniArgsEnvClass, jint n
+){
+ const char * z = sqlite3_compileoption_get(n);
+ jstring const rv = z ? (*env)->NewStringUTF( env, z ) : 0;
+ /* We know these to be ASCII, so MUTF-8 is fine. */;
+ s3jni_oom_check(z ? !!rv : 1);
+ return rv;
+}
+
+S3JniApi(sqlite3_compileoption_used(),jboolean,1compileoption_1used)(
+ JniArgsEnvClass, jstring name
+){
+ const char *zUtf8 = s3jni_jstring_to_mutf8(name)
+ /* We know these to be ASCII, so MUTF-8 is fine (and
+ hypothetically faster to convert). */;
+ const jboolean rc =
+ 0==sqlite3_compileoption_used(zUtf8) ? JNI_FALSE : JNI_TRUE;
+ s3jni_mutf8_release(name, zUtf8);
+ return rc;
+}
+
+S3JniApi(sqlite3_complete(),int,1complete)(
+ JniArgsEnvClass, jbyteArray jSql
+){
+ jbyte * const pBuf = s3jni_jbyteArray_bytes(jSql);
+ const jsize nBA = pBuf ? (*env)->GetArrayLength(env, jSql) : 0;
+ int rc;
+
+ assert( (nBA>0 ? 0==pBuf[nBA-1] : (pBuf ? 0==*pBuf : 1))
+ && "Byte array is not NUL-terminated." );
+ rc = (pBuf && 0==pBuf[(nBA ? nBA-1 : 0)])
+ ? sqlite3_complete( (const char *)pBuf )
+ : (jSql ? SQLITE_NOMEM : SQLITE_MISUSE);
+ s3jni_jbyteArray_release(jSql, pBuf);
+ return rc;
+}
+
+S3JniApi(sqlite3_config() /*for a small subset of options.*/,
+ jint,1config__I)(JniArgsEnvClass, jint n){
+ switch( n ){
+ case SQLITE_CONFIG_SINGLETHREAD:
+ case SQLITE_CONFIG_MULTITHREAD:
+ case SQLITE_CONFIG_SERIALIZED:
+ return sqlite3_config( n );
+ default:
+ return SQLITE_MISUSE;
+ }
+}
+/* C-to-Java SQLITE_CONFIG_LOG wrapper. */
+static void s3jni_config_log(void *ignored, int errCode, const char *z){
+ S3JniDeclLocal_env;
+ S3JniHook hook = S3JniHook_empty;
+
+ S3JniHook_localdup(&SJG.hook.configlog, &hook);
+ if( hook.jObj ){
+ jstring const jArg1 = z ? s3jni_utf8_to_jstring(z, -1) : 0;
+ if( z ? !!jArg1 : 1 ){
+ (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback, errCode, jArg1);
+ }
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("SQLITE_CONFIG_LOG callback");
+ S3JniExceptionClear;
+ }
+ S3JniHook_localundup(hook);
+ S3JniUnrefLocal(jArg1);
+ }
+}
+
+S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_LOG */,
+ jint, 1config__Lorg_sqlite_jni_ConfigLogCallback_2
+)(JniArgsEnvClass, jobject jLog){
+ S3JniHook * const pHook = &SJG.hook.configlog;
+ int rc = 0;
+
+ S3JniGlobal_mutex_enter;
+ if( !jLog ){
+ rc = sqlite3_config( SQLITE_CONFIG_LOG, NULL, NULL );
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ }
+ }else if( pHook->jObj && (*env)->IsSameObject(env, jLog, pHook->jObj) ){
+ /* No-op */
+ }else {
+ jclass const klazz = (*env)->GetObjectClass(env, jLog);
+ jmethodID const midCallback = (*env)->GetMethodID(env, klazz, "call",
+ "(ILjava/lang/String;)V");
+ S3JniUnrefLocal(klazz);
+ if( midCallback ){
+ rc = sqlite3_config( SQLITE_CONFIG_LOG, s3jni_config_log, NULL );
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ pHook->midCallback = midCallback;
+ pHook->jObj = S3JniRefGlobal(jLog);
+ }
+ }else{
+ S3JniExceptionWarnIgnore;
+ rc = SQLITE_ERROR;
+ }
+ }
+ S3JniGlobal_mutex_leave;
+ return rc;
+}
+
+#ifdef SQLITE_ENABLE_SQLLOG
+/* C-to-Java SQLITE_CONFIG_SQLLOG wrapper. */
+static void s3jni_config_sqllog(void *ignored, sqlite3 *pDb, const char *z, int op){
+ jobject jArg0 = 0;
+ jstring jArg1 = 0;
+ S3JniDeclLocal_env;
+ S3JniDb * const ps = S3JniDb_from_c(pDb);
+ S3JniHook hook = S3JniHook_empty;
+
+ if( ps ){
+ S3JniHook_localdup(&SJG.hook.sqllog, &hook);
+ }
+ if( !hook.jObj ) return;
+ jArg0 = S3JniRefLocal(ps->jDb);
+ switch( op ){
+ case 0: /* db opened */
+ case 1: /* SQL executed */
+ jArg1 = s3jni_utf8_to_jstring( z, -1);
+ break;
+ case 2: /* db closed */
+ break;
+ default:
+ (*env)->FatalError(env, "Unhandled 4th arg to SQLITE_CONFIG_SQLLOG.");
+ break;
+ }
+ (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback, jArg0, jArg1, op);
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("SQLITE_CONFIG_SQLLOG callback");
+ S3JniExceptionClear;
+ }
+ S3JniHook_localundup(hook);
+ S3JniUnrefLocal(jArg0);
+ S3JniUnrefLocal(jArg1);
+}
+//! Requirement of SQLITE_CONFIG_SQLLOG.
+void sqlite3_init_sqllog(void){
+ sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, 0 );
+}
+#endif
+
+S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_SQLLOG */,
+ jint, 1config__Lorg_sqlite_jni_ConfigSqllogCallback_2)(
+ JniArgsEnvClass, jobject jLog){
+#ifndef SQLITE_ENABLE_SQLLOG
+ return SQLITE_MISUSE;
+#else
+ S3JniHook * const pHook = &SJG.hook.sqllog;
+ int rc = 0;
+
+ S3JniGlobal_mutex_enter;
+ if( !jLog ){
+ rc = sqlite3_config( SQLITE_CONFIG_SQLLOG, NULL );
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ }
+ }else if( pHook->jObj && (*env)->IsSameObject(env, jLog, pHook->jObj) ){
+ /* No-op */
+ }else {
+ jclass const klazz = (*env)->GetObjectClass(env, jLog);
+ jmethodID const midCallback = (*env)->GetMethodID(env, klazz, "call",
+ "(Lorg/sqlite/jni/capi/sqlite3;"
+ "Ljava/lang/String;"
+ "I)V");
+ S3JniUnrefLocal(klazz);
+ if( midCallback ){
+ rc = sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, NULL );
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ pHook->midCallback = midCallback;
+ pHook->jObj = S3JniRefGlobal(jLog);
+ }
+ }else{
+ S3JniExceptionWarnIgnore;
+ rc = SQLITE_ERROR;
+ }
+ }
+ S3JniGlobal_mutex_leave;
+ return rc;
+#endif
+}
+
+S3JniApi(sqlite3_context_db_handle(),jobject,1context_1db_1handle)(
+ JniArgsEnvClass, jobject jpCx
+){
+ sqlite3_context * const pCx = PtrGet_sqlite3_context(jpCx);
+ sqlite3 * const pDb = pCx ? sqlite3_context_db_handle(pCx) : 0;
+ S3JniDb * const ps = pDb ? S3JniDb_from_c(pDb) : 0;
+ return ps ? ps->jDb : 0;
+}
+
+/*
+** State for CollationCallbacks. This used to be its own separate
+** type, but has since been consolidated with S3JniHook. It retains
+** its own typedef for code legibility and searchability reasons.
+*/
+typedef S3JniHook S3JniCollationCallback;
+
+/*
+** Proxy for Java-side CollationCallback.xCompare() callbacks.
+*/
+static int CollationCallback_xCompare(void *pArg, int nLhs, const void *lhs,
+ int nRhs, const void *rhs){
+ S3JniCollationCallback * const pCC = pArg;
+ S3JniDeclLocal_env;
+ jint rc = 0;
+ if( pCC->jObj ){
+ jbyteArray jbaLhs = s3jni_new_jbyteArray(lhs, (jint)nLhs);
+ jbyteArray jbaRhs = jbaLhs
+ ? s3jni_new_jbyteArray(rhs, (jint)nRhs) : 0;
+ if( !jbaRhs ){
+ S3JniUnrefLocal(jbaLhs);
+ /* We have no recovery strategy here. */
+ s3jni_oom_check( jbaRhs );
+ return 0;
+ }
+ rc = (*env)->CallIntMethod(env, pCC->jObj, pCC->midCallback,
+ jbaLhs, jbaRhs);
+ S3JniExceptionIgnore;
+ S3JniUnrefLocal(jbaLhs);
+ S3JniUnrefLocal(jbaRhs);
+ }
+ return (int)rc;
+}
+
+/* CollationCallback finalizer for use by the sqlite3 internals. */
+static void CollationCallback_xDestroy(void *pArg){
+ S3JniCollationCallback * const pCC = pArg;
+ S3JniDeclLocal_env;
+ S3JniHook_free(pCC);
+}
+
+S3JniApi(sqlite3_create_collation() sqlite3_create_collation_v2(),
+ jint,1create_1collation
+)(JniArgsEnvClass, jobject jDb, jstring name, jint eTextRep,
+ jobject oCollation){
+ int rc;
+ S3JniDb * ps;
+
+ if( !jDb || !name || !encodingTypeIsValid(eTextRep) ){
+ return (jint)SQLITE_MISUSE;
+ }
+ S3JniDb_mutex_enter;
+ ps = S3JniDb_from_java(jDb);
+ jclass const klazz = (*env)->GetObjectClass(env, oCollation);
+ jmethodID const midCallback =
+ (*env)->GetMethodID(env, klazz, "call", "([B[B)I");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew{
+ rc = s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ "Could not get call() method from "
+ "CollationCallback object.");
+ }else{
+ char * const zName = s3jni_jstring_to_utf8(name, 0);
+ S3JniCollationCallback * const pCC =
+ zName ? S3JniHook_alloc() : 0;
+ if( pCC ){
+ rc = sqlite3_create_collation_v2(ps->pDb, zName, (int)eTextRep,
+ pCC, CollationCallback_xCompare,
+ CollationCallback_xDestroy);
+ if( 0==rc ){
+ pCC->midCallback = midCallback;
+ pCC->jObj = S3JniRefGlobal(oCollation);
+ pCC->doXDestroy = 1;
+ }else{
+ CollationCallback_xDestroy(pCC);
+ }
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ sqlite3_free(zName);
+ }
+ S3JniDb_mutex_leave;
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_create_function() sqlite3_create_function_v2()
+ sqlite3_create_window_function(),
+ jint,1create_1function
+)(JniArgsEnvClass, jobject jDb, jstring jFuncName, jint nArg,
+ jint eTextRep, jobject jFunctor){
+ S3JniUdf * s = 0;
+ int rc;
+ sqlite3 * const pDb = PtrGet_sqlite3(jDb);
+ char * zFuncName = 0;
+
+ if( !pDb || !jFuncName ){
+ return SQLITE_MISUSE;
+ }else if( !encodingTypeIsValid(eTextRep) ){
+ return s3jni_db_error(pDb, SQLITE_FORMAT,
+ "Invalid function encoding option.");
+ }
+ s = S3JniUdf_alloc(env, jFunctor);
+ if( !s ) return SQLITE_NOMEM;
+
+ if( UDF_UNKNOWN_TYPE==s->type ){
+ rc = s3jni_db_error(pDb, SQLITE_MISUSE,
+ "Cannot unambiguously determine function type.");
+ S3JniUdf_free(env, s, 1);
+ goto error_cleanup;
+ }
+ zFuncName = s3jni_jstring_to_utf8(jFuncName,0);
+ if( !zFuncName ){
+ rc = SQLITE_NOMEM;
+ S3JniUdf_free(env, s, 1);
+ goto error_cleanup;
+ }
+ s->zFuncName = zFuncName /* pass on ownership */;
+ if( UDF_WINDOW == s->type ){
+ rc = sqlite3_create_window_function(pDb, zFuncName, nArg, eTextRep, s,
+ udf_xStep, udf_xFinal, udf_xValue,
+ udf_xInverse, S3JniUdf_finalizer);
+ }else{
+ udf_xFunc_f xFunc = 0;
+ udf_xStep_f xStep = 0;
+ udf_xFinal_f xFinal = 0;
+ if( UDF_SCALAR == s->type ){
+ xFunc = udf_xFunc;
+ }else{
+ assert( UDF_AGGREGATE == s->type );
+ xStep = udf_xStep;
+ xFinal = udf_xFinal;
+ }
+ rc = sqlite3_create_function_v2(pDb, zFuncName, nArg, eTextRep, s,
+ xFunc, xStep, xFinal, S3JniUdf_finalizer);
+ }
+error_cleanup:
+ /* Reminder: on sqlite3_create_function() error, s will be
+ ** destroyed via create_function(). */
+ return (jint)rc;
+}
+
+
+S3JniApi(sqlite3_db_config() /*for MAINDBNAME*/,
+ jint,1db_1config__Lorg_sqlite_jni_capi_sqlite3_2ILjava_lang_String_2
+)(JniArgsEnvClass, jobject jDb, jint op, jstring jStr){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ int rc;
+ char *zStr;
+
+ switch( (ps && jStr) ? op : 0 ){
+ case SQLITE_DBCONFIG_MAINDBNAME:
+ S3JniDb_mutex_enter
+ /* Protect against a race in modifying/freeing
+ ps->zMainDbName. */;
+ zStr = s3jni_jstring_to_utf8( jStr, 0);
+ if( zStr ){
+ rc = sqlite3_db_config(ps->pDb, (int)op, zStr);
+ if( rc ){
+ sqlite3_free( zStr );
+ }else{
+ sqlite3_free( ps->zMainDbName );
+ ps->zMainDbName = zStr;
+ }
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ S3JniDb_mutex_leave;
+ break;
+ case 0:
+ default:
+ rc = SQLITE_MISUSE;
+ }
+ return rc;
+}
+
+S3JniApi(
+ sqlite3_db_config(),
+ /* WARNING: openjdk v19 creates a different mangled name for this
+ ** function than openjdk v8 does. We account for that by exporting
+ ** both versions of the name. */
+ jint,1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_Int32_2
+)(
+ JniArgsEnvClass, jobject jDb, jint op, jint onOff, jobject jOut
+){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ int rc;
+ switch( ps ? op : 0 ){
+ case SQLITE_DBCONFIG_ENABLE_FKEY:
+ case SQLITE_DBCONFIG_ENABLE_TRIGGER:
+ case SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER:
+ case SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION:
+ case SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE:
+ case SQLITE_DBCONFIG_ENABLE_QPSG:
+ case SQLITE_DBCONFIG_TRIGGER_EQP:
+ case SQLITE_DBCONFIG_RESET_DATABASE:
+ case SQLITE_DBCONFIG_DEFENSIVE:
+ case SQLITE_DBCONFIG_WRITABLE_SCHEMA:
+ case SQLITE_DBCONFIG_LEGACY_ALTER_TABLE:
+ case SQLITE_DBCONFIG_DQS_DML:
+ case SQLITE_DBCONFIG_DQS_DDL:
+ case SQLITE_DBCONFIG_ENABLE_VIEW:
+ case SQLITE_DBCONFIG_LEGACY_FILE_FORMAT:
+ case SQLITE_DBCONFIG_TRUSTED_SCHEMA:
+ case SQLITE_DBCONFIG_STMT_SCANSTATUS:
+ case SQLITE_DBCONFIG_REVERSE_SCANORDER: {
+ int pOut = 0;
+ rc = sqlite3_db_config( ps->pDb, (int)op, onOff, &pOut );
+ if( 0==rc && jOut ){
+ OutputPointer_set_Int32(env, jOut, pOut);
+ }
+ break;
+ }
+ case 0:
+ default:
+ rc = SQLITE_MISUSE;
+ }
+ return (jint)rc;
+}
+
+/*
+** This is a workaround for openjdk v19 (and possibly others) encoding
+** this function's name differently than JDK v8 does. If we do not
+** install both names for this function then Java will not be able to
+** find the function in both environments.
+*/
+JniDecl(jint,1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_00024Int32_2)(
+ JniArgsEnvClass, jobject jDb, jint op, jint onOff, jobject jOut
+){
+ return JniFuncName(1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_Int32_2)(
+ env, jKlazz, jDb, op, onOff, jOut
+ );
+}
+
+S3JniApi(sqlite3_db_filename(),jstring,1db_1filename)(
+ JniArgsEnvClass, jobject jDb, jstring jDbName
+){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ char *zDbName;
+ jstring jRv = 0;
+ int nStr = 0;
+
+ if( !ps || !jDbName ){
+ return 0;
+ }
+ zDbName = s3jni_jstring_to_utf8( jDbName, &nStr);
+ if( zDbName ){
+ char const * zRv = sqlite3_db_filename(ps->pDb, zDbName);
+ sqlite3_free(zDbName);
+ if( zRv ){
+ jRv = s3jni_utf8_to_jstring( zRv, -1);
+ }
+ }
+ return jRv;
+}
+
+S3JniApi(sqlite3_db_handle(),jobject,1db_1handle)(
+ JniArgsEnvClass, jobject jpStmt
+){
+ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+ sqlite3 * const pDb = pStmt ? sqlite3_db_handle(pStmt) : 0;
+ S3JniDb * const ps = pDb ? S3JniDb_from_c(pDb) : 0;
+ return ps ? ps->jDb : 0;
+}
+
+S3JniApi(sqlite3_db_readonly(),jint,1db_1readonly)(
+ JniArgsEnvClass, jobject jDb, jstring jDbName
+){
+ int rc = 0;
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ char *zDbName = jDbName ? s3jni_jstring_to_utf8( jDbName, 0 ) : 0;
+ rc = sqlite3_db_readonly(ps ? ps->pDb : 0, zDbName);
+ sqlite3_free(zDbName);
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_db_release_memory(),int,1db_1release_1memory)(
+ JniArgsEnvClass, jobject jDb
+){
+ sqlite3 * const pDb = PtrGet_sqlite3(jDb);
+ return pDb ? sqlite3_db_release_memory(pDb) : SQLITE_MISUSE;
+}
+
+S3JniApi(sqlite3_db_status(),jint,1db_1status)(
+ JniArgsEnvClass, jobject jDb, jint op, jobject jOutCurrent,
+ jobject jOutHigh, jboolean reset
+){
+ int iCur = 0, iHigh = 0;
+ sqlite3 * const pDb = PtrGet_sqlite3(jDb);
+ int rc = sqlite3_db_status( pDb, op, &iCur, &iHigh, reset );
+ if( 0==rc ){
+ OutputPointer_set_Int32(env, jOutCurrent, iCur);
+ OutputPointer_set_Int32(env, jOutHigh, iHigh);
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_errcode(),jint,1errcode)(
+ JniArgsEnvClass, jobject jpDb
+){
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ return pDb ? sqlite3_errcode(pDb) : SQLITE_MISUSE;
+}
+
+S3JniApi(sqlite3_errmsg(),jstring,1errmsg)(
+ JniArgsEnvClass, jobject jpDb
+){
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ return pDb ? s3jni_utf8_to_jstring( sqlite3_errmsg(pDb), -1) : 0
+ /* We don't use errmsg16() directly only because it would cause an
+ additional level of internal encoding in sqlite3. The end
+ effect should be identical to using errmsg16(), however. */;
+}
+
+S3JniApi(sqlite3_errstr(),jstring,1errstr)(
+ JniArgsEnvClass, jint rcCode
+){
+ jstring const rv = (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode))
+ /* We know these values to be plain ASCII, so pose no MUTF-8
+ ** incompatibility */;
+ s3jni_oom_check( rv );
+ return rv;
+}
+
+#ifndef SQLITE_ENABLE_NORMALIZE
+/* Dummy stub for sqlite3_normalized_sql(). Never called. */
+static const char * sqlite3_normalized_sql(sqlite3_stmt *s){
+ S3JniDeclLocal_env;
+ (*env)->FatalError(env, "dummy sqlite3_normalized_sql() was "
+ "impossibly called.") /* does not return */;
+ return 0;
+}
+#endif
+
+/*
+** Impl for sqlite3_expanded_sql() (if isExpanded is true) and
+** sqlite3_normalized_sql().
+*/
+static jstring s3jni_xn_sql(int isExpanded, JNIEnv *env, jobject jpStmt){
+ jstring rv = 0;
+ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+
+ if( pStmt ){
+ char * zSql = isExpanded
+ ? sqlite3_expanded_sql(pStmt)
+ : (char*)sqlite3_normalized_sql(pStmt);
+ s3jni_oom_fatal(zSql);
+ if( zSql ){
+ rv = s3jni_utf8_to_jstring(zSql, -1);
+ if( isExpanded ) sqlite3_free(zSql);
+ }
+ }
+ return rv;
+}
+
+S3JniApi(sqlite3_expanded_sql(),jstring,1expanded_1sql)(
+ JniArgsEnvClass, jobject jpStmt
+){
+ return s3jni_xn_sql(1, env, jpStmt);
+}
+
+S3JniApi(sqlite3_normalized_sql(),jstring,1normalized_1sql)(
+ JniArgsEnvClass, jobject jpStmt
+){
+#ifdef SQLITE_ENABLE_NORMALIZE
+ return s3jni_xn_sql(0, env, jpStmt);
+#else
+ return 0;
+#endif
+}
+
+S3JniApi(sqlite3_extended_result_codes(),jboolean,1extended_1result_1codes)(
+ JniArgsEnvClass, jobject jpDb, jboolean onoff
+){
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ int const rc = pDb ? sqlite3_extended_result_codes(pDb, onoff ? 1 : 0) : 0;
+ return rc ? JNI_TRUE : JNI_FALSE;
+}
+
+S3JniApi(sqlite3_finalize(),jint,1finalize)(
+ JniArgsEnvClass, jlong jpStmt
+){
+ return jpStmt
+ ? sqlite3_finalize(S3JniLongPtr_sqlite3_stmt(jpStmt))
+ : 0;
+}
+
+S3JniApi(sqlite3_get_auxdata(),jobject,1get_1auxdata)(
+ JniArgsEnvClass, jobject jCx, jint n
+){
+ return sqlite3_get_auxdata(PtrGet_sqlite3_context(jCx), (int)n);
+}
+
+S3JniApi(sqlite3_initialize(),jint,1initialize)(
+ JniArgsEnvClass
+){
+ return sqlite3_initialize();
+}
+
+S3JniApi(sqlite3_interrupt(),void,1interrupt)(
+ JniArgsEnvClass, jobject jpDb
+){
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ if( pDb ){
+ sqlite3_interrupt(pDb);
+ }
+}
+
+S3JniApi(sqlite3_is_interrupted(),jboolean,1is_1interrupted)(
+ JniArgsEnvClass, jobject jpDb
+){
+ int rc = 0;
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ if( pDb ){
+ rc = sqlite3_is_interrupted(pDb);
+ }
+ return rc ? JNI_TRUE : JNI_FALSE;
+}
+
+/*
+** Uncaches the current JNIEnv from the S3JniGlobal state, clearing
+** any resources owned by that cache entry and making that slot
+** available for re-use.
+*/
+JniDecl(jboolean,1java_1uncache_1thread)(JniArgsEnvClass){
+ int rc;
+ S3JniEnv_mutex_enter;
+ rc = S3JniEnv_uncache(env);
+ S3JniEnv_mutex_leave;
+ return rc ? JNI_TRUE : JNI_FALSE;
+}
+
+S3JniApi(sqlite3_keyword_check(),jboolean,1keyword_1check)(
+ JniArgsEnvClass, jstring jWord
+){
+ int nWord = 0;
+ char * zWord = s3jni_jstring_to_utf8(jWord, &nWord);
+ int rc = 0;
+
+ s3jni_oom_check(jWord ? !!zWord : 1);
+ if( zWord && nWord ){
+ rc = sqlite3_keyword_check(zWord, nWord);
+ }
+ sqlite3_free(zWord);
+ return rc ? JNI_TRUE : JNI_FALSE;
+}
+
+S3JniApi(sqlite3_keyword_name(),jstring,1keyword_1name)(
+ JniArgsEnvClass, jint ndx
+){
+ const char * zWord = 0;
+ int n = 0;
+ jstring rv = 0;
+
+ if( 0==sqlite3_keyword_name(ndx, &zWord, &n) ){
+ rv = s3jni_utf8_to_jstring(zWord, n);
+ }
+ return rv;
+}
+
+
+S3JniApi(sqlite3_last_insert_rowid(),jlong,1last_1insert_1rowid)(
+ JniArgsEnvClass, jobject jpDb
+){
+ return (jlong)sqlite3_last_insert_rowid(PtrGet_sqlite3(jpDb));
+}
+
+S3JniApi(sqlite3_limit(),jint,1limit)(
+ JniArgsEnvClass, jobject jpDb, jint id, jint newVal
+){
+ jint rc = 0;
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ if( pDb ){
+ rc = sqlite3_limit( pDb, (int)id, (int)newVal );
+ }
+ return rc;
+}
+
+/* Pre-open() code common to sqlite3_open[_v2](). */
+static int s3jni_open_pre(JNIEnv * const env, S3JniEnv **jc,
+ jstring jDbName, char **zDbName,
+ S3JniDb ** ps){
+ int rc = 0;
+ jobject jDb = 0;
+
+ *jc = S3JniEnv_get();
+ if( !*jc ){
+ rc = SQLITE_NOMEM;
+ goto end;
+ }
+ *zDbName = jDbName ? s3jni_jstring_to_utf8( jDbName, 0) : 0;
+ if( jDbName && !*zDbName ){
+ rc = SQLITE_NOMEM;
+ goto end;
+ }
+ jDb = new_java_sqlite3(env, 0);
+ if( !jDb ){
+ sqlite3_free(*zDbName);
+ *zDbName = 0;
+ rc = SQLITE_NOMEM;
+ goto end;
+ }
+ *ps = S3JniDb_alloc(env, jDb);
+ if( *ps ){
+ (*jc)->pdbOpening = *ps;
+ }else{
+ S3JniUnrefLocal(jDb);
+ rc = SQLITE_NOMEM;
+ }
+end:
+ return rc;
+}
+
+/*
+** Post-open() code common to both the sqlite3_open() and
+** sqlite3_open_v2() bindings. ps->jDb must be the
+** org.sqlite.jni.capi.sqlite3 object which will hold the db's native
+** pointer. theRc must be the result code of the open() op. If
+** *ppDb is NULL then ps is set aside and its state cleared,
+** else ps is associated with *ppDb. If *ppDb is not NULL then
+** ps->jDb is stored in jOut (an OutputPointer.sqlite3 instance).
+**
+** Must be called if s3jni_open_pre() succeeds and must not be called
+** if it doesn't.
+**
+** Returns theRc.
+*/
+static int s3jni_open_post(JNIEnv * const env, S3JniEnv * const jc,
+ S3JniDb * ps, sqlite3 **ppDb,
+ jobject jOut, int theRc){
+ int rc = 0;
+ jc->pdbOpening = 0;
+ if( *ppDb ){
+ assert(ps->jDb);
+ if( 0==ps->pDb ){
+ ps->pDb = *ppDb;
+ NativePointerHolder_set(S3JniNph(sqlite3), ps->jDb, *ppDb);
+ }else{
+ assert( ps->pDb==*ppDb
+ && "Set up via s3jni_run_java_auto_extensions()" );
+ }
+ rc = sqlite3_set_clientdata(ps->pDb, S3JniDb_clientdata_key,
+ ps, S3JniDb_xDestroy)
+ /* As of here, the Java/C connection is complete */;
+ }else{
+ S3JniDb_set_aside(ps);
+ ps = 0;
+ }
+ OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3),
+ jOut, ps ? ps->jDb : 0);
+ return theRc ? theRc : rc;
+}
+
+S3JniApi(sqlite3_open(),jint,1open)(
+ JniArgsEnvClass, jstring strName, jobject jOut
+){
+ sqlite3 * pOut = 0;
+ char *zName = 0;
+ S3JniDb * ps = 0;
+ S3JniEnv * jc = 0;
+ int rc;
+
+ if( 0==jOut ) return SQLITE_MISUSE;
+ rc = s3jni_open_pre(env, &jc, strName, &zName, &ps);
+ if( 0==rc ){
+ rc = s3jni_open_post(env, jc, ps, &pOut, jOut,
+ sqlite3_open(zName, &pOut));
+ assert(rc==0 ? pOut!=0 : 1);
+ sqlite3_free(zName);
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_open_v2(),jint,1open_1v2)(
+ JniArgsEnvClass, jstring strName,
+ jobject jOut, jint flags, jstring strVfs
+){
+ sqlite3 * pOut = 0;
+ char *zName = 0;
+ S3JniDb * ps = 0;
+ S3JniEnv * jc = 0;
+ char *zVfs = 0;
+ int rc;
+
+ if( 0==jOut ) return SQLITE_MISUSE;
+ rc = s3jni_open_pre(env, &jc, strName, &zName, &ps);
+ if( 0==rc ){
+ if( strVfs ){
+ zVfs = s3jni_jstring_to_utf8( strVfs, 0);
+ if( !zVfs ){
+ rc = SQLITE_NOMEM;
+ }
+ }
+ if( 0==rc ){
+ rc = sqlite3_open_v2(zName, &pOut, (int)flags, zVfs);
+ }
+ rc = s3jni_open_post(env, jc, ps, &pOut, jOut, rc);
+ }
+ assert(rc==0 ? pOut!=0 : 1);
+ sqlite3_free(zName);
+ sqlite3_free(zVfs);
+ return (jint)rc;
+}
+
+/* Proxy for the sqlite3_prepare[_v2/3]() family. */
+jint sqlite3_jni_prepare_v123( int prepVersion, JNIEnv * const env, jclass self,
+ jlong jpDb, jbyteArray baSql,
+ jint nMax, jint prepFlags,
+ jobject jOutStmt, jobject outTail){
+ sqlite3_stmt * pStmt = 0;
+ jobject jStmt = 0;
+ const char * zTail = 0;
+ sqlite3 * const pDb = S3JniLongPtr_sqlite3(jpDb);
+ jbyte * const pBuf = pDb ? s3jni_jbyteArray_bytes(baSql) : 0;
+ int rc = SQLITE_ERROR;
+
+ assert(prepVersion==1 || prepVersion==2 || prepVersion==3);
+ if( !pDb || !jOutStmt ){
+ rc = SQLITE_MISUSE;
+ goto end;
+ }else if( !pBuf ){
+ rc = baSql ? SQLITE_NOMEM : SQLITE_MISUSE;
+ goto end;
+ }
+ jStmt = new_java_sqlite3_stmt(env, 0);
+ if( !jStmt ){
+ rc = SQLITE_NOMEM;
+ goto end;
+ }
+ switch( prepVersion ){
+ case 1: rc = sqlite3_prepare(pDb, (const char *)pBuf,
+ (int)nMax, &pStmt, &zTail);
+ break;
+ case 2: rc = sqlite3_prepare_v2(pDb, (const char *)pBuf,
+ (int)nMax, &pStmt, &zTail);
+ break;
+ case 3: rc = sqlite3_prepare_v3(pDb, (const char *)pBuf,
+ (int)nMax, (unsigned int)prepFlags,
+ &pStmt, &zTail);
+ break;
+ default:
+ assert(!"Invalid prepare() version");
+ }
+end:
+ s3jni_jbyteArray_release(baSql,pBuf);
+ if( 0==rc ){
+ if( 0!=outTail ){
+ /* Noting that pBuf is deallocated now but its address is all we need for
+ ** what follows... */
+ assert(zTail ? ((void*)zTail>=(void*)pBuf) : 1);
+ assert(zTail ? (((int)((void*)zTail - (void*)pBuf)) >= 0) : 1);
+ OutputPointer_set_Int32(
+ env, outTail, (int)(zTail ? (zTail - (const char *)pBuf) : 0)
+ );
+ }
+ if( pStmt ){
+ NativePointerHolder_set(S3JniNph(sqlite3_stmt), jStmt, pStmt);
+ }else{
+ /* Happens for comments and whitespace. */
+ S3JniUnrefLocal(jStmt);
+ jStmt = 0;
+ }
+ }else{
+ S3JniUnrefLocal(jStmt);
+ jStmt = 0;
+ }
+ if( jOutStmt ){
+ OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_stmt),
+ jOutStmt, jStmt);
+ }
+ return (jint)rc;
+}
+S3JniApi(sqlite3_prepare(),jint,1prepare)(
+ JNIEnv * const env, jclass self, jlong jpDb, jbyteArray baSql,
+ jint nMax, jobject jOutStmt, jobject outTail
+){
+ return sqlite3_jni_prepare_v123(1, env, self, jpDb, baSql, nMax, 0,
+ jOutStmt, outTail);
+}
+S3JniApi(sqlite3_prepare_v2(),jint,1prepare_1v2)(
+ JNIEnv * const env, jclass self, jlong jpDb, jbyteArray baSql,
+ jint nMax, jobject jOutStmt, jobject outTail
+){
+ return sqlite3_jni_prepare_v123(2, env, self, jpDb, baSql, nMax, 0,
+ jOutStmt, outTail);
+}
+S3JniApi(sqlite3_prepare_v3(),jint,1prepare_1v3)(
+ JNIEnv * const env, jclass self, jlong jpDb, jbyteArray baSql,
+ jint nMax, jint prepFlags, jobject jOutStmt, jobject outTail
+){
+ return sqlite3_jni_prepare_v123(3, env, self, jpDb, baSql, nMax,
+ prepFlags, jOutStmt, outTail);
+}
+
+/*
+** Impl for C-to-Java of the callbacks for both sqlite3_update_hook()
+** and sqlite3_preupdate_hook(). The differences are that for
+** update_hook():
+**
+** - pDb is NULL
+** - iKey1 is the row ID
+** - iKey2 is unused
+*/
+static void s3jni_updatepre_hook_impl(void * pState, sqlite3 *pDb, int opId,
+ const char *zDb, const char *zTable,
+ sqlite3_int64 iKey1, sqlite3_int64 iKey2){
+ S3JniDb * const ps = pState;
+ S3JniDeclLocal_env;
+ jstring jDbName;
+ jstring jTable;
+ const int isPre = 0!=pDb;
+ S3JniHook hook;
+
+ S3JniHook_localdup(isPre ?
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ &ps->hooks.preUpdate
+#else
+ &S3JniHook_empty
+#endif
+ : &ps->hooks.update, &hook);
+ if( !hook.jObj ){
+ return;
+ }
+ jDbName = s3jni_utf8_to_jstring( zDb, -1);
+ jTable = jDbName ? s3jni_utf8_to_jstring( zTable, -1) : 0;
+ S3JniIfThrew {
+ S3JniExceptionClear;
+ s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
+ }else{
+ assert( hook.jObj );
+ assert( hook.midCallback );
+ assert( ps->jDb );
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ if( isPre ) (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback,
+ ps->jDb, (jint)opId, jDbName, jTable,
+ (jlong)iKey1, (jlong)iKey2);
+ else
+#endif
+ (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback,
+ (jint)opId, jDbName, jTable, (jlong)iKey1);
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("sqlite3_(pre)update_hook() callback");
+ s3jni_db_exception(ps->pDb, 0,
+ "sqlite3_(pre)update_hook() callback threw");
+ }
+ }
+ S3JniUnrefLocal(jDbName);
+ S3JniUnrefLocal(jTable);
+ S3JniHook_localundup(hook);
+}
+
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+static void s3jni_preupdate_hook_impl(void * pState, sqlite3 *pDb, int opId,
+ const char *zDb, const char *zTable,
+ sqlite3_int64 iKey1, sqlite3_int64 iKey2){
+ return s3jni_updatepre_hook_impl(pState, pDb, opId, zDb, zTable,
+ iKey1, iKey2);
+}
+#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
+
+static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb,
+ const char *zTable, sqlite3_int64 nRowid){
+ return s3jni_updatepre_hook_impl(pState, NULL, opId, zDb, zTable, nRowid, 0);
+}
+
+#if !defined(SQLITE_ENABLE_PREUPDATE_HOOK)
+/* We need no-op impls for preupdate_{count,depth,blobwrite}() */
+S3JniApi(sqlite3_preupdate_blobwrite(),int,1preupdate_1blobwrite)(
+ JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; }
+S3JniApi(sqlite3_preupdate_count(),int,1preupdate_1count)(
+ JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; }
+S3JniApi(sqlite3_preupdate_depth(),int,1preupdate_1depth)(
+ JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; }
+#endif /* !SQLITE_ENABLE_PREUPDATE_HOOK */
+
+/*
+** JNI wrapper for both sqlite3_update_hook() and
+** sqlite3_preupdate_hook() (if isPre is true).
+*/
+static jobject s3jni_updatepre_hook(JNIEnv * env, int isPre, jlong jpDb, jobject jHook){
+ S3JniDb * const ps = S3JniDb_from_jlong(jpDb);
+ jclass klazz;
+ jobject pOld = 0;
+ jmethodID xCallback;
+ S3JniHook * pHook;
+
+ if( !ps ) return 0;
+ S3JniDb_mutex_enter;
+ pHook = isPre ?
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ &ps->hooks.preUpdate
+#else
+ 0
+#endif
+ : &ps->hooks.update;
+ if( !pHook ){
+ goto end;
+ }
+ pOld = pHook->jObj;
+ if( pOld && jHook && (*env)->IsSameObject(env, pOld, jHook) ){
+ goto end;
+ }
+ if( !jHook ){
+ if( pOld ){
+ jobject tmp = S3JniRefLocal(pOld);
+ S3JniUnrefGlobal(pOld);
+ pOld = tmp;
+ }
+ *pHook = S3JniHook_empty;
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ if( isPre ) sqlite3_preupdate_hook(ps->pDb, 0, 0);
+ else
+#endif
+ sqlite3_update_hook(ps->pDb, 0, 0);
+ goto end;
+ }
+ klazz = (*env)->GetObjectClass(env, jHook);
+ xCallback = isPre
+ ? (*env)->GetMethodID(env, klazz, "call",
+ "(Lorg/sqlite/jni/capi/sqlite3;"
+ "I"
+ "Ljava/lang/String;"
+ "Ljava/lang/String;"
+ "JJ)V")
+ : (*env)->GetMethodID(env, klazz, "call",
+ "(ILjava/lang/String;Ljava/lang/String;J)V");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ S3JniExceptionClear;
+ s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ "Cannot not find matching callback on "
+ "(pre)update hook object.");
+ }else{
+ pHook->midCallback = xCallback;
+ pHook->jObj = S3JniRefGlobal(jHook);
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ if( isPre ) sqlite3_preupdate_hook(ps->pDb, s3jni_preupdate_hook_impl, ps);
+ else
+#endif
+ sqlite3_update_hook(ps->pDb, s3jni_update_hook_impl, ps);
+ if( pOld ){
+ jobject tmp = S3JniRefLocal(pOld);
+ S3JniUnrefGlobal(pOld);
+ pOld = tmp;
+ }
+ }
+end:
+ S3JniDb_mutex_leave;
+ return pOld;
+}
+
+
+S3JniApi(sqlite3_preupdate_hook(),jobject,1preupdate_1hook)(
+ JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ return s3jni_updatepre_hook(env, 1, jpDb, jHook);
+#else
+ return NULL;
+#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
+}
+
+/* Impl for sqlite3_preupdate_{new,old}(). */
+static int s3jni_preupdate_newold(JNIEnv * const env, int isNew, jlong jpDb,
+ jint iCol, jobject jOut){
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ sqlite3 * const pDb = S3JniLongPtr_sqlite3(jpDb);
+ int rc = SQLITE_MISUSE;
+ if( pDb ){
+ sqlite3_value * pOut = 0;
+ int (*fOrig)(sqlite3*,int,sqlite3_value**) =
+ isNew ? sqlite3_preupdate_new : sqlite3_preupdate_old;
+ rc = fOrig(pDb, (int)iCol, &pOut);
+ if( 0==rc ){
+ jobject pWrap = new_java_sqlite3_value(env, pOut);
+ if( !pWrap ){
+ rc = SQLITE_NOMEM;
+ }
+ OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_value),
+ jOut, pWrap);
+ S3JniUnrefLocal(pWrap);
+ }
+ }
+ return rc;
+#else
+ return SQLITE_MISUSE;
+#endif
+}
+
+S3JniApi(sqlite3_preupdate_new(),jint,1preupdate_1new)(
+ JniArgsEnvClass, jlong jpDb, jint iCol, jobject jOut
+){
+ return s3jni_preupdate_newold(env, 1, jpDb, iCol, jOut);
+}
+
+S3JniApi(sqlite3_preupdate_old(),jint,1preupdate_1old)(
+ JniArgsEnvClass, jlong jpDb, jint iCol, jobject jOut
+){
+ return s3jni_preupdate_newold(env, 0, jpDb, iCol, jOut);
+}
+
+
+/* Central C-to-Java sqlite3_progress_handler() proxy. */
+static int s3jni_progress_handler_impl(void *pP){
+ S3JniDb * const ps = (S3JniDb *)pP;
+ int rc = 0;
+ S3JniDeclLocal_env;
+ S3JniHook hook;
+
+ S3JniHook_localdup(&ps->hooks.progress, &hook);
+ if( hook.jObj ){
+ rc = (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback);
+ S3JniIfThrew{
+ rc = s3jni_db_exception(ps->pDb, rc,
+ "sqlite3_progress_handler() callback threw");
+ }
+ S3JniHook_localundup(hook);
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_progress_handler(),void,1progress_1handler)(
+ JniArgsEnvClass,jobject jDb, jint n, jobject jProgress
+){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ S3JniHook * const pHook = ps ? &ps->hooks.progress : 0;
+
+ if( !ps ) return;
+ S3JniDb_mutex_enter;
+ if( n<1 || !jProgress ){
+ S3JniHook_unref(pHook);
+ sqlite3_progress_handler(ps->pDb, 0, 0, 0);
+ }else{
+ jclass const klazz = (*env)->GetObjectClass(env, jProgress);
+ jmethodID const xCallback = (*env)->GetMethodID(env, klazz, "call", "()I");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ S3JniExceptionClear;
+ s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ "Cannot not find matching xCallback() on "
+ "ProgressHandler object.");
+ }else{
+ S3JniUnrefGlobal(pHook->jObj);
+ pHook->midCallback = xCallback;
+ pHook->jObj = S3JniRefGlobal(jProgress);
+ sqlite3_progress_handler(ps->pDb, (int)n, s3jni_progress_handler_impl, ps);
+ }
+ }
+ S3JniDb_mutex_leave;
+}
+
+S3JniApi(sqlite3_randomness(),void,1randomness)(
+ JniArgsEnvClass, jbyteArray jTgt
+){
+ jbyte * const jba = s3jni_jbyteArray_bytes(jTgt);
+ if( jba ){
+ jsize const nTgt = (*env)->GetArrayLength(env, jTgt);
+ sqlite3_randomness( (int)nTgt, jba );
+ s3jni_jbyteArray_commit(jTgt, jba);
+ }
+}
+
+
+S3JniApi(sqlite3_reset(),jint,1reset)(
+ JniArgsEnvClass, jobject jpStmt
+){
+ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+ return pStmt ? sqlite3_reset(pStmt) : SQLITE_MISUSE;
+}
+
+/* Clears all entries from S3JniGlobal.autoExt. */
+static void s3jni_reset_auto_extension(JNIEnv *env){
+ int i;
+ S3JniAutoExt_mutex_enter;
+ for( i = 0; i < SJG.autoExt.nExt; ++i ){
+ S3JniAutoExtension_clear( &SJG.autoExt.aExt[i] );
+ }
+ SJG.autoExt.nExt = 0;
+ S3JniAutoExt_mutex_leave;
+}
+
+S3JniApi(sqlite3_reset_auto_extension(),void,1reset_1auto_1extension)(
+ JniArgsEnvClass
+){
+ s3jni_reset_auto_extension(env);
+}
+
+/* Impl for sqlite3_result_text/blob() and friends. */
+static void result_blob_text(int as64 /* true for text64/blob64() mode */,
+ int eTextRep /* 0 for blobs, else SQLITE_UTF... */,
+ JNIEnv * const env, sqlite3_context *pCx,
+ jbyteArray jBa, jlong nMax){
+ int const asBlob = 0==eTextRep;
+ if( !pCx ){
+ /* We should arguably emit a warning here. But where to log it? */
+ return;
+ }else if( jBa ){
+ jbyte * const pBuf = s3jni_jbyteArray_bytes(jBa);
+ jsize nBA = (*env)->GetArrayLength(env, jBa);
+ if( nMax>=0 && nBA>(jsize)nMax ){
+ nBA = (jsize)nMax;
+ /**
+ From the sqlite docs:
+
+ > If the 3rd parameter to any of the sqlite3_result_text*
+ interfaces other than sqlite3_result_text64() is negative,
+ then SQLite computes the string length itself by searching
+ the 2nd parameter for the first zero character.
+
+ Note that the text64() interfaces take an unsigned value for
+ the length, which Java does not support. This binding takes
+ the approach of passing on negative values to the C API,
+ which will in turn fail with SQLITE_TOOBIG at some later
+ point (recall that the sqlite3_result_xyz() family do not
+ have result values).
+ */
+ }
+ if( as64 ){ /* 64-bit... */
+ static const jsize nLimit64 =
+ SQLITE_MAX_ALLOCATION_SIZE/*only _kinda_ arbitrary*/;
+ if( nBA > nLimit64 ){
+ sqlite3_result_error_toobig(pCx);
+ }else if( asBlob ){
+ sqlite3_result_blob64(pCx, pBuf, (sqlite3_uint64)nBA,
+ SQLITE_TRANSIENT);
+ }else{ /* text64... */
+ if( encodingTypeIsValid(eTextRep) ){
+ sqlite3_result_text64(pCx, (const char *)pBuf,
+ (sqlite3_uint64)nBA,
+ SQLITE_TRANSIENT, eTextRep);
+ }else{
+ sqlite3_result_error_code(pCx, SQLITE_FORMAT);
+ }
+ }
+ }else{ /* 32-bit... */
+ static const jsize nLimit = SQLITE_MAX_ALLOCATION_SIZE;
+ if( nBA > nLimit ){
+ sqlite3_result_error_toobig(pCx);
+ }else if( asBlob ){
+ sqlite3_result_blob(pCx, pBuf, (int)nBA,
+ SQLITE_TRANSIENT);
+ }else{
+ switch( eTextRep ){
+ case SQLITE_UTF8:
+ sqlite3_result_text(pCx, (const char *)pBuf, (int)nBA,
+ SQLITE_TRANSIENT);
+ break;
+ case SQLITE_UTF16:
+ sqlite3_result_text16(pCx, (const char *)pBuf, (int)nBA,
+ SQLITE_TRANSIENT);
+ break;
+ case SQLITE_UTF16LE:
+ sqlite3_result_text16le(pCx, (const char *)pBuf, (int)nBA,
+ SQLITE_TRANSIENT);
+ break;
+ case SQLITE_UTF16BE:
+ sqlite3_result_text16be(pCx, (const char *)pBuf, (int)nBA,
+ SQLITE_TRANSIENT);
+ break;
+ }
+ }
+ s3jni_jbyteArray_release(jBa, pBuf);
+ }
+ }else{
+ sqlite3_result_null(pCx);
+ }
+}
+
+S3JniApi(sqlite3_result_blob(),void,1result_1blob)(
+ JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jint nMax
+){
+ return result_blob_text(0, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax);
+}
+
+S3JniApi(sqlite3_result_blob64(),void,1result_1blob64)(
+ JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jlong nMax
+){
+ return result_blob_text(1, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax);
+}
+
+S3JniApi(sqlite3_result_double(),void,1result_1double)(
+ JniArgsEnvClass, jobject jpCx, jdouble v
+){
+ sqlite3_result_double(PtrGet_sqlite3_context(jpCx), v);
+}
+
+S3JniApi(sqlite3_result_error(),void,1result_1error)(
+ JniArgsEnvClass, jobject jpCx, jbyteArray baMsg, int eTextRep
+){
+ const char * zUnspecified = "Unspecified error.";
+ jsize const baLen = (*env)->GetArrayLength(env, baMsg);
+ jbyte * const pjBuf = baMsg ? s3jni_jbyteArray_bytes(baMsg) : NULL;
+ switch( pjBuf ? eTextRep : SQLITE_UTF8 ){
+ case SQLITE_UTF8: {
+ const char *zMsg = pjBuf ? (const char *)pjBuf : zUnspecified;
+ int const n = pjBuf ? (int)baLen : (int)sqlite3Strlen30(zMsg);
+ sqlite3_result_error(PtrGet_sqlite3_context(jpCx), zMsg, n);
+ break;
+ }
+ case SQLITE_UTF16: {
+ const void *zMsg = pjBuf;
+ sqlite3_result_error16(PtrGet_sqlite3_context(jpCx), zMsg, (int)baLen);
+ break;
+ }
+ default:
+ sqlite3_result_error(PtrGet_sqlite3_context(jpCx),
+ "Invalid encoding argument passed "
+ "to sqlite3_result_error().", -1);
+ break;
+ }
+ s3jni_jbyteArray_release(baMsg,pjBuf);
+}
+
+S3JniApi(sqlite3_result_error_code(),void,1result_1error_1code)(
+ JniArgsEnvClass, jobject jpCx, jint v
+){
+ sqlite3_result_error_code(PtrGet_sqlite3_context(jpCx), (int)v);
+}
+
+S3JniApi(sqlite3_result_error_nomem(),void,1result_1error_1nomem)(
+ JniArgsEnvClass, jobject jpCx
+){
+ sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx));
+}
+
+S3JniApi(sqlite3_result_error_toobig(),void,1result_1error_1toobig)(
+ JniArgsEnvClass, jobject jpCx
+){
+ sqlite3_result_error_toobig(PtrGet_sqlite3_context(jpCx));
+}
+
+S3JniApi(sqlite3_result_int(),void,1result_1int)(
+ JniArgsEnvClass, jobject jpCx, jint v
+){
+ sqlite3_result_int(PtrGet_sqlite3_context(jpCx), (int)v);
+}
+
+S3JniApi(sqlite3_result_int64(),void,1result_1int64)(
+ JniArgsEnvClass, jobject jpCx, jlong v
+){
+ sqlite3_result_int64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v);
+}
+
+S3JniApi(sqlite3_result_java_object(),void,1result_1java_1object)(
+ JniArgsEnvClass, jobject jpCx, jobject v
+){
+ sqlite3_context * pCx = PtrGet_sqlite3_context(jpCx);
+ if( !pCx ) return;
+ else if( v ){
+ jobject const rjv = S3JniRefGlobal(v);
+ if( rjv ){
+ sqlite3_result_pointer(pCx, rjv,
+ ResultJavaValuePtrStr, S3Jni_jobject_finalizer);
+ }else{
+ sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx));
+ }
+ }else{
+ sqlite3_result_null(PtrGet_sqlite3_context(jpCx));
+ }
+}
+
+S3JniApi(sqlite3_result_null(),void,1result_1null)(
+ JniArgsEnvClass, jobject jpCx
+){
+ sqlite3_result_null(PtrGet_sqlite3_context(jpCx));
+}
+
+S3JniApi(sqlite3_result_text(),void,1result_1text)(
+ JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jint nMax
+){
+ return result_blob_text(0, SQLITE_UTF8, env,
+ PtrGet_sqlite3_context(jpCx), jBa, nMax);
+}
+
+S3JniApi(sqlite3_result_text64(),void,1result_1text64)(
+ JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jlong nMax,
+ jint eTextRep
+){
+ return result_blob_text(1, eTextRep, env,
+ PtrGet_sqlite3_context(jpCx), jBa, nMax);
+}
+
+S3JniApi(sqlite3_result_value(),void,1result_1value)(
+ JniArgsEnvClass, jobject jpCx, jobject jpSVal
+){
+ sqlite3_result_value(PtrGet_sqlite3_context(jpCx),
+ PtrGet_sqlite3_value(jpSVal));
+}
+
+S3JniApi(sqlite3_result_zeroblob(),void,1result_1zeroblob)(
+ JniArgsEnvClass, jobject jpCx, jint v
+){
+ sqlite3_result_zeroblob(PtrGet_sqlite3_context(jpCx), (int)v);
+}
+
+S3JniApi(sqlite3_result_zeroblob64(),jint,1result_1zeroblob64)(
+ JniArgsEnvClass, jobject jpCx, jlong v
+){
+ return (jint)sqlite3_result_zeroblob64(PtrGet_sqlite3_context(jpCx),
+ (sqlite3_int64)v);
+}
+
+S3JniApi(sqlite3_rollback_hook(),jobject,1rollback_1hook)(
+ JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+ return s3jni_commit_rollback_hook(0, env, jpDb, jHook);
+}
+
+/* Callback for sqlite3_set_authorizer(). */
+int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1,
+ const char*z2,const char*z3){
+ S3JniDb * const ps = pState;
+ S3JniDeclLocal_env;
+ S3JniHook hook;
+ int rc = 0;
+
+ S3JniHook_localdup(&ps->hooks.auth, &hook );
+ if( hook.jObj ){
+ jstring const s0 = z0 ? s3jni_utf8_to_jstring( z0, -1) : 0;
+ jstring const s1 = z1 ? s3jni_utf8_to_jstring( z1, -1) : 0;
+ jstring const s2 = z2 ? s3jni_utf8_to_jstring( z2, -1) : 0;
+ jstring const s3 = z3 ? s3jni_utf8_to_jstring( z3, -1) : 0;
+
+ rc = (*env)->CallIntMethod(env, hook.jObj, hook.midCallback, (jint)op,
+ s0, s1, s3, s3);
+ S3JniIfThrew{
+ rc = s3jni_db_exception(ps->pDb, rc, "sqlite3_set_authorizer() callback");
+ }
+ S3JniUnrefLocal(s0);
+ S3JniUnrefLocal(s1);
+ S3JniUnrefLocal(s2);
+ S3JniUnrefLocal(s3);
+ S3JniHook_localundup(hook);
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_set_authorizer(),jint,1set_1authorizer)(
+ JniArgsEnvClass,jobject jDb, jobject jHook
+){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ S3JniHook * const pHook = ps ? &ps->hooks.auth : 0;
+ int rc = 0;
+
+ if( !ps ) return SQLITE_MISUSE;
+ S3JniDb_mutex_enter;
+ if( !jHook ){
+ S3JniHook_unref(pHook);
+ rc = sqlite3_set_authorizer( ps->pDb, 0, 0 );
+ }else{
+ jclass klazz;
+ if( pHook->jObj ){
+ if( (*env)->IsSameObject(env, pHook->jObj, jHook) ){
+ /* Same object - this is a no-op. */
+ S3JniDb_mutex_leave;
+ return 0;
+ }
+ S3JniHook_unref(pHook);
+ }
+ pHook->jObj = S3JniRefGlobal(jHook);
+ klazz = (*env)->GetObjectClass(env, jHook);
+ pHook->midCallback = (*env)->GetMethodID(env, klazz,
+ "call",
+ "(I"
+ "Ljava/lang/String;"
+ "Ljava/lang/String;"
+ "Ljava/lang/String;"
+ "Ljava/lang/String;"
+ ")I");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ rc = s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ "Error setting up Java parts of authorizer hook.");
+ }else{
+ rc = sqlite3_set_authorizer(ps->pDb, s3jni_xAuth, ps);
+ }
+ if( rc ) S3JniHook_unref(pHook);
+ }
+ S3JniDb_mutex_leave;
+ return rc;
+}
+
+S3JniApi(sqlite3_set_auxdata(),void,1set_1auxdata)(
+ JniArgsEnvClass, jobject jCx, jint n, jobject jAux
+){
+ sqlite3_set_auxdata(PtrGet_sqlite3_context(jCx), (int)n,
+ S3JniRefGlobal(jAux), S3Jni_jobject_finalizer);
+}
+
+S3JniApi(sqlite3_set_last_insert_rowid(),void,1set_1last_1insert_1rowid)(
+ JniArgsEnvClass, jobject jpDb, jlong rowId
+){
+ sqlite3_set_last_insert_rowid(PtrGet_sqlite3(jpDb),
+ (sqlite3_int64)rowId);
+}
+
+S3JniApi(sqlite3_shutdown(),jint,1shutdown)(
+ JniArgsEnvClass
+){
+ s3jni_reset_auto_extension(env);
+#ifdef SQLITE_ENABLE_SQLLOG
+ S3JniHook_unref(&SJG.hook.sqllog);
+#endif
+ S3JniHook_unref(&SJG.hook.configlog);
+ /* Free up S3JniDb recycling bin. */
+ S3JniDb_mutex_enter; {
+ while( S3JniGlobal.perDb.aFree ){
+ S3JniDb * const d = S3JniGlobal.perDb.aFree;
+ S3JniGlobal.perDb.aFree = d->pNext;
+ S3JniDb_clear(env, d);
+ sqlite3_free(d);
+ }
+ } S3JniDb_mutex_leave;
+ S3JniGlobal_mutex_enter; {
+ /* Free up S3JniUdf recycling bin. */
+ while( S3JniGlobal.udf.aFree ){
+ S3JniUdf * const u = S3JniGlobal.udf.aFree;
+ S3JniGlobal.udf.aFree = u->pNext;
+ u->pNext = 0;
+ S3JniUdf_free(env, u, 0);
+ }
+ } S3JniGlobal_mutex_leave;
+ S3JniHook_mutex_enter; {
+ /* Free up S3JniHook recycling bin. */
+ while( S3JniGlobal.hook.aFree ){
+ S3JniHook * const u = S3JniGlobal.hook.aFree;
+ S3JniGlobal.hook.aFree = u->pNext;
+ u->pNext = 0;
+ assert( !u->doXDestroy );
+ assert( !u->jObj );
+ assert( !u->jExtra );
+ sqlite3_free( u );
+ }
+ } S3JniHook_mutex_leave;
+ /* Free up env cache. */
+ S3JniEnv_mutex_enter; {
+ while( SJG.envCache.aHead ){
+ S3JniEnv_uncache( SJG.envCache.aHead->env );
+ }
+ } S3JniEnv_mutex_leave;
+#if 0
+ /*
+ ** Is automatically closing any still-open dbs a good idea? We will
+ ** get rid of the perDb list once sqlite3 gets a per-db client
+ ** state, at which point we won't have a central list of databases
+ ** to close.
+ */
+ S3JniDb_mutex_enter;
+ while( SJG.perDb.pHead ){
+ s3jni_close_db(env, SJG.perDb.pHead->jDb, 2);
+ }
+ S3JniDb_mutex_leave;
+#endif
+ /* Do not clear S3JniGlobal.jvm: it's legal to restart the lib. */
+ return sqlite3_shutdown();
+}
+
+S3JniApi(sqlite3_status(),jint,1status)(
+ JniArgsEnvClass, jint op, jobject jOutCurrent, jobject jOutHigh,
+ jboolean reset
+){
+ int iCur = 0, iHigh = 0;
+ int rc = sqlite3_status( op, &iCur, &iHigh, reset );
+ if( 0==rc ){
+ OutputPointer_set_Int32(env, jOutCurrent, iCur);
+ OutputPointer_set_Int32(env, jOutHigh, iHigh);
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_status64(),jint,1status64)(
+ JniArgsEnvClass, jint op, jobject jOutCurrent, jobject jOutHigh,
+ jboolean reset
+){
+ sqlite3_int64 iCur = 0, iHigh = 0;
+ int rc = sqlite3_status64( op, &iCur, &iHigh, reset );
+ if( 0==rc ){
+ OutputPointer_set_Int64(env, jOutCurrent, iCur);
+ OutputPointer_set_Int64(env, jOutHigh, iHigh);
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_stmt_status(),jint,1stmt_1status)(
+ JniArgsEnvClass, jobject jStmt, jint op, jboolean reset
+){
+ return sqlite3_stmt_status(PtrGet_sqlite3_stmt(jStmt),
+ (int)op, reset ? 1 : 0);
+}
+
+
+static int s3jni_strlike_glob(int isLike, JNIEnv *const env,
+ jbyteArray baG, jbyteArray baT, jint escLike){
+ int rc = 0;
+ jbyte * const pG = s3jni_jbyteArray_bytes(baG);
+ jbyte * const pT = pG ? s3jni_jbyteArray_bytes(baT) : 0;
+
+ /* Note that we're relying on the byte arrays having been
+ NUL-terminated on the Java side. */
+ rc = isLike
+ ? sqlite3_strlike((const char *)pG, (const char *)pT,
+ (unsigned int)escLike)
+ : sqlite3_strglob((const char *)pG, (const char *)pT);
+ s3jni_jbyteArray_release(baG, pG);
+ s3jni_jbyteArray_release(baT, pT);
+ return rc;
+}
+
+S3JniApi(sqlite3_strglob(),jint,1strglob)(
+ JniArgsEnvClass, jbyteArray baG, jbyteArray baT
+){
+ return s3jni_strlike_glob(0, env, baG, baT, 0);
+}
+
+S3JniApi(sqlite3_strlike(),jint,1strlike)(
+ JniArgsEnvClass, jbyteArray baG, jbyteArray baT, jint escChar
+){
+ return s3jni_strlike_glob(1, env, baG, baT, escChar);
+}
+
+S3JniApi(sqlite3_sql(),jstring,1sql)(
+ JniArgsEnvClass, jobject jpStmt
+){
+ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+ jstring rv = 0;
+ if( pStmt ){
+ const char * zSql = 0;
+ zSql = sqlite3_sql(pStmt);
+ rv = s3jni_utf8_to_jstring( zSql, -1);
+ }
+ return rv;
+}
+
+S3JniApi(sqlite3_step(),jint,1step)(
+ JniArgsEnvClass,jobject jStmt
+){
+ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt);
+ return pStmt ? (jint)sqlite3_step(pStmt) : (jint)SQLITE_MISUSE;
+}
+
+S3JniApi(sqlite3_table_column_metadata(),int,1table_1column_1metadata)(
+ JniArgsEnvClass, jobject jDb, jstring jDbName, jstring jTableName,
+ jstring jColumnName, jobject jDataType, jobject jCollSeq, jobject jNotNull,
+ jobject jPrimaryKey, jobject jAutoinc
+){
+ sqlite3 * const db = PtrGet_sqlite3(jDb);
+ char * zDbName = 0, * zTableName = 0, * zColumnName = 0;
+ const char * pzCollSeq = 0;
+ const char * pzDataType = 0;
+ int pNotNull = 0, pPrimaryKey = 0, pAutoinc = 0;
+ int rc;
+
+ if( !db || !jDbName || !jTableName ) return SQLITE_MISUSE;
+ zDbName = s3jni_jstring_to_utf8(jDbName,0);
+ zTableName = zDbName ? s3jni_jstring_to_utf8(jTableName,0) : 0;
+ zColumnName = (zTableName && jColumnName)
+ ? s3jni_jstring_to_utf8(jColumnName,0) : 0;
+ rc = zTableName
+ ? sqlite3_table_column_metadata(db, zDbName, zTableName,
+ zColumnName, &pzDataType, &pzCollSeq,
+ &pNotNull, &pPrimaryKey, &pAutoinc)
+ : SQLITE_NOMEM;
+ if( 0==rc ){
+ jstring jseq = jCollSeq
+ ? (pzCollSeq ? s3jni_utf8_to_jstring(pzCollSeq, -1) : 0)
+ : 0;
+ jstring jdtype = jDataType
+ ? (pzDataType ? s3jni_utf8_to_jstring(pzDataType, -1) : 0)
+ : 0;
+ if( (jCollSeq && pzCollSeq && !jseq)
+ || (jDataType && pzDataType && !jdtype) ){
+ rc = SQLITE_NOMEM;
+ }else{
+ if( jNotNull ) OutputPointer_set_Bool(env, jNotNull, pNotNull);
+ if( jPrimaryKey ) OutputPointer_set_Bool(env, jPrimaryKey, pPrimaryKey);
+ if( jAutoinc ) OutputPointer_set_Bool(env, jAutoinc, pAutoinc);
+ if( jCollSeq ) OutputPointer_set_String(env, jCollSeq, jseq);
+ if( jDataType ) OutputPointer_set_String(env, jDataType, jdtype);
+ }
+ S3JniUnrefLocal(jseq);
+ S3JniUnrefLocal(jdtype);
+ }
+ sqlite3_free(zDbName);
+ sqlite3_free(zTableName);
+ sqlite3_free(zColumnName);
+ return rc;
+}
+
+static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){
+ S3JniDb * const ps = (S3JniDb *)pC;
+ S3JniDeclLocal_env;
+ jobject jX = NULL /* the tracer's X arg */;
+ jobject jP = NULL /* the tracer's P arg */;
+ jobject jPUnref = NULL /* potentially a local ref to jP */;
+ int rc = 0;
+ S3JniHook hook;
+
+ S3JniHook_localdup(&ps->hooks.trace, &hook );
+ if( !hook.jObj ){
+ return 0;
+ }
+ switch( traceflag ){
+ case SQLITE_TRACE_STMT:
+ jX = s3jni_utf8_to_jstring( (const char *)pX, -1);
+ if( !jX ) rc = SQLITE_NOMEM;
+ break;
+ case SQLITE_TRACE_PROFILE:
+ jX = (*env)->NewObject(env, SJG.g.cLong, SJG.g.ctorLong1,
+ (jlong)*((sqlite3_int64*)pX));
+ // hmm. ^^^ (*pX) really is zero.
+ // MARKER(("profile time = %llu\n", *((sqlite3_int64*)pX)));
+ s3jni_oom_check( jX );
+ if( !jX ) rc = SQLITE_NOMEM;
+ break;
+ case SQLITE_TRACE_ROW:
+ break;
+ case SQLITE_TRACE_CLOSE:
+ jP = jPUnref = S3JniRefLocal(ps->jDb);
+ break;
+ default:
+ assert(!"cannot happen - unknown trace flag");
+ rc = SQLITE_ERROR;
+ }
+ if( 0==rc ){
+ if( !jP ){
+ /* Create a new temporary sqlite3_stmt wrapper */
+ jP = jPUnref = new_java_sqlite3_stmt(env, pP);
+ if( !jP ){
+ rc = SQLITE_NOMEM;
+ }
+ }
+ if( 0==rc ){
+ assert(jP);
+ rc = (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback,
+ (jint)traceflag, jP, jX);
+ S3JniIfThrew{
+ rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR,
+ "sqlite3_trace_v2() callback threw.");
+ }
+ }
+ }
+ S3JniUnrefLocal(jPUnref);
+ S3JniUnrefLocal(jX);
+ S3JniHook_localundup(hook);
+ return rc;
+}
+
+S3JniApi(sqlite3_trace_v2(),jint,1trace_1v2)(
+ JniArgsEnvClass,jobject jDb, jint traceMask, jobject jTracer
+){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ int rc;
+
+ if( !ps ) return SQLITE_MISUSE;
+ if( !traceMask || !jTracer ){
+ S3JniDb_mutex_enter;
+ rc = (jint)sqlite3_trace_v2(ps->pDb, 0, 0, 0);
+ S3JniHook_unref(&ps->hooks.trace);
+ S3JniDb_mutex_leave;
+ }else{
+ jclass const klazz = (*env)->GetObjectClass(env, jTracer);
+ S3JniHook hook = S3JniHook_empty;
+ hook.midCallback = (*env)->GetMethodID(
+ env, klazz, "call", "(ILjava/lang/Object;Ljava/lang/Object;)I"
+ );
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ S3JniExceptionClear;
+ rc = s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ "Cannot not find matching call() on "
+ "TracerCallback object.");
+ }else{
+ hook.jObj = S3JniRefGlobal(jTracer);
+ S3JniDb_mutex_enter;
+ rc = sqlite3_trace_v2(ps->pDb, (unsigned)traceMask, s3jni_trace_impl, ps);
+ if( 0==rc ){
+ S3JniHook_unref(&ps->hooks.trace);
+ ps->hooks.trace = hook /* transfer ownership of reference */;
+ }else{
+ S3JniHook_unref(&hook);
+ }
+ S3JniDb_mutex_leave;
+ }
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_txn_state(),jint,1txn_1state)(
+ JniArgsEnvClass,jobject jDb, jstring jSchema
+){
+ sqlite3 * const pDb = PtrGet_sqlite3(jDb);
+ int rc = SQLITE_MISUSE;
+ if( pDb ){
+ char * zSchema = jSchema
+ ? s3jni_jstring_to_utf8(jSchema, 0)
+ : 0;
+ if( !jSchema || (zSchema && jSchema) ){
+ rc = sqlite3_txn_state(pDb, zSchema);
+ sqlite3_free(zSchema);
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_update_hook(),jobject,1update_1hook)(
+ JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+ return s3jni_updatepre_hook(env, 0, jpDb, jHook);
+}
+
+
+S3JniApi(sqlite3_value_blob(),jbyteArray,1value_1blob)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ const jbyte * pBytes = sv ? sqlite3_value_blob(sv) : 0;
+ int const nLen = pBytes ? sqlite3_value_bytes(sv) : 0;
+
+ s3jni_oom_check( nLen ? !!pBytes : 1 );
+ return pBytes
+ ? s3jni_new_jbyteArray(pBytes, nLen)
+ : NULL;
+}
+
+S3JniApi(sqlite3_value_bytes(),int,1value_1bytes)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ return sv ? sqlite3_value_bytes(sv) : 0;
+}
+
+S3JniApi(sqlite3_value_bytes16(),int,1value_1bytes16)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ return sv ? sqlite3_value_bytes16(sv) : 0;
+}
+
+
+S3JniApi(sqlite3_value_double(),jdouble,1value_1double)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ return (jdouble) (sv ? sqlite3_value_double(sv) : 0.0);
+}
+
+
+S3JniApi(sqlite3_value_dup(),jobject,1value_1dup)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ sqlite3_value * const sd = sv ? sqlite3_value_dup(sv) : 0;
+ jobject rv = sd ? new_java_sqlite3_value(env, sd) : 0;
+ if( sd && !rv ) {
+ /* OOM */
+ sqlite3_value_free(sd);
+ }
+ return rv;
+}
+
+S3JniApi(sqlite3_value_free(),void,1value_1free)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ if( sv ){
+ sqlite3_value_free(sv);
+ }
+}
+
+S3JniApi(sqlite3_value_int(),jint,1value_1int)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ return (jint) (sv ? sqlite3_value_int(sv) : 0);
+}
+
+S3JniApi(sqlite3_value_int64(),jlong,1value_1int64)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ return (jlong) (sv ? sqlite3_value_int64(sv) : 0LL);
+}
+
+S3JniApi(sqlite3_value_java_object(),jobject,1value_1java_1object)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ return sv
+ ? sqlite3_value_pointer(sv, ResultJavaValuePtrStr)
+ : 0;
+}
+
+S3JniApi(sqlite3_value_text(),jbyteArray,1value_1text)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ const unsigned char * const p = sv ? sqlite3_value_text(sv) : 0;
+ int const n = p ? sqlite3_value_bytes(sv) : 0;
+ return p ? s3jni_new_jbyteArray(p, n) : 0;
+}
+
+#if 0
+// this impl might prove useful.
+S3JniApi(sqlite3_value_text(),jstring,1value_1text)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ const unsigned char * const p = sv ? sqlite3_value_text(sv) : 0;
+ int const n = p ? sqlite3_value_bytes(sv) : 0;
+ return p ? s3jni_utf8_to_jstring( (const char *)p, n) : 0;
+}
+#endif
+
+S3JniApi(sqlite3_value_text16(),jstring,1value_1text16)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ const int n = sv ? sqlite3_value_bytes16(sv) : 0;
+ const void * const p = sv ? sqlite3_value_text16(sv) : 0;
+ return p ? s3jni_text16_to_jstring(env, p, n) : 0;
+}
+
+JniDecl(void,1jni_1internal_1details)(JniArgsEnvClass){
+ MARKER(("\nVarious bits of internal info:\n"));
+ puts("FTS5 is "
+#ifdef SQLITE_ENABLE_FTS5
+ "available"
+#else
+ "unavailable"
+#endif
+ "."
+ );
+ puts("sizeofs:");
+#define SO(T) printf("\tsizeof(" #T ") = %u\n", (unsigned)sizeof(T))
+ SO(void*);
+ SO(jmethodID);
+ SO(jfieldID);
+ SO(S3JniEnv);
+ SO(S3JniHook);
+ SO(S3JniDb);
+ SO(S3JniNphOps);
+ printf("\t(^^^ %u NativePointerHolder/OutputPointer.T types)\n",
+ (unsigned)S3Jni_NphCache_size);
+ SO(S3JniGlobal);
+ SO(S3JniGlobal.nph);
+ SO(S3JniGlobal.metrics);
+ SO(S3JniAutoExtension);
+ SO(S3JniUdf);
+#undef SO
+#ifdef SQLITE_JNI_ENABLE_METRICS
+ printf("Cache info:\n");
+ printf("\tJNIEnv cache: %u allocs, %u misses, %u hits\n",
+ SJG.metrics.nEnvAlloc, SJG.metrics.nEnvMiss,
+ SJG.metrics.nEnvHit);
+ printf("Mutex entry:"
+ "\n\tglobal = %u"
+ "\n\tenv = %u"
+ "\n\tnph = %u for S3JniNphOp init"
+ "\n\thook = %u"
+ "\n\tperDb = %u"
+ "\n\tautoExt list = %u"
+ "\n\tS3JniUdf = %u (free-list)"
+ "\n\tmetrics = %u\n",
+ SJG.metrics.nMutexGlobal, SJG.metrics.nMutexEnv,
+ SJG.metrics.nMutexNph, SJG.metrics.nMutexHook,
+ SJG.metrics.nMutexPerDb, SJG.metrics.nMutexAutoExt,
+ SJG.metrics.nMutexUdf, SJG.metrics.nMetrics);
+ puts("Allocs:");
+ printf("\tS3JniDb: %u alloced (*%u = %u bytes), %u recycled\n",
+ SJG.metrics.nPdbAlloc, (unsigned) sizeof(S3JniDb),
+ (unsigned)(SJG.metrics.nPdbAlloc * sizeof(S3JniDb)),
+ SJG.metrics.nPdbRecycled);
+ printf("\tS3JniUdf: %u alloced (*%u = %u bytes), %u recycled\n",
+ SJG.metrics.nUdfAlloc, (unsigned) sizeof(S3JniUdf),
+ (unsigned)(SJG.metrics.nUdfAlloc * sizeof(S3JniUdf)),
+ SJG.metrics.nUdfRecycled);
+ printf("\tS3JniHook: %u alloced (*%u = %u bytes), %u recycled\n",
+ SJG.metrics.nHookAlloc, (unsigned) sizeof(S3JniHook),
+ (unsigned)(SJG.metrics.nHookAlloc * sizeof(S3JniHook)),
+ SJG.metrics.nHookRecycled);
+ printf("\tS3JniEnv: %u alloced (*%u = %u bytes)\n",
+ SJG.metrics.nEnvAlloc, (unsigned) sizeof(S3JniEnv),
+ (unsigned)(SJG.metrics.nEnvAlloc * sizeof(S3JniEnv)));
+ puts("Java-side UDF calls:");
+#define UDF(T) printf("\t%-8s = %u\n", "x" #T, SJG.metrics.udf.n##T)
+ UDF(Func); UDF(Step); UDF(Final); UDF(Value); UDF(Inverse);
+#undef UDF
+ printf("xDestroy calls across all callback types: %u\n",
+ SJG.metrics.nDestroy);
+#else
+ puts("Built without SQLITE_JNI_ENABLE_METRICS.");
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////
+// End of the sqlite3_... API bindings. Next up, FTS5...
+////////////////////////////////////////////////////////////////////////
+#ifdef SQLITE_ENABLE_FTS5
+
+/* Creates a verbose JNI Fts5 function name. */
+#define JniFuncNameFtsXA(Suffix) \
+ Java_org_sqlite_jni_fts5_Fts5ExtensionApi_ ## Suffix
+#define JniFuncNameFtsApi(Suffix) \
+ Java_org_sqlite_jni_fts5_fts5_1api_ ## Suffix
+#define JniFuncNameFtsTok(Suffix) \
+ Java_org_sqlite_jni_fts5_fts5_tokenizer_ ## Suffix
+
+#define JniDeclFtsXA(ReturnType,Suffix) \
+ JNIEXPORT ReturnType JNICALL \
+ JniFuncNameFtsXA(Suffix)
+#define JniDeclFtsApi(ReturnType,Suffix) \
+ JNIEXPORT ReturnType JNICALL \
+ JniFuncNameFtsApi(Suffix)
+#define JniDeclFtsTok(ReturnType,Suffix) \
+ JNIEXPORT ReturnType JNICALL \
+ JniFuncNameFtsTok(Suffix)
+
+#define PtrGet_fts5_api(OBJ) NativePointerHolder_get(OBJ,S3JniNph(fts5_api))
+#define PtrGet_fts5_tokenizer(OBJ) NativePointerHolder_get(OBJ,S3JniNph(fts5_tokenizer))
+#define PtrGet_Fts5Context(OBJ) NativePointerHolder_get(OBJ,S3JniNph(Fts5Context))
+#define PtrGet_Fts5Tokenizer(OBJ) NativePointerHolder_get(OBJ,S3JniNph(Fts5Tokenizer))
+#define s3jni_ftsext() &sFts5Api/*singleton from sqlite3.c*/
+#define Fts5ExtDecl Fts5ExtensionApi const * const ext = s3jni_ftsext()
+
+/**
+ State for binding Java-side FTS5 auxiliary functions.
+*/
+typedef struct {
+ jobject jObj /* functor instance */;
+ jobject jUserData /* 2nd arg to JNI binding of
+ xCreateFunction(), ostensibly the 3rd arg
+ to the lib-level xCreateFunction(), except
+ that we necessarily use that slot for a
+ Fts5JniAux instance. */;
+ char * zFuncName /* Only for error reporting and debug logging */;
+ jmethodID jmid /* callback member's method ID */;
+} Fts5JniAux;
+
+static void Fts5JniAux_free(Fts5JniAux * const s){
+ S3JniDeclLocal_env;
+ if( env ){
+ /*MARKER(("FTS5 aux function cleanup: %s\n", s->zFuncName));*/
+ s3jni_call_xDestroy(s->jObj);
+ S3JniUnrefGlobal(s->jObj);
+ S3JniUnrefGlobal(s->jUserData);
+ }
+ sqlite3_free(s->zFuncName);
+ sqlite3_free(s);
+}
+
+static void Fts5JniAux_xDestroy(void *p){
+ if( p ) Fts5JniAux_free(p);
+}
+
+static Fts5JniAux * Fts5JniAux_alloc(JNIEnv * const env, jobject jObj){
+ Fts5JniAux * s = s3jni_malloc( sizeof(Fts5JniAux));
+
+ if( s ){
+ jclass klazz;
+ memset(s, 0, sizeof(Fts5JniAux));
+ s->jObj = S3JniRefGlobal(jObj);
+ klazz = (*env)->GetObjectClass(env, jObj);
+ s->jmid = (*env)->GetMethodID(env, klazz, "call",
+ "(Lorg/sqlite/jni/fts5/Fts5ExtensionApi;"
+ "Lorg/sqlite/jni/fts5/Fts5Context;"
+ "Lorg/sqlite/jni/capi/sqlite3_context;"
+ "[Lorg/sqlite/jni/capi/sqlite3_value;)V");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew{
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ Fts5JniAux_free(s);
+ s = 0;
+ }
+ }
+ return s;
+}
+
+static inline jobject new_java_Fts5Context(JNIEnv * const env, Fts5Context *sv){
+ return NativePointerHolder_new(env, S3JniNph(Fts5Context), sv);
+}
+static inline jobject new_java_fts5_api(JNIEnv * const env, fts5_api *sv){
+ return NativePointerHolder_new(env, S3JniNph(fts5_api), sv);
+}
+
+/*
+** Returns a per-JNIEnv global ref to the Fts5ExtensionApi singleton
+** instance, or NULL on OOM.
+*/
+static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){
+ if( !SJG.fts5.jExt ){
+ S3JniGlobal_mutex_enter;
+ if( !SJG.fts5.jExt ){
+ jobject const pNPH = NativePointerHolder_new(
+ env, S3JniNph(Fts5ExtensionApi), s3jni_ftsext()
+ );
+ if( pNPH ){
+ SJG.fts5.jExt = S3JniRefGlobal(pNPH);
+ S3JniUnrefLocal(pNPH);
+ }
+ }
+ S3JniGlobal_mutex_leave;
+ }
+ return SJG.fts5.jExt;
+}
+
+/*
+** Returns a pointer to the fts5_api instance for database connection
+** db. If an error occurs, returns NULL and leaves an error in the
+** database handle (accessible using sqlite3_errcode()/errmsg()).
+*/
+static fts5_api *s3jni_fts5_api_from_db(sqlite3 *db){
+ fts5_api *pRet = 0;
+ sqlite3_stmt *pStmt = 0;
+ if( SQLITE_OK==sqlite3_prepare(db, "SELECT fts5(?1)", -1, &pStmt, 0) ){
+ sqlite3_bind_pointer(pStmt, 1, (void*)&pRet, "fts5_api_ptr", NULL);
+ sqlite3_step(pStmt);
+ }
+ sqlite3_finalize(pStmt);
+ return pRet;
+}
+
+JniDeclFtsApi(jobject,getInstanceForDb)(JniArgsEnvClass,jobject jDb){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+#if 0
+ jobject rv = 0;
+ if( !ps ) return 0;
+ else if( ps->fts.jApi ){
+ rv = ps->fts.jApi;
+ }else{
+ fts5_api * const pApi = s3jni_fts5_api_from_db(ps->pDb);
+ if( pApi ){
+ rv = new_java_fts5_api(env, pApi);
+ ps->fts.jApi = rv ? S3JniRefGlobal(rv) : 0;
+ }
+ }
+ return rv;
+#else
+ if( ps && !ps->fts.jApi ){
+ S3JniDb_mutex_enter;
+ if( !ps->fts.jApi ){
+ fts5_api * const pApi = s3jni_fts5_api_from_db(ps->pDb);
+ if( pApi ){
+ jobject const rv = new_java_fts5_api(env, pApi);
+ ps->fts.jApi = rv ? S3JniRefGlobal(rv) : 0;
+ }
+ }
+ S3JniDb_mutex_leave;
+ }
+ return ps ? ps->fts.jApi : 0;
+#endif
+}
+
+
+JniDeclFtsXA(jobject,getInstance)(JniArgsEnvClass){
+ return s3jni_getFts5ExensionApi(env);
+}
+
+JniDeclFtsXA(jint,xColumnCount)(JniArgsEnvObj,jobject jCtx){
+ Fts5ExtDecl;
+ return (jint)ext->xColumnCount(PtrGet_Fts5Context(jCtx));
+}
+
+JniDeclFtsXA(jint,xColumnSize)(JniArgsEnvObj,jobject jCtx, jint iIdx, jobject jOut32){
+ Fts5ExtDecl;
+ int n1 = 0;
+ int const rc = ext->xColumnSize(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1);
+ if( 0==rc ) OutputPointer_set_Int32(env, jOut32, n1);
+ return rc;
+}
+
+JniDeclFtsXA(jint,xColumnText)(JniArgsEnvObj,jobject jCtx, jint iCol,
+ jobject jOut){
+ Fts5ExtDecl;
+ const char *pz = 0;
+ int pn = 0;
+ int rc = ext->xColumnText(PtrGet_Fts5Context(jCtx), (int)iCol,
+ &pz, &pn);
+ if( 0==rc ){
+ jstring jstr = pz ? s3jni_utf8_to_jstring( pz, pn) : 0;
+ if( pz ){
+ if( jstr ){
+ OutputPointer_set_String(env, jOut, jstr);
+ S3JniUnrefLocal(jstr)/*jOut has a reference*/;
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ }
+ }
+ return (jint)rc;
+}
+
+JniDeclFtsXA(jint,xColumnTotalSize)(JniArgsEnvObj,jobject jCtx, jint iCol, jobject jOut64){
+ Fts5ExtDecl;
+ sqlite3_int64 nOut = 0;
+ int const rc = ext->xColumnTotalSize(PtrGet_Fts5Context(jCtx), (int)iCol, &nOut);
+ if( 0==rc && jOut64 ) OutputPointer_set_Int64(env, jOut64, (jlong)nOut);
+ return (jint)rc;
+}
+
+/*
+** Proxy for fts5_extension_function instances plugged in via
+** fts5_api::xCreateFunction().
+*/
+static void s3jni_fts5_extension_function(Fts5ExtensionApi const *pApi,
+ Fts5Context *pFts,
+ sqlite3_context *pCx,
+ int argc,
+ sqlite3_value **argv){
+ Fts5JniAux * const pAux = pApi->xUserData(pFts);
+ jobject jpCx = 0;
+ jobjectArray jArgv = 0;
+ jobject jpFts = 0;
+ jobject jFXA;
+ int rc;
+ S3JniDeclLocal_env;
+
+ assert(pAux);
+ jFXA = s3jni_getFts5ExensionApi(env);
+ if( !jFXA ) goto error_oom;
+ jpFts = new_java_Fts5Context(env, pFts);
+ if( !jpFts ) goto error_oom;
+ rc = udf_args(env, pCx, argc, argv, &jpCx, &jArgv);
+ if( rc ) goto error_oom;
+ (*env)->CallVoidMethod(env, pAux->jObj, pAux->jmid,
+ jFXA, jpFts, jpCx, jArgv);
+ S3JniIfThrew{
+ udf_report_exception(env, 1, pCx, pAux->zFuncName, "call");
+ }
+ udf_unargs(env, jpCx, argc, jArgv);
+ S3JniUnrefLocal(jpFts);
+ S3JniUnrefLocal(jpCx);
+ S3JniUnrefLocal(jArgv);
+ return;
+error_oom:
+ s3jni_db_oom( sqlite3_context_db_handle(pCx) );
+ assert( !jArgv );
+ assert( !jpCx );
+ S3JniUnrefLocal(jpFts);
+ sqlite3_result_error_nomem(pCx);
+ return;
+}
+
+JniDeclFtsApi(jint,xCreateFunction)(JniArgsEnvObj, jstring jName,
+ jobject jUserData, jobject jFunc){
+ fts5_api * const pApi = PtrGet_fts5_api(jSelf);
+ int rc;
+ char * zName;
+ Fts5JniAux * pAux;
+
+ assert(pApi);
+ zName = s3jni_jstring_to_utf8( jName, 0);
+ if(!zName) return SQLITE_NOMEM;
+ pAux = Fts5JniAux_alloc(env, jFunc);
+ if( pAux ){
+ rc = pApi->xCreateFunction(pApi, zName, pAux,
+ s3jni_fts5_extension_function,
+ Fts5JniAux_xDestroy);
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ if( 0==rc ){
+ pAux->jUserData = jUserData ? S3JniRefGlobal(jUserData) : 0;
+ pAux->zFuncName = zName;
+ }else{
+ sqlite3_free(zName);
+ }
+ return (jint)rc;
+}
+
+
+typedef struct S3JniFts5AuxData S3JniFts5AuxData;
+/*
+** TODO: this middle-man struct is no longer necessary. Conider
+** removing it and passing around jObj itself instead.
+*/
+struct S3JniFts5AuxData {
+ jobject jObj;
+};
+
+static void S3JniFts5AuxData_xDestroy(void *x){
+ if( x ){
+ S3JniFts5AuxData * const p = x;
+ if( p->jObj ){
+ S3JniDeclLocal_env;
+ s3jni_call_xDestroy(p->jObj);
+ S3JniUnrefGlobal(p->jObj);
+ }
+ sqlite3_free(x);
+ }
+}
+
+JniDeclFtsXA(jobject,xGetAuxdata)(JniArgsEnvObj,jobject jCtx, jboolean bClear){
+ Fts5ExtDecl;
+ jobject rv = 0;
+ S3JniFts5AuxData * const pAux = ext->xGetAuxdata(PtrGet_Fts5Context(jCtx), bClear);
+ if( pAux ){
+ if( bClear ){
+ if( pAux->jObj ){
+ rv = S3JniRefLocal(pAux->jObj);
+ S3JniUnrefGlobal(pAux->jObj);
+ }
+ /* Note that we do not call xDestroy() in this case. */
+ sqlite3_free(pAux);
+ }else{
+ rv = pAux->jObj;
+ }
+ }
+ return rv;
+}
+
+JniDeclFtsXA(jint,xInst)(JniArgsEnvObj,jobject jCtx, jint iIdx, jobject jOutPhrase,
+ jobject jOutCol, jobject jOutOff){
+ Fts5ExtDecl;
+ int n1 = 0, n2 = 2, n3 = 0;
+ int const rc = ext->xInst(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1, &n2, &n3);
+ if( 0==rc ){
+ OutputPointer_set_Int32(env, jOutPhrase, n1);
+ OutputPointer_set_Int32(env, jOutCol, n2);
+ OutputPointer_set_Int32(env, jOutOff, n3);
+ }
+ return rc;
+}
+
+JniDeclFtsXA(jint,xInstCount)(JniArgsEnvObj,jobject jCtx, jobject jOut32){
+ Fts5ExtDecl;
+ int nOut = 0;
+ int const rc = ext->xInstCount(PtrGet_Fts5Context(jCtx), &nOut);
+ if( 0==rc && jOut32 ) OutputPointer_set_Int32(env, jOut32, nOut);
+ return (jint)rc;
+}
+
+JniDeclFtsXA(jint,xPhraseCount)(JniArgsEnvObj,jobject jCtx){
+ Fts5ExtDecl;
+ return (jint)ext->xPhraseCount(PtrGet_Fts5Context(jCtx));
+}
+
+/* Copy the 'a' and 'b' fields from pSrc to Fts5PhraseIter object jIter. */
+static void s3jni_phraseIter_NToJ(JNIEnv *const env,
+ Fts5PhraseIter const * const pSrc,
+ jobject jIter){
+ S3JniGlobalType * const g = &S3JniGlobal;
+ assert(g->fts5.jPhraseIter.fidA);
+ (*env)->SetLongField(env, jIter, g->fts5.jPhraseIter.fidA,
+ S3JniCast_P2L(pSrc->a));
+ S3JniExceptionIsFatal("Cannot set Fts5PhraseIter.a field.");
+ (*env)->SetLongField(env, jIter, g->fts5.jPhraseIter.fidB,
+ S3JniCast_P2L(pSrc->b));
+ S3JniExceptionIsFatal("Cannot set Fts5PhraseIter.b field.");
+}
+
+/* Copy the 'a' and 'b' fields from Fts5PhraseIter object jIter to pDest. */
+static void s3jni_phraseIter_JToN(JNIEnv *const env, jobject jIter,
+ Fts5PhraseIter * const pDest){
+ S3JniGlobalType * const g = &S3JniGlobal;
+ assert(g->fts5.jPhraseIter.fidA);
+ pDest->a = S3JniCast_L2P(
+ (*env)->GetLongField(env, jIter, g->fts5.jPhraseIter.fidA)
+ );
+ S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.a field.");
+ pDest->b = S3JniCast_L2P(
+ (*env)->GetLongField(env, jIter, g->fts5.jPhraseIter.fidB)
+ );
+ S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.b field.");
+}
+
+JniDeclFtsXA(jint,xPhraseFirst)(JniArgsEnvObj,jobject jCtx, jint iPhrase,
+ jobject jIter, jobject jOutCol,
+ jobject jOutOff){
+ Fts5ExtDecl;
+ Fts5PhraseIter iter;
+ int rc, iCol = 0, iOff = 0;
+ rc = ext->xPhraseFirst(PtrGet_Fts5Context(jCtx), (int)iPhrase,
+ &iter, &iCol, &iOff);
+ if( 0==rc ){
+ OutputPointer_set_Int32(env, jOutCol, iCol);
+ OutputPointer_set_Int32(env, jOutOff, iOff);
+ s3jni_phraseIter_NToJ(env, &iter, jIter);
+ }
+ return rc;
+}
+
+JniDeclFtsXA(jint,xPhraseFirstColumn)(JniArgsEnvObj,jobject jCtx, jint iPhrase,
+ jobject jIter, jobject jOutCol){
+ Fts5ExtDecl;
+ Fts5PhraseIter iter;
+ int rc, iCol = 0;
+ rc = ext->xPhraseFirstColumn(PtrGet_Fts5Context(jCtx), (int)iPhrase,
+ &iter, &iCol);
+ if( 0==rc ){
+ OutputPointer_set_Int32(env, jOutCol, iCol);
+ s3jni_phraseIter_NToJ(env, &iter, jIter);
+ }
+ return rc;
+}
+
+JniDeclFtsXA(void,xPhraseNext)(JniArgsEnvObj,jobject jCtx, jobject jIter,
+ jobject jOutCol, jobject jOutOff){
+ Fts5ExtDecl;
+ Fts5PhraseIter iter;
+ int iCol = 0, iOff = 0;
+ s3jni_phraseIter_JToN(env, jIter, &iter);
+ ext->xPhraseNext(PtrGet_Fts5Context(jCtx), &iter, &iCol, &iOff);
+ OutputPointer_set_Int32(env, jOutCol, iCol);
+ OutputPointer_set_Int32(env, jOutOff, iOff);
+ s3jni_phraseIter_NToJ(env, &iter, jIter);
+}
+
+JniDeclFtsXA(void,xPhraseNextColumn)(JniArgsEnvObj,jobject jCtx, jobject jIter,
+ jobject jOutCol){
+ Fts5ExtDecl;
+ Fts5PhraseIter iter;
+ int iCol = 0;
+ s3jni_phraseIter_JToN(env, jIter, &iter);
+ ext->xPhraseNextColumn(PtrGet_Fts5Context(jCtx), &iter, &iCol);
+ OutputPointer_set_Int32(env, jOutCol, iCol);
+ s3jni_phraseIter_NToJ(env, &iter, jIter);
+}
+
+
+JniDeclFtsXA(jint,xPhraseSize)(JniArgsEnvObj,jobject jCtx, jint iPhrase){
+ Fts5ExtDecl;
+ return (jint)ext->xPhraseSize(PtrGet_Fts5Context(jCtx), (int)iPhrase);
+}
+
+/* State for use with xQueryPhrase() and xTokenize(). */
+struct s3jni_xQueryPhraseState {
+ Fts5ExtensionApi const * ext;
+ jmethodID midCallback; /* jCallback->call() method */
+ jobject jCallback; /* Fts5ExtensionApi.XQueryPhraseCallback instance */
+ jobject jFcx; /* (Fts5Context*) for xQueryPhrase()
+ callback. This is NOT the instance that is
+ passed to xQueryPhrase(), it's the one
+ created by xQueryPhrase() for use by its
+ callback. */
+ /* State for xTokenize() */
+ struct {
+ const char * zPrev;
+ int nPrev;
+ jbyteArray jba;
+ } tok;
+};
+
+static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi,
+ Fts5Context * pFcx, void *pData){
+ struct s3jni_xQueryPhraseState * const s = pData;
+ S3JniDeclLocal_env;
+
+ if( !s->jFcx ){
+ s->jFcx = new_java_Fts5Context(env, pFcx);
+ if( !s->jFcx ) return SQLITE_NOMEM;
+ }
+ int rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback,
+ SJG.fts5.jExt, s->jFcx);
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("xQueryPhrase() callback");
+ S3JniExceptionClear;
+ rc = SQLITE_ERROR;
+ }
+ return rc;
+}
+
+JniDeclFtsXA(jint,xQueryPhrase)(JniArgsEnvObj,jobject jFcx, jint iPhrase,
+ jobject jCallback){
+ Fts5ExtDecl;
+ int rc;
+ struct s3jni_xQueryPhraseState s;
+ jclass klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL;
+
+ if( !klazz ) return SQLITE_MISUSE;
+ s.jCallback = jCallback;
+ s.jFcx = 0;
+ s.ext = ext;
+ s.midCallback = (*env)->GetMethodID(env, klazz, "call",
+ "(Lorg/sqlite/jni/fts5/Fts5ExtensionApi;"
+ "Lorg/sqlite/jni/fts5/Fts5Context;)I");
+ S3JniUnrefLocal(klazz);
+ S3JniExceptionIsFatal("Could not extract xQueryPhraseCallback.call() method.");
+ rc = ext->xQueryPhrase(PtrGet_Fts5Context(jFcx), iPhrase, &s,
+ s3jni_xQueryPhrase);
+ S3JniUnrefLocal(s.jFcx);
+ return (jint)rc;
+}
+
+
+JniDeclFtsXA(jint,xRowCount)(JniArgsEnvObj,jobject jCtx, jobject jOut64){
+ Fts5ExtDecl;
+ sqlite3_int64 nOut = 0;
+ int const rc = ext->xRowCount(PtrGet_Fts5Context(jCtx), &nOut);
+ if( 0==rc && jOut64 ) OutputPointer_set_Int64(env, jOut64, (jlong)nOut);
+ return (jint)rc;
+}
+
+JniDeclFtsXA(jlong,xRowid)(JniArgsEnvObj,jobject jCtx){
+ Fts5ExtDecl;
+ return (jlong)ext->xRowid(PtrGet_Fts5Context(jCtx));
+}
+
+JniDeclFtsXA(int,xSetAuxdata)(JniArgsEnvObj,jobject jCtx, jobject jAux){
+ Fts5ExtDecl;
+ int rc;
+ S3JniFts5AuxData * pAux;
+
+ pAux = s3jni_malloc( sizeof(*pAux));
+ if( !pAux ){
+ if( jAux ){
+ /* Emulate how xSetAuxdata() behaves when it cannot alloc
+ ** its auxdata wrapper. */
+ s3jni_call_xDestroy(jAux);
+ }
+ return SQLITE_NOMEM;
+ }
+ pAux->jObj = S3JniRefGlobal(jAux);
+ rc = ext->xSetAuxdata(PtrGet_Fts5Context(jCtx), pAux,
+ S3JniFts5AuxData_xDestroy);
+ return rc;
+}
+
+/* xToken() impl for xTokenize(). */
+static int s3jni_xTokenize_xToken(void *p, int tFlags, const char* z,
+ int nZ, int iStart, int iEnd){
+ int rc;
+ S3JniDeclLocal_env;
+ struct s3jni_xQueryPhraseState * const s = p;
+ jbyteArray jba;
+
+ S3JniUnrefLocal(s->tok.jba);
+ s->tok.zPrev = z;
+ s->tok.nPrev = nZ;
+ s->tok.jba = s3jni_new_jbyteArray(z, nZ);
+ if( !s->tok.jba ) return SQLITE_NOMEM;
+ jba = s->tok.jba;
+ rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback,
+ (jint)tFlags, jba, (jint)iStart,
+ (jint)iEnd);
+ S3JniIfThrew {
+ S3JniExceptionWarnCallbackThrew("xTokenize() callback");
+ rc = SQLITE_ERROR;
+ }
+ return rc;
+}
+
+/*
+** Proxy for Fts5ExtensionApi.xTokenize() and
+** fts5_tokenizer.xTokenize()
+*/
+static jint s3jni_fts5_xTokenize(JniArgsEnvObj, S3JniNphOp const *pRef,
+ jint tokFlags, jobject jFcx,
+ jbyteArray jbaText, jobject jCallback){
+ Fts5ExtDecl;
+ struct s3jni_xQueryPhraseState s;
+ int rc = 0;
+ jbyte * const pText = jCallback ? s3jni_jbyteArray_bytes(jbaText) : 0;
+ jsize nText = pText ? (*env)->GetArrayLength(env, jbaText) : 0;
+ jclass const klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL;
+
+ if( !klazz ) return SQLITE_MISUSE;
+ memset(&s, 0, sizeof(s));
+ s.jCallback = jCallback;
+ s.jFcx = jFcx;
+ s.ext = ext;
+ s.midCallback = (*env)->GetMethodID(env, klazz, "call", "(I[BII)I");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ s3jni_jbyteArray_release(jbaText, pText);
+ return SQLITE_ERROR;
+ }
+ s.tok.jba = S3JniRefLocal(jbaText);
+ s.tok.zPrev = (const char *)pText;
+ s.tok.nPrev = (int)nText;
+ if( pRef == S3JniNph(Fts5ExtensionApi) ){
+ rc = ext->xTokenize(PtrGet_Fts5Context(jFcx),
+ (const char *)pText, (int)nText,
+ &s, s3jni_xTokenize_xToken);
+ }else if( pRef == S3JniNph(fts5_tokenizer) ){
+ fts5_tokenizer * const pTok = PtrGet_fts5_tokenizer(jSelf);
+ rc = pTok->xTokenize(PtrGet_Fts5Tokenizer(jFcx), &s, tokFlags,
+ (const char *)pText, (int)nText,
+ s3jni_xTokenize_xToken);
+ }else{
+ (*env)->FatalError(env, "This cannot happen. Maintenance required.");
+ }
+ if( s.tok.jba ){
+ assert( s.tok.zPrev );
+ S3JniUnrefLocal(s.tok.jba);
+ }
+ s3jni_jbyteArray_release(jbaText, pText);
+ return (jint)rc;
+}
+
+JniDeclFtsXA(jint,xTokenize)(JniArgsEnvObj,jobject jFcx, jbyteArray jbaText,
+ jobject jCallback){
+ return s3jni_fts5_xTokenize(env, jSelf, S3JniNph(Fts5ExtensionApi),
+ 0, jFcx, jbaText, jCallback);
+}
+
+JniDeclFtsTok(jint,xTokenize)(JniArgsEnvObj,jobject jFcx, jint tokFlags,
+ jbyteArray jbaText, jobject jCallback){
+ return s3jni_fts5_xTokenize(env, jSelf, S3JniNph(Fts5Tokenizer),
+ tokFlags, jFcx, jbaText, jCallback);
+}
+
+
+JniDeclFtsXA(jobject,xUserData)(JniArgsEnvObj,jobject jFcx){
+ Fts5ExtDecl;
+ Fts5JniAux * const pAux = ext->xUserData(PtrGet_Fts5Context(jFcx));
+ return pAux ? pAux->jUserData : 0;
+}
+
+#endif /* SQLITE_ENABLE_FTS5 */
+
+////////////////////////////////////////////////////////////////////////
+// End of the main API bindings. Start of SQLTester bits...
+////////////////////////////////////////////////////////////////////////
+
+#ifdef SQLITE_JNI_ENABLE_SQLTester
+typedef struct SQLTesterJni SQLTesterJni;
+struct SQLTesterJni {
+ sqlite3_int64 nDup;
+};
+static SQLTesterJni SQLTester = {
+ 0
+};
+
+static void SQLTester_dup_destructor(void*pToFree){
+ u64 *p = (u64*)pToFree;
+ assert( p!=0 );
+ p--;
+ assert( p[0]==0x2bbf4b7c );
+ p[0] = 0;
+ p[1] = 0;
+ sqlite3_free(p);
+}
+
+/*
+** Implementation of
+**
+** dup(TEXT)
+**
+** This SQL function simply makes a copy of its text argument. But it
+** returns the result using a custom destructor, in order to provide
+** tests for the use of Mem.xDel() in the SQLite VDBE.
+*/
+static void SQLTester_dup_func(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ u64 *pOut;
+ char *z;
+ int n = sqlite3_value_bytes(argv[0]);
+ SQLTesterJni * const p = (SQLTesterJni *)sqlite3_user_data(context);
+ S3JniDeclLocal_env;
+
+ ++p->nDup;
+ if( n>0 && (pOut = s3jni_malloc( (n+16)&~7 ))!=0 ){
+ pOut[0] = 0x2bbf4b7c;
+ z = (char*)&pOut[1];
+ memcpy(z, sqlite3_value_text(argv[0]), n);
+ z[n] = 0;
+ sqlite3_result_text(context, z, n, SQLTester_dup_destructor);
+ }
+ return;
+}
+
+/*
+** Return the number of calls to the dup() SQL function since the
+** SQLTester context was opened or since the last dup_count() call.
+*/
+static void SQLTester_dup_count_func(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ SQLTesterJni * const p = (SQLTesterJni *)sqlite3_user_data(context);
+ sqlite3_result_int64(context, p->nDup);
+ p->nDup = 0;
+}
+
+/*
+** Return non-zero if string z matches glob pattern zGlob and zero if the
+** pattern does not match.
+**
+** To repeat:
+**
+** zero == no match
+** non-zero == match
+**
+** Globbing rules:
+**
+** '*' Matches any sequence of zero or more characters.
+**
+** '?' Matches exactly one character.
+**
+** [...] Matches one character from the enclosed list of
+** characters.
+**
+** [^...] Matches one character not in the enclosed list.
+**
+** '#' Matches any sequence of one or more digits with an
+** optional + or - sign in front, or a hexadecimal
+** literal of the form 0x...
+*/
+static int SQLTester_strnotglob(const char *zGlob, const char *z){
+ int c, c2;
+ int invert;
+ int seen;
+
+ while( (c = (*(zGlob++)))!=0 ){
+ if( c=='*' ){
+ while( (c=(*(zGlob++))) == '*' || c=='?' ){
+ if( c=='?' && (*(z++))==0 ) return 0;
+ }
+ if( c==0 ){
+ return 1;
+ }else if( c=='[' ){
+ while( *z && SQLTester_strnotglob(zGlob-1,z)==0 ){
+ z++;
+ }
+ return (*z)!=0;
+ }
+ while( (c2 = (*(z++)))!=0 ){
+ while( c2!=c ){
+ c2 = *(z++);
+ if( c2==0 ) return 0;
+ }
+ if( SQLTester_strnotglob(zGlob,z) ) return 1;
+ }
+ return 0;
+ }else if( c=='?' ){
+ if( (*(z++))==0 ) return 0;
+ }else if( c=='[' ){
+ int prior_c = 0;
+ seen = 0;
+ invert = 0;
+ c = *(z++);
+ if( c==0 ) return 0;
+ c2 = *(zGlob++);
+ if( c2=='^' ){
+ invert = 1;
+ c2 = *(zGlob++);
+ }
+ if( c2==']' ){
+ if( c==']' ) seen = 1;
+ c2 = *(zGlob++);
+ }
+ while( c2 && c2!=']' ){
+ if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
+ c2 = *(zGlob++);
+ if( c>=prior_c && c<=c2 ) seen = 1;
+ prior_c = 0;
+ }else{
+ if( c==c2 ){
+ seen = 1;
+ }
+ prior_c = c2;
+ }
+ c2 = *(zGlob++);
+ }
+ if( c2==0 || (seen ^ invert)==0 ) return 0;
+ }else if( c=='#' ){
+ if( z[0]=='0'
+ && (z[1]=='x' || z[1]=='X')
+ && sqlite3Isxdigit(z[2])
+ ){
+ z += 3;
+ while( sqlite3Isxdigit(z[0]) ){ z++; }
+ }else{
+ if( (z[0]=='-' || z[0]=='+') && sqlite3Isdigit(z[1]) ) z++;
+ if( !sqlite3Isdigit(z[0]) ) return 0;
+ z++;
+ while( sqlite3Isdigit(z[0]) ){ z++; }
+ }
+ }else{
+ if( c!=(*(z++)) ) return 0;
+ }
+ }
+ return *z==0;
+}
+
+JNIEXPORT jint JNICALL
+Java_org_sqlite_jni_capi_SQLTester_strglob(
+ JniArgsEnvClass, jbyteArray baG, jbyteArray baT
+){
+ int rc = 0;
+ jbyte * const pG = s3jni_jbyteArray_bytes(baG);
+ jbyte * const pT = pG ? s3jni_jbyteArray_bytes(baT) : 0;
+
+ s3jni_oom_fatal(pT);
+ /* Note that we're relying on the byte arrays having been
+ NUL-terminated on the Java side. */
+ rc = !SQLTester_strnotglob((const char *)pG, (const char *)pT);
+ s3jni_jbyteArray_release(baG, pG);
+ s3jni_jbyteArray_release(baT, pT);
+ return rc;
+}
+
+
+static int SQLTester_auto_extension(sqlite3 *pDb, const char **pzErr,
+ const struct sqlite3_api_routines *ignored){
+ sqlite3_create_function(pDb, "dup", 1, SQLITE_UTF8, &SQLTester,
+ SQLTester_dup_func, 0, 0);
+ sqlite3_create_function(pDb, "dup_count", 0, SQLITE_UTF8, &SQLTester,
+ SQLTester_dup_count_func, 0, 0);
+ return 0;
+}
+
+JNIEXPORT void JNICALL
+Java_org_sqlite_jni_capi_SQLTester_installCustomExtensions(JniArgsEnvClass){
+ sqlite3_auto_extension( (void(*)(void))SQLTester_auto_extension );
+}
+
+#endif /* SQLITE_JNI_ENABLE_SQLTester */
+////////////////////////////////////////////////////////////////////////
+// End of SQLTester bindings. Start of lower-level bits.
+////////////////////////////////////////////////////////////////////////
+
+/*
+** Called during static init of the CApi class to set up global
+** state.
+*/
+JNIEXPORT void JNICALL
+Java_org_sqlite_jni_capi_CApi_init(JniArgsEnvClass){
+ jclass klazz;
+
+ memset(&S3JniGlobal, 0, sizeof(S3JniGlobal));
+ if( (*env)->GetJavaVM(env, &SJG.jvm) ){
+ (*env)->FatalError(env, "GetJavaVM() failure shouldn't be possible.");
+ return;
+ }
+
+ /* Grab references to various global classes and objects... */
+ SJG.g.cLong = S3JniRefGlobal((*env)->FindClass(env,"java/lang/Long"));
+ S3JniExceptionIsFatal("Error getting reference to Long class.");
+ SJG.g.ctorLong1 = (*env)->GetMethodID(env, SJG.g.cLong,
+ "", "(J)V");
+ S3JniExceptionIsFatal("Error getting reference to Long constructor.");
+
+ SJG.g.cString = S3JniRefGlobal((*env)->FindClass(env,"java/lang/String"));
+ S3JniExceptionIsFatal("Error getting reference to String class.");
+ SJG.g.ctorStringBA =
+ (*env)->GetMethodID(env, SJG.g.cString,
+ "", "([BLjava/nio/charset/Charset;)V");
+ S3JniExceptionIsFatal("Error getting reference to String(byte[],Charset) ctor.");
+ SJG.g.stringGetBytes =
+ (*env)->GetMethodID(env, SJG.g.cString,
+ "getBytes", "(Ljava/nio/charset/Charset;)[B");
+ S3JniExceptionIsFatal("Error getting reference to String.getBytes(Charset).");
+
+ { /* java.nio.charset.StandardCharsets.UTF_8 */
+ jfieldID fUtf8;
+ klazz = (*env)->FindClass(env,"java/nio/charset/StandardCharsets");
+ S3JniExceptionIsFatal("Error getting reference to StandardCharsets class.");
+ fUtf8 = (*env)->GetStaticFieldID(env, klazz, "UTF_8",
+ "Ljava/nio/charset/Charset;");
+ S3JniExceptionIsFatal("Error getting StandardCharsets.UTF_8 field.");
+ SJG.g.oCharsetUtf8 =
+ S3JniRefGlobal((*env)->GetStaticObjectField(env, klazz, fUtf8));
+ S3JniExceptionIsFatal("Error getting reference to StandardCharsets.UTF_8.");
+ S3JniUnrefLocal(klazz);
+ }
+
+#ifdef SQLITE_ENABLE_FTS5
+ klazz = (*env)->FindClass(env, "org/sqlite/jni/fts5/Fts5PhraseIter");
+ S3JniExceptionIsFatal("Error getting reference to org.sqlite.jni.fts5.Fts5PhraseIter.");
+ SJG.fts5.jPhraseIter.fidA = (*env)->GetFieldID(env, klazz, "a", "J");
+ S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.a field.");
+ SJG.fts5.jPhraseIter.fidB = (*env)->GetFieldID(env, klazz, "b", "J");
+ S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.b field.");
+ S3JniUnrefLocal(klazz);
+#endif
+
+ SJG.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.mutex );
+ SJG.hook.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.hook.mutex );
+ SJG.nph.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.nph.mutex );
+ SJG.envCache.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.envCache.mutex );
+ SJG.perDb.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.perDb.mutex );
+ SJG.autoExt.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.autoExt.mutex );
+
+#if S3JNI_METRICS_MUTEX
+ SJG.metrics.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.metrics.mutex );
+#endif
+
+ sqlite3_shutdown()
+ /* So that it becomes legal for Java-level code to call
+ ** sqlite3_config(). */;
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/c/sqlite3-jni.h sqlite3-3.44.0-0/ext/jni/src/c/sqlite3-jni.h
--- sqlite3-3.41.0-0/ext/jni/src/c/sqlite3-jni.h 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/c/sqlite3-jni.h 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,2379 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class org_sqlite_jni_capi_CApi */
+
+#ifndef _Included_org_sqlite_jni_capi_CApi
+#define _Included_org_sqlite_jni_capi_CApi
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef org_sqlite_jni_capi_CApi_SQLITE_ACCESS_EXISTS
+#define org_sqlite_jni_capi_CApi_SQLITE_ACCESS_EXISTS 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READWRITE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READ 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DENY
+#define org_sqlite_jni_capi_CApi_SQLITE_DENY 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IGNORE
+#define org_sqlite_jni_capi_CApi_SQLITE_IGNORE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_INDEX 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TABLE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_INDEX 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TABLE 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TRIGGER 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_VIEW 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TRIGGER 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_VIEW 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DELETE
+#define org_sqlite_jni_capi_CApi_SQLITE_DELETE 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_INDEX 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TABLE 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_INDEX 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TABLE 13L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TRIGGER 14L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_VIEW 15L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TRIGGER 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_VIEW 17L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INSERT
+#define org_sqlite_jni_capi_CApi_SQLITE_INSERT 18L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PRAGMA
+#define org_sqlite_jni_capi_CApi_SQLITE_PRAGMA 19L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_READ 20L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SELECT
+#define org_sqlite_jni_capi_CApi_SQLITE_SELECT 21L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRANSACTION
+#define org_sqlite_jni_capi_CApi_SQLITE_TRANSACTION 22L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UPDATE
+#define org_sqlite_jni_capi_CApi_SQLITE_UPDATE 23L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ATTACH
+#define org_sqlite_jni_capi_CApi_SQLITE_ATTACH 24L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DETACH
+#define org_sqlite_jni_capi_CApi_SQLITE_DETACH 25L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ALTER_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_ALTER_TABLE 26L
+#undef org_sqlite_jni_capi_CApi_SQLITE_REINDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_REINDEX 27L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ANALYZE
+#define org_sqlite_jni_capi_CApi_SQLITE_ANALYZE 28L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_VTABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_VTABLE 29L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_VTABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_VTABLE 30L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FUNCTION
+#define org_sqlite_jni_capi_CApi_SQLITE_FUNCTION 31L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SAVEPOINT
+#define org_sqlite_jni_capi_CApi_SQLITE_SAVEPOINT 32L
+#undef org_sqlite_jni_capi_CApi_SQLITE_RECURSIVE
+#define org_sqlite_jni_capi_CApi_SQLITE_RECURSIVE 33L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATIC
+#define org_sqlite_jni_capi_CApi_SQLITE_STATIC 0LL
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRANSIENT
+#define org_sqlite_jni_capi_CApi_SQLITE_TRANSIENT -1LL
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETSTART_INVERT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETSTART_INVERT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_NOSAVEPOINT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_NOSAVEPOINT 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_INVERT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_INVERT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_IGNORENOOP
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_IGNORENOOP 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_DATA
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_DATA 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_NOTFOUND
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_NOTFOUND 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONFLICT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONFLICT 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONSTRAINT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONSTRAINT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_FOREIGN_KEY
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_FOREIGN_KEY 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_OMIT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_OMIT 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_REPLACE
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_REPLACE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_ABORT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_ABORT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SINGLETHREAD
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SINGLETHREAD 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MULTITHREAD
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MULTITHREAD 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SERIALIZED
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SERIALIZED 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MALLOC
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MALLOC 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMALLOC
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMALLOC 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SCRATCH
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SCRATCH 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PAGECACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PAGECACHE 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_HEAP
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_HEAP 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMSTATUS
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMSTATUS 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MUTEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MUTEX 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMUTEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMUTEX 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOOKASIDE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOOKASIDE 13L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE 14L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE 15L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOG
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOG 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_URI
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_URI 17L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE2
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE2 18L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE2
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE2 19L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_COVERING_INDEX_SCAN
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_COVERING_INDEX_SCAN 20L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SQLLOG
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SQLLOG 21L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MMAP_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MMAP_SIZE 22L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_WIN32_HEAPSIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_WIN32_HEAPSIZE 23L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE_HDRSZ
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE_HDRSZ 24L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PMASZ
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PMASZ 25L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_STMTJRNL_SPILL
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_STMTJRNL_SPILL 26L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SMALL_MALLOC
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SMALL_MALLOC 27L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SORTERREF_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SORTERREF_SIZE 28L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMDB_MAXSIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMDB_MAXSIZE 29L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INTEGER
+#define org_sqlite_jni_capi_CApi_SQLITE_INTEGER 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FLOAT
+#define org_sqlite_jni_capi_CApi_SQLITE_FLOAT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TEXT
+#define org_sqlite_jni_capi_CApi_SQLITE_TEXT 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BLOB
+#define org_sqlite_jni_capi_CApi_SQLITE_BLOB 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NULL
+#define org_sqlite_jni_capi_CApi_SQLITE_NULL 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAINDBNAME
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAINDBNAME 1000L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LOOKASIDE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LOOKASIDE 1001L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FKEY
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FKEY 1002L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_TRIGGER 1003L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_QPSG
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_QPSG 1007L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRIGGER_EQP
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRIGGER_EQP 1008L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_RESET_DATABASE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_RESET_DATABASE 1009L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DEFENSIVE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DEFENSIVE 1010L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_WRITABLE_SCHEMA
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DML
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DML 1013L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DDL
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DDL 1014L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_VIEW 1015L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRUSTED_SCHEMA
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_STMT_SCANSTATUS
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_STMT_SCANSTATUS 1018L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_REVERSE_SCANORDER
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_REVERSE_SCANORDER 1019L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAX
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAX 1019L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_USED 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_SCHEMA_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_SCHEMA_USED 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_STMT_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_STMT_USED 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_HIT
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_HIT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_HIT
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_HIT 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_MISS
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_MISS 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_WRITE 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_DEFERRED_FKS
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_DEFERRED_FKS 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED_SHARED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED_SHARED 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_SPILL
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_SPILL 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_MAX
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_MAX 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF8
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF8 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16LE
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF16LE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16BE
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF16BE 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF16 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16_ALIGNED
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF16_ALIGNED 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCKSTATE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCKSTATE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_GET_LOCKPROXYFILE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_GET_LOCKPROXYFILE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SET_LOCKPROXYFILE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SET_LOCKPROXYFILE 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LAST_ERRNO
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LAST_ERRNO 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_HINT
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_HINT 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CHUNK_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CHUNK_SIZE 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_FILE_POINTER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_FILE_POINTER 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC_OMITTED
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC_OMITTED 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_AV_RETRY
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_AV_RETRY 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PERSIST_WAL
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PERSIST_WAL 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_OVERWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_OVERWRITE 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFSNAME
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFSNAME 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_POWERSAFE_OVERWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_POWERSAFE_OVERWRITE 13L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PRAGMA
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PRAGMA 14L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BUSYHANDLER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BUSYHANDLER 15L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TEMPFILENAME
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TEMPFILENAME 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_MMAP_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_MMAP_SIZE 18L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TRACE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TRACE 19L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_HAS_MOVED
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_HAS_MOVED 20L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC 21L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_PHASETWO
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_PHASETWO 22L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_SET_HANDLE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_SET_HANDLE 23L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WAL_BLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WAL_BLOCK 24L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ZIPVFS
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ZIPVFS 25L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RBU
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RBU 26L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFS_POINTER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFS_POINTER 27L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_JOURNAL_POINTER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_JOURNAL_POINTER 28L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_GET_HANDLE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_GET_HANDLE 29L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PDB
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PDB 30L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCK_TIMEOUT
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCK_TIMEOUT 34L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_DATA_VERSION
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_DATA_VERSION 35L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_LIMIT
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_LIMIT 36L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_DONE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_DONE 37L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESERVE_BYTES
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESERVE_BYTES 38L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_START
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_START 39L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_EXTERNAL_READER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_EXTERNAL_READER 40L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKSM_FILE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKSM_FILE 41L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESET_CACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESET_CACHE 42L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_NONE
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_NONE 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_SHARED
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_SHARED 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_RESERVED
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_RESERVED 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_PENDING
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_PENDING 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_EXCLUSIVE
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_EXCLUSIVE 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC512
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC512 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC1K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC1K 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC2K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC2K 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC4K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC4K 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC8K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC8K 32L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC16K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC16K 64L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC32K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC32K 128L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC64K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC64K 256L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SAFE_APPEND
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SAFE_APPEND 512L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SEQUENTIAL
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SEQUENTIAL 1024L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 2048L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_POWERSAFE_OVERWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_POWERSAFE_OVERWRITE 4096L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_IMMUTABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_IMMUTABLE 8192L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_BATCH_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_BATCH_ATOMIC 16384L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LENGTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LENGTH 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_SQL_LENGTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_SQL_LENGTH 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COLUMN
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COLUMN 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_EXPR_DEPTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_EXPR_DEPTH 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COMPOUND_SELECT
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COMPOUND_SELECT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VDBE_OP
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VDBE_OP 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_FUNCTION_ARG
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_FUNCTION_ARG 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_ATTACHED
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_ATTACHED 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LIKE_PATTERN_LENGTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LIKE_PATTERN_LENGTH 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VARIABLE_NUMBER
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VARIABLE_NUMBER 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_TRIGGER_DEPTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_TRIGGER_DEPTH 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_WORKER_THREADS
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_WORKER_THREADS 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_READONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_READONLY 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_READWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_READWRITE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_CREATE
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_CREATE 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_URI
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_URI 64L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_MEMORY
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_MEMORY 128L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOMUTEX
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOMUTEX 32768L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_FULLMUTEX
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_FULLMUTEX 65536L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_SHAREDCACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_SHAREDCACHE 131072L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_PRIVATECACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_PRIVATECACHE 262144L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOFOLLOW
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOFOLLOW 16777216L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_EXRESCODE
+#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
+#define org_sqlite_jni_capi_CApi_SQLITE_OK 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR
+#define org_sqlite_jni_capi_CApi_SQLITE_ERROR 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INTERNAL
+#define org_sqlite_jni_capi_CApi_SQLITE_INTERNAL 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PERM
+#define org_sqlite_jni_capi_CApi_SQLITE_PERM 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ABORT
+#define org_sqlite_jni_capi_CApi_SQLITE_ABORT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY
+#define org_sqlite_jni_capi_CApi_SQLITE_BUSY 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCKED
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCKED 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOMEM
+#define org_sqlite_jni_capi_CApi_SQLITE_NOMEM 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INTERRUPT
+#define org_sqlite_jni_capi_CApi_SQLITE_INTERRUPT 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT
+#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTFOUND
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTFOUND 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FULL
+#define org_sqlite_jni_capi_CApi_SQLITE_FULL 13L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN 14L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PROTOCOL
+#define org_sqlite_jni_capi_CApi_SQLITE_PROTOCOL 15L
+#undef org_sqlite_jni_capi_CApi_SQLITE_EMPTY
+#define org_sqlite_jni_capi_CApi_SQLITE_EMPTY 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SCHEMA
+#define org_sqlite_jni_capi_CApi_SQLITE_SCHEMA 17L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TOOBIG
+#define org_sqlite_jni_capi_CApi_SQLITE_TOOBIG 18L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT 19L
+#undef org_sqlite_jni_capi_CApi_SQLITE_MISMATCH
+#define org_sqlite_jni_capi_CApi_SQLITE_MISMATCH 20L
+#undef org_sqlite_jni_capi_CApi_SQLITE_MISUSE
+#define org_sqlite_jni_capi_CApi_SQLITE_MISUSE 21L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOLFS
+#define org_sqlite_jni_capi_CApi_SQLITE_NOLFS 22L
+#undef org_sqlite_jni_capi_CApi_SQLITE_AUTH
+#define org_sqlite_jni_capi_CApi_SQLITE_AUTH 23L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FORMAT
+#define org_sqlite_jni_capi_CApi_SQLITE_FORMAT 24L
+#undef org_sqlite_jni_capi_CApi_SQLITE_RANGE
+#define org_sqlite_jni_capi_CApi_SQLITE_RANGE 25L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTADB
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTADB 26L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTICE
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTICE 27L
+#undef org_sqlite_jni_capi_CApi_SQLITE_WARNING
+#define org_sqlite_jni_capi_CApi_SQLITE_WARNING 28L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ROW
+#define org_sqlite_jni_capi_CApi_SQLITE_ROW 100L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DONE
+#define org_sqlite_jni_capi_CApi_SQLITE_DONE 101L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR_MISSING_COLLSEQ
+#define org_sqlite_jni_capi_CApi_SQLITE_ERROR_MISSING_COLLSEQ 257L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR_RETRY
+#define org_sqlite_jni_capi_CApi_SQLITE_ERROR_RETRY 513L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR_SNAPSHOT
+#define org_sqlite_jni_capi_CApi_SQLITE_ERROR_SNAPSHOT 769L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_READ 266L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHORT_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHORT_READ 522L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_WRITE 778L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSYNC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSYNC 1034L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_FSYNC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_FSYNC 1290L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_TRUNCATE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_TRUNCATE 1546L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSTAT
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSTAT 1802L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_UNLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_UNLOCK 2058L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_RDLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_RDLOCK 2314L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE 2570L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_BLOCKED
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_BLOCKED 2826L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_NOMEM
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_NOMEM 3082L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_ACCESS
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_ACCESS 3338L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CHECKRESERVEDLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CHECKRESERVEDLOCK 3594L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_LOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_LOCK 3850L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CLOSE 4106L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_CLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_CLOSE 4362L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMOPEN
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMOPEN 4618L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMSIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMSIZE 4874L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMLOCK 5130L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMMAP
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMMAP 5386L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SEEK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SEEK 5642L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE_NOENT
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE_NOENT 5898L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_MMAP
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_MMAP 6154L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_GETTEMPPATH
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_GETTEMPPATH 6410L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CONVPATH
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CONVPATH 6666L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_VNODE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_VNODE 6922L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_AUTH
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_AUTH 7178L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_BEGIN_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_BEGIN_ATOMIC 7434L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_COMMIT_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_COMMIT_ATOMIC 7690L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_ROLLBACK_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_ROLLBACK_ATOMIC 7946L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DATA
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DATA 8202L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CORRUPTFS
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CORRUPTFS 8458L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCKED_SHAREDCACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCKED_SHAREDCACHE 262L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCKED_VTAB
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCKED_VTAB 518L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY_RECOVERY
+#define org_sqlite_jni_capi_CApi_SQLITE_BUSY_RECOVERY 261L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY_SNAPSHOT
+#define org_sqlite_jni_capi_CApi_SQLITE_BUSY_SNAPSHOT 517L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY_TIMEOUT
+#define org_sqlite_jni_capi_CApi_SQLITE_BUSY_TIMEOUT 773L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_NOTEMPDIR
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_NOTEMPDIR 270L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_ISDIR
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_ISDIR 526L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_FULLPATH
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_FULLPATH 782L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_CONVPATH
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_CONVPATH 1038L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_SYMLINK
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_SYMLINK 1550L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_VTAB
+#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_VTAB 267L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_SEQUENCE
+#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_SEQUENCE 523L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_INDEX 779L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_RECOVERY
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_RECOVERY 264L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTLOCK 520L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_ROLLBACK
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_ROLLBACK 776L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_DBMOVED
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_DBMOVED 1032L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTINIT
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTINIT 1288L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_DIRECTORY
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_DIRECTORY 1544L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ABORT_ROLLBACK
+#define org_sqlite_jni_capi_CApi_SQLITE_ABORT_ROLLBACK 516L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_CHECK
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_CHECK 275L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_COMMITHOOK
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_COMMITHOOK 531L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FOREIGNKEY
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FOREIGNKEY 787L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FUNCTION
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FUNCTION 1043L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_NOTNULL
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_NOTNULL 1299L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PRIMARYKEY
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PRIMARYKEY 1555L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_TRIGGER 1811L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_UNIQUE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_UNIQUE 2067L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_VTAB
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_VTAB 2323L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_ROWID
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_ROWID 2579L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PINNED
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PINNED 2835L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_DATATYPE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_DATATYPE 3091L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_WAL
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_WAL 283L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_ROLLBACK
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_ROLLBACK 539L
+#undef org_sqlite_jni_capi_CApi_SQLITE_WARNING_AUTOINDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_WARNING_AUTOINDEX 284L
+#undef org_sqlite_jni_capi_CApi_SQLITE_AUTH_USER
+#define org_sqlite_jni_capi_CApi_SQLITE_AUTH_USER 279L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OK_LOAD_PERMANENTLY
+#define org_sqlite_jni_capi_CApi_SQLITE_OK_LOAD_PERMANENTLY 256L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SERIALIZE_NOCOPY
+#define org_sqlite_jni_capi_CApi_SQLITE_SERIALIZE_NOCOPY 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_FREEONCLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_FREEONCLOSE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_READONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_READONLY 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_RESIZEABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_RESIZEABLE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SESSION_CONFIG_STRMSIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_SESSION_CONFIG_STRMSIZE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SESSION_OBJCONFIG_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_SESSION_OBJCONFIG_SIZE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_MEMORY_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_MEMORY_USED 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_USED 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_OVERFLOW
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_OVERFLOW 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_SIZE 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PARSER_STACK
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PARSER_STACK 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_SIZE 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_COUNT
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_COUNT 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FULLSCAN_STEP
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FULLSCAN_STEP 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_SORT
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_SORT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_AUTOINDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_AUTOINDEX 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_VM_STEP
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_VM_STEP 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_REPREPARE
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_REPREPARE 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_RUN
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_RUN 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_MISS
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_MISS 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_HIT
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_HIT 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_MEMUSED
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_MEMUSED 99L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SYNC_NORMAL
+#define org_sqlite_jni_capi_CApi_SQLITE_SYNC_NORMAL 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SYNC_FULL
+#define org_sqlite_jni_capi_CApi_SQLITE_SYNC_FULL 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SYNC_DATAONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_SYNC_DATAONLY 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_STMT
+#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_STMT 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_PROFILE
+#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_PROFILE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_ROW
+#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_ROW 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_CLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_CLOSE 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TXN_NONE
+#define org_sqlite_jni_capi_CApi_SQLITE_TXN_NONE 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TXN_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_TXN_READ 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TXN_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_TXN_WRITE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DETERMINISTIC
+#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_INNOCUOUS
+#define org_sqlite_jni_capi_CApi_SQLITE_INNOCUOUS 2097152L
+#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
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_EQ 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GT
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LE
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LE 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LT
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LT 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GE
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GE 32L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_MATCH
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_MATCH 64L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIKE
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIKE 65L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GLOB
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GLOB 66L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_REGEXP
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_REGEXP 67L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_NE
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_NE 68L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOT
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOT 69L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOTNULL
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNULL
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNULL 71L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_IS
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_IS 72L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIMIT
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIMIT 73L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_OFFSET
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_OFFSET 74L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_FUNCTION
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_FUNCTION 150L
+#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_CONSTRAINT_SUPPORT
+#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_CONSTRAINT_SUPPORT 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_INNOCUOUS
+#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_INNOCUOUS 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_DIRECTONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_DIRECTONLY 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_USES_ALL_SCHEMAS
+#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_USES_ALL_SCHEMAS 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ROLLBACK
+#define org_sqlite_jni_capi_CApi_SQLITE_ROLLBACK 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FAIL
+#define org_sqlite_jni_capi_CApi_SQLITE_FAIL 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_REPLACE
+#define org_sqlite_jni_capi_CApi_SQLITE_REPLACE 5L
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: init
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_init
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_java_uncache_thread
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1java_1uncache_1thread
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_aggregate_context
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Z)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1aggregate_1context
+ (JNIEnv *, jclass, jobject, jboolean);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_auto_extension
+ * Signature: (Lorg/sqlite/jni/capi/AutoExtensionCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1auto_1extension
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_backup_finish
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1finish
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_backup_init
+ * Signature: (JLjava/lang/String;JLjava/lang/String;)Lorg/sqlite/jni/capi/sqlite3_backup;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1init
+ (JNIEnv *, jclass, jlong, jstring, jlong, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_backup_pagecount
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1pagecount
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_backup_remaining
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1remaining
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_backup_step
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1step
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_blob
+ * Signature: (JI[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1blob
+ (JNIEnv *, jclass, jlong, jint, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_double
+ * Signature: (JID)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1double
+ (JNIEnv *, jclass, jlong, jint, jdouble);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_int
+ * Signature: (JII)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1int
+ (JNIEnv *, jclass, jlong, jint, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_int64
+ * Signature: (JIJ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1int64
+ (JNIEnv *, jclass, jlong, jint, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_java_object
+ * Signature: (JILjava/lang/Object;)I
+ */
+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_null
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1null
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_parameter_count
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1parameter_1count
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_parameter_index
+ * Signature: (J[B)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1parameter_1index
+ (JNIEnv *, jclass, jlong, jbyteArray);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_parameter_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1parameter_1name
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_text
+ * Signature: (JI[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1text
+ (JNIEnv *, jclass, jlong, jint, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_text16
+ * Signature: (JI[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1text16
+ (JNIEnv *, jclass, jlong, jint, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_value
+ * Signature: (JIJ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1value
+ (JNIEnv *, jclass, jlong, jint, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_zeroblob
+ * Signature: (JII)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1zeroblob
+ (JNIEnv *, jclass, jlong, jint, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_zeroblob64
+ * Signature: (JIJ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1zeroblob64
+ (JNIEnv *, jclass, jlong, jint, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_blob_bytes
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1bytes
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_blob_close
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1close
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_blob_open
+ * Signature: (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;JILorg/sqlite/jni/capi/OutputPointer/sqlite3_blob;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1open
+ (JNIEnv *, jclass, jlong, jstring, jstring, jstring, jlong, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_blob_read
+ * Signature: (J[BI)I
+ */
+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_reopen
+ * Signature: (JJ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1reopen
+ (JNIEnv *, jclass, jlong, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_blob_write
+ * Signature: (J[BI)I
+ */
+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_busy_handler
+ * Signature: (JLorg/sqlite/jni/capi/BusyHandlerCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1busy_1handler
+ (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_busy_timeout
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1busy_1timeout
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_cancel_auto_extension
+ * Signature: (Lorg/sqlite/jni/capi/AutoExtensionCallback;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1cancel_1auto_1extension
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_changes
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1changes
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_changes64
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1changes64
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_clear_bindings
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1clear_1bindings
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_close
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1close
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_close_v2
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1close_1v2
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_blob
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1blob
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_bytes
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1bytes
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_bytes16
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1bytes16
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_count
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1count
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_decltype
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1decltype
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_double
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)D
+ */
+JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1double
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_int
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1int
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_int64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)J
+ */
+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_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1name
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * 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_origin_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1origin_1name
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_table_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1table_1name
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_text
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1text
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_text16
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1text16
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_type
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1type
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_value
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)Lorg/sqlite/jni/capi/sqlite3_value;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1value
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_collation_needed
+ * Signature: (JLorg/sqlite/jni/capi/CollationNeededCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1collation_1needed
+ (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_commit_hook
+ * Signature: (JLorg/sqlite/jni/capi/CommitHookCallback;)Lorg/sqlite/jni/capi/CommitHookCallback;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1commit_1hook
+ (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_compileoption_get
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1compileoption_1get
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_compileoption_used
+ * Signature: (Ljava/lang/String;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1compileoption_1used
+ (JNIEnv *, jclass, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_complete
+ * Signature: ([B)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1complete
+ (JNIEnv *, jclass, jbyteArray);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_config
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__I
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_config
+ * Signature: (Lorg/sqlite/jni/capi/ConfigSqllogCallback;)I
+ */
+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
+ * Signature: (Lorg/sqlite/jni/capi/ConfigLogCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__Lorg_sqlite_jni_capi_ConfigLogCallback_2
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_context_db_handle
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)Lorg/sqlite/jni/capi/sqlite3;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1context_1db_1handle
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_create_collation
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;ILorg/sqlite/jni/capi/CollationCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1create_1collation
+ (JNIEnv *, jclass, jobject, jstring, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_create_function
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;IILorg/sqlite/jni/capi/SQLFunction;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1create_1function
+ (JNIEnv *, jclass, jobject, jstring, jint, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_data_count
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1data_1count
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_config
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;IILorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_Int32_2
+ (JNIEnv *, jclass, jobject, jint, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_config
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1config__Lorg_sqlite_jni_capi_sqlite3_2ILjava_lang_String_2
+ (JNIEnv *, jclass, jobject, jint, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1name
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_filename
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1filename
+ (JNIEnv *, jclass, jobject, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_handle
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Lorg/sqlite/jni/capi/sqlite3;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1handle
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_readonly
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1readonly
+ (JNIEnv *, jclass, jobject, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_release_memory
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1release_1memory
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_status
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;Z)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1status
+ (JNIEnv *, jclass, jobject, jint, jobject, jobject, jboolean);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_errcode
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1errcode
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_errmsg
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1errmsg
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_error_offset
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1error_1offset
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_errstr
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1errstr
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_expanded_sql
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1expanded_1sql
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_extended_errcode
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1errcode
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_extended_result_codes
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Z)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1result_1codes
+ (JNIEnv *, jclass, jobject, jboolean);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_get_autocommit
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1get_1autocommit
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_get_auxdata
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1get_1auxdata
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_finalize
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1finalize
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_initialize
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1initialize
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_interrupt
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1interrupt
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_is_interrupted
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1is_1interrupted
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_keyword_check
+ * Signature: (Ljava/lang/String;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1keyword_1check
+ (JNIEnv *, jclass, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_keyword_count
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1keyword_1count
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_keyword_name
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1keyword_1name
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_last_insert_rowid
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1last_1insert_1rowid
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_libversion
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1libversion
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_libversion_number
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1libversion_1number
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_limit
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;II)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1limit
+ (JNIEnv *, jclass, jobject, jint, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_normalized_sql
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1normalized_1sql
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_open
+ * Signature: (Ljava/lang/String;Lorg/sqlite/jni/capi/OutputPointer/sqlite3;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1open
+ (JNIEnv *, jclass, jstring, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_open_v2
+ * Signature: (Ljava/lang/String;Lorg/sqlite/jni/capi/OutputPointer/sqlite3;ILjava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1open_1v2
+ (JNIEnv *, jclass, jstring, jobject, jint, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_prepare
+ * Signature: (J[BILorg/sqlite/jni/capi/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1prepare
+ (JNIEnv *, jclass, jlong, jbyteArray, jint, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_prepare_v2
+ * Signature: (J[BILorg/sqlite/jni/capi/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1prepare_1v2
+ (JNIEnv *, jclass, jlong, jbyteArray, jint, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_prepare_v3
+ * Signature: (J[BIILorg/sqlite/jni/capi/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1prepare_1v3
+ (JNIEnv *, jclass, jlong, jbyteArray, jint, jint, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_preupdate_blobwrite
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1blobwrite
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_preupdate_count
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1count
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_preupdate_depth
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1depth
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_preupdate_hook
+ * Signature: (JLorg/sqlite/jni/capi/PreupdateHookCallback;)Lorg/sqlite/jni/capi/PreupdateHookCallback;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1hook
+ (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_preupdate_new
+ * Signature: (JILorg/sqlite/jni/capi/OutputPointer/sqlite3_value;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1new
+ (JNIEnv *, jclass, jlong, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_preupdate_old
+ * Signature: (JILorg/sqlite/jni/capi/OutputPointer/sqlite3_value;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1old
+ (JNIEnv *, jclass, jlong, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_progress_handler
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILorg/sqlite/jni/capi/ProgressHandlerCallback;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1progress_1handler
+ (JNIEnv *, jclass, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_randomness
+ * Signature: ([B)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1randomness
+ (JNIEnv *, jclass, jbyteArray);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_release_memory
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1release_1memory
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_reset
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1reset
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_reset_auto_extension
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1reset_1auto_1extension
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_double
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;D)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1double
+ (JNIEnv *, jclass, jobject, jdouble);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_error
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BI)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error
+ (JNIEnv *, jclass, jobject, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_error_toobig
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error_1toobig
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_error_nomem
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error_1nomem
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_error_code
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V
+ */
+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
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1int
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_int64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;J)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1int64
+ (JNIEnv *, jclass, jobject, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_java_object
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Ljava/lang/Object;)V
+ */
+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_value
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Lorg/sqlite/jni/capi/sqlite3_value;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1value
+ (JNIEnv *, jclass, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_zeroblob
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1zeroblob
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_zeroblob64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1zeroblob64
+ (JNIEnv *, jclass, jobject, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_blob
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BI)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1blob
+ (JNIEnv *, jclass, jobject, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_blob64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BJ)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1blob64
+ (JNIEnv *, jclass, jobject, jbyteArray, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_text
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BI)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1text
+ (JNIEnv *, jclass, jobject, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_text64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BJI)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1text64
+ (JNIEnv *, jclass, jobject, jbyteArray, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_rollback_hook
+ * Signature: (JLorg/sqlite/jni/capi/RollbackHookCallback;)Lorg/sqlite/jni/capi/RollbackHookCallback;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1rollback_1hook
+ (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_set_authorizer
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Lorg/sqlite/jni/capi/AuthorizerCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1set_1authorizer
+ (JNIEnv *, jclass, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_set_auxdata
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;ILjava/lang/Object;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1set_1auxdata
+ (JNIEnv *, jclass, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_set_last_insert_rowid
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;J)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1set_1last_1insert_1rowid
+ (JNIEnv *, jclass, jobject, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_shutdown
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1shutdown
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_sleep
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1sleep
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_sourceid
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1sourceid
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_sql
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1sql
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_status
+ * Signature: (ILorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;Z)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1status
+ (JNIEnv *, jclass, jint, jobject, jobject, jboolean);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_status64
+ * Signature: (ILorg/sqlite/jni/capi/OutputPointer/Int64;Lorg/sqlite/jni/capi/OutputPointer/Int64;Z)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1status64
+ (JNIEnv *, jclass, jint, jobject, jobject, jboolean);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_step
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1step
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_stmt_busy
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1busy
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_stmt_explain
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1explain
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_stmt_isexplain
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1isexplain
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_stmt_readonly
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1readonly
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_stmt_status
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;IZ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1status
+ (JNIEnv *, jclass, jobject, jint, jboolean);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_strglob
+ * Signature: ([B[B)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1strglob
+ (JNIEnv *, jclass, jbyteArray, jbyteArray);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_strlike
+ * Signature: ([B[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1strlike
+ (JNIEnv *, jclass, jbyteArray, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_system_errno
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1system_1errno
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_table_column_metadata
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/sqlite/jni/capi/OutputPointer/String;Lorg/sqlite/jni/capi/OutputPointer/String;Lorg/sqlite/jni/capi/OutputPointer/Bool;Lorg/sqlite/jni/capi/OutputPointer/Bool;Lorg/sqlite/jni/capi/OutputPointer/Bool;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1table_1column_1metadata
+ (JNIEnv *, jclass, jobject, jstring, jstring, jstring, jobject, jobject, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_threadsafe
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1threadsafe
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_total_changes
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1total_1changes
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_total_changes64
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1total_1changes64
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_trace_v2
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILorg/sqlite/jni/capi/TraceV2Callback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1trace_1v2
+ (JNIEnv *, jclass, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_txn_state
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1txn_1state
+ (JNIEnv *, jclass, jobject, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_update_hook
+ * Signature: (JLorg/sqlite/jni/capi/UpdateHookCallback;)Lorg/sqlite/jni/capi/UpdateHookCallback;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1update_1hook
+ (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_blob
+ * Signature: (J)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1blob
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_bytes
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1bytes
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_bytes16
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1bytes16
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_double
+ * Signature: (J)D
+ */
+JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1double
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_dup
+ * Signature: (J)Lorg/sqlite/jni/capi/sqlite3_value;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1dup
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_encoding
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1encoding
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_free
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1free
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_frombind
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1frombind
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_int
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1int
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_int64
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1int64
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_java_object
+ * Signature: (J)Ljava/lang/Object;
+ */
+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_nochange
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1nochange
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_numeric_type
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1numeric_1type
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_subtype
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1subtype
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_text
+ * Signature: (J)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1text
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_text16
+ * Signature: (J)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1text16
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_type
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1type
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_jni_internal_details
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1internal_1details
+ (JNIEnv *, jclass);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class org_sqlite_jni_capi_SQLTester */
+
+#ifndef _Included_org_sqlite_jni_capi_SQLTester
+#define _Included_org_sqlite_jni_capi_SQLTester
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: org_sqlite_jni_capi_SQLTester
+ * Method: strglob
+ * Signature: ([B[B)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_SQLTester_strglob
+ (JNIEnv *, jclass, jbyteArray, jbyteArray);
+
+/*
+ * Class: org_sqlite_jni_capi_SQLTester
+ * Method: installCustomExtensions
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_SQLTester_installCustomExtensions
+ (JNIEnv *, jclass);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class org_sqlite_jni_fts5_Fts5ExtensionApi */
+
+#ifndef _Included_org_sqlite_jni_fts5_Fts5ExtensionApi
+#define _Included_org_sqlite_jni_fts5_Fts5ExtensionApi
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: getInstance
+ * Signature: ()Lorg/sqlite/jni/fts5/Fts5ExtensionApi;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_getInstance
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xColumnCount
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnCount
+ (JNIEnv *, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xColumnSize
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnSize
+ (JNIEnv *, jobject, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xColumnText
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnText
+ (JNIEnv *, jobject, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xColumnTotalSize
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/Int64;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnTotalSize
+ (JNIEnv *, jobject, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xGetAuxdata
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Z)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xGetAuxdata
+ (JNIEnv *, jobject, jobject, jboolean);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xInst
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xInst
+ (JNIEnv *, jobject, jobject, jint, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xInstCount
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xInstCount
+ (JNIEnv *, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xPhraseCount
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseCount
+ (JNIEnv *, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xPhraseFirst
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseFirst
+ (JNIEnv *, jobject, jobject, jint, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xPhraseFirstColumn
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseFirstColumn
+ (JNIEnv *, jobject, jobject, jint, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xPhraseNext
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseNext
+ (JNIEnv *, jobject, jobject, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xPhraseNextColumn
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseNextColumn
+ (JNIEnv *, jobject, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xPhraseSize
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;I)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseSize
+ (JNIEnv *, jobject, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xQueryPhrase
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/fts5/Fts5ExtensionApi/XQueryPhraseCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xQueryPhrase
+ (JNIEnv *, jobject, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xRowCount
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/capi/OutputPointer/Int64;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xRowCount
+ (JNIEnv *, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xRowid
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xRowid
+ (JNIEnv *, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xSetAuxdata
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Ljava/lang/Object;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xSetAuxdata
+ (JNIEnv *, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xTokenize
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;[BLorg/sqlite/jni/fts5/XTokenizeCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xTokenize
+ (JNIEnv *, jobject, jobject, jbyteArray, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xUserData
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xUserData
+ (JNIEnv *, jobject, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class org_sqlite_jni_fts5_fts5_api */
+
+#ifndef _Included_org_sqlite_jni_fts5_fts5_api
+#define _Included_org_sqlite_jni_fts5_fts5_api
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef org_sqlite_jni_fts5_fts5_api_iVersion
+#define org_sqlite_jni_fts5_fts5_api_iVersion 2L
+/*
+ * Class: org_sqlite_jni_fts5_fts5_api
+ * Method: getInstanceForDb
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)Lorg/sqlite/jni/fts5/fts5_api;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_fts5_1api_getInstanceForDb
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_fts5_api
+ * Method: xCreateFunction
+ * Signature: (Ljava/lang/String;Ljava/lang/Object;Lorg/sqlite/jni/fts5/fts5_extension_function;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_fts5_1api_xCreateFunction
+ (JNIEnv *, jobject, jstring, jobject, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class org_sqlite_jni_fts5_fts5_tokenizer */
+
+#ifndef _Included_org_sqlite_jni_fts5_fts5_tokenizer
+#define _Included_org_sqlite_jni_fts5_fts5_tokenizer
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: org_sqlite_jni_fts5_fts5_tokenizer
+ * Method: xTokenize
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Tokenizer;I[BLorg/sqlite/jni/fts5/XTokenizeCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_fts5_1tokenizer_xTokenize
+ (JNIEnv *, jobject, jobject, jint, jbyteArray, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/annotation/NotNull.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/annotation/NotNull.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/annotation/NotNull.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/annotation/NotNull.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,59 @@
+/*
+** 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 NotNull annotaion for the sqlite3 C API.
+*/
+package org.sqlite.jni.annotation;
+
+/**
+ This annotation is for flagging parameters which may not legally be
+ null or point to closed/finalized C-side resources.
+
+ In the case of Java types which map directly to C struct types
+ (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.
+
+ When used in the context of Java interfaces which are called
+ from the C APIs, this annotation communicates that the C API will
+ never pass a null value to the callback for that parameter.
+
+ Passing a null, for this annotation's definition of null, for
+ any parameter marked with this annoation specifically invokes
+ undefined behavior.
+
+ Passing 0 (i.e. C NULL) or a negative value for any long-type
+ parameter marked with this annoation specifically invokes undefined
+ behavior. Such values are treated as C pointers in the JNI
+ layer.
+
+ 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
+ semantics, but it may trigger NullPointerExceptions (or similar) if
+ passed a null for a parameter flagged with this annotation.
+
+ This annotation is informational only. No policy is in place to
+ programmatically ensure that NotNull is conformed to in client
+ code.
+
+ This annotation is solely for the use by the classes in the
+ 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.
+*/
+@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{}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/annotation/Nullable.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/annotation/Nullable.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/annotation/Nullable.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/annotation/Nullable.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,32 @@
+/*
+** 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 Nullable annotaion for the sqlite3 C API.
+*/
+package org.sqlite.jni.annotation;
+
+/**
+ This annotation is for flagging parameters which may legally be
+ null, noting that they may behave differently if passed null but
+ are prepared to expect null as a value. When used in the context of
+ callback methods which are called into from the C APIs, this
+ annotation communicates that the C API may pass a null value to the
+ callback.
+
+ This annotation is solely for the use by the classes in this
+ package 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.
+*/
+@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{}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/annotation/package-info.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/annotation/package-info.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/annotation/package-info.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/annotation/package-info.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,17 @@
+/*
+** 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 package houses annotations specific to the JNI bindings of the
+ SQLite3 C API.
+*/
+package org.sqlite.jni.annotation;
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,34 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import org.sqlite.jni.annotation.NotNull;
+
+/**
+ An implementation of {@link CollationCallback} which provides a
+ no-op xDestroy() method.
+*/
+public abstract class AbstractCollationCallback
+ implements CollationCallback, XDestroyCallback {
+ /**
+ Must compare the given byte arrays and return the result using
+ {@code memcmp()} semantics.
+ */
+ public abstract int call(@NotNull byte[] lhs, @NotNull byte[] rhs);
+
+ /**
+ Optionally override to be notified when the UDF is finalized by
+ SQLite. This implementation does nothing.
+ */
+ public void xDestroy(){}
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,72 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+
+/**
+ A SQLFunction implementation for aggregate functions. Its T is the
+ data type of its "accumulator" state, an instance of which is
+ intended to be be managed using the getAggregateState() and
+ takeAggregateState() methods.
+*/
+public abstract class AggregateFunction implements SQLFunction {
+
+ /**
+ As for the xStep() argument of the C API's
+ sqlite3_create_function(). If this function throws, the
+ exception is not propagated and a warning might be emitted to a
+ debugging channel.
+ */
+ public abstract void xStep(sqlite3_context cx, sqlite3_value[] args);
+
+ /**
+ As for the xFinal() argument of the C API's sqlite3_create_function().
+ If this function throws, it is translated into an sqlite3_result_error().
+ */
+ public abstract void xFinal(sqlite3_context cx);
+
+ /**
+ Optionally override to be notified when the UDF is finalized by
+ SQLite.
+ */
+ public void xDestroy() {}
+
+ /** Per-invocation state for the UDF. */
+ private final SQLFunction.PerContextState map =
+ new SQLFunction.PerContextState<>();
+
+ /**
+ To be called from the implementation's xStep() method, as well
+ as the xValue() and xInverse() methods of the {@link WindowFunction}
+ subclass, to fetch the current per-call UDF state. On the
+ first call to this method for any given sqlite3_context
+ argument, the context is set to the given initial value. On all other
+ calls, the 2nd argument is ignored.
+
+ @see SQLFunction.PerContextState#getAggregateState
+ */
+ protected final ValueHolder getAggregateState(sqlite3_context cx, T initialValue){
+ return map.getAggregateState(cx, initialValue);
+ }
+
+ /**
+ To be called from the implementation's xFinal() method to fetch
+ the final state of the UDF and remove its mapping.
+
+ see SQLFunction.PerContextState#takeAggregateState
+ */
+ protected final T takeAggregateState(sqlite3_context cx){
+ return map.takeAggregateState(cx);
+ }
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,28 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import org.sqlite.jni.annotation.*;
+
+/**
+ Callback for use with {@link CApi#sqlite3_set_authorizer}.
+*/
+public interface AuthorizerCallback extends CallbackProxy {
+ /**
+ Must function as described for the C-level
+ sqlite3_set_authorizer() callback.
+ */
+ int call(int opId, @Nullable String s1, @Nullable String s2,
+ @Nullable String s3, @Nullable String s4);
+
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,40 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with the {@link CApi#sqlite3_auto_extension}
+ family of APIs.
+*/
+public interface AutoExtensionCallback extends CallbackProxy {
+ /**
+ Must function as described for a C-level
+ sqlite3_auto_extension() callback.
+
+ This callback may throw and the exception's error message will
+ be set as the db's error string.
+
+
Tips for implementations:
+
+
- Opening a database from an auto-extension handler will lead to
+ an endless recursion of the auto-handler triggering itself
+ indirectly for each newly-opened database.
+
+
- If this routine is stateful, it may be useful to make the
+ overridden method synchronized.
+
+
- Results are undefined if the given db is closed by an auto-extension.
+ */
+ int call(sqlite3 db);
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,26 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_busy_handler}.
+*/
+public interface BusyHandlerCallback extends CallbackProxy {
+ /**
+ Must function as documented for the C-level
+ sqlite3_busy_handler() callback argument, minus the (void*)
+ argument the C-level function requires.
+ */
+ int call(int n);
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/CApi.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CApi.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/CApi.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CApi.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,2449 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file declares JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import java.nio.charset.StandardCharsets;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import org.sqlite.jni.annotation.*;
+import java.util.Arrays;
+
+/**
+ This class contains the entire C-style sqlite3 JNI API binding,
+ minus a few bits and pieces declared in other files. For client-side
+ use, a static import is recommended:
+
+
{@code
+ import static org.sqlite.jni.capi.CApi.*;
+ }
+
+ The C-side part can be found in sqlite3-jni.c.
+
+
This class is package-private in order to keep Java clients from
+ having direct access to the low-level C-style APIs, a design
+ decision made by Java developers based on the C-style API being
+ riddled with opportunities for Java developers to proverbially shoot
+ themselves in the foot with. Third-party copies of this code may
+ eliminate that guard by simply changing this class from
+ package-private to public. Its methods which are intended to be
+ exposed that way are all public.
+
+
Only functions which materially differ from their C counterparts
+ are documented here, and only those material differences are
+ documented. The C documentation is otherwise applicable for these
+ APIs:
+
+
https://sqlite.org/c3ref/intro.html
+
+
A handful of Java-specific APIs have been added which are
+ documented here. A number of convenience overloads are provided
+ which are not documented but whose semantics map 1-to-1 in an
+ intuitive manner. e.g. {@link
+ #sqlite3_result_set(sqlite3_context,int)} is equivalent to {@link
+ #sqlite3_result_int}, and sqlite3_result_set() has many
+ type-specific overloads.
+
+
Notes regarding Java's Modified UTF-8 vs standard UTF-8:
+
+
SQLite internally uses UTF-8 encoding, whereas Java natively uses
+ UTF-16. Java JNI has routines for converting to and from UTF-8,
+ but JNI uses what its docs call modified UTF-8 (see links below)
+ Care must be taken when converting Java strings to or from standard
+ UTF-8 to ensure that the proper conversion is performed. In short,
+ Java's {@code String.getBytes(StandardCharsets.UTF_8)} performs the proper
+ conversion in Java, and there are no JNI C APIs for that conversion
+ (JNI's {@code NewStringUTF()} requires its input to be in MUTF-8).
+
+
The known consequences and limitations this discrepancy places on
+ the SQLite3 JNI binding include:
+
+
+
+ - C functions which take C-style strings without a length argument
+ require special care when taking input from Java. In particular,
+ Java strings converted to byte arrays for encoding purposes are not
+ NUL-terminated, and conversion to a Java byte array must sometimes
+ be careful to add one. Functions which take a length do not require
+ this so long as the length is provided. Search the CApi class
+ for "\0" for many examples.
+
+
+
+ Further reading:
+
+
https://stackoverflow.com/questions/57419723
+
https://stackoverflow.com/questions/7921016
+
https://itecnote.com/tecnote/java-getting-true-utf-8-characters-in-java-jni/
+
https://docs.oracle.com/javase/8/docs/api/java/lang/Character.html#unicode
+
https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8
+
+*/
+public final class CApi {
+ static {
+ System.loadLibrary("sqlite3-jni");
+ }
+ //! Not used
+ private CApi(){}
+ //! Called from static init code.
+ private static native void init();
+
+ /**
+ Returns a nul-terminated copy of s as a UTF-8-encoded byte array,
+ or null if s is null.
+ */
+ private static byte[] nulTerminateUtf8(String s){
+ return null==s ? null : (s+"\0").getBytes(StandardCharsets.UTF_8);
+ }
+
+ /**
+ Each thread which uses the SQLite3 JNI APIs should call
+ sqlite3_jni_uncache_thread() when it is done with the library -
+ either right before it terminates or when it finishes using the
+ SQLite API. This will clean up any cached per-thread info.
+
+
This process does not close any databases or finalize
+ any prepared statements because their ownership does not depend on
+ a given thread. For proper library behavior, and to
+ avoid C-side leaks, be sure to finalize all statements and close
+ all databases before calling this function.
+
+
Calling this from the main application thread is not strictly
+ required. Additional threads must call this before ending or they
+ will leak cache entries in the C heap, which in turn may keep
+ numerous Java-side global references active.
+
+
This routine returns false without side effects if the current
+ JNIEnv is not cached, else returns true, but this information is
+ primarily for testing of the JNI bindings and is not information
+ which client-level code can use to make any informed decisions.
+ */
+ public static native boolean sqlite3_java_uncache_thread();
+
+ //////////////////////////////////////////////////////////////////////
+ // Maintenance reminder: please keep the sqlite3_.... functions
+ // alphabetized. The SQLITE_... values. on the other hand, are
+ // grouped by category.
+
+ /**
+ Functions exactly like the native form except that (A) the 2nd
+ argument is a boolean instead of an int and (B) the returned
+ value is not a pointer address and is only intended for use as a
+ per-UDF-call lookup key in a higher-level data structure.
+
+
Passing a true second argument is analogous to passing some
+ unspecified small, non-0 positive value to the C API and passing
+ false is equivalent to passing 0 to the C API.
+
+
Like the C API, it returns 0 if allocation fails or if
+ initialize is false and no prior aggregate context was allocated
+ for cx. If initialize is true then it returns 0 only on
+ allocation error. In all casses, 0 is considered the sentinel
+ "not a key" value.
+ */
+ public static native long sqlite3_aggregate_context(sqlite3_context cx, boolean initialize);
+
+ /**
+ Functions almost as documented for the C API, with these
+ exceptions:
+
+
- The callback interface is shorter because of
+ cross-language differences. Specifically, 3rd argument to the C
+ auto-extension callback interface is unnecessary here.
+
+
The C API docs do not specifically say so, but if the list of
+ auto-extensions is manipulated from an auto-extension, it is
+ undefined which, if any, auto-extensions will subsequently
+ execute for the current database. That is, doing so will result
+ in unpredictable, but not undefined, behavior.
+
+
See the AutoExtension class docs for more information.
+ */
+ public static native int sqlite3_auto_extension(@NotNull AutoExtensionCallback callback);
+
+ static native int sqlite3_backup_finish(@NotNull long ptrToBackup);
+
+ public static int sqlite3_backup_finish(@NotNull sqlite3_backup b){
+ return sqlite3_backup_finish(b.clearNativePointer());
+ }
+
+ static native sqlite3_backup sqlite3_backup_init(
+ @NotNull long ptrToDbDest, @NotNull String destTableName,
+ @NotNull long ptrToDbSrc, @NotNull String srcTableName
+ );
+
+ public static sqlite3_backup sqlite3_backup_init(
+ @NotNull sqlite3 dbDest, @NotNull String destTableName,
+ @NotNull sqlite3 dbSrc, @NotNull String srcTableName
+ ){
+ return sqlite3_backup_init( dbDest.getNativePointer(), destTableName,
+ dbSrc.getNativePointer(), srcTableName );
+ }
+
+ static native int sqlite3_backup_pagecount(@NotNull long ptrToBackup);
+
+ public static int sqlite3_backup_pagecount(@NotNull sqlite3_backup b){
+ return sqlite3_backup_pagecount(b.getNativePointer());
+ }
+
+ static native int sqlite3_backup_remaining(@NotNull long ptrToBackup);
+
+ public static int sqlite3_backup_remaining(@NotNull sqlite3_backup b){
+ return sqlite3_backup_remaining(b.getNativePointer());
+ }
+
+ static native int sqlite3_backup_step(@NotNull long ptrToBackup, int nPage);
+
+ public static int sqlite3_backup_step(@NotNull sqlite3_backup b, int nPage){
+ return sqlite3_backup_step(b.getNativePointer(), nPage);
+ }
+
+ static native int sqlite3_bind_blob(
+ @NotNull long ptrToStmt, int ndx, @Nullable byte[] data, int n
+ );
+
+ /**
+ If n is negative, SQLITE_MISUSE is returned. If n>data.length
+ then n is silently truncated to data.length.
+ */
+ static int sqlite3_bind_blob(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n
+ ){
+ return sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, n);
+ }
+
+ public static int sqlite3_bind_blob(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data
+ ){
+ return (null==data)
+ ? sqlite3_bind_null(stmt.getNativePointer(), ndx)
+ : sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, data.length);
+ }
+
+ static native int sqlite3_bind_double(
+ @NotNull long ptrToStmt, int ndx, double v
+ );
+
+ public static int sqlite3_bind_double(
+ @NotNull sqlite3_stmt stmt, int ndx, double v
+ ){
+ return sqlite3_bind_double(stmt.getNativePointer(), ndx, v);
+ }
+
+ static native int sqlite3_bind_int(
+ @NotNull long ptrToStmt, int ndx, int v
+ );
+
+ public static int sqlite3_bind_int(
+ @NotNull sqlite3_stmt stmt, int ndx, int v
+ ){
+ return sqlite3_bind_int(stmt.getNativePointer(), ndx, v);
+ }
+
+ static native int sqlite3_bind_int64(
+ @NotNull long ptrToStmt, int ndx, long v
+ );
+
+ public static int sqlite3_bind_int64(@NotNull sqlite3_stmt stmt, int ndx, long v){
+ return sqlite3_bind_int64( stmt.getNativePointer(), ndx, v );
+ }
+
+ static native int sqlite3_bind_java_object(
+ @NotNull long ptrToStmt, int ndx, @Nullable Object o
+ );
+
+ /**
+ Binds the given object at the given index. If o is null then this behaves like
+ sqlite3_bind_null().
+
+ @see #sqlite3_result_java_object
+ */
+ public static int sqlite3_bind_java_object(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable Object o
+ ){
+ return sqlite3_bind_java_object(stmt.getNativePointer(), ndx, o);
+ }
+
+ static native int sqlite3_bind_null(@NotNull long ptrToStmt, int ndx);
+
+ public static int sqlite3_bind_null(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_bind_null(stmt.getNativePointer(), ndx);
+ }
+
+ static native int sqlite3_bind_parameter_count(@NotNull long ptrToStmt);
+
+ public static int sqlite3_bind_parameter_count(@NotNull sqlite3_stmt stmt){
+ return sqlite3_bind_parameter_count(stmt.getNativePointer());
+ }
+
+ /**
+ Requires that paramName be a NUL-terminated UTF-8 string.
+
+ This overload is private because: (A) to keep users from
+ inadvertently passing non-NUL-terminated byte arrays (an easy
+ thing to do). (B) it is cheaper to NUL-terminate the
+ String-to-byte-array conversion in the public-facing Java-side
+ overload than to do that in C, so that signature is the
+ public-facing one.
+ */
+ private static native int sqlite3_bind_parameter_index(
+ @NotNull long ptrToStmt, @NotNull byte[] paramName
+ );
+
+ public static int sqlite3_bind_parameter_index(
+ @NotNull sqlite3_stmt stmt, @NotNull String paramName
+ ){
+ final byte[] utf8 = nulTerminateUtf8(paramName);
+ return null==utf8 ? 0 : sqlite3_bind_parameter_index(stmt.getNativePointer(), utf8);
+ }
+
+ static native String sqlite3_bind_parameter_name(
+ @NotNull long ptrToStmt, int index
+ );
+
+ public static String sqlite3_bind_parameter_name(@NotNull sqlite3_stmt stmt, int index){
+ return sqlite3_bind_parameter_name(stmt.getNativePointer(), index);
+ }
+
+ static native int sqlite3_bind_text(
+ @NotNull long ptrToStmt, int ndx, @Nullable byte[] utf8, int maxBytes
+ );
+
+ /**
+ Works like the C-level sqlite3_bind_text() but assumes
+ SQLITE_TRANSIENT for the final C API parameter. The byte array is
+ assumed to be in UTF-8 encoding.
+
+
If data is not null and maxBytes>utf8.length then maxBytes is
+ silently truncated to utf8.length. If maxBytes is negative then
+ results are undefined if data is not null and does not contain a
+ NUL byte.
+ */
+ static int sqlite3_bind_text(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] utf8, int maxBytes
+ ){
+ return sqlite3_bind_text(stmt.getNativePointer(), ndx, utf8, maxBytes);
+ }
+
+ /**
+ Converts data, if not null, to a UTF-8-encoded byte array and
+ binds it as such, returning the result of the C-level
+ sqlite3_bind_null() or sqlite3_bind_text().
+ */
+ public static int sqlite3_bind_text(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable String data
+ ){
+ if( null==data ) return sqlite3_bind_null(stmt.getNativePointer(), ndx);
+ final byte[] utf8 = data.getBytes(StandardCharsets.UTF_8);
+ return sqlite3_bind_text(stmt.getNativePointer(), ndx, utf8, utf8.length);
+ }
+
+ /**
+ Requires that utf8 be null or in UTF-8 encoding.
+ */
+ public static int sqlite3_bind_text(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] utf8
+ ){
+ return ( null==utf8 )
+ ? sqlite3_bind_null(stmt.getNativePointer(), ndx)
+ : sqlite3_bind_text(stmt.getNativePointer(), ndx, utf8, utf8.length);
+ }
+
+ static native int sqlite3_bind_text16(
+ @NotNull long ptrToStmt, int ndx, @Nullable byte[] data, int maxBytes
+ );
+
+ /**
+ Identical to the sqlite3_bind_text() overload with the same
+ signature but requires that its input be encoded in UTF-16 in
+ platform byte order.
+ */
+ static int sqlite3_bind_text16(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes
+ ){
+ return sqlite3_bind_text16(stmt.getNativePointer(), ndx, data, maxBytes);
+ }
+
+ /**
+ Converts its string argument to UTF-16 and binds it as such, returning
+ the result of the C-side function of the same name. The 3rd argument
+ may be null.
+ */
+ public static int sqlite3_bind_text16(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable String data
+ ){
+ if(null == data) return sqlite3_bind_null(stmt, ndx);
+ final byte[] bytes = data.getBytes(StandardCharsets.UTF_16);
+ return sqlite3_bind_text16(stmt.getNativePointer(), ndx, bytes, bytes.length);
+ }
+
+ /**
+ Requires that data be null or in UTF-16 encoding in platform byte
+ order. Returns the result of the C-level sqlite3_bind_null() or
+ sqlite3_bind_text16().
+ */
+ public static int sqlite3_bind_text16(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data
+ ){
+ return (null == data)
+ ? sqlite3_bind_null(stmt.getNativePointer(), ndx)
+ : sqlite3_bind_text16(stmt.getNativePointer(), ndx, data, data.length);
+ }
+
+ static native int sqlite3_bind_value(@NotNull long ptrToStmt, int ndx, long ptrToValue);
+
+ /**
+ Functions like the C-level sqlite3_bind_value(), or
+ sqlite3_bind_null() if val is null.
+ */
+ public static int sqlite3_bind_value(@NotNull sqlite3_stmt stmt, int ndx, sqlite3_value val){
+ return sqlite3_bind_value(stmt.getNativePointer(), ndx,
+ null==val ? 0L : val.getNativePointer());
+ }
+
+ static native int sqlite3_bind_zeroblob(@NotNull long ptrToStmt, int ndx, int n);
+
+ public static int sqlite3_bind_zeroblob(@NotNull sqlite3_stmt stmt, int ndx, int n){
+ return sqlite3_bind_zeroblob(stmt.getNativePointer(), ndx, n);
+ }
+
+ static native int sqlite3_bind_zeroblob64(
+ @NotNull long ptrToStmt, int ndx, long n
+ );
+
+ public static int sqlite3_bind_zeroblob64(@NotNull sqlite3_stmt stmt, int ndx, long n){
+ return sqlite3_bind_zeroblob64(stmt.getNativePointer(), ndx, n);
+ }
+
+ static native int sqlite3_blob_bytes(@NotNull long ptrToBlob);
+
+ public static int sqlite3_blob_bytes(@NotNull sqlite3_blob blob){
+ return sqlite3_blob_bytes(blob.getNativePointer());
+ }
+
+ static native int sqlite3_blob_close(@Nullable long ptrToBlob);
+
+ public static int sqlite3_blob_close(@Nullable sqlite3_blob blob){
+ return sqlite3_blob_close(blob.clearNativePointer());
+ }
+
+ static native int sqlite3_blob_open(
+ @NotNull long ptrToDb, @NotNull String dbName,
+ @NotNull String tableName, @NotNull String columnName,
+ long iRow, int flags, @NotNull OutputPointer.sqlite3_blob out
+ );
+
+ public static int sqlite3_blob_open(
+ @NotNull sqlite3 db, @NotNull String dbName,
+ @NotNull String tableName, @NotNull String columnName,
+ long iRow, int flags, @NotNull OutputPointer.sqlite3_blob out
+ ){
+ return sqlite3_blob_open(db.getNativePointer(), dbName, tableName,
+ columnName, iRow, flags, out);
+ }
+
+ /**
+ Convenience overload.
+ */
+ public static sqlite3_blob sqlite3_blob_open(
+ @NotNull sqlite3 db, @NotNull String dbName,
+ @NotNull String tableName, @NotNull String columnName,
+ long iRow, int flags ){
+ final OutputPointer.sqlite3_blob out = new OutputPointer.sqlite3_blob();
+ sqlite3_blob_open(db.getNativePointer(), dbName, tableName, columnName,
+ iRow, flags, out);
+ return out.take();
+ };
+
+ static native int sqlite3_blob_read(
+ @NotNull long ptrToBlob, @NotNull byte[] target, int iOffset
+ );
+
+ public static int sqlite3_blob_read(
+ @NotNull sqlite3_blob b, @NotNull byte[] target, int iOffset
+ ){
+ return sqlite3_blob_read(b.getNativePointer(), target, iOffset);
+ }
+
+ static native int sqlite3_blob_reopen(
+ @NotNull long ptrToBlob, long newRowId
+ );
+
+ public static int sqlite3_blob_reopen(@NotNull sqlite3_blob b, long newRowId){
+ return sqlite3_blob_reopen(b.getNativePointer(), newRowId);
+ }
+
+ static native int sqlite3_blob_write(
+ @NotNull long ptrToBlob, @NotNull byte[] bytes, int iOffset
+ );
+
+ public static int sqlite3_blob_write(
+ @NotNull sqlite3_blob b, @NotNull byte[] bytes, int iOffset
+ ){
+ return sqlite3_blob_write(b.getNativePointer(), bytes, iOffset);
+ }
+
+ static native int sqlite3_busy_handler(
+ @NotNull long ptrToDb, @Nullable BusyHandlerCallback handler
+ );
+
+ /**
+ As for the C-level function of the same name, with a
+ BusyHandlerCallback instance in place of a callback
+ function. Pass it a null handler to clear the busy handler.
+ */
+ public static int sqlite3_busy_handler(
+ @NotNull sqlite3 db, @Nullable BusyHandlerCallback handler
+ ){
+ return sqlite3_busy_handler(db.getNativePointer(), handler);
+ }
+
+ static native int sqlite3_busy_timeout(@NotNull long ptrToDb, int ms);
+
+ public static int sqlite3_busy_timeout(@NotNull sqlite3 db, int ms){
+ return sqlite3_busy_timeout(db.getNativePointer(), ms);
+ }
+
+ public static native boolean sqlite3_cancel_auto_extension(
+ @NotNull AutoExtensionCallback ax
+ );
+
+ static native int sqlite3_changes(@NotNull long ptrToDb);
+
+ public static int sqlite3_changes(@NotNull sqlite3 db){
+ return sqlite3_changes(db.getNativePointer());
+ }
+
+ static native long sqlite3_changes64(@NotNull long ptrToDb);
+
+ public static long sqlite3_changes64(@NotNull sqlite3 db){
+ return sqlite3_changes64(db.getNativePointer());
+ }
+
+ static native int sqlite3_clear_bindings(@NotNull long ptrToStmt);
+
+ public static int sqlite3_clear_bindings(@NotNull sqlite3_stmt stmt){
+ return sqlite3_clear_bindings(stmt.getNativePointer());
+ }
+
+ static native int sqlite3_close(@Nullable long ptrToDb);
+
+ public static int sqlite3_close(@Nullable sqlite3 db){
+ int rc = 0;
+ if( null!=db ){
+ rc = sqlite3_close(db.getNativePointer());
+ if( 0==rc ) db.clearNativePointer();
+ }
+ return rc;
+ }
+
+ static native int sqlite3_close_v2(@Nullable long ptrToDb);
+
+ public static int sqlite3_close_v2(@Nullable sqlite3 db){
+ return db==null ? 0 : sqlite3_close_v2(db.clearNativePointer());
+ }
+
+ public static native byte[] sqlite3_column_blob(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ static native int sqlite3_column_bytes(@NotNull long ptrToStmt, int ndx);
+
+ public static int sqlite3_column_bytes(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_bytes(stmt.getNativePointer(), ndx);
+ }
+
+ static native int sqlite3_column_bytes16(@NotNull long ptrToStmt, int ndx);
+
+ public static int sqlite3_column_bytes16(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_bytes16(stmt.getNativePointer(), ndx);
+ }
+
+ static native int sqlite3_column_count(@NotNull long ptrToStmt);
+
+ public static int sqlite3_column_count(@NotNull sqlite3_stmt stmt){
+ return sqlite3_column_count(stmt.getNativePointer());
+ }
+
+ static native String sqlite3_column_decltype(@NotNull long ptrToStmt, int ndx);
+
+ public static String sqlite3_column_decltype(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_decltype(stmt.getNativePointer(), ndx);
+ }
+
+ public static native double sqlite3_column_double(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ public static native int sqlite3_column_int(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ public static native long sqlite3_column_int64(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ static native String sqlite3_column_name(@NotNull long ptrToStmt, int ndx);
+
+ public static String sqlite3_column_name(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_name(stmt.getNativePointer(), ndx);
+ }
+
+ static native String sqlite3_column_database_name(@NotNull long ptrToStmt, int ndx);
+
+ public static String sqlite3_column_database_name(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_database_name(stmt.getNativePointer(), ndx);
+ }
+
+ static native String sqlite3_column_origin_name(@NotNull long ptrToStmt, int ndx);
+
+ public static String sqlite3_column_origin_name(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_origin_name(stmt.getNativePointer(), ndx);
+ }
+
+ static native String sqlite3_column_table_name(@NotNull long ptrToStmt, int ndx);
+
+ public static String sqlite3_column_table_name(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_table_name(stmt.getNativePointer(), ndx);
+ }
+
+ /**
+ Functions identially to the C API, and this note is just to
+ stress that the returned bytes are encoded as UTF-8. It returns
+ null if the underlying C-level sqlite3_column_text() returns NULL
+ or on allocation error.
+
+ @see #sqlite3_column_text16(sqlite3_stmt,int)
+ */
+ public static native byte[] sqlite3_column_text(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ public static native String sqlite3_column_text16(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ // The real utility of this function is questionable.
+ // /**
+ // Returns a Java value representation based on the value of
+ // sqlite_value_type(). For integer types it returns either Integer
+ // or Long, depending on whether the value will fit in an
+ // Integer. For floating-point values it always returns type Double.
+
+ // If the column was bound using sqlite3_result_java_object() then
+ // that value, as an Object, is returned.
+ // */
+ // public static Object sqlite3_column_to_java(@NotNull sqlite3_stmt stmt,
+ // int ndx){
+ // sqlite3_value v = sqlite3_column_value(stmt, ndx);
+ // Object rv = null;
+ // if(null == v) return v;
+ // v = sqlite3_value_dup(v)/*need a protected value*/;
+ // if(null == v) return v /* OOM error in C */;
+ // if(112/* 'p' */ == sqlite3_value_subtype(v)){
+ // rv = sqlite3_value_java_object(v);
+ // }else{
+ // switch(sqlite3_value_type(v)){
+ // case SQLITE_INTEGER: {
+ // final long i = sqlite3_value_int64(v);
+ // rv = (i<=0x7fffffff && i>=-0x7fffffff-1)
+ // ? new Integer((int)i) : new Long(i);
+ // break;
+ // }
+ // case SQLITE_FLOAT: rv = new Double(sqlite3_value_double(v)); break;
+ // case SQLITE_BLOB: rv = sqlite3_value_blob(v); break;
+ // case SQLITE_TEXT: rv = sqlite3_value_text16(v); break;
+ // default: break;
+ // }
+ // }
+ // sqlite3_value_free(v);
+ // return rv;
+ // }
+
+ static native int sqlite3_column_type(@NotNull long ptrToStmt, int ndx);
+
+ public static int sqlite3_column_type(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_type(stmt.getNativePointer(), ndx);
+ }
+
+ public static native sqlite3_value sqlite3_column_value(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ static native int sqlite3_collation_needed(
+ @NotNull long ptrToDb, @Nullable CollationNeededCallback callback
+ );
+
+ /**
+ This functions like C's sqlite3_collation_needed16() because
+ Java's string type is inherently compatible with that interface.
+ */
+ public static int sqlite3_collation_needed(
+ @NotNull sqlite3 db, @Nullable CollationNeededCallback callback
+ ){
+ return sqlite3_collation_needed(db.getNativePointer(), callback);
+ }
+
+ static native CommitHookCallback sqlite3_commit_hook(
+ @NotNull long ptrToDb, @Nullable CommitHookCallback hook
+ );
+
+ public static CommitHookCallback sqlite3_commit_hook(
+ @NotNull sqlite3 db, @Nullable CommitHookCallback hook
+ ){
+ return sqlite3_commit_hook(db.getNativePointer(), hook);
+ }
+
+ public static native String sqlite3_compileoption_get(int n);
+
+ public static native boolean sqlite3_compileoption_used(String optName);
+
+ /**
+ This implementation is private because it's too easy to pass it
+ non-NUL-terminated byte arrays from client code.
+ */
+ private static native int sqlite3_complete(
+ @NotNull byte[] nulTerminatedUtf8Sql
+ );
+
+ /**
+ Unlike the C API, this returns SQLITE_MISUSE if its argument is
+ null (as opposed to invoking UB).
+ */
+ public static int sqlite3_complete(@NotNull String sql){
+ return sqlite3_complete( nulTerminateUtf8(sql) );
+ }
+
+
+ /**
+
Works like in the C API with the exception that it only supports
+ the following subset of configution flags:
+
+
SQLITE_CONFIG_SINGLETHREAD
+ SQLITE_CONFIG_MULTITHREAD
+ SQLITE_CONFIG_SERIALIZED
+
+
Others may be added in the future. It returns SQLITE_MISUSE if
+ given an argument it does not handle.
+
+
Note that sqlite3_config() is not threadsafe with regards to
+ the rest of the library. This must not be called when any other
+ library APIs are being called.
+ */
+ public static native int sqlite3_config(int op);
+
+ /**
+ If the native library was built with SQLITE_ENABLE_SQLLOG defined
+ then this acts as a proxy for C's
+ sqlite3_config(SQLITE_ENABLE_SQLLOG,...). This sets or clears the
+ logger. If installation of a logger fails, any previous logger is
+ retained.
+
+
If not built with SQLITE_ENABLE_SQLLOG defined, this returns
+ SQLITE_MISUSE.
+
+
Note that sqlite3_config() is not threadsafe with regards to
+ the rest of the library. This must not be called when any other
+ library APIs are being called.
+ */
+ public static native int sqlite3_config( @Nullable ConfigSqllogCallback logger );
+
+ /**
+ The sqlite3_config() overload for handling the SQLITE_CONFIG_LOG
+ option.
+ */
+ public static native int sqlite3_config( @Nullable ConfigLogCallback logger );
+
+ /**
+ Unlike the C API, this returns null if its argument is
+ null (as opposed to invoking UB).
+ */
+ public static native sqlite3 sqlite3_context_db_handle(
+ @NotNull sqlite3_context cx
+ );
+
+ public static native int sqlite3_create_collation(
+ @NotNull sqlite3 db, @NotNull String name, int eTextRep,
+ @NotNull CollationCallback col
+ );
+
+ /**
+ The Java counterpart to the C-native sqlite3_create_function(),
+ sqlite3_create_function_v2(), and
+ sqlite3_create_window_function(). Which one it behaves like
+ depends on which methods the final argument implements. See
+ SQLFunction's subclasses (ScalarFunction, AggregateFunction,
+ and WindowFunction) for details.
+
+ Unlike the C API, this returns SQLITE_MISUSE null if its db or
+ functionName arguments are null (as opposed to invoking UB).
+ */
+ public static native int sqlite3_create_function(
+ @NotNull sqlite3 db, @NotNull String functionName,
+ int nArg, int eTextRep, @NotNull SQLFunction func
+ );
+
+ static native int sqlite3_data_count(@NotNull long ptrToStmt);
+
+ public static int sqlite3_data_count(@NotNull sqlite3_stmt stmt){
+ return sqlite3_data_count(stmt.getNativePointer());
+ }
+
+ /**
+ Overload for sqlite3_db_config() calls which take (int,int*)
+ variadic arguments. Returns SQLITE_MISUSE if op is not one of the
+ SQLITE_DBCONFIG_... options which uses this call form.
+
+
Unlike the C API, this returns SQLITE_MISUSE if its db argument
+ are null (as opposed to invoking UB).
+ */
+ public static native int sqlite3_db_config(
+ @NotNull sqlite3 db, int op, int onOff, @Nullable OutputPointer.Int32 out
+ );
+
+ /**
+ Overload for sqlite3_db_config() calls which take a (const char*)
+ variadic argument. As of SQLite3 v3.43 the only such option is
+ SQLITE_DBCONFIG_MAINDBNAME. Returns SQLITE_MISUSE if op is not
+ SQLITE_DBCONFIG_MAINDBNAME, but that set of options may be
+ extended in future versions.
+ */
+ public static native int sqlite3_db_config(
+ @NotNull sqlite3 db, int op, @NotNull String val
+ );
+
+ private static native String sqlite3_db_name(@NotNull long ptrToDb, int ndx);
+
+ public static String sqlite3_db_name(@NotNull sqlite3 db, int ndx){
+ return null==db ? null : sqlite3_db_name(db.getNativePointer(), ndx);
+ }
+
+
+ public static native String sqlite3_db_filename(
+ @NotNull sqlite3 db, @NotNull String dbName
+ );
+
+ public static native sqlite3 sqlite3_db_handle(@NotNull sqlite3_stmt stmt);
+
+ public static native int sqlite3_db_readonly(@NotNull sqlite3 db, String dbName);
+
+ public static native int sqlite3_db_release_memory(sqlite3 db);
+
+ public static native int sqlite3_db_status(
+ @NotNull sqlite3 db, int op, @NotNull OutputPointer.Int32 pCurrent,
+ @NotNull OutputPointer.Int32 pHighwater, boolean reset
+ );
+
+ public static native int sqlite3_errcode(@NotNull sqlite3 db);
+
+ public static native String sqlite3_errmsg(@NotNull sqlite3 db);
+
+ static native int sqlite3_error_offset(@NotNull long ptrToDb);
+
+ /**
+ Note that the returned byte offset values assume UTF-8-encoded
+ inputs, so won't always match character offsets in Java Strings.
+ */
+ public static int sqlite3_error_offset(@NotNull sqlite3 db){
+ return sqlite3_error_offset(db.getNativePointer());
+ }
+
+ public static native String sqlite3_errstr(int resultCode);
+
+ public static native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt);
+
+ static native int sqlite3_extended_errcode(@NotNull long ptrToDb);
+
+ public static int sqlite3_extended_errcode(@NotNull sqlite3 db){
+ return sqlite3_extended_errcode(db.getNativePointer());
+ }
+
+ public static native boolean sqlite3_extended_result_codes(
+ @NotNull sqlite3 db, boolean onoff
+ );
+
+ static native boolean sqlite3_get_autocommit(@NotNull long ptrToDb);
+
+ public static boolean sqlite3_get_autocommit(@NotNull sqlite3 db){
+ return sqlite3_get_autocommit(db.getNativePointer());
+ }
+
+ public static native Object sqlite3_get_auxdata(
+ @NotNull sqlite3_context cx, int n
+ );
+
+ static native int sqlite3_finalize(long ptrToStmt);
+
+ public static int sqlite3_finalize(@NotNull sqlite3_stmt stmt){
+ return null==stmt ? 0 : sqlite3_finalize(stmt.clearNativePointer());
+ }
+
+ public static native int sqlite3_initialize();
+
+ public static native void sqlite3_interrupt(@NotNull sqlite3 db);
+
+ public static native boolean sqlite3_is_interrupted(@NotNull sqlite3 db);
+
+ public static native boolean sqlite3_keyword_check(@NotNull String word);
+
+ public static native int sqlite3_keyword_count();
+
+ public static native String sqlite3_keyword_name(int index);
+
+
+ public static native long sqlite3_last_insert_rowid(@NotNull sqlite3 db);
+
+ public static native String sqlite3_libversion();
+
+ public static native int sqlite3_libversion_number();
+
+ public static native int sqlite3_limit(@NotNull sqlite3 db, int id, int newVal);
+
+ /**
+ Only available if built with SQLITE_ENABLE_NORMALIZE. If not, it always
+ returns null.
+ */
+ public static native String sqlite3_normalized_sql(@NotNull sqlite3_stmt stmt);
+
+ /**
+ Works like its C counterpart and makes the native pointer of the
+ underling (sqlite3*) object available via
+ ppDb.getNativePointer(). That pointer is necessary for looking up
+ the JNI-side native, but clients need not pay it any
+ heed. Passing the object to sqlite3_close() or sqlite3_close_v2()
+ will clear that pointer mapping.
+
+
Recall that even if opening fails, the output pointer might be
+ non-null. Any error message about the failure will be in that
+ object and it is up to the caller to sqlite3_close() that
+ db handle.
+ */
+ public static native int sqlite3_open(
+ @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb
+ );
+
+ /**
+ Convenience overload which returns its db handle directly. The returned
+ object might not have been successfully opened: use sqlite3_errcode() to
+ check whether it is in an error state.
+
+
Ownership of the returned value is passed to the caller, who must eventually
+ pass it to sqlite3_close() or sqlite3_close_v2().
+ */
+ public static sqlite3 sqlite3_open(@Nullable String filename){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ sqlite3_open(filename, out);
+ return out.take();
+ };
+
+ public static native int sqlite3_open_v2(
+ @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb,
+ int flags, @Nullable String zVfs
+ );
+
+ /**
+ Has the same semantics as the sqlite3-returning sqlite3_open()
+ but uses sqlite3_open_v2() instead of sqlite3_open().
+ */
+ public static sqlite3 sqlite3_open_v2(@Nullable String filename, int flags,
+ @Nullable String zVfs){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ sqlite3_open_v2(filename, out, flags, zVfs);
+ return out.take();
+ };
+
+ /**
+ The sqlite3_prepare() family of functions require slightly
+ different signatures than their native counterparts, but (A) they
+ retain functionally equivalent semantics and (B) overloading
+ allows us to install several convenience forms.
+
+
All of them which take their SQL in the form of a byte[] require
+ that it be in UTF-8 encoding unless explicitly noted otherwise.
+
+
The forms which take a "tail" output pointer return (via that
+ output object) the index into their SQL byte array at which the
+ end of the first SQL statement processed by the call was
+ found. That's fundamentally how the C APIs work but making use of
+ that value requires more copying of the input SQL into
+ consecutively smaller arrays in order to consume all of
+ it. (There is an example of doing that in this project's Tester1
+ class.) For that vast majority of uses, that capability is not
+ necessary, however, and overloads are provided which gloss over
+ that.
+
+
Results are undefined if maxBytes>sqlUtf8.length.
+
+
This routine is private because its maxBytes value is not
+ strictly necessary in the Java interface, as sqlUtf8.length tells
+ us the information we need. Making this public would give clients
+ more ways to shoot themselves in the foot without providing any
+ real utility.
+ */
+ private static native int sqlite3_prepare(
+ @NotNull long ptrToDb, @NotNull byte[] sqlUtf8, int maxBytes,
+ @NotNull OutputPointer.sqlite3_stmt outStmt,
+ @Nullable OutputPointer.Int32 pTailOffset
+ );
+
+ /**
+ Works like the canonical sqlite3_prepare() but its "tail" output
+ argument is returned as the index offset into the given
+ UTF-8-encoded byte array at which SQL parsing stopped. The
+ semantics are otherwise identical to the C API counterpart.
+
+
Several overloads provided simplified call signatures.
+ */
+ public static int sqlite3_prepare(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+ @NotNull OutputPointer.sqlite3_stmt outStmt,
+ @Nullable OutputPointer.Int32 pTailOffset
+ ){
+ return sqlite3_prepare(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+ outStmt, pTailOffset);
+ }
+
+ public static int sqlite3_prepare(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+ @NotNull OutputPointer.sqlite3_stmt outStmt
+ ){
+ return sqlite3_prepare(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+ outStmt, null);
+ }
+
+ public static int sqlite3_prepare(
+ @NotNull sqlite3 db, @NotNull String sql,
+ @NotNull OutputPointer.sqlite3_stmt outStmt
+ ){
+ final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8);
+ return sqlite3_prepare(db.getNativePointer(), utf8, utf8.length,
+ outStmt, null);
+ }
+
+ /**
+ Convenience overload which returns its statement handle directly,
+ or null on error or when reading only whitespace or
+ comments. sqlite3_errcode() can be used to determine whether
+ there was an error or the input was empty. Ownership of the
+ returned object is passed to the caller, who must eventually pass
+ it to sqlite3_finalize().
+ */
+ public static sqlite3_stmt sqlite3_prepare(
+ @NotNull sqlite3 db, @NotNull String sql
+ ){
+ final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+ sqlite3_prepare(db, sql, out);
+ return out.take();
+ }
+ /**
+ @see #sqlite3_prepare
+ */
+ private static native int sqlite3_prepare_v2(
+ @NotNull long ptrToDb, @NotNull byte[] sqlUtf8, int maxBytes,
+ @NotNull OutputPointer.sqlite3_stmt outStmt,
+ @Nullable OutputPointer.Int32 pTailOffset
+ );
+
+ /**
+ Works like the canonical sqlite3_prepare_v2() but its "tail"
+ output paramter is returned as the index offset into the given
+ byte array at which SQL parsing stopped.
+ */
+ public static int sqlite3_prepare_v2(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+ @NotNull OutputPointer.sqlite3_stmt outStmt,
+ @Nullable OutputPointer.Int32 pTailOffset
+ ){
+ return sqlite3_prepare_v2(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+ outStmt, pTailOffset);
+ }
+
+ public static int sqlite3_prepare_v2(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+ @NotNull OutputPointer.sqlite3_stmt outStmt
+ ){
+ return sqlite3_prepare_v2(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+ outStmt, null);
+ }
+
+ public static int sqlite3_prepare_v2(
+ @NotNull sqlite3 db, @NotNull String sql,
+ @NotNull OutputPointer.sqlite3_stmt outStmt
+ ){
+ final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8);
+ return sqlite3_prepare_v2(db.getNativePointer(), utf8, utf8.length,
+ outStmt, null);
+ }
+
+ /**
+ Works identically to the sqlite3_stmt-returning sqlite3_prepare()
+ but uses sqlite3_prepare_v2().
+ */
+ public static sqlite3_stmt sqlite3_prepare_v2(
+ @NotNull sqlite3 db, @NotNull String sql
+ ){
+ final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+ sqlite3_prepare_v2(db, sql, out);
+ return out.take();
+ }
+
+ /**
+ @see #sqlite3_prepare
+ */
+ private static native int sqlite3_prepare_v3(
+ @NotNull long ptrToDb, @NotNull byte[] sqlUtf8, int maxBytes,
+ int prepFlags, @NotNull OutputPointer.sqlite3_stmt outStmt,
+ @Nullable OutputPointer.Int32 pTailOffset
+ );
+
+ /**
+ Works like the canonical sqlite3_prepare_v2() but its "tail"
+ output paramter is returned as the index offset into the given
+ byte array at which SQL parsing stopped.
+ */
+ public static int sqlite3_prepare_v3(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags,
+ @NotNull OutputPointer.sqlite3_stmt outStmt,
+ @Nullable OutputPointer.Int32 pTailOffset
+ ){
+ return sqlite3_prepare_v3(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+ prepFlags, outStmt, pTailOffset);
+ }
+
+ /**
+ Convenience overload which elides the seldom-used pTailOffset
+ parameter.
+ */
+ public static int sqlite3_prepare_v3(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags,
+ @NotNull OutputPointer.sqlite3_stmt outStmt
+ ){
+ return sqlite3_prepare_v3(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+ prepFlags, outStmt, null);
+ }
+
+ /**
+ Convenience overload which elides the seldom-used pTailOffset
+ parameter and converts the given string to UTF-8 before passing
+ it on.
+ */
+ public static int sqlite3_prepare_v3(
+ @NotNull sqlite3 db, @NotNull String sql, int prepFlags,
+ @NotNull OutputPointer.sqlite3_stmt outStmt
+ ){
+ final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8);
+ return sqlite3_prepare_v3(db.getNativePointer(), utf8, utf8.length,
+ prepFlags, outStmt, null);
+ }
+
+ /**
+ Works identically to the sqlite3_stmt-returning sqlite3_prepare()
+ but uses sqlite3_prepare_v3().
+ */
+ public static sqlite3_stmt sqlite3_prepare_v3(
+ @NotNull sqlite3 db, @NotNull String sql, int prepFlags
+ ){
+ final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+ sqlite3_prepare_v3(db, sql, prepFlags, out);
+ return out.take();
+ }
+
+ /**
+ A convenience wrapper around sqlite3_prepare_v3() which accepts
+ an arbitrary amount of input provided as a UTF-8-encoded byte
+ array. It loops over the input bytes looking for
+ statements. Each one it finds is passed to p.call(), passing
+ ownership of it to that function. If p.call() returns 0, looping
+ continues, else the loop stops.
+
+
If p.call() throws, the exception is propagated.
+
+
How each statement is handled, including whether it is finalized
+ or not, is up to the callback object. e.g. the callback might
+ collect them for later use. If it does not collect them then it
+ must finalize them. See PrepareMultiCallback.Finalize for a
+ simple proxy which does that.
+ */
+ public static int sqlite3_prepare_multi(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+ int preFlags,
+ @NotNull PrepareMultiCallback p){
+ final OutputPointer.Int32 oTail = new OutputPointer.Int32();
+ int pos = 0, n = 1;
+ byte[] sqlChunk = sqlUtf8;
+ int rc = 0;
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+ while(0==rc && pos 0){
+ sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
+ sqlChunk.length);
+ }
+ if( 0==sqlChunk.length ) break;
+ rc = sqlite3_prepare_v3(db, sqlChunk, preFlags, outStmt, oTail);
+ if( 0!=rc ) break;
+ pos = oTail.value;
+ stmt = outStmt.take();
+ if( null == stmt ){
+ // empty statement was parsed.
+ continue;
+ }
+ rc = p.call(stmt);
+ }
+ return rc;
+ }
+
+ /**
+ Convenience overload which accepts its SQL as a String and uses
+ no statement-preparation flags.
+ */
+ public static int sqlite3_prepare_multi(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+ @NotNull PrepareMultiCallback p){
+ return sqlite3_prepare_multi(db, sqlUtf8, 0, p);
+ }
+
+ /**
+ Convenience overload which accepts its SQL as a String.
+ */
+ public static int sqlite3_prepare_multi(
+ @NotNull sqlite3 db, @NotNull String sql, int prepFlags,
+ @NotNull PrepareMultiCallback p){
+ return sqlite3_prepare_multi(
+ db, sql.getBytes(StandardCharsets.UTF_8), prepFlags, p
+ );
+ }
+
+ /**
+ Convenience overload which accepts its SQL as a String and uses
+ no statement-preparation flags.
+ */
+ public static int sqlite3_prepare_multi(
+ @NotNull sqlite3 db, @NotNull String sql,
+ @NotNull PrepareMultiCallback p){
+ return sqlite3_prepare_multi(db, sql, 0, p);
+ }
+
+ /**
+ Convenience overload which accepts its SQL as a String
+ array. They will be concatenated together as-is, with no
+ separator, and passed on to one of the other overloads.
+ */
+ public static int sqlite3_prepare_multi(
+ @NotNull sqlite3 db, @NotNull String[] sql, int prepFlags,
+ @NotNull PrepareMultiCallback p){
+ return sqlite3_prepare_multi(db, String.join("",sql), prepFlags, p);
+ }
+
+ /**
+ Convenience overload which uses no statement-preparation flags.
+ */
+ public static int sqlite3_prepare_multi(
+ @NotNull sqlite3 db, @NotNull String[] sql,
+ @NotNull PrepareMultiCallback p){
+ return sqlite3_prepare_multi(db, sql, 0, p);
+ }
+
+ static native int sqlite3_preupdate_blobwrite(@NotNull long ptrToDb);
+
+ /**
+ If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+ acts as a proxy for C's sqlite3_preupdate_blobwrite(), else it returns
+ SQLITE_MISUSE with no side effects.
+ */
+ public static int sqlite3_preupdate_blobwrite(@NotNull sqlite3 db){
+ return sqlite3_preupdate_blobwrite(db.getNativePointer());
+ }
+
+ static native int sqlite3_preupdate_count(@NotNull long ptrToDb);
+
+ /**
+ If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+ acts as a proxy for C's sqlite3_preupdate_count(), else it returns
+ SQLITE_MISUSE with no side effects.
+ */
+ public static int sqlite3_preupdate_count(@NotNull sqlite3 db){
+ return sqlite3_preupdate_count(db.getNativePointer());
+ }
+
+ static native int sqlite3_preupdate_depth(@NotNull long ptrToDb);
+
+ /**
+ If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+ acts as a proxy for C's sqlite3_preupdate_depth(), else it returns
+ SQLITE_MISUSE with no side effects.
+ */
+ public static int sqlite3_preupdate_depth(@NotNull sqlite3 db){
+ return sqlite3_preupdate_depth(db.getNativePointer());
+ }
+
+ static native PreupdateHookCallback sqlite3_preupdate_hook(
+ @NotNull long ptrToDb, @Nullable PreupdateHookCallback hook
+ );
+
+ /**
+ If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+ acts as a proxy for C's sqlite3_preupdate_hook(), else it returns null
+ with no side effects.
+ */
+ public static PreupdateHookCallback sqlite3_preupdate_hook(
+ @NotNull sqlite3 db, @Nullable PreupdateHookCallback hook
+ ){
+ return sqlite3_preupdate_hook(db.getNativePointer(), hook);
+ }
+
+ static native int sqlite3_preupdate_new(@NotNull long ptrToDb, int col,
+ @NotNull OutputPointer.sqlite3_value out);
+
+ /**
+ If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined,
+ this acts as a proxy for C's sqlite3_preupdate_new(), else it
+ returns SQLITE_MISUSE with no side effects.
+ */
+ public static int sqlite3_preupdate_new(@NotNull sqlite3 db, int col,
+ @NotNull OutputPointer.sqlite3_value out){
+ return sqlite3_preupdate_new(db.getNativePointer(), col, out);
+ }
+
+ /**
+ Convenience wrapper for the 3-arg sqlite3_preupdate_new() which returns
+ null on error.
+ */
+ public static sqlite3_value sqlite3_preupdate_new(@NotNull sqlite3 db, int col){
+ final OutputPointer.sqlite3_value out = new OutputPointer.sqlite3_value();
+ sqlite3_preupdate_new(db.getNativePointer(), col, out);
+ return out.take();
+ }
+
+ static native int sqlite3_preupdate_old(@NotNull long ptrToDb, int col,
+ @NotNull OutputPointer.sqlite3_value out);
+
+ /**
+ If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined,
+ this acts as a proxy for C's sqlite3_preupdate_old(), else it
+ returns SQLITE_MISUSE with no side effects.
+ */
+ public static int sqlite3_preupdate_old(@NotNull sqlite3 db, int col,
+ @NotNull OutputPointer.sqlite3_value out){
+ return sqlite3_preupdate_old(db.getNativePointer(), col, out);
+ }
+
+ /**
+ Convenience wrapper for the 3-arg sqlite3_preupdate_old() which returns
+ null on error.
+ */
+ public static sqlite3_value sqlite3_preupdate_old(@NotNull sqlite3 db, int col){
+ final OutputPointer.sqlite3_value out = new OutputPointer.sqlite3_value();
+ sqlite3_preupdate_old(db.getNativePointer(), col, out);
+ return out.take();
+ }
+
+ public static native void sqlite3_progress_handler(
+ @NotNull sqlite3 db, int n, @Nullable ProgressHandlerCallback h
+ );
+
+ public static native void sqlite3_randomness(byte[] target);
+
+ public static native int sqlite3_release_memory(int n);
+
+ public static native int sqlite3_reset(@NotNull sqlite3_stmt stmt);
+
+ /**
+ Works like the C API except that it has no side effects if auto
+ extensions are currently running. (The JNI-level list of
+ extensions cannot be manipulated while it is being traversed.)
+ */
+ public static native void sqlite3_reset_auto_extension();
+
+ public static native void sqlite3_result_double(
+ @NotNull sqlite3_context cx, double v
+ );
+
+ /**
+ The main sqlite3_result_error() impl of which all others are
+ proxies. eTextRep must be one of SQLITE_UTF8 or SQLITE_UTF16 and
+ msg must be encoded correspondingly. Any other eTextRep value
+ results in the C-level sqlite3_result_error() being called with a
+ complaint about the invalid argument.
+ */
+ static native void sqlite3_result_error(
+ @NotNull sqlite3_context cx, @NotNull byte[] msg, int eTextRep
+ );
+
+ public static void sqlite3_result_error(
+ @NotNull sqlite3_context cx, @NotNull byte[] utf8
+ ){
+ sqlite3_result_error(cx, utf8, SQLITE_UTF8);
+ }
+
+ public static void sqlite3_result_error(
+ @NotNull sqlite3_context cx, @NotNull String msg
+ ){
+ final byte[] utf8 = msg.getBytes(StandardCharsets.UTF_8);
+ sqlite3_result_error(cx, utf8, SQLITE_UTF8);
+ }
+
+ public static void sqlite3_result_error16(
+ @NotNull sqlite3_context cx, @NotNull byte[] utf16
+ ){
+ sqlite3_result_error(cx, utf16, SQLITE_UTF16);
+ }
+
+ public static void sqlite3_result_error16(
+ @NotNull sqlite3_context cx, @NotNull String msg
+ ){
+ final byte[] utf16 = msg.getBytes(StandardCharsets.UTF_16);
+ sqlite3_result_error(cx, utf16, SQLITE_UTF16);
+ }
+
+ /**
+ Equivalent to passing e.toString() to {@link
+ #sqlite3_result_error(sqlite3_context,String)}. Note that
+ toString() is used instead of getMessage() because the former
+ prepends the exception type name to the message.
+ */
+ public static void sqlite3_result_error(
+ @NotNull sqlite3_context cx, @NotNull Exception e
+ ){
+ sqlite3_result_error(cx, e.toString());
+ }
+
+ public static native void sqlite3_result_error_toobig(
+ @NotNull sqlite3_context cx
+ );
+
+ public static native void sqlite3_result_error_nomem(
+ @NotNull sqlite3_context cx
+ );
+
+ public static native void sqlite3_result_error_code(
+ @NotNull sqlite3_context cx, int c
+ );
+
+ public static native void sqlite3_result_null(
+ @NotNull sqlite3_context cx
+ );
+
+ public static native void sqlite3_result_int(
+ @NotNull sqlite3_context cx, int v
+ );
+
+ public static native void sqlite3_result_int64(
+ @NotNull sqlite3_context cx, long v
+ );
+
+ /**
+ Binds the SQL result to the given object, or {@link
+ #sqlite3_result_null} if {@code o} is null. Use {@link
+ #sqlite3_value_java_object} to fetch it.
+
+ This is implemented in terms of C's sqlite3_result_pointer(),
+ but that function is not exposed to JNI because (A)
+ cross-language semantic mismatch and (B) Java doesn't need that
+ argument for its intended purpose (type safety).
+
+
Note that there is no sqlite3_column_java_object(), as the
+ C-level API has no sqlite3_column_pointer() to proxy.
+
+ @see #sqlite3_value_java_object
+ @see #sqlite3_bind_java_object
+ */
+ public static native void sqlite3_result_java_object(
+ @NotNull sqlite3_context cx, @NotNull Object o
+ );
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, @NotNull Boolean v
+ ){
+ sqlite3_result_int(cx, v ? 1 : 0);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, boolean v
+ ){
+ sqlite3_result_int(cx, v ? 1 : 0);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, @NotNull Double v
+ ){
+ sqlite3_result_double(cx, v);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, double v
+ ){
+ sqlite3_result_double(cx, v);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, @NotNull Integer v
+ ){
+ sqlite3_result_int(cx, v);
+ }
+
+ public static void sqlite3_result_set(@NotNull sqlite3_context cx, int v){
+ sqlite3_result_int(cx, v);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, @NotNull Long v
+ ){
+ sqlite3_result_int64(cx, v);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, long v
+ ){
+ sqlite3_result_int64(cx, v);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, @Nullable String v
+ ){
+ if( null==v ) sqlite3_result_null(cx);
+ else sqlite3_result_text(cx, v);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, @Nullable byte[] blob
+ ){
+ if( null==blob ) sqlite3_result_null(cx);
+ else sqlite3_result_blob(cx, blob, blob.length);
+ }
+
+ public static native void sqlite3_result_value(
+ @NotNull sqlite3_context cx, @NotNull sqlite3_value v
+ );
+
+ public static native void sqlite3_result_zeroblob(
+ @NotNull sqlite3_context cx, int n
+ );
+
+ public static native int sqlite3_result_zeroblob64(
+ @NotNull sqlite3_context cx, long n
+ );
+
+ /**
+ This overload is private because its final parameter is arguably
+ unnecessary in Java.
+ */
+ private static native void sqlite3_result_blob(
+ @NotNull sqlite3_context cx, @Nullable byte[] blob, int maxLen
+ );
+
+ public static void sqlite3_result_blob(
+ @NotNull sqlite3_context cx, @Nullable byte[] blob
+ ){
+ sqlite3_result_blob(cx, blob, (int)(null==blob ? 0 : blob.length));
+ }
+
+ /**
+ Binds the given text using C's sqlite3_result_blob64() unless:
+
+
+
+ - @param blob is null: translates to sqlite3_result_null()
+
+ - @param blob is too large: translates to
+ sqlite3_result_error_toobig()
+
+
+
+ If @param maxLen is larger than blob.length, it is truncated
+ to that value. If it is negative, results are undefined.
+
+ This overload is private because its final parameter is
+ arguably unnecessary in Java.
+ */
+ private static native void sqlite3_result_blob64(
+ @NotNull sqlite3_context cx, @Nullable byte[] blob, long maxLen
+ );
+
+ public static void sqlite3_result_blob64(
+ @NotNull sqlite3_context cx, @Nullable byte[] blob
+ ){
+ sqlite3_result_blob64(cx, blob, (long)(null==blob ? 0 : blob.length));
+ }
+
+ /**
+ This overload is private because its final parameter is
+ arguably unnecessary in Java.
+ */
+ private static native void sqlite3_result_text(
+ @NotNull sqlite3_context cx, @Nullable byte[] utf8, int maxLen
+ );
+
+ public static void sqlite3_result_text(
+ @NotNull sqlite3_context cx, @Nullable byte[] utf8
+ ){
+ sqlite3_result_text(cx, utf8, null==utf8 ? 0 : utf8.length);
+ }
+
+ public static void sqlite3_result_text(
+ @NotNull sqlite3_context cx, @Nullable String text
+ ){
+ if(null == text) sqlite3_result_null(cx);
+ else{
+ final byte[] utf8 = text.getBytes(StandardCharsets.UTF_8);
+ sqlite3_result_text(cx, utf8, utf8.length);
+ }
+ }
+
+ /**
+ Binds the given text using C's sqlite3_result_text64() unless:
+
+
+
+ - text is null: translates to a call to sqlite3_result_null()
+
+ - text is too large: translates to a call to
+ {@link #sqlite3_result_error_toobig}
+
+ - The @param encoding argument has an invalid value: translates to
+ {@link sqlite3_result_error_code} with code SQLITE_FORMAT.
+
+
+
+ If maxLength (in bytes, not characters) is larger than
+ text.length, it is silently truncated to text.length. If it is
+ negative, results are undefined. If text is null, the subsequent
+ arguments are ignored.
+
+ This overload is private because its maxLength parameter is
+ arguably unnecessary in Java.
+ */
+ private static native void sqlite3_result_text64(
+ @NotNull sqlite3_context cx, @Nullable byte[] text,
+ long maxLength, int encoding
+ );
+
+ /**
+ Sets the current UDF result to the given bytes, which are assumed
+ be encoded in UTF-16 using the platform's byte order.
+ */
+ public static void sqlite3_result_text16(
+ @NotNull sqlite3_context cx, @Nullable byte[] utf16
+ ){
+ if(null == utf16) sqlite3_result_null(cx);
+ else sqlite3_result_text64(cx, utf16, utf16.length, SQLITE_UTF16);
+ }
+
+ public static void sqlite3_result_text16(
+ @NotNull sqlite3_context cx, @Nullable String text
+ ){
+ if(null == text) sqlite3_result_null(cx);
+ else{
+ final byte[] b = text.getBytes(StandardCharsets.UTF_16);
+ sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16);
+ }
+ }
+
+ static native RollbackHookCallback sqlite3_rollback_hook(
+ @NotNull long ptrToDb, @Nullable RollbackHookCallback hook
+ );
+
+ public static RollbackHookCallback sqlite3_rollback_hook(
+ @NotNull sqlite3 db, @Nullable RollbackHookCallback hook
+ ){
+ return sqlite3_rollback_hook(db.getNativePointer(), hook);
+ }
+
+ public static native int sqlite3_set_authorizer(
+ @NotNull sqlite3 db, @Nullable AuthorizerCallback auth
+ );
+
+ public static native void sqlite3_set_auxdata(
+ @NotNull sqlite3_context cx, int n, @Nullable Object data
+ );
+
+ public static native void sqlite3_set_last_insert_rowid(
+ @NotNull sqlite3 db, long rowid
+ );
+
+
+ /**
+ In addition to calling the C-level sqlite3_shutdown(), the JNI
+ binding also cleans up all stale per-thread state managed by the
+ library, as well as any registered auto-extensions, and frees up
+ various bits of memory. Calling this while database handles or
+ prepared statements are still active will leak resources. Trying
+ to use those objects after this routine is called invoked
+ undefined behavior.
+ */
+ public static synchronized native int sqlite3_shutdown();
+
+ public static native int sqlite3_sleep(int ms);
+
+ public static native String sqlite3_sourceid();
+
+ public static native String sqlite3_sql(@NotNull sqlite3_stmt stmt);
+
+ //! Consider removing this. We can use sqlite3_status64() instead,
+ // or use that one's impl with this one's name.
+ public static native int sqlite3_status(
+ int op, @NotNull OutputPointer.Int32 pCurrent,
+ @NotNull OutputPointer.Int32 pHighwater, boolean reset
+ );
+
+ public static native int sqlite3_status64(
+ int op, @NotNull OutputPointer.Int64 pCurrent,
+ @NotNull OutputPointer.Int64 pHighwater, boolean reset
+ );
+
+ public static native int sqlite3_step(@NotNull sqlite3_stmt stmt);
+
+ public static native boolean sqlite3_stmt_busy(@NotNull sqlite3_stmt stmt);
+
+ static native int sqlite3_stmt_explain(@NotNull long ptrToStmt, int op);
+
+ public static int sqlite3_stmt_explain(@NotNull sqlite3_stmt stmt, int op){
+ return sqlite3_stmt_explain(stmt.getNativePointer(), op);
+ }
+
+ static native int sqlite3_stmt_isexplain(@NotNull long ptrToStmt);
+
+ public static int sqlite3_stmt_isexplain(@NotNull sqlite3_stmt stmt){
+ return sqlite3_stmt_isexplain(stmt.getNativePointer());
+ }
+
+ public static native boolean sqlite3_stmt_readonly(@NotNull sqlite3_stmt stmt);
+
+ public static native int sqlite3_stmt_status(
+ @NotNull sqlite3_stmt stmt, int op, boolean reset
+ );
+
+ /**
+ Internal impl of the public sqlite3_strglob() method. Neither
+ argument may be null and both must be NUL-terminated UTF-8.
+
+ This overload is private because: (A) to keep users from
+ inadvertently passing non-NUL-terminated byte arrays (an easy
+ thing to do). (B) it is cheaper to NUL-terminate the
+ String-to-byte-array conversion in the Java implementation
+ (sqlite3_strglob(String,String)) than to do that in C, so that
+ signature is the public-facing one.
+ */
+ private static native int sqlite3_strglob(
+ @NotNull byte[] glob, @NotNull byte[] nullTerminatedUtf8
+ );
+
+ public static int sqlite3_strglob(
+ @NotNull String glob, @NotNull String txt
+ ){
+ return sqlite3_strglob(nulTerminateUtf8(glob),
+ nulTerminateUtf8(txt));
+ }
+
+ /**
+ The LIKE counterpart of the private sqlite3_strglob() method.
+ */
+ private static native int sqlite3_strlike(
+ @NotNull byte[] glob, @NotNull byte[] nullTerminatedUtf8,
+ int escChar
+ );
+
+ public static int sqlite3_strlike(
+ @NotNull String glob, @NotNull String txt, char escChar
+ ){
+ return sqlite3_strlike(nulTerminateUtf8(glob),
+ nulTerminateUtf8(txt),
+ (int)escChar);
+ }
+
+ static native int sqlite3_system_errno(@NotNull long ptrToDb);
+
+ public static int sqlite3_system_errno(@NotNull sqlite3 db){
+ return sqlite3_system_errno(db.getNativePointer());
+ }
+
+ public static native int sqlite3_table_column_metadata(
+ @NotNull sqlite3 db, @NotNull String zDbName,
+ @NotNull String zTableName, @NotNull String zColumnName,
+ @Nullable OutputPointer.String pzDataType,
+ @Nullable OutputPointer.String pzCollSeq,
+ @Nullable OutputPointer.Bool pNotNull,
+ @Nullable OutputPointer.Bool pPrimaryKey,
+ @Nullable OutputPointer.Bool pAutoinc
+ );
+
+ /**
+ Convenience overload which returns its results via a single
+ output object. If this function returns non-0 (error), the the
+ contents of the output object are not modified.
+ */
+ public static int sqlite3_table_column_metadata(
+ @NotNull sqlite3 db, @NotNull String zDbName,
+ @NotNull String zTableName, @NotNull String zColumnName,
+ @NotNull TableColumnMetadata out){
+ return sqlite3_table_column_metadata(
+ db, zDbName, zTableName, zColumnName,
+ out.pzDataType, out.pzCollSeq, out.pNotNull,
+ out.pPrimaryKey, out.pAutoinc);
+ }
+
+ /**
+ Convenience overload which returns the column metadata object on
+ success and null on error.
+ */
+ public static TableColumnMetadata sqlite3_table_column_metadata(
+ @NotNull sqlite3 db, @NotNull String zDbName,
+ @NotNull String zTableName, @NotNull String zColumnName){
+ final TableColumnMetadata out = new TableColumnMetadata();
+ return 0==sqlite3_table_column_metadata(
+ db, zDbName, zTableName, zColumnName, out
+ ) ? out : null;
+ }
+
+ public static native int sqlite3_threadsafe();
+
+ static native int sqlite3_total_changes(@NotNull long ptrToDb);
+
+ public static int sqlite3_total_changes(@NotNull sqlite3 db){
+ return sqlite3_total_changes(db.getNativePointer());
+ }
+
+ static native long sqlite3_total_changes64(@NotNull long ptrToDb);
+
+ public static long sqlite3_total_changes64(@NotNull sqlite3 db){
+ return sqlite3_total_changes64(db.getNativePointer());
+ }
+
+ /**
+ Works like C's sqlite3_trace_v2() except that the 3rd argument to that
+ function is elided here because the roles of that functions' 3rd and 4th
+ arguments are encapsulated in the final argument to this function.
+
+ Unlike the C API, which is documented as always returning 0,
+ this implementation returns non-0 if initialization of the tracer
+ mapping state fails (e.g. on OOM).
+ */
+ public static native int sqlite3_trace_v2(
+ @NotNull sqlite3 db, int traceMask, @Nullable TraceV2Callback tracer
+ );
+
+ public static native int sqlite3_txn_state(
+ @NotNull sqlite3 db, @Nullable String zSchema
+ );
+
+ static native UpdateHookCallback sqlite3_update_hook(
+ @NotNull long ptrToDb, @Nullable UpdateHookCallback hook
+ );
+
+ public static UpdateHookCallback sqlite3_update_hook(
+ @NotNull sqlite3 db, @Nullable UpdateHookCallback hook
+ ){
+ return sqlite3_update_hook(db.getNativePointer(), hook);
+ }
+
+ /*
+ Note that:
+
+ void * sqlite3_user_data(sqlite3_context*)
+
+ Is not relevant in the JNI binding, as its feature is replaced by
+ the ability to pass an object, including any relevant state, to
+ sqlite3_create_function().
+ */
+
+ static native byte[] sqlite3_value_blob(@NotNull long ptrToValue);
+
+ public static byte[] sqlite3_value_blob(@NotNull sqlite3_value v){
+ return sqlite3_value_blob(v.getNativePointer());
+ }
+
+ static native int sqlite3_value_bytes(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_bytes(@NotNull sqlite3_value v){
+ return sqlite3_value_bytes(v.getNativePointer());
+ }
+
+ static native int sqlite3_value_bytes16(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_bytes16(@NotNull sqlite3_value v){
+ return sqlite3_value_bytes16(v.getNativePointer());
+ }
+
+ static native double sqlite3_value_double(@NotNull long ptrToValue);
+
+ public static double sqlite3_value_double(@NotNull sqlite3_value v){
+ return sqlite3_value_double(v.getNativePointer());
+ }
+
+ static native sqlite3_value sqlite3_value_dup(@NotNull long ptrToValue);
+
+ public static sqlite3_value sqlite3_value_dup(@NotNull sqlite3_value v){
+ return sqlite3_value_dup(v.getNativePointer());
+ }
+
+ static native int sqlite3_value_encoding(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_encoding(@NotNull sqlite3_value v){
+ return sqlite3_value_encoding(v.getNativePointer());
+ }
+
+ static native void sqlite3_value_free(@Nullable long ptrToValue);
+
+ public static void sqlite3_value_free(@Nullable sqlite3_value v){
+ sqlite3_value_free(v.getNativePointer());
+ }
+
+ static native boolean sqlite3_value_frombind(@NotNull long ptrToValue);
+
+ public static boolean sqlite3_value_frombind(@NotNull sqlite3_value v){
+ return sqlite3_value_frombind(v.getNativePointer());
+ }
+
+ static native int sqlite3_value_int(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_int(@NotNull sqlite3_value v){
+ return sqlite3_value_int(v.getNativePointer());
+ }
+
+ static native long sqlite3_value_int64(@NotNull long ptrToValue);
+
+ public static long sqlite3_value_int64(@NotNull sqlite3_value v){
+ return sqlite3_value_int64(v.getNativePointer());
+ }
+
+ static native Object sqlite3_value_java_object(@NotNull long ptrToValue);
+
+ /**
+ If the given value was set using {@link
+ #sqlite3_result_java_object} then this function returns that
+ object, else it returns null.
+
+
It is up to the caller to inspect the object to determine its
+ type, and cast it if necessary.
+ */
+ public static Object sqlite3_value_java_object(@NotNull sqlite3_value v){
+ return sqlite3_value_java_object(v.getNativePointer());
+ }
+
+ /**
+ A variant of sqlite3_value_java_object() which returns the
+ fetched object cast to T if the object is an instance of the
+ given Class, else it returns null.
+ */
+ @SuppressWarnings("unchecked")
+ public static T sqlite3_value_java_casted(@NotNull sqlite3_value v,
+ @NotNull Class type){
+ final Object o = sqlite3_value_java_object(v);
+ return type.isInstance(o) ? (T)o : null;
+ }
+
+ static native int sqlite3_value_nochange(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_nochange(@NotNull sqlite3_value v){
+ return sqlite3_value_nochange(v.getNativePointer());
+ }
+
+ static native int sqlite3_value_numeric_type(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_numeric_type(@NotNull sqlite3_value v){
+ return sqlite3_value_numeric_type(v.getNativePointer());
+ }
+
+ static native int sqlite3_value_subtype(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_subtype(@NotNull sqlite3_value v){
+ return sqlite3_value_subtype(v.getNativePointer());
+ }
+
+ static native byte[] sqlite3_value_text(@NotNull long ptrToValue);
+
+ /**
+ Functions identially to the C API, and this note is just to
+ stress that the returned bytes are encoded as UTF-8. It returns
+ null if the underlying C-level sqlite3_value_text() returns NULL
+ or on allocation error.
+ */
+ public static byte[] sqlite3_value_text(@NotNull sqlite3_value v){
+ return sqlite3_value_text(v.getNativePointer());
+ }
+
+ static native String sqlite3_value_text16(@NotNull long ptrToValue);
+
+ public static String sqlite3_value_text16(@NotNull sqlite3_value v){
+ return sqlite3_value_text16(v.getNativePointer());
+ }
+
+ static native int sqlite3_value_type(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_type(@NotNull sqlite3_value v){
+ return sqlite3_value_type(v.getNativePointer());
+ }
+
+ /**
+ This is NOT part of the public API. It exists solely as a place
+ for this code's developers to collect internal metrics and such.
+ It has no stable interface. It may go way or change behavior at
+ any time.
+ */
+ public static native void sqlite3_jni_internal_details();
+
+ //////////////////////////////////////////////////////////////////////
+ // SQLITE_... constants follow...
+
+ // version info
+ public static final int SQLITE_VERSION_NUMBER = sqlite3_libversion_number();
+ public static final String SQLITE_VERSION = sqlite3_libversion();
+ public static final String SQLITE_SOURCE_ID = sqlite3_sourceid();
+
+ // access
+ public static final int SQLITE_ACCESS_EXISTS = 0;
+ public static final int SQLITE_ACCESS_READWRITE = 1;
+ public static final int SQLITE_ACCESS_READ = 2;
+
+ // authorizer
+ public static final int SQLITE_DENY = 1;
+ public static final int SQLITE_IGNORE = 2;
+ public static final int SQLITE_CREATE_INDEX = 1;
+ public static final int SQLITE_CREATE_TABLE = 2;
+ public static final int SQLITE_CREATE_TEMP_INDEX = 3;
+ public static final int SQLITE_CREATE_TEMP_TABLE = 4;
+ public static final int SQLITE_CREATE_TEMP_TRIGGER = 5;
+ public static final int SQLITE_CREATE_TEMP_VIEW = 6;
+ public static final int SQLITE_CREATE_TRIGGER = 7;
+ public static final int SQLITE_CREATE_VIEW = 8;
+ public static final int SQLITE_DELETE = 9;
+ public static final int SQLITE_DROP_INDEX = 10;
+ public static final int SQLITE_DROP_TABLE = 11;
+ public static final int SQLITE_DROP_TEMP_INDEX = 12;
+ public static final int SQLITE_DROP_TEMP_TABLE = 13;
+ public static final int SQLITE_DROP_TEMP_TRIGGER = 14;
+ public static final int SQLITE_DROP_TEMP_VIEW = 15;
+ public static final int SQLITE_DROP_TRIGGER = 16;
+ public static final int SQLITE_DROP_VIEW = 17;
+ public static final int SQLITE_INSERT = 18;
+ public static final int SQLITE_PRAGMA = 19;
+ public static final int SQLITE_READ = 20;
+ public static final int SQLITE_SELECT = 21;
+ public static final int SQLITE_TRANSACTION = 22;
+ public static final int SQLITE_UPDATE = 23;
+ public static final int SQLITE_ATTACH = 24;
+ public static final int SQLITE_DETACH = 25;
+ public static final int SQLITE_ALTER_TABLE = 26;
+ public static final int SQLITE_REINDEX = 27;
+ public static final int SQLITE_ANALYZE = 28;
+ public static final int SQLITE_CREATE_VTABLE = 29;
+ public static final int SQLITE_DROP_VTABLE = 30;
+ public static final int SQLITE_FUNCTION = 31;
+ public static final int SQLITE_SAVEPOINT = 32;
+ public static final int SQLITE_RECURSIVE = 33;
+
+ // blob finalizers: these should, because they are treated as
+ // special pointer values in C, ideally have the same sizeof() as
+ // the platform's (void*), but we can't know that size from here.
+ public static final long SQLITE_STATIC = 0;
+ public static final long SQLITE_TRANSIENT = -1;
+
+ // changeset
+ public static final int SQLITE_CHANGESETSTART_INVERT = 2;
+ public static final int SQLITE_CHANGESETAPPLY_NOSAVEPOINT = 1;
+ public static final int SQLITE_CHANGESETAPPLY_INVERT = 2;
+ public static final int SQLITE_CHANGESETAPPLY_IGNORENOOP = 4;
+ public static final int SQLITE_CHANGESET_DATA = 1;
+ public static final int SQLITE_CHANGESET_NOTFOUND = 2;
+ public static final int SQLITE_CHANGESET_CONFLICT = 3;
+ public static final int SQLITE_CHANGESET_CONSTRAINT = 4;
+ public static final int SQLITE_CHANGESET_FOREIGN_KEY = 5;
+ public static final int SQLITE_CHANGESET_OMIT = 0;
+ public static final int SQLITE_CHANGESET_REPLACE = 1;
+ public static final int SQLITE_CHANGESET_ABORT = 2;
+
+ // config
+ public static final int SQLITE_CONFIG_SINGLETHREAD = 1;
+ public static final int SQLITE_CONFIG_MULTITHREAD = 2;
+ public static final int SQLITE_CONFIG_SERIALIZED = 3;
+ public static final int SQLITE_CONFIG_MALLOC = 4;
+ public static final int SQLITE_CONFIG_GETMALLOC = 5;
+ public static final int SQLITE_CONFIG_SCRATCH = 6;
+ public static final int SQLITE_CONFIG_PAGECACHE = 7;
+ public static final int SQLITE_CONFIG_HEAP = 8;
+ public static final int SQLITE_CONFIG_MEMSTATUS = 9;
+ public static final int SQLITE_CONFIG_MUTEX = 10;
+ public static final int SQLITE_CONFIG_GETMUTEX = 11;
+ public static final int SQLITE_CONFIG_LOOKASIDE = 13;
+ public static final int SQLITE_CONFIG_PCACHE = 14;
+ public static final int SQLITE_CONFIG_GETPCACHE = 15;
+ public static final int SQLITE_CONFIG_LOG = 16;
+ public static final int SQLITE_CONFIG_URI = 17;
+ public static final int SQLITE_CONFIG_PCACHE2 = 18;
+ public static final int SQLITE_CONFIG_GETPCACHE2 = 19;
+ public static final int SQLITE_CONFIG_COVERING_INDEX_SCAN = 20;
+ public static final int SQLITE_CONFIG_SQLLOG = 21;
+ public static final int SQLITE_CONFIG_MMAP_SIZE = 22;
+ public static final int SQLITE_CONFIG_WIN32_HEAPSIZE = 23;
+ public static final int SQLITE_CONFIG_PCACHE_HDRSZ = 24;
+ public static final int SQLITE_CONFIG_PMASZ = 25;
+ public static final int SQLITE_CONFIG_STMTJRNL_SPILL = 26;
+ public static final int SQLITE_CONFIG_SMALL_MALLOC = 27;
+ public static final int SQLITE_CONFIG_SORTERREF_SIZE = 28;
+ public static final int SQLITE_CONFIG_MEMDB_MAXSIZE = 29;
+
+ // data types
+ public static final int SQLITE_INTEGER = 1;
+ public static final int SQLITE_FLOAT = 2;
+ public static final int SQLITE_TEXT = 3;
+ public static final int SQLITE_BLOB = 4;
+ public static final int SQLITE_NULL = 5;
+
+ // db config
+ public static final int SQLITE_DBCONFIG_MAINDBNAME = 1000;
+ public static final int SQLITE_DBCONFIG_LOOKASIDE = 1001;
+ public static final int SQLITE_DBCONFIG_ENABLE_FKEY = 1002;
+ public static final int SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003;
+ public static final int SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004;
+ public static final int SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005;
+ public static final int SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006;
+ public static final int SQLITE_DBCONFIG_ENABLE_QPSG = 1007;
+ public static final int SQLITE_DBCONFIG_TRIGGER_EQP = 1008;
+ public static final int SQLITE_DBCONFIG_RESET_DATABASE = 1009;
+ public static final int SQLITE_DBCONFIG_DEFENSIVE = 1010;
+ public static final int SQLITE_DBCONFIG_WRITABLE_SCHEMA = 1011;
+ public static final int SQLITE_DBCONFIG_LEGACY_ALTER_TABLE = 1012;
+ public static final int SQLITE_DBCONFIG_DQS_DML = 1013;
+ public static final int SQLITE_DBCONFIG_DQS_DDL = 1014;
+ public static final int SQLITE_DBCONFIG_ENABLE_VIEW = 1015;
+ public static final int SQLITE_DBCONFIG_LEGACY_FILE_FORMAT = 1016;
+ public static final int SQLITE_DBCONFIG_TRUSTED_SCHEMA = 1017;
+ public static final int SQLITE_DBCONFIG_STMT_SCANSTATUS = 1018;
+ public static final int SQLITE_DBCONFIG_REVERSE_SCANORDER = 1019;
+ public static final int SQLITE_DBCONFIG_MAX = 1019;
+
+ // db status
+ public static final int SQLITE_DBSTATUS_LOOKASIDE_USED = 0;
+ public static final int SQLITE_DBSTATUS_CACHE_USED = 1;
+ public static final int SQLITE_DBSTATUS_SCHEMA_USED = 2;
+ public static final int SQLITE_DBSTATUS_STMT_USED = 3;
+ public static final int SQLITE_DBSTATUS_LOOKASIDE_HIT = 4;
+ public static final int SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE = 5;
+ public static final int SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL = 6;
+ public static final int SQLITE_DBSTATUS_CACHE_HIT = 7;
+ public static final int SQLITE_DBSTATUS_CACHE_MISS = 8;
+ public static final int SQLITE_DBSTATUS_CACHE_WRITE = 9;
+ public static final int SQLITE_DBSTATUS_DEFERRED_FKS = 10;
+ public static final int SQLITE_DBSTATUS_CACHE_USED_SHARED = 11;
+ public static final int SQLITE_DBSTATUS_CACHE_SPILL = 12;
+ public static final int SQLITE_DBSTATUS_MAX = 12;
+
+ // encodings
+ public static final int SQLITE_UTF8 = 1;
+ public static final int SQLITE_UTF16LE = 2;
+ public static final int SQLITE_UTF16BE = 3;
+ public static final int SQLITE_UTF16 = 4;
+ public static final int SQLITE_UTF16_ALIGNED = 8;
+
+ // fcntl
+ public static final int SQLITE_FCNTL_LOCKSTATE = 1;
+ public static final int SQLITE_FCNTL_GET_LOCKPROXYFILE = 2;
+ public static final int SQLITE_FCNTL_SET_LOCKPROXYFILE = 3;
+ public static final int SQLITE_FCNTL_LAST_ERRNO = 4;
+ public static final int SQLITE_FCNTL_SIZE_HINT = 5;
+ public static final int SQLITE_FCNTL_CHUNK_SIZE = 6;
+ public static final int SQLITE_FCNTL_FILE_POINTER = 7;
+ public static final int SQLITE_FCNTL_SYNC_OMITTED = 8;
+ public static final int SQLITE_FCNTL_WIN32_AV_RETRY = 9;
+ public static final int SQLITE_FCNTL_PERSIST_WAL = 10;
+ public static final int SQLITE_FCNTL_OVERWRITE = 11;
+ public static final int SQLITE_FCNTL_VFSNAME = 12;
+ public static final int SQLITE_FCNTL_POWERSAFE_OVERWRITE = 13;
+ public static final int SQLITE_FCNTL_PRAGMA = 14;
+ public static final int SQLITE_FCNTL_BUSYHANDLER = 15;
+ public static final int SQLITE_FCNTL_TEMPFILENAME = 16;
+ public static final int SQLITE_FCNTL_MMAP_SIZE = 18;
+ public static final int SQLITE_FCNTL_TRACE = 19;
+ public static final int SQLITE_FCNTL_HAS_MOVED = 20;
+ public static final int SQLITE_FCNTL_SYNC = 21;
+ public static final int SQLITE_FCNTL_COMMIT_PHASETWO = 22;
+ public static final int SQLITE_FCNTL_WIN32_SET_HANDLE = 23;
+ public static final int SQLITE_FCNTL_WAL_BLOCK = 24;
+ public static final int SQLITE_FCNTL_ZIPVFS = 25;
+ public static final int SQLITE_FCNTL_RBU = 26;
+ public static final int SQLITE_FCNTL_VFS_POINTER = 27;
+ public static final int SQLITE_FCNTL_JOURNAL_POINTER = 28;
+ public static final int SQLITE_FCNTL_WIN32_GET_HANDLE = 29;
+ public static final int SQLITE_FCNTL_PDB = 30;
+ public static final int SQLITE_FCNTL_BEGIN_ATOMIC_WRITE = 31;
+ public static final int SQLITE_FCNTL_COMMIT_ATOMIC_WRITE = 32;
+ public static final int SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE = 33;
+ public static final int SQLITE_FCNTL_LOCK_TIMEOUT = 34;
+ public static final int SQLITE_FCNTL_DATA_VERSION = 35;
+ public static final int SQLITE_FCNTL_SIZE_LIMIT = 36;
+ public static final int SQLITE_FCNTL_CKPT_DONE = 37;
+ public static final int SQLITE_FCNTL_RESERVE_BYTES = 38;
+ public static final int SQLITE_FCNTL_CKPT_START = 39;
+ public static final int SQLITE_FCNTL_EXTERNAL_READER = 40;
+ public static final int SQLITE_FCNTL_CKSM_FILE = 41;
+ public static final int SQLITE_FCNTL_RESET_CACHE = 42;
+
+ // flock
+ public static final int SQLITE_LOCK_NONE = 0;
+ public static final int SQLITE_LOCK_SHARED = 1;
+ public static final int SQLITE_LOCK_RESERVED = 2;
+ public static final int SQLITE_LOCK_PENDING = 3;
+ public static final int SQLITE_LOCK_EXCLUSIVE = 4;
+
+ // iocap
+ public static final int SQLITE_IOCAP_ATOMIC = 1;
+ public static final int SQLITE_IOCAP_ATOMIC512 = 2;
+ public static final int SQLITE_IOCAP_ATOMIC1K = 4;
+ public static final int SQLITE_IOCAP_ATOMIC2K = 8;
+ public static final int SQLITE_IOCAP_ATOMIC4K = 16;
+ public static final int SQLITE_IOCAP_ATOMIC8K = 32;
+ public static final int SQLITE_IOCAP_ATOMIC16K = 64;
+ public static final int SQLITE_IOCAP_ATOMIC32K = 128;
+ public static final int SQLITE_IOCAP_ATOMIC64K = 256;
+ public static final int SQLITE_IOCAP_SAFE_APPEND = 512;
+ public static final int SQLITE_IOCAP_SEQUENTIAL = 1024;
+ public static final int SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN = 2048;
+ public static final int SQLITE_IOCAP_POWERSAFE_OVERWRITE = 4096;
+ public static final int SQLITE_IOCAP_IMMUTABLE = 8192;
+ public static final int SQLITE_IOCAP_BATCH_ATOMIC = 16384;
+
+ // limits
+ public static final int SQLITE_LIMIT_LENGTH = 0;
+ public static final int SQLITE_LIMIT_SQL_LENGTH = 1;
+ public static final int SQLITE_LIMIT_COLUMN = 2;
+ public static final int SQLITE_LIMIT_EXPR_DEPTH = 3;
+ public static final int SQLITE_LIMIT_COMPOUND_SELECT = 4;
+ public static final int SQLITE_LIMIT_VDBE_OP = 5;
+ public static final int SQLITE_LIMIT_FUNCTION_ARG = 6;
+ public static final int SQLITE_LIMIT_ATTACHED = 7;
+ public static final int SQLITE_LIMIT_LIKE_PATTERN_LENGTH = 8;
+ public static final int SQLITE_LIMIT_VARIABLE_NUMBER = 9;
+ public static final int SQLITE_LIMIT_TRIGGER_DEPTH = 10;
+ public static final int SQLITE_LIMIT_WORKER_THREADS = 11;
+
+ // open flags
+
+ public static final int SQLITE_OPEN_READONLY = 0x00000001 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_READWRITE = 0x00000002 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_CREATE = 0x00000004 /* Ok for sqlite3_open_v2() */;
+ //public static final int SQLITE_OPEN_DELETEONCLOSE = 0x00000008 /* VFS only */;
+ //public static final int SQLITE_OPEN_EXCLUSIVE = 0x00000010 /* VFS only */;
+ //public static final int SQLITE_OPEN_AUTOPROXY = 0x00000020 /* VFS only */;
+ public static final int SQLITE_OPEN_URI = 0x00000040 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_MEMORY = 0x00000080 /* Ok for sqlite3_open_v2() */;
+ //public static final int SQLITE_OPEN_MAIN_DB = 0x00000100 /* VFS only */;
+ //public static final int SQLITE_OPEN_TEMP_DB = 0x00000200 /* VFS only */;
+ //public static final int SQLITE_OPEN_TRANSIENT_DB = 0x00000400 /* VFS only */;
+ //public static final int SQLITE_OPEN_MAIN_JOURNAL = 0x00000800 /* VFS only */;
+ //public static final int SQLITE_OPEN_TEMP_JOURNAL = 0x00001000 /* VFS only */;
+ //public static final int SQLITE_OPEN_SUBJOURNAL = 0x00002000 /* VFS only */;
+ //public static final int SQLITE_OPEN_SUPER_JOURNAL = 0x00004000 /* VFS only */;
+ public static final int SQLITE_OPEN_NOMUTEX = 0x00008000 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_FULLMUTEX = 0x00010000 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_SHAREDCACHE = 0x00020000 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_PRIVATECACHE = 0x00040000 /* Ok for sqlite3_open_v2() */;
+ //public static final int SQLITE_OPEN_WAL = 0x00080000 /* VFS only */;
+ public static final int SQLITE_OPEN_NOFOLLOW = 0x01000000 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_EXRESCODE = 0x02000000 /* Extended result codes */;
+
+ // prepare flags
+ public static final int SQLITE_PREPARE_PERSISTENT = 1;
+ public static final int SQLITE_PREPARE_NORMALIZE = 2;
+ public static final int SQLITE_PREPARE_NO_VTAB = 4;
+
+ // result codes
+ public static final int SQLITE_OK = 0;
+ public static final int SQLITE_ERROR = 1;
+ public static final int SQLITE_INTERNAL = 2;
+ public static final int SQLITE_PERM = 3;
+ public static final int SQLITE_ABORT = 4;
+ public static final int SQLITE_BUSY = 5;
+ public static final int SQLITE_LOCKED = 6;
+ public static final int SQLITE_NOMEM = 7;
+ public static final int SQLITE_READONLY = 8;
+ public static final int SQLITE_INTERRUPT = 9;
+ public static final int SQLITE_IOERR = 10;
+ public static final int SQLITE_CORRUPT = 11;
+ public static final int SQLITE_NOTFOUND = 12;
+ public static final int SQLITE_FULL = 13;
+ public static final int SQLITE_CANTOPEN = 14;
+ public static final int SQLITE_PROTOCOL = 15;
+ public static final int SQLITE_EMPTY = 16;
+ public static final int SQLITE_SCHEMA = 17;
+ public static final int SQLITE_TOOBIG = 18;
+ public static final int SQLITE_CONSTRAINT = 19;
+ public static final int SQLITE_MISMATCH = 20;
+ public static final int SQLITE_MISUSE = 21;
+ public static final int SQLITE_NOLFS = 22;
+ public static final int SQLITE_AUTH = 23;
+ public static final int SQLITE_FORMAT = 24;
+ public static final int SQLITE_RANGE = 25;
+ public static final int SQLITE_NOTADB = 26;
+ public static final int SQLITE_NOTICE = 27;
+ public static final int SQLITE_WARNING = 28;
+ public static final int SQLITE_ROW = 100;
+ public static final int SQLITE_DONE = 101;
+ public static final int SQLITE_ERROR_MISSING_COLLSEQ = 257;
+ public static final int SQLITE_ERROR_RETRY = 513;
+ public static final int SQLITE_ERROR_SNAPSHOT = 769;
+ public static final int SQLITE_IOERR_READ = 266;
+ public static final int SQLITE_IOERR_SHORT_READ = 522;
+ public static final int SQLITE_IOERR_WRITE = 778;
+ public static final int SQLITE_IOERR_FSYNC = 1034;
+ public static final int SQLITE_IOERR_DIR_FSYNC = 1290;
+ public static final int SQLITE_IOERR_TRUNCATE = 1546;
+ public static final int SQLITE_IOERR_FSTAT = 1802;
+ public static final int SQLITE_IOERR_UNLOCK = 2058;
+ public static final int SQLITE_IOERR_RDLOCK = 2314;
+ public static final int SQLITE_IOERR_DELETE = 2570;
+ public static final int SQLITE_IOERR_BLOCKED = 2826;
+ public static final int SQLITE_IOERR_NOMEM = 3082;
+ public static final int SQLITE_IOERR_ACCESS = 3338;
+ public static final int SQLITE_IOERR_CHECKRESERVEDLOCK = 3594;
+ public static final int SQLITE_IOERR_LOCK = 3850;
+ public static final int SQLITE_IOERR_CLOSE = 4106;
+ public static final int SQLITE_IOERR_DIR_CLOSE = 4362;
+ public static final int SQLITE_IOERR_SHMOPEN = 4618;
+ public static final int SQLITE_IOERR_SHMSIZE = 4874;
+ public static final int SQLITE_IOERR_SHMLOCK = 5130;
+ public static final int SQLITE_IOERR_SHMMAP = 5386;
+ public static final int SQLITE_IOERR_SEEK = 5642;
+ public static final int SQLITE_IOERR_DELETE_NOENT = 5898;
+ public static final int SQLITE_IOERR_MMAP = 6154;
+ public static final int SQLITE_IOERR_GETTEMPPATH = 6410;
+ public static final int SQLITE_IOERR_CONVPATH = 6666;
+ public static final int SQLITE_IOERR_VNODE = 6922;
+ public static final int SQLITE_IOERR_AUTH = 7178;
+ public static final int SQLITE_IOERR_BEGIN_ATOMIC = 7434;
+ public static final int SQLITE_IOERR_COMMIT_ATOMIC = 7690;
+ public static final int SQLITE_IOERR_ROLLBACK_ATOMIC = 7946;
+ public static final int SQLITE_IOERR_DATA = 8202;
+ public static final int SQLITE_IOERR_CORRUPTFS = 8458;
+ public static final int SQLITE_LOCKED_SHAREDCACHE = 262;
+ public static final int SQLITE_LOCKED_VTAB = 518;
+ public static final int SQLITE_BUSY_RECOVERY = 261;
+ public static final int SQLITE_BUSY_SNAPSHOT = 517;
+ public static final int SQLITE_BUSY_TIMEOUT = 773;
+ public static final int SQLITE_CANTOPEN_NOTEMPDIR = 270;
+ public static final int SQLITE_CANTOPEN_ISDIR = 526;
+ public static final int SQLITE_CANTOPEN_FULLPATH = 782;
+ public static final int SQLITE_CANTOPEN_CONVPATH = 1038;
+ public static final int SQLITE_CANTOPEN_SYMLINK = 1550;
+ public static final int SQLITE_CORRUPT_VTAB = 267;
+ public static final int SQLITE_CORRUPT_SEQUENCE = 523;
+ public static final int SQLITE_CORRUPT_INDEX = 779;
+ public static final int SQLITE_READONLY_RECOVERY = 264;
+ public static final int SQLITE_READONLY_CANTLOCK = 520;
+ public static final int SQLITE_READONLY_ROLLBACK = 776;
+ public static final int SQLITE_READONLY_DBMOVED = 1032;
+ public static final int SQLITE_READONLY_CANTINIT = 1288;
+ public static final int SQLITE_READONLY_DIRECTORY = 1544;
+ public static final int SQLITE_ABORT_ROLLBACK = 516;
+ public static final int SQLITE_CONSTRAINT_CHECK = 275;
+ public static final int SQLITE_CONSTRAINT_COMMITHOOK = 531;
+ public static final int SQLITE_CONSTRAINT_FOREIGNKEY = 787;
+ public static final int SQLITE_CONSTRAINT_FUNCTION = 1043;
+ public static final int SQLITE_CONSTRAINT_NOTNULL = 1299;
+ public static final int SQLITE_CONSTRAINT_PRIMARYKEY = 1555;
+ public static final int SQLITE_CONSTRAINT_TRIGGER = 1811;
+ public static final int SQLITE_CONSTRAINT_UNIQUE = 2067;
+ public static final int SQLITE_CONSTRAINT_VTAB = 2323;
+ public static final int SQLITE_CONSTRAINT_ROWID = 2579;
+ public static final int SQLITE_CONSTRAINT_PINNED = 2835;
+ public static final int SQLITE_CONSTRAINT_DATATYPE = 3091;
+ public static final int SQLITE_NOTICE_RECOVER_WAL = 283;
+ public static final int SQLITE_NOTICE_RECOVER_ROLLBACK = 539;
+ public static final int SQLITE_WARNING_AUTOINDEX = 284;
+ public static final int SQLITE_AUTH_USER = 279;
+ public static final int SQLITE_OK_LOAD_PERMANENTLY = 256;
+
+ // serialize
+ public static final int SQLITE_SERIALIZE_NOCOPY = 1;
+ public static final int SQLITE_DESERIALIZE_FREEONCLOSE = 1;
+ public static final int SQLITE_DESERIALIZE_READONLY = 4;
+ public static final int SQLITE_DESERIALIZE_RESIZEABLE = 2;
+
+ // session
+ public static final int SQLITE_SESSION_CONFIG_STRMSIZE = 1;
+ public static final int SQLITE_SESSION_OBJCONFIG_SIZE = 1;
+
+ // sqlite3 status
+ public static final int SQLITE_STATUS_MEMORY_USED = 0;
+ public static final int SQLITE_STATUS_PAGECACHE_USED = 1;
+ public static final int SQLITE_STATUS_PAGECACHE_OVERFLOW = 2;
+ public static final int SQLITE_STATUS_MALLOC_SIZE = 5;
+ public static final int SQLITE_STATUS_PARSER_STACK = 6;
+ public static final int SQLITE_STATUS_PAGECACHE_SIZE = 7;
+ public static final int SQLITE_STATUS_MALLOC_COUNT = 9;
+
+ // stmt status
+ public static final int SQLITE_STMTSTATUS_FULLSCAN_STEP = 1;
+ public static final int SQLITE_STMTSTATUS_SORT = 2;
+ public static final int SQLITE_STMTSTATUS_AUTOINDEX = 3;
+ public static final int SQLITE_STMTSTATUS_VM_STEP = 4;
+ public static final int SQLITE_STMTSTATUS_REPREPARE = 5;
+ public static final int SQLITE_STMTSTATUS_RUN = 6;
+ public static final int SQLITE_STMTSTATUS_FILTER_MISS = 7;
+ public static final int SQLITE_STMTSTATUS_FILTER_HIT = 8;
+ public static final int SQLITE_STMTSTATUS_MEMUSED = 99;
+
+ // sync flags
+ public static final int SQLITE_SYNC_NORMAL = 2;
+ public static final int SQLITE_SYNC_FULL = 3;
+ public static final int SQLITE_SYNC_DATAONLY = 16;
+
+ // tracing flags
+ public static final int SQLITE_TRACE_STMT = 1;
+ public static final int SQLITE_TRACE_PROFILE = 2;
+ public static final int SQLITE_TRACE_ROW = 4;
+ public static final int SQLITE_TRACE_CLOSE = 8;
+
+ // transaction state
+ public static final int SQLITE_TXN_NONE = 0;
+ public static final int SQLITE_TXN_READ = 1;
+ public static final int SQLITE_TXN_WRITE = 2;
+
+ // udf flags
+ public static final int SQLITE_DETERMINISTIC = 0x000000800;
+ public static final int SQLITE_DIRECTONLY = 0x000080000;
+ public static final int SQLITE_INNOCUOUS = 0x000200000;
+
+ // virtual tables
+ public static final int SQLITE_INDEX_SCAN_UNIQUE = 1;
+ public static final int SQLITE_INDEX_CONSTRAINT_EQ = 2;
+ public static final int SQLITE_INDEX_CONSTRAINT_GT = 4;
+ public static final int SQLITE_INDEX_CONSTRAINT_LE = 8;
+ public static final int SQLITE_INDEX_CONSTRAINT_LT = 16;
+ public static final int SQLITE_INDEX_CONSTRAINT_GE = 32;
+ public static final int SQLITE_INDEX_CONSTRAINT_MATCH = 64;
+ public static final int SQLITE_INDEX_CONSTRAINT_LIKE = 65;
+ public static final int SQLITE_INDEX_CONSTRAINT_GLOB = 66;
+ public static final int SQLITE_INDEX_CONSTRAINT_REGEXP = 67;
+ public static final int SQLITE_INDEX_CONSTRAINT_NE = 68;
+ public static final int SQLITE_INDEX_CONSTRAINT_ISNOT = 69;
+ public static final int SQLITE_INDEX_CONSTRAINT_ISNOTNULL = 70;
+ public static final int SQLITE_INDEX_CONSTRAINT_ISNULL = 71;
+ public static final int SQLITE_INDEX_CONSTRAINT_IS = 72;
+ public static final int SQLITE_INDEX_CONSTRAINT_LIMIT = 73;
+ public static final int SQLITE_INDEX_CONSTRAINT_OFFSET = 74;
+ public static final int SQLITE_INDEX_CONSTRAINT_FUNCTION = 150;
+ public static final int SQLITE_VTAB_CONSTRAINT_SUPPORT = 1;
+ public static final int SQLITE_VTAB_INNOCUOUS = 2;
+ public static final int SQLITE_VTAB_DIRECTONLY = 3;
+ public static final int SQLITE_VTAB_USES_ALL_SCHEMAS = 4;
+ public static final int SQLITE_ROLLBACK = 1;
+ public static final int SQLITE_FAIL = 3;
+ public static final int SQLITE_REPLACE = 5;
+ static {
+ // This MUST come after the SQLITE_MAX_... values or else
+ // attempting to modify them silently fails.
+ init();
+ }
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,44 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+/**
+ This marker interface exists soley for use as a documentation and
+ class-grouping tool. It should be applied to interfaces or
+ classes which have a call() method implementing some specific
+ callback interface on behalf of the C library.
+
+ Unless very explicitely documented otherwise, callbacks must
+ never throw. Any which do throw but should not might trigger debug
+ output regarding the error, but the exception will not be
+ 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, all exceptions will necessarily be suppressed in order to
+ retain the C-style no-throw semantics.
+
+
Callbacks of this style follow a common naming convention:
+
+
1) They use the UpperCamelCase form of the C function they're
+ proxying for, minus the {@code sqlite3_} prefix, plus a {@code
+ Callback} suffix. e.g. {@code sqlite3_busy_handler()}'s callback is
+ named {@code BusyHandlerCallback}. Exceptions are made where that
+ would potentially be ambiguous, e.g. {@link ConfigSqllogCallback}
+ instead of {@code ConfigCallback} because the {@code
+ sqlite3_config()} interface may need to support more callback types
+ in the future.
+
+
2) They all have a {@code call()} method but its signature is
+ callback-specific.
+*/
+public interface CallbackProxy {}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/CollationCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CollationCallback.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/CollationCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CollationCallback.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,35 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import org.sqlite.jni.annotation.NotNull;
+
+/**
+ Callback for use with {@link CApi#sqlite3_create_collation}.
+
+ @see AbstractCollationCallback
+*/
+public interface CollationCallback
+ extends CallbackProxy, XDestroyCallback {
+ /**
+ Must compare the given byte arrays and return the result using
+ {@code memcmp()} semantics.
+ */
+ int call(@NotNull byte[] lhs, @NotNull byte[] rhs);
+
+ /**
+ Called by SQLite when the collation is destroyed. If a collation
+ requires custom cleanup, override this method.
+ */
+ void xDestroy();
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,28 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_collation_needed}.
+*/
+public interface CollationNeededCallback extends CallbackProxy {
+ /**
+ Has the same semantics as the C-level sqlite3_create_collation()
+ callback.
+
+
If it throws, the exception message is passed on to the db and
+ the exception is suppressed.
+ */
+ int call(sqlite3 db, int eTextRep, String collationName);
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,25 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_commit_hook}.
+*/
+public interface CommitHookCallback extends CallbackProxy {
+ /**
+ Works as documented for the C-level sqlite3_commit_hook()
+ callback. Must not throw.
+ */
+ int call();
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,25 @@
+/*
+** 2023-08-23
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A callback for use with sqlite3_config().
+*/
+public interface ConfigLogCallback {
+ /**
+ Must function as described for a C-level callback for
+ {@link CApi#sqlite3_config(ConfigLogCallback)}, with the slight signature change.
+ */
+ void call(int errCode, String msg);
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,25 @@
+/*
+** 2023-08-23
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A callback for use with sqlite3_config().
+*/
+public interface ConfigSqllogCallback {
+ /**
+ Must function as described for a C-level callback for
+ {@link CApi#sqlite3_config(ConfigSqllogCallback)}, with the slight signature change.
+ */
+ void call(sqlite3 db, String msg, int msgType );
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,46 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A helper for passing pointers between JNI C code and Java, in
+ particular for output pointers of high-level object types in the
+ sqlite3 C API, e.g. (sqlite3**) and (sqlite3_stmt**). This is
+ intended to be subclassed and the ContextType is intended to be the
+ class which is doing the subclassing. The intent of the ContextType
+ is strictly to provide some level of type safety by avoiding that
+ NativePointerHolder is not inadvertently passed to an incompatible
+ function signature.
+
+ These objects do not own the pointer they refer to. They are
+ intended simply to communicate that pointer between C and Java.
+*/
+public class NativePointerHolder {
+ //! Only set from JNI, where access permissions don't matter.
+ private volatile long nativePointer = 0;
+ /**
+ For use ONLY by package-level APIs which act as proxies for
+ close/finalize operations. Such ops must call this to zero out
+ the pointer so that this object is not carrying a stale
+ pointer. This function returns the prior value of the pointer and
+ sets it to 0.
+ */
+ final long clearNativePointer() {
+ final long rv = nativePointer;
+ nativePointer= 0;
+ return rv;
+ }
+
+ public final long getNativePointer(){ return nativePointer; }
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,231 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Helper classes for handling JNI output pointers.
+
+ We do not use a generic OutputPointer because working with those
+ from the native JNI code is unduly quirky due to a lack of
+ autoboxing at that level.
+
+ The usage is similar for all of thes types:
+
+
{@code
+ OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ assert( null==out.get() );
+ int rc = sqlite3_open(":memory:", out);
+ if( 0!=rc ) ... error;
+ assert( null!=out.get() );
+ sqlite3 db = out.take();
+ assert( null==out.get() );
+ }
+
+ With the minor exception that the primitive types permit direct
+ access to the object's value via the `value` property, whereas the
+ JNI-level opaque types do not permit client-level code to set that
+ property.
+
+
Warning: do not share instances of these classes across
+ threads. Doing so may lead to corrupting sqlite3-internal state.
+*/
+public final class OutputPointer {
+
+ /**
+ Output pointer for use with routines, such as sqlite3_open(),
+ which return a database handle via an output pointer. These
+ pointers can only be set by the JNI layer, not by client-level
+ code.
+ */
+ public static final class sqlite3 {
+ private org.sqlite.jni.capi.sqlite3 value;
+ /** Initializes with a null value. */
+ public sqlite3(){value = null;}
+ /** Sets the current value to null. */
+ public void clear(){value = null;}
+ /** Returns the current value. */
+ public final org.sqlite.jni.capi.sqlite3 get(){return value;}
+ /** Equivalent to calling get() then clear(). */
+ public final org.sqlite.jni.capi.sqlite3 take(){
+ final org.sqlite.jni.capi.sqlite3 v = value;
+ value = null;
+ return v;
+ }
+ }
+
+ /**
+ Output pointer for sqlite3_blob_open(). These
+ pointers can only be set by the JNI layer, not by client-level
+ code.
+ */
+ public static final class sqlite3_blob {
+ private org.sqlite.jni.capi.sqlite3_blob value;
+ /** Initializes with a null value. */
+ public sqlite3_blob(){value = null;}
+ /** Sets the current value to null. */
+ public void clear(){value = null;}
+ /** Returns the current value. */
+ public final org.sqlite.jni.capi.sqlite3_blob get(){return value;}
+ /** Equivalent to calling get() then clear(). */
+ public final org.sqlite.jni.capi.sqlite3_blob take(){
+ final org.sqlite.jni.capi.sqlite3_blob v = value;
+ value = null;
+ return v;
+ }
+ }
+
+ /**
+ Output pointer for use with routines, such as sqlite3_prepare(),
+ which return a statement handle via an output pointer. These
+ pointers can only be set by the JNI layer, not by client-level
+ code.
+ */
+ public static final class sqlite3_stmt {
+ private org.sqlite.jni.capi.sqlite3_stmt value;
+ /** Initializes with a null value. */
+ public sqlite3_stmt(){value = null;}
+ /** Sets the current value to null. */
+ public void clear(){value = null;}
+ /** Returns the current value. */
+ public final org.sqlite.jni.capi.sqlite3_stmt get(){return value;}
+ /** Equivalent to calling get() then clear(). */
+ public final org.sqlite.jni.capi.sqlite3_stmt take(){
+ final org.sqlite.jni.capi.sqlite3_stmt v = value;
+ value = null;
+ return v;
+ }
+ }
+
+ /**
+ Output pointer for use with routines, such as sqlite3_prepupdate_new(),
+ which return a sqlite3_value handle via an output pointer. These
+ pointers can only be set by the JNI layer, not by client-level
+ code.
+ */
+ public static final class sqlite3_value {
+ private org.sqlite.jni.capi.sqlite3_value value;
+ /** Initializes with a null value. */
+ public sqlite3_value(){value = null;}
+ /** Sets the current value to null. */
+ public void clear(){value = null;}
+ /** Returns the current value. */
+ public final org.sqlite.jni.capi.sqlite3_value get(){return value;}
+ /** Equivalent to calling get() then clear(). */
+ public final org.sqlite.jni.capi.sqlite3_value take(){
+ final org.sqlite.jni.capi.sqlite3_value v = value;
+ value = null;
+ return v;
+ }
+ }
+
+ /**
+ Output pointer for use with native routines which return booleans
+ via integer output pointers.
+ */
+ public static final class Bool {
+ /**
+ This is public for ease of use. Accessors are provided for
+ consistency with the higher-level types.
+ */
+ public boolean value;
+ /** Initializes with the value 0. */
+ public Bool(){this(false);}
+ /** Initializes with the value v. */
+ public Bool(boolean v){value = v;}
+ /** Returns the current value. */
+ public final boolean get(){return value;}
+ /** Sets the current value to v. */
+ public final void set(boolean v){value = v;}
+ }
+
+ /**
+ Output pointer for use with native routines which return integers via
+ output pointers.
+ */
+ public static final class Int32 {
+ /**
+ This is public for ease of use. Accessors are provided for
+ consistency with the higher-level types.
+ */
+ public int value;
+ /** Initializes with the value 0. */
+ public Int32(){this(0);}
+ /** Initializes with the value v. */
+ public Int32(int v){value = v;}
+ /** Returns the current value. */
+ public final int get(){return value;}
+ /** Sets the current value to v. */
+ public final void set(int v){value = v;}
+ }
+
+ /**
+ Output pointer for use with native routines which return 64-bit integers
+ via output pointers.
+ */
+ public static final class Int64 {
+ /**
+ This is public for ease of use. Accessors are provided for
+ consistency with the higher-level types.
+ */
+ public long value;
+ /** Initializes with the value 0. */
+ public Int64(){this(0);}
+ /** Initializes with the value v. */
+ public Int64(long v){value = v;}
+ /** Returns the current value. */
+ public final long get(){return value;}
+ /** Sets the current value. */
+ public final void set(long v){value = v;}
+ }
+
+ /**
+ Output pointer for use with native routines which return strings via
+ output pointers.
+ */
+ public static final class String {
+ /**
+ This is public for ease of use. Accessors are provided for
+ consistency with the higher-level types.
+ */
+ public java.lang.String value;
+ /** Initializes with a null value. */
+ public String(){this(null);}
+ /** Initializes with the value v. */
+ public String(java.lang.String v){value = v;}
+ /** Returns the current value. */
+ public final java.lang.String get(){return value;}
+ /** Sets the current value. */
+ public final void set(java.lang.String v){value = v;}
+ }
+
+ /**
+ Output pointer for use with native routines which return byte
+ arrays via output pointers.
+ */
+ public static final class ByteArray {
+ /**
+ This is public for ease of use. Accessors are provided for
+ consistency with the higher-level types.
+ */
+ public byte[] value;
+ /** Initializes with the value null. */
+ public ByteArray(){this(null);}
+ /** Initializes with the value v. */
+ public ByteArray(byte[] v){value = v;}
+ /** Returns the current value. */
+ public final byte[] get(){return value;}
+ /** Sets the current value. */
+ public final void set(byte[] v){value = v;}
+ }
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,78 @@
+/*
+** 2023-09-13
+**
+** 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 JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_prepare_multi}.
+*/
+public interface PrepareMultiCallback extends CallbackProxy {
+
+ /**
+ Gets passed a sqlite3_stmt which it may handle in arbitrary ways,
+ transfering ownership of it to this function.
+
+ 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.
+
+ See the {@link Finalize} class for a wrapper which finalizes the
+ statement after calling a proxy PrepareMultiCallback.
+ */
+ int call(sqlite3_stmt st);
+
+ /**
+ A PrepareMultiCallback impl which wraps a separate impl and finalizes
+ any sqlite3_stmt passed to its callback.
+ */
+ public static final class Finalize implements PrepareMultiCallback {
+ private PrepareMultiCallback p;
+ /**
+ p is the proxy to call() when this.call() is called.
+ */
+ public Finalize( PrepareMultiCallback p ){
+ this.p = p;
+ }
+ /**
+ Calls the call() method of the proxied callback and either returns its
+ result or propagates an exception. Either way, it passes its argument to
+ sqlite3_finalize() before returning.
+ */
+ @Override public int call(sqlite3_stmt st){
+ try {
+ return this.p.call(st);
+ }finally{
+ CApi.sqlite3_finalize(st);
+ }
+ }
+ }
+
+ /**
+ A PrepareMultiCallback impl which steps entirely through a result set,
+ ignoring all non-error results.
+ */
+ public static final class StepAll implements PrepareMultiCallback {
+ public StepAll(){}
+ /**
+ Calls sqlite3_step() on st until it returns something other than
+ SQLITE_ROW. If the final result is SQLITE_DONE then 0 is returned,
+ else the result of the final step is returned.
+ */
+ @Override public int call(sqlite3_stmt st){
+ int rc = CApi.SQLITE_DONE;
+ while( CApi.SQLITE_ROW == (rc = CApi.sqlite3_step(st)) ){}
+ return CApi.SQLITE_DONE==rc ? 0 : rc;
+ }
+ }
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,26 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_preupdate_hook}.
+*/
+public interface PreupdateHookCallback extends CallbackProxy {
+ /**
+ Must function as described for the C-level sqlite3_preupdate_hook()
+ callback.
+ */
+ void call(sqlite3 db, int op, String dbName, String dbTable,
+ long iKey1, long iKey2 );
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,27 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_progress_handler}.
+*/
+public interface ProgressHandlerCallback extends CallbackProxy {
+ /**
+ Works as documented for the C-level sqlite3_progress_handler() callback.
+
+
If it throws, the exception message is passed on to the db and
+ the exception is suppressed.
+ */
+ int call();
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/ResultCode.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ResultCode.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/ResultCode.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ResultCode.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,155 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ This enum contains all of the core and "extended" result codes used
+ by the sqlite3 library. It is provided not for use with the C-style
+ API (with which it won't work) but for higher-level code which may
+ find it useful to map SQLite result codes to human-readable names.
+*/
+public enum ResultCode {
+ SQLITE_OK(CApi.SQLITE_OK),
+ SQLITE_ERROR(CApi.SQLITE_ERROR),
+ SQLITE_INTERNAL(CApi.SQLITE_INTERNAL),
+ SQLITE_PERM(CApi.SQLITE_PERM),
+ SQLITE_ABORT(CApi.SQLITE_ABORT),
+ SQLITE_BUSY(CApi.SQLITE_BUSY),
+ SQLITE_LOCKED(CApi.SQLITE_LOCKED),
+ SQLITE_NOMEM(CApi.SQLITE_NOMEM),
+ SQLITE_READONLY(CApi.SQLITE_READONLY),
+ SQLITE_INTERRUPT(CApi.SQLITE_INTERRUPT),
+ SQLITE_IOERR(CApi.SQLITE_IOERR),
+ SQLITE_CORRUPT(CApi.SQLITE_CORRUPT),
+ SQLITE_NOTFOUND(CApi.SQLITE_NOTFOUND),
+ SQLITE_FULL(CApi.SQLITE_FULL),
+ SQLITE_CANTOPEN(CApi.SQLITE_CANTOPEN),
+ SQLITE_PROTOCOL(CApi.SQLITE_PROTOCOL),
+ SQLITE_EMPTY(CApi.SQLITE_EMPTY),
+ SQLITE_SCHEMA(CApi.SQLITE_SCHEMA),
+ SQLITE_TOOBIG(CApi.SQLITE_TOOBIG),
+ SQLITE_CONSTRAINT(CApi.SQLITE_CONSTRAINT),
+ SQLITE_MISMATCH(CApi.SQLITE_MISMATCH),
+ SQLITE_MISUSE(CApi.SQLITE_MISUSE),
+ SQLITE_NOLFS(CApi.SQLITE_NOLFS),
+ SQLITE_AUTH(CApi.SQLITE_AUTH),
+ SQLITE_FORMAT(CApi.SQLITE_FORMAT),
+ SQLITE_RANGE(CApi.SQLITE_RANGE),
+ SQLITE_NOTADB(CApi.SQLITE_NOTADB),
+ SQLITE_NOTICE(CApi.SQLITE_NOTICE),
+ SQLITE_WARNING(CApi.SQLITE_WARNING),
+ SQLITE_ROW(CApi.SQLITE_ROW),
+ SQLITE_DONE(CApi.SQLITE_DONE),
+ SQLITE_ERROR_MISSING_COLLSEQ(CApi.SQLITE_ERROR_MISSING_COLLSEQ),
+ SQLITE_ERROR_RETRY(CApi.SQLITE_ERROR_RETRY),
+ SQLITE_ERROR_SNAPSHOT(CApi.SQLITE_ERROR_SNAPSHOT),
+ SQLITE_IOERR_READ(CApi.SQLITE_IOERR_READ),
+ SQLITE_IOERR_SHORT_READ(CApi.SQLITE_IOERR_SHORT_READ),
+ SQLITE_IOERR_WRITE(CApi.SQLITE_IOERR_WRITE),
+ SQLITE_IOERR_FSYNC(CApi.SQLITE_IOERR_FSYNC),
+ SQLITE_IOERR_DIR_FSYNC(CApi.SQLITE_IOERR_DIR_FSYNC),
+ SQLITE_IOERR_TRUNCATE(CApi.SQLITE_IOERR_TRUNCATE),
+ SQLITE_IOERR_FSTAT(CApi.SQLITE_IOERR_FSTAT),
+ SQLITE_IOERR_UNLOCK(CApi.SQLITE_IOERR_UNLOCK),
+ SQLITE_IOERR_RDLOCK(CApi.SQLITE_IOERR_RDLOCK),
+ SQLITE_IOERR_DELETE(CApi.SQLITE_IOERR_DELETE),
+ SQLITE_IOERR_BLOCKED(CApi.SQLITE_IOERR_BLOCKED),
+ SQLITE_IOERR_NOMEM(CApi.SQLITE_IOERR_NOMEM),
+ SQLITE_IOERR_ACCESS(CApi.SQLITE_IOERR_ACCESS),
+ SQLITE_IOERR_CHECKRESERVEDLOCK(CApi.SQLITE_IOERR_CHECKRESERVEDLOCK),
+ SQLITE_IOERR_LOCK(CApi.SQLITE_IOERR_LOCK),
+ SQLITE_IOERR_CLOSE(CApi.SQLITE_IOERR_CLOSE),
+ SQLITE_IOERR_DIR_CLOSE(CApi.SQLITE_IOERR_DIR_CLOSE),
+ SQLITE_IOERR_SHMOPEN(CApi.SQLITE_IOERR_SHMOPEN),
+ SQLITE_IOERR_SHMSIZE(CApi.SQLITE_IOERR_SHMSIZE),
+ SQLITE_IOERR_SHMLOCK(CApi.SQLITE_IOERR_SHMLOCK),
+ SQLITE_IOERR_SHMMAP(CApi.SQLITE_IOERR_SHMMAP),
+ SQLITE_IOERR_SEEK(CApi.SQLITE_IOERR_SEEK),
+ SQLITE_IOERR_DELETE_NOENT(CApi.SQLITE_IOERR_DELETE_NOENT),
+ SQLITE_IOERR_MMAP(CApi.SQLITE_IOERR_MMAP),
+ SQLITE_IOERR_GETTEMPPATH(CApi.SQLITE_IOERR_GETTEMPPATH),
+ SQLITE_IOERR_CONVPATH(CApi.SQLITE_IOERR_CONVPATH),
+ SQLITE_IOERR_VNODE(CApi.SQLITE_IOERR_VNODE),
+ SQLITE_IOERR_AUTH(CApi.SQLITE_IOERR_AUTH),
+ SQLITE_IOERR_BEGIN_ATOMIC(CApi.SQLITE_IOERR_BEGIN_ATOMIC),
+ SQLITE_IOERR_COMMIT_ATOMIC(CApi.SQLITE_IOERR_COMMIT_ATOMIC),
+ SQLITE_IOERR_ROLLBACK_ATOMIC(CApi.SQLITE_IOERR_ROLLBACK_ATOMIC),
+ SQLITE_IOERR_DATA(CApi.SQLITE_IOERR_DATA),
+ SQLITE_IOERR_CORRUPTFS(CApi.SQLITE_IOERR_CORRUPTFS),
+ SQLITE_LOCKED_SHAREDCACHE(CApi.SQLITE_LOCKED_SHAREDCACHE),
+ SQLITE_LOCKED_VTAB(CApi.SQLITE_LOCKED_VTAB),
+ SQLITE_BUSY_RECOVERY(CApi.SQLITE_BUSY_RECOVERY),
+ SQLITE_BUSY_SNAPSHOT(CApi.SQLITE_BUSY_SNAPSHOT),
+ SQLITE_BUSY_TIMEOUT(CApi.SQLITE_BUSY_TIMEOUT),
+ SQLITE_CANTOPEN_NOTEMPDIR(CApi.SQLITE_CANTOPEN_NOTEMPDIR),
+ SQLITE_CANTOPEN_ISDIR(CApi.SQLITE_CANTOPEN_ISDIR),
+ SQLITE_CANTOPEN_FULLPATH(CApi.SQLITE_CANTOPEN_FULLPATH),
+ SQLITE_CANTOPEN_CONVPATH(CApi.SQLITE_CANTOPEN_CONVPATH),
+ SQLITE_CANTOPEN_SYMLINK(CApi.SQLITE_CANTOPEN_SYMLINK),
+ SQLITE_CORRUPT_VTAB(CApi.SQLITE_CORRUPT_VTAB),
+ SQLITE_CORRUPT_SEQUENCE(CApi.SQLITE_CORRUPT_SEQUENCE),
+ SQLITE_CORRUPT_INDEX(CApi.SQLITE_CORRUPT_INDEX),
+ SQLITE_READONLY_RECOVERY(CApi.SQLITE_READONLY_RECOVERY),
+ SQLITE_READONLY_CANTLOCK(CApi.SQLITE_READONLY_CANTLOCK),
+ SQLITE_READONLY_ROLLBACK(CApi.SQLITE_READONLY_ROLLBACK),
+ SQLITE_READONLY_DBMOVED(CApi.SQLITE_READONLY_DBMOVED),
+ SQLITE_READONLY_CANTINIT(CApi.SQLITE_READONLY_CANTINIT),
+ SQLITE_READONLY_DIRECTORY(CApi.SQLITE_READONLY_DIRECTORY),
+ SQLITE_ABORT_ROLLBACK(CApi.SQLITE_ABORT_ROLLBACK),
+ SQLITE_CONSTRAINT_CHECK(CApi.SQLITE_CONSTRAINT_CHECK),
+ SQLITE_CONSTRAINT_COMMITHOOK(CApi.SQLITE_CONSTRAINT_COMMITHOOK),
+ SQLITE_CONSTRAINT_FOREIGNKEY(CApi.SQLITE_CONSTRAINT_FOREIGNKEY),
+ SQLITE_CONSTRAINT_FUNCTION(CApi.SQLITE_CONSTRAINT_FUNCTION),
+ SQLITE_CONSTRAINT_NOTNULL(CApi.SQLITE_CONSTRAINT_NOTNULL),
+ SQLITE_CONSTRAINT_PRIMARYKEY(CApi.SQLITE_CONSTRAINT_PRIMARYKEY),
+ SQLITE_CONSTRAINT_TRIGGER(CApi.SQLITE_CONSTRAINT_TRIGGER),
+ SQLITE_CONSTRAINT_UNIQUE(CApi.SQLITE_CONSTRAINT_UNIQUE),
+ SQLITE_CONSTRAINT_VTAB(CApi.SQLITE_CONSTRAINT_VTAB),
+ SQLITE_CONSTRAINT_ROWID(CApi.SQLITE_CONSTRAINT_ROWID),
+ SQLITE_CONSTRAINT_PINNED(CApi.SQLITE_CONSTRAINT_PINNED),
+ SQLITE_CONSTRAINT_DATATYPE(CApi.SQLITE_CONSTRAINT_DATATYPE),
+ SQLITE_NOTICE_RECOVER_WAL(CApi.SQLITE_NOTICE_RECOVER_WAL),
+ SQLITE_NOTICE_RECOVER_ROLLBACK(CApi.SQLITE_NOTICE_RECOVER_ROLLBACK),
+ SQLITE_WARNING_AUTOINDEX(CApi.SQLITE_WARNING_AUTOINDEX),
+ SQLITE_AUTH_USER(CApi.SQLITE_AUTH_USER),
+ SQLITE_OK_LOAD_PERMANENTLY(CApi.SQLITE_OK_LOAD_PERMANENTLY);
+
+ public final int value;
+
+ ResultCode(int rc){
+ value = rc;
+ ResultCodeMap.set(rc, this);
+ }
+
+ /**
+ Returns the entry from this enum for the given result code, or
+ null if no match is found.
+ */
+ public static ResultCode getEntryForInt(int rc){
+ return ResultCodeMap.get(rc);
+ }
+
+ /**
+ Internal level of indirection required because we cannot initialize
+ static enum members in an enum before the enum constructor is
+ invoked.
+ */
+ private static final class ResultCodeMap {
+ private static final java.util.Map i2e
+ = new java.util.HashMap<>();
+ private static void set(int rc, ResultCode e){ i2e.put(rc, e); }
+ private static ResultCode get(int rc){ return i2e.get(rc); }
+ }
+
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,25 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_rollback_hook}.
+*/
+public interface RollbackHookCallback extends CallbackProxy {
+ /**
+ Works as documented for the C-level sqlite3_rollback_hook()
+ callback.
+ */
+ void call();
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,103 @@
+/*
+** 2023-07-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.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ SQLFunction is used in conjunction with the
+ sqlite3_create_function() JNI-bound API to give that native code
+ access to the callback functions needed in order to implement SQL
+ functions in Java.
+
+
+
+ This class is not used by itself, but is a marker base class. The
+ three UDF types are modelled by the inner classes Scalar,
+ Aggregate, and Window. Most simply, clients may subclass
+ those, or create anonymous classes from them, to implement
+ UDFs. Clients are free to create their own classes for use with
+ UDFs, so long as they conform to the public interfaces defined by
+ those three classes. The JNI layer only actively relies on the
+ SQLFunction base class and the method names and signatures used by
+ the UDF callback interfaces.
+*/
+public interface SQLFunction {
+
+ /**
+ PerContextState assists aggregate and window functions in
+ managing their accumulator state across calls to the UDF's
+ callbacks.
+
+ T must be of a type which can be legally stored as a value in
+ java.util.HashMap.
+
+ 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.
+
+
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.
+
+
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 {
+ private final java.util.Map> 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
+ which can be used to modify that state directly without
+ requiring that the client update the underlying map's entry.
+
+ The caller is obligated to eventually call
+ takeAggregateState() to clear the mapping.
+ */
+ public ValueHolder getAggregateState(sqlite3_context cx, T initialValue){
+ final Long key = cx.getAggregateContext(true);
+ ValueHolder 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 h = map.remove(cx.getAggregateContext(false));
+ return null==h ? null : h.value;
+ }
+ }
+
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/SQLTester.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/SQLTester.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/SQLTester.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/SQLTester.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,1433 @@
+/*
+** 2023-08-08
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the main application entry pointer for the
+** SQLTester framework.
+*/
+package org.sqlite.jni.capi;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.nio.charset.StandardCharsets;
+import java.util.regex.*;
+import static org.sqlite.jni.capi.CApi.*;
+
+/**
+ Modes for how to escape (or not) column values and names from
+ SQLTester.execSql() to the result buffer output.
+*/
+enum ResultBufferMode {
+ //! Do not append to result buffer
+ NONE,
+ //! Append output escaped.
+ ESCAPED,
+ //! Append output as-is
+ ASIS
+};
+
+/**
+ Modes to specify how to emit multi-row output from
+ SQLTester.execSql() to the result buffer.
+*/
+enum ResultRowMode {
+ //! Keep all result rows on one line, space-separated.
+ ONELINE,
+ //! Add a newline between each result row.
+ NEWLINE
+};
+
+/**
+ Base exception type for test-related failures.
+*/
+class SQLTesterException extends RuntimeException {
+ private boolean bFatal = false;
+
+ SQLTesterException(String msg){
+ super(msg);
+ }
+
+ protected SQLTesterException(String msg, boolean fatal){
+ super(msg);
+ bFatal = fatal;
+ }
+
+ /**
+ Indicates whether the framework should consider this exception
+ type as immediately fatal to the test run or not.
+ */
+ final boolean isFatal(){ return bFatal; }
+}
+
+class DbException extends SQLTesterException {
+ DbException(sqlite3 db, int rc, boolean closeDb){
+ super("DB error #"+rc+": "+sqlite3_errmsg(db),true);
+ if( closeDb ) sqlite3_close_v2(db);
+ }
+ DbException(sqlite3 db, int rc){
+ this(db, rc, false);
+ }
+}
+
+/**
+ Generic test-failed exception.
+ */
+class TestScriptFailed extends SQLTesterException {
+ public TestScriptFailed(TestScript ts, String msg){
+ super(ts.getOutputPrefix()+": "+msg, true);
+ }
+}
+
+/**
+ Thrown when an unknown test command is encountered in a script.
+*/
+class UnknownCommand extends SQLTesterException {
+ public UnknownCommand(TestScript ts, String cmd){
+ super(ts.getOutputPrefix()+": unknown command: "+cmd, false);
+ }
+}
+
+/**
+ Thrown when an "incompatible directive" is found in a script. This
+ can be the presence of a C-preprocessor construct, specific
+ metadata tags within a test script's header, or specific test
+ constructs which are incompatible with this particular
+ implementation.
+*/
+class IncompatibleDirective extends SQLTesterException {
+ public IncompatibleDirective(TestScript ts, String line){
+ super(ts.getOutputPrefix()+": incompatible directive: "+line, false);
+ }
+}
+
+/**
+ Console output utility class.
+*/
+class Outer {
+ private int verbosity = 0;
+
+ static void out(Object val){
+ System.out.print(val);
+ }
+
+ Outer out(Object... vals){
+ for(Object v : vals) out(v);
+ return this;
+ }
+
+ Outer outln(Object... vals){
+ out(vals).out("\n");
+ return this;
+ }
+
+ Outer verbose(Object... vals){
+ if(verbosity>0){
+ out("VERBOSE",(verbosity>1 ? "+: " : ": ")).outln(vals);
+ }
+ return this;
+ }
+
+ void setVerbosity(int level){
+ verbosity = level;
+ }
+
+ int getVerbosity(){
+ return verbosity;
+ }
+
+ public boolean isVerbose(){return verbosity > 0;}
+
+}
+
+/**
+ This class provides an application which aims to implement the
+ rudimentary SQL-driven test tool described in the accompanying
+ {@code test-script-interpreter.md}.
+
+
This class is an internal testing tool, not part of the public
+ interface but is (A) in the same package as the library because
+ access permissions require it to be so and (B) the JDK8 javadoc
+ offers no way to filter individual classes out of the doc
+ generation process (it can only exclude packages, but see (A)).
+
+
An instance of this application provides a core set of services
+ which TestScript instances use for processing testing logic.
+ TestScripts, in turn, delegate the concrete test work to Command
+ objects, which the TestScript parses on their behalf.
+*/
+public class SQLTester {
+ //! List of input script files.
+ private final java.util.List listInFiles = new ArrayList<>();
+ //! Console output utility.
+ private final Outer outer = new Outer();
+ //! Test input buffer.
+ private final StringBuilder inputBuffer = new StringBuilder();
+ //! Test result buffer.
+ private final StringBuilder resultBuffer = new StringBuilder();
+ //! Buffer for REQUIRED_PROPERTIES pragmas.
+ private final StringBuilder dbInitSql = new StringBuilder();
+ //! Output representation of SQL NULL.
+ private String nullView = "nil";
+ //! Total tests run.
+ private int nTotalTest = 0;
+ //! Total test script files run.
+ private int nTestFile = 0;
+ //! Number of scripts which were aborted.
+ private int nAbortedScript = 0;
+ //! Incremented by test case handlers
+ private int nTest = 0;
+ //! True to enable column name output from execSql()
+ private boolean emitColNames;
+ //! True to keep going regardless of how a test fails.
+ private boolean keepGoing = false;
+ //! The list of available db handles.
+ private final sqlite3[] aDb = new sqlite3[7];
+ //! Index into aDb of the current db.
+ private int iCurrentDb = 0;
+ //! Name of the default db, re-created for each script.
+ private final String initialDbName = "test.db";
+
+
+ public SQLTester(){
+ reset();
+ }
+
+ void setVerbosity(int level){
+ this.outer.setVerbosity( level );
+ }
+ int getVerbosity(){
+ return this.outer.getVerbosity();
+ }
+ boolean isVerbose(){
+ return this.outer.isVerbose();
+ }
+
+ void outputColumnNames(boolean b){ emitColNames = b; }
+
+ void verbose(Object... vals){
+ outer.verbose(vals);
+ }
+
+ void outln(Object... vals){
+ outer.outln(vals);
+ }
+
+ void out(Object... vals){
+ outer.out(vals);
+ }
+
+ //! Adds the given test script to the to-test list.
+ public void addTestScript(String filename){
+ listInFiles.add(filename);
+ //verbose("Added file ",filename);
+ }
+
+ private void setupInitialDb() throws DbException {
+ if( null==aDb[0] ){
+ Util.unlink(initialDbName);
+ openDb(0, initialDbName, true);
+ }else{
+ outln("WARNING: setupInitialDb() unexpectedly ",
+ "triggered while it is opened.");
+ }
+ }
+
+ static final String[] startEmoji = {
+ "🚴", "🏄", "🏇", "🤸", "⛹", "🏊", "⛷", "🧗", "🏋"
+ };
+ static final int nStartEmoji = startEmoji.length;
+ static int iStartEmoji = 0;
+
+ private static String nextStartEmoji(){
+ return startEmoji[iStartEmoji++ % nStartEmoji];
+ }
+
+ public void runTests() throws Exception {
+ final long tStart = System.currentTimeMillis();
+ for(String f : listInFiles){
+ reset();
+ ++nTestFile;
+ final TestScript ts = new TestScript(f);
+ outln(nextStartEmoji(), " starting [",f,"]");
+ boolean threw = false;
+ final long timeStart = System.currentTimeMillis();
+ try{
+ ts.run(this);
+ }catch(SQLTesterException e){
+ threw = true;
+ outln("🔥EXCEPTION: ",e.getClass().getSimpleName(),": ",e.getMessage());
+ ++nAbortedScript;
+ if( keepGoing ) outln("Continuing anyway becaure of the keep-going option.");
+ else if( e.isFatal() ) throw e;
+ }finally{
+ final long timeEnd = System.currentTimeMillis();
+ outln("🏁",(threw ? "❌" : "✅")," ",nTest," test(s) in ",
+ (timeEnd-timeStart),"ms.");
+ }
+ }
+ final long tEnd = System.currentTimeMillis();
+ outln("Total run-time: ",(tEnd-tStart),"ms");
+ Util.unlink(initialDbName);
+ }
+
+ private StringBuilder clearBuffer(StringBuilder b){
+ b.setLength(0);;
+ return b;
+ }
+
+ StringBuilder clearInputBuffer(){
+ return clearBuffer(inputBuffer);
+ }
+
+ StringBuilder clearResultBuffer(){
+ return clearBuffer(resultBuffer);
+ }
+
+ StringBuilder getInputBuffer(){ return inputBuffer; }
+
+ void appendInput(String n, boolean addNL){
+ inputBuffer.append(n);
+ if(addNL) inputBuffer.append('\n');
+ }
+
+ void appendResult(String n, boolean addNL){
+ resultBuffer.append(n);
+ if(addNL) resultBuffer.append('\n');
+ }
+
+ void appendDbInitSql(String n) throws DbException {
+ dbInitSql.append(n).append('\n');
+ if( null!=getCurrentDb() ){
+ //outln("RUNNING DB INIT CODE: ",n);
+ execSql(null, true, ResultBufferMode.NONE, null, n);
+ }
+ }
+ String getDbInitSql(){ return dbInitSql.toString(); }
+
+ String getInputText(){ return inputBuffer.toString(); }
+
+ String getResultText(){ return resultBuffer.toString(); }
+
+ private String takeBuffer(StringBuilder b){
+ final String rc = b.toString();
+ clearBuffer(b);
+ return rc;
+ }
+
+ String takeInputBuffer(){ return takeBuffer(inputBuffer); }
+
+ String takeResultBuffer(){ return takeBuffer(resultBuffer); }
+
+ int getCurrentDbId(){ return iCurrentDb; }
+
+ SQLTester affirmDbId(int n) throws IndexOutOfBoundsException {
+ if(n<0 || n>=aDb.length){
+ throw new IndexOutOfBoundsException("illegal db number: "+n);
+ }
+ return this;
+ }
+
+ sqlite3 setCurrentDb(int n) throws Exception{
+ affirmDbId(n);
+ iCurrentDb = n;
+ return this.aDb[n];
+ }
+
+ sqlite3 getCurrentDb(){ return aDb[iCurrentDb]; }
+
+ sqlite3 getDbById(int id) throws Exception{
+ return affirmDbId(id).aDb[id];
+ }
+
+ void closeDb(int id) {
+ final sqlite3 db = affirmDbId(id).aDb[id];
+ if( null != db ){
+ sqlite3_close_v2(db);
+ aDb[id] = null;
+ }
+ }
+
+ void closeDb() { closeDb(iCurrentDb); }
+
+ void closeAllDbs(){
+ for(int i = 0; i 0){
+ //outln("RUNNING DB INIT CODE: ",dbInitSql.toString());
+ rc = execSql(db, false, ResultBufferMode.NONE,
+ null, dbInitSql.toString());
+ }
+ if( 0!=rc ){
+ throw new DbException(db, rc, true);
+ }
+ return aDb[iCurrentDb] = db;
+ }
+
+ sqlite3 openDb(int slot, String name, boolean createIfNeeded) throws DbException {
+ affirmDbId(slot);
+ iCurrentDb = slot;
+ return openDb(name, createIfNeeded);
+ }
+
+ /**
+ Resets all tester context state except for that related to
+ tracking running totals.
+ */
+ void reset(){
+ clearInputBuffer();
+ clearResultBuffer();
+ clearBuffer(dbInitSql);
+ closeAllDbs();
+ nTest = 0;
+ nullView = "nil";
+ emitColNames = false;
+ iCurrentDb = 0;
+ //dbInitSql.append("SELECT 1;");
+ }
+
+ void setNullValue(String v){nullView = v;}
+
+ /**
+ If true, encountering an unknown command in a script causes the
+ remainder of the script to be skipped, rather than aborting the
+ whole script run.
+ */
+ boolean skipUnknownCommands(){
+ // Currently hard-coded. Potentially a flag someday.
+ return true;
+ }
+
+ void incrementTestCounter(){ ++nTest; ++nTotalTest; }
+
+ //! "Special" characters - we have to escape output if it contains any.
+ static final Pattern patternSpecial = Pattern.compile(
+ "[\\x00-\\x20\\x22\\x5c\\x7b\\x7d]"
+ );
+ //! Either of '{' or '}'.
+ static final Pattern patternSquiggly = Pattern.compile("[{}]");
+
+ /**
+ Returns v or some escaped form of v, as defined in the tester's
+ spec doc.
+ */
+ String escapeSqlValue(String v){
+ if( "".equals(v) ) return "{}";
+ Matcher m = patternSpecial.matcher(v);
+ if( !m.find() ){
+ return v /* no escaping needed */;
+ }
+ m = patternSquiggly.matcher(v);
+ if( !m.find() ){
+ return "{"+v+"}";
+ }
+ final StringBuilder sb = new StringBuilder("\"");
+ final int n = v.length();
+ for(int i = 0; i < n; ++i){
+ final char ch = v.charAt(i);
+ switch(ch){
+ case '\\': sb.append("\\\\"); break;
+ case '"': sb.append("\\\""); break;
+ default:
+ //verbose("CHAR ",(int)ch," ",ch," octal=",String.format("\\%03o", (int)ch));
+ if( (int)ch < 32 ) sb.append(String.format("\\%03o", (int)ch));
+ else sb.append(ch);
+ break;
+ }
+ }
+ sb.append("\"");
+ return sb.toString();
+ }
+
+ private void appendDbErr(sqlite3 db, StringBuilder sb, int rc){
+ sb.append(org.sqlite.jni.capi.ResultCode.getEntryForInt(rc)).append(' ');
+ final String msg = escapeSqlValue(sqlite3_errmsg(db));
+ if( '{' == msg.charAt(0) ){
+ sb.append(msg);
+ }else{
+ sb.append('{').append(msg).append('}');
+ }
+ }
+
+ /**
+ Runs SQL on behalf of test commands and outputs the results following
+ the very specific rules of the test framework.
+
+ If db is null, getCurrentDb() is assumed. If throwOnError is true then
+ any db-side error will result in an exception, else they result in
+ the db's result code.
+
+ appendMode specifies how/whether to append results to the result
+ buffer. rowMode specifies whether to output all results in a
+ single line or one line per row. If appendMode is
+ ResultBufferMode.NONE then rowMode is ignored and may be null.
+ */
+ public int execSql(sqlite3 db, boolean throwOnError,
+ ResultBufferMode appendMode, ResultRowMode rowMode,
+ String sql) throws SQLTesterException {
+ if( null==db && null==aDb[0] ){
+ // Delay opening of the initial db to enable tests to change its
+ // name and inject on-connect code via, e.g., the MEMDB
+ // directive. this setup as the potential to misinteract with
+ // auto-extension timing and must be done carefully.
+ setupInitialDb();
+ }
+ final OutputPointer.Int32 oTail = new OutputPointer.Int32();
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+ final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8);
+ if( null==db ) db = getCurrentDb();
+ int pos = 0, n = 1;
+ byte[] sqlChunk = sqlUtf8;
+ int rc = 0;
+ sqlite3_stmt stmt = null;
+ int spacing = 0 /* emit a space for --result if>0 */ ;
+ final StringBuilder sb = (ResultBufferMode.NONE==appendMode)
+ ? null : resultBuffer;
+ //outln("sqlChunk len= = ",sqlChunk.length);
+ try{
+ while(pos < sqlChunk.length){
+ if(pos > 0){
+ sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
+ sqlChunk.length);
+ }
+ if( 0==sqlChunk.length ) break;
+ rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail);
+ /*outln("PREPARE rc ",rc," oTail=",oTail.get(),": ",
+ new String(sqlChunk,StandardCharsets.UTF_8),"\n");*/
+ if( 0!=rc ){
+ if(throwOnError){
+ throw new DbException(db, rc);
+ }else if( null!=sb ){
+ appendDbErr(db, sb, rc);
+ }
+ break;
+ }
+ pos = oTail.value;
+ stmt = outStmt.take();
+ if( null == stmt ){
+ // empty statement was parsed.
+ continue;
+ }
+ if( null!=sb ){
+ // Add the output to the result buffer...
+ final int nCol = sqlite3_column_count(stmt);
+ String colName = null, val = null;
+ while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){
+ for(int i = 0; i < nCol; ++i){
+ if( spacing++ > 0 ) sb.append(' ');
+ if( emitColNames ){
+ colName = sqlite3_column_name(stmt, i);
+ switch(appendMode){
+ case ASIS:
+ sb.append( colName );
+ break;
+ case ESCAPED:
+ sb.append( escapeSqlValue(colName) );
+ break;
+ default:
+ throw new SQLTesterException("Unhandled ResultBufferMode: "+appendMode);
+ }
+ sb.append(' ');
+ }
+ val = sqlite3_column_text16(stmt, i);
+ if( null==val ){
+ sb.append( nullView );
+ continue;
+ }
+ switch(appendMode){
+ case ASIS:
+ sb.append( val );
+ break;
+ case ESCAPED:
+ sb.append( escapeSqlValue(val) );
+ break;
+ default:
+ throw new SQLTesterException("Unhandled ResultBufferMode: "+appendMode);
+ }
+ }
+ if( ResultRowMode.NEWLINE == rowMode ){
+ spacing = 0;
+ sb.append('\n');
+ }
+ }
+ }else{
+ while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){}
+ }
+ sqlite3_finalize(stmt);
+ stmt = null;
+ if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0;
+ else if( rc!=0 ){
+ if( null!=sb ){
+ appendDbErr(db, sb, rc);
+ }
+ break;
+ }
+ }
+ }finally{
+ sqlite3_reset(stmt
+ /* In order to trigger an exception in the
+ INSERT...RETURNING locking scenario:
+ https://sqlite.org/forum/forumpost/36f7a2e7494897df */);
+ sqlite3_finalize(stmt);
+ }
+ if( 0!=rc && throwOnError ){
+ throw new DbException(db, rc);
+ }
+ return rc;
+ }
+
+ public static void main(String[] argv) throws Exception{
+ installCustomExtensions();
+ boolean dumpInternals = false;
+ final SQLTester t = new SQLTester();
+ for(String a : argv){
+ if(a.startsWith("-")){
+ final String flag = a.replaceFirst("-+","");
+ if( flag.equals("verbose") ){
+ // Use --verbose up to 3 times
+ t.setVerbosity(t.getVerbosity() + 1);
+ }else if( flag.equals("keep-going") ){
+ t.keepGoing = true;
+ }else if( flag.equals("internals") ){
+ dumpInternals = true;
+ }else{
+ throw new IllegalArgumentException("Unhandled flag: "+flag);
+ }
+ continue;
+ }
+ t.addTestScript(a);
+ }
+ final AutoExtensionCallback ax = new AutoExtensionCallback() {
+ private final SQLTester tester = t;
+ @Override public int call(sqlite3 db){
+ final String init = tester.getDbInitSql();
+ if( !init.isEmpty() ){
+ tester.execSql(db, true, ResultBufferMode.NONE, null, init);
+ }
+ return 0;
+ }
+ };
+ sqlite3_auto_extension(ax);
+ try {
+ t.runTests();
+ }finally{
+ sqlite3_cancel_auto_extension(ax);
+ t.outln("Processed ",t.nTotalTest," test(s) in ",t.nTestFile," file(s).");
+ if( t.nAbortedScript > 0 ){
+ t.outln("Aborted ",t.nAbortedScript," script(s).");
+ }
+ if( dumpInternals ){
+ sqlite3_jni_internal_details();
+ }
+ }
+ }
+
+ /**
+ Internal impl of the public strglob() method. Neither argument
+ may be NULL and both _MUST_ be NUL-terminated.
+ */
+ private static native int strglob(byte[] glob, byte[] txt);
+
+ /**
+ Works essentially the same as sqlite3_strglob() except that the
+ glob character '#' matches a sequence of one or more digits. It
+ does not match when it appears at the start or middle of a series
+ of digits, e.g. "#23" or "1#3", but will match at the end,
+ e.g. "12#".
+ */
+ static int strglob(String glob, String txt){
+ return strglob(
+ (glob+"\0").getBytes(StandardCharsets.UTF_8),
+ (txt+"\0").getBytes(StandardCharsets.UTF_8)
+ );
+ }
+
+ /**
+ Sets up C-side components needed by the test framework. This must
+ not be called until main() is triggered so that it does not
+ interfere with library clients who don't use this class.
+ */
+ static native void installCustomExtensions();
+ static {
+ System.loadLibrary("sqlite3-jni")
+ /* Interestingly, when SQLTester is the main app, we have to
+ load that lib from here. The same load from CApi does
+ not happen early enough. Without this,
+ installCustomExtensions() is an unresolved symbol. */;
+ }
+
+}
+
+/**
+ General utilities for the SQLTester bits.
+*/
+final class Util {
+
+ //! Throws a new T, appending all msg args into a string for the message.
+ static void toss(Class extends Exception> errorType, Object... msg) throws Exception {
+ StringBuilder sb = new StringBuilder();
+ for(Object s : msg) sb.append(s);
+ final java.lang.reflect.Constructor extends Exception> ctor =
+ errorType.getConstructor(String.class);
+ throw ctor.newInstance(sb.toString());
+ }
+
+ static void toss(Object... msg) throws Exception{
+ toss(RuntimeException.class, msg);
+ }
+
+ //! Tries to delete the given file, silently ignoring failure.
+ static void unlink(String filename){
+ try{
+ final java.io.File f = new java.io.File(filename);
+ f.delete();
+ }catch(Exception e){
+ /* ignore */
+ }
+ }
+
+ /**
+ Appends all entries in argv[1..end] into a space-separated
+ string, argv[0] is not included because it's expected to be a
+ command name.
+ */
+ static String argvToString(String[] argv){
+ StringBuilder sb = new StringBuilder();
+ for(int i = 1; i < argv.length; ++i ){
+ if( i>1 ) sb.append(" ");
+ sb.append( argv[i] );
+ }
+ return sb.toString();
+ }
+
+}
+
+/**
+ Base class for test script commands. It provides a set of utility
+ APIs for concrete command implementations.
+
+ Each subclass must have a public no-arg ctor and must implement
+ the process() method which is abstract in this class.
+
+ Commands are intended to be stateless, except perhaps for counters
+ and similar internals. Specifically, no state which changes the
+ behavior between any two invocations of process() should be
+ retained.
+*/
+abstract class Command {
+ protected Command(){}
+
+ /**
+ Must process one command-unit of work and either return
+ (on success) or throw (on error).
+
+ The first two arguments specify the context of the test. The TestScript
+ provides the content of the test and the SQLTester providers the sandbox
+ in which that script is being evaluated.
+
+ argv is a list with the command name followed by any arguments to
+ that command. The argcCheck() method from this class provides
+ very basic argc validation.
+ */
+ public abstract void process(
+ SQLTester st, TestScript ts, String[] argv
+ ) throws Exception;
+
+ /**
+ If argv.length-1 (-1 because the command's name is in argv[0]) does not
+ fall in the inclusive range (min,max) then this function throws. Use
+ a max value of -1 to mean unlimited.
+ */
+ protected final void argcCheck(TestScript ts, String[] argv, int min, int max) throws Exception{
+ int argc = argv.length-1;
+ if(argc=0 && argc>max)){
+ if( min==max ){
+ ts.toss(argv[0]," requires exactly ",min," argument(s)");
+ }else if(max>0){
+ ts.toss(argv[0]," requires ",min,"-",max," arguments.");
+ }else{
+ ts.toss(argv[0]," requires at least ",min," arguments.");
+ }
+ }
+ }
+
+ /**
+ Equivalent to argcCheck(argv,argc,argc).
+ */
+ protected final void argcCheck(TestScript ts, String[] argv, int argc) throws Exception{
+ argcCheck(ts, argv, argc, argc);
+ }
+}
+
+//! --close command
+class CloseDbCommand extends Command {
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,0,1);
+ Integer id;
+ if(argv.length>1){
+ String arg = argv[1];
+ if("all".equals(arg)){
+ t.closeAllDbs();
+ return;
+ }
+ else{
+ id = Integer.parseInt(arg);
+ }
+ }else{
+ id = t.getCurrentDbId();
+ }
+ t.closeDb(id);
+ }
+}
+
+//! --column-names command
+class ColumnNamesCommand extends Command {
+ public void process(
+ SQLTester st, TestScript ts, String[] argv
+ ) throws Exception{
+ argcCheck(ts,argv,1);
+ st.outputColumnNames( Integer.parseInt(argv[1])!=0 );
+ }
+}
+
+//! --db command
+class DbCommand extends Command {
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,1);
+ t.setCurrentDb( Integer.parseInt(argv[1]) );
+ }
+}
+
+//! --glob command
+class GlobCommand extends Command {
+ private boolean negate = false;
+ public GlobCommand(){}
+ protected GlobCommand(boolean negate){ this.negate = negate; }
+
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,1,-1);
+ t.incrementTestCounter();
+ final String sql = t.takeInputBuffer();
+ int rc = t.execSql(null, true, ResultBufferMode.ESCAPED,
+ ResultRowMode.ONELINE, sql);
+ final String result = t.getResultText();
+ final String sArgs = Util.argvToString(argv);
+ //t2.verbose2(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs);
+ final String glob = Util.argvToString(argv);
+ rc = SQLTester.strglob(glob, result);
+ if( (negate && 0==rc) || (!negate && 0!=rc) ){
+ ts.toss(argv[0], " mismatch: ", glob," vs input: ",result);
+ }
+ }
+}
+
+//! --json command
+class JsonCommand extends ResultCommand {
+ public JsonCommand(){ super(ResultBufferMode.ASIS); }
+}
+
+//! --json-block command
+class JsonBlockCommand extends TableResultCommand {
+ public JsonBlockCommand(){ super(true); }
+}
+
+//! --new command
+class NewDbCommand extends OpenDbCommand {
+ public NewDbCommand(){ super(true); }
+}
+
+//! Placeholder dummy/no-op commands
+class NoopCommand extends Command {
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ }
+}
+
+//! --notglob command
+class NotGlobCommand extends GlobCommand {
+ public NotGlobCommand(){
+ super(true);
+ }
+}
+
+//! --null command
+class NullCommand extends Command {
+ public void process(
+ SQLTester st, TestScript ts, String[] argv
+ ) throws Exception{
+ argcCheck(ts,argv,1);
+ st.setNullValue( argv[1] );
+ }
+}
+
+//! --open command
+class OpenDbCommand extends Command {
+ private boolean createIfNeeded = false;
+ public OpenDbCommand(){}
+ protected OpenDbCommand(boolean c){createIfNeeded = c;}
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,1);
+ t.openDb(argv[1], createIfNeeded);
+ }
+}
+
+//! --print command
+class PrintCommand extends Command {
+ public void process(
+ SQLTester st, TestScript ts, String[] argv
+ ) throws Exception{
+ st.out(ts.getOutputPrefix(),": ");
+ if( 1==argv.length ){
+ st.out( st.getInputText() );
+ }else{
+ st.outln( Util.argvToString(argv) );
+ }
+ }
+}
+
+//! --result command
+class ResultCommand extends Command {
+ private final ResultBufferMode bufferMode;
+ protected ResultCommand(ResultBufferMode bm){ bufferMode = bm; }
+ public ResultCommand(){ this(ResultBufferMode.ESCAPED); }
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,0,-1);
+ t.incrementTestCounter();
+ final String sql = t.takeInputBuffer();
+ //ts.verbose2(argv[0]," SQL =\n",sql);
+ int rc = t.execSql(null, false, bufferMode, ResultRowMode.ONELINE, sql);
+ final String result = t.getResultText().trim();
+ final String sArgs = argv.length>1 ? Util.argvToString(argv) : "";
+ if( !result.equals(sArgs) ){
+ t.outln(argv[0]," FAILED comparison. Result buffer:\n",
+ result,"\nExpected result:\n",sArgs);
+ ts.toss(argv[0]+" comparison failed.");
+ }
+ }
+}
+
+//! --run command
+class RunCommand extends Command {
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,0,1);
+ final sqlite3 db = (1==argv.length)
+ ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) );
+ final String sql = t.takeInputBuffer();
+ final int rc = t.execSql(db, false, ResultBufferMode.NONE,
+ ResultRowMode.ONELINE, sql);
+ if( 0!=rc && t.isVerbose() ){
+ String msg = sqlite3_errmsg(db);
+ ts.verbose1(argv[0]," non-fatal command error #",rc,": ",
+ msg,"\nfor SQL:\n",sql);
+ }
+ }
+}
+
+//! --tableresult command
+class TableResultCommand extends Command {
+ private final boolean jsonMode;
+ protected TableResultCommand(boolean jsonMode){ this.jsonMode = jsonMode; }
+ public TableResultCommand(){ this(false); }
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,0);
+ t.incrementTestCounter();
+ String body = ts.fetchCommandBody(t);
+ if( null==body ) ts.toss("Missing ",argv[0]," body.");
+ body = body.trim();
+ if( !body.endsWith("\n--end") ){
+ ts.toss(argv[0], " must be terminated with --end.");
+ }else{
+ body = body.substring(0, body.length()-6);
+ }
+ final String[] globs = body.split("\\s*\\n\\s*");
+ if( globs.length < 1 ){
+ ts.toss(argv[0], " requires 1 or more ",
+ (jsonMode ? "json snippets" : "globs"),".");
+ }
+ final String sql = t.takeInputBuffer();
+ t.execSql(null, true,
+ jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED,
+ ResultRowMode.NEWLINE, sql);
+ final String rbuf = t.getResultText();
+ final String[] res = rbuf.split("\n");
+ if( res.length != globs.length ){
+ ts.toss(argv[0], " failure: input has ", res.length,
+ " row(s) but expecting ",globs.length);
+ }
+ for(int i = 0; i < res.length; ++i){
+ final String glob = globs[i].replaceAll("\\s+"," ").trim();
+ //ts.verbose2(argv[0]," <<",glob,">> vs <<",res[i],">>");
+ if( jsonMode ){
+ if( !glob.equals(res[i]) ){
+ ts.toss(argv[0], " json <<",glob, ">> does not match: <<",
+ res[i],">>");
+ }
+ }else if( 0 != SQLTester.strglob(glob, res[i]) ){
+ ts.toss(argv[0], " glob <<",glob,">> does not match: <<",res[i],">>");
+ }
+ }
+ }
+}
+
+//! --testcase command
+class TestCaseCommand extends Command {
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,1);
+ ts.setTestCaseName(argv[1]);
+ t.clearResultBuffer();
+ t.clearInputBuffer();
+ }
+}
+
+//! --verbosity command
+class VerbosityCommand extends Command {
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,1);
+ ts.setVerbosity( Integer.parseInt(argv[1]) );
+ }
+}
+
+class CommandDispatcher {
+
+ private static java.util.Map commandMap =
+ new java.util.HashMap<>();
+
+ /**
+ Returns a (cached) instance mapped to name, or null if no match
+ is found.
+ */
+ static Command getCommandByName(String name){
+ Command rv = commandMap.get(name);
+ if( null!=rv ) return rv;
+ switch(name){
+ case "close": rv = new CloseDbCommand(); break;
+ case "column-names": rv = new ColumnNamesCommand(); break;
+ case "db": rv = new DbCommand(); break;
+ case "glob": rv = new GlobCommand(); break;
+ case "json": rv = new JsonCommand(); break;
+ case "json-block": rv = new JsonBlockCommand(); break;
+ case "new": rv = new NewDbCommand(); break;
+ case "notglob": rv = new NotGlobCommand(); break;
+ case "null": rv = new NullCommand(); break;
+ case "oom": rv = new NoopCommand(); break;
+ case "open": rv = new OpenDbCommand(); break;
+ case "print": rv = new PrintCommand(); break;
+ case "result": rv = new ResultCommand(); break;
+ case "run": rv = new RunCommand(); break;
+ case "tableresult": rv = new TableResultCommand(); break;
+ case "testcase": rv = new TestCaseCommand(); break;
+ case "verbosity": rv = new VerbosityCommand(); break;
+ default: rv = null; break;
+ }
+ if( null!=rv ) commandMap.put(name, rv);
+ return rv;
+ }
+
+ /**
+ Treats argv[0] as a command name, looks it up with
+ getCommandByName(), and calls process() on that instance, passing
+ it arguments given to this function.
+ */
+ static void dispatch(SQLTester tester, TestScript ts, String[] argv) throws Exception{
+ final Command cmd = getCommandByName(argv[0]);
+ if(null == cmd){
+ throw new UnknownCommand(ts, argv[0]);
+ }
+ cmd.process(tester, ts, argv);
+ }
+}
+
+
+/**
+ This class represents a single test script. It handles (or
+ delegates) its the reading-in and parsing, but the details of
+ evaluation are delegated elsewhere.
+*/
+class TestScript {
+ //! input file
+ private String filename = null;
+ //! Name pulled from the SCRIPT_MODULE_NAME directive of the file
+ private String moduleName = null;
+ //! Current test case name.
+ private String testCaseName = null;
+ //! Content buffer state.
+ private final Cursor cur = new Cursor();
+ //! Utility for console output.
+ private final Outer outer = new Outer();
+
+ //! File content and parse state.
+ private static final class Cursor {
+ private final StringBuilder sb = new StringBuilder();
+ byte[] src = null;
+ //! Current position in this.src.
+ int pos = 0;
+ //! Current line number. Starts at 0 for internal reasons and will
+ // line up with 1-based reality once parsing starts.
+ int lineNo = 0 /* yes, zero */;
+ //! Putback value for this.pos.
+ int putbackPos = 0;
+ //! Putback line number
+ int putbackLineNo = 0;
+ //! Peeked-to pos, used by peekLine() and consumePeeked().
+ int peekedPos = 0;
+ //! Peeked-to line number.
+ int peekedLineNo = 0;
+
+ //! Restore parsing state to the start of the stream.
+ void rewind(){
+ sb.setLength(0);
+ pos = lineNo = putbackPos = putbackLineNo = peekedPos = peekedLineNo = 0
+ /* kinda missing memset() about now. */;
+ }
+ }
+
+ private byte[] readFile(String filename) throws Exception {
+ return java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filename));
+ }
+
+ /**
+ Initializes the script with the content of the given file.
+ Throws if it cannot read the file.
+ */
+ public TestScript(String filename) throws Exception{
+ this.filename = filename;
+ setVerbosity(2);
+ cur.src = readFile(filename);
+ }
+
+ public String getFilename(){
+ return filename;
+ }
+
+ public String getModuleName(){
+ return moduleName;
+ }
+
+ /**
+ Verbosity level 0 produces no debug/verbose output. Level 1 produces
+ some and level 2 produces more.
+ */
+ public void setVerbosity(int level){
+ outer.setVerbosity(level);
+ }
+
+ public String getOutputPrefix(){
+ String rc = "["+(moduleName==null ? "" : moduleName)+"]";
+ if( null!=testCaseName ) rc += "["+testCaseName+"]";
+ if( null!=filename ) rc += "["+filename+"]";
+ return rc + " line "+ cur.lineNo;
+ }
+
+ static final String[] verboseLabel = {"🔈",/*"🔉",*/"🔊","📢"};
+ //! Output vals only if level<=current verbosity level.
+ private TestScript verboseN(int level, Object... vals){
+ final int verbosity = outer.getVerbosity();
+ if(verbosity>=level){
+ outer.out( verboseLabel[level-1], getOutputPrefix(), " ",level,": "
+ ).outln(vals);
+ }
+ return this;
+ }
+
+ TestScript verbose1(Object... vals){return verboseN(1,vals);}
+ TestScript verbose2(Object... vals){return verboseN(2,vals);}
+ TestScript verbose3(Object... vals){return verboseN(3,vals);}
+
+ private void reset(){
+ testCaseName = null;
+ cur.rewind();
+ }
+
+ void setTestCaseName(String n){ testCaseName = n; }
+
+ /**
+ Returns the next line from the buffer, minus the trailing EOL.
+
+ Returns null when all input is consumed. Throws if it reads
+ illegally-encoded input, e.g. (non-)characters in the range
+ 128-256.
+ */
+ String getLine(){
+ if( cur.pos==cur.src.length ){
+ return null /* EOF */;
+ }
+ cur.putbackPos = cur.pos;
+ cur.putbackLineNo = cur.lineNo;
+ cur.sb.setLength(0);
+ final boolean skipLeadingWs = false;
+ byte b = 0, prevB = 0;
+ int i = cur.pos;
+ if(skipLeadingWs) {
+ /* Skip any leading spaces, including newlines. This will eliminate
+ blank lines. */
+ for(; i < cur.src.length; ++i, prevB=b){
+ b = cur.src[i];
+ switch((int)b){
+ case 32/*space*/: case 9/*tab*/: case 13/*CR*/: continue;
+ case 10/*NL*/: ++cur.lineNo; continue;
+ default: break;
+ }
+ break;
+ }
+ if( i==cur.src.length ){
+ return null /* EOF */;
+ }
+ }
+ boolean doBreak = false;
+ final byte[] aChar = {0,0,0,0} /* multi-byte char buffer */;
+ int nChar = 0 /* number of bytes in the char */;
+ for(; i < cur.src.length && !doBreak; ++i){
+ b = cur.src[i];
+ switch( (int)b ){
+ case 13/*CR*/: continue;
+ case 10/*NL*/:
+ ++cur.lineNo;
+ if(cur.sb.length()>0) doBreak = true;
+ // Else it's an empty string
+ break;
+ default:
+ /* Multi-byte chars need to be gathered up and appended at
+ one time. Appending individual bytes to the StringBuffer
+ appends their integer value. */
+ nChar = 1;
+ switch( b & 0xF0 ){
+ case 0xC0: nChar = 2; break;
+ case 0xE0: nChar = 3; break;
+ case 0xF0: nChar = 4; break;
+ default:
+ if( b > 127 ) this.toss("Invalid character (#"+(int)b+").");
+ break;
+ }
+ if( 1==nChar ){
+ cur.sb.append((char)b);
+ }else{
+ for(int x = 0; x < nChar; ++x) aChar[x] = cur.src[i+x];
+ cur.sb.append(new String(Arrays.copyOf(aChar, nChar),
+ StandardCharsets.UTF_8));
+ i += nChar-1;
+ }
+ break;
+ }
+ }
+ cur.pos = i;
+ final String rv = cur.sb.toString();
+ if( i==cur.src.length && 0==rv.length() ){
+ return null /* EOF */;
+ }
+ return rv;
+ }/*getLine()*/
+
+ /**
+ Fetches the next line then resets the cursor to its pre-call
+ state. consumePeeked() can be used to consume this peeked line
+ without having to re-parse it.
+ */
+ String peekLine(){
+ final int oldPos = cur.pos;
+ final int oldPB = cur.putbackPos;
+ final int oldPBL = cur.putbackLineNo;
+ final int oldLine = cur.lineNo;
+ try{ return getLine(); }
+ finally{
+ cur.peekedPos = cur.pos;
+ cur.peekedLineNo = cur.lineNo;
+ cur.pos = oldPos;
+ cur.lineNo = oldLine;
+ cur.putbackPos = oldPB;
+ cur.putbackLineNo = oldPBL;
+ }
+ }
+
+ /**
+ Only valid after calling peekLine() and before calling getLine().
+ This places the cursor to the position it would have been at had
+ the peekLine() had been fetched with getLine().
+ */
+ void consumePeeked(){
+ cur.pos = cur.peekedPos;
+ cur.lineNo = cur.peekedLineNo;
+ }
+
+ /**
+ Restores the cursor to the position it had before the previous
+ call to getLine().
+ */
+ void putbackLine(){
+ cur.pos = cur.putbackPos;
+ cur.lineNo = cur.putbackLineNo;
+ }
+
+ private boolean checkRequiredProperties(SQLTester t, String[] props) throws SQLTesterException{
+ if( true ) return false;
+ int nOk = 0;
+ for(String rp : props){
+ verbose1("REQUIRED_PROPERTIES: ",rp);
+ switch(rp){
+ case "RECURSIVE_TRIGGERS":
+ t.appendDbInitSql("pragma recursive_triggers=on;");
+ ++nOk;
+ break;
+ case "TEMPSTORE_FILE":
+ /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2,
+ which we just happen to know is the case */
+ t.appendDbInitSql("pragma temp_store=1;");
+ ++nOk;
+ break;
+ case "TEMPSTORE_MEM":
+ /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2,
+ which we just happen to know is the case */
+ t.appendDbInitSql("pragma temp_store=0;");
+ ++nOk;
+ break;
+ case "AUTOVACUUM":
+ t.appendDbInitSql("pragma auto_vacuum=full;");
+ ++nOk;
+ case "INCRVACUUM":
+ t.appendDbInitSql("pragma auto_vacuum=incremental;");
+ ++nOk;
+ default:
+ break;
+ }
+ }
+ return props.length == nOk;
+ }
+
+ private static final Pattern patternRequiredProperties =
+ Pattern.compile(" REQUIRED_PROPERTIES:[ \\t]*(\\S.*)\\s*$");
+ private static final Pattern patternScriptModuleName =
+ Pattern.compile(" SCRIPT_MODULE_NAME:[ \\t]*(\\S+)\\s*$");
+ private static final Pattern patternMixedModuleName =
+ Pattern.compile(" ((MIXED_)?MODULE_NAME):[ \\t]*(\\S+)\\s*$");
+ private static final Pattern patternCommand =
+ Pattern.compile("^--(([a-z-]+)( .*)?)$");
+
+ /**
+ Looks for "directives." If a compatible one is found, it is
+ processed and this function returns. If an incompatible one is found,
+ a description of it is returned and processing of the test must
+ end immediately.
+ */
+ private void checkForDirective(
+ SQLTester tester, String line
+ ) throws IncompatibleDirective {
+ if(line.startsWith("#")){
+ throw new IncompatibleDirective(this, "C-preprocessor input: "+line);
+ }else if(line.startsWith("---")){
+ new IncompatibleDirective(this, "triple-dash: "+line);
+ }
+ Matcher m = patternScriptModuleName.matcher(line);
+ if( m.find() ){
+ moduleName = m.group(1);
+ return;
+ }
+ m = patternRequiredProperties.matcher(line);
+ if( m.find() ){
+ final String rp = m.group(1);
+ if( ! checkRequiredProperties( tester, rp.split("\\s+") ) ){
+ throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp);
+ }
+ }
+ m = patternMixedModuleName.matcher(line);
+ if( m.find() ){
+ throw new IncompatibleDirective(this, m.group(1)+": "+m.group(3));
+ }
+ if( line.indexOf("\n|")>=0 ){
+ throw new IncompatibleDirective(this, "newline-pipe combination.");
+ }
+ return;
+ }
+
+ boolean isCommandLine(String line, boolean checkForImpl){
+ final Matcher m = patternCommand.matcher(line);
+ boolean rc = m.find();
+ if( rc && checkForImpl ){
+ rc = null!=CommandDispatcher.getCommandByName(m.group(2));
+ }
+ return rc;
+ }
+
+ /**
+ If line looks like a command, returns an argv for that command
+ invocation, else returns null.
+ */
+ String[] getCommandArgv(String line){
+ final Matcher m = patternCommand.matcher(line);
+ return m.find() ? m.group(1).trim().split("\\s+") : null;
+ }
+
+ /**
+ Fetches lines until the next recognized command. Throws if
+ checkForDirective() does. Returns null if there is no input or
+ it's only whitespace. The returned string retains all whitespace.
+
+ Note that "subcommands", --command-like constructs in the body
+ which do not match a known command name are considered to be
+ content, not commands.
+ */
+ String fetchCommandBody(SQLTester tester){
+ final StringBuilder sb = new StringBuilder();
+ String line;
+ while( (null != (line = peekLine())) ){
+ checkForDirective(tester, line);
+ if( isCommandLine(line, true) ) break;
+ else {
+ sb.append(line).append("\n");
+ consumePeeked();
+ }
+ }
+ line = sb.toString();
+ return line.trim().isEmpty() ? null : line;
+ }
+
+ private void processCommand(SQLTester t, String[] argv) throws Exception{
+ verbose1("running command: ",argv[0], " ", Util.argvToString(argv));
+ if(outer.getVerbosity()>1){
+ final String input = t.getInputText();
+ if( !input.isEmpty() ) verbose3("Input buffer = ",input);
+ }
+ CommandDispatcher.dispatch(t, this, argv);
+ }
+
+ void toss(Object... msg) throws TestScriptFailed {
+ StringBuilder sb = new StringBuilder();
+ for(Object s : msg) sb.append(s);
+ throw new TestScriptFailed(this, sb.toString());
+ }
+
+ /**
+ Runs this test script in the context of the given tester object.
+ */
+ public boolean run(SQLTester tester) throws Exception {
+ reset();
+ setVerbosity(tester.getVerbosity());
+ String line, directive;
+ String[] argv;
+ while( null != (line = getLine()) ){
+ verbose3("input line: ",line);
+ checkForDirective(tester, line);
+ argv = getCommandArgv(line);
+ if( null!=argv ){
+ processCommand(tester, argv);
+ continue;
+ }
+ tester.appendInput(line,true);
+ }
+ return true;
+ }
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,33 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+
+/**
+ A SQLFunction implementation for scalar functions.
+*/
+public abstract class ScalarFunction implements SQLFunction {
+ /**
+ As for the xFunc() argument of the C API's
+ sqlite3_create_function(). If this function throws, it is
+ translated into an sqlite3_result_error().
+ */
+ public abstract void xFunc(sqlite3_context cx, sqlite3_value[] args);
+
+ /**
+ Optionally override to be notified when the UDF is finalized by
+ SQLite. This default implementation does nothing.
+ */
+ public void xDestroy() {}
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,35 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A wrapper object for use with sqlite3_table_column_metadata().
+ They are populated only via that interface.
+*/
+public final class TableColumnMetadata {
+ OutputPointer.Bool pNotNull = new OutputPointer.Bool();
+ OutputPointer.Bool pPrimaryKey = new OutputPointer.Bool();
+ OutputPointer.Bool pAutoinc = new OutputPointer.Bool();
+ OutputPointer.String pzCollSeq = new OutputPointer.String();
+ OutputPointer.String pzDataType = new OutputPointer.String();
+
+ public TableColumnMetadata(){
+ }
+
+ public String getDataType(){ return pzDataType.value; }
+ public String getCollation(){ return pzCollSeq.value; }
+ public boolean isNotNull(){ return pNotNull.value; }
+ public boolean isPrimaryKey(){ return pPrimaryKey.value; }
+ public boolean isAutoincrement(){ return pAutoinc.value; }
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/Tester1.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/Tester1.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/Tester1.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/Tester1.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,1976 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains a set of tests for the sqlite3 JNI bindings.
+*/
+package org.sqlite.jni.capi;
+import static org.sqlite.jni.capi.CApi.*;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ An annotation for Tester1 tests which we do not want to run in
+ reflection-driven test mode because either they are not suitable
+ for multi-threaded threaded mode or we have to control their execution
+ order.
+*/
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+@interface ManualTest{}
+/**
+ Annotation for Tester1 tests which mark those which must be skipped
+ in multi-threaded mode.
+*/
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+@interface SingleThreadOnly{}
+
+public class Tester1 implements Runnable {
+ //! True when running in multi-threaded mode.
+ private static boolean mtMode = false;
+ //! True to sleep briefly between tests.
+ private static boolean takeNaps = false;
+ //! 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 boolean listRunTests = false;
+ //! True to squelch all out() and outln() output.
+ private static boolean quietMode = false;
+ //! Total number of runTests() calls.
+ private static int nTestRuns = 0;
+ //! List of test*() methods to run.
+ private static List testMethods = null;
+ //! List of exceptions collected by run()
+ private static List listErrors = new ArrayList<>();
+ private static final class Metrics {
+ //! Number of times createNewDb() (or equivalent) is invoked.
+ volatile int dbOpen = 0;
+ }
+
+ private Integer tId;
+
+ Tester1(Integer id){
+ tId = id;
+ }
+
+ static final Metrics metrics = new Metrics();
+
+ public static synchronized void outln(){
+ if( !quietMode ){
+ System.out.println("");
+ }
+ }
+
+ public static synchronized void outPrefix(){
+ if( !quietMode ){
+ System.out.print(Thread.currentThread().getName()+": ");
+ }
+ }
+
+ public static synchronized void outln(Object val){
+ if( !quietMode ){
+ outPrefix();
+ System.out.println(val);
+ }
+ }
+
+ public static synchronized void out(Object val){
+ if( !quietMode ){
+ System.out.print(val);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static synchronized void out(Object... vals){
+ if( !quietMode ){
+ outPrefix();
+ for(Object v : vals) out(v);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static synchronized void outln(Object... vals){
+ if( !quietMode ){
+ out(vals); out("\n");
+ }
+ }
+
+ static volatile int affirmCount = 0;
+ public static synchronized int affirm(Boolean v, String comment){
+ ++affirmCount;
+ if( false ) assert( v /* prefer assert over exception if it's enabled because
+ the JNI layer sometimes has to suppress exceptions,
+ so they might be squelched on their way back to the
+ top. */);
+ if( !v ) throw new RuntimeException(comment);
+ return affirmCount;
+ }
+
+ public static void affirm(Boolean v){
+ affirm(v, "Affirmation failed.");
+ }
+
+ @SingleThreadOnly /* because it's thread-agnostic */
+ private void test1(){
+ affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER);
+ }
+
+ public static sqlite3 createNewDb(){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ int rc = sqlite3_open(":memory:", out);
+ ++metrics.dbOpen;
+ sqlite3 db = out.take();
+ if( 0!=rc ){
+ final String msg =
+ null==db ? sqlite3_errstr(rc) : sqlite3_errmsg(db);
+ sqlite3_close(db);
+ throw new RuntimeException("Opening db failed: "+msg);
+ }
+ affirm( null == out.get() );
+ affirm( 0 != db.getNativePointer() );
+ rc = sqlite3_busy_timeout(db, 2000);
+ affirm( 0 == rc );
+ return db;
+ }
+
+ public static void execSql(sqlite3 db, String[] sql){
+ execSql(db, String.join("", sql));
+ }
+
+ public static int execSql(sqlite3 db, boolean throwOnError, String sql){
+ OutputPointer.Int32 oTail = new OutputPointer.Int32();
+ final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8);
+ int pos = 0, n = 1;
+ byte[] sqlChunk = sqlUtf8;
+ int rc = 0;
+ sqlite3_stmt stmt = null;
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+ while(pos < sqlChunk.length){
+ if(pos > 0){
+ sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
+ sqlChunk.length);
+ }
+ if( 0==sqlChunk.length ) break;
+ rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail);
+ if(throwOnError) affirm(0 == rc);
+ else if( 0!=rc ) break;
+ pos = oTail.value;
+ stmt = outStmt.take();
+ if( null == stmt ){
+ // empty statement was parsed.
+ continue;
+ }
+ affirm(0 != stmt.getNativePointer());
+ while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){
+ }
+ sqlite3_finalize(stmt);
+ affirm(0 == stmt.getNativePointer());
+ if(0!=rc && SQLITE_ROW!=rc && SQLITE_DONE!=rc){
+ break;
+ }
+ }
+ sqlite3_finalize(stmt);
+ if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0;
+ if( 0!=rc && throwOnError){
+ throw new RuntimeException("db op failed with rc="
+ +rc+": "+sqlite3_errmsg(db));
+ }
+ return rc;
+ }
+
+ public static void execSql(sqlite3 db, String sql){
+ execSql(db, true, sql);
+ }
+
+ public static sqlite3_stmt prepare(sqlite3 db, boolean throwOnError, String sql){
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+ int rc = sqlite3_prepare_v2(db, sql, outStmt);
+ if( throwOnError ){
+ affirm( 0 == rc );
+ }
+ final sqlite3_stmt rv = outStmt.take();
+ affirm( null == outStmt.get() );
+ if( throwOnError ){
+ affirm( 0 != rv.getNativePointer() );
+ }
+ return rv;
+ }
+
+ public static sqlite3_stmt prepare(sqlite3 db, String sql){
+ return prepare(db, true, sql);
+ }
+
+ private void showCompileOption(){
+ int i = 0;
+ String optName;
+ outln("compile options:");
+ for( ; null != (optName = sqlite3_compileoption_get(i)); ++i){
+ outln("\t"+optName+"\t (used="+
+ sqlite3_compileoption_used(optName)+")");
+ }
+ }
+
+ private void testCompileOption(){
+ int i = 0;
+ String optName;
+ for( ; null != (optName = sqlite3_compileoption_get(i)); ++i){
+ }
+ affirm( i > 10 );
+ affirm( null==sqlite3_compileoption_get(-1) );
+ }
+
+ private void testOpenDb1(){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ int rc = sqlite3_open(":memory:", out);
+ ++metrics.dbOpen;
+ sqlite3 db = out.get();
+ affirm(0 == rc);
+ affirm(db.getNativePointer()!=0);
+ sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, null)
+ /* This function has different mangled names in jdk8 vs jdk19,
+ and this call is here to ensure that the build fails
+ if it cannot find both names. */;
+
+ affirm( 0==sqlite3_db_readonly(db,"main") );
+ affirm( 0==sqlite3_db_readonly(db,null) );
+ affirm( 0>sqlite3_db_readonly(db,"nope") );
+ affirm( 0>sqlite3_db_readonly(null,null) );
+ affirm( 0==sqlite3_last_insert_rowid(null) );
+
+ // These interrupt checks are only to make sure that the JNI binding
+ // has the proper exported symbol names. They don't actually test
+ // anything useful.
+ affirm( !sqlite3_is_interrupted(db) );
+ sqlite3_interrupt(db);
+ affirm( sqlite3_is_interrupted(db) );
+ sqlite3_close_v2(db);
+ affirm(0 == db.getNativePointer());
+ }
+
+ private void testOpenDb2(){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ int rc = sqlite3_open_v2(":memory:", out,
+ SQLITE_OPEN_READWRITE
+ | SQLITE_OPEN_CREATE, null);
+ ++metrics.dbOpen;
+ affirm(0 == rc);
+ sqlite3 db = out.get();
+ affirm(0 != db.getNativePointer());
+ sqlite3_close_v2(db);
+ affirm(0 == db.getNativePointer());
+ }
+
+ private void testPrepare123(){
+ sqlite3 db = createNewDb();
+ int rc;
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+ rc = sqlite3_prepare(db, "CREATE TABLE t1(a);", outStmt);
+ affirm(0 == rc);
+ sqlite3_stmt stmt = outStmt.take();
+ affirm(0 != stmt.getNativePointer());
+ affirm( !sqlite3_stmt_readonly(stmt) );
+ affirm( db == sqlite3_db_handle(stmt) );
+ rc = sqlite3_step(stmt);
+ affirm(SQLITE_DONE == rc);
+ sqlite3_finalize(stmt);
+ affirm( null == sqlite3_db_handle(stmt) );
+ affirm(0 == stmt.getNativePointer());
+
+ { /* Demonstrate how to use the "zTail" option of
+ sqlite3_prepare() family of functions. */
+ OutputPointer.Int32 oTail = new OutputPointer.Int32();
+ final byte[] sqlUtf8 =
+ "CREATE TABLE t2(a); INSERT INTO t2(a) VALUES(1),(2),(3)"
+ .getBytes(StandardCharsets.UTF_8);
+ int pos = 0, n = 1;
+ byte[] sqlChunk = sqlUtf8;
+ while(pos < sqlChunk.length){
+ if(pos > 0){
+ sqlChunk = Arrays.copyOfRange(sqlChunk, pos, sqlChunk.length);
+ }
+ //outln("SQL chunk #"+n+" length = "+sqlChunk.length+", pos = "+pos);
+ if( 0==sqlChunk.length ) break;
+ rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail);
+ affirm(0 == rc);
+ stmt = outStmt.get();
+ pos = oTail.value;
+ /*outln("SQL tail pos = "+pos+". Chunk = "+
+ (new String(Arrays.copyOfRange(sqlChunk,0,pos),
+ StandardCharsets.UTF_8)));*/
+ switch(n){
+ case 1: affirm(19 == pos); break;
+ case 2: affirm(36 == pos); break;
+ default: affirm( false /* can't happen */ );
+
+ }
+ ++n;
+ affirm(0 != stmt.getNativePointer());
+ rc = sqlite3_step(stmt);
+ affirm(SQLITE_DONE == rc);
+ sqlite3_finalize(stmt);
+ affirm(0 == stmt.getNativePointer());
+ }
+ }
+
+
+ rc = sqlite3_prepare_v3(db, "INSERT INTO t2(a) VALUES(1),(2),(3)",
+ SQLITE_PREPARE_NORMALIZE, outStmt);
+ affirm(0 == rc);
+ stmt = outStmt.get();
+ affirm(0 != stmt.getNativePointer());
+ sqlite3_finalize(stmt);
+ affirm(0 == stmt.getNativePointer() );
+
+ affirm( 0==sqlite3_errcode(db) );
+ stmt = sqlite3_prepare(db, "intentional error");
+ affirm( null==stmt );
+ affirm( 0!=sqlite3_errcode(db) );
+ affirm( 0==sqlite3_errmsg(db).indexOf("near \"intentional\"") );
+ sqlite3_finalize(stmt);
+ stmt = sqlite3_prepare(db, "/* empty input*/\n-- comments only");
+ affirm( null==stmt );
+ affirm( 0==sqlite3_errcode(db) );
+ sqlite3_close_v2(db);
+ }
+
+ private void testBindFetchInt(){
+ sqlite3 db = createNewDb();
+ execSql(db, "CREATE TABLE t(a)");
+
+ sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(:a);");
+ affirm(1 == sqlite3_bind_parameter_count(stmt));
+ final int paramNdx = sqlite3_bind_parameter_index(stmt, ":a");
+ affirm(1 == paramNdx);
+ affirm( ":a".equals(sqlite3_bind_parameter_name(stmt, paramNdx)));
+ int total1 = 0;
+ long rowid = -1;
+ int changes = sqlite3_changes(db);
+ int changesT = sqlite3_total_changes(db);
+ long changes64 = sqlite3_changes64(db);
+ long changesT64 = sqlite3_total_changes64(db);
+ int rc;
+ for(int i = 99; i < 102; ++i ){
+ total1 += i;
+ rc = sqlite3_bind_int(stmt, paramNdx, i);
+ affirm(0 == rc);
+ rc = sqlite3_step(stmt);
+ sqlite3_reset(stmt);
+ affirm(SQLITE_DONE == rc);
+ long x = sqlite3_last_insert_rowid(db);
+ affirm(x > rowid);
+ rowid = x;
+ }
+ sqlite3_finalize(stmt);
+ affirm(300 == total1);
+ affirm(sqlite3_changes(db) > changes);
+ affirm(sqlite3_total_changes(db) > changesT);
+ affirm(sqlite3_changes64(db) > changes64);
+ affirm(sqlite3_total_changes64(db) > changesT64);
+ stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+ affirm( sqlite3_stmt_readonly(stmt) );
+ affirm( !sqlite3_stmt_busy(stmt) );
+ int total2 = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ affirm( sqlite3_stmt_busy(stmt) );
+ total2 += sqlite3_column_int(stmt, 0);
+ sqlite3_value sv = sqlite3_column_value(stmt, 0);
+ affirm( null != sv );
+ affirm( 0 != sv.getNativePointer() );
+ affirm( SQLITE_INTEGER == sqlite3_value_type(sv) );
+ }
+ affirm( !sqlite3_stmt_busy(stmt) );
+ sqlite3_finalize(stmt);
+ affirm(total1 == total2);
+
+ // sqlite3_value_frombind() checks...
+ stmt = prepare(db, "SELECT 1, ?");
+ sqlite3_bind_int(stmt, 1, 2);
+ rc = sqlite3_step(stmt);
+ affirm( SQLITE_ROW==rc );
+ affirm( !sqlite3_value_frombind(sqlite3_column_value(stmt, 0)) );
+ affirm( sqlite3_value_frombind(sqlite3_column_value(stmt, 1)) );
+ sqlite3_finalize(stmt);
+
+ sqlite3_close_v2(db);
+ affirm(0 == db.getNativePointer());
+ }
+
+ private void testBindFetchInt64(){
+ try (sqlite3 db = createNewDb()){
+ execSql(db, "CREATE TABLE t(a)");
+ sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
+ long total1 = 0;
+ for(long i = 0xffffffff; i < 0xffffffff + 3; ++i ){
+ total1 += i;
+ sqlite3_bind_int64(stmt, 1, i);
+ sqlite3_step(stmt);
+ sqlite3_reset(stmt);
+ }
+ sqlite3_finalize(stmt);
+ stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+ long total2 = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ total2 += sqlite3_column_int64(stmt, 0);
+ }
+ sqlite3_finalize(stmt);
+ affirm(total1 == total2);
+ //sqlite3_close_v2(db);
+ }
+ }
+
+ private void testBindFetchDouble(){
+ try (sqlite3 db = createNewDb()){
+ execSql(db, "CREATE TABLE t(a)");
+ sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
+ double total1 = 0;
+ for(double i = 1.5; i < 5.0; i = i + 1.0 ){
+ total1 += i;
+ sqlite3_bind_double(stmt, 1, i);
+ sqlite3_step(stmt);
+ sqlite3_reset(stmt);
+ }
+ sqlite3_finalize(stmt);
+ stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+ double total2 = 0;
+ int counter = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ ++counter;
+ total2 += sqlite3_column_double(stmt, 0);
+ }
+ affirm(4 == counter);
+ sqlite3_finalize(stmt);
+ affirm(total2<=total1+0.01 && total2>=total1-0.01);
+ //sqlite3_close_v2(db);
+ }
+ }
+
+ private void testBindFetchText(){
+ sqlite3 db = createNewDb();
+ execSql(db, "CREATE TABLE t(a)");
+ sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
+ String[] list1 = { "hell🤩", "w😃rld", "!🤩" };
+ int rc;
+ int n = 0;
+ for( String e : list1 ){
+ rc = (0==n)
+ ? sqlite3_bind_text(stmt, 1, e)
+ : sqlite3_bind_text16(stmt, 1, e);
+ affirm(0 == rc);
+ rc = sqlite3_step(stmt);
+ affirm(SQLITE_DONE==rc);
+ sqlite3_reset(stmt);
+ }
+ sqlite3_finalize(stmt);
+ stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+ StringBuilder sbuf = new StringBuilder();
+ n = 0;
+ 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);
+ sbuf.append( txt );
+ affirm( txt.equals(new String(
+ sqlite3_column_text(stmt, 0),
+ StandardCharsets.UTF_8
+ )) );
+ affirm( txt.length() < sqlite3_value_bytes(sv) );
+ affirm( txt.equals(new String(
+ sqlite3_value_text(sv),
+ StandardCharsets.UTF_8)) );
+ affirm( txt.length() == sqlite3_value_bytes16(sv)/2 );
+ affirm( txt.equals(sqlite3_value_text16(sv)) );
+ sqlite3_value_free(sv);
+ ++n;
+ }
+ sqlite3_finalize(stmt);
+ affirm(3 == n);
+ affirm("w😃rldhell🤩!🤩".equals(sbuf.toString()));
+
+ try( sqlite3_stmt stmt2 = prepare(db, "SELECT ?, ?") ){
+ rc = sqlite3_bind_text(stmt2, 1, "");
+ affirm( 0==rc );
+ rc = sqlite3_bind_text(stmt2, 2, (String)null);
+ affirm( 0==rc );
+ rc = sqlite3_step(stmt2);
+ affirm( SQLITE_ROW==rc );
+ byte[] colBa = sqlite3_column_text(stmt2, 0);
+ affirm( 0==colBa.length );
+ colBa = sqlite3_column_text(stmt2, 1);
+ affirm( null==colBa );
+ //sqlite3_finalize(stmt);
+ }
+
+ if(true){
+ sqlite3_close_v2(db);
+ }else{
+ // Let the Object.finalize() override deal with it.
+ }
+ }
+
+ private void testBindFetchBlob(){
+ sqlite3 db = createNewDb();
+ execSql(db, "CREATE TABLE t(a)");
+ sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
+ byte[] list1 = { 0x32, 0x33, 0x34 };
+ int rc = sqlite3_bind_blob(stmt, 1, list1);
+ affirm( 0==rc );
+ rc = sqlite3_step(stmt);
+ affirm(SQLITE_DONE == rc);
+ sqlite3_finalize(stmt);
+ stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+ int n = 0;
+ int total = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ byte[] blob = sqlite3_column_blob(stmt, 0);
+ affirm(3 == blob.length);
+ int i = 0;
+ for(byte b : blob){
+ affirm(b == list1[i++]);
+ total += b;
+ }
+ ++n;
+ }
+ sqlite3_finalize(stmt);
+ affirm(1 == n);
+ affirm(total == 0x32 + 0x33 + 0x34);
+ sqlite3_close_v2(db);
+ }
+
+ private void testSql(){
+ sqlite3 db = createNewDb();
+ sqlite3_stmt stmt = prepare(db, "SELECT 1");
+ affirm( "SELECT 1".equals(sqlite3_sql(stmt)) );
+ sqlite3_finalize(stmt);
+ stmt = prepare(db, "SELECT ?");
+ sqlite3_bind_text(stmt, 1, "hell😃");
+ final String expect = "SELECT 'hell😃'";
+ affirm( expect.equals(sqlite3_expanded_sql(stmt)) );
+ String n = sqlite3_normalized_sql(stmt);
+ affirm( null==n || "SELECT?;".equals(n) );
+ sqlite3_finalize(stmt);
+ sqlite3_close(db);
+ }
+
+ private void testCollation(){
+ final sqlite3 db = createNewDb();
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ final ValueHolder xDestroyCalled = new ValueHolder<>(0);
+ final CollationCallback myCollation = new CollationCallback() {
+ private String myState =
+ "this is local state. There is much like it, but this is mine.";
+ @Override
+ // Reverse-sorts its inputs...
+ public int call(byte[] lhs, byte[] rhs){
+ int len = lhs.length > rhs.length ? rhs.length : lhs.length;
+ int c = 0, i = 0;
+ for(i = 0; i < len; ++i){
+ c = lhs[i] - rhs[i];
+ if(0 != c) break;
+ }
+ if(0==c){
+ if(i < lhs.length) c = 1;
+ else if(i < rhs.length) c = -1;
+ }
+ return -c;
+ }
+ @Override
+ public void xDestroy() {
+ // Just demonstrates that xDestroy is called.
+ ++xDestroyCalled.value;
+ }
+ };
+ final CollationNeededCallback collLoader = new CollationNeededCallback(){
+ @Override
+ public int call(sqlite3 dbArg, int eTextRep, String collationName){
+ affirm(dbArg == db/* as opposed to a temporary object*/);
+ return sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation);
+ }
+ };
+ int rc = sqlite3_collation_needed(db, collLoader);
+ affirm( 0 == rc );
+ rc = sqlite3_collation_needed(db, collLoader);
+ affirm( 0 == rc /* Installing the same object again is a no-op */);
+ sqlite3_stmt stmt = prepare(db, "SELECT a FROM t ORDER BY a COLLATE reversi");
+ int counter = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ final String val = sqlite3_column_text16(stmt, 0);
+ ++counter;
+ //outln("REVERSI'd row#"+counter+": "+val);
+ switch(counter){
+ case 1: affirm("c".equals(val)); break;
+ case 2: affirm("b".equals(val)); break;
+ case 3: affirm("a".equals(val)); break;
+ }
+ }
+ affirm(3 == counter);
+ sqlite3_finalize(stmt);
+ stmt = prepare(db, "SELECT a FROM t ORDER BY a");
+ counter = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ final String val = sqlite3_column_text16(stmt, 0);
+ ++counter;
+ //outln("Non-REVERSI'd row#"+counter+": "+val);
+ switch(counter){
+ case 3: affirm("c".equals(val)); break;
+ case 2: affirm("b".equals(val)); break;
+ case 1: affirm("a".equals(val)); break;
+ }
+ }
+ affirm(3 == counter);
+ sqlite3_finalize(stmt);
+ affirm( 0 == xDestroyCalled.value );
+ rc = sqlite3_collation_needed(db, null);
+ affirm( 0 == rc );
+ sqlite3_close_v2(db);
+ affirm( 0 == db.getNativePointer() );
+ affirm( 1 == xDestroyCalled.value );
+ }
+
+ @SingleThreadOnly /* because it's thread-agnostic */
+ private void testToUtf8(){
+ /**
+ https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html
+
+ Let's ensure that we can convert to standard UTF-8 in Java code
+ (noting that the JNI native API has no way to do this).
+ */
+ final byte[] ba = "a \0 b".getBytes(StandardCharsets.UTF_8);
+ affirm( 5 == ba.length /* as opposed to 6 in modified utf-8 */);
+ }
+
+ private void testStatus(){
+ final OutputPointer.Int64 cur64 = new OutputPointer.Int64();
+ final OutputPointer.Int64 high64 = new OutputPointer.Int64();
+ final OutputPointer.Int32 cur32 = new OutputPointer.Int32();
+ final OutputPointer.Int32 high32 = new OutputPointer.Int32();
+ final sqlite3 db = createNewDb();
+ execSql(db, "create table t(a); insert into t values(1),(2),(3)");
+
+ int rc = sqlite3_status(SQLITE_STATUS_MEMORY_USED, cur32, high32, false);
+ affirm( 0 == rc );
+ affirm( cur32.value > 0 );
+ affirm( high32.value >= cur32.value );
+
+ rc = sqlite3_status64(SQLITE_STATUS_MEMORY_USED, cur64, high64, false);
+ affirm( 0 == rc );
+ affirm( cur64.value > 0 );
+ affirm( high64.value >= cur64.value );
+
+ cur32.value = 0;
+ high32.value = 1;
+ rc = sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, cur32, high32, false);
+ affirm( 0 == rc );
+ affirm( cur32.value > 0 );
+ affirm( high32.value == 0 /* always 0 for SCHEMA_USED */ );
+
+ sqlite3_close_v2(db);
+ }
+
+ private void testUdf1(){
+ final sqlite3 db = createNewDb();
+ // These ValueHolders are just to confirm that the func did what we want...
+ final ValueHolder xDestroyCalled = new ValueHolder<>(false);
+ final ValueHolder xFuncAccum = new ValueHolder<>(0);
+ final ValueHolder neverEverDoThisInClientCode = new ValueHolder<>(null);
+ final ValueHolder neverEverDoThisInClientCode2 = new ValueHolder<>(null);
+
+ // Create an SQLFunction instance using one of its 3 subclasses:
+ // Scalar, Aggregate, or Window:
+ SQLFunction func =
+ // Each of the 3 subclasses requires a different set of
+ // functions, all of which must be implemented. Anonymous
+ // classes are a convenient way to implement these.
+ new ScalarFunction(){
+ public void xFunc(sqlite3_context cx, sqlite3_value[] args){
+ affirm(db == sqlite3_context_db_handle(cx));
+ 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. */
+ neverEverDoThisInClientCode2.value = cx;
+ neverEverDoThisInClientCode.value = args;
+ }
+ int result = 0;
+ for( sqlite3_value v : args ) result += sqlite3_value_int(v);
+ xFuncAccum.value += result;// just for post-run testing
+ sqlite3_result_int(cx, result);
+ }
+ /* OPTIONALLY override xDestroy... */
+ public void xDestroy(){
+ xDestroyCalled.value = true;
+ }
+ };
+
+ // Register and use the function...
+ int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func);
+ affirm(0 == rc);
+ affirm(0 == xFuncAccum.value);
+ final sqlite3_stmt stmt = prepare(db, "SELECT myfunc(1,2,3)");
+ int n = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ affirm( 6 == sqlite3_column_int(stmt, 0) );
+ ++n;
+ }
+ sqlite3_finalize(stmt);
+ affirm(1 == n);
+ affirm(6 == xFuncAccum.value);
+ affirm( !xDestroyCalled.value );
+ affirm( null!=neverEverDoThisInClientCode.value );
+ affirm( null!=neverEverDoThisInClientCode2.value );
+ affirm( 0 xFuncAccum = new ValueHolder<>(0);
+
+ SQLFunction funcAgg = new AggregateFunction(){
+ @Override public void xStep(sqlite3_context cx, sqlite3_value[] args){
+ /** Throwing from here should emit loud noise on stdout or stderr
+ but the exception is supressed because we have no way to inform
+ sqlite about it from these callbacks. */
+ //throw new RuntimeException("Throwing from an xStep");
+ }
+ @Override public void xFinal(sqlite3_context cx){
+ throw new RuntimeException("Throwing from an xFinal");
+ }
+ };
+ int rc = sqlite3_create_function(db, "myagg", 1, SQLITE_UTF8, funcAgg);
+ affirm(0 == rc);
+ affirm(0 == xFuncAccum.value);
+ sqlite3_stmt stmt = prepare(db, "SELECT myagg(1)");
+ rc = sqlite3_step(stmt);
+ sqlite3_finalize(stmt);
+ affirm( 0 != rc );
+ affirm( sqlite3_errmsg(db).indexOf("an xFinal") > 0 );
+
+ SQLFunction funcSc = new ScalarFunction(){
+ @Override public void xFunc(sqlite3_context cx, sqlite3_value[] args){
+ throw new RuntimeException("Throwing from an xFunc");
+ }
+ };
+ rc = sqlite3_create_function(db, "mysca", 0, SQLITE_UTF8, funcSc);
+ affirm(0 == rc);
+ affirm(0 == xFuncAccum.value);
+ stmt = prepare(db, "SELECT mysca()");
+ rc = sqlite3_step(stmt);
+ sqlite3_finalize(stmt);
+ affirm( 0 != rc );
+ affirm( sqlite3_errmsg(db).indexOf("an xFunc") > 0 );
+ rc = sqlite3_create_function(db, "mysca", 1, -1, funcSc);
+ affirm( SQLITE_FORMAT==rc, "invalid encoding value." );
+ sqlite3_close_v2(db);
+ }
+
+ @SingleThreadOnly
+ private void testUdfJavaObject(){
+ affirm( !mtMode );
+ final sqlite3 db = createNewDb();
+ final ValueHolder testResult = new ValueHolder<>(db);
+ final ValueHolder boundObj = new ValueHolder<>(42);
+ final SQLFunction func = new ScalarFunction(){
+ public void xFunc(sqlite3_context cx, sqlite3_value args[]){
+ sqlite3_result_java_object(cx, testResult.value);
+ affirm( sqlite3_value_java_object(args[0]) == boundObj );
+ }
+ };
+ int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func);
+ affirm(0 == rc);
+ sqlite3_stmt stmt = prepare(db, "select myfunc(?)");
+ affirm( 0 != stmt.getNativePointer() );
+ affirm( testResult.value == db );
+ rc = sqlite3_bind_java_object(stmt, 1, boundObj);
+ affirm( 0==rc );
+ int n = 0;
+ if( SQLITE_ROW == sqlite3_step(stmt) ){
+ final sqlite3_value v = sqlite3_column_value(stmt, 0);
+ affirm( testResult.value == sqlite3_value_java_object(v) );
+ affirm( testResult.value == sqlite3_value_java_casted(v, sqlite3.class) );
+ affirm( testResult.value ==
+ 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);
+ affirm( 1 == n );
+ affirm( 0==sqlite3_db_release_memory(db) );
+ sqlite3_close_v2(db);
+ }
+
+ private void testUdfAggregate(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder xFinalNull =
+ // To confirm that xFinal() is called with no aggregate state
+ // when the corresponding result set is empty.
+ new ValueHolder<>(false);
+ SQLFunction func = new AggregateFunction(){
+ @Override
+ public void xStep(sqlite3_context cx, sqlite3_value[] args){
+ final ValueHolder 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){
+ final Integer v = this.takeAggregateState(cx);
+ if(null == v){
+ xFinalNull.value = true;
+ sqlite3_result_null(cx);
+ }else{
+ sqlite3_result_int(cx, v);
+ }
+ }
+ };
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES(1),(2),(3)");
+ int rc = sqlite3_create_function(db, "myfunc", 1, SQLITE_UTF8, func);
+ affirm(0 == rc);
+ sqlite3_stmt stmt = prepare(db, "select myfunc(a), myfunc(a+10) from t");
+ affirm( 0==sqlite3_stmt_status(stmt, SQLITE_STMTSTATUS_RUN, false) );
+ int n = 0;
+ if( SQLITE_ROW == sqlite3_step(stmt) ){
+ int v = sqlite3_column_int(stmt, 0);
+ affirm( 6 == v );
+ int v2 = sqlite3_column_int(stmt, 1);
+ affirm( 30+v == v2 );
+ ++n;
+ }
+ affirm( 1==n );
+ affirm(!xFinalNull.value);
+ sqlite3_reset(stmt);
+ affirm( 1==sqlite3_stmt_status(stmt, SQLITE_STMTSTATUS_RUN, false) );
+ // Ensure that the accumulator is reset on subsequent calls...
+ n = 0;
+ if( SQLITE_ROW == sqlite3_step(stmt) ){
+ final int v = sqlite3_column_int(stmt, 0);
+ affirm( 6 == v );
+ ++n;
+ }
+ sqlite3_finalize(stmt);
+ affirm( 1==n );
+
+ stmt = prepare(db, "select myfunc(a), myfunc(a+a) from t order by a");
+ n = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ final int c0 = sqlite3_column_int(stmt, 0);
+ final int c1 = sqlite3_column_int(stmt, 1);
+ ++n;
+ affirm( 6 == c0 );
+ affirm( 12 == c1 );
+ }
+ sqlite3_finalize(stmt);
+ affirm( 1 == n );
+ affirm(!xFinalNull.value);
+
+ execSql(db, "SELECT myfunc(1) WHERE 0");
+ affirm(xFinalNull.value);
+ sqlite3_close_v2(db);
+ }
+
+ private void testUdfWindow(){
+ final sqlite3 db = createNewDb();
+ /* Example window function, table, and results taken from:
+ https://sqlite.org/windowfunctions.html#udfwinfunc */
+ final SQLFunction func = new WindowFunction(){
+
+ private void xStepInverse(sqlite3_context cx, int v){
+ this.getAggregateState(cx,0).value += v;
+ }
+ @Override public void xStep(sqlite3_context cx, sqlite3_value[] args){
+ this.xStepInverse(cx, sqlite3_value_int(args[0]));
+ }
+ @Override public void xInverse(sqlite3_context cx, sqlite3_value[] args){
+ this.xStepInverse(cx, -sqlite3_value_int(args[0]));
+ }
+
+ private void xFinalValue(sqlite3_context cx, Integer v){
+ if(null == v) sqlite3_result_null(cx);
+ else sqlite3_result_int(cx, v);
+ }
+ @Override public void xFinal(sqlite3_context cx){
+ xFinalValue(cx, this.takeAggregateState(cx));
+ }
+ @Override public void xValue(sqlite3_context cx){
+ xFinalValue(cx, this.getAggregateState(cx,null).value);
+ }
+ };
+ int rc = sqlite3_create_function(db, "winsumint", 1, SQLITE_UTF8, func);
+ affirm( 0 == rc );
+ execSql(db, new String[] {
+ "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES",
+ "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)"
+ });
+ final sqlite3_stmt stmt = prepare(db,
+ "SELECT x, winsumint(y) OVER ("+
+ "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);
+ final int i = sqlite3_column_int(stmt, 1);
+ switch(++n){
+ case 1: affirm( "a".equals(s) && 9==i ); break;
+ case 2: affirm( "b".equals(s) && 12==i ); break;
+ case 3: affirm( "c".equals(s) && 16==i ); break;
+ case 4: affirm( "d".equals(s) && 12==i ); break;
+ case 5: affirm( "e".equals(s) && 9==i ); break;
+ default: affirm( false /* cannot happen */ );
+ }
+ }
+ sqlite3_finalize(stmt);
+ affirm( 5 == n );
+ sqlite3_close_v2(db);
+ }
+
+ private void listBoundMethods(){
+ if(false){
+ final java.lang.reflect.Field[] declaredFields =
+ CApi.class.getDeclaredFields();
+ outln("Bound constants:\n");
+ for(java.lang.reflect.Field field : declaredFields) {
+ if(java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
+ outln("\t",field.getName());
+ }
+ }
+ }
+ final java.lang.reflect.Method[] declaredMethods =
+ CApi.class.getDeclaredMethods();
+ final java.util.List funcList = new java.util.ArrayList<>();
+ for(java.lang.reflect.Method m : declaredMethods){
+ if((m.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0){
+ final String name = m.getName();
+ if(name.startsWith("sqlite3_")){
+ funcList.add(name);
+ }
+ }
+ }
+ int count = 0;
+ java.util.Collections.sort(funcList);
+ for(String n : funcList){
+ ++count;
+ outln("\t",n,"()");
+ }
+ outln(count," functions named sqlite3_*.");
+ }
+
+ private void testTrace(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder counter = new ValueHolder<>(0);
+ /* Ensure that characters outside of the UTF BMP survive the trip
+ from Java to sqlite3 and back to Java. (At no small efficiency
+ penalty.) */
+ final String nonBmpChar = "😃";
+ int rc = sqlite3_trace_v2(
+ db, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE
+ | SQLITE_TRACE_ROW | SQLITE_TRACE_CLOSE,
+ new TraceV2Callback(){
+ @Override public int call(int traceFlag, Object pNative, Object x){
+ ++counter.value;
+ //outln("TRACE "+traceFlag+" pNative = "+pNative.getClass().getName());
+ switch(traceFlag){
+ case SQLITE_TRACE_STMT:
+ affirm(pNative instanceof sqlite3_stmt);
+ //outln("TRACE_STMT sql = "+x);
+ affirm(x instanceof String);
+ affirm( ((String)x).indexOf(nonBmpChar) > 0 );
+ break;
+ case SQLITE_TRACE_PROFILE:
+ affirm(pNative instanceof sqlite3_stmt);
+ affirm(x instanceof Long);
+ //outln("TRACE_PROFILE time = "+x);
+ break;
+ case SQLITE_TRACE_ROW:
+ affirm(pNative instanceof sqlite3_stmt);
+ affirm(null == x);
+ //outln("TRACE_ROW = "+sqlite3_column_text16((sqlite3_stmt)pNative, 0));
+ break;
+ case SQLITE_TRACE_CLOSE:
+ affirm(pNative instanceof sqlite3);
+ affirm(null == x);
+ break;
+ default:
+ affirm(false /*cannot happen*/);
+ break;
+ }
+ return 0;
+ }
+ });
+ affirm( 0==rc );
+ execSql(db, "SELECT coalesce(null,null,'"+nonBmpChar+"'); "+
+ "SELECT 'w"+nonBmpChar+"orld'");
+ affirm( 6 == counter.value );
+ sqlite3_close_v2(db);
+ affirm( 7 == counter.value );
+ }
+
+ @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 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 java.io.File f = new java.io.File(dbName);
+ f.delete();
+ }catch(Exception e){
+ /* ignore */
+ }
+ }
+
+ private void testProgress(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder counter = new ValueHolder<>(0);
+ sqlite3_progress_handler(db, 1, new ProgressHandlerCallback(){
+ @Override public int call(){
+ ++counter.value;
+ return 0;
+ }
+ });
+ execSql(db, "SELECT 1; SELECT 2;");
+ affirm( counter.value > 0 );
+ int nOld = counter.value;
+ sqlite3_progress_handler(db, 0, null);
+ execSql(db, "SELECT 1; SELECT 2;");
+ affirm( nOld == counter.value );
+ sqlite3_close_v2(db);
+ }
+
+ private void testCommitHook(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder counter = new ValueHolder<>(0);
+ final ValueHolder hookResult = new ValueHolder<>(0);
+ final CommitHookCallback theHook = new CommitHookCallback(){
+ @Override public int call(){
+ ++counter.value;
+ return hookResult.value;
+ }
+ };
+ CommitHookCallback oldHook = sqlite3_commit_hook(db, theHook);
+ affirm( null == oldHook );
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ affirm( 2 == counter.value );
+ execSql(db, "BEGIN; SELECT 1; SELECT 2; COMMIT;");
+ affirm( 2 == counter.value /* NOT invoked if no changes are made */ );
+ execSql(db, "BEGIN; update t set a='d' where a='c'; COMMIT;");
+ affirm( 3 == counter.value );
+ oldHook = sqlite3_commit_hook(db, theHook);
+ affirm( theHook == oldHook );
+ execSql(db, "BEGIN; update t set a='e' where a='d'; COMMIT;");
+ affirm( 4 == counter.value );
+ oldHook = sqlite3_commit_hook(db, null);
+ affirm( theHook == oldHook );
+ execSql(db, "BEGIN; update t set a='f' where a='e'; COMMIT;");
+ affirm( 4 == counter.value );
+ oldHook = sqlite3_commit_hook(db, null);
+ affirm( null == oldHook );
+ execSql(db, "BEGIN; update t set a='g' where a='f'; COMMIT;");
+ affirm( 4 == counter.value );
+
+ final CommitHookCallback newHook = new CommitHookCallback(){
+ @Override public int call(){return 0;}
+ };
+ oldHook = sqlite3_commit_hook(db, newHook);
+ affirm( null == oldHook );
+ execSql(db, "BEGIN; update t set a='h' where a='g'; COMMIT;");
+ affirm( 4 == counter.value );
+ oldHook = sqlite3_commit_hook(db, theHook);
+ affirm( newHook == oldHook );
+ execSql(db, "BEGIN; update t set a='i' where a='h'; COMMIT;");
+ 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 == rc );
+ affirm( 6 == counter.value );
+ sqlite3_close_v2(db);
+ }
+
+ private void testUpdateHook(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder counter = new ValueHolder<>(0);
+ final ValueHolder expectedOp = new ValueHolder<>(0);
+ final UpdateHookCallback theHook = new UpdateHookCallback(){
+ @Override
+ public void call(int opId, String dbName, String tableName, long rowId){
+ ++counter.value;
+ if( 0!=expectedOp.value ){
+ affirm( expectedOp.value == opId );
+ }
+ }
+ };
+ UpdateHookCallback oldHook = sqlite3_update_hook(db, theHook);
+ affirm( null == oldHook );
+ expectedOp.value = SQLITE_INSERT;
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ affirm( 3 == counter.value );
+ expectedOp.value = SQLITE_UPDATE;
+ execSql(db, "update t set a='d' where a='c';");
+ affirm( 4 == counter.value );
+ oldHook = sqlite3_update_hook(db, theHook);
+ affirm( theHook == oldHook );
+ expectedOp.value = SQLITE_DELETE;
+ execSql(db, "DELETE FROM t where a='d'");
+ affirm( 5 == counter.value );
+ oldHook = sqlite3_update_hook(db, null);
+ affirm( theHook == oldHook );
+ execSql(db, "update t set a='e' where a='b';");
+ affirm( 5 == counter.value );
+ oldHook = sqlite3_update_hook(db, null);
+ affirm( null == oldHook );
+
+ final UpdateHookCallback newHook = new UpdateHookCallback(){
+ @Override public void call(int opId, String dbName, String tableName, long rowId){
+ }
+ };
+ oldHook = sqlite3_update_hook(db, newHook);
+ affirm( null == oldHook );
+ execSql(db, "update t set a='h' where a='a'");
+ affirm( 5 == counter.value );
+ oldHook = sqlite3_update_hook(db, theHook);
+ affirm( newHook == oldHook );
+ expectedOp.value = SQLITE_UPDATE;
+ execSql(db, "update t set a='i' where a='h'");
+ affirm( 6 == counter.value );
+ sqlite3_close_v2(db);
+ }
+
+ /**
+ This test is functionally identical to testUpdateHook(), only with a
+ different callback type.
+ */
+ private void testPreUpdateHook(){
+ if( !sqlite3_compileoption_used("ENABLE_PREUPDATE_HOOK") ){
+ //outln("Skipping testPreUpdateHook(): no pre-update hook support.");
+ return;
+ }
+ final sqlite3 db = createNewDb();
+ final ValueHolder counter = new ValueHolder<>(0);
+ final ValueHolder expectedOp = new ValueHolder<>(0);
+ final PreupdateHookCallback theHook = new PreupdateHookCallback(){
+ @Override
+ public void call(sqlite3 db, int opId, String dbName, String dbTable,
+ long iKey1, long iKey2 ){
+ ++counter.value;
+ switch( opId ){
+ case SQLITE_UPDATE:
+ affirm( 0 < sqlite3_preupdate_count(db) );
+ affirm( null != sqlite3_preupdate_new(db, 0) );
+ affirm( null != sqlite3_preupdate_old(db, 0) );
+ break;
+ case SQLITE_INSERT:
+ affirm( null != sqlite3_preupdate_new(db, 0) );
+ break;
+ case SQLITE_DELETE:
+ affirm( null != sqlite3_preupdate_old(db, 0) );
+ break;
+ default:
+ break;
+ }
+ if( 0!=expectedOp.value ){
+ affirm( expectedOp.value == opId );
+ }
+ }
+ };
+ PreupdateHookCallback oldHook = sqlite3_preupdate_hook(db, theHook);
+ affirm( null == oldHook );
+ expectedOp.value = SQLITE_INSERT;
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ affirm( 3 == counter.value );
+ expectedOp.value = SQLITE_UPDATE;
+ execSql(db, "update t set a='d' where a='c';");
+ affirm( 4 == counter.value );
+ oldHook = sqlite3_preupdate_hook(db, theHook);
+ affirm( theHook == oldHook );
+ expectedOp.value = SQLITE_DELETE;
+ execSql(db, "DELETE FROM t where a='d'");
+ affirm( 5 == counter.value );
+ oldHook = sqlite3_preupdate_hook(db, null);
+ affirm( theHook == oldHook );
+ execSql(db, "update t set a='e' where a='b';");
+ affirm( 5 == counter.value );
+ oldHook = sqlite3_preupdate_hook(db, null);
+ affirm( null == oldHook );
+
+ final PreupdateHookCallback newHook = new PreupdateHookCallback(){
+ @Override
+ public void call(sqlite3 db, int opId, String dbName,
+ String tableName, long iKey1, long iKey2){
+ }
+ };
+ oldHook = sqlite3_preupdate_hook(db, newHook);
+ affirm( null == oldHook );
+ execSql(db, "update t set a='h' where a='a'");
+ affirm( 5 == counter.value );
+ oldHook = sqlite3_preupdate_hook(db, theHook);
+ affirm( newHook == oldHook );
+ expectedOp.value = SQLITE_UPDATE;
+ execSql(db, "update t set a='i' where a='h'");
+ affirm( 6 == counter.value );
+
+ sqlite3_close_v2(db);
+ }
+
+ private void testRollbackHook(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder counter = new ValueHolder<>(0);
+ final RollbackHookCallback theHook = new RollbackHookCallback(){
+ @Override public void call(){
+ ++counter.value;
+ }
+ };
+ RollbackHookCallback oldHook = sqlite3_rollback_hook(db, theHook);
+ affirm( null == oldHook );
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ affirm( 0 == counter.value );
+ execSql(db, false, "BEGIN; SELECT 1; SELECT 2; ROLLBACK;");
+ affirm( 1 == counter.value /* contra to commit hook, is invoked if no changes are made */ );
+
+ final RollbackHookCallback newHook = new RollbackHookCallback(){
+ @Override public void call(){return;}
+ };
+ oldHook = sqlite3_rollback_hook(db, newHook);
+ affirm( theHook == oldHook );
+ execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+ affirm( 1 == counter.value );
+ oldHook = sqlite3_rollback_hook(db, theHook);
+ affirm( newHook == oldHook );
+ execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+ affirm( 2 == counter.value );
+ int rc = execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+ affirm( 0 == rc );
+ affirm( 3 == counter.value );
+ sqlite3_close_v2(db);
+ }
+
+ /**
+ If FTS5 is available, runs FTS5 tests, else returns with no side
+ effects. If it is available but loading of the FTS5 bits fails,
+ it throws.
+ */
+ @SuppressWarnings("unchecked")
+ @SingleThreadOnly /* because the Fts5 parts are not yet known to be
+ thread-safe */
+ private void testFts5() throws Exception {
+ if( !sqlite3_compileoption_used("ENABLE_FTS5") ){
+ //outln("SQLITE_ENABLE_FTS5 is not set. Skipping FTS5 tests.");
+ return;
+ }
+ Exception err = null;
+ try {
+ Class t = Class.forName("org.sqlite.jni.fts5.TesterFts5");
+ java.lang.reflect.Constructor ctor = t.getConstructor();
+ ctor.setAccessible(true);
+ final long timeStart = System.currentTimeMillis();
+ ctor.newInstance() /* will run all tests */;
+ final long timeEnd = System.currentTimeMillis();
+ outln("FTS5 Tests done in ",(timeEnd - timeStart),"ms");
+ }catch(ClassNotFoundException e){
+ outln("FTS5 classes not loaded.");
+ err = e;
+ }catch(NoSuchMethodException e){
+ outln("FTS5 tester ctor not found.");
+ err = e;
+ }catch(Exception e){
+ outln("Instantiation of FTS5 tester threw.");
+ err = e;
+ }
+ if( null != err ){
+ outln("Exception: "+err);
+ err.printStackTrace();
+ throw err;
+ }
+ }
+
+ private void testAuthorizer(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder counter = new ValueHolder<>(0);
+ final ValueHolder authRc = new ValueHolder<>(0);
+ final AuthorizerCallback auth = new AuthorizerCallback(){
+ public int call(int op, String s0, String s1, String s2, String s3){
+ ++counter.value;
+ //outln("xAuth(): "+s0+" "+s1+" "+s2+" "+s3);
+ return authRc.value;
+ }
+ };
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ sqlite3_set_authorizer(db, auth);
+ execSql(db, "UPDATE t SET a=1");
+ affirm( 1 == counter.value );
+ authRc.value = SQLITE_DENY;
+ int rc = execSql(db, false, "UPDATE t SET a=2");
+ affirm( SQLITE_AUTH==rc );
+ // TODO: expand these tests considerably
+ sqlite3_close(db);
+ }
+
+ @SingleThreadOnly /* because multiple threads legitimately make these
+ results unpredictable */
+ private synchronized void testAutoExtension(){
+ final ValueHolder val = new ValueHolder<>(0);
+ final ValueHolder toss = new ValueHolder<>(null);
+ final AutoExtensionCallback ax = new AutoExtensionCallback(){
+ @Override public int call(sqlite3 db){
+ ++val.value;
+ if( null!=toss.value ){
+ throw new RuntimeException(toss.value);
+ }
+ return 0;
+ }
+ };
+ int rc = sqlite3_auto_extension( ax );
+ affirm( 0==rc );
+ sqlite3_close(createNewDb());
+ affirm( 1==val.value );
+ sqlite3_close(createNewDb());
+ affirm( 2==val.value );
+ sqlite3_reset_auto_extension();
+ sqlite3_close(createNewDb());
+ affirm( 2==val.value );
+ rc = sqlite3_auto_extension( ax );
+ affirm( 0==rc );
+ // Must not add a new entry
+ rc = sqlite3_auto_extension( ax );
+ affirm( 0==rc );
+ sqlite3_close( createNewDb() );
+ affirm( 3==val.value );
+
+ sqlite3 db = createNewDb();
+ affirm( 4==val.value );
+ execSql(db, "ATTACH ':memory:' as foo");
+ affirm( 4==val.value, "ATTACH uses the same connection, not sub-connections." );
+ sqlite3_close(db);
+ db = null;
+
+ affirm( sqlite3_cancel_auto_extension(ax) );
+ affirm( !sqlite3_cancel_auto_extension(ax) );
+ sqlite3_close(createNewDb());
+ affirm( 4==val.value );
+ rc = sqlite3_auto_extension( ax );
+ affirm( 0==rc );
+ Exception err = null;
+ toss.value = "Throwing from auto_extension.";
+ try{
+ sqlite3_close(createNewDb());
+ }catch(Exception e){
+ err = e;
+ }
+ affirm( err!=null );
+ affirm( err.getMessage().indexOf(toss.value)>0 );
+ toss.value = null;
+
+ val.value = 0;
+ final AutoExtensionCallback ax2 = new AutoExtensionCallback(){
+ @Override public synchronized int call(sqlite3 db){
+ ++val.value;
+ return 0;
+ }
+ };
+ rc = sqlite3_auto_extension( ax2 );
+ affirm( 0 == rc );
+ sqlite3_close(createNewDb());
+ affirm( 2 == val.value );
+ affirm( sqlite3_cancel_auto_extension(ax) );
+ affirm( !sqlite3_cancel_auto_extension(ax) );
+ sqlite3_close(createNewDb());
+ affirm( 3 == val.value );
+ rc = sqlite3_auto_extension( ax );
+ affirm( 0 == rc );
+ sqlite3_close(createNewDb());
+ affirm( 5 == val.value );
+ affirm( sqlite3_cancel_auto_extension(ax2) );
+ affirm( !sqlite3_cancel_auto_extension(ax2) );
+ sqlite3_close(createNewDb());
+ affirm( 6 == val.value );
+ rc = sqlite3_auto_extension( ax2 );
+ affirm( 0 == rc );
+ sqlite3_close(createNewDb());
+ affirm( 8 == val.value );
+
+ sqlite3_reset_auto_extension();
+ sqlite3_close(createNewDb());
+ affirm( 8 == val.value );
+ affirm( !sqlite3_cancel_auto_extension(ax) );
+ affirm( !sqlite3_cancel_auto_extension(ax2) );
+ sqlite3_close(createNewDb());
+ affirm( 8 == val.value );
+ }
+
+
+ private void testColumnMetadata(){
+ final sqlite3 db = createNewDb();
+ execSql(db, new String[] {
+ "CREATE TABLE t(a duck primary key not null collate noCase); ",
+ "INSERT INTO t(a) VALUES(1),(2),(3);"
+ });
+ OutputPointer.Bool bNotNull = new OutputPointer.Bool();
+ OutputPointer.Bool bPrimaryKey = new OutputPointer.Bool();
+ OutputPointer.Bool bAutoinc = new OutputPointer.Bool();
+ OutputPointer.String zCollSeq = new OutputPointer.String();
+ OutputPointer.String zDataType = new OutputPointer.String();
+ int rc = sqlite3_table_column_metadata(
+ db, "main", "t", "a", zDataType, zCollSeq,
+ bNotNull, bPrimaryKey, bAutoinc);
+ affirm( 0==rc );
+ affirm( bPrimaryKey.value );
+ affirm( !bAutoinc.value );
+ affirm( bNotNull.value );
+ affirm( "noCase".equals(zCollSeq.value) );
+ affirm( "duck".equals(zDataType.value) );
+
+ TableColumnMetadata m =
+ sqlite3_table_column_metadata(db, "main", "t", "a");
+ affirm( null != m );
+ affirm( bPrimaryKey.value == m.isPrimaryKey() );
+ affirm( bAutoinc.value == m.isAutoincrement() );
+ affirm( bNotNull.value == m.isNotNull() );
+ affirm( zCollSeq.value.equals(m.getCollation()) );
+ affirm( zDataType.value.equals(m.getDataType()) );
+
+ affirm( null == sqlite3_table_column_metadata(db, "nope", "t", "a") );
+ affirm( null == sqlite3_table_column_metadata(db, "main", "nope", "a") );
+
+ m = sqlite3_table_column_metadata(db, "main", "t", null)
+ /* Check only for existence of table */;
+ affirm( null != m );
+ affirm( m.isPrimaryKey() );
+ affirm( !m.isAutoincrement() );
+ affirm( !m.isNotNull() );
+ affirm( "BINARY".equalsIgnoreCase(m.getCollation()) );
+ affirm( "INTEGER".equalsIgnoreCase(m.getDataType()) );
+
+ sqlite3_close_v2(db);
+ }
+
+ private void testTxnState(){
+ final sqlite3 db = createNewDb();
+ affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
+ affirm( sqlite3_get_autocommit(db) );
+ execSql(db, "BEGIN;");
+ affirm( !sqlite3_get_autocommit(db) );
+ affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
+ execSql(db, "SELECT * FROM sqlite_schema;");
+ affirm( SQLITE_TXN_READ == sqlite3_txn_state(db, "main") );
+ execSql(db, "CREATE TABLE t(a);");
+ affirm( SQLITE_TXN_WRITE == sqlite3_txn_state(db, null) );
+ execSql(db, "ROLLBACK;");
+ affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
+ sqlite3_close_v2(db);
+ }
+
+
+ private void testExplain(){
+ final sqlite3 db = createNewDb();
+ sqlite3_stmt stmt = prepare(db,"SELECT 1");
+
+ affirm( 0 == sqlite3_stmt_isexplain(stmt) );
+ int rc = sqlite3_stmt_explain(stmt, 1);
+ affirm( 1 == sqlite3_stmt_isexplain(stmt) );
+ rc = sqlite3_stmt_explain(stmt, 2);
+ affirm( 2 == sqlite3_stmt_isexplain(stmt) );
+ sqlite3_finalize(stmt);
+ sqlite3_close_v2(db);
+ }
+
+ private void testLimit(){
+ final sqlite3 db = createNewDb();
+ int v;
+
+ v = sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1);
+ affirm( v > 0 );
+ affirm( v == sqlite3_limit(db, SQLITE_LIMIT_LENGTH, v-1) );
+ affirm( v-1 == sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1) );
+ sqlite3_close_v2(db);
+ }
+
+ private void testComplete(){
+ affirm( 0==sqlite3_complete("select 1") );
+ affirm( 0!=sqlite3_complete("select 1;") );
+ affirm( 0!=sqlite3_complete("nope 'nope' 'nope' 1;"), "Yup" );
+ }
+
+ private void testKeyword(){
+ final int n = sqlite3_keyword_count();
+ affirm( n>0 );
+ affirm( !sqlite3_keyword_check("_nope_") );
+ affirm( sqlite3_keyword_check("seLect") );
+ affirm( null!=sqlite3_keyword_name(0) );
+ affirm( null!=sqlite3_keyword_name(n-1) );
+ affirm( null==sqlite3_keyword_name(n) );
+ }
+
+ private void testBackup(){
+ final sqlite3 dbDest = createNewDb();
+
+ try (sqlite3 dbSrc = createNewDb()) {
+ execSql(dbSrc, new String[]{
+ "pragma page_size=512; VACUUM;",
+ "create table t(a);",
+ "insert into t(a) values(1),(2),(3);"
+ });
+ affirm( null==sqlite3_backup_init(dbSrc,"main",dbSrc,"main") );
+ try (sqlite3_backup b = sqlite3_backup_init(dbDest,"main",dbSrc,"main")) {
+ affirm( null!=b );
+ affirm( b.getNativePointer()!=0 );
+ int rc;
+ while( SQLITE_DONE!=(rc = sqlite3_backup_step(b, 1)) ){
+ affirm( 0==rc );
+ }
+ affirm( sqlite3_backup_pagecount(b) > 0 );
+ rc = sqlite3_backup_finish(b);
+ affirm( 0==rc );
+ affirm( b.getNativePointer()==0 );
+ }
+ }
+
+ try (sqlite3_stmt stmt = prepare(dbDest,"SELECT sum(a) from t")) {
+ sqlite3_step(stmt);
+ affirm( sqlite3_column_int(stmt,0) == 6 );
+ }
+ sqlite3_close_v2(dbDest);
+ }
+
+ private void testRandomness(){
+ byte[] foo = new byte[20];
+ int i = 0;
+ for( byte b : foo ){
+ i += b;
+ }
+ affirm( i==0 );
+ sqlite3_randomness(foo);
+ for( byte b : foo ){
+ if(b!=0) ++i;
+ }
+ affirm( i!=0, "There's a very slight chance that 0 is actually correct." );
+ }
+
+ private void testBlobOpen(){
+ final sqlite3 db = createNewDb();
+
+ execSql(db, "CREATE TABLE T(a BLOB);"
+ +"INSERT INTO t(rowid,a) VALUES(1, 'def'),(2, 'XYZ');"
+ );
+ final OutputPointer.sqlite3_blob pOut = new OutputPointer.sqlite3_blob();
+ int rc = sqlite3_blob_open(db, "main", "t", "a",
+ sqlite3_last_insert_rowid(db), 1, pOut);
+ affirm( 0==rc );
+ sqlite3_blob b = pOut.take();
+ affirm( null!=b );
+ affirm( 0!=b.getNativePointer() );
+ affirm( 3==sqlite3_blob_bytes(b) );
+ rc = sqlite3_blob_write( b, new byte[] {100, 101, 102 /*"DEF"*/}, 0);
+ affirm( 0==rc );
+ rc = sqlite3_blob_close(b);
+ affirm( 0==rc );
+ rc = sqlite3_blob_close(b);
+ affirm( 0!=rc );
+ affirm( 0==b.getNativePointer() );
+ sqlite3_stmt stmt = prepare(db,"SELECT length(a), a FROM t ORDER BY a");
+ affirm( SQLITE_ROW == sqlite3_step(stmt) );
+ affirm( 3 == sqlite3_column_int(stmt,0) );
+ affirm( "def".equals(sqlite3_column_text16(stmt,1)) );
+ sqlite3_finalize(stmt);
+
+ b = sqlite3_blob_open(db, "main", "t", "a",
+ sqlite3_last_insert_rowid(db), 1);
+ affirm( null!=b );
+ rc = sqlite3_blob_reopen(b, 2);
+ affirm( 0==rc );
+ final byte[] tgt = new byte[3];
+ rc = sqlite3_blob_read(b, tgt, 0);
+ affirm( 0==rc );
+ affirm( 100==tgt[0] && 101==tgt[1] && 102==tgt[2], "DEF" );
+ rc = sqlite3_blob_close(b);
+ affirm( 0==rc );
+ sqlite3_close_v2(db);
+ }
+
+ private void testPrepareMulti(){
+ final sqlite3 db = createNewDb();
+ final String[] sql = {
+ "create table t(","a)",
+ "; insert into t(a) values(1),(2),(3);",
+ "select a from t;"
+ };
+ final List liStmt = new ArrayList();
+ final PrepareMultiCallback proxy = new PrepareMultiCallback.StepAll();
+ PrepareMultiCallback m = new PrepareMultiCallback() {
+ @Override public int call(sqlite3_stmt st){
+ liStmt.add(st);
+ return proxy.call(st);
+ }
+ };
+ int rc = sqlite3_prepare_multi(db, sql, m);
+ affirm( 0==rc );
+ affirm( liStmt.size() == 3 );
+ for( sqlite3_stmt st : liStmt ){
+ sqlite3_finalize(st);
+ }
+ sqlite3_close_v2(db);
+ }
+
+ /* Copy/paste/rename this to add new tests. */
+ private void _testTemplate(){
+ final sqlite3 db = createNewDb();
+ sqlite3_stmt stmt = prepare(db,"SELECT 1");
+ sqlite3_finalize(stmt);
+ sqlite3_close_v2(db);
+ }
+
+
+ @ManualTest /* we really only want to run this test manually */
+ private void testSleep(){
+ out("Sleeping briefly... ");
+ sqlite3_sleep(600);
+ outln("Woke up.");
+ }
+
+ private void nap() throws InterruptedException {
+ if( takeNaps ){
+ Thread.sleep(java.util.concurrent.ThreadLocalRandom.current().nextInt(3, 17), 0);
+ }
+ }
+
+ @ManualTest /* because we only want to run this test on demand */
+ private void testFail(){
+ affirm( false, "Intentional failure." );
+ }
+
+ private void runTests(boolean fromThread) throws Exception {
+ if(false) showCompileOption();
+ List mlist = testMethods;
+ affirm( null!=mlist );
+ if( shuffle ){
+ mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) );
+ java.util.Collections.shuffle(mlist);
+ }
+ if( listRunTests ){
+ synchronized(this.getClass()){
+ if( !fromThread ){
+ out("Initial test"," list: ");
+ for(java.lang.reflect.Method m : testMethods){
+ out(m.getName()+" ");
+ }
+ outln();
+ outln("(That list excludes some which are hard-coded to run.)");
+ }
+ out("Running"," tests: ");
+ for(java.lang.reflect.Method m : mlist){
+ out(m.getName()+" ");
+ }
+ outln();
+ }
+ }
+ for(java.lang.reflect.Method m : mlist){
+ nap();
+ try{
+ m.invoke(this);
+ }catch(java.lang.reflect.InvocationTargetException e){
+ outln("FAILURE: ",m.getName(),"(): ", e.getCause());
+ throw e;
+ }
+ }
+ synchronized( this.getClass() ){
+ ++nTestRuns;
+ }
+ }
+
+ public void run() {
+ try {
+ runTests(0!=this.tId);
+ }catch(Exception e){
+ synchronized( listErrors ){
+ listErrors.add(e);
+ }
+ }finally{
+ affirm( sqlite3_java_uncache_thread() );
+ affirm( !sqlite3_java_uncache_thread() );
+ }
+ }
+
+ /**
+ Runs the basic sqlite3 JNI binding sanity-check suite.
+
+ CLI flags:
+
+ -q|-quiet: disables most test output.
+
+ -t|-thread N: runs the tests in N threads
+ concurrently. Default=1.
+
+ -r|-repeat N: repeats the tests in a loop N times, each one
+ consisting of the -thread value's threads.
+
+ -shuffle: randomizes the order of most of the test functions.
+
+ -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. 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.
+
+ -v: emit some developer-mode info at the end.
+ */
+ public static void main(String[] args) throws Exception {
+ Integer nThread = 1;
+ boolean doSomethingForDev = false;
+ Integer nRepeat = 1;
+ boolean forceFail = false;
+ boolean sqlLog = false;
+ boolean configLog = false;
+ boolean squelchTestOutput = false;
+ for( int i = 0; i < args.length; ){
+ String arg = args[i++];
+ if(arg.startsWith("-")){
+ arg = arg.replaceFirst("-+","");
+ if(arg.equals("v")){
+ doSomethingForDev = true;
+ //listBoundMethods();
+ }else if(arg.equals("t") || arg.equals("thread")){
+ nThread = Integer.parseInt(args[i++]);
+ }else if(arg.equals("r") || arg.equals("repeat")){
+ nRepeat = Integer.parseInt(args[i++]);
+ }else if(arg.equals("shuffle")){
+ shuffle = true;
+ }else if(arg.equals("list-tests")){
+ listRunTests = true;
+ }else if(arg.equals("fail")){
+ forceFail = true;
+ }else if(arg.equals("sqllog")){
+ sqlLog = true;
+ }else if(arg.equals("configlog")){
+ configLog = true;
+ }else if(arg.equals("naps")){
+ takeNaps = true;
+ }else if(arg.equals("q") || arg.equals("quiet")){
+ squelchTestOutput = true;
+ }else{
+ throw new IllegalArgumentException("Unhandled flag:"+arg);
+ }
+ }
+ }
+
+ if( sqlLog ){
+ if( sqlite3_compileoption_used("ENABLE_SQLLOG") ){
+ final ConfigSqllogCallback log = new ConfigSqllogCallback() {
+ @Override public void call(sqlite3 db, String msg, int op){
+ switch(op){
+ case 0: outln("Opening db: ",db); break;
+ case 1: outln("SQL ",db,": ",msg); break;
+ case 2: outln("Closing db: ",db); break;
+ }
+ }
+ };
+ int rc = sqlite3_config( log );
+ affirm( 0==rc );
+ rc = sqlite3_config( (ConfigSqllogCallback)null );
+ affirm( 0==rc );
+ rc = sqlite3_config( log );
+ affirm( 0==rc );
+ }else{
+ outln("WARNING: -sqllog is not active because library was built ",
+ "without SQLITE_ENABLE_SQLLOG.");
+ }
+ }
+ if( configLog ){
+ final ConfigLogCallback log = new ConfigLogCallback() {
+ @Override public void call(int code, String msg){
+ outln("ConfigLogCallback: ",ResultCode.getEntryForInt(code),": ", msg);
+ };
+ };
+ int rc = sqlite3_config( log );
+ affirm( 0==rc );
+ rc = sqlite3_config( (ConfigLogCallback)null );
+ affirm( 0==rc );
+ rc = sqlite3_config( log );
+ affirm( 0==rc );
+ }
+
+ quietMode = squelchTestOutput;
+ outln("If you just saw warning messages regarding CallStaticObjectMethod, ",
+ "you are very likely seeing the side effects of a known openjdk8 ",
+ "bug. It is unsightly but does not affect the library.");
+
+ {
+ // Build list of tests to run from the methods named test*().
+ testMethods = new ArrayList<>();
+ int nSkipped = 0;
+ for(final java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){
+ final String name = m.getName();
+ if( name.equals("testFail") ){
+ if( forceFail ){
+ testMethods.add(m);
+ }
+ }else if( !m.isAnnotationPresent( ManualTest.class ) ){
+ if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){
+ 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();
+ int nLoop = 0;
+ switch( sqlite3_threadsafe() ){ /* Sanity checking */
+ case 0:
+ affirm( SQLITE_ERROR==sqlite3_config( SQLITE_CONFIG_SINGLETHREAD ),
+ "Could not switch to single-thread mode." );
+ affirm( SQLITE_ERROR==sqlite3_config( SQLITE_CONFIG_MULTITHREAD ),
+ "Could switch to multithread mode." );
+ affirm( SQLITE_ERROR==sqlite3_config( SQLITE_CONFIG_SERIALIZED ),
+ "Could not switch to serialized threading mode." );
+ outln("This is a single-threaded build. Not using threads.");
+ nThread = 1;
+ break;
+ case 1:
+ case 2:
+ affirm( 0==sqlite3_config( SQLITE_CONFIG_SINGLETHREAD ),
+ "Could not switch to single-thread mode." );
+ affirm( 0==sqlite3_config( SQLITE_CONFIG_MULTITHREAD ),
+ "Could not switch to multithread mode." );
+ affirm( 0==sqlite3_config( SQLITE_CONFIG_SERIALIZED ),
+ "Could not switch to serialized threading mode." );
+ break;
+ default:
+ affirm( false, "Unhandled SQLITE_THREADSAFE value." );
+ }
+ outln("libversion_number: ",
+ sqlite3_libversion_number(),"\n",
+ sqlite3_libversion(),"\n",SQLITE_SOURCE_ID,"\n",
+ "SQLITE_THREADSAFE=",sqlite3_threadsafe());
+ final boolean showLoopCount = (nRepeat>1 && nThread>1);
+ if( showLoopCount ){
+ outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each.");
+ }
+ if( takeNaps ) outln("Napping between tests is enabled.");
+ for( int n = 0; n < nRepeat; ++n ){
+ ++nLoop;
+ if( showLoopCount ) out((1==nLoop ? "" : " ")+nLoop);
+ if( nThread<=1 ){
+ new Tester1(0).runTests(false);
+ continue;
+ }
+ Tester1.mtMode = true;
+ final ExecutorService ex = Executors.newFixedThreadPool( nThread );
+ for( int i = 0; i < nThread; ++i ){
+ ex.submit( new Tester1(i), i );
+ }
+ ex.shutdown();
+ try{
+ ex.awaitTermination(nThread*200, java.util.concurrent.TimeUnit.MILLISECONDS);
+ ex.shutdownNow();
+ }catch (InterruptedException ie){
+ ex.shutdownNow();
+ Thread.currentThread().interrupt();
+ }
+ if( !listErrors.isEmpty() ){
+ quietMode = false;
+ outln("TEST ERRORS:");
+ Exception err = null;
+ for( Exception e : listErrors ){
+ e.printStackTrace();
+ if( null==err ) err = e;
+ }
+ if( null!=err ) throw err;
+ }
+ }
+ if( showLoopCount ) outln();
+ quietMode = false;
+
+ final long timeEnd = System.currentTimeMillis();
+ outln("Tests done. Metrics across ",nTestRuns," total iteration(s):");
+ outln("\tAssertions checked: ",affirmCount);
+ outln("\tDatabases opened: ",metrics.dbOpen);
+ if( doSomethingForDev ){
+ sqlite3_jni_internal_details();
+ }
+ affirm( 0==sqlite3_release_memory(1) );
+ sqlite3_shutdown();
+ int nMethods = 0;
+ int nNatives = 0;
+ final java.lang.reflect.Method[] declaredMethods =
+ CApi.class.getDeclaredMethods();
+ for(java.lang.reflect.Method m : declaredMethods){
+ final int mod = m.getModifiers();
+ if( 0!=(mod & java.lang.reflect.Modifier.STATIC) ){
+ final String name = m.getName();
+ if(name.startsWith("sqlite3_")){
+ ++nMethods;
+ if( 0!=(mod & java.lang.reflect.Modifier.NATIVE) ){
+ ++nNatives;
+ }
+ }
+ }
+ }
+ outln("\tCApi.sqlite3_*() methods: "+
+ nMethods+" total, with "+
+ nNatives+" native, "+
+ (nMethods - nNatives)+" Java"
+ );
+ outln("\tTotal test time = "
+ +(timeEnd - timeStart)+"ms");
+ }
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,50 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import org.sqlite.jni.annotation.Nullable;
+
+/**
+ Callback for use with {@link CApi#sqlite3_trace_v2}.
+*/
+public interface TraceV2Callback extends CallbackProxy {
+ /**
+ Called by sqlite3 for various tracing operations, as per
+ sqlite3_trace_v2(). Note that this interface elides the 2nd
+ argument to the native trace callback, as that role is better
+ filled by instance-local state.
+
+ These callbacks may throw, in which case their exceptions are
+ converted to C-level error information.
+
+
The 2nd argument to this function, if non-null, will be a an
+ sqlite3 or sqlite3_stmt object, depending on the first argument
+ (see below).
+
+
The final argument to this function is the "X" argument
+ documented for sqlite3_trace() and sqlite3_trace_v2(). Its type
+ depends on value of the first argument:
+
+
- SQLITE_TRACE_STMT: pNative is a sqlite3_stmt. pX is a String
+ containing the prepared SQL.
+
+
- SQLITE_TRACE_PROFILE: pNative is a sqlite3_stmt. pX is a Long
+ holding an approximate number of nanoseconds the statement took
+ to run.
+
+
- SQLITE_TRACE_ROW: pNative is a sqlite3_stmt. pX is null.
+
+
- SQLITE_TRACE_CLOSE: pNative is a sqlite3. pX is null.
+ */
+ int call(int traceFlag, Object pNative, @Nullable Object pX);
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,25 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_update_hook}.
+*/
+public interface UpdateHookCallback extends CallbackProxy {
+ /**
+ Must function as described for the C-level sqlite3_update_hook()
+ callback.
+ */
+ void call(int opId, String dbName, String tableName, long rowId);
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,25 @@
+/*
+** 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 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.
+*/
+public class ValueHolder {
+ public T value;
+ public ValueHolder(){}
+ public ValueHolder(T v){value = v;}
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/WindowFunction.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/WindowFunction.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/WindowFunction.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/WindowFunction.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,39 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+
+/**
+ A SQLFunction implementation for window functions. Note that
+ WindowFunction inherits from {@link AggregateFunction} and each
+ instance is required to implement the inherited abstract methods
+ from that class. See {@link AggregateFunction} for information on
+ managing the UDF's invocation-specific state.
+*/
+public abstract class WindowFunction extends AggregateFunction {
+
+ /**
+ As for the xInverse() argument of the C API's
+ sqlite3_create_window_function(). If this function throws, the
+ exception is not propagated and a warning might be emitted
+ to a debugging channel.
+ */
+ public abstract void xInverse(sqlite3_context cx, sqlite3_value[] args);
+
+ /**
+ As for the xValue() argument of the C API's sqlite3_create_window_function().
+ See xInverse() for the fate of any exceptions this throws.
+ */
+ public abstract void xValue(sqlite3_context cx);
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,37 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file declares JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for a hook called by SQLite when certain client-provided
+ state are destroyed. It gets its name from the pervasive use of
+ the symbol name xDestroy() for this purpose in the C API
+ documentation.
+*/
+public interface XDestroyCallback {
+ /**
+ Must perform any cleanup required by this object. Must not
+ throw. Must not call back into the sqlite3 API, else it might
+ invoke a deadlock.
+
+ WARNING: as a rule, it is never safe to register individual
+ instances with this interface multiple times in the
+ library. e.g., do not register the same CollationCallback with
+ multiple arities or names using sqlite3_create_collation(). If
+ this rule is violated, the library will eventually try to free
+ each individual reference, leading to memory corruption or a
+ crash via duplicate free().
+ */
+ public void xDestroy();
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/package-info.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/package-info.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/package-info.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/package-info.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,89 @@
+/**
+ This package houses a JNI binding to the SQLite3 C API.
+
+ The primary interfaces are in {@link
+ org.sqlite.jni.capi.CApi}.
+
+ API Goals and Requirements
+
+
+
+ - A 1-to-1(-ish) mapping of the C API to Java via JNI, insofar
+ as cross-language semantics allow for. A closely-related goal is
+ that the C
+ documentation should be usable as-is, insofar as possible,
+ for most of the JNI binding. As a rule, undocumented symbols in
+ the Java interface behave as documented for their C API
+ counterpart. Only semantic differences and Java-specific features
+ are documented here.
+
+ - Support Java as far back as version 8 (2014).
+
+ - Environment-independent. Should work everywhere both Java and
+ SQLite3 do.
+
+ - No 3rd-party dependencies beyond the JDK. That includes no
+ build-level dependencies for specific IDEs and toolchains. We
+ welcome the addition of build files for arbitrary environments
+ insofar as they neither interfere with each other nor become a
+ maintenance burden for the sqlite developers.
+
+
+
+ Non-Goals
+
+
+
+ - Creation of high-level OO wrapper APIs. Clients are free to
+ create them off of the C-style API.
+
+ - 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. 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.
+
+
+
+ State of this API
+
+ As of version 3.43, this software is in "tech preview" form. We
+ tentatively plan to stamp it as stable with the 3.44 release.
+
+ Threading Considerations
+
+ This API is, if built with SQLITE_THREADSAFE set to 1 or 2,
+ thread-safe, insofar as the C API guarantees, with some addenda:
+
+
+
+ - It is not legal to use Java-facing SQLite3 resource handles
+ (sqlite3, sqlite3_stmt, etc) from multiple threads concurrently,
+ nor to use any database-specific resources concurrently in a
+ thread separate from the one the database is currently in use
+ in. i.e. do not use a sqlite3_stmt in thread #2 when thread #1 is
+ using the database which prepared that handle.
+
+
Violating this will eventually corrupt the JNI-level bindings
+ between Java's and C's view of the database. This is a limitation
+ of the JNI bindings, not the lower-level library.
+
+
+ - It is legal to use a given handle, and database-specific
+ resources, across threads, so long as no two threads pass
+ resources owned by the same database into the library
+ concurrently.
+
+
+
+
+ Any number of threads may, of course, create and use any number
+ of database handles they wish. Care only needs to be taken when
+ those handles or their associated resources cross threads, or...
+
+ When built with SQLITE_THREADSAFE=0 then no threading guarantees
+ are provided and multi-threaded use of the library will provoke
+ undefined behavior.
+
+*/
+package org.sqlite.jni.capi;
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,43 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A wrapper for communicating C-level (sqlite3*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java
+ and C via JNI.
+*/
+public final class sqlite3 extends NativePointerHolder
+ implements AutoCloseable {
+
+ // Only invoked from JNI
+ private sqlite3(){}
+
+ public String toString(){
+ final long ptr = getNativePointer();
+ if( 0==ptr ){
+ return sqlite3.class.getSimpleName()+"@null";
+ }
+ final String fn = CApi.sqlite3_db_filename(this, "main");
+ return sqlite3.class.getSimpleName()
+ +"@"+String.format("0x%08x",ptr)
+ +"["+((null == fn) ? "" : fn)+"]"
+ ;
+ }
+
+ @Override public void close(){
+ CApi.sqlite3_close_v2(this.clearNativePointer());
+ }
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,31 @@
+/*
+** 2023-09-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.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A wrapper for passing C-level (sqlite3_backup*) instances around in
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+*/
+public final class sqlite3_backup extends NativePointerHolder
+ implements AutoCloseable {
+ // Only invoked from JNI.
+ private sqlite3_backup(){}
+
+ @Override public void close(){
+ CApi.sqlite3_backup_finish(this);
+ }
+
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,31 @@
+/*
+** 2023-09-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.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A wrapper for passing C-level (sqlite3_blob*) instances around in
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+*/
+public final class sqlite3_blob extends NativePointerHolder
+ implements AutoCloseable {
+ // Only invoked from JNI.
+ private sqlite3_blob(){}
+
+ @Override public void close(){
+ CApi.sqlite3_blob_close(this.clearNativePointer());
+ }
+
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,79 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ sqlite3_context instances are used in conjunction with user-defined
+ SQL functions (a.k.a. UDFs).
+*/
+public final class sqlite3_context extends NativePointerHolder {
+ private Long aggregateContext = null;
+
+ /**
+ getAggregateContext() corresponds to C's
+ sqlite3_aggregate_context(), with a slightly different interface
+ to account for cross-language differences. It serves the same
+ purposes in a slightly different way: it provides a key which is
+ stable across invocations of a UDF's callbacks, such that all
+ calls into those callbacks can determine which "set" of those
+ calls they belong to.
+
+ Note that use of this method is not a requirement for proper use
+ of this class. sqlite3_aggregate_context() can also be used.
+
+
If the argument is true and the aggregate context has not yet
+ been set up, it will be initialized and fetched on demand, else it
+ won't. The intent is that xStep(), xValue(), and xInverse()
+ methods pass true and xFinal() methods pass false.
+
+
This function treats numeric 0 as null, always returning null instead
+ of 0.
+
+
If this object is being used in the context of an aggregate or
+ window UDF, this function returns a non-0 value which is distinct
+ for each set of UDF callbacks from a single invocation of the
+ UDF, otherwise it returns 0. The returned value is only only
+ valid within the context of execution of a single SQL statement,
+ and must not be re-used by future invocations of the UDF in
+ different SQL statements.
+
+
Consider this SQL, where MYFUNC is a user-defined aggregate function:
+
+
{@code
+ SELECT MYFUNC(A), MYFUNC(B) FROM T;
+ }
+
+ The xStep() and xFinal() methods of the callback need to be able
+ to differentiate between those two invocations in order to
+ perform their work properly. The value returned by
+ getAggregateContext() will be distinct for each of those
+ invocations of MYFUNC() and is intended to be used as a lookup
+ key for mapping callback invocations to whatever client-defined
+ state is needed by the UDF.
+
+
There is one case where this will return null in the context
+ of an aggregate or window function: if the result set has no
+ rows, the UDF's xFinal() will be called without any other x...()
+ members having been called. In that one case, no aggregate
+ context key will have been generated. xFinal() implementations
+ need to be prepared to accept that condition as legal.
+ */
+ public synchronized Long getAggregateContext(boolean initIfNeeded){
+ if( aggregateContext==null ){
+ aggregateContext = CApi.sqlite3_aggregate_context(this, initIfNeeded);
+ if( !initIfNeeded && null==aggregateContext ) aggregateContext = 0L;
+ }
+ return (null==aggregateContext || 0!=aggregateContext) ? aggregateContext : null;
+ }
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,30 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A wrapper for communicating C-level (sqlite3_stmt*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+*/
+public final class sqlite3_stmt extends NativePointerHolder
+ implements AutoCloseable {
+ // Only invoked from JNI.
+ private sqlite3_stmt(){}
+
+ @Override public void close(){
+ CApi.sqlite3_finalize(this.clearNativePointer());
+ }
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,19 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+public final class sqlite3_value extends NativePointerHolder {
+ //! Invoked only from JNI.
+ private sqlite3_value(){}
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,32 @@
+/*
+** 2023-08-05
+**
+** 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 JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+
+/**
+ INCOMPLETE AND COMPLETELY UNTESTED.
+
+ A utility object for holding FTS5-specific types and constants
+ which are used by multiple FTS5 classes.
+*/
+public final class Fts5 {
+ /* Not used */
+ private Fts5(){}
+
+
+ public static final int FTS5_TOKENIZE_QUERY = 0x0001;
+ public static final int FTS5_TOKENIZE_PREFIX = 0x0002;
+ public static final int FTS5_TOKENIZE_DOCUMENT = 0x0004;
+ public static final int FTS5_TOKENIZE_AUX = 0x0008;
+ public static final int FTS5_TOKEN_COLOCATED = 0x0001;
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,24 @@
+/*
+** 2023-08-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.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.*;
+
+/**
+ A wrapper for communicating C-level (Fts5Context*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+*/
+public final class Fts5Context extends NativePointerHolder {
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,97 @@
+/*
+** 2023-08-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.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import java.nio.charset.StandardCharsets;
+import org.sqlite.jni.capi.*;
+import org.sqlite.jni.annotation.*;
+
+/**
+*/
+public final class Fts5ExtensionApi extends NativePointerHolder {
+ //! Only called from JNI
+ private Fts5ExtensionApi(){}
+ private final int iVersion = 2;
+
+ /* Callback type for used by xQueryPhrase(). */
+ public static interface XQueryPhraseCallback {
+ int call(Fts5ExtensionApi fapi, Fts5Context cx);
+ }
+
+ /**
+ Returns the singleton instance of this class.
+ */
+ public static native Fts5ExtensionApi getInstance();
+
+ public native int xColumnCount(@NotNull Fts5Context fcx);
+
+ public native int xColumnSize(@NotNull Fts5Context cx, int iCol,
+ @NotNull OutputPointer.Int32 pnToken);
+
+ public native int xColumnText(@NotNull Fts5Context cx, int iCol,
+ @NotNull OutputPointer.String txt);
+
+ public native int xColumnTotalSize(@NotNull Fts5Context fcx, int iCol,
+ @NotNull OutputPointer.Int64 pnToken);
+
+ public native Object xGetAuxdata(@NotNull Fts5Context cx, boolean clearIt);
+
+ public native int xInst(@NotNull Fts5Context cx, int iIdx,
+ @NotNull OutputPointer.Int32 piPhrase,
+ @NotNull OutputPointer.Int32 piCol,
+ @NotNull OutputPointer.Int32 piOff);
+
+ public native int xInstCount(@NotNull Fts5Context fcx,
+ @NotNull OutputPointer.Int32 pnInst);
+
+ public native int xPhraseCount(@NotNull Fts5Context fcx);
+
+ public native int xPhraseFirst(@NotNull Fts5Context cx, int iPhrase,
+ @NotNull Fts5PhraseIter iter,
+ @NotNull OutputPointer.Int32 iCol,
+ @NotNull OutputPointer.Int32 iOff);
+
+ public native int xPhraseFirstColumn(@NotNull Fts5Context cx, int iPhrase,
+ @NotNull Fts5PhraseIter iter,
+ @NotNull OutputPointer.Int32 iCol);
+ public native void xPhraseNext(@NotNull Fts5Context cx,
+ @NotNull Fts5PhraseIter iter,
+ @NotNull OutputPointer.Int32 iCol,
+ @NotNull OutputPointer.Int32 iOff);
+ public native void xPhraseNextColumn(@NotNull Fts5Context cx,
+ @NotNull Fts5PhraseIter iter,
+ @NotNull OutputPointer.Int32 iCol);
+ public native int xPhraseSize(@NotNull Fts5Context fcx, int iPhrase);
+
+ public native int xQueryPhrase(@NotNull Fts5Context cx, int iPhrase,
+ @NotNull XQueryPhraseCallback callback);
+ public native int xRowCount(@NotNull Fts5Context fcx,
+ @NotNull OutputPointer.Int64 nRow);
+
+ public native long xRowid(@NotNull Fts5Context cx);
+ /* Note that the JNI binding lacks the C version's xDelete()
+ callback argument. Instead, if pAux has an xDestroy() method, it
+ is called if the FTS5 API finalizes the aux state (including if
+ allocation of storage for the auxdata fails). Any reference to
+ pAux held by the JNI layer will be relinquished regardless of
+ whether pAux has an xDestroy() method. */
+
+ public native int xSetAuxdata(@NotNull Fts5Context cx, @Nullable Object pAux);
+
+ public native int xTokenize(@NotNull Fts5Context cx, @NotNull byte[] pText,
+ @NotNull XTokenizeCallback callback);
+
+ public native Object xUserData(Fts5Context cx);
+ //^^^ returns the pointer passed as the 3rd arg to the C-level
+ // fts5_api::xCreateFunction().
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,25 @@
+/*
+** 2023-08-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.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.NativePointerHolder;
+
+/**
+ A wrapper for C-level Fts5PhraseIter. They are only modified and
+ inspected by native-level code.
+*/
+public final class Fts5PhraseIter extends NativePointerHolder {
+ //! Updated and used only by native code.
+ private long a;
+ private long b;
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,31 @@
+/*
+** 2023-08-05x
+**
+** 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 JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.NativePointerHolder;
+
+/**
+ INCOMPLETE AND COMPLETELY UNTESTED.
+
+ A wrapper for communicating C-level (Fts5Tokenizer*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+
+ At the C level, the Fts5Tokenizer type is essentially a void
+ pointer used specifically for tokenizers.
+*/
+public final class Fts5Tokenizer extends NativePointerHolder {
+ //! Only called from JNI.
+ private Fts5Tokenizer(){}
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,832 @@
+/*
+** 2023-08-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.
+**
+*************************************************************************
+** This file contains a set of tests for the sqlite3 JNI bindings.
+*/
+package org.sqlite.jni.fts5;
+import static org.sqlite.jni.capi.CApi.*;
+import static org.sqlite.jni.capi.Tester1.*;
+import org.sqlite.jni.capi.*;
+import org.sqlite.jni.fts5.*;
+
+import java.util.*;
+
+public class TesterFts5 {
+
+ private static void test1(){
+ final Fts5ExtensionApi fea = Fts5ExtensionApi.getInstance();
+ affirm( null != fea );
+ affirm( fea.getNativePointer() != 0 );
+ affirm( fea == Fts5ExtensionApi.getInstance() )/*singleton*/;
+
+ sqlite3 db = createNewDb();
+ fts5_api fApi = fts5_api.getInstanceForDb(db);
+ affirm( fApi != null );
+ affirm( fApi == fts5_api.getInstanceForDb(db) /* singleton per db */ );
+
+ execSql(db, new String[] {
+ "CREATE VIRTUAL TABLE ft USING fts5(a, b);",
+ "INSERT INTO ft(rowid, a, b) VALUES(1, 'X Y', 'Y Z');",
+ "INSERT INTO ft(rowid, a, b) VALUES(2, 'A Z', 'Y Y');"
+ });
+
+ final String pUserData = "This is pUserData";
+ final int outputs[] = {0, 0};
+ final fts5_extension_function func = new fts5_extension_function(){
+ @Override public void call(Fts5ExtensionApi ext, Fts5Context fCx,
+ sqlite3_context pCx, sqlite3_value argv[]){
+ final int nCols = ext.xColumnCount(fCx);
+ affirm( 2 == nCols );
+ affirm( nCols == argv.length );
+ affirm( ext.xUserData(fCx) == pUserData );
+ final OutputPointer.String op = new OutputPointer.String();
+ final OutputPointer.Int32 colsz = new OutputPointer.Int32();
+ final OutputPointer.Int64 colTotalSz = new OutputPointer.Int64();
+ for(int i = 0; i < nCols; ++i ){
+ int rc = ext.xColumnText(fCx, i, op);
+ affirm( 0 == rc );
+ final String val = op.value;
+ affirm( val.equals(sqlite3_value_text16(argv[i])) );
+ rc = ext.xColumnSize(fCx, i, colsz);
+ affirm( 0==rc );
+ affirm( 3==sqlite3_value_bytes(argv[i]) );
+ rc = ext.xColumnTotalSize(fCx, i, colTotalSz);
+ affirm( 0==rc );
+ }
+ ++outputs[0];
+ }
+ public void xDestroy(){
+ outputs[1] = 1;
+ }
+ };
+
+ int rc = fApi.xCreateFunction("myaux", pUserData, func);
+ affirm( 0==rc );
+
+ affirm( 0==outputs[0] );
+ execSql(db, "select myaux(ft,a,b) from ft;");
+ affirm( 2==outputs[0] );
+ affirm( 0==outputs[1] );
+ sqlite3_close_v2(db);
+ affirm( 1==outputs[1] );
+ }
+
+ /*
+ ** Argument sql is a string containing one or more SQL statements
+ ** separated by ";" characters. This function executes each of these
+ ** statements against the database passed as the first argument. If
+ ** no error occurs, the results of the SQL script are returned as
+ ** an array of strings. If an error does occur, a RuntimeException is
+ ** thrown.
+ */
+ private static String[] sqlite3_exec(sqlite3 db, String sql) {
+ List aOut = new ArrayList();
+
+ /* Iterate through the list of SQL statements. For each, step through
+ ** it and add any results to the aOut[] array. */
+ int rc = sqlite3_prepare_multi(db, sql, new PrepareMultiCallback() {
+ @Override public int call(sqlite3_stmt pStmt){
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ int ii;
+ for(ii=0; ii, );
+ */
+ class fts5_aux implements fts5_extension_function {
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length>1 ){
+ throw new RuntimeException("fts5_aux: wrong number of args");
+ }
+
+ boolean bClear = (argv.length==1);
+ Object obj = ext.xGetAuxdata(fCx, bClear);
+ if( obj instanceof String ){
+ sqlite3_result_text16(pCx, (String)obj);
+ }
+
+ if( argv.length==1 ){
+ String val = sqlite3_value_text16(argv[0]);
+ if( !val.equals("") ){
+ ext.xSetAuxdata(fCx, val);
+ }
+ }
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_inst();
+ **
+ ** This is used to test the xInstCount() and xInst() APIs. It returns a
+ ** text value containing a Tcl list with xInstCount() elements. Each
+ ** element is itself a list of 3 integers - the phrase number, column
+ ** number and token offset returned by each call to xInst().
+ */
+ fts5_extension_function fts5_inst = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=0 ){
+ throw new RuntimeException("fts5_inst: wrong number of args");
+ }
+
+ OutputPointer.Int32 pnInst = new OutputPointer.Int32();
+ OutputPointer.Int32 piPhrase = new OutputPointer.Int32();
+ OutputPointer.Int32 piCol = new OutputPointer.Int32();
+ OutputPointer.Int32 piOff = new OutputPointer.Int32();
+ String ret = new String();
+
+ int rc = ext.xInstCount(fCx, pnInst);
+ int nInst = pnInst.get();
+ int ii;
+
+ for(ii=0; rc==SQLITE_OK && ii0 ) ret += " ";
+ ret += "{"+piPhrase.get()+" "+piCol.get()+" "+piOff.get()+"}";
+ }
+
+ sqlite3_result_text(pCx, ret);
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_pinst();
+ **
+ ** Like SQL function fts5_inst(), except using the following
+ **
+ ** xPhraseCount
+ ** xPhraseFirst
+ ** xPhraseNext
+ */
+ fts5_extension_function fts5_pinst = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=0 ){
+ throw new RuntimeException("fts5_pinst: wrong number of args");
+ }
+
+ OutputPointer.Int32 piCol = new OutputPointer.Int32();
+ OutputPointer.Int32 piOff = new OutputPointer.Int32();
+ String ret = new String();
+ int rc = SQLITE_OK;
+
+ int nPhrase = ext.xPhraseCount(fCx);
+ int ii;
+
+ for(ii=0; rc==SQLITE_OK && ii=0;
+ ext.xPhraseNext(fCx, pIter, piCol, piOff)
+ ){
+ if( !ret.equals("") ) ret += " ";
+ ret += "{"+ii+" "+piCol.get()+" "+piOff.get()+"}";
+ }
+ }
+
+ if( rc!=SQLITE_OK ){
+ throw new RuntimeException("fts5_pinst: rc=" + rc);
+ }else{
+ sqlite3_result_text(pCx, ret);
+ }
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_pcolinst();
+ **
+ ** Like SQL function fts5_pinst(), except using the following
+ **
+ ** xPhraseFirstColumn
+ ** xPhraseNextColumn
+ */
+ fts5_extension_function fts5_pcolinst = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=0 ){
+ throw new RuntimeException("fts5_pcolinst: wrong number of args");
+ }
+
+ OutputPointer.Int32 piCol = new OutputPointer.Int32();
+ String ret = new String();
+ int rc = SQLITE_OK;
+
+ int nPhrase = ext.xPhraseCount(fCx);
+ int ii;
+
+ for(ii=0; rc==SQLITE_OK && ii=0;
+ ext.xPhraseNextColumn(fCx, pIter, piCol)
+ ){
+ if( !ret.equals("") ) ret += " ";
+ ret += "{"+ii+" "+piCol.get()+"}";
+ }
+ }
+
+ if( rc!=SQLITE_OK ){
+ throw new RuntimeException("fts5_pcolinst: rc=" + rc);
+ }else{
+ sqlite3_result_text(pCx, ret);
+ }
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_rowcount();
+ */
+ fts5_extension_function fts5_rowcount = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=0 ){
+ throw new RuntimeException("fts5_rowcount: wrong number of args");
+ }
+ OutputPointer.Int64 pnRow = new OutputPointer.Int64();
+
+ int rc = ext.xRowCount(fCx, pnRow);
+ if( rc==SQLITE_OK ){
+ sqlite3_result_int64(pCx, pnRow.get());
+ }else{
+ throw new RuntimeException("fts5_rowcount: rc=" + rc);
+ }
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_phrasesize();
+ */
+ fts5_extension_function fts5_phrasesize = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=1 ){
+ throw new RuntimeException("fts5_phrasesize: wrong number of args");
+ }
+ int iPhrase = sqlite3_value_int(argv[0]);
+
+ int sz = ext.xPhraseSize(fCx, iPhrase);
+ sqlite3_result_int(pCx, sz);
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_phrasehits(, );
+ **
+ ** Use the xQueryPhrase() API to determine how many hits, in total,
+ ** there are for phrase in the database.
+ */
+ fts5_extension_function fts5_phrasehits = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=1 ){
+ throw new RuntimeException("fts5_phrasesize: wrong number of args");
+ }
+ int iPhrase = sqlite3_value_int(argv[0]);
+ int rc = SQLITE_OK;
+
+ class MyCallback implements Fts5ExtensionApi.XQueryPhraseCallback {
+ public int nRet = 0;
+ public int getRet() { return nRet; }
+
+ @Override
+ public int call(Fts5ExtensionApi fapi, Fts5Context cx){
+ OutputPointer.Int32 pnInst = new OutputPointer.Int32();
+ int rc = fapi.xInstCount(cx, pnInst);
+ nRet += pnInst.get();
+ return rc;
+ }
+ };
+
+ MyCallback xCall = new MyCallback();
+ rc = ext.xQueryPhrase(fCx, iPhrase, xCall);
+ if( rc!=SQLITE_OK ){
+ throw new RuntimeException("fts5_phrasehits: rc=" + rc);
+ }
+ sqlite3_result_int(pCx, xCall.getRet());
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_tokenize(, )
+ */
+ fts5_extension_function fts5_tokenize = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=1 ){
+ throw new RuntimeException("fts5_tokenize: wrong number of args");
+ }
+ byte[] utf8 = sqlite3_value_text(argv[0]);
+ int rc = SQLITE_OK;
+
+ class MyCallback implements XTokenizeCallback {
+ private List myList = new ArrayList();
+
+ public String getval() {
+ return String.join("+", myList);
+ }
+
+ @Override
+ public int call(int tFlags, byte[] txt, int iStart, int iEnd){
+ try {
+ String str = new String(txt, "UTF-8");
+ myList.add(str);
+ } catch (Exception e) {
+ }
+ return SQLITE_OK;
+ }
+ };
+
+ MyCallback xCall = new MyCallback();
+ ext.xTokenize(fCx, utf8, xCall);
+ sqlite3_result_text16(pCx, xCall.getval());
+
+ if( rc!=SQLITE_OK ){
+ throw new RuntimeException("fts5_tokenize: rc=" + rc);
+ }
+ }
+ public void xDestroy(){ }
+ };
+
+ fts5_api api = fts5_api.getInstanceForDb(db);
+ api.xCreateFunction("fts5_rowid", fts5_rowid);
+ api.xCreateFunction("fts5_columncount", fts5_columncount);
+ api.xCreateFunction("fts5_columnsize", fts5_columnsize);
+ api.xCreateFunction("fts5_columntext", fts5_columntext);
+ api.xCreateFunction("fts5_columntotalsize", fts5_columntsize);
+
+ api.xCreateFunction("fts5_aux1", new fts5_aux());
+ api.xCreateFunction("fts5_aux2", new fts5_aux());
+
+ api.xCreateFunction("fts5_inst", fts5_inst);
+ api.xCreateFunction("fts5_pinst", fts5_pinst);
+ api.xCreateFunction("fts5_pcolinst", fts5_pcolinst);
+ api.xCreateFunction("fts5_rowcount", fts5_rowcount);
+ api.xCreateFunction("fts5_phrasesize", fts5_phrasesize);
+ api.xCreateFunction("fts5_phrasehits", fts5_phrasehits);
+ api.xCreateFunction("fts5_tokenize", fts5_tokenize);
+ }
+ /*
+ ** Test of various Fts5ExtensionApi methods
+ */
+ private static void test2(){
+
+ /* Open db and populate an fts5 table */
+ sqlite3 db = createNewDb();
+ do_execsql_test(db,
+ "CREATE VIRTUAL TABLE ft USING fts5(a, b);" +
+ "INSERT INTO ft(rowid, a, b) VALUES(-9223372036854775808, 'x', 'x');" +
+ "INSERT INTO ft(rowid, a, b) VALUES(0, 'x', 'x');" +
+ "INSERT INTO ft(rowid, a, b) VALUES(1, 'x y z', 'x y z');" +
+ "INSERT INTO ft(rowid, a, b) VALUES(2, 'x y z', 'x z');" +
+ "INSERT INTO ft(rowid, a, b) VALUES(3, 'x y z', 'x y z');" +
+ "INSERT INTO ft(rowid, a, b) VALUES(9223372036854775807, 'x', 'x');"
+ );
+
+ create_test_functions(db);
+
+ /* Test that fts5_rowid() seems to work */
+ do_execsql_test(db,
+ "SELECT rowid==fts5_rowid(ft) FROM ft('x')",
+ "[1, 1, 1, 1, 1, 1]"
+ );
+
+ /* Test fts5_columncount() */
+ do_execsql_test(db,
+ "SELECT fts5_columncount(ft) FROM ft('x')",
+ "[2, 2, 2, 2, 2, 2]"
+ );
+
+ /* Test fts5_columnsize() */
+ do_execsql_test(db,
+ "SELECT fts5_columnsize(ft, 0) FROM ft('x') ORDER BY rowid",
+ "[1, 1, 3, 3, 3, 1]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_columnsize(ft, 1) FROM ft('x') ORDER BY rowid",
+ "[1, 1, 3, 2, 3, 1]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_columnsize(ft, -1) FROM ft('x') ORDER BY rowid",
+ "[2, 2, 6, 5, 6, 2]"
+ );
+
+ /* Test that xColumnSize() returns SQLITE_RANGE if the column number
+ ** is out-of range */
+ try {
+ do_execsql_test(db,
+ "SELECT fts5_columnsize(ft, 2) FROM ft('x') ORDER BY rowid"
+ );
+ } catch( RuntimeException e ){
+ affirm( e.getMessage().matches(".*column index out of range") );
+ }
+
+ /* Test fts5_columntext() */
+ do_execsql_test(db,
+ "SELECT fts5_columntext(ft, 0) FROM ft('x') ORDER BY rowid",
+ "[x, x, x y z, x y z, x y z, x]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_columntext(ft, 1) FROM ft('x') ORDER BY rowid",
+ "[x, x, x y z, x z, x y z, x]"
+ );
+ 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,
+ "SELECT fts5_columntotalsize(ft, 0) FROM ft('x') ORDER BY rowid",
+ "[12, 12, 12, 12, 12, 12]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_columntotalsize(ft, 1) FROM ft('x') ORDER BY rowid",
+ "[11, 11, 11, 11, 11, 11]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_columntotalsize(ft, -1) FROM ft('x') ORDER BY rowid",
+ "[23, 23, 23, 23, 23, 23]"
+ );
+
+ /* Test that xColumnTotalSize() returns SQLITE_RANGE if the column
+ ** number is out-of range */
+ try {
+ do_execsql_test(db,
+ "SELECT fts5_columntotalsize(ft, 2) FROM ft('x') ORDER BY rowid"
+ );
+ } catch( RuntimeException e ){
+ affirm( e.getMessage().matches(".*column index out of range") );
+ }
+
+ do_execsql_test(db,
+ "SELECT rowid, fts5_rowcount(ft) FROM ft('z')",
+ "[1, 6, 2, 6, 3, 6]"
+ );
+
+ sqlite3_close_v2(db);
+ }
+
+ /*
+ ** Test of various Fts5ExtensionApi methods
+ */
+ private static void test3(){
+
+ /* Open db and populate an fts5 table */
+ sqlite3 db = createNewDb();
+ do_execsql_test(db,
+ "CREATE VIRTUAL TABLE ft USING fts5(a, b);" +
+ "INSERT INTO ft(a, b) VALUES('the one', 1);" +
+ "INSERT INTO ft(a, b) VALUES('the two', 2);" +
+ "INSERT INTO ft(a, b) VALUES('the three', 3);" +
+ "INSERT INTO ft(a, b) VALUES('the four', '');"
+ );
+ create_test_functions(db);
+
+ /* Test fts5_aux1() + fts5_aux2() - users of xGetAuxdata and xSetAuxdata */
+ do_execsql_test(db,
+ "SELECT fts5_aux1(ft, a) FROM ft('the')",
+ "[null, the one, the two, the three]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_aux2(ft, b) FROM ft('the')",
+ "[null, 1, 2, 3]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_aux1(ft, a), fts5_aux2(ft, b) FROM ft('the')",
+ "[null, null, the one, 1, the two, 2, the three, 3]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_aux1(ft, b), fts5_aux1(ft) FROM ft('the')",
+ "[null, 1, 1, 2, 2, 3, 3, null]"
+ );
+ }
+
+ /*
+ ** Test of various Fts5ExtensionApi methods
+ */
+ private static void test4(){
+
+ /* Open db and populate an fts5 table */
+ sqlite3 db = createNewDb();
+ create_test_functions(db);
+ do_execsql_test(db,
+ "CREATE VIRTUAL TABLE ft USING fts5(a, b);" +
+ "INSERT INTO ft(a, b) VALUES('one two three', 'two three four');" +
+ "INSERT INTO ft(a, b) VALUES('two three four', 'three four five');" +
+ "INSERT INTO ft(a, b) VALUES('three four five', 'four five six');"
+ );
+
+
+ do_execsql_test(db,
+ "SELECT fts5_inst(ft) FROM ft('two')",
+ "[{0 0 1} {0 1 0}, {0 0 0}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_inst(ft) FROM ft('four')",
+ "[{0 1 2}, {0 0 2} {0 1 1}, {0 0 1} {0 1 0}]"
+ );
+
+ do_execsql_test(db,
+ "SELECT fts5_inst(ft) FROM ft('a OR b OR four')",
+ "[{2 1 2}, {2 0 2} {2 1 1}, {2 0 1} {2 1 0}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_inst(ft) FROM ft('two four')",
+ "[{0 0 1} {0 1 0} {1 1 2}, {0 0 0} {1 0 2} {1 1 1}]"
+ );
+
+ do_execsql_test(db,
+ "SELECT fts5_pinst(ft) FROM ft('two')",
+ "[{0 0 1} {0 1 0}, {0 0 0}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_pinst(ft) FROM ft('four')",
+ "[{0 1 2}, {0 0 2} {0 1 1}, {0 0 1} {0 1 0}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_pinst(ft) FROM ft('a OR b OR four')",
+ "[{2 1 2}, {2 0 2} {2 1 1}, {2 0 1} {2 1 0}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_pinst(ft) FROM ft('two four')",
+ "[{0 0 1} {0 1 0} {1 1 2}, {0 0 0} {1 0 2} {1 1 1}]"
+ );
+
+ do_execsql_test(db,
+ "SELECT fts5_pcolinst(ft) FROM ft('two')",
+ "[{0 0} {0 1}, {0 0}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_pcolinst(ft) FROM ft('four')",
+ "[{0 1}, {0 0} {0 1}, {0 0} {0 1}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_pcolinst(ft) FROM ft('a OR b OR four')",
+ "[{2 1}, {2 0} {2 1}, {2 0} {2 1}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_pcolinst(ft) FROM ft('two four')",
+ "[{0 0} {0 1} {1 1}, {0 0} {1 0} {1 1}]"
+ );
+
+ do_execsql_test(db,
+ "SELECT fts5_phrasesize(ft, 0) FROM ft('four five six') LIMIT 1;",
+ "[1]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_phrasesize(ft, 0) FROM ft('four + five + six') LIMIT 1;",
+ "[3]"
+ );
+
+
+ sqlite3_close_v2(db);
+ }
+
+ private static void test5(){
+ /* Open db and populate an fts5 table */
+ sqlite3 db = createNewDb();
+ create_test_functions(db);
+ do_execsql_test(db,
+ "CREATE VIRTUAL TABLE ft USING fts5(x, b);" +
+ "INSERT INTO ft(x) VALUES('one two three four five six seven eight');" +
+ "INSERT INTO ft(x) VALUES('one two one four one six one eight');" +
+ "INSERT INTO ft(x) VALUES('one two three four five six seven eight');"
+ );
+
+ do_execsql_test(db,
+ "SELECT fts5_phrasehits(ft, 0) FROM ft('one') LIMIT 1",
+ "[6]"
+ );
+
+ sqlite3_close_v2(db);
+ }
+
+ private static void test6(){
+ sqlite3 db = createNewDb();
+ create_test_functions(db);
+ do_execsql_test(db,
+ "CREATE VIRTUAL TABLE ft USING fts5(x, b);" +
+ "INSERT INTO ft(x) VALUES('one two three four five six seven eight');"
+ );
+
+ do_execsql_test(db,
+ "SELECT fts5_tokenize(ft, 'abc def ghi') FROM ft('one')",
+ "[abc+def+ghi]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_tokenize(ft, 'it''s BEEN a...') FROM ft('one')",
+ "[it+s+been+a]"
+ );
+
+ sqlite3_close_v2(db);
+ }
+
+ private static synchronized void runTests(){
+ test1();
+ test2();
+ test3();
+ test4();
+ test5();
+ test6();
+ }
+
+ public TesterFts5(){
+ runTests();
+ }
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,22 @@
+/*
+** 2023-08-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.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+
+
+/**
+ Callback type for use with xTokenize() variants.
+*/
+public interface XTokenizeCallback {
+ int call(int tFlags, byte[] txt, int iStart, int iEnd);
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/fts5_api.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/fts5_api.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/fts5_api.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/fts5_api.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,76 @@
+/*
+** 2023-08-05
+**
+** 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 JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.annotation.*;
+import org.sqlite.jni.capi.*;
+
+/**
+ A wrapper for communicating C-level (fts5_api*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+*/
+public final class fts5_api extends NativePointerHolder {
+ /* Only invoked from JNI */
+ private fts5_api(){}
+
+ public static final int iVersion = 2;
+
+ /**
+ Returns the fts5_api instance associated with the given db, or
+ null if something goes horribly wrong.
+ */
+ public static synchronized native fts5_api getInstanceForDb(@NotNull sqlite3 db);
+
+ public synchronized native int xCreateFunction(@NotNull String name,
+ @Nullable Object userData,
+ @NotNull fts5_extension_function xFunction);
+
+ /**
+ Convenience overload which passes null as the 2nd argument to the
+ 3-parameter form.
+ */
+ public int xCreateFunction(@NotNull String name,
+ @NotNull fts5_extension_function xFunction){
+ return xCreateFunction(name, null, xFunction);
+ }
+
+ // /* Create a new auxiliary function */
+ // int (*xCreateFunction)(
+ // fts5_api *pApi,
+ // const char *zName,
+ // void *pContext,
+ // fts5_extension_function xFunction,
+ // void (*xDestroy)(void*)
+ // );
+
+ // Still potentially todo:
+
+ // int (*xCreateTokenizer)(
+ // fts5_api *pApi,
+ // const char *zName,
+ // void *pContext,
+ // fts5_tokenizer *pTokenizer,
+ // void (*xDestroy)(void*)
+ // );
+
+ // /* Find an existing tokenizer */
+ // int (*xFindTokenizer)(
+ // fts5_api *pApi,
+ // const char *zName,
+ // void **ppContext,
+ // fts5_tokenizer *pTokenizer
+ // );
+
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,47 @@
+/*
+** 2023-08-05
+**
+** 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 JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.sqlite3_context;
+import org.sqlite.jni.capi.sqlite3_value;
+
+/**
+ JNI-level wrapper for C's fts5_extension_function type.
+*/
+public interface fts5_extension_function {
+ // typedef void (*fts5_extension_function)(
+ // const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
+ // Fts5Context *pFts, /* First arg to pass to pApi functions */
+ // sqlite3_context *pCtx, /* Context for returning result/error */
+ // int nVal, /* Number of values in apVal[] array */
+ // sqlite3_value **apVal /* Array of trailing arguments */
+ // );
+
+ /**
+ The callback implementation, corresponding to the xFunction
+ argument of C's fts5_api::xCreateFunction().
+ */
+ void call(Fts5ExtensionApi ext, Fts5Context fCx,
+ sqlite3_context pCx, sqlite3_value argv[]);
+ /**
+ Is called when this function is destroyed by sqlite3. Typically
+ this function will be empty.
+ */
+ void xDestroy();
+
+ public static abstract class Abstract implements fts5_extension_function {
+ @Override public abstract void call(Fts5ExtensionApi ext, Fts5Context fCx,
+ sqlite3_context pCx, sqlite3_value argv[]);
+ @Override public void xDestroy(){}
+ }
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,49 @@
+/*
+** 2023-08-05
+**
+** 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 JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.NativePointerHolder;
+import org.sqlite.jni.annotation.NotNull;
+
+/**
+ A wrapper for communicating C-level (fts5_tokenizer*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+*/
+public final class fts5_tokenizer extends NativePointerHolder {
+ /* Only invoked by JNI */
+ private fts5_tokenizer(){}
+
+ // int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut);
+ // void (*xDelete)(Fts5Tokenizer*);
+
+ public native int xTokenize(@NotNull Fts5Tokenizer t, int tokFlags,
+ @NotNull byte pText[],
+ @NotNull XTokenizeCallback callback);
+
+
+ // int (*xTokenize)(Fts5Tokenizer*,
+ // void *pCtx,
+ // int flags, /* Mask of FTS5_TOKENIZE_* flags */
+ // const char *pText, int nText,
+ // int (*xToken)(
+ // void *pCtx, /* Copy of 2nd argument to xTokenize() */
+ // int tflags, /* Mask of FTS5_TOKEN_* flags */
+ // const char *pToken, /* Pointer to buffer containing token */
+ // int nToken, /* Size of token in bytes */
+ // int iStart, /* Byte offset of token within input text */
+ // int iEnd /* Byte offset of end of token within input text */
+ // )
+ // );
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/test-script-interpreter.md sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/test-script-interpreter.md
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/test-script-interpreter.md 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/test-script-interpreter.md 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,269 @@
+# Specifications For A Rudimentary SQLite Test Script Interpreter
+
+## Overview
+
+The purpose of the Test Script Interpreter is to read and interpret
+script files that contain SQL commands and desired results. The
+interpreter will check results and report an discrepencies found.
+
+The test script files are ASCII text files. The filename always ends with
+".test". Each script is evaluated independently; context does not carry
+forward from one script to the next. So, for example, the --null command
+run in one test script does not cause any changes in the behavior of
+subsequent test scripts. All open database connections are closed
+at the end of each test script. All database files created by a test
+script are deleted when the script finishes.
+
+## Parsing Rules:
+
+ 1. The test script is read line by line, where a line is a sequence of
+ characters that runs up to the next '\\n' (0x0a) character or until
+ the end of the file. There is never a need to read ahead past the
+ end of the current line.
+
+ 2. If any line contains the string " MODULE_NAME:" (with a space before
+ the initial "M") or "MIXED_MODULE_NAME:" then that test script is
+ incompatible with this spec. Processing of the test script should
+ end immediately. There is no need to read any more of the file.
+ In verbose mode, the interpreter might choose to emit an informational
+ messages saying that the test script was abandoned due to an
+ incompatible module type.
+
+ 3. If any line contains the string "SCRIPT_MODULE_NAME:" then the input
+ script is known to be of the correct type for this specification and
+ processing may continue. The "MODULE_NAME" checking in steps 2 and 3
+ may optionally be discontinued after sighting a "SCRIPT_MODULE_NAME".
+
+ 4. If any line contains "REQUIRED_PROPERTIES:" and that substring is followed
+ by any non-whitespace text, then the script is not compatible with this
+ spec. Processing should stop immediately. In verbose mode, the
+ interpreter might choose to emit an information message saying that the
+ test script was abandoned due to unsupported requirement properties.
+
+ 5. If any line begins with the "\|" (0x7c) character, that indicates that
+ the input script is not compatible with this specification. Processing
+ of the script should stop immediately. In verbose mode, the interpreter
+ might choose to emit an informational message indicating that the
+ test script was abandoned because it contained "a dbtotxt format database
+ specification".
+
+ 6. Any line that begins with "#" is a C-preprocessor line. The interpreter
+ described by this spec does not know how to deal with C-preprocessor lines.
+ Hence, processing should be abandoned. In verbose mode, the interpreter
+ might emit an informational message similar to
+ "script NAME abandoned due to C-preprocessor line: ..."
+
+ 7. If a line begins with exactly two minus signs followed by a
+ lowercase letter, that is a command. Process commands as described
+ below.
+
+ 8. All other lines should be accumulated into the "input buffer".
+ The various commands will have access to this input buffer.
+ Some commands will reset the buffer.
+
+## Initialization
+
+The initial state of the interpreter at the start of processing each script
+is as if the following command sequence had been run:
+
+> ~~~
+--close all
+--db 0
+--new test.db
+--null nil
+~~~
+
+In words, all database connections are closed except for connection 0 (the
+default) which is open on an empty database named "test.db". The string
+"nil" is displayed for NULL column values.
+
+The only context carried forward after the evaluation of one test script
+into the evaluation of the next test script is the count of the number of
+tests run and the number of failures seen.
+
+## Commands:
+
+Each command looks like an SQL comment. The command begins at the left
+margin (no leading space) and starts with exactly 2 minus signs ("-").
+The command name consists of lowercase letters and maybe a "-" or two.
+Some commands have arguments.
+The arguments are separated from the command name by one or more spaces.
+
+Commands have access to the input buffer and might reset the input buffer.
+The command can also optionally read (and consume) additional text from
+script that comes after the command.
+
+Unknown or unrecognized commands indicate that the script contains features
+that are not (yet) supported by this specification. Processing of the
+script should terminate immediately. When this happens and when the
+interpreter is in a "verbose" mode, the interpreter might choose to emit
+an informational message along the lines of "test script NAME abandoned
+due to unsupported command: --whatever".
+
+The initial implemention will only recognize a few commands. Other
+commands may be added later. The following is the initial set of
+commands:
+
+### The --testcase command
+
+Every test case starts with a --testcase command. The --testcase
+command resets both the "input buffer" and the "result buffer". The
+argument to the --testcase command is the name of the test case. That
+test case name is used for logging and debugging and when printing
+errors. The input buffer is set to the body of the test case.
+
+### The --result command
+
+The --result command tries to execute the text in the input buffer as SQL.
+For each row of result coming out of this SQL, the text of that result is
+appended to the "result buffer". If a result row contains multiple columns,
+the columns are processed from left to right. For each column, text is
+appended to the result buffer according to the following rules:
+
+ * If the result buffer already contains some text, append a space.
+ (In this way, all column values and all row values are separated from
+ each other by a single space.)
+
+ * If sqlite3_column_text() returns NULL, then append "nil" - or
+ some other text that is specified by the --null command - and skip
+ all subsequent rules.
+
+ * If sqlite3_column_text() is an empty string, append `{}` to the
+ result buffer and skip all subsequent rules.
+
+ * If sqlite3_column_text() does not contain any special
+ characters, append it to the result buffer without any
+ formatting and skip all subsequent rules. Special characters are:
+ 0x00 to 0x20 (inclusive), double-quote (0x22), backslash (0x5c),
+ curly braces (0x7b and 0x7d).
+
+ * If sqlite3_column_text() does not contains curly braces, then put
+ the text inside of `{...}` and append it and skip all subsequent rules.
+
+ * Append the text within double-quotes (`"..."`) and within the text
+ escape '"' and '\\' by prepending a single '\\' and escape any
+ control characters (characters less than 0x20) using octal notation:
+ '\\NNN'.
+
+If an error is encountered while running the SQL, then append the
+symbolic C-preprocessor name for the error
+code (ex: "SQLITE_CONSTRAINT") as if it were a column value. Then append
+the error message text as if it where a column value. Then stop processing.
+
+After the SQL text has been run, compare the content of the result buffer
+against the argument to the --result command and report a testing error if
+there are any differences.
+
+The --result command resets the input buffer, but it does not reset
+the result buffer. This distinction does not matter for the --result
+command itself, but it is important for related commands like --glob
+and --notglob. Sometimes test cases will contains a bunch of SQL
+followed by multiple --glob and/or --notglob statements. All of the
+globs should be evaluted agains the result buffer correct, but the SQL
+should only be run once. This is accomplished by resetting the input
+buffer but not the result buffer.
+
+### The --glob command
+
+The --glob command works just like --result except that the argument to
+--glob is interpreted as a TEST-GLOB pattern and the results are compared
+using that glob pattern rather than using strcmp(). Other than that,
+the two operate the same.
+
+The TEST-GLOB pattern is slightly different for a standard GLOB:
+
+ * The '*' character matches zero or more characters.
+
+ * The '?' character matches any single character
+
+ * The '[...]' character sequence machines a single character
+ in between the brackets.
+
+ * The '#' character matches one or more digits (This is the main
+ difference between standard unix-glob and TEST-GLOB. unix-glob
+ does not have this feature. It was added to because it comes
+ up a lot during SQLite testing.)
+
+### The --notglob command
+
+The --notglob command works just like --glob except that it reports an
+error if the GLOB does match, rather than if the GLOB does not matches.
+
+### The --oom command
+
+This command is to be used for out-of-memory testing. It means that
+OOM errors should be simulated to ensure that SQLite is able to deal with
+them. This command can be silently ignored for now. We might add support
+for this later.
+
+### The --tableresult command
+
+The --tableresult command works like --glob except that the GLOB pattern
+to be matched is taken from subsequent lines of the input script up to
+the next --end. Every span of one or more whitespace characters in this
+pattern text is collapsed into a single space (0x20).
+Leading and trailing whitespace are removed from the pattern.
+The --end that ends the GLOB pattern is not part of the GLOB pattern, but
+the --end is consumed from the script input.
+
+### The --new and --open commands
+
+The --new and --open commands cause a database file to be opened.
+The name of the file is the argument to the command. The --new command
+opens an initially empty database (it deletes the file before opening it)
+whereas the --open command opens an existing database if it already
+exists.
+
+### The --db command
+
+The script interpreter can have up to 7 different SQLite database
+connections open at a time. The --db command is used to switch between
+them. The argument to --db is an integer between 0 and 6 that selects
+which database connection to use moving forward.
+
+### The --close command
+
+The --close command causes an existing database connection to close.
+This command is a no-op if the database connection is not currently
+open. There can be up to 7 different database connections, numbered 0
+through 6. The number of the database connection to close is an
+argument to the --close command, which will fail if an out-of-range
+value is provided. Or if the argument to --close is "all" then all
+open database connections are closed. If passed no argument, the
+currently-active database is assumed.
+
+### The --null command
+
+The NULL command changes the text that is used to represent SQL NULL
+values in the result buffer.
+
+### The --run command
+
+The --run command executes text in the input buffer as if it where SQL.
+However, nothing is added to the result buffer. Any output from the SQL
+is silently ignored. Errors in the SQL are silently ignored.
+
+The --run command normally executes the SQL in the current database
+connection. However, if --run has an argument that is an integer between
+0 and 6 then the SQL is run in the alternative database connection specified
+by that argument.
+
+### The --json and --json-block commands
+
+The --json and --json-block commands work like --result and --tableresult,
+respectively. The difference is that column values are appended to the
+result buffer literally, without ever enclosing the values in `{...}` or
+`"..."` and without escaping any characters in the column value and comparison
+is always an exact strcmp() not a GLOB.
+
+### The --print command
+
+The --print command emits both its arguments and its body (if any) to
+stdout, indenting each line of output.
+
+### The --column-names command
+
+The --column-names command requires 0 or 1 as an argument, to disable
+resp. enable it, and modifies SQL execution to include column names
+in output. When this option is on, each column value emitted gets
+prefixed by its column name, with a single space between them.
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,82 @@
+/*
+** 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;
+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
+
+ A SqlFunction implementation for aggregate functions. The T type
+ represents the type of data accumulated by this aggregate 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.
+*/
+public abstract class AggregateFunction implements SqlFunction {
+
+ /**
+ As for the xStep() argument of the C API's
+ sqlite3_create_function(). If this function throws, the
+ exception is reported via sqlite3_result_error().
+ */
+ public abstract void xStep(SqlFunction.Arguments args);
+
+ /**
+ As for the xFinal() argument of the C API's
+ sqlite3_create_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 xFinal() but will contain the context object needed
+ for setting the call's result or error state.
+ */
+ public abstract void xFinal(SqlFunction.Arguments args);
+
+ /**
+ Optionally override to be notified when the UDF is finalized by
+ SQLite.
+ */
+ public void xDestroy() {}
+
+ /** Per-invocation state for the UDF. */
+ private final SqlFunction.PerContextState map =
+ new SqlFunction.PerContextState<>();
+
+ /**
+ To be called from the implementation's xStep() method, as well
+ as the xValue() and xInverse() methods of the {@link WindowFunction}
+ subclass, to fetch the current per-call UDF state. On the
+ first call to this method for any given sqlite3_context
+ argument, the context is set to the given initial value. On all other
+ calls, the 2nd argument is ignored.
+
+ @see SQLFunction.PerContextState#getAggregateState
+ */
+ protected final ValueHolder getAggregateState(SqlFunction.Arguments args, T initialValue){
+ return map.getAggregateState(args, initialValue);
+ }
+
+ /**
+ To be called from the implementation's xFinal() method to fetch
+ the final state of the UDF and remove its mapping.
+
+ see SQLFunction.PerContextState#takeAggregateState
+ */
+ protected final T takeAggregateState(SqlFunction.Arguments args){
+ return map.takeAggregateState(args);
+ }
+
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,37 @@
+/*
+** 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;
+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;
+
+/**
+ The SqlFunction type for scalar SQL functions.
+*/
+public abstract class ScalarFunction implements SqlFunction {
+ /**
+ As for the xFunc() argument of the C API's
+ sqlite3_create_function(). If this function throws, it is
+ translated into an sqlite3_result_error().
+ */
+ public abstract void xFunc(SqlFunction.Arguments args);
+
+ /**
+ Optionally override to be notified when the UDF is finalized by
+ SQLite. This default implementation does nothing.
+ */
+ public void xDestroy() {}
+
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,301 @@
+/*
+** 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;
+import org.sqlite.jni.capi.CApi;
+import org.sqlite.jni.capi.sqlite3_context;
+import org.sqlite.jni.capi.sqlite3_value;
+
+/**
+ Base marker interface for SQLite's three types of User-Defined SQL
+ Functions (UDFs): Scalar, Aggregate, and Window functions.
+*/
+public interface SqlFunction {
+
+ /**
+ The Arguments type is an abstraction on top of the lower-level
+ UDF function argument types. It provides _most_ of the functionality
+ of the lower-level interface, insofar as possible without "leaking"
+ those types into this API.
+ */
+ public final static class Arguments implements Iterable{
+ private final sqlite3_context cx;
+ private final sqlite3_value args[];
+ public final int length;
+
+ /**
+ Must be passed the context and arguments for the UDF call this
+ object is wrapping. Intended to be used by internal proxy
+ classes which "convert" the lower-level interface into this
+ package's higher-level interface, e.g. ScalarAdapter and
+ AggregateAdapter.
+
+ Passing null for the args is equivalent to passing a length-0
+ array.
+ */
+ Arguments(sqlite3_context cx, sqlite3_value args[]){
+ this.cx = cx;
+ this.args = args==null ? new sqlite3_value[0] : args;;
+ this.length = this.args.length;
+ }
+
+ /**
+ Wrapper for a single SqlFunction argument. Primarily intended
+ for use with the Arguments class's Iterable interface.
+ */
+ public final static class Arg {
+ private final Arguments a;
+ private final int ndx;
+ /* Only for use by the Arguments class. */
+ private Arg(Arguments a, int ndx){
+ this.a = a;
+ this.ndx = ndx;
+ }
+ /** Returns this argument's index in its parent argument list. */
+ public int getIndex(){return ndx;}
+ public int getInt(){return a.getInt(ndx);}
+ public long getInt64(){return a.getInt64(ndx);}
+ public double getDouble(){return a.getDouble(ndx);}
+ public byte[] getBlob(){return a.getBlob(ndx);}
+ public byte[] getText(){return a.getText(ndx);}
+ public String getText16(){return a.getText16(ndx);}
+ public int getBytes(){return a.getBytes(ndx);}
+ public int getBytes16(){return a.getBytes16(ndx);}
+ public Object getObject(){return a.getObject(ndx);}
+ public T getObjectCasted(Class 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);}
+ }
+
+ @Override
+ public java.util.Iterator iterator(){
+ final Arg[] proxies = new Arg[args.length];
+ for( int i = 0; i < args.length; ++i ){
+ proxies[i] = new Arg(this, i);
+ }
+ 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 getObjectCasted(int arg, Class 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.
+
+ T must be of a type which can be legally stored as a value in
+ java.util.HashMap.
+
+ 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.
+
+
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.
+
+
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 {
+ private final java.util.Map> 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
+ which can be used to modify that state directly without
+ requiring that the client update the underlying map's entry.
+
+ The caller is obligated to eventually call
+ takeAggregateState() to clear the mapping.
+ */
+ public ValueHolder getAggregateState(SqlFunction.Arguments args, T initialValue){
+ final Long key = args.getContext().getAggregateContext(true);
+ ValueHolder 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 h = map.remove(args.getContext().getAggregateContext(false));
+ return null==h ? null : h.value;
+ }
+ }
+
+ /**
+ Internal-use adapter for wrapping this package's ScalarFunction
+ for use with the org.sqlite.jni.capi.ScalarFunction interface.
+ */
+ static final class ScalarAdapter extends org.sqlite.jni.capi.ScalarFunction {
+ final ScalarFunction impl;
+ ScalarAdapter(ScalarFunction impl){
+ this.impl = impl;
+ }
+ /**
+ Proxies this.impl.xFunc(), adapting the call arguments to that
+ function's signature. If the proxy throws, it's translated to
+ sqlite_result_error() with the exception's message.
+ */
+ public void xFunc(sqlite3_context cx, sqlite3_value[] args){
+ try{
+ impl.xFunc( new SqlFunction.Arguments(cx, args) );
+ }catch(Exception e){
+ CApi.sqlite3_result_error(cx, e);
+ }
+ }
+
+ public void xDestroy(){
+ impl.xDestroy();
+ }
+ }
+
+ /**
+ Internal-use adapter for wrapping this package's AggregateFunction
+ for use with the org.sqlite.jni.capi.AggregateFunction interface.
+ */
+ static final class AggregateAdapter extends org.sqlite.jni.capi.AggregateFunction {
+ final AggregateFunction impl;
+ AggregateAdapter(AggregateFunction impl){
+ this.impl = impl;
+ }
+
+ /**
+ Proxies this.impl.xStep(), 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 xStep(sqlite3_context cx, sqlite3_value[] args){
+ try{
+ impl.xStep( new SqlFunction.Arguments(cx, args) );
+ }catch(Exception e){
+ CApi.sqlite3_result_error(cx, e);
+ }
+ }
+
+ /**
+ 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{
+ impl.xFinal( new SqlFunction.Arguments(cx, null) );
+ }catch(Exception e){
+ CApi.sqlite3_result_error(cx, e);
+ }
+ }
+
+ public void xDestroy(){
+ impl.xDestroy();
+ }
+ }
+
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,218 @@
+/*
+** 2023-10-09
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the wrapper1 interface for sqlite3.
+*/
+package org.sqlite.jni.wrapper1;
+import java.nio.charset.StandardCharsets;
+import static org.sqlite.jni.capi.CApi.*;
+import org.sqlite.jni.capi.CApi;
+import org.sqlite.jni.capi.sqlite3;
+import org.sqlite.jni.capi.sqlite3_stmt;
+import org.sqlite.jni.capi.OutputPointer;
+
+/**
+ This class represents a database connection, analog to the C-side
+ sqlite3 class but with added argument validation, exceptions, and
+ similar "smoothing of sharp edges" to make the API safe to use from
+ Java. It also acts as a namespace for other types for which
+ individual instances are tied to a specific database connection.
+*/
+public final class Sqlite implements AutoCloseable {
+ private sqlite3 db;
+
+ //! Used only by the open() factory functions.
+ private Sqlite(sqlite3 db){
+ this.db = db;
+ }
+
+ /**
+ Returns a newly-opened db connection or throws SqliteException if
+ opening fails. All arguments are as documented for
+ sqlite3_open_v2().
+
+ Design question: do we want static factory functions or should
+ this be reformulated as a constructor?
+ */
+ public static Sqlite open(String filename, int flags, String vfsName){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ final int rc = sqlite3_open_v2(filename, out, flags, vfsName);
+ final sqlite3 n = out.take();
+ if( 0!=rc ){
+ if( null==n ) throw new SqliteException(rc);
+ final SqliteException ex = new SqliteException(n);
+ n.close();
+ throw ex;
+ }
+ return new Sqlite(n);
+ }
+
+ public static Sqlite open(String filename, int flags){
+ return open(filename, flags, null);
+ }
+
+ public static Sqlite open(String filename){
+ return open(filename, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, null);
+ }
+
+ @Override public void close(){
+ if(null!=this.db){
+ this.db.close();
+ this.db = null;
+ }
+ }
+
+ /**
+ Returns this object's underlying native db handle, or null if
+ this instance has been closed. This is very specifically not
+ public.
+ */
+ sqlite3 nativeHandle(){ return this.db; }
+
+ private sqlite3 affirmOpen(){
+ if( null==db || 0==db.getNativePointer() ){
+ throw new IllegalArgumentException("This database instance is closed.");
+ }
+ return this.db;
+ }
+
+ // private byte[] stringToUtf8(String s){
+ // return s==null ? null : s.getBytes(StandardCharsets.UTF_8);
+ // }
+
+ private void affirmRcOk(int rc){
+ if( 0!=rc ){
+ throw new SqliteException(db);
+ }
+ }
+
+ /**
+ Corresponds to the sqlite3_stmt class. Use Sqlite.prepare() to
+ create new instances.
+ */
+ public final class Stmt implements AutoCloseable {
+ private Sqlite _db = null;
+ private sqlite3_stmt stmt = null;
+ /** Only called by the prepare() factory functions. */
+ Stmt(Sqlite db, sqlite3_stmt stmt){
+ this._db = db;
+ this.stmt = stmt;
+ }
+
+ sqlite3_stmt nativeHandle(){
+ return stmt;
+ }
+
+ private sqlite3_stmt affirmOpen(){
+ if( null==stmt || 0==stmt.getNativePointer() ){
+ throw new IllegalArgumentException("This Stmt has been finalized.");
+ }
+ return stmt;
+ }
+
+ /**
+ Corresponds to sqlite3_finalize(), but we cannot override the
+ name finalize() here because this one requires a different
+ signature. It does not throw on error here because "destructors
+ do not throw." If it returns non-0, the object is still
+ finalized.
+ */
+ public int finalizeStmt(){
+ int rc = 0;
+ if( null!=stmt ){
+ sqlite3_finalize(stmt);
+ stmt = null;
+ }
+ return rc;
+ }
+
+ @Override public void close(){
+ finalizeStmt();
+ }
+
+ /**
+ Throws if rc is any value other than 0, SQLITE_ROW, or
+ SQLITE_DONE, else returns rc.
+ */
+ private int checkRc(int rc){
+ switch(rc){
+ case 0:
+ case SQLITE_ROW:
+ case SQLITE_DONE: return rc;
+ default:
+ throw new SqliteException(this);
+ }
+ }
+
+ /**
+ Works like sqlite3_step() but throws SqliteException for any
+ result other than 0, SQLITE_ROW, or SQLITE_DONE.
+ */
+ public int step(){
+ return checkRc(sqlite3_step(affirmOpen()));
+ }
+
+ public Sqlite db(){ return this._db; }
+
+ /**
+ Works like sqlite3_reset() but throws on error.
+ */
+ public void reset(){
+ checkRc(sqlite3_reset(affirmOpen()));
+ }
+
+ public void clearBindings(){
+ sqlite3_clear_bindings( affirmOpen() );
+ }
+ }
+
+
+ /**
+ prepare() TODOs include:
+
+ - overloads taking byte[] and ByteBuffer.
+
+ - multi-statement processing, like CApi.sqlite3_prepare_multi()
+ but using a callback specific to the higher-level Stmt class
+ rather than the sqlite3_stmt class.
+ */
+ public Stmt prepare(String sql, int prepFlags){
+ final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+ final int rc = sqlite3_prepare_v3(affirmOpen(), sql, prepFlags, out);
+ affirmRcOk(rc);
+ return new Stmt(this, out.take());
+ }
+
+ public Stmt prepare(String sql){
+ return prepare(sql, 0);
+ }
+
+ public void createFunction(String name, int nArg, int eTextRep, ScalarFunction f ){
+ int rc = CApi.sqlite3_create_function(affirmOpen(), name, nArg, eTextRep,
+ new SqlFunction.ScalarAdapter(f));
+ if( 0!=rc ) throw new SqliteException(db);
+ }
+
+ public void createFunction(String name, int nArg, ScalarFunction f){
+ this.createFunction(name, nArg, CApi.SQLITE_UTF8, f);
+ }
+
+ public void createFunction(String name, int nArg, int eTextRep, AggregateFunction f ){
+ int rc = CApi.sqlite3_create_function(affirmOpen(), name, nArg, eTextRep,
+ new SqlFunction.AggregateAdapter(f));
+ if( 0!=rc ) throw new SqliteException(db);
+ }
+
+ public void createFunction(String name, int nArg, AggregateFunction f){
+ this.createFunction(name, nArg, CApi.SQLITE_UTF8, f);
+ }
+
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,82 @@
+/*
+** 2023-10-09
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the wrapper1 interface for sqlite3.
+*/
+package org.sqlite.jni.wrapper1;
+import static org.sqlite.jni.capi.CApi.*;
+import org.sqlite.jni.capi.sqlite3;
+
+/**
+ A wrapper for communicating C-level (sqlite3*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java
+ and C via JNI.
+*/
+public final class SqliteException extends java.lang.RuntimeException {
+ 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
+ error code and extended error code.
+ */
+ public SqliteException(String msg){
+ super(msg);
+ }
+
+ /**
+ Uses sqlite3_errstr(sqlite3ResultCode) for the error string and
+ sets both the error code and extended error code to the given
+ value.
+ */
+ public SqliteException(int 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
+ the db.
+
+ 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(sqlite3_errmsg(db));
+ errCode = sqlite3_errcode(db);
+ xerrCode = sqlite3_extended_errcode(db);
+ errOffset = sqlite3_error_offset(db);
+ sysErrno = sqlite3_system_errno(db);
+ }
+
+ /**
+ Records the current error state of db (which must not be null and
+ must refer to an open database).
+ */
+ public SqliteException(Sqlite db){
+ this(db.nativeHandle());
+ }
+
+ public SqliteException(Sqlite.Stmt stmt){
+ this( stmt.db() );
+ }
+
+ public int errcode(){ return errCode; }
+ public int extendedErrcode(){ return xerrCode; }
+ public int errorOffset(){ return errOffset; }
+ public int systemErrno(){ return sysErrno; }
+
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,584 @@
+/*
+** 2023-10-09
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains a set of tests for the sqlite3 JNI bindings.
+*/
+package org.sqlite.jni.wrapper1;
+//import static org.sqlite.jni.capi.CApi.*;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import org.sqlite.jni.capi.*;
+
+/**
+ An annotation for Tester2 tests which we do not want to run in
+ reflection-driven test mode because either they are not suitable
+ for multi-threaded threaded mode or we have to control their execution
+ order.
+*/
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+@interface ManualTest{}
+/**
+ Annotation for Tester2 tests which mark those which must be skipped
+ in multi-threaded mode.
+*/
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+@interface SingleThreadOnly{}
+
+public class Tester2 implements Runnable {
+ //! True when running in multi-threaded mode.
+ private static boolean mtMode = false;
+ //! True to sleep briefly between tests.
+ private static boolean takeNaps = false;
+ //! 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 boolean listRunTests = false;
+ //! True to squelch all out() and outln() output.
+ private static boolean quietMode = false;
+ //! Total number of runTests() calls.
+ private static int nTestRuns = 0;
+ //! List of test*() methods to run.
+ private static List testMethods = null;
+ //! List of exceptions collected by run()
+ private static List listErrors = new ArrayList<>();
+ private static final class Metrics {
+ //! Number of times createNewDb() (or equivalent) is invoked.
+ volatile int dbOpen = 0;
+ }
+
+ //! Instance ID.
+ private Integer tId;
+
+ Tester2(Integer id){
+ tId = id;
+ }
+
+ static final Metrics metrics = new Metrics();
+
+ public static synchronized void outln(){
+ if( !quietMode ){
+ System.out.println("");
+ }
+ }
+
+ public static synchronized void outPrefix(){
+ if( !quietMode ){
+ System.out.print(Thread.currentThread().getName()+": ");
+ }
+ }
+
+ public static synchronized void outln(Object val){
+ if( !quietMode ){
+ outPrefix();
+ System.out.println(val);
+ }
+ }
+
+ public static synchronized void out(Object val){
+ if( !quietMode ){
+ System.out.print(val);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static synchronized void out(Object... vals){
+ if( !quietMode ){
+ outPrefix();
+ for(Object v : vals) out(v);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static synchronized void outln(Object... vals){
+ if( !quietMode ){
+ out(vals); out("\n");
+ }
+ }
+
+ static volatile int affirmCount = 0;
+ public static synchronized int affirm(Boolean v, String comment){
+ ++affirmCount;
+ if( false ) assert( v /* prefer assert over exception if it's enabled because
+ the JNI layer sometimes has to suppress exceptions,
+ so they might be squelched on their way back to the
+ top. */);
+ if( !v ) throw new RuntimeException(comment);
+ return affirmCount;
+ }
+
+ public static void affirm(Boolean v){
+ affirm(v, "Affirmation failed.");
+ }
+
+
+ public static void execSql(Sqlite db, String[] sql){
+ execSql(db, String.join("", sql));
+ }
+
+ public static int execSql(Sqlite dbw, boolean throwOnError, String sql){
+ final sqlite3 db = dbw.nativeHandle();
+ OutputPointer.Int32 oTail = new OutputPointer.Int32();
+ final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8);
+ int pos = 0, n = 1;
+ byte[] sqlChunk = sqlUtf8;
+ int rc = 0;
+ sqlite3_stmt stmt = null;
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+ while(pos < sqlChunk.length){
+ if(pos > 0){
+ sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
+ sqlChunk.length);
+ }
+ if( 0==sqlChunk.length ) break;
+ rc = CApi.sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail);
+ if(throwOnError) affirm(0 == rc);
+ else if( 0!=rc ) break;
+ pos = oTail.value;
+ stmt = outStmt.take();
+ if( null == stmt ){
+ // empty statement was parsed.
+ continue;
+ }
+ affirm(0 != stmt.getNativePointer());
+ while( CApi.SQLITE_ROW == (rc = CApi.sqlite3_step(stmt)) ){
+ }
+ CApi.sqlite3_finalize(stmt);
+ affirm(0 == stmt.getNativePointer());
+ if(0!=rc && CApi.SQLITE_ROW!=rc && CApi.SQLITE_DONE!=rc){
+ break;
+ }
+ }
+ CApi.sqlite3_finalize(stmt);
+ if(CApi.SQLITE_ROW==rc || CApi.SQLITE_DONE==rc) rc = 0;
+ if( 0!=rc && throwOnError){
+ throw new SqliteException(db);
+ }
+ return rc;
+ }
+
+ static void execSql(Sqlite db, String sql){
+ execSql(db, true, sql);
+ }
+
+ @SingleThreadOnly /* because it's thread-agnostic */
+ private void test1(){
+ affirm(CApi.sqlite3_libversion_number() == CApi.SQLITE_VERSION_NUMBER);
+ }
+
+ /* Copy/paste/rename this to add new tests. */
+ private void _testTemplate(){
+ //final sqlite3 db = createNewDb();
+ //sqlite3_stmt stmt = prepare(db,"SELECT 1");
+ //sqlite3_finalize(stmt);
+ //sqlite3_close_v2(db);
+ }
+
+ private void nap() throws InterruptedException {
+ if( takeNaps ){
+ Thread.sleep(java.util.concurrent.ThreadLocalRandom.current().nextInt(3, 17), 0);
+ }
+ }
+
+ Sqlite openDb(String name){
+ final Sqlite db = Sqlite.open(name, CApi.SQLITE_OPEN_READWRITE|
+ CApi.SQLITE_OPEN_CREATE|
+ CApi.SQLITE_OPEN_EXRESCODE);
+ ++metrics.dbOpen;
+ return db;
+ }
+
+ Sqlite openDb(){ return openDb(":memory:"); }
+
+ void testOpenDb1(){
+ Sqlite db = openDb();
+ affirm( 0!=db.nativeHandle().getNativePointer() );
+ db.close();
+ affirm( null==db.nativeHandle() );
+
+ SqliteException ex = null;
+ try {
+ db = openDb("/no/such/dir/.../probably");
+ }catch(SqliteException e){
+ ex = e;
+ }
+ affirm( ex!=null );
+ affirm( ex.errcode() != 0 );
+ affirm( ex.extendedErrcode() != 0 );
+ affirm( ex.errorOffset() < 0 );
+ // there's no reliable way to predict what ex.systemErrno() might be
+ }
+
+ void testPrepare1(){
+ try (Sqlite db = openDb()) {
+ Sqlite.Stmt stmt = db.prepare("SELECT 1");
+ affirm( null!=stmt.nativeHandle() );
+ affirm( CApi.SQLITE_ROW == stmt.step() );
+ affirm( CApi.SQLITE_DONE == stmt.step() );
+ stmt.reset();
+ affirm( CApi.SQLITE_ROW == stmt.step() );
+ affirm( CApi.SQLITE_DONE == stmt.step() );
+ affirm( 0 == stmt.finalizeStmt() );
+ affirm( null==stmt.nativeHandle() );
+
+ stmt = db.prepare("SELECT 1");
+ affirm( CApi.SQLITE_ROW == stmt.step() );
+ affirm( 0 == stmt.finalizeStmt() )
+ /* getting a non-0 out of sqlite3_finalize() is tricky */;
+ affirm( null==stmt.nativeHandle() );
+ }
+ }
+
+ void testUdfScalar(){
+ final ValueHolder xDestroyCalled = new ValueHolder<>(0);
+ try (Sqlite db = openDb()) {
+ execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)");
+ final ValueHolder vh = new ValueHolder<>(0);
+ final ScalarFunction f = new ScalarFunction(){
+ public void xFunc(SqlFunction.Arguments args){
+ for( SqlFunction.Arguments.Arg arg : args ){
+ vh.value += arg.getInt();
+ }
+ }
+ public void xDestroy(){
+ ++xDestroyCalled.value;
+ }
+ };
+ db.createFunction("myfunc", -1, f);
+ execSql(db, "select myfunc(1,2,3)");
+ affirm( 6 == vh.value );
+ vh.value = 0;
+ execSql(db, "select myfunc(-1,-2,-3)");
+ affirm( -6 == vh.value );
+ affirm( 0 == xDestroyCalled.value );
+ }
+ affirm( 1 == xDestroyCalled.value );
+ }
+
+ void testUdfAggregate(){
+ final ValueHolder xDestroyCalled = new ValueHolder<>(0);
+ final ValueHolder vh = new ValueHolder<>(0);
+ try (Sqlite db = openDb()) {
+ execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)");
+ final AggregateFunction f = new AggregateFunction(){
+ public void xStep(SqlFunction.Arguments args){
+ final ValueHolder agg = this.getAggregateState(args, 0);
+ for( SqlFunction.Arguments.Arg arg : args ){
+ agg.value += arg.getInt();
+ }
+ }
+ public void xFinal(SqlFunction.Arguments args){
+ final Integer v = this.takeAggregateState(args);
+ if( null==v ) args.resultNull();
+ else args.resultInt(v);
+ vh.value = v;
+ }
+ public void xDestroy(){
+ ++xDestroyCalled.value;
+ }
+ };
+ db.createFunction("myagg", -1, f);
+ execSql(db, "select myagg(a) from t");
+ affirm( 6 == vh.value );
+ affirm( 0 == xDestroyCalled.value );
+ }
+ affirm( 1 == xDestroyCalled.value );
+ }
+
+ private void runTests(boolean fromThread) throws Exception {
+ List mlist = testMethods;
+ affirm( null!=mlist );
+ if( shuffle ){
+ mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) );
+ java.util.Collections.shuffle(mlist);
+ }
+ if( listRunTests ){
+ synchronized(this.getClass()){
+ if( !fromThread ){
+ out("Initial test"," list: ");
+ for(java.lang.reflect.Method m : testMethods){
+ out(m.getName()+" ");
+ }
+ outln();
+ outln("(That list excludes some which are hard-coded to run.)");
+ }
+ out("Running"," tests: ");
+ for(java.lang.reflect.Method m : mlist){
+ out(m.getName()+" ");
+ }
+ outln();
+ }
+ }
+ for(java.lang.reflect.Method m : mlist){
+ nap();
+ try{
+ m.invoke(this);
+ }catch(java.lang.reflect.InvocationTargetException e){
+ outln("FAILURE: ",m.getName(),"(): ", e.getCause());
+ throw e;
+ }
+ }
+ synchronized( this.getClass() ){
+ ++nTestRuns;
+ }
+ }
+
+ public void run() {
+ try {
+ runTests(0!=this.tId);
+ }catch(Exception e){
+ synchronized( listErrors ){
+ listErrors.add(e);
+ }
+ }finally{
+ affirm( CApi.sqlite3_java_uncache_thread() );
+ affirm( !CApi.sqlite3_java_uncache_thread() );
+ }
+ }
+
+ /**
+ Runs the basic sqlite3 JNI binding sanity-check suite.
+
+ CLI flags:
+
+ -q|-quiet: disables most test output.
+
+ -t|-thread N: runs the tests in N threads
+ concurrently. Default=1.
+
+ -r|-repeat N: repeats the tests in a loop N times, each one
+ consisting of the -thread value's threads.
+
+ -shuffle: randomizes the order of most of the test functions.
+
+ -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. 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.
+
+ -v: emit some developer-mode info at the end.
+ */
+ public static void main(String[] args) throws Exception {
+ Integer nThread = 1;
+ boolean doSomethingForDev = false;
+ Integer nRepeat = 1;
+ boolean forceFail = false;
+ boolean sqlLog = false;
+ boolean configLog = false;
+ boolean squelchTestOutput = false;
+ for( int i = 0; i < args.length; ){
+ String arg = args[i++];
+ if(arg.startsWith("-")){
+ arg = arg.replaceFirst("-+","");
+ if(arg.equals("v")){
+ doSomethingForDev = true;
+ //listBoundMethods();
+ }else if(arg.equals("t") || arg.equals("thread")){
+ nThread = Integer.parseInt(args[i++]);
+ }else if(arg.equals("r") || arg.equals("repeat")){
+ nRepeat = Integer.parseInt(args[i++]);
+ }else if(arg.equals("shuffle")){
+ shuffle = true;
+ }else if(arg.equals("list-tests")){
+ listRunTests = true;
+ }else if(arg.equals("fail")){
+ forceFail = true;
+ }else if(arg.equals("sqllog")){
+ sqlLog = true;
+ }else if(arg.equals("configlog")){
+ configLog = true;
+ }else if(arg.equals("naps")){
+ takeNaps = true;
+ }else if(arg.equals("q") || arg.equals("quiet")){
+ squelchTestOutput = true;
+ }else{
+ throw new IllegalArgumentException("Unhandled flag:"+arg);
+ }
+ }
+ }
+
+ if( sqlLog ){
+ if( CApi.sqlite3_compileoption_used("ENABLE_SQLLOG") ){
+ final ConfigSqllogCallback log = new ConfigSqllogCallback() {
+ @Override public void call(sqlite3 db, String msg, int op){
+ switch(op){
+ case 0: outln("Opening db: ",db); break;
+ case 1: outln("SQL ",db,": ",msg); break;
+ case 2: outln("Closing db: ",db); break;
+ }
+ }
+ };
+ int rc = CApi.sqlite3_config( log );
+ affirm( 0==rc );
+ rc = CApi.sqlite3_config( (ConfigSqllogCallback)null );
+ affirm( 0==rc );
+ rc = CApi.sqlite3_config( log );
+ affirm( 0==rc );
+ }else{
+ outln("WARNING: -sqllog is not active because library was built ",
+ "without SQLITE_ENABLE_SQLLOG.");
+ }
+ }
+ if( configLog ){
+ final ConfigLogCallback log = new ConfigLogCallback() {
+ @Override public void call(int code, String msg){
+ outln("ConfigLogCallback: ",ResultCode.getEntryForInt(code),": ", msg);
+ };
+ };
+ int rc = CApi.sqlite3_config( log );
+ affirm( 0==rc );
+ rc = CApi.sqlite3_config( (ConfigLogCallback)null );
+ affirm( 0==rc );
+ rc = CApi.sqlite3_config( log );
+ affirm( 0==rc );
+ }
+
+ quietMode = squelchTestOutput;
+ outln("If you just saw warning messages regarding CallStaticObjectMethod, ",
+ "you are very likely seeing the side effects of a known openjdk8 ",
+ "bug. It is unsightly but does not affect the library.");
+
+ {
+ // Build list of tests to run from the methods named test*().
+ testMethods = new ArrayList<>();
+ int nSkipped = 0;
+ for(final java.lang.reflect.Method m : Tester2.class.getDeclaredMethods()){
+ final String name = m.getName();
+ if( name.equals("testFail") ){
+ if( forceFail ){
+ testMethods.add(m);
+ }
+ }else if( !m.isAnnotationPresent( ManualTest.class ) ){
+ if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){
+ 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();
+ int nLoop = 0;
+ switch( CApi.sqlite3_threadsafe() ){ /* Sanity checking */
+ case 0:
+ affirm( CApi.SQLITE_ERROR==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SINGLETHREAD ),
+ "Could not switch to single-thread mode." );
+ affirm( CApi.SQLITE_ERROR==CApi.sqlite3_config( CApi.SQLITE_CONFIG_MULTITHREAD ),
+ "Could switch to multithread mode." );
+ affirm( CApi.SQLITE_ERROR==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SERIALIZED ),
+ "Could not switch to serialized threading mode." );
+ outln("This is a single-threaded build. Not using threads.");
+ nThread = 1;
+ break;
+ case 1:
+ case 2:
+ affirm( 0==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SINGLETHREAD ),
+ "Could not switch to single-thread mode." );
+ affirm( 0==CApi.sqlite3_config( CApi.SQLITE_CONFIG_MULTITHREAD ),
+ "Could not switch to multithread mode." );
+ affirm( 0==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SERIALIZED ),
+ "Could not switch to serialized threading mode." );
+ break;
+ default:
+ affirm( false, "Unhandled SQLITE_THREADSAFE value." );
+ }
+ outln("libversion_number: ",
+ CApi.sqlite3_libversion_number(),"\n",
+ CApi.sqlite3_libversion(),"\n",CApi.SQLITE_SOURCE_ID,"\n",
+ "SQLITE_THREADSAFE=",CApi.sqlite3_threadsafe());
+ final boolean showLoopCount = (nRepeat>1 && nThread>1);
+ if( showLoopCount ){
+ outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each.");
+ }
+ if( takeNaps ) outln("Napping between tests is enabled.");
+ for( int n = 0; n < nRepeat; ++n ){
+ ++nLoop;
+ if( showLoopCount ) out((1==nLoop ? "" : " ")+nLoop);
+ if( nThread<=1 ){
+ new Tester2(0).runTests(false);
+ continue;
+ }
+ Tester2.mtMode = true;
+ final ExecutorService ex = Executors.newFixedThreadPool( nThread );
+ for( int i = 0; i < nThread; ++i ){
+ ex.submit( new Tester2(i), i );
+ }
+ ex.shutdown();
+ try{
+ ex.awaitTermination(nThread*200, java.util.concurrent.TimeUnit.MILLISECONDS);
+ ex.shutdownNow();
+ }catch (InterruptedException ie){
+ ex.shutdownNow();
+ Thread.currentThread().interrupt();
+ }
+ if( !listErrors.isEmpty() ){
+ quietMode = false;
+ outln("TEST ERRORS:");
+ Exception err = null;
+ for( Exception e : listErrors ){
+ e.printStackTrace();
+ if( null==err ) err = e;
+ }
+ if( null!=err ) throw err;
+ }
+ }
+ if( showLoopCount ) outln();
+ quietMode = false;
+
+ final long timeEnd = System.currentTimeMillis();
+ outln("Tests done. Metrics across ",nTestRuns," total iteration(s):");
+ outln("\tAssertions checked: ",affirmCount);
+ outln("\tDatabases opened: ",metrics.dbOpen);
+ if( doSomethingForDev ){
+ CApi.sqlite3_jni_internal_details();
+ }
+ affirm( 0==CApi.sqlite3_release_memory(1) );
+ CApi.sqlite3_shutdown();
+ int nMethods = 0;
+ int nNatives = 0;
+ int nCanonical = 0;
+ final java.lang.reflect.Method[] declaredMethods =
+ CApi.class.getDeclaredMethods();
+ for(java.lang.reflect.Method m : declaredMethods){
+ final int mod = m.getModifiers();
+ if( 0!=(mod & java.lang.reflect.Modifier.STATIC) ){
+ final String name = m.getName();
+ if(name.startsWith("sqlite3_")){
+ ++nMethods;
+ if( 0!=(mod & java.lang.reflect.Modifier.NATIVE) ){
+ ++nNatives;
+ }
+ }
+ }
+ }
+ outln("\tCApi.sqlite3_*() methods: "+
+ nMethods+" total, with "+
+ nNatives+" native, "+
+ (nMethods - nNatives)+" Java"
+ );
+ outln("\tTotal test time = "
+ +(timeEnd - timeStart)+"ms");
+ }
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java
--- sqlite3-3.41.0-0/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,25 @@
+/*
+** 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 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 classes, as doing so
+ requires a "final" reference.
+*/
+public class ValueHolder {
+ public T value;
+ public ValueHolder(){}
+ public ValueHolder(T v){value = v;}
+}
diff -Nru sqlite3-3.41.0-0/ext/jni/src/tests/000-000-sanity.test sqlite3-3.44.0-0/ext/jni/src/tests/000-000-sanity.test
--- sqlite3-3.41.0-0/ext/jni/src/tests/000-000-sanity.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/tests/000-000-sanity.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,53 @@
+/*
+** This is a comment. There are many like it but this one is mine.
+**
+** SCRIPT_MODULE_NAME: sanity-check
+** xMIXED_MODULE_NAME: mixed-module
+** xMODULE_NAME: module-name
+** xREQUIRED_PROPERTIES: small fast reliable
+** xREQUIRED_PROPERTIES: RECURSIVE_TRIGGERS
+** xREQUIRED_PROPERTIES: TEMPSTORE_FILE TEMPSTORE_MEM
+** xREQUIRED_PROPERTIES: AUTOVACUUM INCRVACUUM
+**
+*/
+--print starting up 😃
+--close all
+--oom
+--db 0
+--new my.db
+--null zilch
+--testcase 1.0
+SELECT 1, null;
+--result 1 zilch
+--glob *zil*
+--notglob *ZIL*
+SELECT 1, 2;
+intentional error;
+--run
+--testcase json-1
+SELECT json_array(1,2,3)
+--json [1,2,3]
+--testcase tableresult-1
+ select 1, 'a';
+ select 2, 'b';
+--tableresult
+ # [a-z]
+ 2 b
+--end
+--testcase json-block-1
+ select json_array(1,2,3);
+ select json_object('a',1,'b',2);
+--json-block
+ [1,2,3]
+ {"a":1,"b":2}
+--end
+--testcase col-names-on
+--column-names 1
+ select 1 as 'a', 2 as 'b';
+--result a 1 b 2
+--testcase col-names-off
+--column-names 0
+ select 1 as 'a', 2 as 'b';
+--result 1 2
+--close
+--print reached the end 😃
diff -Nru sqlite3-3.41.0-0/ext/jni/src/tests/000-001-ignored.test sqlite3-3.44.0-0/ext/jni/src/tests/000-001-ignored.test
--- sqlite3-3.41.0-0/ext/jni/src/tests/000-001-ignored.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/tests/000-001-ignored.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,9 @@
+/*
+** This script must be marked as ignored because it contains
+** content which triggers that condition.
+**
+** SCRIPT_MODULE_NAME: ignored
+**
+*/
+
+|
diff -Nru sqlite3-3.41.0-0/ext/jni/src/tests/900-001-fts.test sqlite3-3.44.0-0/ext/jni/src/tests/900-001-fts.test
--- sqlite3-3.41.0-0/ext/jni/src/tests/900-001-fts.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/tests/900-001-fts.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,12 @@
+/*
+** SCRIPT_MODULE_NAME: fts5-sanity-checks
+** xREQUIRED_PROPERTIES: FTS5
+**
+*/
+
+--testcase 1.0
+CREATE VIRTUAL TABLE email USING fts5(sender, title, body);
+insert into email values('fred','Help!','Dear Sir...');
+insert into email values('barney','Assistance','Dear Madam...');
+select * from email where email match 'assistance';
+--result barney Assistance {Dear Madam...}
diff -Nru sqlite3-3.41.0-0/ext/lsm1/lsm_vtab.c sqlite3-3.44.0-0/ext/lsm1/lsm_vtab.c
--- sqlite3-3.41.0-0/ext/lsm1/lsm_vtab.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/lsm1/lsm_vtab.c 2023-11-04 14:24:27.000000000 +0000
@@ -1061,6 +1061,11 @@
lsm1Rollback, /* xRollback */
0, /* xFindMethod */
0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
diff -Nru sqlite3-3.41.0-0/ext/misc/amatch.c sqlite3-3.44.0-0/ext/misc/amatch.c
--- sqlite3-3.41.0-0/ext/misc/amatch.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/amatch.c 2023-11-04 14:24:27.000000000 +0000
@@ -1475,7 +1475,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
#endif /* SQLITE_OMIT_VIRTUALTABLE */
diff -Nru sqlite3-3.41.0-0/ext/misc/base64.c sqlite3-3.44.0-0/ext/misc/base64.c
--- sqlite3-3.41.0-0/ext/misc/base64.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/base64.c 2023-11-04 14:24:27.000000000 +0000
@@ -76,6 +76,7 @@
#define U8_TYPEDEF
#endif
+/* Decoding table, ASCII (7-bit) value to base 64 digit value or other */
static const u8 b64DigitValues[128] = {
/* HT LF VT FF CR */
ND,ND,ND,ND, ND,ND,ND,ND, ND,WS,WS,WS, WS,WS,ND,ND,
@@ -147,9 +148,9 @@
}
/* Skip over text which is not base64 numeral(s). */
-static char * skipNonB64( char *s ){
+static char * skipNonB64( char *s, int nc ){
char c;
- while( (c = *s) && !IS_BX_DIGIT(BX_DV_PROTO(c)) ) ++s;
+ while( nc-- > 0 && (c = *s) && !IS_BX_DIGIT(BX_DV_PROTO(c)) ) ++s;
return s;
}
@@ -158,7 +159,7 @@
if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn;
while( ncIn>0 && *pIn!=PAD_CHAR ){
static signed char nboi[] = { 0, 0, 1, 2, 3 };
- char *pUse = skipNonB64(pIn);
+ char *pUse = skipNonB64(pIn, ncIn);
unsigned long qv = 0L;
int nti, nbo, nac;
ncIn -= (pUse - pIn);
@@ -218,9 +219,16 @@
sqlite3_result_error(context, "blob expanded to base64 too big", -1);
return;
}
+ bBuf = (u8*)sqlite3_value_blob(av[0]);
+ if( !bBuf ){
+ if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){
+ goto memFail;
+ }
+ sqlite3_result_text(context,"",-1,SQLITE_STATIC);
+ break;
+ }
cBuf = sqlite3_malloc(nc);
if( !cBuf ) goto memFail;
- bBuf = (u8*)sqlite3_value_blob(av[0]);
nc = (int)(toBase64(bBuf, nb, cBuf) - cBuf);
sqlite3_result_text(context, cBuf, nc, sqlite3_free);
break;
@@ -233,9 +241,16 @@
}else if( nb<1 ){
nb = 1;
}
+ cBuf = (char *)sqlite3_value_text(av[0]);
+ if( !cBuf ){
+ if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){
+ goto memFail;
+ }
+ sqlite3_result_zeroblob(context, 0);
+ break;
+ }
bBuf = sqlite3_malloc(nb);
if( !bBuf ) goto memFail;
- cBuf = (char *)sqlite3_value_text(av[0]);
nb = (int)(fromBase64(cBuf, nc, bBuf) - bBuf);
sqlite3_result_blob(context, bBuf, nb, sqlite3_free);
break;
diff -Nru sqlite3-3.41.0-0/ext/misc/base85.c sqlite3-3.44.0-0/ext/misc/base85.c
--- sqlite3-3.41.0-0/ext/misc/base85.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/base85.c 2023-11-04 14:24:27.000000000 +0000
@@ -140,9 +140,9 @@
#define B85_DARK_MAX 80
-static char * skipNonB85( char *s ){
+static char * skipNonB85( char *s, int nc ){
char c;
- while( (c = *s) && !IS_B85(c) ) ++s;
+ while( nc-- > 0 && (c = *s) && !IS_B85(c) ) ++s;
return s;
}
@@ -212,7 +212,7 @@
if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn;
while( ncIn>0 ){
static signed char nboi[] = { 0, 0, 1, 2, 3, 4 };
- char *pUse = skipNonB85(pIn);
+ char *pUse = skipNonB85(pIn, ncIn);
unsigned long qv = 0L;
int nti, nbo;
ncIn -= (pUse - pIn);
@@ -297,9 +297,16 @@
sqlite3_result_error(context, "blob expanded to base85 too big", -1);
return;
}
+ bBuf = (u8*)sqlite3_value_blob(av[0]);
+ if( !bBuf ){
+ if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){
+ goto memFail;
+ }
+ sqlite3_result_text(context,"",-1,SQLITE_STATIC);
+ break;
+ }
cBuf = sqlite3_malloc(nc);
if( !cBuf ) goto memFail;
- bBuf = (u8*)sqlite3_value_blob(av[0]);
nc = (int)(toBase85(bBuf, nb, cBuf, "\n") - cBuf);
sqlite3_result_text(context, cBuf, nc, sqlite3_free);
break;
@@ -312,9 +319,16 @@
}else if( nb<1 ){
nb = 1;
}
+ cBuf = (char *)sqlite3_value_text(av[0]);
+ if( !cBuf ){
+ if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){
+ goto memFail;
+ }
+ sqlite3_result_zeroblob(context, 0);
+ break;
+ }
bBuf = sqlite3_malloc(nb);
if( !bBuf ) goto memFail;
- cBuf = (char *)sqlite3_value_text(av[0]);
nb = (int)(fromBase85(cBuf, nc, bBuf) - bBuf);
sqlite3_result_blob(context, bBuf, nb, sqlite3_free);
break;
diff -Nru sqlite3-3.41.0-0/ext/misc/basexx.c sqlite3-3.44.0-0/ext/misc/basexx.c
--- sqlite3-3.41.0-0/ext/misc/basexx.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/basexx.c 2023-11-04 14:24:27.000000000 +0000
@@ -69,9 +69,12 @@
#endif
int sqlite3_basexx_init(sqlite3 *db, char **pzErr,
const sqlite3_api_routines *pApi){
+ int rc1;
+ int rc2;
+
init_api_ptr(pApi);
- int rc1 = BASE64_INIT(db);
- int rc2 = BASE85_INIT(db);
+ rc1 = BASE64_INIT(db);
+ rc2 = BASE85_INIT(db);
if( rc1==SQLITE_OK && rc2==SQLITE_OK ){
BASE64_EXPOSE(db, pzErr);
diff -Nru sqlite3-3.41.0-0/ext/misc/btreeinfo.c sqlite3-3.44.0-0/ext/misc/btreeinfo.c
--- sqlite3-3.41.0-0/ext/misc/btreeinfo.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/btreeinfo.c 2023-11-04 14:24:27.000000000 +0000
@@ -411,7 +411,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
return sqlite3_create_module(db, "sqlite_btreeinfo", &binfo_module, 0);
}
diff -Nru sqlite3-3.41.0-0/ext/misc/carray.c sqlite3-3.44.0-0/ext/misc/carray.c
--- sqlite3-3.41.0-0/ext/misc/carray.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/carray.c 2023-11-04 14:24:27.000000000 +0000
@@ -409,6 +409,11 @@
0, /* xRollback */
0, /* xFindMethod */
0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0, /* xShadow */
+ 0 /* xIntegrity */
};
/*
diff -Nru sqlite3-3.41.0-0/ext/misc/closure.c sqlite3-3.44.0-0/ext/misc/closure.c
--- sqlite3-3.41.0-0/ext/misc/closure.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/closure.c 2023-11-04 14:24:27.000000000 +0000
@@ -939,7 +939,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
#endif /* SQLITE_OMIT_VIRTUALTABLE */
diff -Nru sqlite3-3.41.0-0/ext/misc/completion.c sqlite3-3.44.0-0/ext/misc/completion.c
--- sqlite3-3.41.0-0/ext/misc/completion.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/completion.c 2023-11-04 14:24:27.000000000 +0000
@@ -470,7 +470,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
#endif /* SQLITE_OMIT_VIRTUALTABLE */
diff -Nru sqlite3-3.41.0-0/ext/misc/csv.c sqlite3-3.44.0-0/ext/misc/csv.c
--- sqlite3-3.41.0-0/ext/misc/csv.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/csv.c 2023-11-04 14:24:27.000000000 +0000
@@ -897,6 +897,11 @@
0, /* xRollback */
0, /* xFindMethod */
0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
#ifdef SQLITE_TEST
@@ -929,6 +934,11 @@
0, /* xRollback */
0, /* xFindMethod */
0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
#endif /* SQLITE_TEST */
diff -Nru sqlite3-3.41.0-0/ext/misc/decimal.c sqlite3-3.44.0-0/ext/misc/decimal.c
--- sqlite3-3.41.0-0/ext/misc/decimal.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/decimal.c 2023-11-04 14:24:27.000000000 +0000
@@ -58,41 +58,24 @@
}
/*
-** Allocate a new Decimal object. Initialize it to the number given
-** by the input string.
+** Allocate a new Decimal object initialized to the text in zIn[].
+** Return NULL if any kind of error occurs.
*/
-static Decimal *decimal_new(
- sqlite3_context *pCtx,
- sqlite3_value *pIn,
- int nAlt,
- const unsigned char *zAlt
-){
- Decimal *p;
- int n, i;
- const unsigned char *zIn;
+static Decimal *decimalNewFromText(const char *zIn, int n){
+ Decimal *p = 0;
+ int i;
int iExp = 0;
+
p = sqlite3_malloc( sizeof(*p) );
- if( p==0 ) goto new_no_mem;
+ if( p==0 ) goto new_from_text_failed;
p->sign = 0;
p->oom = 0;
p->isInit = 1;
p->isNull = 0;
p->nDigit = 0;
p->nFrac = 0;
- if( zAlt ){
- n = nAlt,
- zIn = zAlt;
- }else{
- if( sqlite3_value_type(pIn)==SQLITE_NULL ){
- p->a = 0;
- p->isNull = 1;
- return p;
- }
- n = sqlite3_value_bytes(pIn);
- zIn = sqlite3_value_text(pIn);
- }
p->a = sqlite3_malloc64( n+1 );
- if( p->a==0 ) goto new_no_mem;
+ if( p->a==0 ) goto new_from_text_failed;
for(i=0; isspace(zIn[i]); i++){}
if( zIn[i]=='-' ){
p->sign = 1;
@@ -143,7 +126,7 @@
}
if( iExp>0 ){
p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 );
- if( p->a==0 ) goto new_no_mem;
+ if( p->a==0 ) goto new_from_text_failed;
memset(p->a+p->nDigit, 0, iExp);
p->nDigit += iExp;
}
@@ -162,7 +145,7 @@
}
if( iExp>0 ){
p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 );
- if( p->a==0 ) goto new_no_mem;
+ if( p->a==0 ) goto new_from_text_failed;
memmove(p->a+iExp, p->a, p->nDigit);
memset(p->a, 0, iExp);
p->nDigit += iExp;
@@ -171,7 +154,76 @@
}
return p;
-new_no_mem:
+new_from_text_failed:
+ if( p ){
+ if( p->a ) sqlite3_free(p->a);
+ sqlite3_free(p);
+ }
+ return 0;
+}
+
+/* Forward reference */
+static Decimal *decimalFromDouble(double);
+
+/*
+** Allocate a new Decimal object from an sqlite3_value. Return a pointer
+** to the new object, or NULL if there is an error. If the pCtx argument
+** is not NULL, then errors are reported on it as well.
+**
+** If the pIn argument is SQLITE_TEXT or SQLITE_INTEGER, it is converted
+** directly into a Decimal. For SQLITE_FLOAT or for SQLITE_BLOB of length
+** 8 bytes, the resulting double value is expanded into its decimal equivalent.
+** If pIn is NULL or if it is a BLOB that is not exactly 8 bytes in length,
+** then NULL is returned.
+*/
+static Decimal *decimal_new(
+ sqlite3_context *pCtx, /* Report error here, if not null */
+ sqlite3_value *pIn, /* Construct the decimal object from this */
+ int bTextOnly /* Always interpret pIn as text if true */
+){
+ Decimal *p = 0;
+ int eType = sqlite3_value_type(pIn);
+ if( bTextOnly && (eType==SQLITE_FLOAT || eType==SQLITE_BLOB) ){
+ eType = SQLITE_TEXT;
+ }
+ switch( eType ){
+ case SQLITE_TEXT:
+ case SQLITE_INTEGER: {
+ const char *zIn = (const char*)sqlite3_value_text(pIn);
+ int n = sqlite3_value_bytes(pIn);
+ p = decimalNewFromText(zIn, n);
+ if( p==0 ) goto new_failed;
+ break;
+ }
+
+ case SQLITE_FLOAT: {
+ p = decimalFromDouble(sqlite3_value_double(pIn));
+ break;
+ }
+
+ case SQLITE_BLOB: {
+ const unsigned char *x;
+ unsigned int i;
+ sqlite3_uint64 v = 0;
+ double r;
+
+ if( sqlite3_value_bytes(pIn)!=sizeof(r) ) break;
+ x = sqlite3_value_blob(pIn);
+ for(i=0; ioom ){
+ sqlite3_result_error_nomem(pCtx);
+ return;
+ }
+ if( p->isNull ){
+ sqlite3_result_null(pCtx);
+ return;
+ }
+ for(nDigit=p->nDigit; nDigit>0 && p->a[nDigit-1]==0; nDigit--){}
+ for(nZero=0; nZeroa[nZero]==0; nZero++){}
+ nFrac = p->nFrac + (nDigit - p->nDigit);
+ nDigit -= nZero;
+ z = sqlite3_malloc( nDigit+20 );
+ if( z==0 ){
+ sqlite3_result_error_nomem(pCtx);
+ return;
+ }
+ if( nDigit==0 ){
+ zero = 0;
+ a = &zero;
+ nDigit = 1;
+ nFrac = 0;
+ }else{
+ a = &p->a[nZero];
+ }
+ if( p->sign && nDigit>0 ){
+ z[0] = '-';
+ }else{
+ z[0] = '+';
+ }
+ z[1] = a[0]+'0';
+ z[2] = '.';
+ if( nDigit==1 ){
+ z[3] = '0';
+ i = 4;
+ }else{
+ for(i=1; iisNull ) goto cmp_done;
- pB = decimal_new(context, argv[1], 0, 0);
+ pB = decimal_new(context, argv[1], 1);
if( pB==0 || pB->isNull ) goto cmp_done;
rc = decimal_cmp(pA, pB);
if( rc<0 ) rc = -1;
@@ -338,7 +435,7 @@
}
/*
-** Add the value pB into pA.
+** Add the value pB into pA. A := A + B.
**
** Both pA and pB might become denormalized by this routine.
*/
@@ -408,6 +505,172 @@
}
/*
+** Multiply A by B. A := A * B
+**
+** All significant digits after the decimal point are retained.
+** Trailing zeros after the decimal point are omitted as long as
+** the number of digits after the decimal point is no less than
+** either the number of digits in either input.
+*/
+static void decimalMul(Decimal *pA, Decimal *pB){
+ signed char *acc = 0;
+ int i, j, k;
+ int minFrac;
+
+ if( pA==0 || pA->oom || pA->isNull
+ || pB==0 || pB->oom || pB->isNull
+ ){
+ goto mul_end;
+ }
+ acc = sqlite3_malloc64( pA->nDigit + pB->nDigit + 2 );
+ if( acc==0 ){
+ pA->oom = 1;
+ goto mul_end;
+ }
+ memset(acc, 0, pA->nDigit + pB->nDigit + 2);
+ minFrac = pA->nFrac;
+ if( pB->nFracnFrac;
+ for(i=pA->nDigit-1; i>=0; i--){
+ signed char f = pA->a[i];
+ int carry = 0, x;
+ for(j=pB->nDigit-1, k=i+j+3; j>=0; j--, k--){
+ x = acc[k] + f*pB->a[j] + carry;
+ acc[k] = x%10;
+ carry = x/10;
+ }
+ x = acc[k] + carry;
+ acc[k] = x%10;
+ acc[k-1] += x/10;
+ }
+ sqlite3_free(pA->a);
+ pA->a = acc;
+ acc = 0;
+ pA->nDigit += pB->nDigit + 2;
+ pA->nFrac += pB->nFrac;
+ pA->sign ^= pB->sign;
+ while( pA->nFrac>minFrac && pA->a[pA->nDigit-1]==0 ){
+ pA->nFrac--;
+ pA->nDigit--;
+ }
+
+mul_end:
+ sqlite3_free(acc);
+}
+
+/*
+** Create a new Decimal object that contains an integer power of 2.
+*/
+static Decimal *decimalPow2(int N){
+ Decimal *pA = 0; /* The result to be returned */
+ Decimal *pX = 0; /* Multiplier */
+ if( N<-20000 || N>20000 ) goto pow2_fault;
+ pA = decimalNewFromText("1.0", 3);
+ if( pA==0 || pA->oom ) goto pow2_fault;
+ if( N==0 ) return pA;
+ if( N>0 ){
+ pX = decimalNewFromText("2.0", 3);
+ }else{
+ N = -N;
+ pX = decimalNewFromText("0.5", 3);
+ }
+ if( pX==0 || pX->oom ) goto pow2_fault;
+ while( 1 /* Exit by break */ ){
+ if( N & 1 ){
+ decimalMul(pA, pX);
+ if( pA->oom ) goto pow2_fault;
+ }
+ N >>= 1;
+ if( N==0 ) break;
+ decimalMul(pX, pX);
+ }
+ decimal_free(pX);
+ return pA;
+
+pow2_fault:
+ decimal_free(pA);
+ decimal_free(pX);
+ return 0;
+}
+
+/*
+** Use an IEEE754 binary64 ("double") to generate a new Decimal object.
+*/
+static Decimal *decimalFromDouble(double r){
+ sqlite3_int64 m, a;
+ int e;
+ int isNeg;
+ Decimal *pA;
+ Decimal *pX;
+ char zNum[100];
+ if( r<0.0 ){
+ isNeg = 1;
+ r = -r;
+ }else{
+ isNeg = 0;
+ }
+ memcpy(&a,&r,sizeof(a));
+ if( a==0 ){
+ e = 0;
+ m = 0;
+ }else{
+ e = a>>52;
+ m = a & ((((sqlite3_int64)1)<<52)-1);
+ if( e==0 ){
+ m <<= 1;
+ }else{
+ m |= ((sqlite3_int64)1)<<52;
+ }
+ while( e<1075 && m>0 && (m&1)==0 ){
+ m >>= 1;
+ e++;
+ }
+ if( isNeg ) m = -m;
+ e = e - 1075;
+ if( e>971 ){
+ return 0; /* A NaN or an Infinity */
+ }
+ }
+
+ /* At this point m is the integer significand and e is the exponent */
+ sqlite3_snprintf(sizeof(zNum), zNum, "%lld", m);
+ pA = decimalNewFromText(zNum, (int)strlen(zNum));
+ pX = decimalPow2(e);
+ decimalMul(pA, pX);
+ decimal_free(pX);
+ return pA;
+}
+
+/*
+** SQL Function: decimal(X)
+** OR: decimal_exp(X)
+**
+** Convert input X into decimal and then back into text.
+**
+** If X is originally a float, then a full decimal expansion of that floating
+** point value is done. Or if X is an 8-byte blob, it is interpreted
+** as a float and similarly expanded.
+**
+** The decimal_exp(X) function returns the result in exponential notation.
+** decimal(X) returns a complete decimal, without the e+NNN at the end.
+*/
+static void decimalFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ Decimal *p = decimal_new(context, argv[0], 0);
+ UNUSED_PARAMETER(argc);
+ if( p ){
+ if( sqlite3_user_data(context)!=0 ){
+ decimal_result_sci(context, p);
+ }else{
+ decimal_result(context, p);
+ }
+ decimal_free(p);
+ }
+}
+
+/*
** Compare text in decimal order.
*/
static int decimalCollFunc(
@@ -417,8 +680,8 @@
){
const unsigned char *zA = (const unsigned char*)pKey1;
const unsigned char *zB = (const unsigned char*)pKey2;
- Decimal *pA = decimal_new(0, 0, nKey1, zA);
- Decimal *pB = decimal_new(0, 0, nKey2, zB);
+ Decimal *pA = decimalNewFromText((const char*)zA, nKey1);
+ Decimal *pB = decimalNewFromText((const char*)zB, nKey2);
int rc;
UNUSED_PARAMETER(notUsed);
if( pA==0 || pB==0 ){
@@ -443,8 +706,8 @@
int argc,
sqlite3_value **argv
){
- Decimal *pA = decimal_new(context, argv[0], 0, 0);
- Decimal *pB = decimal_new(context, argv[1], 0, 0);
+ Decimal *pA = decimal_new(context, argv[0], 1);
+ Decimal *pB = decimal_new(context, argv[1], 1);
UNUSED_PARAMETER(argc);
decimal_add(pA, pB);
decimal_result(context, pA);
@@ -456,8 +719,8 @@
int argc,
sqlite3_value **argv
){
- Decimal *pA = decimal_new(context, argv[0], 0, 0);
- Decimal *pB = decimal_new(context, argv[1], 0, 0);
+ Decimal *pA = decimal_new(context, argv[0], 1);
+ Decimal *pB = decimal_new(context, argv[1], 1);
UNUSED_PARAMETER(argc);
if( pB ){
pB->sign = !pB->sign;
@@ -495,7 +758,7 @@
p->nFrac = 0;
}
if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
- pArg = decimal_new(context, argv[0], 0, 0);
+ pArg = decimal_new(context, argv[0], 1);
decimal_add(p, pArg);
decimal_free(pArg);
}
@@ -510,7 +773,7 @@
p = sqlite3_aggregate_context(context, sizeof(*p));
if( p==0 ) return;
if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
- pArg = decimal_new(context, argv[0], 0, 0);
+ pArg = decimal_new(context, argv[0], 1);
if( pArg ) pArg->sign = !pArg->sign;
decimal_add(p, pArg);
decimal_free(pArg);
@@ -531,66 +794,49 @@
** SQL Function: decimal_mul(X, Y)
**
** Return the product of X and Y.
-**
-** All significant digits after the decimal point are retained.
-** Trailing zeros after the decimal point are omitted as long as
-** the number of digits after the decimal point is no less than
-** either the number of digits in either input.
*/
static void decimalMulFunc(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
- Decimal *pA = decimal_new(context, argv[0], 0, 0);
- Decimal *pB = decimal_new(context, argv[1], 0, 0);
- signed char *acc = 0;
- int i, j, k;
- int minFrac;
+ Decimal *pA = decimal_new(context, argv[0], 1);
+ Decimal *pB = decimal_new(context, argv[1], 1);
UNUSED_PARAMETER(argc);
if( pA==0 || pA->oom || pA->isNull
|| pB==0 || pB->oom || pB->isNull
){
goto mul_end;
}
- acc = sqlite3_malloc64( pA->nDigit + pB->nDigit + 2 );
- if( acc==0 ){
- sqlite3_result_error_nomem(context);
+ decimalMul(pA, pB);
+ if( pA->oom ){
goto mul_end;
}
- memset(acc, 0, pA->nDigit + pB->nDigit + 2);
- minFrac = pA->nFrac;
- if( pB->nFracnFrac;
- for(i=pA->nDigit-1; i>=0; i--){
- signed char f = pA->a[i];
- int carry = 0, x;
- for(j=pB->nDigit-1, k=i+j+3; j>=0; j--, k--){
- x = acc[k] + f*pB->a[j] + carry;
- acc[k] = x%10;
- carry = x/10;
- }
- x = acc[k] + carry;
- acc[k] = x%10;
- acc[k-1] += x/10;
- }
- sqlite3_free(pA->a);
- pA->a = acc;
- acc = 0;
- pA->nDigit += pB->nDigit + 2;
- pA->nFrac += pB->nFrac;
- pA->sign ^= pB->sign;
- while( pA->nFrac>minFrac && pA->a[pA->nDigit-1]==0 ){
- pA->nFrac--;
- pA->nDigit--;
- }
decimal_result(context, pA);
mul_end:
- sqlite3_free(acc);
decimal_free(pA);
decimal_free(pB);
}
+/*
+** SQL Function: decimal_pow2(N)
+**
+** Return the N-th power of 2. N must be an integer.
+*/
+static void decimalPow2Func(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ UNUSED_PARAMETER(argc);
+ if( sqlite3_value_type(argv[0])==SQLITE_INTEGER ){
+ Decimal *pA = decimalPow2(sqlite3_value_int(argv[0]));
+ decimal_result_sci(context, pA);
+ decimal_free(pA);
+ }
+}
+
#ifdef _WIN32
__declspec(dllexport)
#endif
@@ -603,13 +849,16 @@
static const struct {
const char *zFuncName;
int nArg;
+ int iArg;
void (*xFunc)(sqlite3_context*,int,sqlite3_value**);
} aFunc[] = {
- { "decimal", 1, decimalFunc },
- { "decimal_cmp", 2, decimalCmpFunc },
- { "decimal_add", 2, decimalAddFunc },
- { "decimal_sub", 2, decimalSubFunc },
- { "decimal_mul", 2, decimalMulFunc },
+ { "decimal", 1, 0, decimalFunc },
+ { "decimal_exp", 1, 1, decimalFunc },
+ { "decimal_cmp", 2, 0, decimalCmpFunc },
+ { "decimal_add", 2, 0, decimalAddFunc },
+ { "decimal_sub", 2, 0, decimalSubFunc },
+ { "decimal_mul", 2, 0, decimalMulFunc },
+ { "decimal_pow2", 1, 0, decimalPow2Func },
};
unsigned int i;
(void)pzErrMsg; /* Unused parameter */
@@ -619,7 +868,7 @@
for(i=0; i<(int)(sizeof(aFunc)/sizeof(aFunc[0])) && rc==SQLITE_OK; i++){
rc = sqlite3_create_function(db, aFunc[i].zFuncName, aFunc[i].nArg,
SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC,
- 0, aFunc[i].xFunc, 0, 0);
+ aFunc[i].iArg ? db : 0, aFunc[i].xFunc, 0, 0);
}
if( rc==SQLITE_OK ){
rc = sqlite3_create_window_function(db, "decimal_sum", 1,
diff -Nru sqlite3-3.41.0-0/ext/misc/explain.c sqlite3-3.44.0-0/ext/misc/explain.c
--- sqlite3-3.41.0-0/ext/misc/explain.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/explain.c 2023-11-04 14:24:27.000000000 +0000
@@ -293,6 +293,7 @@
0, /* xRelease */
0, /* xRollbackTo */
0, /* xShadowName */
+ 0 /* xIntegrity */
};
#endif /* SQLITE_OMIT_VIRTUALTABLE */
diff -Nru sqlite3-3.41.0-0/ext/misc/fileio.c sqlite3-3.44.0-0/ext/misc/fileio.c
--- sqlite3-3.41.0-0/ext/misc/fileio.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/fileio.c 2023-11-04 14:24:27.000000000 +0000
@@ -983,6 +983,7 @@
0, /* xRelease */
0, /* xRollbackTo */
0, /* xShadowName */
+ 0 /* xIntegrity */
};
int rc = sqlite3_create_module(db, "fsdir", &fsdirModule, 0);
diff -Nru sqlite3-3.41.0-0/ext/misc/fossildelta.c sqlite3-3.44.0-0/ext/misc/fossildelta.c
--- sqlite3-3.41.0-0/ext/misc/fossildelta.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/fossildelta.c 2023-11-04 14:24:27.000000000 +0000
@@ -1058,7 +1058,8 @@
/* xSavepoint */ 0,
/* xRelease */ 0,
/* xRollbackTo */ 0,
- /* xShadowName */ 0
+ /* xShadowName */ 0,
+ /* xIntegrity */ 0
};
diff -Nru sqlite3-3.41.0-0/ext/misc/fuzzer.c sqlite3-3.44.0-0/ext/misc/fuzzer.c
--- sqlite3-3.41.0-0/ext/misc/fuzzer.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/fuzzer.c 2023-11-04 14:24:27.000000000 +0000
@@ -1165,6 +1165,11 @@
0, /* xRollback */
0, /* xFindMethod */
0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
#endif /* SQLITE_OMIT_VIRTUALTABLE */
diff -Nru sqlite3-3.41.0-0/ext/misc/ieee754.c sqlite3-3.44.0-0/ext/misc/ieee754.c
--- sqlite3-3.41.0-0/ext/misc/ieee754.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/ieee754.c 2023-11-04 14:24:27.000000000 +0000
@@ -256,6 +256,37 @@
}
}
+/*
+** SQL Function: ieee754_inc(r,N)
+**
+** Move the floating point value r by N quantums and return the new
+** values.
+**
+** Behind the scenes: this routine merely casts r into a 64-bit unsigned
+** integer, adds N, then casts the value back into float.
+**
+** Example: To find the smallest positive number:
+**
+** SELECT ieee754_inc(0.0,+1);
+*/
+static void ieee754inc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ double r;
+ sqlite3_int64 N;
+ sqlite3_uint64 m1, m2;
+ double r2;
+ UNUSED_PARAMETER(argc);
+ r = sqlite3_value_double(argv[0]);
+ N = sqlite3_value_int64(argv[1]);
+ memcpy(&m1, &r, 8);
+ m2 = m1 + N;
+ memcpy(&r2, &m2, 8);
+ sqlite3_result_double(context, r2);
+}
+
#ifdef _WIN32
__declspec(dllexport)
@@ -277,7 +308,7 @@
{ "ieee754_exponent", 1, 2, ieee754func },
{ "ieee754_to_blob", 1, 0, ieee754func_to_blob },
{ "ieee754_from_blob", 1, 0, ieee754func_from_blob },
-
+ { "ieee754_inc", 2, 0, ieee754inc },
};
unsigned int i;
int rc = SQLITE_OK;
diff -Nru sqlite3-3.41.0-0/ext/misc/memstat.c sqlite3-3.44.0-0/ext/misc/memstat.c
--- sqlite3-3.41.0-0/ext/misc/memstat.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/memstat.c 2023-11-04 14:24:27.000000000 +0000
@@ -396,6 +396,7 @@
0, /* xRelease */
0, /* xRollbackTo */
0, /* xShadowName */
+ 0 /* xIntegrity */
};
#endif /* SQLITE_OMIT_VIRTUALTABLE */
diff -Nru sqlite3-3.41.0-0/ext/misc/mmapwarm.c sqlite3-3.44.0-0/ext/misc/mmapwarm.c
--- sqlite3-3.41.0-0/ext/misc/mmapwarm.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/mmapwarm.c 2023-11-04 14:24:27.000000000 +0000
@@ -38,7 +38,7 @@
int rc = SQLITE_OK;
char *zSql = 0;
int pgsz = 0;
- int nTotal = 0;
+ unsigned int nTotal = 0;
if( 0==sqlite3_get_autocommit(db) ) return SQLITE_MISUSE;
@@ -86,8 +86,8 @@
rc = p->xFetch(pFd, pgsz*iPg, pgsz, (void**)&pMap);
if( rc!=SQLITE_OK || pMap==0 ) break;
- nTotal += pMap[0];
- nTotal += pMap[pgsz-1];
+ nTotal += (unsigned int)pMap[0];
+ nTotal += (unsigned int)pMap[pgsz-1];
rc = p->xUnfetch(pFd, pgsz*iPg, (void*)pMap);
if( rc!=SQLITE_OK ) break;
@@ -103,5 +103,6 @@
if( rc==SQLITE_OK ) rc = rc2;
}
+ (void)nTotal;
return rc;
}
diff -Nru sqlite3-3.41.0-0/ext/misc/pcachetrace.c sqlite3-3.44.0-0/ext/misc/pcachetrace.c
--- sqlite3-3.41.0-0/ext/misc/pcachetrace.c 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/pcachetrace.c 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,179 @@
+/*
+** 2023-06-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This file implements an extension that uses the SQLITE_CONFIG_PCACHE2
+** mechanism to add a tracing layer on top of pluggable page cache of
+** SQLite. If this extension is registered prior to sqlite3_initialize(),
+** it will cause all page cache activities to be logged on standard output,
+** or to some other FILE specified by the initializer.
+**
+** This file needs to be compiled into the application that uses it.
+**
+** This extension is used to implement the --pcachetrace option of the
+** command-line shell.
+*/
+#include
+#include
+#include
+
+/* The original page cache routines */
+static sqlite3_pcache_methods2 pcacheBase;
+static FILE *pcachetraceOut;
+
+/* Methods that trace pcache activity */
+static int pcachetraceInit(void *pArg){
+ int nRes;
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xInit(%p)\n", pArg);
+ }
+ nRes = pcacheBase.xInit(pArg);
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xInit(%p) -> %d\n", pArg, nRes);
+ }
+ return nRes;
+}
+static void pcachetraceShutdown(void *pArg){
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xShutdown(%p)\n", pArg);
+ }
+ pcacheBase.xShutdown(pArg);
+}
+static sqlite3_pcache *pcachetraceCreate(int szPage, int szExtra, int bPurge){
+ sqlite3_pcache *pRes;
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xCreate(%d,%d,%d)\n",
+ szPage, szExtra, bPurge);
+ }
+ pRes = pcacheBase.xCreate(szPage, szExtra, bPurge);
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xCreate(%d,%d,%d) -> %p\n",
+ szPage, szExtra, bPurge, pRes);
+ }
+ return pRes;
+}
+static void pcachetraceCachesize(sqlite3_pcache *p, int nCachesize){
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xCachesize(%p, %d)\n", p, nCachesize);
+ }
+ pcacheBase.xCachesize(p, nCachesize);
+}
+static int pcachetracePagecount(sqlite3_pcache *p){
+ int nRes;
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xPagecount(%p)\n", p);
+ }
+ nRes = pcacheBase.xPagecount(p);
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xPagecount(%p) -> %d\n", p, nRes);
+ }
+ return nRes;
+}
+static sqlite3_pcache_page *pcachetraceFetch(
+ sqlite3_pcache *p,
+ unsigned key,
+ int crFg
+){
+ sqlite3_pcache_page *pRes;
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xFetch(%p,%u,%d)\n", p, key, crFg);
+ }
+ pRes = pcacheBase.xFetch(p, key, crFg);
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xFetch(%p,%u,%d) -> %p\n",
+ p, key, crFg, pRes);
+ }
+ return pRes;
+}
+static void pcachetraceUnpin(
+ sqlite3_pcache *p,
+ sqlite3_pcache_page *pPg,
+ int bDiscard
+){
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xUnpin(%p, %p, %d)\n",
+ p, pPg, bDiscard);
+ }
+ pcacheBase.xUnpin(p, pPg, bDiscard);
+}
+static void pcachetraceRekey(
+ sqlite3_pcache *p,
+ sqlite3_pcache_page *pPg,
+ unsigned oldKey,
+ unsigned newKey
+){
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xRekey(%p, %p, %u, %u)\n",
+ p, pPg, oldKey, newKey);
+ }
+ pcacheBase.xRekey(p, pPg, oldKey, newKey);
+}
+static void pcachetraceTruncate(sqlite3_pcache *p, unsigned n){
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xTruncate(%p, %u)\n", p, n);
+ }
+ pcacheBase.xTruncate(p, n);
+}
+static void pcachetraceDestroy(sqlite3_pcache *p){
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xDestroy(%p)\n", p);
+ }
+ pcacheBase.xDestroy(p);
+}
+static void pcachetraceShrink(sqlite3_pcache *p){
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xShrink(%p)\n", p);
+ }
+ pcacheBase.xShrink(p);
+}
+
+/* The substitute pcache methods */
+static sqlite3_pcache_methods2 ersaztPcacheMethods = {
+ 0,
+ 0,
+ pcachetraceInit,
+ pcachetraceShutdown,
+ pcachetraceCreate,
+ pcachetraceCachesize,
+ pcachetracePagecount,
+ pcachetraceFetch,
+ pcachetraceUnpin,
+ pcachetraceRekey,
+ pcachetraceTruncate,
+ pcachetraceDestroy,
+ pcachetraceShrink
+};
+
+/* Begin tracing memory allocations to out. */
+int sqlite3PcacheTraceActivate(FILE *out){
+ int rc = SQLITE_OK;
+ if( pcacheBase.xFetch==0 ){
+ rc = sqlite3_config(SQLITE_CONFIG_GETPCACHE2, &pcacheBase);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_config(SQLITE_CONFIG_PCACHE2, &ersaztPcacheMethods);
+ }
+ }
+ pcachetraceOut = out;
+ return rc;
+}
+
+/* Deactivate memory tracing */
+int sqlite3PcacheTraceDeactivate(void){
+ int rc = SQLITE_OK;
+ if( pcacheBase.xFetch!=0 ){
+ rc = sqlite3_config(SQLITE_CONFIG_PCACHE2, &pcacheBase);
+ if( rc==SQLITE_OK ){
+ memset(&pcacheBase, 0, sizeof(pcacheBase));
+ }
+ }
+ pcachetraceOut = 0;
+ return rc;
+}
diff -Nru sqlite3-3.41.0-0/ext/misc/prefixes.c sqlite3-3.44.0-0/ext/misc/prefixes.c
--- sqlite3-3.41.0-0/ext/misc/prefixes.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/prefixes.c 2023-11-04 14:24:27.000000000 +0000
@@ -248,7 +248,8 @@
/* xSavepoint */ 0,
/* xRelease */ 0,
/* xRollbackTo */ 0,
- /* xShadowName */ 0
+ /* xShadowName */ 0,
+ /* xIntegrity */ 0
};
/*
diff -Nru sqlite3-3.41.0-0/ext/misc/qpvtab.c sqlite3-3.44.0-0/ext/misc/qpvtab.c
--- sqlite3-3.41.0-0/ext/misc/qpvtab.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/qpvtab.c 2023-11-04 14:24:27.000000000 +0000
@@ -439,7 +439,8 @@
/* xSavepoint */ 0,
/* xRelease */ 0,
/* xRollbackTo */ 0,
- /* xShadowName */ 0
+ /* xShadowName */ 0,
+ /* xIntegrity */ 0
};
#endif /* SQLITE_OMIT_VIRTUALTABLE */
diff -Nru sqlite3-3.41.0-0/ext/misc/randomjson.c sqlite3-3.44.0-0/ext/misc/randomjson.c
--- sqlite3-3.41.0-0/ext/misc/randomjson.c 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/randomjson.c 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,202 @@
+/*
+** 2023-04-28
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This SQLite extension implements a the random_json(SEED) and
+** random_json5(SEED) functions. Given a numeric SEED value, these
+** routines generate pseudo-random JSON or JSON5, respectively. The
+** same value is always generated for the same seed.
+**
+** These SQL functions are intended for testing. They do not have any
+** practical real-world use, that we know of.
+**
+** COMPILE:
+**
+** gcc --shared -fPIC -o randomjson.so -I. ext/misc/randomjson.c
+**
+** USING FROM THE CLI:
+**
+** .load ./randomjson
+** SELECT random_json(1);
+*/
+#include "sqlite3ext.h"
+SQLITE_EXTENSION_INIT1
+#include
+#include
+#include
+
+/* Pseudo-random number generator */
+typedef struct Prng {
+ unsigned int x, y;
+} Prng;
+
+/* Reseed the PRNG */
+static void prngSeed(Prng *p, unsigned int iSeed){
+ p->x = iSeed | 1;
+ p->y = iSeed;
+}
+
+/* Extract a random number */
+static unsigned int prngInt(Prng *p){
+ p->x = (p->x>>1) ^ ((1+~(p->x&1)) & 0xd0000001);
+ p->y = p->y*1103515245 + 12345;
+ return p->x ^ p->y;
+}
+
+static const char *azJsonAtoms[] = {
+ /* JSON /* JSON-5 */
+ "0", "0",
+ "1", "1",
+ "-1", "-1",
+ "2", "+2",
+ "3", "3",
+ "2.5", "2.5",
+ "0.75", ".75",
+ "-4.0e2", "-4.e2",
+ "5.0e-3", "+5e-3",
+ "0", "0x0",
+ "512", "0x200",
+ "256", "+0x100",
+ "-2748", "-0xabc",
+ "true", "true",
+ "false", "false",
+ "null", "null",
+ "9.0e999", "Infinity",
+ "-9.0e999", "-Infinity",
+ "9.0e999", "+Infinity",
+ "null", "NaN",
+ "-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\"",
+ "{}", "{}",
+ "[]", "[]",
+ "[]", "[/*empty*/]",
+ "{}", "{//empty\n}",
+ "\"ask\"", "\"ask\"",
+ "\"bag\"", "\"bag\"",
+ "\"can\"", "\"can\"",
+ "\"day\"", "\"day\"",
+ "\"end\"", "'end'",
+ "\"fly\"", "\"fly\"",
+ "\"\"", "\"\"",
+};
+static const char *azJsonTemplate[] = {
+ /* JSON JSON-5 */
+ "{\"a\":%,\"b\":%,\"c\":%}", "{a:%,b:%,c:%}",
+ "{\"a\":%,\"b\":%,\"c\":%,\"d\":%,\"e\":%}", "{a:%,b:%,c:%,d:%,e:%}",
+ "{\"a\":%,\"b\":%,\"c\":%,\"d\":%,\"\":%}", "{a:%,b:%,c:%,d:%,\"\":%}",
+ "{\"d\":%}", "{d:%}",
+ "{\"eeee\":%, \"ffff\":%}", "{eeee:% /*and*/, ffff:%}",
+ "{\"$g\":%,\"_h_\":%}", "{$g:%,_h_:%,}",
+ "{\"x\":%,\n \"y\":%}", "{\"x\":%,\n \"y\":%}",
+ "{\"a b c d\":%,\"e\":%,\"f\":%,\"x\":%,\"y\":%}",
+ "{\"a b c d\":%,e:%,f:%,x:%,y:%}",
+ "{\"Z\":%}", "{Z:%,}",
+ "[%]", "[%,]",
+ "[%,%]", "[%,%]",
+ "[%,%,%]", "[%,%,%,]",
+ "[%,%,%,%]", "[%,%,%,%]",
+ "[%,%,%,%,%]", "[%,%,%,%,%]",
+};
+
+#define count(X) (sizeof(X)/sizeof(X[0]))
+
+#define STRSZ 10000
+
+static void jsonExpand(
+ const char *zSrc,
+ char *zDest,
+ Prng *p,
+ int eType, /* 0 for JSON, 1 for JSON5 */
+ unsigned int r /* Growth probability 0..1000. 0 means no growth */
+){
+ unsigned int i, j, k;
+ const char *z;
+ size_t n;
+
+ j = 0;
+ 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]!='%' ){
+ if( jnState;
+ unsigned int iFirst = p->nState;
if( rePeek(p)=='^' ){
re_append(p, RE_OP_CC_EXC, 0);
p->sIn.i++;
@@ -619,7 +619,7 @@
if( rePeek(p)==']' ){ p->sIn.i++; break; }
}
if( c==0 ) return "unclosed '['";
- p->aArg[iFirst] = p->nState - iFirst;
+ if( p->nState>iFirst ) p->aArg[iFirst] = p->nState - iFirst;
break;
}
case '\\': {
diff -Nru sqlite3-3.41.0-0/ext/misc/series.c sqlite3-3.44.0-0/ext/misc/series.c
--- sqlite3-3.41.0-0/ext/misc/series.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/series.c 2023-11-04 14:24:27.000000000 +0000
@@ -1,5 +1,5 @@
/*
-** 2015-08-18
+** 2015-08-18, 2023-04-28
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
@@ -12,7 +12,19 @@
**
** This file demonstrates how to create a table-valued-function using
** a virtual table. This demo implements the generate_series() function
-** which gives similar results to the eponymous function in PostgreSQL.
+** which gives the same results as the eponymous function in PostgreSQL,
+** within the limitation that its arguments are signed 64-bit integers.
+**
+** Considering its equivalents to generate_series(start,stop,step): A
+** value V[n] sequence is produced for integer n ascending from 0 where
+** ( V[n] == start + n * step && sgn(V[n] - stop) * sgn(step) >= 0 )
+** for each produced value (independent of production time ordering.)
+**
+** All parameters must be either integer or convertable to integer.
+** The start parameter is required.
+** The stop parameter defaults to (1<<32)-1 (aka 4294967295 or 0xffffffff)
+** The step parameter defaults to 1 and 0 is treated as 1.
+**
** Examples:
**
** SELECT * FROM generate_series(0,100,5);
@@ -28,6 +40,14 @@
**
** Integers 20 through 29.
**
+** SELECT * FROM generate_series(0,-100,-5);
+**
+** Integers 0 -5 -10 ... -100.
+**
+** SELECT * FROM generate_series(0,-1);
+**
+** Empty sequence.
+**
** HOW IT WORKS
**
** The generate_series "function" is really a virtual table with the
@@ -40,6 +60,9 @@
** step HIDDEN
** );
**
+** The virtual table also has a rowid, logically equivalent to n+1 where
+** "n" is the ascending integer in the aforesaid production definition.
+**
** Function arguments in queries against this virtual table are translated
** into equality constraints against successive hidden columns. In other
** words, the following pairs of queries are equivalent to each other:
@@ -72,9 +95,126 @@
SQLITE_EXTENSION_INIT1
#include
#include
+#include
#ifndef SQLITE_OMIT_VIRTUALTABLE
+/*
+** Return that member of a generate_series(...) sequence whose 0-based
+** 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){
+ if( ix>=(sqlite3_uint64)LLONG_MAX ){
+ /* Get ix into signed i64 range. */
+ 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 += (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. */
+ if( ix>=2 ){
+ sqlite3_int64 ix2 = (sqlite3_int64)ix/2;
+ smBase += ix2*smStep;
+ ix -= ix2;
+ }
+ return smBase + ((sqlite3_int64)ix)*smStep;
+}
+typedef unsigned char u8;
+
+typedef struct SequenceSpec {
+ sqlite3_int64 iBase; /* Starting value ("start") */
+ sqlite3_int64 iTerm; /* Given terminal value ("stop") */
+ sqlite3_int64 iStep; /* Increment ("step") */
+ sqlite3_uint64 uSeqIndexMax; /* maximum sequence index (aka "n") */
+ sqlite3_uint64 uSeqIndexNow; /* Current index during generation */
+ sqlite3_int64 iValueNow; /* Current value during generation */
+ u8 isNotEOF; /* Sequence generation not exhausted */
+ u8 isReversing; /* Sequence is being reverse generated */
+} SequenceSpec;
+
+/*
+** Prepare a SequenceSpec for use in generating an integer series
+** given initialized iBase, iTerm and iStep values. Sequence is
+** initialized per given isReversing. Other members are computed.
+*/
+static void setupSequence( SequenceSpec *pss ){
+ int bSameSigns;
+ pss->uSeqIndexMax = 0;
+ pss->isNotEOF = 0;
+ bSameSigns = (pss->iBase < 0)==(pss->iTerm < 0);
+ if( pss->iTerm < pss->iBase ){
+ sqlite3_uint64 nuspan = 0;
+ if( bSameSigns ){
+ nuspan = (sqlite3_uint64)(pss->iBase - pss->iTerm);
+ }else{
+ /* Under UBSAN (or on 1's complement machines), must do this in steps.
+ * In this clause, iBase>=0 and iTerm<0 . */
+ nuspan = 1;
+ nuspan += pss->iBase;
+ nuspan += -(pss->iTerm+1);
+ }
+ if( pss->iStep<0 ){
+ pss->isNotEOF = 1;
+ if( nuspan==ULONG_MAX ){
+ pss->uSeqIndexMax = ( pss->iStep>LLONG_MIN )? nuspan/-pss->iStep : 1;
+ }else if( pss->iStep>LLONG_MIN ){
+ pss->uSeqIndexMax = nuspan/-pss->iStep;
+ }
+ }
+ }else if( pss->iTerm > pss->iBase ){
+ sqlite3_uint64 puspan = 0;
+ if( bSameSigns ){
+ puspan = (sqlite3_uint64)(pss->iTerm - pss->iBase);
+ }else{
+ /* Under UBSAN (or on 1's complement machines), must do this in steps.
+ * In this clause, iTerm>=0 and iBase<0 . */
+ puspan = 1;
+ puspan += pss->iTerm;
+ puspan += -(pss->iBase+1);
+ }
+ if( pss->iStep>0 ){
+ pss->isNotEOF = 1;
+ pss->uSeqIndexMax = puspan/pss->iStep;
+ }
+ }else if( pss->iTerm == pss->iBase ){
+ pss->isNotEOF = 1;
+ pss->uSeqIndexMax = 0;
+ }
+ pss->uSeqIndexNow = (pss->isReversing)? pss->uSeqIndexMax : 0;
+ pss->iValueNow = (pss->isReversing)
+ ? genSeqMember(pss->iBase, pss->iStep, pss->uSeqIndexMax)
+ : pss->iBase;
+}
+
+/*
+** Progress sequence generator to yield next value, if any.
+** Leave its state to either yield next value or be at EOF.
+** Return whether there is a next value, or 0 at EOF.
+*/
+static int progressSequence( SequenceSpec *pss ){
+ if( !pss->isNotEOF ) return 0;
+ if( pss->isReversing ){
+ if( pss->uSeqIndexNow > 0 ){
+ pss->uSeqIndexNow--;
+ pss->iValueNow -= pss->iStep;
+ }else{
+ pss->isNotEOF = 0;
+ }
+ }else{
+ if( pss->uSeqIndexNow < pss->uSeqIndexMax ){
+ pss->uSeqIndexNow++;
+ pss->iValueNow += pss->iStep;
+ }else{
+ pss->isNotEOF = 0;
+ }
+ }
+ return pss->isNotEOF;
+}
/* series_cursor is a subclass of sqlite3_vtab_cursor which will
** serve as the underlying representation of a cursor that scans
@@ -83,12 +223,7 @@
typedef struct series_cursor series_cursor;
struct series_cursor {
sqlite3_vtab_cursor base; /* Base class - must be first */
- int isDesc; /* True to count down rather than up */
- sqlite3_int64 iRowid; /* The rowid */
- sqlite3_int64 iValue; /* Current value ("value") */
- sqlite3_int64 mnValue; /* Mimimum value ("start") */
- sqlite3_int64 mxValue; /* Maximum value ("stop") */
- sqlite3_int64 iStep; /* Increment ("step") */
+ SequenceSpec ss; /* (this) Derived class data */
};
/*
@@ -170,12 +305,7 @@
*/
static int seriesNext(sqlite3_vtab_cursor *cur){
series_cursor *pCur = (series_cursor*)cur;
- if( pCur->isDesc ){
- pCur->iValue -= pCur->iStep;
- }else{
- pCur->iValue += pCur->iStep;
- }
- pCur->iRowid++;
+ progressSequence( & pCur->ss );
return SQLITE_OK;
}
@@ -191,23 +321,27 @@
series_cursor *pCur = (series_cursor*)cur;
sqlite3_int64 x = 0;
switch( i ){
- case SERIES_COLUMN_START: x = pCur->mnValue; break;
- case SERIES_COLUMN_STOP: x = pCur->mxValue; break;
- case SERIES_COLUMN_STEP: x = pCur->iStep; break;
- default: x = pCur->iValue; break;
+ case SERIES_COLUMN_START: x = pCur->ss.iBase; break;
+ case SERIES_COLUMN_STOP: x = pCur->ss.iTerm; break;
+ case SERIES_COLUMN_STEP: x = pCur->ss.iStep; break;
+ default: x = pCur->ss.iValueNow; break;
}
sqlite3_result_int64(ctx, x);
return SQLITE_OK;
}
+#ifndef LARGEST_UINT64
+#define LARGEST_UINT64 (0xffffffff|(((sqlite3_uint64)0xffffffff)<<32))
+#endif
+
/*
-** Return the rowid for the current row. In this implementation, the
-** first row returned is assigned rowid value 1, and each subsequent
-** row a value 1 more than that of the previous.
+** Return the rowid for the current row, logically equivalent to n+1 where
+** "n" is the ascending integer in the aforesaid production definition.
*/
static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
series_cursor *pCur = (series_cursor*)cur;
- *pRowid = pCur->iRowid;
+ sqlite3_uint64 n = pCur->ss.uSeqIndexNow;
+ *pRowid = (sqlite3_int64)((nisDesc ){
- return pCur->iValue < pCur->mnValue;
- }else{
- return pCur->iValue > pCur->mxValue;
- }
+ return !pCur->ss.isNotEOF;
}
-/* True to cause run-time checking of the start=, stop=, and/or step=
+/* True to cause run-time checking of the start=, stop=, and/or step=
** parameters. The only reason to do this is for testing the
** constraint checking logic for virtual tables in the SQLite core.
*/
@@ -235,7 +365,7 @@
/*
** This method is called to "rewind" the series_cursor object back
** to the first row of output. This method is always called at least
-** once prior to any call to seriesColumn() or seriesRowid() or
+** once prior to any call to seriesColumn() or seriesRowid() or
** seriesEof().
**
** The query plan selected by seriesBestIndex is passed in the idxNum
@@ -255,7 +385,7 @@
** (so that seriesEof() will return true) if the table is empty.
*/
static int seriesFilter(
- sqlite3_vtab_cursor *pVtabCursor,
+ sqlite3_vtab_cursor *pVtabCursor,
int idxNum, const char *idxStrUnused,
int argc, sqlite3_value **argv
){
@@ -263,46 +393,41 @@
int i = 0;
(void)idxStrUnused;
if( idxNum & 1 ){
- pCur->mnValue = sqlite3_value_int64(argv[i++]);
+ pCur->ss.iBase = sqlite3_value_int64(argv[i++]);
}else{
- pCur->mnValue = 0;
+ pCur->ss.iBase = 0;
}
if( idxNum & 2 ){
- pCur->mxValue = sqlite3_value_int64(argv[i++]);
+ pCur->ss.iTerm = sqlite3_value_int64(argv[i++]);
}else{
- pCur->mxValue = 0xffffffff;
+ pCur->ss.iTerm = 0xffffffff;
}
if( idxNum & 4 ){
- pCur->iStep = sqlite3_value_int64(argv[i++]);
- if( pCur->iStep==0 ){
- pCur->iStep = 1;
- }else if( pCur->iStep<0 ){
- pCur->iStep = -pCur->iStep;
+ pCur->ss.iStep = sqlite3_value_int64(argv[i++]);
+ if( pCur->ss.iStep==0 ){
+ pCur->ss.iStep = 1;
+ }else if( pCur->ss.iStep<0 ){
if( (idxNum & 16)==0 ) idxNum |= 8;
}
}else{
- pCur->iStep = 1;
+ pCur->ss.iStep = 1;
}
for(i=0; imnValue = 1;
- pCur->mxValue = 0;
+ pCur->ss.iBase = 1;
+ pCur->ss.iTerm = 0;
+ pCur->ss.iStep = 1;
break;
}
}
if( idxNum & 8 ){
- pCur->isDesc = 1;
- pCur->iValue = pCur->mxValue;
- if( pCur->iStep>0 ){
- pCur->iValue -= (pCur->mxValue - pCur->mnValue)%pCur->iStep;
- }
+ pCur->ss.isReversing = pCur->ss.iStep > 0;
}else{
- pCur->isDesc = 0;
- pCur->iValue = pCur->mnValue;
+ pCur->ss.isReversing = pCur->ss.iStep < 0;
}
- pCur->iRowid = 1;
+ setupSequence( &pCur->ss );
return SQLITE_OK;
}
@@ -432,7 +557,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
#endif /* SQLITE_OMIT_VIRTUALTABLE */
diff -Nru sqlite3-3.41.0-0/ext/misc/shathree.c sqlite3-3.44.0-0/ext/misc/shathree.c
--- sqlite3-3.41.0-0/ext/misc/shathree.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/shathree.c 2023-11-04 14:24:27.000000000 +0000
@@ -10,7 +10,8 @@
**
******************************************************************************
**
-** This SQLite extension implements functions that compute SHA3 hashes.
+** This SQLite extension implements functions that compute SHA3 hashes
+** in the way described by the (U.S.) NIST FIPS 202 SHA-3 Standard.
** Two SQL functions are implemented:
**
** sha3(X,SIZE)
diff -Nru sqlite3-3.41.0-0/ext/misc/spellfix.c sqlite3-3.44.0-0/ext/misc/spellfix.c
--- sqlite3-3.41.0-0/ext/misc/spellfix.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/spellfix.c 2023-11-04 14:24:27.000000000 +0000
@@ -3009,6 +3009,11 @@
0, /* xRollback */
0, /* xFindMethod */
spellfix1Rename, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
/*
diff -Nru sqlite3-3.41.0-0/ext/misc/stmt.c sqlite3-3.44.0-0/ext/misc/stmt.c
--- sqlite3-3.41.0-0/ext/misc/stmt.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/stmt.c 2023-11-04 14:24:27.000000000 +0000
@@ -314,6 +314,7 @@
0, /* xRelease */
0, /* xRollbackTo */
0, /* xShadowName */
+ 0 /* xIntegrity */
};
#endif /* SQLITE_OMIT_VIRTUALTABLE */
diff -Nru sqlite3-3.41.0-0/ext/misc/templatevtab.c sqlite3-3.44.0-0/ext/misc/templatevtab.c
--- sqlite3-3.41.0-0/ext/misc/templatevtab.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/templatevtab.c 2023-11-04 14:24:27.000000000 +0000
@@ -249,7 +249,8 @@
/* xSavepoint */ 0,
/* xRelease */ 0,
/* xRollbackTo */ 0,
- /* xShadowName */ 0
+ /* xShadowName */ 0,
+ /* xIntegrity */ 0
};
diff -Nru sqlite3-3.41.0-0/ext/misc/unionvtab.c sqlite3-3.44.0-0/ext/misc/unionvtab.c
--- sqlite3-3.41.0-0/ext/misc/unionvtab.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/unionvtab.c 2023-11-04 14:24:27.000000000 +0000
@@ -1351,7 +1351,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
int rc;
diff -Nru sqlite3-3.41.0-0/ext/misc/vfsstat.c sqlite3-3.44.0-0/ext/misc/vfsstat.c
--- sqlite3-3.41.0-0/ext/misc/vfsstat.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/vfsstat.c 2023-11-04 14:24:27.000000000 +0000
@@ -775,6 +775,11 @@
0, /* xRollback */
0, /* xFindMethod */
0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
/*
diff -Nru sqlite3-3.41.0-0/ext/misc/vtablog.c sqlite3-3.44.0-0/ext/misc/vtablog.c
--- sqlite3-3.41.0-0/ext/misc/vtablog.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/vtablog.c 2023-11-04 14:24:27.000000000 +0000
@@ -493,6 +493,7 @@
0, /* xRelease */
0, /* xRollbackTo */
0, /* xShadowName */
+ 0 /* xIntegrity */
};
#ifdef _WIN32
diff -Nru sqlite3-3.41.0-0/ext/misc/wholenumber.c sqlite3-3.44.0-0/ext/misc/wholenumber.c
--- sqlite3-3.41.0-0/ext/misc/wholenumber.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/wholenumber.c 2023-11-04 14:24:27.000000000 +0000
@@ -254,6 +254,11 @@
0, /* xRollback */
0, /* xFindMethod */
0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
#endif /* SQLITE_OMIT_VIRTUALTABLE */
diff -Nru sqlite3-3.41.0-0/ext/misc/zipfile.c sqlite3-3.44.0-0/ext/misc/zipfile.c
--- sqlite3-3.41.0-0/ext/misc/zipfile.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/zipfile.c 2023-11-04 14:24:27.000000000 +0000
@@ -29,6 +29,7 @@
#include
#include
#include
+#include
#include
@@ -1097,7 +1098,10 @@
** it to be a directory either if the mode suggests so, or if
** the final character in the name is '/'. */
u32 mode = pCDS->iExternalAttr >> 16;
- if( !(mode & S_IFDIR) && pCDS->zFile[pCDS->nFile-1]!='/' ){
+ if( !(mode & S_IFDIR)
+ && pCDS->nFile>=1
+ && pCDS->zFile[pCDS->nFile-1]!='/'
+ ){
sqlite3_result_blob(ctx, "", 0, SQLITE_STATIC);
}
}
@@ -1534,9 +1538,19 @@
*/
static void zipfileRemoveEntryFromList(ZipfileTab *pTab, ZipfileEntry *pOld){
if( pOld ){
- ZipfileEntry **pp;
- for(pp=&pTab->pFirstEntry; (*pp)!=pOld; pp=&((*pp)->pNext));
- *pp = (*pp)->pNext;
+ if( pTab->pFirstEntry==pOld ){
+ pTab->pFirstEntry = pOld->pNext;
+ if( pTab->pLastEntry==pOld ) pTab->pLastEntry = 0;
+ }else{
+ ZipfileEntry *p;
+ for(p=pTab->pFirstEntry; p; p=p->pNext){
+ if( p->pNext==pOld ){
+ p->pNext = pOld->pNext;
+ if( pTab->pLastEntry==pOld ) pTab->pLastEntry = p;
+ break;
+ }
+ }
+ }
zipfileEntryFree(pOld);
}
}
@@ -2187,7 +2201,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollback */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
int rc = sqlite3_create_module(db, "zipfile" , &zipfileModule, 0);
diff -Nru sqlite3-3.41.0-0/ext/rbu/rbuexlock.test sqlite3-3.44.0-0/ext/rbu/rbuexlock.test
--- sqlite3-3.41.0-0/ext/rbu/rbuexlock.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/rbu/rbuexlock.test 2023-11-04 14:24:27.000000000 +0000
@@ -207,5 +207,86 @@
} SQLITE_OK
rbu close
+#-------------------------------------------------------------------------
+reset_db
+forcedelete rbu1.db
+forcedelete rbu2.db
+
+sqlite3 rbu rbu1.db
+do_execsql_test -db rbu 4.1 {
+ CREATE TABLE data_t1(a, b, rbu_control);
+ INSERT INTO data_t1 VALUES(1, 'one', 0);
+}
+rbu close
+sqlite3 rbu rbu2.db
+do_execsql_test -db rbu 4.2 {
+ CREATE TABLE data_t1(a, b, rbu_control);
+ INSERT INTO data_t1 VALUES(2, 'two', 0);
+}
+rbu close
+
+do_execsql_test 4.3 {
+ CREATE TABLE t1(a PRIMARY KEY, b);
+}
+db close
+
+do_test 4.4 {
+ sqlite3rbu rbu file:test.db?rbu_exclusive_checkpoint=1 rbu1.db
+ rbu step
+ rbu state
+} {oal}
+
+sqlite3 cons test.db
+do_execsql_test -db cons 4.5 {
+ SELECT * FROM t1
+} {}
+
+do_test 4.6 { rbu step ; rbu state } {oal}
+do_test 4.7 { rbu step ; rbu state } {move}
+do_execsql_test -db cons 4.8 {
+ SELECT * FROM t1
+} {}
+do_test 4.9 { rbu step ; rbu state } {checkpoint}
+do_test 4.10 {
+ catchsql { SELECT * FROM t1 } cons
+} {1 {database is locked}}
+do_test 4.11 { rbu step ; rbu state } {checkpoint}
+do_test 4.11 { rbu step ; rbu state } {done}
+rbu close
+
+do_test 4.12 {
+ catchsql { SELECT * FROM t1 } cons
+} {0 {1 one}}
+
+do_test 4.13 {
+ sqlite3rbu rbu file:test.db?rbu_exclusive_checkpoint=1 rbu2.db
+ rbu step
+ rbu state
+} {oal}
+
+do_test 4.14 {
+ catchsql { SELECT * FROM t1 } cons
+} {0 {1 one}}
+
+do_test 4.15 { rbu step ; rbu state } {oal}
+do_test 4.16 { rbu step ; rbu state } {move}
+
+do_test 4.17 {
+ catchsql { SELECT * FROM t1 } cons
+} {0 {1 one}}
+
+do_test 4.18 { rbu step ; rbu state } {checkpoint}
+do_test 4.19 {
+ catchsql { SELECT * FROM t1 } cons
+} {1 {database is locked}}
+do_test 4.20 { rbu step ; rbu state } {checkpoint}
+do_test 4.21 { rbu step ; rbu state } {done}
+rbu close
+
+do_test 4.22 {
+ catchsql { SELECT * FROM t1 } cons
+} {0 {1 one 2 two}}
+
+cons close
finish_test
diff -Nru sqlite3-3.41.0-0/ext/rbu/sqlite3rbu.c sqlite3-3.44.0-0/ext/rbu/sqlite3rbu.c
--- sqlite3-3.41.0-0/ext/rbu/sqlite3rbu.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/rbu/sqlite3rbu.c 2023-11-04 14:24:27.000000000 +0000
@@ -3141,6 +3141,11 @@
p->rc = pDb->pMethods->xWrite(pDb, p->aBuf, p->pgsz, iOff);
}
+/*
+** This value is copied from the definition of ZIPVFS_CTRL_FILE_POINTER
+** in zipvfs.h.
+*/
+#define RBU_ZIPVFS_CTRL_FILE_POINTER 230439
/*
** Take an EXCLUSIVE lock on the database file. Return SQLITE_OK if
@@ -3149,9 +3154,20 @@
static int rbuLockDatabase(sqlite3 *db){
int rc = SQLITE_OK;
sqlite3_file *fd = 0;
- sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, &fd);
- if( fd->pMethods ){
+ sqlite3_file_control(db, "main", RBU_ZIPVFS_CTRL_FILE_POINTER, &fd);
+ if( fd ){
+ sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, &fd);
+ rc = fd->pMethods->xLock(fd, SQLITE_LOCK_SHARED);
+ if( rc==SQLITE_OK ){
+ rc = fd->pMethods->xUnlock(fd, SQLITE_LOCK_NONE);
+ }
+ sqlite3_file_control(db, "main", RBU_ZIPVFS_CTRL_FILE_POINTER, &fd);
+ }else{
+ sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, &fd);
+ }
+
+ if( rc==SQLITE_OK && fd->pMethods ){
rc = fd->pMethods->xLock(fd, SQLITE_LOCK_SHARED);
if( rc==SQLITE_OK ){
rc = fd->pMethods->xLock(fd, SQLITE_LOCK_EXCLUSIVE);
diff -Nru sqlite3-3.41.0-0/ext/recover/dbdata.c sqlite3-3.44.0-0/ext/recover/dbdata.c
--- sqlite3-3.41.0-0/ext/recover/dbdata.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/recover/dbdata.c 2023-11-04 14:24:27.000000000 +0000
@@ -73,13 +73,12 @@
*/
#if !defined(SQLITEINT_H)
-#include "sqlite3ext.h"
+#include "sqlite3.h"
typedef unsigned char u8;
typedef unsigned int u32;
#endif
-SQLITE_EXTENSION_INIT1
#include
#include
@@ -167,6 +166,7 @@
(void)argc;
(void)argv;
(void)pzErr;
+ sqlite3_vtab_config(db, SQLITE_VTAB_USES_ALL_SCHEMAS);
if( rc==SQLITE_OK ){
pTab = (DbdataTable*)sqlite3_malloc64(sizeof(DbdataTable));
if( pTab==0 ){
@@ -663,8 +663,14 @@
if( pCsr->pHdrPtr>&pCsr->pRec[pCsr->nRec] ){
bNextPage = 1;
}else{
+ int szField = 0;
pCsr->pHdrPtr += dbdataGetVarintU32(pCsr->pHdrPtr, &iType);
- pCsr->pPtr += dbdataValueBytes(iType);
+ szField = dbdataValueBytes(iType);
+ if( (pCsr->nRec - (pCsr->pPtr - pCsr->pRec))pPtr = &pCsr->pRec[pCsr->nRec];
+ }else{
+ pCsr->pPtr += szField;
+ }
}
}
}
@@ -812,8 +818,6 @@
}
if( rc==SQLITE_OK ){
rc = sqlite3_bind_text(pCsr->pStmt, 1, zSchema, -1, SQLITE_TRANSIENT);
- }else{
- pTab->base.zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pTab->db));
}
/* Try to determine the encoding of the db by inspecting the header
@@ -822,6 +826,10 @@
rc = dbdataGetEncoding(pCsr);
}
+ if( rc!=SQLITE_OK ){
+ pTab->base.zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pTab->db));
+ }
+
if( rc==SQLITE_OK ){
rc = dbdataNext(pCursor);
}
@@ -925,7 +933,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
int rc = sqlite3_create_module(db, "sqlite_dbdata", &dbdata_module, 0);
@@ -935,15 +944,11 @@
return rc;
}
-#ifdef _WIN32
-__declspec(dllexport)
-#endif
int sqlite3_dbdata_init(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
- SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg;
return sqlite3DbdataRegister(db);
}
diff -Nru sqlite3-3.41.0-0/ext/recover/recover1.test sqlite3-3.44.0-0/ext/recover/recover1.test
--- sqlite3-3.41.0-0/ext/recover/recover1.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/recover/recover1.test 2023-11-04 14:24:27.000000000 +0000
@@ -316,5 +316,51 @@
COMMIT;
} {1 2 3 4}
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 17.1 {
+ CREATE TABLE t(a, PRIMARY KEY(a, a COLLATE NOCASE)) WITHOUT ROWID;
+ INSERT INTO t VALUES('abc');
+ INSERT INTO t VALUES('def');
+}
+do_test 17.2 {
+ set R [sqlite3_recover_init db main test.db2]
+ $R run
+ list [catch { $R finish } msg] $msg
+} {0 {}}
+
+#-------------------------------------------------------------------------
+foreach enc {utf8 utf16 utf16le utf16be} {
+ reset_db
+ do_execsql_test 18.$enc.1 {
+ PRAGMA auto_vacuum = 0;
+ PRAGMA encoding='utf16';
+ CREATE TABLE t1(a, b);
+ CREATE TABLE t2(a, b);
+ INSERT INTO t1 VALUES('abc', 'def');
+ PRAGMA writable_schema = 1;
+ DELETE FROM sqlite_schema WHERE name='t1';
+ }
+
+ proc my_sql_hook {sql} {
+ if {[string match "INSERT INTO lostandfound*" $sql]} {
+ lappend ::script $sql
+ }
+ return 0
+ }
+ do_test 18.$enc.2 {
+ set ::script [list]
+ set R [sqlite3_recover_init_sql db main my_sql_hook]
+ $R config lostandfound lostandfound
+ $R run
+ $R finish
+ set ::script
+ } {{INSERT INTO lostandfound VALUES(2, 2, 2, 1, 'abc', 'def')}}
+}
+
+
+
+
+
finish_test
diff -Nru sqlite3-3.41.0-0/ext/recover/recoverbuild.test sqlite3-3.44.0-0/ext/recover/recoverbuild.test
--- sqlite3-3.41.0-0/ext/recover/recoverbuild.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/recover/recoverbuild.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,42 @@
+# 2023 February 28
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+
+source [file join [file dirname [info script]] recover_common.tcl]
+set testprefix recoverbuild
+
+
+# The following tests verify that if the recovery extension is used with
+# a build that does not support the sqlite_dbpage table, the error message
+# is "no such table: sqlite_dbpage", and not something more generic.
+#
+reset_db
+create_null_module db sqlite_dbpage
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);
+ INSERT INTO t1 VALUES(123, 'one hundred and twenty three');
+}
+
+forcedelete test.db2
+do_test 1.1 {
+ set R [sqlite3_recover_init db main test.db2]
+} {/sqlite_recover.*/}
+
+do_test 1.2 {
+ $R run
+} {1}
+
+do_test 1.3 {
+ list [catch { $R finish } msg] $msg
+} {1 {no such table: sqlite_dbpage}}
+
+finish_test
+
diff -Nru sqlite3-3.41.0-0/ext/recover/recovercorrupt2.test sqlite3-3.44.0-0/ext/recover/recovercorrupt2.test
--- sqlite3-3.41.0-0/ext/recover/recovercorrupt2.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/recover/recovercorrupt2.test 2023-11-04 14:24:27.000000000 +0000
@@ -285,5 +285,244 @@
list [catch { $R finish } msg] $msg
} {0 {}}
+#-------------------------------------------------------------------------
+#
+reset_db
+do_test 6.0 {
+ sqlite3 db {}
+ db deserialize [decode_hexdb {
+| size 8192 pagesize 4096 filename abc.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 02 00 00 00 02 .....@ ........
+| 32: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................
+| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................
+| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 ................
+| 96: 00 2e 6e b8 0d 00 00 00 01 0f dc 00 0f dc 00 00 ..n.............
+| 4048: 00 00 00 00 00 00 00 00 00 00 00 00 22 01 06 17 ................
+| 4064: 11 11 01 31 74 61 62 6c 65 74 31 74 31 02 43 52 ...1tablet1t1.CR
+| 4080: 45 41 54 45 20 54 41 42 4c 45 20 74 31 28 78 29 EATE TABLE t1(x)
+| page 2 offset 4096
+| 0: 0d 00 00 00 01 0f e2 00 0f e2 00 00 00 00 00 00 ................
+| 4064: 00 00 1c 01 02 41 61 62 63 64 65 66 67 68 69 6a .....Aabcdefghij
+| 4080: 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a klmnopqrstuvwxyz
+| end abc.db
+}]} {}
+do_test 6.1 {
+ set R [sqlite3_recover_init db main test.db2]
+ catch { $R run }
+ list [catch { $R finish } msg] $msg
+} {0 {}}
+
+reset_db
+breakpoint
+do_test 6.2 {
+ sqlite3 db {}
+ db deserialize [decode_hexdb {
+| size 8192 pagesize 4096 filename abc.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 02 00 00 00 02 .....@ ........
+| 32: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................
+| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................
+| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 ................
+| 96: 00 2e 6e b8 0d 00 00 00 01 0f dc 00 0f dc 00 00 ..n.............
+| 4048: 00 00 00 00 00 00 00 00 00 00 00 00 22 01 06 17 ................
+| 4064: 11 11 01 31 74 61 62 6c 65 74 31 74 31 02 43 52 ...1tablet1t1.CR
+| 4080: 45 41 54 45 20 54 41 42 4c 45 20 74 31 28 78 29 EATE TABLE t1(x)
+| page 2 offset 4096
+| 0: 0d 00 00 00 01 0f e2 00 0f e2 00 00 00 00 00 00 ................
+| 4064: 00 00 1c 01 02 8F FF FF FF 7E 65 66 67 68 69 6a .....Aabcdefghij
+| 4080: 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a klmnopqrstuvwxyz
+| end abc.db
+}]} {}
+do_test 6.3 {
+ set R [sqlite3_recover_init db main test.db2]
+ catch { $R run }
+ list [catch { $R finish } msg] $msg
+} {0 {}}
+
+reset_db
+breakpoint
+do_test 7.0 {
+ sqlite3 db {}
+ db deserialize [decode_hexdb {
+| size 4108 pagesize 4096 filename x1.db
+| page 1 offset 0
+| 0: 02 01 00 00 00 00 14 15 40 00 00 00 00 00 00 00 ........@.......
+| 16: 33 3a 6d 65 6d 6f 72 79 3a 02 02 02 02 02 02 02 3:memory:.......
+| 32: 02 02 02 02 02 02 12 02 02 02 63 6f 6c 6f 72 20 ..........color
+| 48: 73 70 61 63 00 f3 a0 81 a1 00 00 a0 02 02 02 02 spac............
+| 64: 69 95 73 6f 36 00 ff 0d 00 97 8c 90 3f 0a 70 02 i.so6.......?.p.
+| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 01 00 00 ................
+| 96: 06 02 02 02 02 5f 02 02 02 2c 02 02 02 02 02 02 ....._...,......
+| 112: 02 02 02 02 02 02 02 02 02 12 02 02 02 63 6f 6c .............col
+| 128: 6f 72 20 73 70 61 63 00 f3 a0 81 a1 00 00 a0 02 or spac.........
+| 144: 02 02 02 69 95 73 6f 36 00 ff 0d 00 97 8c 90 3f ...i.so6.......?
+| 160: 0a 70 02 02 02 02 02 02 02 02 02 02 02 02 02 02 .p..............
+| 176: 01 00 00 06 02 02 02 02 5f 02 02 02 2c 02 02 00 ........_...,...
+| 192: 00 01 00 01 00 00 00 01 00 02 fe 00 00 03 00 01 ................
+| 208: 00 00 00 01 c5 04 00 00 00 01 00 01 00 00 00 01 ................
+| 224: 00 fa 02 00 00 00 03 00 01 00 00 00 81 00 04 00 ................
+| 240: 00 00 01 00 01 00 00 00 01 00 02 00 fe 00 03 00 ................
+| 256: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
+| 272: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................
+| 288: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................
+| 304: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
+| 320: 01 00 02 00 00 00 03 00 01 00 00 00 40 00 84 00 ............@...
+| 336: 84 00 84 00 01 00 00 00 09 00 06 00 f5 00 01 00 ................
+| 352: 08 01 03 00 03 00 62 00 62 00 23 00 01 00 62 00 ......b.b.#...b.
+| 368: 04 00 1e 00 62 00 62 00 62 00 01 00 00 00 0a 00 ....b.b.b.......
+| 384: 01 00 03 00 01 00 03 00 04 00 02 00 01 00 01 00 ................
+| 400: 08 00 01 00 31 c6 00 03 00 0c 00 12 00 18 00 02 ....1...........
+| 416: 00 05 00 08 00 02 00 06 00 08 00 02 00 07 00 08 ................
+| 432: 00 02 00 01 00 01 00 08 00 01 00 0c 00 03 00 16 ................
+| 448: 00 1c 00 22 00 01 00 03 00 05 00 06 00 07 00 02 ................
+| 464: 00 05 00 09 00 02 00 06 00 09 00 02 00 07 00 09 ................
+| 480: 00 00 00 00 01 00 05 00 00 00 01 00 01 00 00 00 ................
+| 496: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................
+| 512: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................
+| 528: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
+| 544: 01 00 02 00 00 f6 03 00 00 02 00 00 01 00 04 00 ................
+| 560: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................
+| 576: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
+| 592: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................
+| 608: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................
+| 624: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
+| 640: 01 3d 02 00 00 00 03 00 06 00 00 00 01 00 01 00 .=..............
+| 656: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................
+| 672: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
+| 688: 01 00 02 00 00 00 55 52 4c 52 65 71 75 65 73 74 ......URLRequest
+| 704: 43 6f 6e 00 00 00 01 01 0e d4 00 04 00 00 00 01 Con.............
+| 720: 0e f8 00 04 00 00 00 01 0f 1c 00 04 00 00 00 01 ................
+| 736: 0f 00 00 01 00 00 00 01 0f 86 00 01 00 00 00 01 ................
+| 752: 0f 84 00 01 00 00 00 01 00 00 01 0f c0 00 01 00 ................
+| 768: 00 00 01 0f e8 00 d6 0f 00 01 6f 00 02 0f d6 00 ..........o.....
+| 784: 02 34 03 03 03 00 01 00 00 00 01 00 05 00 00 00 .4..............
+| 800: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................
+| 816: 00 00 01 00 04 00 00 00 01 00 01 00 00 00 01 00 ................
+| 832: 02 00 00 00 03 00 01 00 10 00 01 00 04 00 00 00 ................
+| 848: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 00 02 ................
+| 864: 00 00 01 40 04 00 00 03 01 00 01 00 00 00 01 00 ...@............
+| 880: 02 00 00 00 03 00 01 00 00 00 00 00 01 0e f8 00 ................
+| 896: 04 77 4f 46 32 73 40 23 70 00 00 00 70 00 1f 00 .wOF2s@#p...p...
+| 912: 00 00 d8 00 00 00 ff ff 00 00 00 00 43 00 00 00 ............C...
+| 928: 00 00 ff ff ff ff ff 00 00 a8 00 00 0c 00 00 00 ................
+| 1024: 00 00 00 00 00 00 00 00 00 00 10 22 00 22 0f 00 ................
+| 1040: 00 00 00 00 00 00 10 22 00 00 70 00 1f 00 00 0f ..........p.....
+| 1056: d8 00 00 00 00 00 00 00 00 00 03 00 00 00 00 00 ................
+| 1072: 00 01 00 00 00 3f 23 70 00 00 00 01 0f 1c 00 04 .....?#p........
+| 1088: 00 00 00 01 0f 40 00 01 00 00 00 01 0f 86 00 01 .....@..........
+| 1104: 00 00 00 01 0f 84 00 01 00 00 00 01 00 00 01 0f ................
+| 1120: c0 00 01 00 00 00 01 0f e8 00 01 0f d6 00 6f 00 ..............o.
+| 1136: 02 0f d6 00 03 02 31 03 2b 03 2a f2 00 0f d4 00 ......1.+.*.....
+| 1152: 01 00 08 00 01 00 04 03 2b 00 02 02 32 00 01 0f ........+...2...
+| 1168: c8 01 15 00 02 20 c8 00 02 12 ad 02 00 24 06 c0 ..... .......$..
+| 1184: 00 00 00 03 00 00 01 24 00 2a 06 e4 00 00 00 03 .......$.*......
+| 1200: 00 00 01 25 00 38 07 0e 00 00 00 03 00 00 01 26 ...%.8.........&
+| 1216: 00 34 07 46 00 00 00 03 00 00 01 27 00 1c 07 7a .4.F.......'...z
+| 1232: 00 00 00 03 00 00 01 28 00 2a 07 96 00 00 00 03 .......(.*......
+| 1248: 00 e5 01 29 00 34 07 c0 00 00 00 03 00 00 01 2a ...).4.........*
+| 1264: 67 34 07 f4 00 00 00 03 00 00 01 2b 00 22 08 28 g4.........+...(
+| 1280: 00 00 00 00 01 00 01 00 00 00 01 00 02 00 00 00 ................
+| 1296: 03 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 ................
+| 1312: 01 00 00 00 01 00 02 00 00 00 03 00 00 02 00 00 ................
+| 1328: 01 00 04 00 00 00 01 00 01 00 00 00 01 00 02 00 ................
+| 1344: 00 00 03 00 01 00 00 00 01 00 04 00 00 00 01 00 ................
+| 1360: 01 00 00 00 01 00 02 00 00 00 03 00 01 00 00 21 ...............!
+| 1376: 04 00 01 00 00 00 00 00 01 00 00 00 01 00 02 00 ................
+| 1392: 00 00 03 00 01 00 00 00 01 00 04 00 00 00 01 00 ................
+| 1408: 01 00 00 00 01 00 02 00 00 00 03 00 01 00 00 00 ................
+| 1424: 01 00 05 00 00 00 01 00 01 00 00 01 00 02 02 02 ................
+| 1440: 12 02 02 02 63 6f 6c 6f 72 20 73 70 61 63 00 f3 ....color spac..
+| 1456: a0 81 a1 00 00 a0 02 02 02 02 69 95 73 6f 36 00 ..........i.so6.
+| 1472: ff 0d 00 97 8c 90 3f 0a 70 02 02 02 02 02 02 02 ......?.p.......
+| 1488: 02 02 02 02 02 02 02 01 00 00 06 02 02 02 02 5f ..............._
+| 1504: 02 02 02 2c 02 02 00 00 01 00 01 00 00 00 01 00 ...,............
+| 1520: 02 fe 00 00 03 00 01 00 00 00 01 c5 04 00 00 00 ................
+| 1536: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................
+| 1552: 00 00 81 00 04 00 00 00 01 00 01 00 00 00 01 00 ................
+| 1568: 02 00 fe 00 03 00 01 00 00 00 01 00 04 00 00 00 ................
+| 1584: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................
+| 1600: 00 00 01 00 04 00 00 00 01 00 01 00 00 00 01 00 ................
+| 1616: 02 00 00 00 03 00 01 00 00 00 01 00 04 00 00 00 ................
+| 1632: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................
+| 1648: 00 00 40 00 84 00 84 00 84 00 01 00 00 00 09 00 ..@.............
+| 1664: 06 00 f5 00 01 00 08 01 03 15 15 15 15 15 15 15 ................
+| 1680: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................
+| 1696: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................
+| 1712: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................
+| 1728: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................
+| 1744: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................
+| 1760: 15 15 15 15 15 15 15 15 15 15 15 00 03 00 62 00 ..............b.
+| 1776: 62 00 23 00 01 00 62 00 04 00 1e 00 62 00 62 00 b.#...b.....b.b.
+| 1792: 62 00 01 00 00 00 0a 00 01 00 03 00 01 00 03 00 b...............
+| 1808: 04 00 02 00 01 00 01 00 08 00 01 00 31 c6 00 03 ............1...
+| 1824: 00 0c 00 12 00 18 00 02 00 05 00 08 00 02 00 06 ................
+| 1840: 00 08 00 02 00 07 00 08 00 02 00 01 00 01 00 08 ................
+| 1856: 00 01 00 0c 00 03 00 16 00 1c 00 22 00 01 00 03 ................
+| 1872: 00 05 00 06 00 07 00 02 00 05 00 09 00 02 00 06 ................
+| 1888: 00 09 00 02 00 07 00 09 00 00 00 00 01 00 05 00 ................
+| 1904: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................
+| 1920: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
+| 1936: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................
+| 1952: 00 00 01 0f d6 00 02 34 03 03 03 00 01 00 00 00 .......4........
+| 1968: 01 00 05 00 00 00 01 00 01 00 00 00 01 00 02 00 ................
+| 1984: 00 00 03 00 01 00 00 00 01 00 04 00 00 00 01 00 ................
+| 2000: 01 00 00 00 01 00 02 fc 42 dc 19 5c 74 23 18 cd ........B...t#..
+| 2016: b3 a5 a8 7a 90 40 1d 66 12 5d e5 4f 85 00 68 f4 ...z.@.f.].O..h.
+| 2032: 05 98 86 25 24 dd bc c2 f6 f6 4e a3 e2 61 d2 c6 ...%$.....N..a..
+| 2048: aa c1 56 50 d4 80 82 35 f1 e2 59 41 50 a6 da 51 ..VP...5..YAP..Q
+| 2064: d4 62 9c 19 94 58 aa 31 30 8a 22 c2 5f 33 2b c9 .b...X.10..._3+.
+| 2080: b6 e6 b4 11 4e 51 82 c4 d8 b6 d8 b4 06 04 fb 68 ....NQ.........h
+| 2096: f4 d2 6f e7 cb 8a a8 82 d5 74 00 00 00 00 00 00 ..o......t......
+| 2368: 00 00 00 00 00 00 00 00 00 08 00 00 00 00 00 00 ................
+| 2432: 00 00 00 00 00 03 00 01 00 10 00 01 00 04 00 00 ................
+| 2448: 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 00 ................
+| 2464: 02 00 00 01 40 04 00 00 03 01 00 01 00 00 00 01 ....@...........
+| 2480: 00 02 00 00 00 03 00 01 00 00 00 00 00 01 0e f8 ................
+| 2496: 00 04 77 4f 46 32 73 40 23 70 00 00 00 70 00 1f ..wOF2s@#p...p..
+| 2512: 00 00 00 d8 00 00 00 ff ff 00 00 00 00 43 00 00 .............C..
+| 2528: 00 00 00 ff ff ff ff ff 00 00 a8 00 00 0c 00 00 ................
+| 2624: 00 00 00 00 00 00 00 00 00 00 00 10 22 00 22 0f ................
+| 2640: 00 00 00 00 00 00 00 10 22 00 00 70 00 1f 00 00 ...........p....
+| 2656: 0f d8 00 00 00 00 00 00 00 00 00 03 00 00 00 00 ................
+| 2672: 00 00 01 00 00 00 3f 23 70 00 00 00 01 0f 1c 00 ......?#p.......
+| 2688: 04 00 00 00 01 0f 40 00 01 00 00 00 01 0f 86 00 ......@.........
+| 2704: 01 00 00 00 01 0f 84 00 01 00 00 00 01 00 00 01 ................
+| 2720: 0f c0 00 01 00 00 00 01 0f e8 00 01 0f d6 00 6f ...............o
+| 2736: 00 02 0f d6 00 03 02 31 03 2b 03 2a f2 00 0f d4 .......1.+.*....
+| 2752: 00 01 00 08 00 01 00 04 03 2b 00 02 02 32 00 01 .........+...2..
+| 2768: 0f c8 01 15 00 02 20 c8 00 02 12 ad 02 00 24 06 ...... .......$.
+| 2784: c0 00 00 5a 03 00 00 01 24 00 2a 06 e4 00 00 00 ...Z....$.*.....
+| 2800: 03 00 00 01 25 00 38 07 0e 00 00 00 03 00 00 01 ....%.8.........
+| 2816: 26 00 34 07 46 00 00 00 03 00 00 01 27 00 1c 07 &.4.F.......'...
+| 2832: 7a 00 00 00 03 00 00 01 28 00 2a 07 96 00 00 00 z.......(.*.....
+| 2848: 03 00 e5 01 29 00 34 07 c0 00 00 00 03 00 00 01 ....).4.........
+| 2864: 2a 67 34 07 f4 00 00 00 03 00 00 01 2b 00 22 08 *g4.........+...
+| 2880: 28 00 00 00 00 01 00 01 00 00 00 01 00 02 00 00 (...............
+| 2896: 00 03 00 01 00 00 00 01 00 00 00 01 00 00 00 01 ................
+| 2912: 00 01 00 00 00 01 00 02 00 00 00 03 00 00 02 00 ................
+| 2928: 00 01 00 04 00 00 00 01 00 01 00 00 00 00 00 00 ................
+| 2992: 00 48 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .H..............
+| 3504: 00 00 00 00 00 00 00 00 00 00 00 97 00 00 00 00 ................
+| 3904: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 09 ................
+| 3920: 03 fe 00 00 01 36 00 3c 0a 38 00 00 00 03 00 00 .....6.<.8......
+| 3936: 01 37 00 20 0a 74 00 00 00 fb ff ff 00 38 00 2a .7. .t.......8.*
+| 3952: 0a 94 00 00 00 03 00 00 01 39 4f 54 54 4f 00 0e .........9OTTO..
+| 3968: 00 80 00 03 00 60 43 46 46 20 e3 ae 89 2a 00 00 .....`CFF ...*..
+| 3984: 02 b0 00 00 02 76 42 50 4f 53 00 15 00 0a 00 00 .....vBPOS......
+| 4000: 05 28 00 00 00 0c 54 53 55 42 c9 70 c3 06 00 00 .(....TSUB.p....
+| 4016: 05 34 1f 00 40 00 48 00 00 00 00 00 00 00 00 00 .4..@.H.........
+| 4064: 00 00 00 00 00 08 00 01 00 01 00 01 00 01 00 06 ................
+| 4080: 00 02 00 08 00 01 00 01 00 01 00 01 00 00 00 00 ................
+| end x1.db
+}]} {}
+do_test 7.1 {
+ set R [sqlite3_recover_init db main test.db2]
+ catch { $R run }
+ list [catch { $R finish } msg] $msg
+} {1 {file is not a database}}
+
finish_test
diff -Nru sqlite3-3.41.0-0/ext/recover/sqlite3recover.c sqlite3-3.44.0-0/ext/recover/sqlite3recover.c
--- sqlite3-3.41.0-0/ext/recover/sqlite3recover.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/recover/sqlite3recover.c 2023-11-04 14:24:27.000000000 +0000
@@ -1106,7 +1106,7 @@
int iField = sqlite3_column_int(pStmt, 0);
int iCol = sqlite3_column_int(pStmt, 1);
- assert( iFieldnCol && iColnCol );
+ assert( iColnCol );
pNew->aCol[iCol].iField = iField;
pNew->bIntkey = 0;
@@ -2103,7 +2103,7 @@
if( iFree>(n-4) ) return 0;
iNext = recoverGetU16(&a[iFree]);
nByte = recoverGetU16(&a[iFree+2]);
- if( iFree+nByte>n ) return 0;
+ if( iFree+nByte>n || nByte<4 ) return 0;
if( iNext && iNextnBusy = 1;
pRtree->base.pModule = &rtreeModule;
pRtree->zDb = (char *)&pRtree[1];
pRtree->zName = &pRtree->zDb[nDb+1];
+ pRtree->zNodeName = &pRtree->zName[nName+1];
pRtree->eCoordType = RTREE_COORD_REAL32;
pRtree->nDim = 2;
pRtree->nDim2 = 4;
memcpy(pRtree->zDb, argv[1], nDb);
memcpy(pRtree->zName, argv[2], nName);
+ memcpy(pRtree->zNodeName, argv[2], nName);
+ memcpy(&pRtree->zNodeName[nName], "_node", 6);
/* Create/Connect to the underlying relational database schema. If
@@ -1683,7 +1687,6 @@
}
if( rc==SQLITE_OK ){
int rc2;
- pRtree->iReinsertHeight = -1;
rc = rtreeInsertCell(pRtree, pLeaf, &cell, 0);
rc2 = nodeRelease(pRtree, pLeaf);
if( rc==SQLITE_OK ){
@@ -1780,7 +1783,8 @@
rtreeSavepoint, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- rtreeShadowName /* xShadowName */
+ rtreeShadowName, /* xShadowName */
+ rtreeIntegrity /* xIntegrity */
};
static int sqlite3_geopoly_init(sqlite3 *db){
diff -Nru sqlite3-3.41.0-0/ext/rtree/rtree.c sqlite3-3.44.0-0/ext/rtree/rtree.c
--- sqlite3-3.41.0-0/ext/rtree/rtree.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/rtree/rtree.c 2023-11-04 14:24:27.000000000 +0000
@@ -96,6 +96,11 @@
#endif
#endif /* !defined(SQLITE_AMALGAMATION) */
+/* Macro to check for 4-byte alignment. Only used inside of assert() */
+#ifdef SQLITE_DEBUG
+# define FOUR_BYTE_ALIGNED(X) ((((char*)(X) - (char*)0) & 3)==0)
+#endif
+
#include
#include
#include
@@ -161,6 +166,7 @@
int iDepth; /* Current depth of the r-tree structure */
char *zDb; /* Name of database containing r-tree table */
char *zName; /* Name of r-tree table */
+ char *zNodeName; /* Name of the %_node table */
u32 nBusy; /* Current number of users of this structure */
i64 nRowEst; /* Estimated number of rows in this table */
u32 nCursor; /* Number of open cursors */
@@ -173,7 +179,6 @@
** headed by the node (leaf nodes have RtreeNode.iNode==0).
*/
RtreeNode *pDeleted;
- int iReinsertHeight; /* Height of sub-trees Reinsert() has run on */
/* Blob I/O on xxx_node */
sqlite3_blob *pNodeBlob;
@@ -195,7 +200,7 @@
/* Statement for writing to the "aux:" fields, if there are any */
sqlite3_stmt *pWriteAux;
- RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */
+ RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */
};
/* Possible values for Rtree.eCoordType: */
@@ -470,17 +475,23 @@
** -DSQLITE_RUNTIME_BYTEORDER=1 is set, then byte-order is determined
** at run-time.
*/
-#ifndef SQLITE_BYTEORDER
-#if defined(i386) || defined(__i386__) || defined(_M_IX86) || \
- defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \
- defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \
- defined(__arm__)
-# define SQLITE_BYTEORDER 1234
-#elif defined(sparc) || defined(__ppc__)
-# define SQLITE_BYTEORDER 4321
-#else
-# define SQLITE_BYTEORDER 0 /* 0 means "unknown at compile-time" */
-#endif
+#ifndef SQLITE_BYTEORDER /* Replicate changes at tag-20230904a */
+# if defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_BIG_ENDIAN__
+# define SQLITE_BYTEORDER 4321
+# elif defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__
+# define SQLITE_BYTEORDER 1234
+# elif defined(__BIG_ENDIAN__) && __BIG_ENDIAN__==1
+# define SQLITE_BYTEORDER 4321
+# elif defined(i386) || defined(__i386__) || defined(_M_IX86) || \
+ defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \
+ defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \
+ defined(__ARMEL__) || defined(__AARCH64EL__) || defined(_M_ARM64)
+# define SQLITE_BYTEORDER 1234
+# elif defined(sparc) || defined(__ARMEB__) || defined(__AARCH64EB__)
+# define SQLITE_BYTEORDER 4321
+# else
+# define SQLITE_BYTEORDER 0
+# endif
#endif
@@ -501,7 +512,7 @@
return (p[0]<<8) + p[1];
}
static void readCoord(u8 *p, RtreeCoord *pCoord){
- assert( (((sqlite3_uint64)p)&3)==0 ); /* p is always 4-byte aligned */
+ assert( FOUR_BYTE_ALIGNED(p) );
#if SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300
pCoord->u = _byteswap_ulong(*(u32*)p);
#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000
@@ -555,7 +566,7 @@
}
static int writeCoord(u8 *p, RtreeCoord *pCoord){
u32 i;
- assert( (((sqlite3_uint64)p)&3)==0 ); /* p is always 4-byte aligned */
+ assert( FOUR_BYTE_ALIGNED(p) );
assert( sizeof(RtreeCoord)==4 );
assert( sizeof(u32)==4 );
#if SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000
@@ -726,11 +737,9 @@
}
}
if( pRtree->pNodeBlob==0 ){
- char *zTab = sqlite3_mprintf("%s_node", pRtree->zName);
- if( zTab==0 ) return SQLITE_NOMEM;
- rc = sqlite3_blob_open(pRtree->db, pRtree->zDb, zTab, "data", iNode, 0,
+ rc = sqlite3_blob_open(pRtree->db, pRtree->zDb, pRtree->zNodeName,
+ "data", iNode, 0,
&pRtree->pNodeBlob);
- sqlite3_free(zTab);
}
if( rc ){
nodeBlobReset(pRtree);
@@ -1283,7 +1292,7 @@
assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE
|| p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE
|| p->op==RTREE_FALSE );
- assert( (((sqlite3_uint64)pCellData)&3)==0 ); /* 4-byte aligned */
+ assert( FOUR_BYTE_ALIGNED(pCellData) );
switch( p->op ){
case RTREE_TRUE: return; /* Always satisfied */
case RTREE_FALSE: break; /* Never satisfied */
@@ -1336,7 +1345,7 @@
|| p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE
|| p->op==RTREE_FALSE );
pCellData += 8 + p->iCoord*4;
- assert( (((sqlite3_uint64)pCellData)&3)==0 ); /* 4-byte aligned */
+ assert( FOUR_BYTE_ALIGNED(pCellData) );
RTREE_DECODE_COORD(eInt, pCellData, xN);
switch( p->op ){
case RTREE_TRUE: return; /* Always satisfied */
@@ -1906,7 +1915,20 @@
p->pInfo->nCoord = pRtree->nDim2;
p->pInfo->anQueue = pCsr->anQueue;
p->pInfo->mxLevel = pRtree->iDepth + 1;
- }else if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
+ }else if( eType==SQLITE_INTEGER ){
+ sqlite3_int64 iVal = sqlite3_value_int64(argv[ii]);
+#ifdef SQLITE_RTREE_INT_ONLY
+ p->u.rValue = iVal;
+#else
+ p->u.rValue = (double)iVal;
+ if( iVal>=((sqlite3_int64)1)<<48
+ || iVal<=-(((sqlite3_int64)1)<<48)
+ ){
+ if( p->op==RTREE_LT ) p->op = RTREE_LE;
+ if( p->op==RTREE_GT ) p->op = RTREE_GE;
+ }
+#endif
+ }else if( eType==SQLITE_FLOAT ){
#ifdef SQLITE_RTREE_INT_ONLY
p->u.rValue = sqlite3_value_int64(argv[ii]);
#else
@@ -2037,11 +2059,12 @@
|| p->op==SQLITE_INDEX_CONSTRAINT_MATCH)
){
u8 op;
+ u8 doOmit = 1;
switch( p->op ){
- case SQLITE_INDEX_CONSTRAINT_EQ: op = RTREE_EQ; break;
- case SQLITE_INDEX_CONSTRAINT_GT: op = RTREE_GT; break;
+ case SQLITE_INDEX_CONSTRAINT_EQ: op = RTREE_EQ; doOmit = 0; break;
+ case SQLITE_INDEX_CONSTRAINT_GT: op = RTREE_GT; doOmit = 0; break;
case SQLITE_INDEX_CONSTRAINT_LE: op = RTREE_LE; break;
- case SQLITE_INDEX_CONSTRAINT_LT: op = RTREE_LT; break;
+ case SQLITE_INDEX_CONSTRAINT_LT: op = RTREE_LT; doOmit = 0; break;
case SQLITE_INDEX_CONSTRAINT_GE: op = RTREE_GE; break;
case SQLITE_INDEX_CONSTRAINT_MATCH: op = RTREE_MATCH; break;
default: op = 0; break;
@@ -2050,15 +2073,19 @@
zIdxStr[iIdx++] = op;
zIdxStr[iIdx++] = (char)(p->iColumn - 1 + '0');
pIdxInfo->aConstraintUsage[ii].argvIndex = (iIdx/2);
- pIdxInfo->aConstraintUsage[ii].omit = 1;
+ pIdxInfo->aConstraintUsage[ii].omit = doOmit;
}
}
}
pIdxInfo->idxNum = 2;
pIdxInfo->needToFreeIdxStr = 1;
- if( iIdx>0 && 0==(pIdxInfo->idxStr = sqlite3_mprintf("%s", zIdxStr)) ){
- return SQLITE_NOMEM;
+ if( iIdx>0 ){
+ pIdxInfo->idxStr = sqlite3_malloc( iIdx+1 );
+ if( pIdxInfo->idxStr==0 ){
+ return SQLITE_NOMEM;
+ }
+ memcpy(pIdxInfo->idxStr, zIdxStr, iIdx+1);
}
nRow = pRtree->nRowEst >> (iIdx/2);
@@ -2137,31 +2164,22 @@
*/
static int cellContains(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){
int ii;
- int isInt = (pRtree->eCoordType==RTREE_COORD_INT32);
- for(ii=0; iinDim2; ii+=2){
- RtreeCoord *a1 = &p1->aCoord[ii];
- RtreeCoord *a2 = &p2->aCoord[ii];
- if( (!isInt && (a2[0].fa1[1].f))
- || ( isInt && (a2[0].ia1[1].i))
- ){
- return 0;
+ if( pRtree->eCoordType==RTREE_COORD_INT32 ){
+ for(ii=0; iinDim2; ii+=2){
+ RtreeCoord *a1 = &p1->aCoord[ii];
+ RtreeCoord *a2 = &p2->aCoord[ii];
+ if( a2[0].ia1[1].i ) return 0;
+ }
+ }else{
+ for(ii=0; iinDim2; ii+=2){
+ RtreeCoord *a1 = &p1->aCoord[ii];
+ RtreeCoord *a2 = &p2->aCoord[ii];
+ if( a2[0].fa1[1].f ) return 0;
}
}
return 1;
}
-/*
-** Return the amount cell p would grow by if it were unioned with pCell.
-*/
-static RtreeDValue cellGrowth(Rtree *pRtree, RtreeCell *p, RtreeCell *pCell){
- RtreeDValue area;
- RtreeCell cell;
- memcpy(&cell, p, sizeof(RtreeCell));
- area = cellArea(pRtree, &cell);
- cellUnion(pRtree, &cell, pCell);
- return (cellArea(pRtree, &cell)-area);
-}
-
static RtreeDValue cellOverlap(
Rtree *pRtree,
RtreeCell *p,
@@ -2208,38 +2226,52 @@
for(ii=0; rc==SQLITE_OK && ii<(pRtree->iDepth-iHeight); ii++){
int iCell;
sqlite3_int64 iBest = 0;
-
+ int bFound = 0;
RtreeDValue fMinGrowth = RTREE_ZERO;
RtreeDValue fMinArea = RTREE_ZERO;
-
int nCell = NCELL(pNode);
- RtreeCell cell;
RtreeNode *pChild = 0;
- RtreeCell *aCell = 0;
-
- /* Select the child node which will be enlarged the least if pCell
- ** is inserted into it. Resolve ties by choosing the entry with
- ** the smallest area.
+ /* First check to see if there is are any cells in pNode that completely
+ ** contains pCell. If two or more cells in pNode completely contain pCell
+ ** then pick the smallest.
*/
for(iCell=0; iCell1 ){
- int iLeft = 0;
- int iRight = 0;
-
- int nLeft = nIdx/2;
- int nRight = nIdx-nLeft;
- int *aLeft = aIdx;
- int *aRight = &aIdx[nLeft];
-
- SortByDistance(aLeft, nLeft, aDistance, aSpare);
- SortByDistance(aRight, nRight, aDistance, aSpare);
-
- memcpy(aSpare, aLeft, sizeof(int)*nLeft);
- aLeft = aSpare;
-
- while( iLeftnDim; iDim++){
- aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2]);
- aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2+1]);
- }
- }
- for(iDim=0; iDimnDim; iDim++){
- aCenterCoord[iDim] = (aCenterCoord[iDim]/(nCell*(RtreeDValue)2));
- }
-
- for(ii=0; iinDim; iDim++){
- RtreeDValue coord = (DCOORD(aCell[ii].aCoord[iDim*2+1]) -
- DCOORD(aCell[ii].aCoord[iDim*2]));
- aDistance[ii] += (coord-aCenterCoord[iDim])*(coord-aCenterCoord[iDim]);
- }
- }
-
- SortByDistance(aOrder, nCell, aDistance, aSpare);
- nodeZero(pRtree, pNode);
-
- for(ii=0; rc==SQLITE_OK && ii<(nCell-(RTREE_MINCELLS(pRtree)+1)); ii++){
- RtreeCell *p = &aCell[aOrder[ii]];
- nodeInsertCell(pRtree, pNode, p);
- if( p->iRowid==pCell->iRowid ){
- if( iHeight==0 ){
- rc = rowidWrite(pRtree, p->iRowid, pNode->iNode);
- }else{
- rc = parentWrite(pRtree, p->iRowid, pNode->iNode);
- }
- }
- }
- if( rc==SQLITE_OK ){
- rc = fixBoundingBox(pRtree, pNode);
- }
- for(; rc==SQLITE_OK && iiiNode currently contains
- ** the height of the sub-tree headed by the cell.
- */
- RtreeNode *pInsert;
- RtreeCell *p = &aCell[aOrder[ii]];
- rc = ChooseLeaf(pRtree, p, iHeight, &pInsert);
- if( rc==SQLITE_OK ){
- int rc2;
- rc = rtreeInsertCell(pRtree, pInsert, p, iHeight);
- rc2 = nodeRelease(pRtree, pInsert);
- if( rc==SQLITE_OK ){
- rc = rc2;
- }
- }
- }
-
- sqlite3_free(aCell);
- return rc;
-}
-
+
/*
** Insert cell pCell into node pNode. Node pNode is the head of a
** subtree iHeight high (leaf nodes have iHeight==0).
@@ -2988,12 +2848,7 @@
}
}
if( nodeInsertCell(pRtree, pNode, pCell) ){
- if( iHeight<=pRtree->iReinsertHeight || pNode->iNode==1){
- rc = SplitNode(pRtree, pNode, pCell, iHeight);
- }else{
- pRtree->iReinsertHeight = iHeight;
- rc = Reinsert(pRtree, pNode, pCell, iHeight);
- }
+ rc = SplitNode(pRtree, pNode, pCell, iHeight);
}else{
rc = AdjustTree(pRtree, pNode, pCell);
if( ALWAYS(rc==SQLITE_OK) ){
@@ -3336,7 +3191,6 @@
}
if( rc==SQLITE_OK ){
int rc2;
- pRtree->iReinsertHeight = -1;
rc = rtreeInsertCell(pRtree, pLeaf, &cell, 0);
rc2 = nodeRelease(pRtree, pLeaf);
if( rc==SQLITE_OK ){
@@ -3477,8 +3331,11 @@
return 0;
}
+/* Forward declaration */
+static int rtreeIntegrity(sqlite3_vtab*, const char*, const char*, int, char**);
+
static sqlite3_module rtreeModule = {
- 3, /* iVersion */
+ 4, /* iVersion */
rtreeCreate, /* xCreate - create a table */
rtreeConnect, /* xConnect - connect to an existing table */
rtreeBestIndex, /* xBestIndex - Determine search strategy */
@@ -3501,7 +3358,8 @@
rtreeSavepoint, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- rtreeShadowName /* xShadowName */
+ rtreeShadowName, /* xShadowName */
+ rtreeIntegrity /* xIntegrity */
};
static int rtreeSqlInit(
@@ -3757,22 +3615,27 @@
}
sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1);
+ sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS);
+
/* Allocate the sqlite3_vtab structure */
nDb = (int)strlen(argv[1]);
nName = (int)strlen(argv[2]);
- pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName+2);
+ pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName*2+8);
if( !pRtree ){
return SQLITE_NOMEM;
}
- memset(pRtree, 0, sizeof(Rtree)+nDb+nName+2);
+ memset(pRtree, 0, sizeof(Rtree)+nDb+nName*2+8);
pRtree->nBusy = 1;
pRtree->base.pModule = &rtreeModule;
pRtree->zDb = (char *)&pRtree[1];
pRtree->zName = &pRtree->zDb[nDb+1];
+ pRtree->zNodeName = &pRtree->zName[nName+1];
pRtree->eCoordType = (u8)eCoordType;
memcpy(pRtree->zDb, argv[1], nDb);
memcpy(pRtree->zName, argv[2], nName);
+ memcpy(pRtree->zNodeName, argv[2], nName);
+ memcpy(&pRtree->zNodeName[nName], "_node", 6);
/* Create/Connect to the underlying relational database schema. If
@@ -4269,7 +4132,6 @@
){
RtreeCheck check; /* Common context for various routines */
sqlite3_stmt *pStmt = 0; /* Used to find column count of rtree table */
- int bEnd = 0; /* True if transaction should be closed */
int nAux = 0; /* Number of extra columns. */
/* Initialize the context object */
@@ -4278,14 +4140,6 @@
check.zDb = zDb;
check.zTab = zTab;
- /* If there is not already an open transaction, open one now. This is
- ** to ensure that the queries run as part of this integrity-check operate
- ** on a consistent snapshot. */
- if( sqlite3_get_autocommit(db) ){
- check.rc = sqlite3_exec(db, "BEGIN", 0, 0, 0);
- bEnd = 1;
- }
-
/* Find the number of auxiliary columns */
if( check.rc==SQLITE_OK ){
pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.'%q_rowid'", zDb, zTab);
@@ -4326,16 +4180,35 @@
sqlite3_finalize(check.aCheckMapping[0]);
sqlite3_finalize(check.aCheckMapping[1]);
- /* If one was opened, close the transaction */
- if( bEnd ){
- int rc = sqlite3_exec(db, "END", 0, 0, 0);
- if( check.rc==SQLITE_OK ) check.rc = rc;
- }
*pzReport = check.zReport;
return check.rc;
}
/*
+** Implementation of the xIntegrity method for Rtree.
+*/
+static int rtreeIntegrity(
+ sqlite3_vtab *pVtab, /* The virtual table to check */
+ const char *zSchema, /* Schema in which the virtual table lives */
+ const char *zName, /* Name of the virtual table */
+ int isQuick, /* True for a quick_check */
+ char **pzErr /* Write results here */
+){
+ Rtree *pRtree = (Rtree*)pVtab;
+ int rc;
+ assert( pzErr!=0 && *pzErr==0 );
+ UNUSED_PARAMETER(zSchema);
+ UNUSED_PARAMETER(zName);
+ UNUSED_PARAMETER(isQuick);
+ rc = rtreeCheckTable(pRtree->db, pRtree->zDb, pRtree->zName, pzErr);
+ if( rc==SQLITE_OK && *pzErr ){
+ *pzErr = sqlite3_mprintf("In RTree %s.%s:\n%z",
+ pRtree->zDb, pRtree->zName, *pzErr);
+ }
+ return rc;
+}
+
+/*
** Usage:
**
** rtreecheck();
diff -Nru sqlite3-3.41.0-0/ext/rtree/rtree1.test sqlite3-3.44.0-0/ext/rtree/rtree1.test
--- sqlite3-3.41.0-0/ext/rtree/rtree1.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/rtree/rtree1.test 2023-11-04 14:24:27.000000000 +0000
@@ -756,4 +756,45 @@
SELECT * FROM t1 JOIN t0 ON true RIGHT JOIN rt0 ON x0>a WHERE x0 = 0;
} {- - 0 0.0 0.0}
+# 2023-05-19 https://sqlite.org/forum/forumpost/da61c4a1b5b4af19
+# Do not omit constraints that involve equality comparisons of
+# floating-point values.
+#
+reset_db
+do_execsql_test 21.0 {
+ CREATE VIRTUAL TABLE t1 USING rtree(id, x0, x1);
+ INSERT INTO t1 VALUES(0, 1, 9223372036854775807);
+ SELECT count(*) FROM t1 WHERE x1=9223372036854775807;
+} {0}
+do_execsql_test 21.1 {
+ SELECT x1=9223372036854775807 FROM t1;
+} {0}
+
+# 2023-05-22 https://sqlite.org/forum/forumpost/da70ee0d0d
+# Round-off error associated with using large integer constraints on
+# a rtree search.
+#
+if {$tcl_platform(machine)!="i686" || $tcl_platform(os)!="Linux"} {
+ reset_db
+ do_execsql_test 22.0 {
+ CREATE VIRTUAL TABLE t1 USING rtree ( id, x0, x1 );
+ INSERT INTO t1 VALUES (123, 9223372036854775799, 9223372036854775800);
+ SELECT id FROM t1 WHERE x0 > 9223372036854775807;
+ } {123}
+ do_execsql_test 22.1 {
+ SELECT id, x0 > 9223372036854775807 AS 'a0' FROM t1;
+ } {123 1}
+}
+
+# 2023-10-14 dbsqlfuzz --sql-fuzz find. rtreecheck() should not call
+# BEGIN/COMMIT because that causes problems with statement transactions,
+# and it is unnecessary.
+#
+reset_db
+do_test 23.0 {
+ db eval {CREATE TABLE t1(a,b,c);}
+ catch {db eval {CREATE TABLE t2 AS SELECT rtreecheck('t1') AS y;}}
+ db eval {PRAGMA integrity_check;}
+} {ok}
+
finish_test
diff -Nru sqlite3-3.41.0-0/ext/rtree/rtree6.test sqlite3-3.44.0-0/ext/rtree/rtree6.test
--- sqlite3-3.41.0-0/ext/rtree/rtree6.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/rtree/rtree6.test 2023-11-04 14:24:27.000000000 +0000
@@ -104,6 +104,7 @@
} {
QUERY PLAN
|--SCAN t1 VIRTUAL TABLE INDEX 2:C0E1
+ |--BLOOM FILTER ON t2 (v=?)
`--SEARCH t2 USING AUTOMATIC COVERING INDEX (v=?)
}
do_eqp_test rtree6.2.4.2 {
@@ -111,6 +112,7 @@
} {
QUERY PLAN
|--SCAN t1 VIRTUAL TABLE INDEX 2:C0E1
+ |--BLOOM FILTER ON t2 (v=?)
`--SEARCH t2 USING AUTOMATIC PARTIAL COVERING INDEX (v=?)
}
diff -Nru sqlite3-3.41.0-0/ext/rtree/rtree8.test sqlite3-3.44.0-0/ext/rtree/rtree8.test
--- sqlite3-3.41.0-0/ext/rtree/rtree8.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/rtree/rtree8.test 2023-11-04 14:24:27.000000000 +0000
@@ -75,7 +75,7 @@
#
populate_t1 1500
do_rtree_integrity_test rtree8-1.3.0 t1
-do_execsql_test rtree8-1.3.1 { SELECT max(nodeno) FROM t1_node } {164}
+do_execsql_test rtree8-1.3.1 { SELECT max(nodeno) FROM t1_node } {183}
do_test rtree8-1.3.2 {
set rowids [execsql {SELECT min(rowid) FROM t1_rowid GROUP BY nodeno}]
set stmt_list [list]
diff -Nru sqlite3-3.41.0-0/ext/rtree/rtreeA.test sqlite3-3.44.0-0/ext/rtree/rtreeA.test
--- sqlite3-3.41.0-0/ext/rtree/rtreeA.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/rtree/rtreeA.test 2023-11-04 14:24:27.000000000 +0000
@@ -113,7 +113,7 @@
SELECT rtreecheck('main', 't1')
} {{Node 1 missing from database
Wrong number of entries in %_rowid table - expected 0, actual 500
-Wrong number of entries in %_parent table - expected 0, actual 23}}
+Wrong number of entries in %_parent table - expected 0, actual 25}}
do_execsql_test rtreeA-1.2.0 { DROP TABLE t1_node } {}
do_corruption_tests rtreeA-1.2 -error "database disk image is malformed" {
@@ -191,7 +191,7 @@
SELECT rtreecheck('main', 't1')
} {{Rtree depth out of range (65535)
Wrong number of entries in %_rowid table - expected 0, actual 499
-Wrong number of entries in %_parent table - expected 0, actual 23}}
+Wrong number of entries in %_parent table - expected 0, actual 25}}
#-------------------------------------------------------------------------
# Set the "number of entries" field on some nodes incorrectly.
diff -Nru sqlite3-3.41.0-0/ext/rtree/rtree_util.tcl sqlite3-3.44.0-0/ext/rtree/rtree_util.tcl
--- sqlite3-3.41.0-0/ext/rtree/rtree_util.tcl 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/rtree/rtree_util.tcl 2023-11-04 14:24:27.000000000 +0000
@@ -192,6 +192,6 @@
}
proc do_rtree_integrity_test {tn tbl} {
- uplevel [list do_execsql_test $tn "SELECT rtreecheck('$tbl')" ok]
+ uplevel [list do_execsql_test $tn.1 "SELECT rtreecheck('$tbl')" ok]
+ uplevel [list do_execsql_test $tn.2 "PRAGMA integrity_check" ok]
}
-
diff -Nru sqlite3-3.41.0-0/ext/rtree/rtreecheck.test sqlite3-3.44.0-0/ext/rtree/rtreecheck.test
--- sqlite3-3.41.0-0/ext/rtree/rtreecheck.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/rtree/rtreecheck.test 2023-11-04 14:24:27.000000000 +0000
@@ -78,6 +78,11 @@
SELECT rtreecheck('r1')
} {{Dimension 0 of cell 0 on node 1 is corrupt
Dimension 1 of cell 3 on node 1 is corrupt}}
+do_execsql_test 2.3b {
+ PRAGMA integrity_check;
+} {{In RTree main.r1:
+Dimension 0 of cell 0 on node 1 is corrupt
+Dimension 1 of cell 3 on node 1 is corrupt}}
setup_simple_db
do_execsql_test 2.4 {
@@ -85,12 +90,21 @@
SELECT rtreecheck('r1')
} {{Mapping (3 -> 1) missing from %_rowid table
Wrong number of entries in %_rowid table - expected 5, actual 4}}
+do_execsql_test 2.4b {
+ PRAGMA integrity_check
+} {{In RTree main.r1:
+Mapping (3 -> 1) missing from %_rowid table
+Wrong number of entries in %_rowid table - expected 5, actual 4}}
setup_simple_db
do_execsql_test 2.5 {
UPDATE r1_rowid SET nodeno=2 WHERE rowid=3;
SELECT rtreecheck('r1')
} {{Found (3 -> 2) in %_rowid table, expected (3 -> 1)}}
+do_execsql_test 2.5b {
+ PRAGMA integrity_check
+} {{In RTree main.r1:
+Found (3 -> 2) in %_rowid table, expected (3 -> 1)}}
reset_db
do_execsql_test 3.0 {
@@ -104,14 +118,16 @@
INSERT INTO r1 VALUES(7, 5, 0x00000080);
INSERT INTO r1 VALUES(8, 5, 0x40490fdb);
INSERT INTO r1 VALUES(9, 0x7f800000, 0x7f900000);
- SELECT rtreecheck('r1')
-} {ok}
+ SELECT rtreecheck('r1');
+ PRAGMA integrity_check;
+} {ok ok}
do_execsql_test 3.1 {
CREATE VIRTUAL TABLE r2 USING rtree_i32(id, x1, x2);
INSERT INTO r2 VALUES(2, -1*(1<<31), -1*(1<<31)+5);
- SELECT rtreecheck('r2')
-} {ok}
+ SELECT rtreecheck('r2');
+ PRAGMA integrity_check;
+} {ok ok}
sqlite3_db_config db DEFENSIVE 0
do_execsql_test 3.2 {
@@ -125,6 +141,11 @@
UPDATE r2_node SET data = X'00001234';
SELECT rtreecheck('r2')!='ok';
} {1}
+do_execsql_test 3.4 {
+ PRAGMA integrity_check;
+} {{In RTree main.r2:
+Node 1 is too small for cell count of 4660 (4 bytes)
+Wrong number of entries in %_rowid table - expected 0, actual 1}}
do_execsql_test 4.0 {
CREATE TABLE notanrtree(i);
@@ -181,4 +202,3 @@
}
finish_test
-
diff -Nru sqlite3-3.41.0-0/ext/rtree/rtreedoc.test sqlite3-3.44.0-0/ext/rtree/rtreedoc.test
--- sqlite3-3.41.0-0/ext/rtree/rtreedoc.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/rtree/rtreedoc.test 2023-11-04 14:24:27.000000000 +0000
@@ -601,11 +601,21 @@
SELECT A.id FROM demo_index AS A, demo_index AS B
WHERE A.maxX>=B.minX AND A.minX<=B.maxX
AND A.maxY>=B.minY AND A.minY<=B.maxY
- AND B.id=28269;
+ AND B.id=28269 ORDER BY +A.id;
} {
- 28293 28216 28322 28286 28269
- 28215 28336 28262 28291 28320
- 28313 28298 28287
+ 28215
+ 28216
+ 28262
+ 28269
+ 28286
+ 28287
+ 28291
+ 28293
+ 28298
+ 28313
+ 28320
+ 28322
+ 28336
}
# EVIDENCE-OF: R-02723-34107 Note that it is not necessary for all
@@ -1575,7 +1585,7 @@
do_test 3.6 {
execsql { INSERT INTO rt2_parent VALUES(1000, 1000) }
execsql { SELECT rtreecheck('rt2') }
-} {{Wrong number of entries in %_parent table - expected 9, actual 10}}
+} {{Wrong number of entries in %_parent table - expected 10, actual 11}}
execsql ROLLBACK
diff -Nru sqlite3-3.41.0-0/ext/rtree/rtreefuzz001.test sqlite3-3.44.0-0/ext/rtree/rtreefuzz001.test
--- sqlite3-3.41.0-0/ext/rtree/rtreefuzz001.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/rtree/rtreefuzz001.test 2023-11-04 14:24:27.000000000 +0000
@@ -1043,7 +1043,7 @@
| end crash-2e81f5dce5cbd4.db}]
execsql { PRAGMA writable_schema = 1;}
catchsql {UPDATE t1 SET ex= ex ISNULL}
-} {1 {database disk image is malformed}}
+} {0 {}}
do_test rtreefuzz001-600 {
sqlite3 db {}
diff -Nru sqlite3-3.41.0-0/ext/session/session2.test sqlite3-3.44.0-0/ext/session/session2.test
--- sqlite3-3.41.0-0/ext/session/session2.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/session2.test 2023-11-04 14:24:27.000000000 +0000
@@ -191,7 +191,7 @@
}
foreach {tn sql} [string map {%T1% t1 %T2% t2 %T3% t3 %T4% t4} $set_of_tests] {
- do_then_apply_sql $sql
+ do_then_apply_sql -ignorenoop $sql
do_test 2.$tn { compare_db db db2 } {}
}
@@ -598,7 +598,7 @@
INSERT INTO t1 SELECT NULL, 0, 0, 0, 0, 0 FROM s
}
-do_then_apply_sql {
+do_then_apply_sql -ignorenoop {
UPDATE t1 SET f=f+1 WHERE a=1;
UPDATE t1 SET e=e+1 WHERE a=2;
UPDATE t1 SET e=e+1, f=f+1 WHERE a=3;
diff -Nru sqlite3-3.41.0-0/ext/session/session3.test sqlite3-3.44.0-0/ext/session/session3.test
--- sqlite3-3.41.0-0/ext/session/session3.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/session3.test 2023-11-04 14:24:27.000000000 +0000
@@ -135,8 +135,8 @@
DROP TABLE t2;
CREATE TABLE t2(a, b PRIMARY KEY, c, d);
}
- list [catch { S changeset } msg] $msg
-} {1 SQLITE_SCHEMA}
+ catch { S changeset }
+} {0}
do_test 2.2.3 {
S delete
sqlite3session S db main
@@ -167,8 +167,8 @@
CREATE TABLE t2(a, b PRIMARY KEY, c, d);
INSERT INTO t2 VALUES(4, 5, 6, 7);
}
- list [catch { S changeset } msg] $msg
-} {1 SQLITE_SCHEMA}
+ catch { S changeset }
+} {0}
do_test 2.3 {
S delete
diff -Nru sqlite3-3.41.0-0/ext/session/sessionD.test sqlite3-3.44.0-0/ext/session/sessionD.test
--- sqlite3-3.41.0-0/ext/session/sessionD.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sessionD.test 2023-11-04 14:24:27.000000000 +0000
@@ -41,30 +41,6 @@
return [md5 $txt]
}
-proc do_diff_test {tn setup} {
- reset_db
- forcedelete test.db2
- execsql { ATTACH 'test.db2' AS aux }
- execsql $setup
-
- sqlite3session S db main
- foreach tbl [db eval {SELECT name FROM sqlite_master WHERE type='table'}] {
- S attach $tbl
- S diff aux $tbl
- }
-
- set C [S changeset]
- S delete
-
- sqlite3 db2 test.db2
- sqlite3changeset_apply db2 $C ""
- uplevel do_test $tn.1 [list {execsql { PRAGMA integrity_check } db2}] ok
- db2 close
-
- set cksum [scksum db main]
- uplevel do_test $tn.2 [list {scksum db aux}] [list $cksum]
-}
-
# Ensure that the diff produced by comparing the current contents of [db]
# with itself is empty.
proc do_empty_diff_test {tn} {
diff -Nru sqlite3-3.41.0-0/ext/session/sessionG.test sqlite3-3.44.0-0/ext/session/sessionG.test
--- sqlite3-3.41.0-0/ext/session/sessionG.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sessionG.test 2023-11-04 14:24:27.000000000 +0000
@@ -34,7 +34,7 @@
INSERT INTO t1 VALUES(2, 'two');
INSERT INTO t1 VALUES(3, 'three');
}
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
DELETE FROM t1 WHERE a=1;
INSERT INTO t1 VALUES(4, 'one');
}
@@ -42,7 +42,7 @@
} {}
do_test 1.1 {
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
DELETE FROM t1 WHERE a=4;
INSERT INTO t1 VALUES(1, 'one');
}
@@ -51,7 +51,7 @@
do_test 1.2 {
execsql { INSERT INTO t1 VALUES(5, 'five') } db2
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
INSERT INTO t1 VALUES(11, 'eleven');
INSERT INTO t1 VALUES(12, 'five');
}
@@ -82,7 +82,7 @@
# It is not possible to apply the changeset generated by the following
# SQL, as none of the three updated rows may be updated as part of the
# first pass.
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
UPDATE t1 SET b=0 WHERE a=1;
UPDATE t1 SET b=1 WHERE a=2;
UPDATE t1 SET b=2 WHERE a=3;
@@ -109,7 +109,7 @@
} {}
do_test 3.3 {
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
UPDATE t1 SET b=4 WHERE a=3;
UPDATE t1 SET b=3 WHERE a=2;
UPDATE t1 SET b=2 WHERE a=1;
@@ -118,7 +118,7 @@
} {}
do_test 3.4 {
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
UPDATE t1 SET b=1 WHERE a=1;
UPDATE t1 SET b=2 WHERE a=2;
UPDATE t1 SET b=3 WHERE a=3;
@@ -148,7 +148,7 @@
} {}
do_test 4.2 {
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
UPDATE t1 SET b=4 WHERE a=3;
UPDATE t1 SET b=3 WHERE a=2;
UPDATE t1 SET b=2 WHERE a=1;
@@ -161,7 +161,7 @@
} {}
do_test 4.3 {
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
UPDATE t1 SET b=1 WHERE a=1;
UPDATE t1 SET b=2 WHERE a=2;
UPDATE t1 SET b=3 WHERE a=3;
@@ -191,7 +191,7 @@
}
do_test 5.1 {
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
INSERT INTO t1 VALUES(1, 2, 3);
INSERT INTO t2 VALUES(4, 5, 6);
INSERT INTO t3 VALUES(7, 8, 9);
diff -Nru sqlite3-3.41.0-0/ext/session/sessionH.test sqlite3-3.44.0-0/ext/session/sessionH.test
--- sqlite3-3.41.0-0/ext/session/sessionH.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sessionH.test 2023-11-04 14:24:27.000000000 +0000
@@ -25,7 +25,7 @@
do_common_sql {
CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b));
}
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
WITH s(i) AS (
VALUES(1) UNION ALL SELECT i+1 FROM s WHERe i<10000
)
diff -Nru sqlite3-3.41.0-0/ext/session/session_common.tcl sqlite3-3.44.0-0/ext/session/session_common.tcl
--- sqlite3-3.41.0-0/ext/session/session_common.tcl 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/session_common.tcl 2023-11-04 14:24:27.000000000 +0000
@@ -52,6 +52,7 @@
proc bgerror {args} { set ::background_error $args }
sqlite3session S db main
+ S object_config rowid 1
foreach t $O(-tables) { S attach $t }
execsql $O(-sql)
@@ -81,6 +82,7 @@
}
set rc [catch {
sqlite3session S db $dbname
+ S object_config rowid 1
db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" {
S attach $name
}
@@ -112,24 +114,59 @@
return $patchset
}
-proc do_then_apply_sql {sql {dbname main}} {
- proc xConflict args { return "OMIT" }
+# Usage: do_then_apply_sql ?-ignorenoop? SQL ?DBNAME?
+#
+proc do_then_apply_sql {args} {
+
+ set bIgnoreNoop 0
+ set a1 [lindex $args 0]
+ if {[string length $a1]>1 && [string first $a1 -ignorenoop]==0} {
+ set bIgnoreNoop 1
+ set args [lrange $args 1 end]
+ }
+
+ if {[llength $args]!=1 && [llength $args]!=2} {
+ error "usage: do_then_apply_sql ?-ignorenoop? SQL ?DBNAME?"
+ }
+
+ set sql [lindex $args 0]
+ if {[llength $args]==1} {
+ set dbname main
+ } else {
+ set dbname [lindex $args 1]
+ }
+
+ set ::n_conflict 0
+ proc xConflict args { incr ::n_conflict ; return "OMIT" }
set rc [catch {
sqlite3session S db $dbname
+ S object_config rowid 1
db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" {
S attach $name
}
db eval $sql
- sqlite3changeset_apply db2 [S changeset] xConflict
+ set ::changeset [S changeset]
+ sqlite3changeset_apply db2 $::changeset xConflict
} msg]
catch { S delete }
-
if {$rc} {error $msg}
+
+ if {$bIgnoreNoop} {
+ set nSave $::n_conflict
+ set ::n_conflict 0
+ proc xConflict args { incr ::n_conflict ; return "OMIT" }
+ sqlite3changeset_apply_v2 -ignorenoop db2 $::changeset xConflict
+ if {$::n_conflict!=$nSave} {
+ error "-ignorenoop problem ($::n_conflict $nSave)..."
+ }
+ }
}
proc do_iterator_test {tn tbl_list sql res} {
sqlite3session S db main
+ S object_config rowid 1
+
if {[llength $tbl_list]==0} { S attach * }
foreach t $tbl_list {S attach $t}
@@ -139,6 +176,7 @@
foreach v $res { lappend r $v }
set x [list]
+# set ::c [S changeset] ; execsql_pp { SELECT quote($::c) }
sqlite3session_foreach c [S changeset] { lappend x $c }
uplevel do_test $tn [list [list set {} $x]] [list $r]
@@ -213,3 +251,49 @@
if {$txt==""} {set txt zero}
return $txt
}
+
+proc scksum {db dbname} {
+
+ if {$dbname=="temp"} {
+ set master sqlite_temp_master
+ } else {
+ set master $dbname.sqlite_master
+ }
+
+ set alltab [$db eval "SELECT name FROM $master WHERE type='table'"]
+ set txt [$db eval "SELECT * FROM $master ORDER BY type,name,sql"]
+ foreach tab $alltab {
+ set cols [list]
+ db eval "PRAGMA $dbname.table_info = $tab" x {
+ lappend cols "quote($x(name))"
+ }
+ set cols [join $cols ,]
+ append txt [db eval "SELECT $cols FROM $dbname.$tab ORDER BY $cols"]
+ }
+ return [md5 $txt]
+}
+
+proc do_diff_test {tn setup} {
+ reset_db
+ forcedelete test.db2
+ execsql { ATTACH 'test.db2' AS aux }
+ execsql $setup
+
+ sqlite3session S db main
+ S object_config rowid 1
+ foreach tbl [db eval {SELECT name FROM sqlite_master WHERE type='table'}] {
+ S attach $tbl
+ S diff aux $tbl
+ }
+
+ set C [S changeset]
+ S delete
+
+ sqlite3 db2 test.db2
+ sqlite3changeset_apply db2 $C ""
+ uplevel do_test $tn.1 [list {execsql { PRAGMA integrity_check } db2}] ok
+ db2 close
+
+ set cksum [scksum db main]
+ uplevel do_test $tn.2 [list {scksum db aux}] [list $cksum]
+}
diff -Nru sqlite3-3.41.0-0/ext/session/sessionalter.test sqlite3-3.44.0-0/ext/session/sessionalter.test
--- sqlite3-3.41.0-0/ext/session/sessionalter.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sessionalter.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,238 @@
+# 2023 October 02
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements that the sessions module interacts well with
+# the ALTER TABLE ADD COLUMN command.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] session_common.tcl]
+source $testdir/tester.tcl
+
+ifcapable !session {finish_test; return}
+set testprefix sessionalter
+
+
+forcedelete test.db2
+sqlite3 db2 test.db2
+
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+}
+
+do_execsql_test -db db2 1.1 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c DEFAULT 1234);
+}
+
+do_then_apply_sql {
+ INSERT INTO t1 VALUES(1, 'one');
+ INSERT INTO t1 VALUES(2, 'two');
+}
+
+do_execsql_test -db db2 1.2 {
+ SELECT * FROM t1
+} {
+ 1 one 1234
+ 2 two 1234
+}
+
+do_then_apply_sql {
+ UPDATE t1 SET b='four' WHERE a=2;
+}
+
+do_execsql_test -db db2 1.3 {
+ SELECT * FROM t1
+} {
+ 1 one 1234
+ 2 four 1234
+}
+
+do_then_apply_sql {
+ DELETE FROM t1 WHERE a=1;
+}
+
+do_execsql_test -db db2 1.4 {
+ SELECT * FROM t1
+} {
+ 2 four 1234
+}
+
+db2 close
+
+#--------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 2.0 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+}
+
+do_test 2.1 {
+ sqlite3session S db main
+ S attach t1
+ set {} {}
+} {}
+do_execsql_test 2.2 {
+ INSERT INTO t1 VALUES(1, 2);
+ ALTER TABLE t1 ADD COLUMN c DEFAULT 'abcd';
+ INSERT INTO t1 VALUES(2, 3, 4);
+}
+do_changeset_test 2.3 S {
+ {INSERT t1 0 X.. {} {i 1 i 2 t abcd}}
+ {INSERT t1 0 X.. {} {i 2 i 3 i 4}}
+}
+
+do_iterator_test 2.4 {} {
+ DELETE FROM t1 WHERE a=2;
+ ALTER TABLE t1 ADD COLUMN d DEFAULT 'abcd';
+ ALTER TABLE t1 ADD COLUMN e DEFAULT 5;
+ ALTER TABLE t1 ADD COLUMN f DEFAULT 7.2;
+ -- INSERT INTO t1 VALUES(9, 9, 9, 9);
+} {
+ {DELETE t1 0 X..... {i 2 i 3 i 4 t abcd i 5 f 7.2} {}}
+}
+
+#-------------------------------------------------------------------------
+# Tests of the sqlite3changegroup_xxx() APIs.
+#
+reset_db
+do_execsql_test 3.0 {
+ CREATE TABLE t1(x INTEGER PRIMARY KEY, y);
+ CREATE TABLE t2(x PRIMARY KEY, y);
+ CREATE TABLE t3(x, y);
+ CREATE TABLE t4(y PRIMARY KEY, x) WITHOUT ROWID;
+
+ INSERT INTO t1 VALUES(1, 2), (3, 4), (5, 6);
+ INSERT INTO t2 VALUES('one', 'two'), ('three', 'four'), ('five', 'six');
+ INSERT INTO t3 VALUES(1, 2), (3, 4), (5, 6);
+
+ INSERT INTO t4(x, y) VALUES(1, 2), (3, 4), (5, 6);
+}
+
+db_save_and_close
+foreach {tn sql1 at sql2} {
+ 1 {
+ INSERT INTO t1(x, y) VALUES(7, 8);
+ } {
+ ALTER TABLE t1 ADD COLUMN z DEFAULT 10;
+ } {
+ UPDATE t1 SET y=11 WHERE x=7;
+ }
+
+ 2 {
+ UPDATE t2 SET y='two.two' WHERE x='one';
+ DELETE FROM t2 WHERE x='five';
+ INSERT INTO t2(x, y) VALUES('seven', 'eight');
+ } {
+ ALTER TABLE t2 ADD COLUMN z;
+ ALTER TABLE t2 ADD COLUMN zz;
+ } {
+ }
+
+ 3 {
+ DELETE FROM t2 WHERE x='five';
+ } {
+ ALTER TABLE t2 ADD COLUMN z DEFAULT 'xyz';
+ } {
+ }
+
+ 4 {
+ UPDATE t2 SET y='two.two' WHERE x='three';
+ } {
+ ALTER TABLE t2 ADD COLUMN z;
+ } {
+ UPDATE t2 SET z='abc' WHERE x='one';
+ }
+
+ 5* {
+ UPDATE t2 SET y='two.two' WHERE x='three';
+ } {
+ ALTER TABLE t2 ADD COLUMN z DEFAULT 'defu1';
+ } {
+ }
+
+ 6* {
+ INSERT INTO t2(x, y) VALUES('nine', 'ten');
+ } {
+ ALTER TABLE t2 ADD COLUMN z;
+ ALTER TABLE t2 ADD COLUMN a DEFAULT 'eelve';
+ ALTER TABLE t2 ADD COLUMN b DEFAULT x'1234abcd';
+ ALTER TABLE t2 ADD COLUMN c DEFAULT 4.2;
+ ALTER TABLE t2 ADD COLUMN d DEFAULT NULL;
+ } {
+ }
+
+ 7 {
+ INSERT INTO t3(x, y) VALUES(7, 8);
+ UPDATE t3 SET y='fourteen' WHERE x=1;
+ DELETE FROM t3 WHERE x=3;
+ } {
+ ALTER TABLE t3 ADD COLUMN c;
+ } {
+ INSERT INTO t3(x, y, c) VALUES(9, 10, 11);
+ }
+
+ 8 {
+ INSERT INTO t4(x, y) VALUES(7, 8);
+ UPDATE t4 SET y='fourteen' WHERE x=1;
+ DELETE FROM t4 WHERE x=3;
+ } {
+ ALTER TABLE t4 ADD COLUMN c;
+ } {
+ INSERT INTO t4(x, y, c) VALUES(9, 10, 11);
+ }
+} {
+ foreach {tn2 cmd} {
+ 1 changeset_from_sql
+ 2 patchset_from_sql
+ } {
+ db_restore_and_reopen
+
+ set C1 [$cmd $sql1]
+ execsql $at
+ set C2 [$cmd $sql2]
+
+ sqlite3changegroup grp
+ grp schema db main
+ grp add $C1
+ grp add $C2
+ set T1 [grp output]
+ grp delete
+
+ db_restore_and_reopen
+ execsql $at
+ set T2 [$cmd "$sql1 ; $sql2"]
+
+ if {[string range $tn end end]!="*"} {
+ do_test 3.1.$tn.$tn2.1 { changeset_to_list $T1 } [changeset_to_list $T2]
+ set testname "$tn.$tn2"
+ } else {
+ set testname "[string range $tn 0 end-1].$tn2"
+ }
+
+ db_restore_and_reopen
+ proc xConflict {args} { return "REPLACE" }
+ sqlite3changeset_apply_v2 db $T1 xConflict
+ set S1 [scksum db main]
+
+ db_restore_and_reopen
+ sqlite3changeset_apply_v2 db $T2 xConflict
+ set S2 [scksum db main]
+
+ # if { $tn==7 } { puts [changeset_to_list $T1] }
+
+ do_test 3.1.$tn.2 { set S1 } $S2
+ }
+}
+
+
+finish_test
+
diff -Nru sqlite3-3.41.0-0/ext/session/sessionat.test sqlite3-3.44.0-0/ext/session/sessionat.test
--- sqlite3-3.41.0-0/ext/session/sessionat.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sessionat.test 2023-11-04 14:24:27.000000000 +0000
@@ -110,7 +110,7 @@
CREATE TABLE t3(a, b, c DEFAULT 'D', PRIMARY KEY(b)) %WR%;
}
do_test $tn.3.2 {
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
INSERT INTO t3 VALUES(1, 2);
INSERT INTO t3 VALUES(3, 4);
INSERT INTO t3 VALUES(5, 6);
@@ -118,7 +118,7 @@
db2 eval {SELECT * FROM t3}
} {1 2 D 3 4 D 5 6 D}
do_test $tn.3.3 {
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
UPDATE t3 SET a=45 WHERE b=4;
DELETE FROM t3 WHERE a=5;
};
@@ -253,7 +253,7 @@
CREATE TABLE t8(a PRIMARY KEY, b, c, d DEFAULT 'D', e DEFAULT 'E');
}
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
INSERT INTO t8 VALUES(1, 2, 3);
INSERT INTO t8 VALUES(4, 5, 6);
}
@@ -264,7 +264,7 @@
SELECT * FROM t8
} {1 2 3 D E 4 5 6 D E}
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
UPDATE t8 SET c=45 WHERE a=4;
}
do_execsql_test $tn.7.3.1 {
@@ -282,7 +282,7 @@
do_execsql_test -db db2 $tn.8.1 {
CREATE TABLE t9(a PRIMARY KEY, b, c, d, e, f, g, h, i, j, k, l);
}
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
INSERT INTO t9 VALUES(1, 2, 3, 4, 5, 6, 7, 8);
}
do_then_apply_sql {
@@ -291,7 +291,7 @@
do_execsql_test -db db2 $tn.8.2 {
SELECT * FROM t9
} {1 2 3 4 5 6 7 450 {} {} {} {}}
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
UPDATE t9 SET h=NULL
}
do_execsql_test -db db2 $tn.8.2 {
diff -Nru sqlite3-3.41.0-0/ext/session/sessionbig.test sqlite3-3.44.0-0/ext/session/sessionbig.test
--- sqlite3-3.41.0-0/ext/session/sessionbig.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sessionbig.test 2023-11-04 14:24:27.000000000 +0000
@@ -43,7 +43,7 @@
}
do_test 1.2 {
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
@@ -71,7 +71,7 @@
do_test 1.4 {
set rc [catch {
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
diff -Nru sqlite3-3.41.0-0/ext/session/sessionfault.test sqlite3-3.44.0-0/ext/session/sessionfault.test
--- sqlite3-3.41.0-0/ext/session/sessionfault.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sessionfault.test 2023-11-04 14:24:27.000000000 +0000
@@ -44,7 +44,7 @@
faultsim_restore_and_reopen
sqlite3 db2 test.db2
} -body {
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
INSERT INTO t1 VALUES('a string value', 8, 9);
UPDATE t1 SET c = 10 WHERE a = 1;
DELETE FROM t1 WHERE a = 4;
diff -Nru sqlite3-3.41.0-0/ext/session/sessionfault2.test sqlite3-3.44.0-0/ext/session/sessionfault2.test
--- sqlite3-3.41.0-0/ext/session/sessionfault2.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sessionfault2.test 2023-11-04 14:24:27.000000000 +0000
@@ -132,7 +132,7 @@
faultsim_restore_and_reopen
sqlite3 db2 test.db2
} -body {
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
INSERT INTO sqlite_stat1 VALUES('x', 'y', 45);
UPDATE sqlite_stat1 SET stat = 123 WHERE tbl='t1' AND idx='i1';
UPDATE sqlite_stat1 SET stat = 456 WHERE tbl='t2';
diff -Nru sqlite3-3.41.0-0/ext/session/sessionfault3.test sqlite3-3.44.0-0/ext/session/sessionfault3.test
--- sqlite3-3.41.0-0/ext/session/sessionfault3.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sessionfault3.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,59 @@
+# 2016 October 6
+#
+# 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 session module.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] session_common.tcl]
+source $testdir/tester.tcl
+ifcapable !session {finish_test; return}
+set testprefix sessionfault3
+
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a, b, PRIMARY KEY(a));
+ INSERT INTO t1 VALUES(1, 2);
+ INSERT INTO t1 VALUES(3, 4);
+ INSERT INTO t1 VALUES('five', 'six');
+}
+
+set C1 [changeset_from_sql {
+ INSERT INTO t1 VALUES('seven', 'eight');
+ UPDATE t1 SET b=6 WHERE a='five';
+ DELETE FROM t1 WHERE a=1;
+}]
+
+do_execsql_test 1.1 {
+ ALTER TABLE t1 ADD COLUMN d DEFAULT 123;
+ ALTER TABLE t1 ADD COLUMN e DEFAULT 'string';
+}
+
+set C2 [changeset_from_sql {
+ UPDATE t1 SET e='new value' WHERE a='seven';
+ INSERT INTO t1 VALUES(0, 0, 0, 0);
+}]
+
+do_faultsim_test 1 -faults oom* -prep {
+ sqlite3changegroup G
+} -body {
+ G schema db main
+ G add $::C1
+ G add $::C2
+ G output
+ set {} {}
+} -test {
+ catch { G delete }
+ faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
+}
+
+finish_test
diff -Nru sqlite3-3.41.0-0/ext/session/sessionnoact.test sqlite3-3.44.0-0/ext/session/sessionnoact.test
--- sqlite3-3.41.0-0/ext/session/sessionnoact.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sessionnoact.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,110 @@
+# 2023 October 20
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] session_common.tcl]
+source $testdir/tester.tcl
+ifcapable !session {finish_test; return}
+
+set testprefix sessionnoact
+
+do_execsql_test 1.0 {
+ CREATE TABLE p1(a INTEGER PRIMARY KEY, b, c UNIQUE);
+ INSERT INTO p1 VALUES(1, 1, 'one');
+ INSERT INTO p1 VALUES(2, 2, 'two');
+ INSERT INTO p1 VALUES(3, 3, 'three');
+ INSERT INTO p1 VALUES(4, 4, 'four');
+}
+
+db_save
+
+set C [changeset_from_sql {
+ DELETE FROM p1 WHERE a=2;
+ UPDATE p1 SET c='six' WHERE a=3;
+ INSERT INTO p1 VALUES(5, 5, 'two');
+ INSERT INTO p1 VALUES(6, 6, 'three');
+}]
+
+db_restore_and_reopen
+
+do_execsql_test 1.1 {
+ CREATE TABLE c1(x INTEGER PRIMARY KEY, y,
+ FOREIGN KEY(y) REFERENCES p1(c) ON DELETE CASCADE ON UPDATE SET NULL
+ );
+
+ INSERT INTO c1 VALUES(10, 'one');
+ INSERT INTO c1 VALUES(20, 'two');
+ INSERT INTO c1 VALUES(30, 'three');
+ INSERT INTO c1 VALUES(40, 'four');
+}
+
+db_save
+
+do_execsql_test 1.2 {
+ PRAGMA foreign_keys = 1;
+}
+
+set ::nConflict 0
+proc conflict {args} {
+ incr ::nConflict
+ return "OMIT"
+}
+
+sqlite3changeset_apply_v2 db $C conflict
+
+do_execsql_test 1.3 {
+ SELECT * FROM c1
+} {
+ 10 one
+ 30 {}
+ 40 four
+}
+
+db_restore_and_reopen
+
+do_execsql_test 1.4 {
+ PRAGMA foreign_keys = 1;
+}
+
+do_execsql_test 1.5 {
+ UPDATE p1 SET c=12345 WHERE a = 45;
+}
+
+sqlite3changeset_apply_v2 -noaction db $C conflict
+do_execsql_test 1.6 {
+ SELECT * FROM c1
+} {
+ 10 one
+ 20 two
+ 30 three
+ 40 four
+}
+
+do_execsql_test 1.7 {
+ PRAGMA foreign_keys = 1;
+ UPDATE p1 SET c = 'ten' WHERE c='two';
+ SELECT * FROM c1;
+} {
+ 10 one
+ 20 {}
+ 30 three
+ 40 four
+}
+
+do_execsql_test 1.8 {
+ PRAGMA foreign_key_check
+}
+
+finish_test
diff -Nru sqlite3-3.41.0-0/ext/session/sessionnoop2.test sqlite3-3.44.0-0/ext/session/sessionnoop2.test
--- sqlite3-3.41.0-0/ext/session/sessionnoop2.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sessionnoop2.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,180 @@
+# 2011 March 07
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] session_common.tcl]
+source $testdir/tester.tcl
+ifcapable !session {finish_test; return}
+
+set testprefix sessionnoop2
+
+foreach {tn wo} {
+ 1 ""
+ 2 " WITHOUT ROWID "
+} {
+ reset_db
+ eval [string map [list %WO% $wo] {
+do_execsql_test $tn.1.0 {
+ CREATE TABLE t1(a PRIMARY KEY, b, c) %WO%;
+ INSERT INTO t1 VALUES('a', 'A', 'AAA');
+ INSERT INTO t1 VALUES('b', 'B', 'BBB');
+ INSERT INTO t1 VALUES('c', 'C', 'CCC');
+ INSERT INTO t1 VALUES('d', 'D', 'DDD');
+ INSERT INTO t1 VALUES('e', 'E', 'EEE');
+}
+
+forcedelete test.db2
+sqlite3 db2 test.db2
+
+do_execsql_test -db db2 $tn.1.1 {
+ CREATE TABLE t1(a PRIMARY KEY, b, c) %WO%;
+ INSERT INTO t1 VALUES('a', 'A', 'AAA');
+ INSERT INTO t1 VALUES('b', 'B', '123');
+ INSERT INTO t1 VALUES('c', 'C', 'CCC');
+ INSERT INTO t1 VALUES('e', 'E', 'EEE');
+ INSERT INTO t1 VALUES('f', 'F', 'FFF');
+}
+
+set C [changeset_from_sql {
+ UPDATE t1 SET c='123' WHERE a='b';
+ DELETE FROM t1 WHERE a='d';
+ INSERT INTO t1 VALUES('f', 'F', 'FFF');
+}]
+
+
+set ::conflict_list [list]
+proc xConflict {args} {
+ lappend ::conflict_list $args
+ return "OMIT"
+}
+do_test $tn.1.2 {
+ sqlite3changeset_apply_v2 db2 $C xConflict
+ set ::conflict_list
+} [list {*}{
+ {UPDATE t1 DATA {t b {} {} t BBB} {{} {} {} {} t 123} {t b t B t 123}}
+ {INSERT t1 CONFLICT {t f t F t FFF} {t f t F t FFF}}
+ {DELETE t1 NOTFOUND {t d t D t DDD}}
+}]
+do_test $tn.1.3 {
+ set ::conflict_list [list]
+ sqlite3changeset_apply_v2 db2 $C xConflict
+ set ::conflict_list
+} [list {*}{
+ {UPDATE t1 DATA {t b {} {} t BBB} {{} {} {} {} t 123} {t b t B t 123}}
+ {INSERT t1 CONFLICT {t f t F t FFF} {t f t F t FFF}}
+ {DELETE t1 NOTFOUND {t d t D t DDD}}
+}]
+
+do_test $tn.1.4 {
+ set ::conflict_list [list]
+ sqlite3changeset_apply_v2 -ignorenoop db2 $C xConflict
+ set ::conflict_list
+} {}
+
+do_execsql_test -db db2 1.5 {
+ UPDATE t1 SET b='G' WHERE a='f';
+ UPDATE t1 SET c='456' WHERE a='b';
+}
+
+do_test $tn.1.6 {
+ set ::conflict_list [list]
+ sqlite3changeset_apply_v2 -ignorenoop db2 $C xConflict
+ set ::conflict_list
+} [list {*}{
+ {UPDATE t1 DATA {t b {} {} t BBB} {{} {} {} {} t 123} {t b t B t 456}}
+ {INSERT t1 CONFLICT {t f t F t FFF} {t f t G t FFF}}
+}]
+
+db2 close
+
+#--------------------------------------------------------------------------
+
+reset_db
+forcedelete test.db2
+sqlite3 db2 test.db2
+do_execsql_test $tn.2.0 {
+ CREATE TABLE t1(a PRIMARY KEY, b) %WO%;
+}
+do_execsql_test -db db2 $tn.2.1 {
+ CREATE TABLE t1(a PRIMARY KEY, b, c DEFAULT 'val') %WO%;
+}
+
+do_test $tn.2.2 {
+ do_then_apply_sql -ignorenoop {
+ INSERT INTO t1 VALUES(1, 2);
+ }
+ do_then_apply_sql -ignorenoop {
+ UPDATE t1 SET b=2 WHERE a=1
+ }
+} {}
+
+db2 close
+
+}]
+}
+
+
+#-------------------------------------------------------------------------
+reset_db
+forcedelete test.db2
+do_execsql_test 3.0 {
+ CREATE TABLE xyz(a, b, c, PRIMARY KEY(a, b), UNIQUE(c));
+ ANALYZE;
+ WITH s(i) AS (
+ VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<100
+ )
+ INSERT INTO xyz SELECT i, i, i FROM s;
+ VACUUM INTO 'test.db2';
+}
+
+set C [changeset_from_sql { ANALYZE }]
+sqlite3 db2 test.db2
+
+set ::conflict_list [list]
+proc xConflict {args} { lappend ::conflict_list $args ; return "OMIT" }
+do_test 3.1 {
+ sqlite3changeset_apply_v2 db2 $C xConflict
+ set ::conflict_list
+} {}
+
+do_test 3.2 {
+ sqlite3changeset_apply_v2 -ignorenoop db2 $C xConflict
+ set ::conflict_list
+} {}
+
+do_test 3.3 {
+ sqlite3changeset_apply_v2 db2 $C xConflict
+ set ::conflict_list
+} [list {*}{
+ {INSERT sqlite_stat1 CONFLICT {t xyz t sqlite_autoindex_xyz_1 t {100 1 1}} {t xyz t sqlite_autoindex_xyz_1 t {100 1 1}}}
+ {INSERT sqlite_stat1 CONFLICT {t xyz t sqlite_autoindex_xyz_2 t {100 1}} {t xyz t sqlite_autoindex_xyz_2 t {100 1}}}
+}]
+
+do_execsql_test -db db2 3.4 {
+ UPDATE sqlite_stat1 SET stat='200 1 1' WHERE idx='sqlite_autoindex_xyz_1';
+}
+
+do_test 3.5 {
+ set ::conflict_list [list]
+ sqlite3changeset_apply_v2 -ignorenoop db2 $C xConflict
+ set ::conflict_list
+} [list {*}{
+ {INSERT sqlite_stat1 CONFLICT {t xyz t sqlite_autoindex_xyz_1 t {100 1 1}} {t xyz t sqlite_autoindex_xyz_1 t {200 1 1}}}
+}]
+
+
+
+finish_test
+
diff -Nru sqlite3-3.41.0-0/ext/session/sessionrebase.test sqlite3-3.44.0-0/ext/session/sessionrebase.test
--- sqlite3-3.41.0-0/ext/session/sessionrebase.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sessionrebase.test 2023-11-04 14:24:27.000000000 +0000
@@ -84,6 +84,7 @@
db eval BEGIN
sqlite3session S1 db main
+ S1 object_config rowid 1
S1 attach *
execsql $sql1 db
set c1 [S1 changeset]
@@ -91,6 +92,7 @@
if {$i==1} {
sqlite3session S2 db2 main
+ S2 object_config rowid 1
S2 attach *
execsql $sql2 db2
set c2 [S2 changeset]
@@ -100,6 +102,7 @@
foreach sql [split $sql2 ";"] {
if {[string is space $sql]} continue
sqlite3session S2 db2 main
+ S2 object_config rowid 1
S2 attach *
execsql $sql db2
lappend c2 [S2 changeset]
@@ -341,6 +344,79 @@
OMIT
} { SELECT * FROM t2 WHERE z='B' } { 1 one B }
+
+reset_db
+do_execsql_test 2.3.0 {
+ CREATE TABLE t1 (b TEXT);
+ INSERT INTO t1(rowid, b) VALUES(1, 'one');
+ INSERT INTO t1(rowid, b) VALUES(2, 'two');
+ INSERT INTO t1(rowid, b) VALUES(3, 'three');
+}
+do_rebase_test 2.3.1 {
+ UPDATE t1 SET b = 'two.1' WHERE rowid=2
+} {
+ UPDATE t1 SET b = 'two.2' WHERE rowid=2;
+} {
+ OMIT
+} { SELECT rowid, * FROM t1 } {1 one 2 two.1 3 three}
+
+do_rebase_test 2.3.2 {
+ UPDATE t1 SET b = 'two.1' WHERE rowid=2
+} {
+ UPDATE t1 SET b = 'two.2' WHERE rowid=2;
+} {
+ REPLACE
+} { SELECT rowid, * FROM t1 } {1 one 2 two.2 3 three}
+
+do_rebase_test 2.3.3 {
+ DELETE FROM t1 WHERE rowid=3
+} {
+ DELETE FROM t1 WHERE rowid=3;
+} {
+ OMIT
+} { SELECT rowid, * FROM t1 } {1 one 2 two}
+
+do_rebase_test 2.3.4 {
+ DELETE FROM t1 WHERE rowid=1
+} {
+ UPDATE t1 SET b='one.2' WHERE rowid=1
+} {
+ OMIT
+} { SELECT rowid, * FROM t1 } {2 two 3 three}
+
+do_rebase_test 2.3.6 {
+ UPDATE t1 SET b='three.1' WHERE rowid=3
+} {
+ DELETE FROM t1 WHERE rowid=3;
+} {
+ OMIT
+} { SELECT rowid, * FROM t1 } {1 one 2 two 3 three.1}
+
+do_rebase_test 2.3.7 {
+ UPDATE t1 SET b='three.1' WHERE rowid=3
+} {
+ DELETE FROM t1 WHERE rowid=3;
+} {
+ REPLACE
+} { SELECT rowid, * FROM t1 } {1 one 2 two}
+
+do_rebase_test 2.3.8 {
+ INSERT INTO t1(rowid, b) VALUES(4, 'four.1')
+} {
+ INSERT INTO t1(rowid, b) VALUES(4, 'four.2');
+} {
+ REPLACE
+} { SELECT rowid, * FROM t1 } {1 one 2 two 3 three 4 four.2}
+
+do_rebase_test 2.3.9 {
+ INSERT INTO t1(rowid, b) VALUES(4, 'four.1')
+} {
+ INSERT INTO t1(rowid, b) VALUES(4, 'four.2');
+} {
+ OMIT
+} { SELECT rowid, * FROM t1 } {1 one 2 two 3 three 4 four.1}
+
+
#-------------------------------------------------------------------------
reset_db
do_execsql_test 3.0 {
diff -Nru sqlite3-3.41.0-0/ext/session/sessionrowid.test sqlite3-3.44.0-0/ext/session/sessionrowid.test
--- sqlite3-3.41.0-0/ext/session/sessionrowid.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sessionrowid.test 2023-11-04 14:24:27.000000000 +0000
@@ -0,0 +1,282 @@
+# 2011 Mar 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.
+#
+#***********************************************************************
+#
+# The focus of this file is testing the session module.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] session_common.tcl]
+source $testdir/tester.tcl
+ifcapable !session {finish_test; return}
+
+set testprefix sessionrowid
+
+do_execsql_test 0.0 {
+ CREATE TABLE t1(a, b);
+}
+
+foreach {tn rowid bEmpty} {
+ 1 0 1
+ 2 1 0
+ 3 -1 1
+} {
+ do_test 0.$tn {
+ sqlite3session S db main
+ if {$rowid>=0} { S object_config rowid $rowid }
+ S attach t1
+ execsql { INSERT INTO t1 VALUES(1, 2); }
+ expr [string length [S changeset]]==0
+ } $bEmpty
+ S delete
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a, b);
+}
+
+do_iterator_test 1.1 t1 {
+ INSERT INTO t1 VALUES('i', 'one');
+} {
+ {INSERT t1 0 X.. {} {i 1 t i t one}}
+}
+
+do_execsql_test 1.2 {
+ SELECT rowid, * FROM t1
+} {1 i one}
+
+do_iterator_test 1.3 t1 {
+ UPDATE t1 SET b='two'
+} {
+ {UPDATE t1 0 X.. {i 1 {} {} t one} {{} {} {} {} t two}}
+}
+
+do_iterator_test 1.4 t1 {
+ DELETE FROM t1;
+} {
+ {DELETE t1 0 X.. {i 1 t i t two} {}}
+}
+
+do_iterator_test 1.5 t1 {
+ INSERT INTO t1(rowid, a, b) VALUES(14, 'hello', 'world');
+ INSERT INTO t1(rowid, a, b) VALUES(NULL, 'yes', 'no');
+ INSERT INTO t1(rowid, a, b) VALUES(-123, 'ii', 'iii');
+} {
+ {INSERT t1 0 X.. {} {i -123 t ii t iii}}
+ {INSERT t1 0 X.. {} {i 15 t yes t no}}
+ {INSERT t1 0 X.. {} {i 14 t hello t world}}
+}
+
+do_iterator_test 1.6 t1 {
+ UPDATE t1 SET a='deluxe' WHERE rowid=14;
+ DELETE FROM t1 WHERE rowid=-123;
+ INSERT INTO t1 VALUES('x', 'xi');
+} {
+ {DELETE t1 0 X.. {i -123 t ii t iii} {}}
+ {UPDATE t1 0 X.. {i 14 t hello {} {}} {{} {} t deluxe {} {}}}
+ {INSERT t1 0 X.. {} {i 16 t x t xi}}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+forcedelete test.db2
+sqlite3 db2 test.db2
+
+do_execsql_test 2.0 {
+ CREATE TABLE t1(a, b);
+}
+do_execsql_test -db db2 2.0.1 {
+ CREATE TABLE t1(a, b);
+}
+
+proc xConflict {args} {
+ puts "CONFLICT!"
+ return "OMIT"
+}
+
+do_test 2.1 {
+ set C [changeset_from_sql {
+ INSERT INTO t1 VALUES('abc', 'def');
+ }]
+ sqlite3changeset_apply db2 $C xConflict
+ execsql { SELECT * FROM t1 } db2
+} {abc def}
+do_test 2.2 {
+ set C [changeset_from_sql {
+ UPDATE t1 SET b='hello'
+ }]
+ sqlite3changeset_apply db2 $C xConflict
+ execsql { SELECT * FROM t1 } db2
+} {abc hello}
+do_test 2.3 {
+ set C [changeset_from_sql {
+ DELETE FROM t1 WHERE b='hello'
+ }]
+ sqlite3changeset_apply db2 $C xConflict
+ execsql { SELECT * FROM t1 } db2
+} {}
+
+do_test 2.4 {
+ do_then_apply_sql {
+ INSERT INTO t1 VALUES('i', 'one');
+ INSERT INTO t1 VALUES('ii', 'two');
+ INSERT INTO t1 VALUES('iii', 'three');
+ INSERT INTO t1 VALUES('iv', 'four');
+ }
+ compare_db db db2
+} {}
+
+do_test 2.5 {
+ do_then_apply_sql {
+ DELETE FROM t1 WHERE a='ii';
+ UPDATE t1 SET b='THREE' WHERE a='iii';
+ UPDATE t1 SET a='III' WHERE a='iii';
+ INSERT INTO t1 VALUES('v', 'five');
+ }
+ compare_db db db2
+} {}
+
+do_execsql_test 2.6 {SELECT * FROM t1} {i one III THREE iv four v five}
+do_execsql_test -db db2 2.7 {SELECT * FROM t1} {i one III THREE iv four v five}
+
+#-------------------------------------------------------------------------
+db2 close
+reset_db
+forcedelete test.db2
+sqlite3 db2 test.db2
+
+set init_sql {
+ CREATE TABlE t4(a, b);
+ CREATE INDEX t4a ON t4(a);
+ CREATE UNIQUE INDEX t4b ON t4(b);
+}
+
+do_execsql_test 3.0 $init_sql
+do_execsql_test -db db2 3.0a $init_sql
+
+do_execsql_test -db db2 3.1 {
+ INSERT INTO t4(rowid, a, b) VALUES(43, 'hello', 'world');
+}
+do_conflict_test 3.2 -sql {
+ INSERT INTO t4(rowid, a, b) VALUES(43, 'abc', 'def');
+} -tables t4 -conflicts {
+ {INSERT t4 CONFLICT {i 43 t abc t def} {i 43 t hello t world}}
+}
+do_execsql_test -db db2 3.3 {
+ SELECT * FROM t4
+} {hello world}
+
+do_execsql_test 3.4 { DELETE FROM t4 }
+do_conflict_test 3.5 -sql {
+ INSERT INTO t4(rowid, a, b) VALUES(43, 'abc', 'def');
+} -tables t4 -conflicts {
+ {INSERT t4 CONFLICT {i 43 t abc t def} {i 43 t hello t world}}
+} -policy REPLACE
+do_execsql_test -db db2 3.6 {
+ SELECT * FROM t4
+} {abc def}
+
+do_execsql_test 3.7 { DELETE FROM t4 }
+do_conflict_test 3.8 -sql {
+ INSERT INTO t4(rowid, a, b) VALUES(45, 'xyz', 'def');
+} -tables t4 -conflicts {
+ {INSERT t4 CONSTRAINT {i 45 t xyz t def}}
+}
+do_execsql_test -db db2 3.9 {
+ SELECT * FROM t4
+} {abc def}
+
+
+do_execsql_test -db db 3.10a { DELETE FROM t4 }
+do_execsql_test -db db2 3.10b { DELETE FROM t4 }
+
+do_execsql_test -db db 3.11a {
+ INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'one');
+ INSERT INTO t4(rowid, a, b) VALUES(222, 'two', 'two');
+}
+do_execsql_test -db db2 3.11b {
+ INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'blip');
+}
+
+do_conflict_test 3.12 -sql {
+ DELETE FROM t4 WHERE a='one';
+} -tables t4 -conflicts {
+ {DELETE t4 DATA {i 111 t one t one} {i 111 t one t blip}}
+}
+do_execsql_test -db db2 3.13 {
+ SELECT * FROM t4
+} {one blip}
+
+do_conflict_test 3.14 -sql {
+ DELETE FROM t4 WHERE a='two';
+} -tables t4 -conflicts {
+ {DELETE t4 NOTFOUND {i 222 t two t two}}
+}
+do_execsql_test -db db2 3.15 {
+ SELECT * FROM t4
+} {one blip}
+
+do_execsql_test -db db 3.16a { DELETE FROM t4 }
+do_execsql_test -db db2 3.16b { DELETE FROM t4 }
+
+do_execsql_test -db db 3.17a {
+ INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'one');
+ INSERT INTO t4(rowid, a, b) VALUES(222, 'two', 'two');
+}
+do_execsql_test -db db2 3.17b {
+ INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'blip');
+}
+
+do_conflict_test 3.18 -sql {
+ UPDATE t4 SET b='xyz' WHERE a='one'
+} -tables t4 -conflicts {
+ {UPDATE t4 DATA {i 111 {} {} t one} {{} {} {} {} t xyz} {i 111 t one t blip}}
+}
+do_execsql_test -db db2 3.19 {
+ SELECT * FROM t4
+} {one blip}
+
+do_conflict_test 3.20 -sql {
+ UPDATE t4 SET b='123' WHERE a='two'
+} -tables t4 -conflicts {
+ {UPDATE t4 NOTFOUND {i 222 {} {} t two} {{} {} {} {} t 123}}
+}
+do_execsql_test -db db2 3.21 {
+ SELECT * FROM t4
+} {one blip}
+db2 close
+
+#--------------------------------------------------------------------------
+breakpoint
+do_diff_test 4.0 {
+ CREATE TABLE t1(x, y);
+ CREATE TABLE aux.t1(x, y);
+ INSERT INTO t1 VALUES(1, 2);
+}
+
+do_diff_test 4.1 {
+ CREATE TABLE t1(x, y);
+ CREATE TABLE aux.t1(x, y);
+ INSERT INTO aux.t1 VALUES(1, 2);
+}
+
+do_diff_test 4.2 {
+ CREATE TABLE t1(x, y);
+ CREATE TABLE aux.t1(x, y);
+ INSERT INTO t1(rowid, x, y) VALUES(413, 'hello', 'there');
+ INSERT INTO aux.t1(rowid, x, y) VALUES(413, 'hello', 'world');
+}
+
+finish_test
+
diff -Nru sqlite3-3.41.0-0/ext/session/sessionsize.test sqlite3-3.44.0-0/ext/session/sessionsize.test
--- sqlite3-3.41.0-0/ext/session/sessionsize.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sessionsize.test 2023-11-04 14:24:27.000000000 +0000
@@ -113,17 +113,17 @@
do_test 3.1 {
sqlite3session S db main
- S object_config_size -1
+ S object_config size -1
} 1
-do_test 3.2.1 { S object_config_size 0 } 0
-do_test 3.2.2 { S object_config_size -1 } 0
-do_test 3.2.3 { S object_config_size 1 } 1
-do_test 3.2.4 { S object_config_size -1 } 1
+do_test 3.2.1 { S object_config size 0 } 0
+do_test 3.2.2 { S object_config size -1 } 0
+do_test 3.2.3 { S object_config size 1 } 1
+do_test 3.2.4 { S object_config size -1 } 1
do_test 3.3 { S attach t1 } {}
-do_test 3.4 { S object_config_size 1 } {SQLITE_MISUSE}
-do_test 3.4 { S object_config_size -1 } {1}
+do_test 3.4 { S object_config size 1 } {SQLITE_MISUSE}
+do_test 3.4 { S object_config size -1 } {1}
S delete
diff -Nru sqlite3-3.41.0-0/ext/session/sessionstat1.test sqlite3-3.44.0-0/ext/session/sessionstat1.test
--- sqlite3-3.41.0-0/ext/session/sessionstat1.test 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sessionstat1.test 2023-11-04 14:24:27.000000000 +0000
@@ -82,7 +82,7 @@
} {}
do_test 2.1 {
- do_then_apply_sql {
+ do_then_apply_sql -ignorenoop {
WITH s(i) AS (
SELECT 0 UNION ALL SELECT i+1 FROM s WHERE (i+1)<32
)
@@ -100,7 +100,7 @@
}
do_test 2.3 {
- do_then_apply_sql { DROP INDEX t1c }
+ do_then_apply_sql -ignorenoop { DROP INDEX t1c }
} {}
do_execsql_test -db db2 2.4 {
@@ -111,7 +111,7 @@
}
do_test 2.3 {
- do_then_apply_sql { DROP TABLE t1 }
+ do_then_apply_sql -ignorenoop { DROP TABLE t1 }
} {}
do_execsql_test -db db2 2.4 {
@@ -153,16 +153,16 @@
} {t1 null 4}
do_test 3.3 {
execsql { DELETE FROM sqlite_stat1 }
- do_then_apply_sql { ANALYZE }
+ do_then_apply_sql -ignorenoop { ANALYZE }
execsql { SELECT * FROM sqlite_stat1 } db2
} {t1 null 4}
do_test 3.4 {
execsql { INSERT INTO t1 VALUES(5,5,5) }
- do_then_apply_sql { ANALYZE }
+ do_then_apply_sql -ignorenoop { ANALYZE }
execsql { SELECT * FROM sqlite_stat1 } db2
} {t1 null 5}
do_test 3.5 {
- do_then_apply_sql { DROP TABLE t1 }
+ do_then_apply_sql -ignorenoop { DROP TABLE t1 }
execsql { SELECT * FROM sqlite_stat1 } db2
} {}
diff -Nru sqlite3-3.41.0-0/ext/session/sqlite3session.c sqlite3-3.44.0-0/ext/session/sqlite3session.c
--- sqlite3-3.41.0-0/ext/session/sqlite3session.c 2023-03-17 22:48:33.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sqlite3session.c 2023-11-04 14:24:27.000000000 +0000
@@ -25,6 +25,8 @@
# endif
#endif
+#define SESSIONS_ROWID "_rowid_"
+
static int sessions_strm_chunk_size = SESSIONS_STRM_CHUNK_SIZE;
typedef struct SessionHook SessionHook;
@@ -46,6 +48,7 @@
int bEnable; /* True if currently recording */
int bIndirect; /* True if all changes are indirect */
int bAutoAttach; /* True to auto-attach tables */
+ int bImplicitPK; /* True to handle tables with implicit PK */
int rc; /* Non-zero if an error has occurred */
void *pFilterCtx; /* First argument to pass to xTableFilter */
int (*xTableFilter)(void *pCtx, const char *zTab);
@@ -116,17 +119,32 @@
** The data associated with each hash-table entry is a structure containing
** a subset of the initial values that the modified row contained at the
** start of the session. Or no initial values if the row was inserted.
+**
+** pDfltStmt:
+** This is only used by the sqlite3changegroup_xxx() APIs, not by
+** regular sqlite3_session objects. It is a SELECT statement that
+** selects the default value for each table column. For example,
+** if the table is
+**
+** CREATE TABLE xx(a DEFAULT 1, b, c DEFAULT 'abc')
+**
+** then this variable is the compiled version of:
+**
+** SELECT 1, NULL, 'abc'
*/
struct SessionTable {
SessionTable *pNext;
char *zName; /* Local name of table */
int nCol; /* Number of columns in table zName */
int bStat1; /* True if this is sqlite_stat1 */
+ int bRowid; /* True if this table uses rowid for PK */
const char **azCol; /* Column names */
+ const char **azDflt; /* Default value expressions */
u8 *abPK; /* Array of primary key flags */
int nEntry; /* Total number of entries in hash table */
int nChange; /* Size of apChange[] array */
SessionChange **apChange; /* Hash table buckets */
+ sqlite3_stmt *pDfltStmt;
};
/*
@@ -295,6 +313,7 @@
struct SessionChange {
u8 op; /* One of UPDATE, DELETE, INSERT */
u8 bIndirect; /* True if this change is "indirect" */
+ u16 nRecordField; /* Number of fields in aRecord[] */
int nMaxSize; /* Max size of eventual changeset record */
int nRecord; /* Number of bytes in buffer aRecord[] */
u8 *aRecord; /* Buffer containing old.* record */
@@ -320,7 +339,7 @@
** Read a varint value from aBuf[] into *piVal. Return the number of
** bytes read.
*/
-static int sessionVarintGet(u8 *aBuf, int *piVal){
+static int sessionVarintGet(const u8 *aBuf, int *piVal){
return getVarint32(aBuf, *piVal);
}
@@ -514,6 +533,7 @@
*/
static int sessionPreupdateHash(
sqlite3_session *pSession, /* Session object that owns pTab */
+ i64 iRowid,
SessionTable *pTab, /* Session table handle */
int bNew, /* True to hash the new.* PK */
int *piHash, /* OUT: Hash value */
@@ -522,48 +542,53 @@
unsigned int h = 0; /* Hash value to return */
int i; /* Used to iterate through columns */
- assert( *pbNullPK==0 );
- assert( pTab->nCol==pSession->hook.xCount(pSession->hook.pCtx) );
- for(i=0; inCol; i++){
- if( pTab->abPK[i] ){
- int rc;
- int eType;
- sqlite3_value *pVal;
-
- if( bNew ){
- rc = pSession->hook.xNew(pSession->hook.pCtx, i, &pVal);
- }else{
- rc = pSession->hook.xOld(pSession->hook.pCtx, i, &pVal);
- }
- if( rc!=SQLITE_OK ) return rc;
+ if( pTab->bRowid ){
+ assert( pTab->nCol-1==pSession->hook.xCount(pSession->hook.pCtx) );
+ h = sessionHashAppendI64(h, iRowid);
+ }else{
+ assert( *pbNullPK==0 );
+ assert( pTab->nCol==pSession->hook.xCount(pSession->hook.pCtx) );
+ for(i=0; inCol; i++){
+ if( pTab->abPK[i] ){
+ int rc;
+ int eType;
+ sqlite3_value *pVal;
- eType = sqlite3_value_type(pVal);
- h = sessionHashAppendType(h, eType);
- if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
- i64 iVal;
- if( eType==SQLITE_INTEGER ){
- iVal = sqlite3_value_int64(pVal);
+ if( bNew ){
+ rc = pSession->hook.xNew(pSession->hook.pCtx, i, &pVal);
}else{
- double rVal = sqlite3_value_double(pVal);
- assert( sizeof(iVal)==8 && sizeof(rVal)==8 );
- memcpy(&iVal, &rVal, 8);
+ rc = pSession->hook.xOld(pSession->hook.pCtx, i, &pVal);
}
- h = sessionHashAppendI64(h, iVal);
- }else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
- const u8 *z;
- int n;
- if( eType==SQLITE_TEXT ){
- z = (const u8 *)sqlite3_value_text(pVal);
+ if( rc!=SQLITE_OK ) return rc;
+
+ eType = sqlite3_value_type(pVal);
+ h = sessionHashAppendType(h, eType);
+ if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
+ i64 iVal;
+ if( eType==SQLITE_INTEGER ){
+ iVal = sqlite3_value_int64(pVal);
+ }else{
+ double rVal = sqlite3_value_double(pVal);
+ assert( sizeof(iVal)==8 && sizeof(rVal)==8 );
+ memcpy(&iVal, &rVal, 8);
+ }
+ h = sessionHashAppendI64(h, iVal);
+ }else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
+ const u8 *z;
+ int n;
+ if( eType==SQLITE_TEXT ){
+ z = (const u8 *)sqlite3_value_text(pVal);
+ }else{
+ z = (const u8 *)sqlite3_value_blob(pVal);
+ }
+ n = sqlite3_value_bytes(pVal);
+ if( !z && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM;
+ h = sessionHashAppendBlob(h, n, z);
}else{
- z = (const u8 *)sqlite3_value_blob(pVal);
+ assert( eType==SQLITE_NULL );
+ assert( pTab->bStat1==0 || i!=1 );
+ *pbNullPK = 1;
}
- n = sqlite3_value_bytes(pVal);
- if( !z && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM;
- h = sessionHashAppendBlob(h, n, z);
- }else{
- assert( eType==SQLITE_NULL );
- assert( pTab->bStat1==0 || i!=1 );
- *pbNullPK = 1;
}
}
}
@@ -577,9 +602,11 @@
** Return the number of bytes of space occupied by the value (including
** the type byte).
*/
-static int sessionSerialLen(u8 *a){
- int e = *a;
+static int sessionSerialLen(const u8 *a){
+ int e;
int n;
+ assert( a!=0 );
+ e = *a;
if( e==0 || e==0xFF ) return 1;
if( e==SQLITE_NULL ) return 1;
if( e==SQLITE_INTEGER || e==SQLITE_FLOAT ) return 9;
@@ -846,6 +873,7 @@
*/
static int sessionPreupdateEqual(
sqlite3_session *pSession, /* Session object that owns SessionTable */
+ i64 iRowid, /* Rowid value if pTab->bRowid */
SessionTable *pTab, /* Table associated with change */
SessionChange *pChange, /* Change to compare to */
int op /* Current pre-update operation */
@@ -853,6 +881,11 @@
int iCol; /* Used to iterate through columns */
u8 *a = pChange->aRecord; /* Cursor used to scan change record */
+ if( pTab->bRowid ){
+ if( a[0]!=SQLITE_INTEGER ) return 0;
+ return sessionGetI64(&a[1])==iRowid;
+ }
+
assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE );
for(iCol=0; iColnCol; iCol++){
if( !pTab->abPK[iCol] ){
@@ -875,6 +908,7 @@
rc = pSession->hook.xOld(pSession->hook.pCtx, iCol, &pVal);
}
assert( rc==SQLITE_OK );
+ (void)rc; /* Suppress warning about unused variable */
if( sqlite3_value_type(pVal)!=eType ) return 0;
/* A SessionChange object never has a NULL value in a PK column */
@@ -977,13 +1011,14 @@
**
** For example, if the table is declared as:
**
-** CREATE TABLE tbl1(w, x, y, z, PRIMARY KEY(w, z));
+** CREATE TABLE tbl1(w, x DEFAULT 'abc', y, z, PRIMARY KEY(w, z));
**
-** Then the four output variables are populated as follows:
+** Then the five output variables are populated as follows:
**
** *pnCol = 4
** *pzTab = "tbl1"
** *pazCol = {"w", "x", "y", "z"}
+** *pazDflt = {NULL, 'abc', NULL, NULL}
** *pabPK = {1, 0, 0, 1}
**
** All returned buffers are part of the same single allocation, which must
@@ -997,7 +1032,9 @@
int *pnCol, /* OUT: number of columns */
const char **pzTab, /* OUT: Copy of zThis */
const char ***pazCol, /* OUT: Array of column names for table */
- u8 **pabPK /* OUT: Array of booleans - true for PK col */
+ const char ***pazDflt, /* OUT: Array of default value expressions */
+ u8 **pabPK, /* OUT: Array of booleans - true for PK col */
+ int *pbRowid /* OUT: True if only PK is a rowid */
){
char *zPragma;
sqlite3_stmt *pStmt;
@@ -1008,10 +1045,18 @@
int i;
u8 *pAlloc = 0;
char **azCol = 0;
+ char **azDflt = 0;
u8 *abPK = 0;
+ int bRowid = 0; /* Set to true to use rowid as PK */
assert( pazCol && pabPK );
+ *pazCol = 0;
+ *pabPK = 0;
+ *pnCol = 0;
+ if( pzTab ) *pzTab = 0;
+ if( pazDflt ) *pazDflt = 0;
+
nThis = sqlite3Strlen30(zThis);
if( nThis==12 && 0==sqlite3_stricmp("sqlite_stat1", zThis) ){
rc = sqlite3_table_column_metadata(db, zDb, zThis, 0, 0, 0, 0, 0, 0);
@@ -1025,50 +1070,47 @@
}else if( rc==SQLITE_ERROR ){
zPragma = sqlite3_mprintf("");
}else{
- *pazCol = 0;
- *pabPK = 0;
- *pnCol = 0;
- if( pzTab ) *pzTab = 0;
return rc;
}
}else{
zPragma = sqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, zThis);
}
if( !zPragma ){
- *pazCol = 0;
- *pabPK = 0;
- *pnCol = 0;
- if( pzTab ) *pzTab = 0;
return SQLITE_NOMEM;
}
rc = sqlite3_prepare_v2(db, zPragma, -1, &pStmt, 0);
sqlite3_free(zPragma);
if( rc!=SQLITE_OK ){
- *pazCol = 0;
- *pabPK = 0;
- *pnCol = 0;
- if( pzTab ) *pzTab = 0;
return rc;
}
nByte = nThis + 1;
+ bRowid = (pbRowid!=0);
while( SQLITE_ROW==sqlite3_step(pStmt) ){
- nByte += sqlite3_column_bytes(pStmt, 1);
+ nByte += sqlite3_column_bytes(pStmt, 1); /* name */
+ nByte += sqlite3_column_bytes(pStmt, 4); /* dflt_value */
nDbCol++;
+ if( sqlite3_column_int(pStmt, 5) ) bRowid = 0; /* pk */
}
+ if( nDbCol==0 ) bRowid = 0;
+ nDbCol += bRowid;
+ nByte += strlen(SESSIONS_ROWID);
rc = sqlite3_reset(pStmt);
if( rc==SQLITE_OK ){
- nByte += nDbCol * (sizeof(const char *) + sizeof(u8) + 1);
+ nByte += nDbCol * (sizeof(const char *)*2 + sizeof(u8) + 1 + 1);
pAlloc = sessionMalloc64(pSession, nByte);
if( pAlloc==0 ){
rc = SQLITE_NOMEM;
+ }else{
+ memset(pAlloc, 0, nByte);
}
}
if( rc==SQLITE_OK ){
azCol = (char **)pAlloc;
- pAlloc = (u8 *)&azCol[nDbCol];
+ azDflt = (char**)&azCol[nDbCol];
+ pAlloc = (u8 *)&azDflt[nDbCol];
abPK = (u8 *)pAlloc;
pAlloc = &abPK[nDbCol];
if( pzTab ){
@@ -1078,43 +1120,57 @@
}
i = 0;
+ if( bRowid ){
+ size_t nName = strlen(SESSIONS_ROWID);
+ memcpy(pAlloc, SESSIONS_ROWID, nName+1);
+ azCol[i] = (char*)pAlloc;
+ pAlloc += nName+1;
+ abPK[i] = 1;
+ i++;
+ }
while( SQLITE_ROW==sqlite3_step(pStmt) ){
int nName = sqlite3_column_bytes(pStmt, 1);
+ int nDflt = sqlite3_column_bytes(pStmt, 4);
const unsigned char *zName = sqlite3_column_text(pStmt, 1);
+ const unsigned char *zDflt = sqlite3_column_text(pStmt, 4);
+
if( zName==0 ) break;
memcpy(pAlloc, zName, nName+1);
azCol[i] = (char *)pAlloc;
pAlloc += nName+1;
+ if( zDflt ){
+ memcpy(pAlloc, zDflt, nDflt+1);
+ azDflt[i] = (char *)pAlloc;
+ pAlloc += nDflt+1;
+ }else{
+ azDflt[i] = 0;
+ }
abPK[i] = sqlite3_column_int(pStmt, 5);
i++;
}
rc = sqlite3_reset(pStmt);
-
}
/* If successful, populate the output variables. Otherwise, zero them and
** free any allocation made. An error code will be returned in this case.
*/
if( rc==SQLITE_OK ){
- *pazCol = (const char **)azCol;
+ *pazCol = (const char**)azCol;
+ if( pazDflt ) *pazDflt = (const char**)azDflt;
*pabPK = abPK;
*pnCol = nDbCol;
}else{
- *pazCol = 0;
- *pabPK = 0;
- *pnCol = 0;
- if( pzTab ) *pzTab = 0;
sessionFree(pSession, azCol);
}
+ if( pbRowid ) *pbRowid = bRowid;
sqlite3_finalize(pStmt);
return rc;
}
/*
-** This function is only called from within a pre-update handler for a
-** write to table pTab, part of session pSession. If this is the first
-** write to this table, initalize the SessionTable.nCol, azCol[] and
-** abPK[] arrays accordingly.
+** This function is called to initialize the SessionTable.nCol, azCol[]
+** abPK[] and azDflt[] members of SessionTable object pTab. If these
+** fields are already initilialized, this function is a no-op.
**
** If an error occurs, an error code is stored in sqlite3_session.rc and
** non-zero returned. Or, if no error occurs but the table has no primary
@@ -1122,14 +1178,22 @@
** indicate that updates on this table should be ignored. SessionTable.abPK
** is set to NULL in this case.
*/
-static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
+static int sessionInitTable(
+ sqlite3_session *pSession, /* Optional session handle */
+ SessionTable *pTab, /* Table object to initialize */
+ sqlite3 *db, /* Database handle to read schema from */
+ const char *zDb /* Name of db - "main", "temp" etc. */
+){
+ int rc = SQLITE_OK;
+
if( pTab->nCol==0 ){
u8 *abPK;
assert( pTab->azCol==0 || pTab->abPK==0 );
- pSession->rc = sessionTableInfo(pSession, pSession->db, pSession->zDb,
- pTab->zName, &pTab->nCol, 0, &pTab->azCol, &abPK
+ rc = sessionTableInfo(pSession, db, zDb,
+ pTab->zName, &pTab->nCol, 0, &pTab->azCol, &pTab->azDflt, &abPK,
+ ((pSession==0 || pSession->bImplicitPK) ? &pTab->bRowid : 0)
);
- if( pSession->rc==SQLITE_OK ){
+ if( rc==SQLITE_OK ){
int i;
for(i=0; i