Index: Makefile.in ================================================================== --- Makefile.in +++ Makefile.in @@ -416,12 +416,10 @@ $(TOP)/ext/fts3/fts3_test.c \ $(TOP)/ext/session/test_session.c \ $(TOP)/ext/recover/sqlite3recover.c \ $(TOP)/ext/recover/dbdata.c \ $(TOP)/ext/recover/test_recover.c \ - $(TOP)/ext/intck/test_intck.c \ - $(TOP)/ext/intck/sqlite3intck.c \ $(TOP)/ext/rbu/test_rbu.c # Statically linked extensions # TESTSRC += \ @@ -447,11 +445,10 @@ $(TOP)/ext/misc/nextchar.c \ $(TOP)/ext/misc/normalize.c \ $(TOP)/ext/misc/percentile.c \ $(TOP)/ext/misc/prefixes.c \ $(TOP)/ext/misc/qpvtab.c \ - $(TOP)/ext/misc/randomjson.c \ $(TOP)/ext/misc/regexp.c \ $(TOP)/ext/misc/remember.c \ $(TOP)/ext/misc/series.c \ $(TOP)/ext/misc/spellfix.c \ $(TOP)/ext/misc/totype.c \ @@ -601,11 +598,10 @@ SHELL_OPT += -DSQLITE_ENABLE_STMTVTAB SHELL_OPT += -DSQLITE_ENABLE_DBPAGE_VTAB SHELL_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB SHELL_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB SHELL_OPT += -DSQLITE_ENABLE_OFFSET_SQL_FUNC -SHELL_OPT += -DSQLITE_STRICT_SUBTYPE=1 FUZZERSHELL_OPT = FUZZCHECK_OPT += -I$(TOP)/test FUZZCHECK_OPT += -I$(TOP)/ext/recover FUZZCHECK_OPT += \ -DSQLITE_OSS_FUZZ \ @@ -632,21 +628,18 @@ -DSQLITE_ENABLE_STMT_SCANSTATUS \ -DSQLITE_MAX_MEMORY=50000000 \ -DSQLITE_MAX_MMAP_SIZE=0 \ -DSQLITE_OMIT_LOAD_EXTENSION \ -DSQLITE_PRINTF_PRECISION_LIMIT=1000 \ - -DSQLITE_PRIVATE="" \ - -DSQLITE_STRICT_SUBTYPE=1 \ - -DSQLITE_STATIC_RANDOMJSON + -DSQLITE_PRIVATE="" FUZZCHECK_SRC += $(TOP)/test/fuzzcheck.c FUZZCHECK_SRC += $(TOP)/test/ossfuzz.c FUZZCHECK_SRC += $(TOP)/test/fuzzinvariants.c FUZZCHECK_SRC += $(TOP)/ext/recover/dbdata.c FUZZCHECK_SRC += $(TOP)/ext/recover/sqlite3recover.c FUZZCHECK_SRC += $(TOP)/test/vt02.c -FUZZCHECK_SRC += $(TOP)/ext/misc/randomjson.c DBFUZZ_OPT = ST_OPT = -DSQLITE_OS_KV_OPTIONAL # In wasi-sdk builds, disable the CLI shell build in the "all" target. @@ -715,25 +708,10 @@ $(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) -# Usage: FUZZDB=filename make run-fuzzcheck -# -# Where filename is a fuzzcheck database, this target builds and runs -# fuzzcheck, fuzzcheck-asan, and fuzzcheck-ubsan on that database. -# -# FUZZDB can be a glob pattern of two or more databases. Example: -# -# FUZZDB=test/fuzzdata*.db make run-fuzzcheck -# -run-fuzzcheck: fuzzcheck$(TEXE) fuzzcheck-asan$(TEXE) fuzzcheck-ubsan$(TEXE) - @if test "$(FUZZDB)" = ""; then echo 'ERROR: No FUZZDB specified. Rerun with FUZZDB=filename'; exit 1; fi - ./fuzzcheck$(TEXE) --spinner $(FUZZDB) - ./fuzzcheck-asan$(TEXE) --spinner $(FUZZDB) - ./fuzzcheck-ubsan$(TEXE) --spinner $(FUZZDB) - ossshell$(TEXE): $(TOP)/test/ossfuzz.c $(TOP)/test/ossshell.c sqlite3.c sqlite3.h $(LTLINK) -o $@ $(FUZZCHECK_OPT) $(TOP)/test/ossshell.c \ $(TOP)/test/ossfuzz.c sqlite3.c $(TLIBS) sessionfuzz$(TEXE): $(TOP)/test/sessionfuzz.c sqlite3.c sqlite3.h @@ -815,11 +793,11 @@ mv vdbe.new tsrc/vdbe.c cp fts5.c fts5.h tsrc touch .target_source sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl src-verify has_tclsh84 - $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_LINE_MACROS) $(EXTRA_SRC) + $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_LINE_MACROS) cp tsrc/sqlite3ext.h . cp $(TOP)/ext/session/sqlite3session.h . sqlite3r.h: sqlite3.h has_tclsh84 $(TCLSH_CMD) $(TOP)/tool/mksqlite3h.tcl $(TOP) --enable-recover >sqlite3r.h @@ -826,11 +804,11 @@ sqlite3r.c: sqlite3.c sqlite3r.h has_tclsh84 cp $(TOP)/ext/recover/sqlite3recover.c tsrc/ cp $(TOP)/ext/recover/sqlite3recover.h tsrc/ cp $(TOP)/ext/recover/dbdata.c tsrc/ - $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl --enable-recover $(AMALGAMATION_LINE_MACROS) $(EXTRA_SRC) + $(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl --enable-recover $(AMALGAMATION_LINE_MACROS) sqlite3ext.h: .target_source cp tsrc/sqlite3ext.h . tclsqlite3.c: sqlite3.c @@ -1152,41 +1130,39 @@ keywordhash.h: $(TOP)/tool/mkkeywordhash.c $(BCC) -o mkkeywordhash$(BEXE) $(OPT_FEATURE_FLAGS) $(OPTS) $(TOP)/tool/mkkeywordhash.c ./mkkeywordhash$(BEXE) >keywordhash.h -# Source and header files that shell.c depends on -SHELL_DEP = \ - $(TOP)/src/shell.c.in \ - $(TOP)/ext/consio/console_io.c \ - $(TOP)/ext/consio/console_io.h \ - $(TOP)/ext/expert/sqlite3expert.c \ - $(TOP)/ext/expert/sqlite3expert.h \ - $(TOP)/ext/intck/sqlite3intck.c \ - $(TOP)/ext/intck/sqlite3intck.h \ - $(TOP)/ext/misc/appendvfs.c \ - $(TOP)/ext/misc/base64.c \ - $(TOP)/ext/misc/base85.c \ - $(TOP)/ext/misc/completion.c \ - $(TOP)/ext/misc/decimal.c \ - $(TOP)/ext/misc/fileio.c \ - $(TOP)/ext/misc/ieee754.c \ - $(TOP)/ext/misc/memtrace.c \ - $(TOP)/ext/misc/pcachetrace.c \ - $(TOP)/ext/misc/regexp.c \ - $(TOP)/ext/misc/series.c \ - $(TOP)/ext/misc/shathree.c \ - $(TOP)/ext/misc/sqlar.c \ - $(TOP)/ext/misc/uint.c \ - $(TOP)/ext/misc/zipfile.c \ - $(TOP)/ext/recover/dbdata.c \ - $(TOP)/ext/recover/sqlite3recover.c \ - $(TOP)/ext/recover/sqlite3recover.h \ - $(TOP)/src/test_windirent.c \ - $(TOP)/src/test_windirent.h - -shell.c: $(SHELL_DEP) $(TOP)/tool/mkshellc.tcl has_tclsh84 +# Source files that go into making shell.c +SHELL_SRC = \ + $(TOP)/src/shell.c.in \ + $(TOP)/ext/misc/appendvfs.c \ + $(TOP)/ext/misc/completion.c \ + $(TOP)/ext/consio/console_io.c \ + $(TOP)/ext/consio/console_io.h \ + $(TOP)/ext/misc/decimal.c \ + $(TOP)/ext/misc/basexx.c \ + $(TOP)/ext/misc/base64.c \ + $(TOP)/ext/misc/base85.c \ + $(TOP)/ext/misc/fileio.c \ + $(TOP)/ext/misc/ieee754.c \ + $(TOP)/ext/misc/regexp.c \ + $(TOP)/ext/misc/series.c \ + $(TOP)/ext/misc/shathree.c \ + $(TOP)/ext/misc/sqlar.c \ + $(TOP)/ext/misc/uint.c \ + $(TOP)/ext/expert/sqlite3expert.c \ + $(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 has_tclsh84 $(TCLSH_CMD) $(TOP)/tool/mkshellc.tcl >shell.c @@ -1298,12 +1274,10 @@ TESTFIXTURE_FLAGS += -DSQLITE_DEFAULT_PAGE_SIZE=1024 TESTFIXTURE_FLAGS += -DSQLITE_ENABLE_STMTVTAB TESTFIXTURE_FLAGS += -DSQLITE_ENABLE_DBPAGE_VTAB TESTFIXTURE_FLAGS += -DSQLITE_ENABLE_BYTECODE_VTAB TESTFIXTURE_FLAGS += -DSQLITE_CKSUMVFS_STATIC -TESTFIXTURE_FLAGS += -DSQLITE_STATIC_RANDOMJSON -TESTFIXTURE_FLAGS += -DSQLITE_STRICT_SUBTYPE=1 TESTFIXTURE_SRC0 = $(TESTSRC2) libsqlite3.la TESTFIXTURE_SRC1 = sqlite3.c TESTFIXTURE_SRC = $(TESTSRC) $(TOP)/src/tclsqlite.c TESTFIXTURE_SRC += $(TESTFIXTURE_SRC$(USE_AMALGAMATION)) @@ -1310,13 +1284,13 @@ testfixture$(TEXE): has_tclsh85 $(TESTFIXTURE_SRC) $(LTLINK) -DSQLITE_NO_SYNC=1 $(TEMP_STORE) $(TESTFIXTURE_FLAGS) \ -o $@ $(TESTFIXTURE_SRC) $(LIBTCL) $(TLIBS) -coretestprogs: testfixture$(BEXE) sqlite3$(BEXE) +coretestprogs: $(TESTPROGS) -testprogs: $(TESTPROGS) srcck1$(BEXE) fuzzcheck$(TEXE) sessionfuzz$(TEXE) +testprogs: coretestprogs srcck1$(BEXE) fuzzcheck$(TEXE) sessionfuzz$(TEXE) # A very detailed test running most or all test cases fulltest: alltest fuzztest # Run most or all tcl test cases Index: Makefile.msc ================================================================== --- Makefile.msc +++ Makefile.msc @@ -16,17 +16,10 @@ !IFNDEF USE_AMALGAMATION USE_AMALGAMATION = 1 !ENDIF # <> -# Optionally set EXTRA_SRC to a list of C files to append to -# the generated sqlite3.c. -# -!IFNDEF EXTRA_SRC -EXTRA_SRC = -!ENDIF - # Set this non-0 to enable full warnings (-W4, etc) when compiling. # !IFNDEF USE_FULLWARN USE_FULLWARN = 1 !ENDIF @@ -1589,11 +1582,10 @@ $(TOP)\ext\misc\nextchar.c \ $(TOP)\ext\misc\normalize.c \ $(TOP)\ext\misc\percentile.c \ $(TOP)\ext\misc\prefixes.c \ $(TOP)\ext\misc\qpvtab.c \ - $(TOP)\ext\misc\randomjson.c \ $(TOP)\ext\misc\regexp.c \ $(TOP)\ext\misc\remember.c \ $(TOP)\ext\misc\series.c \ $(TOP)\ext\misc\spellfix.c \ $(TOP)\ext\misc\totype.c \ @@ -1600,12 +1592,10 @@ $(TOP)\ext\misc\unionvtab.c \ $(TOP)\ext\misc\wholenumber.c \ $(TOP)\ext\rtree\test_rtreedoc.c \ $(TOP)\ext\recover\sqlite3recover.c \ $(TOP)\ext\recover\test_recover.c \ - $(TOP)\ext\intck\test_intck.c \ - $(TOP)\ext\intck\sqlite3intck.c \ $(TOP)\ext\recover\dbdata.c # If use of zlib is enabled, add the "zipfile.c" source file. # !IF $(USE_ZLIB)!=0 @@ -1700,11 +1690,10 @@ 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 -SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_STRICT_SUBTYPE=1 !ENDIF # <> # Extra compiler options for various test tools. # @@ -1737,12 +1726,10 @@ 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_STRICT_SUBTYPE=1 -FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_STATIC_RANDOMJSON FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_MAX_MEMORY=50000000 FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_PRINTF_PRECISION_LIMIT=1000 FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_OMIT_LOAD_EXTENSION FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_FTS4 @@ -1755,11 +1742,10 @@ FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\test\ossfuzz.c FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\test\fuzzinvariants.c FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\test\vt02.c FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\recover\dbdata.c FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\recover\sqlite3recover.c -FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\randomjson.c OSSSHELL_SRC = $(TOP)\test\ossshell.c $(TOP)\test\ossfuzz.c DBFUZZ_COMPILE_OPTS = -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION KV_COMPILE_OPTS = -DSQLITE_THREADSAFE=0 -DSQLITE_DIRECT_OVERFLOW_READ ST_COMPILE_OPTS = -DSQLITE_THREADSAFE=0 @@ -1835,12 +1821,12 @@ $(SQLITE3EXE): shell.c $(SHELL_CORE_DEP) $(LIBRESOBJS) $(SHELL_CORE_SRC) $(SQLITE3H) $(LTLINK) $(SHELL_COMPILE_OPTS) $(READLINE_FLAGS) shell.c $(SHELL_CORE_SRC) \ /link $(SQLITE3EXEPDB) $(LDFLAGS) $(LTLINKOPTS) $(SHELL_LINK_OPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS) # <> -sqldiff.exe: $(TOP)\tool\sqldiff.c $(TOP)\ext\consio\console_io.h $(TOP)\ext\consio\console_io.c $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS) - $(LTLINK) $(NO_WARN) -I$(TOP)\ext\consio $(TOP)\tool\sqldiff.c $(TOP)\ext\consio\console_io.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(LIBRESOBJS) +sqldiff.exe: $(TOP)\tool\sqldiff.c $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS) + $(LTLINK) $(NO_WARN) $(TOP)\tool\sqldiff.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(LIBRESOBJS) dbhash.exe: $(TOP)\tool\dbhash.c $(SQLITE3C) $(SQLITE3H) $(LTLINK) $(NO_WARN) $(TOP)\tool\dbhash.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) scrub.exe: $(TOP)\ext\misc\scrub.c $(SQLITE3C) $(SQLITE3H) @@ -1920,11 +1906,11 @@ $(TCLSH_CMD) $(TOP)\tool\vdbe-compress.tcl $(OPTS) < tsrc\vdbe.c > vdbe.new move vdbe.new tsrc\vdbe.c echo > .target_source sqlite3.c: .target_source sqlite3ext.h sqlite3session.h $(MKSQLITE3C_TOOL) src-verify.exe - $(TCLSH_CMD) $(MKSQLITE3C_TOOL) $(MKSQLITE3C_ARGS) $(EXTRA_SRC) + $(TCLSH_CMD) $(MKSQLITE3C_TOOL) $(MKSQLITE3C_ARGS) sqlite3-all.c: sqlite3.c $(TOP)\tool\split-sqlite3c.tcl $(TCLSH_CMD) $(TOP)\tool\split-sqlite3c.tcl # <> @@ -2270,48 +2256,43 @@ $(TOP)\tool\mkkeywordhash.c /link $(LDFLAGS) $(NLTLINKOPTS) $(NLTLIBPATHS) keywordhash.h: $(TOP)\tool\mkkeywordhash.c mkkeywordhash.exe .\mkkeywordhash.exe > keywordhash.h -# Source and header files that shell.c depends on -SHELL_DEP = \ - $(TOP)\src\shell.c.in \ - $(TOP)\ext\consio\console_io.c \ - $(TOP)\ext\consio\console_io.h \ - $(TOP)\ext\expert\sqlite3expert.c \ - $(TOP)\ext\expert\sqlite3expert.h \ - $(TOP)\ext\intck\sqlite3intck.c \ - $(TOP)\ext\intck\sqlite3intck.h \ - $(TOP)\ext\misc\appendvfs.c \ - $(TOP)\ext\misc\base64.c \ - $(TOP)\ext\misc\base85.c \ - $(TOP)\ext\misc\completion.c \ - $(TOP)\ext\misc\decimal.c \ - $(TOP)\ext\misc\fileio.c \ - $(TOP)\ext\misc\ieee754.c \ - $(TOP)\ext\misc\memtrace.c \ - $(TOP)\ext\misc\pcachetrace.c \ - $(TOP)\ext\misc\regexp.c \ - $(TOP)\ext\misc\series.c \ - $(TOP)\ext\misc\shathree.c \ - $(TOP)\ext\misc\sqlar.c \ - $(TOP)\ext\misc\uint.c \ - $(TOP)\ext\misc\zipfile.c \ - $(TOP)\ext\recover\dbdata.c \ - $(TOP)\ext\recover\sqlite3recover.c \ - $(TOP)\ext\recover\sqlite3recover.h \ - $(TOP)\src\test_windirent.c \ - $(TOP)\src\test_windirent.h +# Source files that go into making shell.c +SHELL_SRC = \ + $(TOP)\src\shell.c.in \ + $(TOP)\ext\consio\console_io.c \ + $(TOP)\ext\consio\console_io.h \ + $(TOP)\ext\misc\appendvfs.c \ + $(TOP)\ext\misc\completion.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 \ + $(TOP)\ext\misc\regexp.c \ + $(TOP)\ext\misc\series.c \ + $(TOP)\ext\misc\shathree.c \ + $(TOP)\ext\misc\uint.c \ + $(TOP)\ext\expert\sqlite3expert.c \ + $(TOP)\ext\expert\sqlite3expert.h \ + $(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 # If use of zlib is enabled, add the "zipfile.c" source file. # !IF $(USE_ZLIB)!=0 -SHELL_DEP = $(SHELL_DEP) $(TOP)\ext\misc\sqlar.c -SHELL_DEP = $(SHELL_DEP) $(TOP)\ext\misc\zipfile.c +SHELL_SRC = $(SHELL_SRC) $(TOP)\ext\misc\sqlar.c +SHELL_SRC = $(SHELL_SRC) $(TOP)\ext\misc\zipfile.c !ENDIF -shell.c: $(SHELL_DEP) $(TOP)\tool\mkshellc.tcl +shell.c: $(SHELL_SRC) $(TOP)\tool\mkshellc.tcl $(TCLSH_CMD) $(TOP)\tool\mkshellc.tcl > shell.c zlib: pushd $(ZLIBDIR) && $(MAKE) /f win32\Makefile.msc clean $(ZLIBLIB) && popd @@ -2450,12 +2431,10 @@ TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_ENABLE_STMTVTAB=1 TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_ENABLE_DBPAGE_VTAB=1 TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_ENABLE_BYTECODE_VTAB=1 TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_CKSUMVFS_STATIC=1 TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) $(TEST_CCONV_OPTS) -TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_STATIC_RANDOMJSON -TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_STRICT_SUBTYPE=1 TESTFIXTURE_SRC0 = $(TESTEXT) $(TESTSRC2) TESTFIXTURE_SRC1 = $(TESTEXT) $(SQLITE3C) !IF $(USE_AMALGAMATION)==0 TESTFIXTURE_SRC = $(TESTSRC) $(TOP)\src\tclsqlite.c $(TESTFIXTURE_SRC0) @@ -2494,13 +2473,13 @@ .\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: testfixture.exe sqlite3.exe +coretestprogs: $(TESTPROGS) -testprogs: $(TESTPROGS) srcck1.exe fuzzcheck.exe sessionfuzz.exe +testprogs: coretestprogs srcck1.exe fuzzcheck.exe sessionfuzz.exe fulltest: alltest fuzztest alltest: $(TESTPROGS) @set PATH=$(LIBTCLPATH);$(PATH) @@ -2551,11 +2530,11 @@ mdevtest: $(TCLSH_CMD) $(TOP)\test\testrunner.tcl mdevtest # Testing for a release # -releasetest: testfixture.exe +releasetest: testfixture.exe fuzztest testfixture.exe $(TOP)\test\testrunner.tcl release smoketest: $(TESTPROGS) @set PATH=$(LIBTCLPATH);$(PATH) @@ -2562,11 +2541,11 @@ .\testfixture.exe $(TOP)\test\main.test $(TESTOPTS) shelltest: $(TESTPROGS) .\testfixture.exe $(TOP)\test\permutations.test shell -sqlite3_analyzer.c: $(SQLITE3C) $(SQLITE3H) $(TOP)\src\tclsqlite.c $(TOP)\tool\spaceanal.tcl $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqlite3_analyzer.c.in $(TOP)\ext\consio\console_io.h $(TOP)\ext\consio\console_io.c $(SQLITE_TCL_DEP) +sqlite3_analyzer.c: $(SQLITE3C) $(SQLITE3H) $(TOP)\src\tclsqlite.c $(TOP)\tool\spaceanal.tcl $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqlite3_analyzer.c.in $(SQLITE_TCL_DEP) $(TCLSH_CMD) $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqlite3_analyzer.c.in > $@ sqlite3_analyzer.exe: sqlite3_analyzer.c $(LIBRESOBJS) $(LTLINK) $(NO_WARN) -DBUILD_sqlite -I$(TCLINCDIR) sqlite3_analyzer.c \ /link $(LDFLAGS) $(LTLINKOPTS) $(TCLLIBPATHS) $(LTLIBPATHS) $(LIBRESOBJS) $(TCLLIBS) $(LTLIBS) $(TLIBS) Index: README.md ================================================================== --- README.md +++ README.md @@ -157,11 +157,11 @@ (Historical note: SQLite began as a Tcl extension and only later escaped to the wild as an independent library.) Test scripts and programs are found in the **test/** subdirectory. Additional test code is found in other source repositories. -See [How SQLite Is Tested](https://www.sqlite.org/testing.html) for +See [How SQLite Is Tested](http://www.sqlite.org/testing.html) for additional information. The **ext/** subdirectory contains code for extensions. The Full-text search engine is in **ext/fts3**. The R-Tree engine is in **ext/rtree**. The **ext/misc** subdirectory contains a number of @@ -181,11 +181,11 @@ fill it with all the source files needed to build SQLite, both manually-edited files and automatically-generated files. The SQLite interface is defined by the **sqlite3.h** header file, which is generated from src/sqlite.h.in, ./manifest.uuid, and ./VERSION. The -[Tcl script](https://www.tcl.tk) at tool/mksqlite3h.tcl does the conversion. +[Tcl script](http://www.tcl.tk) at tool/mksqlite3h.tcl does the conversion. The manifest.uuid file contains the SHA3 hash of the particular check-in and is used to generate the SQLITE\_SOURCE\_ID macro. The VERSION file contains the current SQLite version number. The sqlite3.h header is really just a copy of src/sqlite.h.in with the source-id and version number inserted at just the right spots. Note that comment text in the sqlite3.h file is @@ -248,18 +248,18 @@ individual source file exceeds 32K lines in length. ## How It All Fits Together SQLite is modular in design. -See the [architectural description](https://www.sqlite.org/arch.html) +See the [architectural description](http://www.sqlite.org/arch.html) for details. Other documents that are useful in (helping to understand how SQLite works include the -[file format](https://www.sqlite.org/fileformat2.html) description, -the [virtual machine](https://www.sqlite.org/opcode.html) that runs +[file format](http://www.sqlite.org/fileformat2.html) description, +the [virtual machine](http://www.sqlite.org/opcode.html) that runs prepared statements, the description of -[how transactions work](https://www.sqlite.org/atomiccommit.html), and -the [overview of the query planner](https://www.sqlite.org/optoverview.html). +[how transactions work](http://www.sqlite.org/atomiccommit.html), and +the [overview of the query planner](http://www.sqlite.org/optoverview.html). Years of effort have gone into optimizing SQLite, both for small size and high performance. And optimizations tend to result in complex code. So there is a lot of complexity in the current SQLite implementation. It will not be the easiest library in the world to hack. @@ -351,9 +351,9 @@ accidental changes to the source tree, but malicious changes could be hidden by also modifying the makefiles. ## Contacts -The main SQLite website is [https://sqlite.org/](https://sqlite.org/) +The main SQLite website is [http:/sqlite.org/](http://sqlite.org/) with geographically distributed backups at -[https://www2.sqlite.org/](https://www2.sqlite.org) and -[https://www3.sqlite.org/](https://www3.sqlite.org). +[http://www2.sqlite.org/](http://www2.sqlite.org) and +[http://www3.sqlite.org/](http://www3.sqlite.org). Index: VERSION ================================================================== --- VERSION +++ VERSION @@ -1,1 +1,1 @@ -3.46.0 +3.44.1 DELETED art/icon-243x273.gif Index: art/icon-243x273.gif ================================================================== --- art/icon-243x273.gif +++ /dev/null cannot compute difference between binary files DELETED art/icon-80x90.gif Index: art/icon-80x90.gif ================================================================== --- art/icon-80x90.gif +++ /dev/null cannot compute difference between binary files Index: autoconf/Makefile.msc ================================================================== --- autoconf/Makefile.msc +++ autoconf/Makefile.msc @@ -16,17 +16,10 @@ # that contains this "Makefile.msc". # TOP = . -# Optionally set EXTRA_SRC to a list of C files to append to -# the generated sqlite3.c. -# -!IFNDEF EXTRA_SRC -EXTRA_SRC = -!ENDIF - # Set this non-0 to enable full warnings (-W4, etc) when compiling. # !IFNDEF USE_FULLWARN USE_FULLWARN = 1 !ENDIF @@ -995,11 +988,10 @@ 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 -SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_STRICT_SUBTYPE=1 !ENDIF # This is the default Makefile target. The objects listed here # are what get build when you type just "make" with no arguments. Index: autoconf/tea/configure.ac ================================================================== --- autoconf/tea/configure.ac +++ autoconf/tea/configure.ac @@ -17,11 +17,11 @@ # so you can encode the package version directly into the source files. # This will also define a special symbol for Windows (BUILD_ # so that we create the export library with the dll. #----------------------------------------------------------------------- -AC_INIT([sqlite],[3.46.0]) +AC_INIT([sqlite],[3.44.1]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. # This will define a ${TEA_PLATFORM} variable == "unix" or "windows" # as well as PKG_LIB_FILE and PKG_STUB_LIB_FILE. Index: configure ================================================================== --- configure +++ configure @@ -1,8 +1,8 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for sqlite 3.46.0. +# Generated by GNU Autoconf 2.69 for sqlite 3.44.1. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. # # @@ -724,12 +724,12 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.46.0' -PACKAGE_STRING='sqlite 3.46.0' +PACKAGE_VERSION='3.44.1' +PACKAGE_STRING='sqlite 3.44.1' PACKAGE_BUGREPORT='' PACKAGE_URL='' # Factoring default headers for most tests. ac_includes_default="\ @@ -1470,11 +1470,11 @@ # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures sqlite 3.46.0 to adapt to many kinds of systems. +\`configure' configures sqlite 3.44.1 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. @@ -1535,11 +1535,11 @@ _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.46.0:";; + short | recursive ) echo "Configuration of sqlite 3.44.1:";; esac cat <<\_ACEOF Optional Features: --disable-option-checking ignore unrecognized --enable/--with options @@ -1666,11 +1666,11 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.46.0 +sqlite configure 3.44.1 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. @@ -2085,11 +2085,11 @@ } # ac_fn_c_check_header_mongrel cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by sqlite $as_me 3.46.0, which was +It was created by sqlite $as_me 3.44.1, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ _ACEOF @@ -12479,11 +12479,11 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by sqlite $as_me 3.46.0, which was +This file was extended by sqlite $as_me 3.44.1, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS @@ -12545,11 +12545,11 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite config.status 3.46.0 +sqlite config.status 3.44.1 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" Copyright (C) 2012 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -72,11 +72,11 @@ # This configure.in file is easy to reuse on other projects. Just # change the argument to AC_INIT. And disable any features that # you don't need (for example BLT) by erasing or commenting out # the corresponding code. # -AC_INIT([sqlite],m4_esyscmd(cat VERSION | tr -d '\n')) +AC_INIT([sqlite],[m4_esyscmd(cat VERSION | tr -d '\n')]) dnl Make sure the local VERSION file matches this configure script sqlite_version_sanity_check=`cat $srcdir/VERSION | tr -d '\n'` if test "$PACKAGE_VERSION" != "$sqlite_version_sanity_check" ; then AC_MSG_ERROR([configure script is out of date: Index: doc/compile-for-windows.md ================================================================== --- doc/compile-for-windows.md +++ doc/compile-for-windows.md @@ -1,9 +1,9 @@ # Notes On Compiling SQLite On Windows 11 Here are step-by-step instructions on how to build SQLite from -canonical source on a new Windows 11 PC, as of 2023-11-01: +canonical source on a new Windows 11 PC, as of 2023-08-16: 1. Install Microsoft Visual Studio. The free "community edition" will work fine. Do a standard install for C++ development. SQLite only needs the "cl" compiler and the "nmake" build tool. @@ -82,22 +82,10 @@ a command like: -## Building a DLL - -The command the developers use for building the deliverable DLL on the -[download page](https://sqlite.org/download.html) is as follows: - -> ~~~~ -nmake /f Makefile.msc sqlite3.dll USE_NATIVE_LIBPATHS=1 "OPTS=-DSQLITE_ENABLE_FTS3=1 -DSQLITE_ENABLE_FTS4=1 -DSQLITE_ENABLE_FTS5=1 -DSQLITE_ENABLE_RTREE=1 -DSQLITE_ENABLE_JSON1=1 -DSQLITE_ENABLE_GEOPOLY=1 -DSQLITE_ENABLE_SESSION=1 -DSQLITE_ENABLE_PREUPDATE_HOOK=1 -DSQLITE_ENABLE_SERIALIZE=1 -DSQLITE_ENABLE_MATH_FUNCTIONS=1" -~~~~ - -That command generates both the sqlite3.dll and sqlite3.def files. The same -command works for both 32-bit and 64-bit builds. - ## Statically Linking The TCL Library Some utility programs associated with SQLite need to be linked 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 DELETED doc/jsonb.md Index: doc/jsonb.md ================================================================== --- doc/jsonb.md +++ /dev/null @@ -1,290 +0,0 @@ -# The JSONB Format - -This document describes SQLite's JSONB binary encoding of -JSON. - -## 1.0 What Is JSONB? - -Beginning with version 3.45.0 (circa 2024-01-01), SQLite supports an -alternative binary encoding of JSON which we call "JSONB". JSONB is -a binary format that stored as a BLOB. - -The advantage of JSONB over ordinary text RFC 8259 JSON is that JSONB -is both slightly smaller (by between 5% and 10% in most cases) and -can be processed in less than half the number of CPU cycles. The built-in -[JSON SQL functions] of SQLite can accept either ordinary text JSON -or the binary JSONB encoding for any of their JSON inputs. - -The "JSONB" name is inspired by [PostgreSQL](https://postgresql.org), but the -on-disk format for SQLite's JSONB is not the same as PostgreSQL's. -The two formats have the same name, but they have wildly different internal -representations and are not in any way binary compatible. - -The central idea behind this JSONB specification is that each element -begins with a header that includes the size and type of that element. -The header takes the place of punctuation such as double-quotes, -curly-brackes, square-brackets, commas, and colons. Since the size -and type of each element is contained in its header, the element can -be read faster since it is no longer necessary to carefully scan forward -looking for the closing delimiter. The payload of JSONB is the same -as for corresponding text JSON. The same payload bytes occur in the -same order. The only real difference between JSONB and ordinary text -JSON is that JSONB includes a binary header on -each element and omits delimiter and separator punctuation. - -### 1.1 Internal Use Only - -The details of the JSONB are not intended to be visible to application -developers. Application developers should look at JSONB as an opaque BLOB -used internally by SQLite. Nevertheless, we want the format to be backwards -compatible across all future versions of SQLite. To that end, the format -is documented by this file in the source tree. But this file should be -used only by SQLite core developers, not by developers of applications -that only use SQLite. - -## 2.0 The Purpose Of This Document - -JSONB is not intended as an external format to be used by -applications. JSONB is designed for internal use by SQLite only. -Programmers do not need to understand the JSONB format in order to -use it effectively. -Applications should access JSONB only through the [JSON SQL functions], -not by looking at individual bytes of the BLOB. - -However, JSONB is intended to be portable and backwards compatible -for all future versions of SQLite. In other words, you should not have -to export and reimport your SQLite database files when you upgrade to -a newer SQLite version. For that reason, the JSONB format needs to -be well-defined. - -This document is therefore similar in purpose to the -[SQLite database file format] document that describes the on-disk -format of an SQLite database file. Applications are not expected -to directly read and write the bits and bytes of SQLite database files. -The SQLite database file format is carefully documented so that it -can be stable and enduring. In the same way, the JSONB representation -of JSON is documented here so that it too can be stable and enduring, -not so that applications can read or writes individual bytes. - -## 3.0 Encoding - -JSONB is a direct translation of the underlying text JSON. The difference -is that JSONB uses a binary encoding that is faster to parse compared to -the detailed syntax of text JSON. - -Each JSON element is encoded as a header and a payload. The header -determines type of element (string, numeric, boolean, null, object, or -array) and the size of the payload. The header can be between 1 and -9 bytes in size. The payload can be any size from zero bytes up to the -maximum allowed BLOB size. - -### 3.1 Payload Size - -The upper four bits of the first byte of the header determine size of the -header and possibly also the size of the payload. -If the upper four bits have a value between 0 and 11, then the header is -exactly one byte in size and the payload size is determined by those -upper four bits. If the upper four bits have a value between 12 and 15, -that means that the total header size is 2, 3, 5, or 9 bytes and the -payload size is unsigned big-endian integer that is contained in the -subsequent bytes. The size integer is the one byte that following the -initial header byte if the upper four bits -are 12, two bytes if the upper bits are 13, four bytes if the upper bits -are 14, and eight bytes if the upper bits are 15. The current design -of SQLite does not support BLOB values larger than 2GiB, so the eight-byte -variant of the payload size integer will never be used by the current code. -The eight-byte payload size integer is included in the specification -to allow for future expansion. - -The header for an element does *not* need to be in its simplest -form. For example, consider the JSON numeric value "`1`". -That element can be encode in five different ways: - - * `0x13 0x31` - * `0xc3 0x01 0x31` - * `0xd3 0x00 0x01 0x31` - * `0xe3 0x00 0x00 0x00 0x01 0x31` - * `0xf3 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x01 0x31` - -The shortest encoding is preferred, of course, and usually happens with -primitive elements such as numbers. However the total size of an array -or object might not be known exactly when the header of the element is -first generated. It is convenient to reserve space for the largest -possible header and then go back and fill in the correct payload size -at the end. This technique can result in array or object headers that -are larger than absolutely necessary. - -### 3.2 Element Type - -The least-significant four bits of the first byte of the header (the first -byte masked against 0x0f) determine element type. The following codes are -used: - -
    -
  1. NULL → -The element is a JSON "null". The payload size for a true JSON NULL must -must be zero. Future versions of SQLite might extend the JSONB format -with elements that have a zero element type but a non-zero size. In that -way, legacy versions of SQLite will interpret the element as a NULL -for backwards compatibility while newer versions will interpret the -element in some other way. - -

  2. TRUE → -The element is a JSON "true". The payload size must be zero for a actual -"true" value. Elements with type 1 and a non-zero payload size are -reserved for future expansion. Legacy implementations that see an element -type of 1 with a non-zero payload size should continue to interpret that -element as "true" for compatibility. - -

  3. FALSE → -The element is a JSON "false". The payload size must be zero for a actual -"false" value. Elements with type 2 and a non-zero payload size are -reserved for future expansion. Legacy implementations that see an element -type of 2 with a non-zero payload size should continue to interpret that -element as "false" for compatibility. - -

  4. INT → -The element is a JSON integer value in the canonical -RFC 8259 format, without extensions. The payload is the ASCII -text representation of that numeric value. - -

  5. INT5 → -The element is a JSON integer value that is not in the -canonical format. The payload is the ASCII -text representation of that numeric value. Because the payload is in a -non-standard format, it will need to be translated when the JSONB is -converted into RFC 8259 text JSON. - -

  6. FLOAT → -The element is a JSON floating-point value in the canonical -RFC 8259 format, without extensions. The payload is the ASCII -text representation of that numeric value. - -

  7. FLOAT5 → -The element is a JSON floating-point value that is not in the -canonical format. The payload is the ASCII -text representation of that numeric value. Because the payload is in a -non-standard format, it will need to be translated when the JSONB is -converted into RFC 8259 text JSON. - -

  8. TEXT → -The element is a JSON string value that does not contain -any escapes nor any characters that need to be escaped for either SQL or -JSON. The payload is the UTF8 text representation of the string value. -The payload does not include string delimiters. - -

  9. TEXTJ → -The element is a JSON string value that contains -RFC 8259 character escapes (such as "\n" or "\u0020"). -Those escapes will need to be translated into actual UTF8 if this element -is [json_extract|extracted] into SQL. -The payload is the UTF8 text representation of the escaped string value. -The payload does not include string delimiters. - -

  10. TEXT5 → -The element is a JSON string value that contains -character escapes, including some character escapes that part of JSON5 -and which are not found in the canonical RFC 8259 spec. -Those escapes will need to be translated into standard JSON prior to -rendering the JSON as text, or into their actual UTF8 characters if this -element is [json_extract|extracted] into SQL. -The payload is the UTF8 text representation of the escaped string value. -The payload does not include string delimiters. - -

  11. TEXTRAW → -The element is a JSON string value that contains -UTF8 characters that need to be escaped if this string is rendered into -standard JSON text. -The payload does not include string delimiters. - -

  12. ARRAY → -The element is a JSON array. The payload contains -JSONB elements that comprise values contained within the array. - -

  13. OBJECT → -The element is a JSON object. The payload contains -pairs of JSONB elements that comprise entries for the JSON object. -The first element in each pair must be a string (types 7 through 10). -The second element of each pair may be any types, including nested -arrays or objects. - -

  14. RESERVED-13 → -Reserved for future expansion. Legacy implements that encounter this -element type should raise an error. - -

  15. RESERVED-14 → -Reserved for future expansion. Legacy implements that encounter this -element type should raise an error. - -

  16. RESERVED-15 → -Reserved for future expansion. Legacy implements that encounter this -element type should raise an error. -

- -Element types outside the range of 0 to 12 are reserved for future -expansion. The current implement raises an error if see an element type -other than those listed above. However, future versions of SQLite might -use of the three remaining element types to implement indexing or similar -optimizations, to speed up lookup against large JSON arrays and/or objects. - -### 3.3 Design Rationale For Element Types - -A key goal of JSONB is that it should be quick to translate -to and from text JSON and/or be constructed from SQL values. -When converting from text into JSONB, we do not want the -converter subroutine to burn CPU cycles converting elements -values into some standard format which might never be used. -Format conversion is "lazy" - it is deferred until actually -needed. This has implications for the JSONB format design: - - 1. Numeric values are stored as text, not a numbers. The values are - a direct copy of the text JSON values from which they are derived. - - 2. There are multiple element types depending on the details of value - formats. For example, INT is used for pure RFC-8259 integer - literals and INT5 exists for JSON5 extensions such as hexadecimal - notation. FLOAT is used for pure RFC-8259 floating point literals - and FLOAT5 is used for JSON5 extensions. There are four different - representations of strings, depending on where the string came from - and how special characters within the string are escaped. - -A second goal of JSONB is that it should be capable of serving as the -"parse tree" for JSON when a JSON value is being processed by the -various [JSON SQL functions] built into SQLite. Before JSONB was -developed, operations such [json_replace()] and [json_patch()] -and similar worked in three stages: - - - 1. Translate the text JSON into a internal format that is - easier to scan and edit. - 2. Perform the requested operation on the JSON. - 3. Translate the internal format back into text. - -JSONB seeks to serve as the internal format directly - bypassing -the first and third stages of that process. Since most of the CPU -cycles are spent on the first and third stages, that suggests that -JSONB processing will be much faster than text JSON processing. - -So when processing JSONB, only the second stage of the three-stage -process is required. But when processing text JSON, it is still necessary -to do stages one and three. If JSONB is to be used as the internal -binary representation, this is yet another reason to store numeric -values as text. Storing numbers as text minimizes the amount of -conversion work needed for stages one and three. This is also why -there are four different representations of text in JSONB. Different -text representations are used for text coming from different sources -(RFC-8259 JSON, JSON5, or SQL string values) and conversions only -happen if and when they are actually needed. - -### 3.4 Valid JSONB BLOBs - -A valid JSONB BLOB consists of a single JSON element. The element must -exactly fill the BLOB. This one element is often a JSON object or array -and those usually contain additional elements as its payload, but the -element can be a primite value such a string, number, boolean, or null. - -When the built-in JSON functions are attempting to determine if a BLOB -argument is a JSONB or just a random BLOB, they look at the header of -the outer element to see that it is well-formed and that the element -completely fills the BLOB. If these conditions are met, then the BLOB -is accepted as a JSONB value. Index: doc/lemon.html ================================================================== --- doc/lemon.html +++ doc/lemon.html @@ -681,11 +681,10 @@
  • %destructor
  • %else
  • %endif
  • %extra_argument
  • %fallback -
  • %free
  • %if
  • %ifdef
  • %ifndef
  • %include
  • %left @@ -692,11 +691,10 @@
  • %name
  • %nonassoc
  • %parse_accept
  • %parse_failure
  • %right -
  • %realloc
  • %stack_overflow
  • %stack_size
  • %start_symbol
  • %syntax_error
  • %token @@ -1200,25 +1198,10 @@

    When the generated parser has the choice of matching an input against the wildcard token and some other token, the other token is always used. The wildcard token is only matched if there are no alternatives.

    - -

    4.4.26 The %realloc and %free directives

    - -

    The %realloc and %free directives defines function -that allocate and free heap memory. The signatures of these functions -should be the same as the realloc() and free() functions from the standard -C library. - -

    If both of these functions are defined -then these functions are used to allocate and free -memory for supplemental parser stack space, if the initial -parse stack space is exceeded. The initial parser stack size -is specified by either %stack_size or the --DYYSTACKDEPTH compile-time flag. -

    5.0 Error Processing

    After extensive experimentation over several years, it has been discovered that the error recovery strategy used by yacc is about @@ -1238,11 +1221,10 @@ %parse_failure routine is invoked and the parser resets itself to its start state, ready to begin parsing a new file. This is what will happen at the very first syntax error, of course, if there are no instances of the "error" non-terminal in your grammar.

    -

    6.0 History of Lemon

    Lemon was originally written by Richard Hipp sometime in the late Index: doc/testrunner.md ================================================================== --- doc/testrunner.md +++ doc/testrunner.md @@ -1,47 +1,22 @@ # 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. Examples: - - `make mdevtest` - - `make releasetest` - - `make sdevtest` - - `make testrunner` + * 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 current working directory. Search this -file to find details of errors. Suggested search commands: - - * `grep "^!" testrunner.log` - * `grep failed testrunner.log` +**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: @@ -63,21 +38,20 @@ 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 +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 +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 +[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. @@ -85,11 +59,10 @@ 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 @@ -116,11 +89,10 @@ 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: ``` @@ -139,16 +111,10 @@ ``` ./testfixture $TESTDIR/testrunner.tcl fts5% ./testfixture $TESTDIR/testrunner.tcl 'fts5*' ``` - -Strictly speaking, for a test to be run the pattern must match the script -filename, not including the directory, using the rules of Tcl's -\[string match\] command. Except that before the matching is done, any "%" -characters specified as part of the pattern are transformed to "\*". - To run "all" tests (full + permutations): ``` ./testfixture $TESTDIR/testrunner.tcl all @@ -173,11 +139,10 @@ ./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: @@ -192,15 +157,14 @@ 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. - -## 3.1. Commands to Run SQLite Tests +## Commands to Run SQLite Tests The **mdevtest** command is equivalent to running the veryquick tests and -the `make fuzztest` target once for each of two --enable-all builds - one +the [make fuzztest] target once for each of two --enable-all builds - one with debugging enabled and one without: ``` tclsh $TESTDIR/testrunner.tcl mdevtest ``` @@ -235,22 +199,11 @@ ``` tclsh $TESTDIR/testrunner.tcl release ``` -As with source code tests, one or more patterns -may be appended to any of the above commands (mdevtest, sdevtest or release). -In that case only Tcl tests (no fuzz or other tests) that match the specified -pattern are run. For example, to run the just the Tcl rtree tests in all -builds and configurations supported by "release": - -``` - tclsh $TESTDIR/testrunner.tcl release rtree% -``` - - -## 3.2. Running ZipVFS Tests +## Running ZipVFS Tests testrunner.tcl can build a zipvfs-enabled testfixture and use it to run tests from the Zipvfs project with the following command: ``` @@ -262,12 +215,11 @@ ``` tclsh $TESTDIR/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS mdevtest ``` - -## 3.3. Investigating Source Code Test Failures +## Investigating Source Code Test Failures Investigating a test failure that occurs during source code testing is a two step process: 1. Recreating the build configuration in which the test failed, and @@ -286,49 +238,17 @@ # 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, +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. Extra testrunner.tcl Options - -The testrunner.tcl script options in this section may be used with both source -code and binary tests. - -The **--buildonly** option instructs testrunner.tcl just to build the binaries -required by a test, not to run any actual tests. For example: - -``` - # Build binaries required by release test. - tclsh $TESTDIR/testrunner.tcl --buildonly release" -``` - -The **--dryrun** option prevents testrunner.tcl from building any binaries -or running any tests. Instead, it just writes the shell commands that it -would normally execute into the testrunner.log file. Example: - -``` - # Log the shell commmands that make up the mdevtest test. - tclsh $TESTDIR/testrunner.tcl --dryrun mdevtest" -``` - -The **--explain** option is similar to --dryrun in that it prevents testrunner.tcl -from building any binaries or running any tests. The difference is that --explain -prints on standard output a human-readable summary of all the builds and tests that -would have been run. - -``` - # Show what builds and tests would have been run - tclsh $TESTDIR/testrunner.tcl --explain mdevtest -``` - - -# 5. Controlling CPU Core Utilization + + +# 4. Controlling CPU Core Utilization When running either binary or source code tests, testrunner.tcl reports the number of jobs it intends to use to stdout. e.g. ``` @@ -352,5 +272,13 @@ testrunner.log and testrunner.db files: ``` $ ./testfixture $TESTDIR/testrunner.tcl njob $NEW_NUMBER_OF_JOBS ``` + + + + + + + + Index: ext/consio/console_io.c ================================================================== --- ext/consio/console_io.c +++ ext/consio/console_io.c @@ -22,17 +22,12 @@ # include # include # include # include # include -# include "sqlite3.h" -#endif -#ifndef HAVE_CONSOLE_IO_H # include "console_io.h" -#endif -#if defined(_MSC_VER) -# pragma warning(disable : 4204) +# include "sqlite3.h" #endif #ifndef SQLITE_CIO_NO_TRANSLATE # if (defined(_WIN32) || defined(WIN32)) && !SQLITE_OS_WINRT # ifndef SHELL_NO_SYSINC @@ -128,14 +123,10 @@ ppst->reachesConsole = ( (short)isatty(fileno(pf)) ); return ppst->reachesConsole; # endif } -# ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING -# define ENABLE_VIRTUAL_TERMINAL_PROCESSING (0x4) -# endif - # if CIO_WIN_WC_XLATE /* Define console modes for use with the Windows Console API. */ # define SHELL_CONI_MODE \ (ENABLE_ECHO_INPUT | ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | 0x80 \ | ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS | ENABLE_PROCESSED_INPUT) @@ -350,12 +341,12 @@ /* Get stream info, either for designated output or error stream when ** chix equals 1 or 2, or for an arbitrary stream when chix == 0. ** In either case, ppst references a caller-owned PerStreamTags ** struct which may be filled in if none of the known writable -** streams is being held by consoleInfo. The ppf parameter is a -** byref output when chix!=0 and a byref input when chix==0. +** streams is being held by consoleInfo. The ppf parameter is an +** output when chix!=0 and an input when chix==0. */ static PerStreamTags * getEmitStreamInfo(unsigned chix, PerStreamTags *ppst, /* in/out */ FILE **ppf){ PerStreamTags *ppstTry; @@ -364,11 +355,11 @@ ppstTry = &consoleInfo.pstDesignated[chix]; if( !isValidStreamInfo(ppstTry) ){ ppstTry = &consoleInfo.pstSetup[chix]; pfEmit = ppst->pf; }else pfEmit = ppstTry->pf; - if( !isValidStreamInfo(ppstTry) ){ + if( !isValidStreamInfo(ppst) ){ pfEmit = (chix > 1)? stderr : stdout; ppstTry = ppst; streamOfConsole(pfEmit, ppstTry); } *ppf = pfEmit; @@ -556,31 +547,32 @@ return z; } #endif /*!(defined(SQLITE_CIO_NO_UTF8SCAN)&&defined(SQLITE_CIO_NO_TRANSLATE))*/ #ifndef SQLITE_CIO_NO_TRANSLATE -# ifdef CONSIO_SPUTB + +#ifdef CONSIO_SPUTB SQLITE_INTERNAL_LINKAGE int fPutbUtf8(FILE *pfO, const char *cBuf, int nAccept){ assert(pfO!=0); -# if CIO_WIN_WC_XLATE +# if CIO_WIN_WC_XLATE PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ PerStreamTags *ppst = getEmitStreamInfo(0, &pst, &pfO); if( pstReachesConsole(ppst) ){ int rv; maybeSetupAsConsole(ppst, 1); rv = conZstrEmit(ppst, cBuf, nAccept); if( 0 == isKnownWritable(ppst->pf) ) restoreConsoleArb(ppst); return rv; }else { -# endif +# endif return (int)fwrite(cBuf, 1, nAccept, pfO); -# if CIO_WIN_WC_XLATE +# if CIO_WIN_WC_XLATE } -# endif +# endif } -# endif +#endif /* defined(CONSIO_SPUTB) */ SQLITE_INTERNAL_LINKAGE int oPutbUtf8(const char *cBuf, int nAccept){ FILE *pfOut; PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ @@ -682,10 +674,7 @@ } # endif } #endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */ -#if defined(_MSC_VER) -# pragma warning(default : 4204) -#endif - +#undef CIO_WIN_WC_XLATE #undef SHELL_INVALID_FILE_PTR Index: ext/consio/console_io.h ================================================================== --- ext/consio/console_io.h +++ ext/consio/console_io.h @@ -20,17 +20,12 @@ ** source or object code compiled from it need no explicit conditional ** compilation in their source for their console and stream I/O. ** ** The symbols and functionality exposed here are not a public API. ** This code may change in tandem with other project code as needed. -** -** When this .h file and its companion .c are directly incorporated into -** a source conglomeration (such as shell.c), the preprocessor symbol -** CIO_WIN_WC_XLATE is defined as 0 or 1, reflecting whether console I/O -** translation for Windows is effected for the build. */ -#define HAVE_CONSOLE_IO_H 1 + #ifndef SQLITE_INTERNAL_LINKAGE # define SQLITE_INTERNAL_LINKAGE extern /* external to translation unit */ # include #else # define SHELL_NO_SYSINC /* Better yet, modify mkshellc.tcl for this. */ @@ -164,12 +159,12 @@ ** Returns the number of accepted char values. */ #ifdef CONSIO_SPUTB SQLITE_INTERNAL_LINKAGE int fPutbUtf8(FILE *pfOut, const char *cBuf, int nAccept); -/* Like fPutbUtf8 except stream is always the designated output. */ #endif +/* Like fPutbUtf8 except stream is always the designated output. */ SQLITE_INTERNAL_LINKAGE int oPutbUtf8(const char *cBuf, int nAccept); /* Like fPutbUtf8 except stream is always the designated error. */ #ifdef CONSIO_EPUTB SQLITE_INTERNAL_LINKAGE int Index: ext/expert/expert1.test ================================================================== --- ext/expert/expert1.test +++ ext/expert/expert1.test @@ -6,11 +6,10 @@ # 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: shell # # The focus of this file is testing the CLI shell tool. Specifically, # the ".recommend" command. # # Index: ext/expert/sqlite3expert.c ================================================================== --- ext/expert/sqlite3expert.c +++ ext/expert/sqlite3expert.c @@ -1946,11 +1946,11 @@ /* Copy the entire schema of database [db] into [dbm]. */ if( rc==SQLITE_OK ){ sqlite3_stmt *pSql = 0; rc = idxPrintfPrepareStmt(pNew->db, &pSql, pzErrmsg, "SELECT sql FROM sqlite_schema WHERE name NOT LIKE 'sqlite_%%'" - " AND sql NOT LIKE 'CREATE VIRTUAL %%' ORDER BY rowid" + " AND sql NOT LIKE 'CREATE VIRTUAL %%'" ); while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ const char *zSql = (const char*)sqlite3_column_text(pSql, 0); if( zSql ) rc = sqlite3_exec(pNew->dbm, zSql, 0, 0, pzErrmsg); } Index: ext/fts3/fts3.c ================================================================== --- ext/fts3/fts3.c +++ ext/fts3/fts3.c @@ -4004,36 +4004,43 @@ /* ** Implementation of the xIntegrity() method on the FTS3/FTS4 virtual ** table. */ -static int fts3IntegrityMethod( +static int fts3Integrity( sqlite3_vtab *pVtab, /* The virtual table to be checked */ const char *zSchema, /* Name of schema in which pVtab lives */ const char *zTabname, /* Name of the pVTab table */ int isQuick, /* True if this is a quick_check */ char **pzErr /* Write error message here */ ){ Fts3Table *p = (Fts3Table*)pVtab; - int rc = SQLITE_OK; - int bOk = 0; + char *zSql; + int rc; + char *zErr = 0; + assert( pzErr!=0 ); + assert( *pzErr==0 ); UNUSED_PARAMETER(isQuick); - rc = sqlite3Fts3IntegrityCheck(p, &bOk); - assert( rc!=SQLITE_CORRUPT_VTAB ); - if( rc==SQLITE_ERROR || (rc&0xFF)==SQLITE_CORRUPT ){ - *pzErr = sqlite3_mprintf("unable to validate the inverted index for" - " FTS%d table %s.%s: %s", - p->bFts4 ? 4 : 3, zSchema, zTabname, sqlite3_errstr(rc)); - if( *pzErr ) rc = SQLITE_OK; - }else if( rc==SQLITE_OK && bOk==0 ){ + zSql = sqlite3_mprintf( + "INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');", + zSchema, zTabname, zTabname); + if( zSql==0 ){ + return SQLITE_NOMEM; + } + rc = sqlite3_exec(p->db, zSql, 0, 0, &zErr); + sqlite3_free(zSql); + if( (rc&0xff)==SQLITE_CORRUPT ){ *pzErr = sqlite3_mprintf("malformed inverted index for FTS%d table %s.%s", p->bFts4 ? 4 : 3, zSchema, zTabname); - if( *pzErr==0 ) rc = SQLITE_NOMEM; + }else if( rc!=SQLITE_OK ){ + *pzErr = sqlite3_mprintf("unable to validate the inverted index for" + " FTS%d table %s.%s: %s", + p->bFts4 ? 4 : 3, zSchema, zTabname, zErr); } - sqlite3Fts3SegmentsClose(p); - return rc; + sqlite3_free(zErr); + return SQLITE_OK; } static const sqlite3_module fts3Module = { @@ -4059,11 +4066,11 @@ /* xRename */ fts3RenameMethod, /* xSavepoint */ fts3SavepointMethod, /* xRelease */ fts3ReleaseMethod, /* xRollbackTo */ fts3RollbackToMethod, /* xShadowName */ fts3ShadowName, - /* xIntegrity */ fts3IntegrityMethod, + /* xIntegrity */ fts3Integrity, }; /* ** This function is registered as the module destructor (called when an ** FTS3 enabled database connection is closed). It frees the memory Index: ext/fts3/fts3Int.h ================================================================== --- ext/fts3/fts3Int.h +++ ext/fts3/fts3Int.h @@ -651,9 +651,7 @@ int sqlite3FtsUnicodeIsdiacritic(int); #endif int sqlite3Fts3ExprIterate(Fts3Expr*, int (*x)(Fts3Expr*,int,void*), void*); -int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk); - #endif /* !SQLITE_CORE || SQLITE_ENABLE_FTS3 */ #endif /* _FTSINT_H */ Index: ext/fts3/fts3_write.c ================================================================== --- ext/fts3/fts3_write.c +++ ext/fts3/fts3_write.c @@ -5292,11 +5292,11 @@ ** to false before returning. ** ** If an error occurs (e.g. an OOM or IO error), return an SQLite error ** code. The final value of *pbOk is undefined in this case. */ -int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk){ +static int fts3IntegrityCheck(Fts3Table *p, int *pbOk){ int rc = SQLITE_OK; /* Return code */ u64 cksum1 = 0; /* Checksum based on FTS index contents */ u64 cksum2 = 0; /* Checksum based on %_content contents */ sqlite3_stmt *pAllLangid = 0; /* Statement to return all language-ids */ @@ -5370,16 +5370,11 @@ } sqlite3_finalize(pStmt); } - if( rc==SQLITE_CORRUPT_VTAB ){ - rc = SQLITE_OK; - *pbOk = 0; - }else{ - *pbOk = (rc==SQLITE_OK && cksum1==cksum2); - } + *pbOk = (cksum1==cksum2); return rc; } /* ** Run the integrity-check. If no error occurs and the current contents of @@ -5415,11 +5410,11 @@ static int fts3DoIntegrityCheck( Fts3Table *p /* FTS3 table handle */ ){ int rc; int bOk = 0; - rc = sqlite3Fts3IntegrityCheck(p, &bOk); + rc = fts3IntegrityCheck(p, &bOk); if( rc==SQLITE_OK && bOk==0 ) rc = FTS_CORRUPT_VTAB; return rc; } /* Index: ext/fts5/extract_api_docs.tcl ================================================================== --- ext/fts5/extract_api_docs.tcl +++ ext/fts5/extract_api_docs.tcl @@ -221,16 +221,14 @@ } Fts5ExtensionApi { set struct [get_fts5_struct $data "^struct Fts5ExtensionApi" "^.;"] set map [list] - set lKey [list] foreach {k v} [get_struct_members $data] { if {[string match x* $k]==0} continue - lappend lKey $k + lappend map $k "$k" } - foreach k [lsort -decr $lKey] { lappend map $k "$k" } output [string map $map $struct] } api { get_api_docs $data Index: ext/fts5/fts5.h ================================================================== --- ext/fts5/fts5.h +++ ext/fts5/fts5.h @@ -86,28 +86,23 @@ ** ** This function may be quite inefficient if used with an FTS5 table ** created with the "columnsize=0" option. ** ** xColumnText: -** If parameter iCol is less than zero, or greater than or equal to the -** number of columns in the table, SQLITE_RANGE is returned. -** -** Otherwise, this function attempts to retrieve the text of column iCol of -** the current document. If successful, (*pz) is set to point to a buffer +** This function attempts to retrieve the text of column iCol of the +** current document. If successful, (*pz) is set to point to a buffer ** containing the text in utf-8 encoding, (*pn) is set to the size in bytes ** (not characters) of the buffer and SQLITE_OK is returned. Otherwise, ** if an error occurs, an SQLite error code is returned and the final values ** of (*pz) and (*pn) are undefined. ** ** xPhraseCount: ** Returns the number of phrases in the current query expression. ** ** xPhraseSize: -** If parameter iCol is less than zero, or greater than or equal to the -** number of phrases in the current query, as returned by xPhraseCount, -** 0 is returned. Otherwise, this function returns the number of tokens in -** phrase iPhrase of the query. Phrases are numbered starting from zero. +** Returns the number of tokens in phrase iPhrase of the query. Phrases +** are numbered starting from zero. ** ** xInstCount: ** Set *pnInst to the total number of occurrences of all phrases within ** the query within the current row. Return SQLITE_OK if successful, or ** an error code (i.e. SQLITE_NOMEM) if an error occurs. @@ -119,17 +114,16 @@ ** ** xInst: ** Query for the details of phrase match iIdx within the current row. ** Phrase matches are numbered starting from zero, so the iIdx argument ** should be greater than or equal to zero and smaller than the value -** output by xInstCount(). If iIdx is less than zero or greater than -** or equal to the value returned by xInstCount(), SQLITE_RANGE is returned. +** output by xInstCount(). ** -** Otherwise, output parameter *piPhrase is set to the phrase number, *piCol +** Usually, output parameter *piPhrase is set to the phrase number, *piCol ** to the column in which it occurs and *piOff the token offset of the -** first token of the phrase. SQLITE_OK is returned if successful, or an -** error code (i.e. SQLITE_NOMEM) if an error occurs. +** first token of the phrase. Returns SQLITE_OK if successful, or an error +** code (i.e. SQLITE_NOMEM) if an error occurs. ** ** This API can be quite slow if used with an FTS5 table created with the ** "detail=none" or "detail=column" option. ** ** xRowid: @@ -151,14 +145,10 @@ ** is invoked. The context and API objects passed to the callback ** function may be used to access the properties of each matched row. ** Invoking Api.xUserData() returns a copy of the pointer passed as ** the third argument to pUserData. ** -** If parameter iPhrase is less than zero, or greater than or equal to -** the number of phrases in the query, as returned by xPhraseCount(), -** this function returns SQLITE_RANGE. -** ** If the callback function returns any value other than SQLITE_OK, the ** query is abandoned and the xQueryPhrase function returns immediately. ** If the returned value is SQLITE_DONE, xQueryPhrase returns SQLITE_OK. ** Otherwise, the error code is propagated upwards. ** @@ -269,46 +259,13 @@ ** significantly more efficient than those alternatives when used with ** "detail=column" tables. ** ** xPhraseNextColumn() ** See xPhraseFirstColumn above. -** -** xQueryToken(pFts5, iPhrase, iToken, ppToken, pnToken) -** This is used to access token iToken of phrase iPhrase of the current -** query. Before returning, output parameter *ppToken is set to point -** to a buffer containing the requested token, and *pnToken to the -** size of this buffer in bytes. -** -** If iPhrase or iToken are less than zero, or if iPhrase is greater than -** or equal to the number of phrases in the query as reported by -** xPhraseCount(), or if iToken is equal to or greater than the number of -** tokens in the phrase, SQLITE_RANGE is returned and *ppToken and *pnToken - are both zeroed. -** -** The output text is not a copy of the query text that specified the -** token. It is the output of the tokenizer module. For tokendata=1 -** tables, this includes any embedded 0x00 and trailing data. -** -** xInstToken(pFts5, iIdx, iToken, ppToken, pnToken) -** This is used to access token iToken of phrase hit iIdx within the -** current row. If iIdx is less than zero or greater than or equal to the -** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise, -** output variable (*ppToken) is set to point to a buffer containing the -** matching document token, and (*pnToken) to the size of that buffer in -** bytes. This API is not available if the specified token matches a -** prefix query term. In that case both output variables are always set -** to 0. -** -** The output text is not a copy of the document text that was tokenized. -** It is the output of the tokenizer module. For tokendata=1 tables, this -** includes any embedded 0x00 and trailing data. -** -** This API can be quite slow if used with an FTS5 table created with the -** "detail=none" or "detail=column" option. */ struct Fts5ExtensionApi { - int iVersion; /* Currently always set to 3 */ + int iVersion; /* Currently always set to 2 */ void *(*xUserData)(Fts5Context*); int (*xColumnCount)(Fts5Context*); int (*xRowCount)(Fts5Context*, sqlite3_int64 *pnRow); @@ -339,17 +296,10 @@ int (*xPhraseFirst)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*); void (*xPhraseNext)(Fts5Context*, Fts5PhraseIter*, int *piCol, int *piOff); int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*); void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol); - - /* Below this point are iVersion>=3 only */ - int (*xQueryToken)(Fts5Context*, - int iPhrase, int iToken, - const char **ppToken, int *pnToken - ); - int (*xInstToken)(Fts5Context*, int iIdx, int iToken, const char**, int*); }; /* ** CUSTOM AUXILIARY FUNCTIONS *************************************************************************/ Index: ext/fts5/fts5Int.h ================================================================== --- ext/fts5/fts5Int.h +++ ext/fts5/fts5Int.h @@ -194,11 +194,10 @@ 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) */ - int bTokendata; /* "tokendata=" option value (dflt==0) */ int eDetail; /* FTS5_DETAIL_XXX value */ char *zContentExprlist; Fts5Tokenizer *pTok; fts5_tokenizer *pTokApi; int bLock; /* True when table is preparing statement */ @@ -383,23 +382,21 @@ #define sqlite3Fts5IterEof(x) ((x)->bEof) /* ** Values used as part of the flags argument passed to IndexQuery(). */ -#define FTS5INDEX_QUERY_PREFIX 0x0001 /* Prefix query */ -#define FTS5INDEX_QUERY_DESC 0x0002 /* Docs in descending rowid order */ -#define FTS5INDEX_QUERY_TEST_NOIDX 0x0004 /* Do not use prefix index */ -#define FTS5INDEX_QUERY_SCAN 0x0008 /* Scan query (fts5vocab) */ +#define FTS5INDEX_QUERY_PREFIX 0x0001 /* Prefix query */ +#define FTS5INDEX_QUERY_DESC 0x0002 /* Docs in descending rowid order */ +#define FTS5INDEX_QUERY_TEST_NOIDX 0x0004 /* Do not use prefix index */ +#define FTS5INDEX_QUERY_SCAN 0x0008 /* Scan query (fts5vocab) */ /* The following are used internally by the fts5_index.c module. They are ** defined here only to make it easier to avoid clashes with the flags ** above. */ -#define FTS5INDEX_QUERY_SKIPEMPTY 0x0010 -#define FTS5INDEX_QUERY_NOOUTPUT 0x0020 -#define FTS5INDEX_QUERY_SKIPHASH 0x0040 -#define FTS5INDEX_QUERY_NOTOKENDATA 0x0080 -#define FTS5INDEX_QUERY_SCANONETERM 0x0100 +#define FTS5INDEX_QUERY_SKIPEMPTY 0x0010 +#define FTS5INDEX_QUERY_NOOUTPUT 0x0020 +#define FTS5INDEX_QUERY_SKIPHASH 0x0040 /* ** Create/destroy an Fts5Index object. */ int sqlite3Fts5IndexOpen(Fts5Config *pConfig, int bCreate, Fts5Index**, char**); @@ -464,14 +461,10 @@ int sqlite3Fts5IterNextScan(Fts5IndexIter*); void *sqlite3Fts5StructureRef(Fts5Index*); void sqlite3Fts5StructureRelease(void*); int sqlite3Fts5StructureTest(Fts5Index*, void*); -/* -** Used by xInstToken(): -*/ -int sqlite3Fts5IterToken(Fts5IndexIter*, i64, int, int, const char**, int*); /* ** Insert or remove data to or from the index. Each time a document is ** added to or removed from the index, this function is called one or more ** times. @@ -545,17 +538,10 @@ int sqlite3Fts5IndexLoadConfig(Fts5Index *p); int sqlite3Fts5IndexGetOrigin(Fts5Index *p, i64 *piOrigin); int sqlite3Fts5IndexContentlessDelete(Fts5Index *p, i64 iOrigin, i64 iRowid); -void sqlite3Fts5IndexIterClearTokendata(Fts5IndexIter*); - -/* Used to populate hash tables for xInstToken in detail=none/column mode. */ -int sqlite3Fts5IndexIterWriteTokendata( - Fts5IndexIter*, const char*, int, i64 iRowid, int iCol, int iOff -); - /* ** End of interface to code in fts5_index.c. **************************************************************************/ /************************************************************************** @@ -657,11 +643,10 @@ ); void sqlite3Fts5HashScanNext(Fts5Hash*); int sqlite3Fts5HashScanEof(Fts5Hash*); void sqlite3Fts5HashScanEntry(Fts5Hash *, const char **pzTerm, /* OUT: term (nul-terminated) */ - int *pnTerm, /* OUT: Size of term in bytes */ const u8 **ppDoclist, /* OUT: pointer to doclist */ int *pnDoclist /* OUT: size of doclist in bytes */ ); @@ -784,14 +769,10 @@ int sqlite3Fts5ExprClonePhrase(Fts5Expr*, int, Fts5Expr**); int sqlite3Fts5ExprPhraseCollist(Fts5Expr *, int, const u8 **, int *); -int sqlite3Fts5ExprQueryToken(Fts5Expr*, int, int, const char**, int*); -int sqlite3Fts5ExprInstToken(Fts5Expr*, i64, int, int, int, int, const char**, int*); -void sqlite3Fts5ExprClearTokens(Fts5Expr*); - /******************************************* ** The fts5_expr.c API above this point is used by the other hand-written ** C code in this module. The interfaces below this point are called by ** the parser code in fts5parse.y. */ Index: ext/fts5/fts5_aux.c ================================================================== --- ext/fts5/fts5_aux.c +++ ext/fts5/fts5_aux.c @@ -209,18 +209,10 @@ rc = fts5CInstIterNext(&p->iter); } } if( iPos==p->iRangeEnd ){ - if( p->bOpen ){ - if( p->iter.iStart>=0 && iPos>=p->iter.iStart ){ - fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff); - p->iOff = iEndOff; - } - fts5HighlightAppend(&rc, p, p->zClose, -1); - p->bOpen = 0; - } fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff); p->iOff = iEndOff; } return rc; @@ -250,14 +242,12 @@ 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( rc==SQLITE_RANGE ){ - sqlite3_result_text(pCtx, "", -1, SQLITE_STATIC); - rc = SQLITE_OK; - }else if( ctx.zIn ){ + + if( ctx.zIn ){ if( rc==SQLITE_OK ){ rc = fts5CInstIterInit(pApi, pFts, iCol, &ctx.iter); } if( rc==SQLITE_OK ){ Index: ext/fts5/fts5_buffer.c ================================================================== --- ext/fts5/fts5_buffer.c +++ ext/fts5/fts5_buffer.c @@ -66,11 +66,10 @@ u32 nData, const u8 *pData ){ if( nData ){ if( fts5BufferGrow(pRc, pBuf, nData) ) return; - assert( pBuf->p!=0 ); memcpy(&pBuf->p[pBuf->n], pData, nData); pBuf->n += nData; } } @@ -168,19 +167,17 @@ const u8 *a, int n, /* Buffer containing poslist */ int *pi, /* IN/OUT: Offset within a[] */ i64 *piOff /* IN/OUT: Current offset */ ){ int i = *pi; - assert( a!=0 || i==0 ); if( i>=n ){ /* EOF */ *piOff = -1; return 1; }else{ i64 iOff = *piOff; u32 iVal; - assert( a!=0 ); fts5FastGetVarint32(a, i, iVal); if( iVal<=1 ){ if( iVal==0 ){ *pi = i; return 0; Index: ext/fts5/fts5_config.c ================================================================== --- ext/fts5/fts5_config.c +++ ext/fts5/fts5_config.c @@ -393,20 +393,10 @@ }; if( (rc = fts5ConfigSetEnum(aDetail, zArg, &pConfig->eDetail)) ){ *pzErr = sqlite3_mprintf("malformed detail=... directive"); } - return rc; - } - - if( sqlite3_strnicmp("tokendata", zCmd, nCmd)==0 ){ - if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){ - *pzErr = sqlite3_mprintf("malformed tokendata=... directive"); - rc = SQLITE_ERROR; - }else{ - pConfig->bTokendata = (zArg[0]=='1'); - } return rc; } *pzErr = sqlite3_mprintf("unrecognized option: \"%.*s\"", nCmd, zCmd); return SQLITE_ERROR; Index: ext/fts5/fts5_expr.c ================================================================== --- ext/fts5/fts5_expr.c +++ ext/fts5/fts5_expr.c @@ -98,13 +98,11 @@ ** or term prefix. */ struct Fts5ExprTerm { u8 bPrefix; /* True for a prefix term */ u8 bFirst; /* True if token must be first in column */ - char *pTerm; /* Term data */ - int nQueryTerm; /* Effective size of term in bytes */ - int nFullTerm; /* Size of term in bytes incl. tokendata */ + char *zTerm; /* nul-terminated term */ Fts5IndexIter *pIter; /* Iterator for this term */ Fts5ExprTerm *pSynonym; /* Pointer to first in list of synonyms */ }; /* @@ -967,11 +965,11 @@ if( p->pIter ){ sqlite3Fts5IterClose(p->pIter); p->pIter = 0; } rc = sqlite3Fts5IndexQuery( - pExpr->pIndex, p->pTerm, p->nQueryTerm, + pExpr->pIndex, p->zTerm, (int)strlen(p->zTerm), (pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0) | (pExpr->bDesc ? FTS5INDEX_QUERY_DESC : 0), pNear->pColset, &p->pIter ); @@ -1604,11 +1602,11 @@ int i; for(i=0; inTerm; i++){ Fts5ExprTerm *pSyn; Fts5ExprTerm *pNext; Fts5ExprTerm *pTerm = &pPhrase->aTerm[i]; - sqlite3_free(pTerm->pTerm); + sqlite3_free(pTerm->zTerm); sqlite3Fts5IterClose(pTerm->pIter); for(pSyn=pTerm->pSynonym; pSyn; pSyn=pNext){ pNext = pSyn->pSynonym; sqlite3Fts5IterClose(pSyn->pIter); fts5BufferFree((Fts5Buffer*)&pSyn[1]); @@ -1702,11 +1700,10 @@ } typedef struct TokenCtx TokenCtx; struct TokenCtx { Fts5ExprPhrase *pPhrase; - Fts5Config *pConfig; int rc; }; /* ** Callback for tokenizing terms used by ParseTerm(). @@ -1736,16 +1733,12 @@ pSyn = (Fts5ExprTerm*)sqlite3_malloc64(nByte); if( pSyn==0 ){ rc = SQLITE_NOMEM; }else{ memset(pSyn, 0, (size_t)nByte); - pSyn->pTerm = ((char*)pSyn) + sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer); - pSyn->nFullTerm = pSyn->nQueryTerm = nToken; - if( pCtx->pConfig->bTokendata ){ - pSyn->nQueryTerm = (int)strlen(pSyn->pTerm); - } - memcpy(pSyn->pTerm, pToken, nToken); + pSyn->zTerm = ((char*)pSyn) + sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer); + memcpy(pSyn->zTerm, pToken, nToken); pSyn->pSynonym = pPhrase->aTerm[pPhrase->nTerm-1].pSynonym; pPhrase->aTerm[pPhrase->nTerm-1].pSynonym = pSyn; } }else{ Fts5ExprTerm *pTerm; @@ -1766,15 +1759,11 @@ } if( rc==SQLITE_OK ){ pTerm = &pPhrase->aTerm[pPhrase->nTerm++]; memset(pTerm, 0, sizeof(Fts5ExprTerm)); - pTerm->pTerm = sqlite3Fts5Strndup(&rc, pToken, nToken); - pTerm->nFullTerm = pTerm->nQueryTerm = nToken; - if( pCtx->pConfig->bTokendata && rc==SQLITE_OK ){ - pTerm->nQueryTerm = (int)strlen(pTerm->pTerm); - } + pTerm->zTerm = sqlite3Fts5Strndup(&rc, pToken, nToken); } } pCtx->rc = rc; return rc; @@ -1837,11 +1826,10 @@ int rc; /* Tokenize return code */ char *z = 0; memset(&sCtx, 0, sizeof(TokenCtx)); sCtx.pPhrase = pAppend; - sCtx.pConfig = pConfig; rc = fts5ParseStringFromToken(pToken, &z); if( rc==SQLITE_OK ){ int flags = FTS5_TOKENIZE_QUERY | (bPrefix ? FTS5_TOKENIZE_PREFIX : 0); int n; @@ -1885,19 +1873,16 @@ Fts5Expr *pExpr, int iPhrase, Fts5Expr **ppNew ){ int rc = SQLITE_OK; /* Return code */ - Fts5ExprPhrase *pOrig = 0; /* The phrase extracted from pExpr */ + Fts5ExprPhrase *pOrig; /* The phrase extracted from pExpr */ Fts5Expr *pNew = 0; /* Expression to return via *ppNew */ - TokenCtx sCtx = {0,0,0}; /* Context object for fts5ParseTokenize */ - if( iPhrase<0 || iPhrase>=pExpr->nPhrase ){ - rc = SQLITE_RANGE; - }else{ - pOrig = pExpr->apExprPhrase[iPhrase]; - pNew = (Fts5Expr*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Expr)); - } + TokenCtx sCtx = {0,0}; /* Context object for fts5ParseTokenize */ + + pOrig = pExpr->apExprPhrase[iPhrase]; + pNew = (Fts5Expr*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Expr)); if( rc==SQLITE_OK ){ pNew->apExprPhrase = (Fts5ExprPhrase**)sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprPhrase*)); } if( rc==SQLITE_OK ){ @@ -1906,11 +1891,11 @@ } if( rc==SQLITE_OK ){ pNew->pRoot->pNear = (Fts5ExprNearset*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprNearset) + sizeof(Fts5ExprPhrase*)); } - if( rc==SQLITE_OK && ALWAYS(pOrig!=0) ){ + if( rc==SQLITE_OK ){ Fts5Colset *pColsetOrig = pOrig->pNode->pNear->pColset; if( pColsetOrig ){ sqlite3_int64 nByte; Fts5Colset *pColset; nByte = sizeof(Fts5Colset) + (pColsetOrig->nCol-1) * sizeof(int); @@ -1920,31 +1905,30 @@ } pNew->pRoot->pNear->pColset = pColset; } } - if( rc==SQLITE_OK ){ - if( pOrig->nTerm ){ - int i; /* Used to iterate through phrase terms */ - sCtx.pConfig = pExpr->pConfig; - for(i=0; rc==SQLITE_OK && inTerm; i++){ - int tflags = 0; - Fts5ExprTerm *p; - for(p=&pOrig->aTerm[i]; p && rc==SQLITE_OK; p=p->pSynonym){ - rc = fts5ParseTokenize((void*)&sCtx,tflags,p->pTerm,p->nFullTerm,0,0); - tflags = FTS5_TOKEN_COLOCATED; - } - if( rc==SQLITE_OK ){ - sCtx.pPhrase->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix; - sCtx.pPhrase->aTerm[i].bFirst = pOrig->aTerm[i].bFirst; - } - } - }else{ - /* This happens when parsing a token or quoted phrase that contains - ** no token characters at all. (e.g ... MATCH '""'). */ - sCtx.pPhrase = sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprPhrase)); - } + if( pOrig->nTerm ){ + int i; /* Used to iterate through phrase terms */ + for(i=0; rc==SQLITE_OK && inTerm; i++){ + int tflags = 0; + Fts5ExprTerm *p; + for(p=&pOrig->aTerm[i]; p && rc==SQLITE_OK; p=p->pSynonym){ + const char *zTerm = p->zTerm; + rc = fts5ParseTokenize((void*)&sCtx, tflags, zTerm, (int)strlen(zTerm), + 0, 0); + tflags = FTS5_TOKEN_COLOCATED; + } + if( rc==SQLITE_OK ){ + sCtx.pPhrase->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix; + sCtx.pPhrase->aTerm[i].bFirst = pOrig->aTerm[i].bFirst; + } + } + }else{ + /* This happens when parsing a token or quoted phrase that contains + ** no token characters at all. (e.g ... MATCH '""'). */ + sCtx.pPhrase = sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprPhrase)); } if( rc==SQLITE_OK && ALWAYS(sCtx.pPhrase) ){ /* All the allocations succeeded. Put the expression object together. */ pNew->pIndex = pExpr->pIndex; @@ -2310,17 +2294,15 @@ ); if( pPhrase ){ if( parseGrowPhraseArray(pParse) ){ fts5ExprPhraseFree(pPhrase); }else{ - Fts5ExprTerm *p = &pNear->apPhrase[0]->aTerm[ii]; - Fts5ExprTerm *pTo = &pPhrase->aTerm[0]; pParse->apPhrase[pParse->nPhrase++] = pPhrase; pPhrase->nTerm = 1; - pTo->pTerm = sqlite3Fts5Strndup(&pParse->rc, p->pTerm, p->nFullTerm); - pTo->nQueryTerm = p->nQueryTerm; - pTo->nFullTerm = p->nFullTerm; + pPhrase->aTerm[0].zTerm = sqlite3Fts5Strndup( + &pParse->rc, pNear->apPhrase[0]->aTerm[ii].zTerm, -1 + ); pRet->apChild[ii] = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, sqlite3Fts5ParseNearset(pParse, 0, pPhrase) ); } } @@ -2501,21 +2483,20 @@ Fts5ExprTerm *p; char *zQuoted; /* Determine the maximum amount of space required. */ for(p=pTerm; p; p=p->pSynonym){ - nByte += pTerm->nQueryTerm * 2 + 3 + 2; + nByte += (int)strlen(pTerm->zTerm) * 2 + 3 + 2; } zQuoted = sqlite3_malloc64(nByte); if( zQuoted ){ int i = 0; for(p=pTerm; p; p=p->pSynonym){ - char *zIn = p->pTerm; - char *zEnd = &zIn[p->nQueryTerm]; + char *zIn = p->zTerm; zQuoted[i++] = '"'; - while( zInpSynonym ) zQuoted[i++] = '|'; @@ -2589,14 +2570,12 @@ for(i=0; inPhrase; i++){ Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; zRet = fts5PrintfAppend(zRet, " {"); for(iTerm=0; zRet && iTermnTerm; iTerm++){ - Fts5ExprTerm *p = &pPhrase->aTerm[iTerm]; - zRet = fts5PrintfAppend(zRet, "%s%.*s", iTerm==0?"":" ", - p->nQueryTerm, p->pTerm - ); + char *zTerm = pPhrase->aTerm[iTerm].zTerm; + zRet = fts5PrintfAppend(zRet, "%s%s", iTerm==0?"":" ", zTerm); if( pPhrase->aTerm[iTerm].bPrefix ){ zRet = fts5PrintfAppend(zRet, "*"); } } @@ -2993,21 +2972,10 @@ if( pColset->aiCol[i]==iCol ) return 1; } return 0; } -/* -** pToken is a buffer nToken bytes in size that may or may not contain -** an embedded 0x00 byte. If it does, return the number of bytes in -** the buffer before the 0x00. If it does not, return nToken. -*/ -static int fts5QueryTerm(const char *pToken, int nToken){ - int ii; - for(ii=0; iipExpr; int i; - int nQuery = nToken; - i64 iRowid = pExpr->pRoot->iRowid; UNUSED_PARAM2(iUnused1, iUnused2); - if( nQuery>FTS5_MAX_TOKEN_SIZE ) nQuery = FTS5_MAX_TOKEN_SIZE; - if( pExpr->pConfig->bTokendata ){ - nQuery = fts5QueryTerm(pToken, nQuery); - } + if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE; if( (tflags & FTS5_TOKEN_COLOCATED)==0 ) p->iOff++; for(i=0; inPhrase; i++){ - Fts5ExprTerm *pT; + Fts5ExprTerm *pTerm; if( p->aPopulator[i].bOk==0 ) continue; - for(pT=&pExpr->apExprPhrase[i]->aTerm[0]; pT; pT=pT->pSynonym){ - if( (pT->nQueryTerm==nQuery || (pT->nQueryTermbPrefix)) - && memcmp(pT->pTerm, pToken, pT->nQueryTerm)==0 + for(pTerm=&pExpr->apExprPhrase[i]->aTerm[0]; pTerm; pTerm=pTerm->pSynonym){ + int nTerm = (int)strlen(pTerm->zTerm); + if( (nTerm==nToken || (nTermbPrefix)) + && memcmp(pTerm->zTerm, pToken, nTerm)==0 ){ int rc = sqlite3Fts5PoslistWriterAppend( &pExpr->apExprPhrase[i]->poslist, &p->aPopulator[i].writer, p->iOff ); - if( rc==SQLITE_OK && pExpr->pConfig->bTokendata && !pT->bPrefix ){ - int iCol = p->iOff>>32; - int iTokOff = p->iOff & 0x7FFFFFFF; - rc = sqlite3Fts5IndexIterWriteTokendata( - pT->pIter, pToken, nToken, iRowid, iCol, iTokOff - ); - } if( rc ) return rc; break; } } } @@ -3176,82 +3133,5 @@ *pnCollist = 0; } return rc; } - -/* -** Does the work of the fts5_api.xQueryToken() API method. -*/ -int sqlite3Fts5ExprQueryToken( - Fts5Expr *pExpr, - int iPhrase, - int iToken, - const char **ppOut, - int *pnOut -){ - Fts5ExprPhrase *pPhrase = 0; - - if( iPhrase<0 || iPhrase>=pExpr->nPhrase ){ - return SQLITE_RANGE; - } - pPhrase = pExpr->apExprPhrase[iPhrase]; - if( iToken<0 || iToken>=pPhrase->nTerm ){ - return SQLITE_RANGE; - } - - *ppOut = pPhrase->aTerm[iToken].pTerm; - *pnOut = pPhrase->aTerm[iToken].nFullTerm; - return SQLITE_OK; -} - -/* -** Does the work of the fts5_api.xInstToken() API method. -*/ -int sqlite3Fts5ExprInstToken( - Fts5Expr *pExpr, - i64 iRowid, - int iPhrase, - int iCol, - int iOff, - int iToken, - const char **ppOut, - int *pnOut -){ - Fts5ExprPhrase *pPhrase = 0; - Fts5ExprTerm *pTerm = 0; - int rc = SQLITE_OK; - - if( iPhrase<0 || iPhrase>=pExpr->nPhrase ){ - return SQLITE_RANGE; - } - pPhrase = pExpr->apExprPhrase[iPhrase]; - if( iToken<0 || iToken>=pPhrase->nTerm ){ - return SQLITE_RANGE; - } - pTerm = &pPhrase->aTerm[iToken]; - if( pTerm->bPrefix==0 ){ - if( pExpr->pConfig->bTokendata ){ - rc = sqlite3Fts5IterToken( - pTerm->pIter, iRowid, iCol, iOff+iToken, ppOut, pnOut - ); - }else{ - *ppOut = pTerm->pTerm; - *pnOut = pTerm->nFullTerm; - } - } - return rc; -} - -/* -** Clear the token mappings for all Fts5IndexIter objects mannaged by -** the expression passed as the only argument. -*/ -void sqlite3Fts5ExprClearTokens(Fts5Expr *pExpr){ - int ii; - for(ii=0; iinPhrase; ii++){ - Fts5ExprTerm *pT; - for(pT=&pExpr->apExprPhrase[ii]->aTerm[0]; pT; pT=pT->pSynonym){ - sqlite3Fts5IndexIterClearTokendata(pT->pIter); - } - } -} Index: ext/fts5/fts5_hash.c ================================================================== --- ext/fts5/fts5_hash.c +++ ext/fts5/fts5_hash.c @@ -34,19 +34,14 @@ Fts5HashEntry **aSlot; /* Array of hash slots */ }; /* ** Each entry in the hash table is represented by an object of the -** following type. Each object, its key, and its current data are stored -** in a single memory allocation. The key immediately follows the object -** in memory. The position list data immediately follows the key data -** in memory. -** -** The key is Fts5HashEntry.nKey bytes in size. It consists of a single -** byte identifying the index (either the main term index or a prefix-index), -** followed by the term data. For example: "0token". There is no -** nul-terminator - in this case nKey=6. +** following type. Each object, its key (a nul-terminated string) and +** its current data are stored in a single memory allocation. The +** key immediately follows the object in memory. The position list +** data immediately follows the key data in memory. ** ** The data that follows the key is in a similar, but not identical format ** to the doclist data stored in the database. It is: ** ** * Rowid, as a varint @@ -177,11 +172,12 @@ for(i=0; inSlot; i++){ while( apOld[i] ){ unsigned int iHash; Fts5HashEntry *p = apOld[i]; apOld[i] = p->pHashNext; - iHash = fts5HashKey(nNew, (u8*)fts5EntryKey(p), p->nKey); + iHash = fts5HashKey(nNew, (u8*)fts5EntryKey(p), + (int)strlen(fts5EntryKey(p))); p->pHashNext = apNew[iHash]; apNew[iHash] = p; } } @@ -261,11 +257,11 @@ /* Attempt to locate an existing hash entry */ iHash = fts5HashKey2(pHash->nSlot, (u8)bByte, (const u8*)pToken, nToken); for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){ char *zKey = fts5EntryKey(p); if( zKey[0]==bByte - && p->nKey==nToken+1 + && p->nKey==nToken && memcmp(&zKey[1], pToken, nToken)==0 ){ break; } } @@ -291,13 +287,13 @@ p->nAlloc = (int)nByte; zKey = fts5EntryKey(p); zKey[0] = bByte; memcpy(&zKey[1], pToken, nToken); assert( iHash==fts5HashKey(pHash->nSlot, (u8*)zKey, nToken+1) ); - p->nKey = nToken+1; + p->nKey = nToken; zKey[nToken+1] = '\0'; - p->nData = nToken+1 + sizeof(Fts5HashEntry); + p->nData = nToken+1 + 1 + sizeof(Fts5HashEntry); p->pHashNext = pHash->aSlot[iHash]; pHash->aSlot[iHash] = p; pHash->nEntry++; /* Add the first rowid field to the hash-entry */ @@ -410,21 +406,16 @@ p2 = 0; }else if( p2==0 ){ *ppOut = p1; p1 = 0; }else{ + int i = 0; char *zKey1 = fts5EntryKey(p1); char *zKey2 = fts5EntryKey(p2); - int nMin = MIN(p1->nKey, p2->nKey); - - int cmp = memcmp(zKey1, zKey2, nMin); - if( cmp==0 ){ - cmp = p1->nKey - p2->nKey; - } - assert( cmp!=0 ); - - if( cmp>0 ){ + while( zKey1[i]==zKey2[i] ) i++; + + if( ((u8)zKey1[i])>((u8)zKey2[i]) ){ /* p2 is smaller */ *ppOut = p2; ppOut = &p2->pScanNext; p2 = p2->pScanNext; }else{ @@ -462,11 +453,11 @@ for(iSlot=0; iSlotnSlot; iSlot++){ Fts5HashEntry *pIter; for(pIter=pHash->aSlot[iSlot]; pIter; pIter=pIter->pHashNext){ if( pTerm==0 - || (pIter->nKey>=nTerm && 0==memcmp(fts5EntryKey(pIter), pTerm, nTerm)) + || (pIter->nKey+1>=nTerm && 0==memcmp(fts5EntryKey(pIter), pTerm, nTerm)) ){ Fts5HashEntry *pEntry = pIter; pEntry->pScanNext = 0; for(i=0; ap[i]; i++){ pEntry = fts5HashEntryMerge(pEntry, ap[i]); @@ -501,15 +492,16 @@ char *zKey = 0; Fts5HashEntry *p; for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){ zKey = fts5EntryKey(p); - if( nTerm==p->nKey && memcmp(zKey, pTerm, nTerm)==0 ) break; + assert( p->nKey+1==(int)strlen(zKey) ); + if( nTerm==p->nKey+1 && memcmp(zKey, pTerm, nTerm)==0 ) break; } if( p ){ - int nHashPre = sizeof(Fts5HashEntry) + nTerm; + int nHashPre = sizeof(Fts5HashEntry) + nTerm + 1; int nList = p->nData - nHashPre; u8 *pRet = (u8*)(*ppOut = sqlite3_malloc64(nPre + nList + 10)); if( pRet ){ Fts5HashEntry *pFaux = (Fts5HashEntry*)&pRet[nPre-nHashPre]; memcpy(&pRet[nPre], &((u8*)p)[nHashPre], nList); @@ -566,25 +558,22 @@ } void sqlite3Fts5HashScanEntry( Fts5Hash *pHash, const char **pzTerm, /* OUT: term (nul-terminated) */ - int *pnTerm, /* OUT: Size of term in bytes */ const u8 **ppDoclist, /* OUT: pointer to doclist */ int *pnDoclist /* OUT: size of doclist in bytes */ ){ Fts5HashEntry *p; if( (p = pHash->pScan) ){ char *zKey = fts5EntryKey(p); - int nTerm = p->nKey; + int nTerm = (int)strlen(zKey); fts5HashAddPoslistSize(pHash, p, 0); *pzTerm = zKey; - *pnTerm = nTerm; - *ppDoclist = (const u8*)&zKey[nTerm]; - *pnDoclist = p->nData - (sizeof(Fts5HashEntry) + nTerm); + *ppDoclist = (const u8*)&zKey[nTerm+1]; + *pnDoclist = p->nData - (sizeof(Fts5HashEntry) + nTerm + 1); }else{ *pzTerm = 0; - *pnTerm = 0; *ppDoclist = 0; *pnDoclist = 0; } } Index: ext/fts5/fts5_index.c ================================================================== --- ext/fts5/fts5_index.c +++ ext/fts5/fts5_index.c @@ -321,13 +321,10 @@ typedef struct Fts5DoclistIter Fts5DoclistIter; typedef struct Fts5SegWriter Fts5SegWriter; typedef struct Fts5Structure Fts5Structure; typedef struct Fts5StructureLevel Fts5StructureLevel; typedef struct Fts5StructureSegment Fts5StructureSegment; -typedef struct Fts5TokenDataIter Fts5TokenDataIter; -typedef struct Fts5TokenDataMap Fts5TokenDataMap; -typedef struct Fts5TombstoneArray Fts5TombstoneArray; struct Fts5Data { u8 *p; /* Pointer to buffer containing record */ int nn; /* Size of record in bytes */ int szLeaf; /* Size of leaf without page-index */ @@ -358,20 +355,18 @@ int nContentlessDelete; /* Number of contentless delete ops */ int nPendingRow; /* Number of INSERT in hash table */ /* Error state. */ int rc; /* Current error code */ - int flushRc; /* State used by the fts5DataXXX() functions. */ sqlite3_blob *pReader; /* RO incr-blob open on %_data table */ sqlite3_stmt *pWriter; /* "INSERT ... %_data VALUES(?,?)" */ sqlite3_stmt *pDeleter; /* "DELETE FROM %_data ... id>=? AND id<=?" */ sqlite3_stmt *pIdxWriter; /* "INSERT ... %_idx VALUES(?,?,?,?)" */ sqlite3_stmt *pIdxDeleter; /* "DELETE FROM %_idx WHERE segid=?" */ sqlite3_stmt *pIdxSelect; - sqlite3_stmt *pIdxNextSelect; int nRead; /* Total number of blocks read */ sqlite3_stmt *pDeleteFromIdx; sqlite3_stmt *pDataVersion; @@ -521,11 +516,12 @@ int flags; /* Mask of configuration flags */ int iLeafPgno; /* Current leaf page number */ Fts5Data *pLeaf; /* Current leaf data */ Fts5Data *pNextLeaf; /* Leaf page (iLeafPgno+1) */ i64 iLeafOffset; /* Byte offset within current leaf */ - Fts5TombstoneArray *pTombArray; /* Array of tombstone pages */ + Fts5Data **apTombstone; /* Array of tombstone pages */ + int nTombstone; /* Next method */ void (*xNext)(Fts5Index*, Fts5SegIter*, int*); /* The page and offset from which the current term was read. The offset @@ -548,19 +544,10 @@ i64 iRowid; /* Current rowid */ int nPos; /* Number of bytes in current position list */ u8 bDel; /* True if the delete flag is set */ }; -/* -** Array of tombstone pages. Reference counted. -*/ -struct Fts5TombstoneArray { - int nRef; /* Number of pointers to this object */ - int nTombstone; - Fts5Data *apTombstone[1]; /* Array of tombstone pages */ -}; - /* ** Argument is a pointer to an Fts5Data structure that contains a ** leaf page. */ #define ASSERT_SZLEAF_OK(x) assert( \ @@ -601,20 +588,13 @@ ** the smallest key overall. aFirst[0] is unused. ** ** poslist: ** Used by sqlite3Fts5IterPoslist() when the poslist needs to be buffered. ** There is no way to tell if this is populated or not. -** -** pColset: -** If not NULL, points to an object containing a set of column indices. -** Only matches that occur in one of these columns will be returned. -** The Fts5Iter does not own the Fts5Colset object, and so it is not -** freed when the iterator is closed - it is owned by the upper layer. */ struct Fts5Iter { Fts5IndexIter base; /* Base class containing output vars */ - Fts5TokenDataIter *pTokenDataIter; Fts5Index *pIndex; /* Index that owns this iterator */ Fts5Buffer poslist; /* Buffer containing current poslist */ Fts5Colset *pColset; /* Restrict matches to these columns */ @@ -627,10 +607,11 @@ i64 iSwitchRowid; /* Firstest rowid of other than aFirst[1] */ Fts5CResult *aFirst; /* Current merge state (see above) */ Fts5SegIter aSeg[1]; /* Array of segment iterators */ }; + /* ** An instance of the following type is used to iterate through the contents ** of a doclist-index record. ** @@ -1545,13 +1526,13 @@ for(iOff=pLvl->iOff; iOffnn; iOff++){ if( pData->p[iOff] ) break; } if( iOffnn ){ - u64 iVal; + i64 iVal; pLvl->iLeafPgno += (iOff - pLvl->iOff) + 1; - iOff += fts5GetVarint(&pData->p[iOff], &iVal); + iOff += fts5GetVarint(&pData->p[iOff], (u64*)&iVal); pLvl->iRowid += iVal; pLvl->iOff = iOff; }else{ pLvl->bEof = 1; } @@ -1926,24 +1907,22 @@ pIter->xNext = fts5SegIterNext; } } /* -** Allocate a tombstone hash page array object (pIter->pTombArray) for -** the iterator passed as the second argument. If an OOM error occurs, -** leave an error in the Fts5Index object. +** 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 ){ - int nByte = nTomb * sizeof(Fts5Data*) + sizeof(Fts5TombstoneArray); - Fts5TombstoneArray *pNew; - pNew = (Fts5TombstoneArray*)sqlite3Fts5MallocZero(&p->rc, nByte); - if( pNew ){ - pNew->nTombstone = nTomb; - pNew->nRef = 1; - pIter->pTombArray = pNew; + Fts5Data **apTomb = 0; + apTomb = (Fts5Data**)sqlite3Fts5MallocZero(&p->rc, sizeof(Fts5Data)*nTomb); + if( apTomb ){ + pIter->apTombstone = apTomb; + pIter->nTombstone = nTomb; } } } /* @@ -2196,20 +2175,19 @@ pIter->iLeafOffset = iOff; fts5SegIterLoadTerm(p, pIter, nKeep); }else{ const u8 *pList = 0; const char *zTerm = 0; - int nTerm = 0; int nList; sqlite3Fts5HashScanNext(p->pHash); - sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &nTerm, &pList, &nList); + sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &pList, &nList); if( pList==0 ) goto next_none_eof; pIter->pLeaf->p = (u8*)pList; pIter->pLeaf->nn = nList; pIter->pLeaf->szLeaf = nList; pIter->iEndofDoclist = nList; - sqlite3Fts5BufferSet(&p->rc,&pIter->term, nTerm, (u8*)zTerm); + sqlite3Fts5BufferSet(&p->rc,&pIter->term, (int)strlen(zTerm), (u8*)zTerm); pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid); } if( pbNewTerm ) *pbNewTerm = 1; }else{ @@ -2271,26 +2249,26 @@ pIter->iLeafOffset = iOff; }else if( pIter->pSeg==0 ){ const u8 *pList = 0; const char *zTerm = 0; - int nTerm = 0; int nList = 0; assert( (pIter->flags & FTS5_SEGITER_ONETERM) || pbNewTerm ); if( 0==(pIter->flags & FTS5_SEGITER_ONETERM) ){ sqlite3Fts5HashScanNext(p->pHash); - sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &nTerm, &pList, &nList); + sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &pList, &nList); } if( pList==0 ){ fts5DataRelease(pIter->pLeaf); pIter->pLeaf = 0; }else{ pIter->pLeaf->p = (u8*)pList; pIter->pLeaf->nn = nList; pIter->pLeaf->szLeaf = nList; pIter->iEndofDoclist = nList+1; - sqlite3Fts5BufferSet(&p->rc, &pIter->term, nTerm, (u8*)zTerm); + sqlite3Fts5BufferSet(&p->rc, &pIter->term, (int)strlen(zTerm), + (u8*)zTerm); pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid); *pbNewTerm = 1; } }else{ iOff = 0; @@ -2672,11 +2650,11 @@ if( pIter->pLeaf ){ fts5LeafSeek(p, bGe, pIter, pTerm, nTerm); } - if( p->rc==SQLITE_OK && (bGe==0 || (flags & FTS5INDEX_QUERY_SCANONETERM)) ){ + if( p->rc==SQLITE_OK && bGe==0 ){ pIter->flags |= FTS5_SEGITER_ONETERM; if( pIter->pLeaf ){ if( flags & FTS5INDEX_QUERY_DESC ){ pIter->flags |= FTS5_SEGITER_REVERSE; } @@ -2688,13 +2666,11 @@ } } } fts5SegIterSetNext(p, pIter); - if( 0==(flags & FTS5INDEX_QUERY_SCANONETERM) ){ - fts5SegIterAllocTombstone(p, pIter); - } + fts5SegIterAllocTombstone(p, pIter); /* Either: ** ** 1) an error has occurred, or ** 2) the iterator points to EOF, or @@ -2707,83 +2683,10 @@ || fts5BufferCompareBlob(&pIter->term, pTerm, nTerm)==0 /* 3 */ || (bGe && fts5BufferCompareBlob(&pIter->term, pTerm, nTerm)>0) /* 4 */ ); } - -/* -** SQL used by fts5SegIterNextInit() to find the page to open. -*/ -static sqlite3_stmt *fts5IdxNextStmt(Fts5Index *p){ - if( p->pIdxNextSelect==0 ){ - Fts5Config *pConfig = p->pConfig; - fts5IndexPrepareStmt(p, &p->pIdxNextSelect, sqlite3_mprintf( - "SELECT pgno FROM '%q'.'%q_idx' WHERE " - "segid=? AND term>? ORDER BY term ASC LIMIT 1", - pConfig->zDb, pConfig->zName - )); - - } - return p->pIdxNextSelect; -} - -/* -** This is similar to fts5SegIterSeekInit(), except that it initializes -** the segment iterator to point to the first term following the page -** with pToken/nToken on it. -*/ -static void fts5SegIterNextInit( - Fts5Index *p, - const char *pTerm, int nTerm, - Fts5StructureSegment *pSeg, /* Description of segment */ - Fts5SegIter *pIter /* Object to populate */ -){ - int iPg = -1; /* Page of segment to open */ - int bDlidx = 0; - sqlite3_stmt *pSel = 0; /* SELECT to find iPg */ - - pSel = fts5IdxNextStmt(p); - if( pSel ){ - assert( p->rc==SQLITE_OK ); - sqlite3_bind_int(pSel, 1, pSeg->iSegid); - sqlite3_bind_blob(pSel, 2, pTerm, nTerm, SQLITE_STATIC); - - if( sqlite3_step(pSel)==SQLITE_ROW ){ - i64 val = sqlite3_column_int64(pSel, 0); - iPg = (int)(val>>1); - bDlidx = (val & 0x0001); - } - p->rc = sqlite3_reset(pSel); - sqlite3_bind_null(pSel, 2); - if( p->rc ) return; - } - - memset(pIter, 0, sizeof(*pIter)); - pIter->pSeg = pSeg; - pIter->flags |= FTS5_SEGITER_ONETERM; - if( iPg>=0 ){ - pIter->iLeafPgno = iPg - 1; - fts5SegIterNextPage(p, pIter); - fts5SegIterSetNext(p, pIter); - } - if( pIter->pLeaf ){ - const u8 *a = pIter->pLeaf->p; - int iTermOff = 0; - - pIter->iPgidxOff = pIter->pLeaf->szLeaf; - pIter->iPgidxOff += fts5GetVarint32(&a[pIter->iPgidxOff], iTermOff); - pIter->iLeafOffset = iTermOff; - fts5SegIterLoadTerm(p, pIter, 0); - fts5SegIterLoadNPos(p, pIter); - if( bDlidx ) fts5SegIterLoadDlidx(p, pIter); - - assert( p->rc!=SQLITE_OK || - fts5BufferCompareBlob(&pIter->term, (const u8*)pTerm, nTerm)>0 - ); - } -} - /* ** Initialize the object pIter to point to term pTerm/nTerm within the ** in-memory hash table. If there is no such term in the hash-table, the ** iterator is set to EOF. ** @@ -2806,11 +2709,12 @@ if( pTerm==0 || (flags & FTS5INDEX_QUERY_SCAN) ){ const u8 *pList = 0; p->rc = sqlite3Fts5HashScanInit(p->pHash, (const char*)pTerm, nTerm); - sqlite3Fts5HashScanEntry(p->pHash, (const char**)&z, &n, &pList, &nList); + sqlite3Fts5HashScanEntry(p->pHash, (const char**)&z, &pList, &nList); + n = (z ? (int)strlen((const char*)z) : 0); if( pList ){ pLeaf = fts5IdxMalloc(p, sizeof(Fts5Data)); if( pLeaf ){ pLeaf->p = (u8*)pList; } @@ -2865,35 +2769,18 @@ } sqlite3_free(ap); } } -/* -** Decrement the ref-count of the object passed as the only argument. If it -** reaches 0, free it and its contents. -*/ -static void fts5TombstoneArrayDelete(Fts5TombstoneArray *p){ - if( p ){ - p->nRef--; - if( p->nRef<=0 ){ - int ii; - for(ii=0; iinTombstone; ii++){ - fts5DataRelease(p->apTombstone[ii]); - } - sqlite3_free(p); - } - } -} - /* ** Zero the iterator passed as the only argument. */ static void fts5SegIterClear(Fts5SegIter *pIter){ fts5BufferFree(&pIter->term); fts5DataRelease(pIter->pLeaf); fts5DataRelease(pIter->pNextLeaf); - fts5TombstoneArrayDelete(pIter->pTombArray); + fts5IndexFreeArray(pIter->apTombstone, pIter->nTombstone); fts5DlidxIterFree(pIter->pDlidx); sqlite3_free(pIter->aRowidOffset); memset(pIter, 0, sizeof(Fts5SegIter)); } @@ -3133,10 +3020,11 @@ if( bRev==0 && pIter->iRowid>=iMatch ) break; if( bRev!=0 && pIter->iRowid<=iMatch ) break; bMove = 1; }while( p->rc==SQLITE_OK ); } + /* ** Free the iterator object passed as the second argument. */ static void fts5MultiIterFree(Fts5Iter *pIter){ @@ -3278,29 +3166,28 @@ ** 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]; - Fts5TombstoneArray *pArray = pSeg->pTombArray; - if( pSeg->pLeaf && pArray ){ + if( pSeg->pLeaf && pSeg->nTombstone ){ /* Figure out which page the rowid might be present on. */ - int iPg = ((u64)pSeg->iRowid) % pArray->nTombstone; + 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( pArray->apTombstone[iPg]==0 ){ - pArray->apTombstone[iPg] = fts5DataRead(pIter->pIndex, + if( pSeg->apTombstone[iPg]==0 ){ + pSeg->apTombstone[iPg] = fts5DataRead(pIter->pIndex, FTS5_TOMBSTONE_ROWID(pSeg->pSeg->iSegid, iPg) ); - if( pArray->apTombstone[iPg]==0 ) return 0; + if( pSeg->apTombstone[iPg]==0 ) return 0; } return fts5IndexTombstoneQuery( - pArray->apTombstone[iPg], - pArray->nTombstone, + pSeg->apTombstone[iPg], + pSeg->nTombstone, pSeg->iRowid ); } return 0; @@ -3835,36 +3722,10 @@ } } } } -/* -** All the component segment-iterators of pIter have been set up. This -** functions finishes setup for iterator pIter itself. -*/ -static void fts5MultiIterFinishSetup(Fts5Index *p, Fts5Iter *pIter){ - int iIter; - for(iIter=pIter->nSeg-1; iIter>0; iIter--){ - int iEq; - if( (iEq = fts5MultiIterDoCompare(pIter, iIter)) ){ - Fts5SegIter *pSeg = &pIter->aSeg[iEq]; - if( p->rc==SQLITE_OK ) pSeg->xNext(p, pSeg, 0); - fts5MultiIterAdvanced(p, pIter, iEq, iIter); - } - } - fts5MultiIterSetEof(pIter); - fts5AssertMultiIterSetup(p, pIter); - - if( (pIter->bSkipEmpty && fts5MultiIterIsEmpty(p, pIter)) - || fts5MultiIterIsDeleted(pIter) - ){ - fts5MultiIterNext(p, pIter, 0, 0); - }else if( pIter->base.bEof==0 ){ - Fts5SegIter *pSeg = &pIter->aSeg[pIter->aFirst[1].iFirst]; - pIter->xSetOutputs(pIter, pSeg); - } -} /* ** Allocate a new Fts5Iter object. ** ** The new object will be used to iterate through data in structure pStruct. @@ -3942,16 +3803,35 @@ } } assert( iIter==nSeg ); } - /* If the above was successful, each component iterator now points + /* If the above was successful, each component iterators now points ** to the first entry in its segment. In this case initialize the ** aFirst[] array. Or, if an error has occurred, free the iterator ** object and set the output variable to NULL. */ if( p->rc==SQLITE_OK ){ - fts5MultiIterFinishSetup(p, pNew); + for(iIter=pNew->nSeg-1; iIter>0; iIter--){ + int iEq; + if( (iEq = fts5MultiIterDoCompare(pNew, iIter)) ){ + Fts5SegIter *pSeg = &pNew->aSeg[iEq]; + if( p->rc==SQLITE_OK ) pSeg->xNext(p, pSeg, 0); + fts5MultiIterAdvanced(p, pNew, iEq, iIter); + } + } + fts5MultiIterSetEof(pNew); + fts5AssertMultiIterSetup(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]; + pNew->xSetOutputs(pNew, pSeg); + } + }else{ fts5MultiIterFree(pNew); *ppOut = 0; } @@ -3972,10 +3852,11 @@ ){ Fts5Iter *pNew; pNew = fts5MultiIterAlloc(p, 2); if( pNew ){ Fts5SegIter *pIter = &pNew->aSeg[1]; + pIter->flags = FTS5_SEGITER_ONETERM; if( pData->szLeaf>0 ){ pIter->pLeaf = pData; pIter->iLeafOffset = fts5GetVarint(pData->p, (u64*)&pIter->iRowid); pIter->iEndofDoclist = pData->nn; @@ -4119,11 +4000,10 @@ assert( p->pHash || p->nPendingData==0 ); if( p->pHash ){ sqlite3Fts5HashClear(p->pHash); p->nPendingData = 0; p->nPendingRow = 0; - p->flushRc = SQLITE_OK; } p->nContentlessDelete = 0; } /* @@ -4335,11 +4215,11 @@ }else{ bDone = 1; } if( pDlidx->bPrevValid ){ - iVal = (u64)iRowid - (u64)pDlidx->iPrev; + iVal = iRowid - pDlidx->iPrev; }else{ i64 iPgno = (i==0 ? pWriter->writer.pgno : pDlidx[-1].pgno); assert( pDlidx->buf.n==0 ); sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx->buf, !bDone); sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx->buf, iPgno); @@ -5255,28 +5135,22 @@ } } iOff = iStart; - /* If the position-list for the entry being removed flows over past - ** the end of this page, delete the portion of the position-list on the - ** next page and beyond. - ** - ** Set variable bLastInDoclist to true if this entry happens - ** to be the last rowid in the doclist for its term. */ - if( iNextOff>=iPgIdx ){ - int pgno = pSeg->iLeafPgno+1; - fts5SecureDeleteOverflow(p, pSeg->pSeg, pgno, &bLastInDoclist); - iNextOff = iPgIdx; - } - + /* 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 ){ + 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. */ + ** in its doclist. */ int iKeyOff = 0; for(iIdx=0; iIdxrc!=SQLITE_OK ) break; assert( writer.bFirstRowidInPage==0 ); } @@ -5565,21 +5440,21 @@ ** in fact a delete, then edit the existing segments directly ** using fts5FlushSecureDelete(). */ if( bSecureDelete ){ if( eDetail==FTS5_DETAIL_NONE ){ if( iOffrc!=SQLITE_OK || pDoclist[iOff]==0x01 ){ iOff++; continue; } } @@ -5701,24 +5576,18 @@ /* ** Flush any data stored in the in-memory hash tables to the database. */ static void fts5IndexFlush(Fts5Index *p){ /* Unless it is empty, flush the hash table to disk */ - if( p->flushRc ){ - p->rc = p->flushRc; - return; - } if( p->nPendingData || p->nContentlessDelete ){ assert( p->pHash ); fts5FlushOneHash(p); if( p->rc==SQLITE_OK ){ sqlite3Fts5HashClear(p->pHash); p->nPendingData = 0; p->nPendingRow = 0; p->nContentlessDelete = 0; - }else if( p->nPendingData || p->nContentlessDelete ){ - p->flushRc = p->rc; } } } static Fts5Structure *fts5IndexOptimizeStruct( @@ -6201,11 +6070,11 @@ int bDesc, /* True for "ORDER BY rowid DESC" */ int iIdx, /* Index to scan for data */ u8 *pToken, /* Buffer containing prefix to match */ int nToken, /* Size of buffer pToken in bytes */ Fts5Colset *pColset, /* Restrict matches to these columns */ - Fts5Iter **ppIter /* OUT: New iterator */ + Fts5Iter **ppIter /* OUT: New iterator */ ){ Fts5Structure *pStruct; Fts5Buffer *aBuf; int nBuf = 32; int nMerge = 1; @@ -6222,13 +6091,12 @@ xAppend = fts5AppendPoslist; } aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof(Fts5Buffer)*nBuf); pStruct = fts5StructureRead(p); - assert( p->rc!=SQLITE_OK || (aBuf && pStruct) ); - if( p->rc==SQLITE_OK ){ + if( aBuf && pStruct ){ const int flags = FTS5INDEX_QUERY_SCAN | FTS5INDEX_QUERY_SKIPEMPTY | FTS5INDEX_QUERY_NOOUTPUT; int i; i64 iLastRowid = 0; @@ -6236,16 +6104,10 @@ Fts5Data *pData; Fts5Buffer doclist; int bNewTerm = 1; memset(&doclist, 0, sizeof(doclist)); - - /* If iIdx is non-zero, then it is the number of a prefix-index for - ** prefixes 1 character longer than the prefix being queried for. That - ** index contains all the doclists required, except for the one - ** corresponding to the prefix itself. That one is extracted from the - ** main term index here. */ if( iIdx!=0 ){ int dummy = 0; const int f2 = FTS5INDEX_QUERY_SKIPEMPTY|FTS5INDEX_QUERY_NOOUTPUT; pToken[0] = FTS5_MAIN_PREFIX; fts5MultiIterNew(p, pStruct, f2, pColset, pToken, nToken, -1, 0, &p1); @@ -6265,11 +6127,10 @@ } pToken[0] = FTS5_MAIN_PREFIX + iIdx; fts5MultiIterNew(p, pStruct, flags, pColset, pToken, nToken, -1, 0, &p1); fts5IterSetOutputCb(&p->rc, p1); - for( /* no-op */ ; fts5MultiIterEof(p, p1)==0; fts5MultiIterNext2(p, p1, &bNewTerm) ){ Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ]; @@ -6281,10 +6142,11 @@ if( bNewTerm ){ if( nTermbase.nData==0 ) continue; + if( p1->base.iRowid<=iLastRowid && doclist.n>0 ){ for(i=0; p->rc==SQLITE_OK && doclist.n; i++){ int i1 = i*nMerge; int iStore; assert( i1+nMerge<=nBuf ); @@ -6319,11 +6181,11 @@ fts5BufferFree(&aBuf[iFree]); } } fts5MultiIterFree(p1); - pData = fts5IdxMalloc(p, sizeof(*pData)+doclist.n+FTS5_DATA_ZERO_PADDING); + pData = fts5IdxMalloc(p, sizeof(Fts5Data)+doclist.n+FTS5_DATA_ZERO_PADDING); if( pData ){ pData->p = (u8*)&pData[1]; pData->nn = pData->szLeaf = doclist.n; if( doclist.n ) memcpy(pData->p, doclist.p, doclist.n); fts5MultiIterNew2(p, pData, bDesc, ppIter); @@ -6462,11 +6324,10 @@ sqlite3_finalize(p->pWriter); sqlite3_finalize(p->pDeleter); sqlite3_finalize(p->pIdxWriter); sqlite3_finalize(p->pIdxDeleter); sqlite3_finalize(p->pIdxSelect); - sqlite3_finalize(p->pIdxNextSelect); sqlite3_finalize(p->pDataVersion); sqlite3_finalize(p->pDeleteFromIdx); sqlite3Fts5HashFree(p->pHash); sqlite3_free(p->zDataTbl); sqlite3_free(p); @@ -6557,461 +6418,10 @@ } } return rc; } - -/* -** pToken points to a buffer of size nToken bytes containing a search -** term, including the index number at the start, used on a tokendata=1 -** table. This function returns true if the term in buffer pBuf matches -** token pToken/nToken. -*/ -static int fts5IsTokendataPrefix( - Fts5Buffer *pBuf, - const u8 *pToken, - int nToken -){ - return ( - pBuf->n>=nToken - && 0==memcmp(pBuf->p, pToken, nToken) - && (pBuf->n==nToken || pBuf->p[nToken]==0x00) - ); -} - -/* -** Ensure the segment-iterator passed as the only argument points to EOF. -*/ -static void fts5SegIterSetEOF(Fts5SegIter *pSeg){ - fts5DataRelease(pSeg->pLeaf); - pSeg->pLeaf = 0; -} - -/* -** Usually, a tokendata=1 iterator (struct Fts5TokenDataIter) accumulates an -** array of these for each row it visits. Or, for an iterator used by an -** "ORDER BY rank" query, it accumulates an array of these for the entire -** query. -** -** Each instance in the array indicates the iterator (and therefore term) -** associated with position iPos of rowid iRowid. This is used by the -** xInstToken() API. -*/ -struct Fts5TokenDataMap { - i64 iRowid; /* Row this token is located in */ - i64 iPos; /* Position of token */ - int iIter; /* Iterator token was read from */ -}; - -/* -** An object used to supplement Fts5Iter for tokendata=1 iterators. -*/ -struct Fts5TokenDataIter { - int nIter; - int nIterAlloc; - - int nMap; - int nMapAlloc; - Fts5TokenDataMap *aMap; - - Fts5PoslistReader *aPoslistReader; - int *aPoslistToIter; - Fts5Iter *apIter[1]; -}; - -/* -** This function appends iterator pAppend to Fts5TokenDataIter pIn and -** returns the result. -*/ -static Fts5TokenDataIter *fts5AppendTokendataIter( - Fts5Index *p, /* Index object (for error code) */ - Fts5TokenDataIter *pIn, /* Current Fts5TokenDataIter struct */ - Fts5Iter *pAppend /* Append this iterator */ -){ - Fts5TokenDataIter *pRet = pIn; - - if( p->rc==SQLITE_OK ){ - if( pIn==0 || pIn->nIter==pIn->nIterAlloc ){ - int nAlloc = pIn ? pIn->nIterAlloc*2 : 16; - int nByte = nAlloc * sizeof(Fts5Iter*) + sizeof(Fts5TokenDataIter); - Fts5TokenDataIter *pNew = (Fts5TokenDataIter*)sqlite3_realloc(pIn, nByte); - - if( pNew==0 ){ - p->rc = SQLITE_NOMEM; - }else{ - if( pIn==0 ) memset(pNew, 0, nByte); - pRet = pNew; - pNew->nIterAlloc = nAlloc; - } - } - } - if( p->rc ){ - sqlite3Fts5IterClose((Fts5IndexIter*)pAppend); - }else{ - pRet->apIter[pRet->nIter++] = pAppend; - } - assert( pRet==0 || pRet->nIter<=pRet->nIterAlloc ); - - return pRet; -} - -/* -** Delete an Fts5TokenDataIter structure and its contents. -*/ -static void fts5TokendataIterDelete(Fts5TokenDataIter *pSet){ - if( pSet ){ - int ii; - for(ii=0; iinIter; ii++){ - fts5MultiIterFree(pSet->apIter[ii]); - } - sqlite3_free(pSet->aPoslistReader); - sqlite3_free(pSet->aMap); - sqlite3_free(pSet); - } -} - -/* -** Append a mapping to the token-map belonging to object pT. -*/ -static void fts5TokendataIterAppendMap( - Fts5Index *p, - Fts5TokenDataIter *pT, - int iIter, - i64 iRowid, - i64 iPos -){ - if( p->rc==SQLITE_OK ){ - if( pT->nMap==pT->nMapAlloc ){ - int nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64; - int nByte = nNew * sizeof(Fts5TokenDataMap); - Fts5TokenDataMap *aNew; - - aNew = (Fts5TokenDataMap*)sqlite3_realloc(pT->aMap, nByte); - if( aNew==0 ){ - p->rc = SQLITE_NOMEM; - return; - } - - pT->aMap = aNew; - pT->nMapAlloc = nNew; - } - - pT->aMap[pT->nMap].iRowid = iRowid; - pT->aMap[pT->nMap].iPos = iPos; - pT->aMap[pT->nMap].iIter = iIter; - pT->nMap++; - } -} - -/* -** The iterator passed as the only argument must be a tokendata=1 iterator -** (pIter->pTokenDataIter!=0). This function sets the iterator output -** variables (pIter->base.*) according to the contents of the current -** row. -*/ -static void fts5IterSetOutputsTokendata(Fts5Iter *pIter){ - int ii; - int nHit = 0; - i64 iRowid = SMALLEST_INT64; - int iMin = 0; - - Fts5TokenDataIter *pT = pIter->pTokenDataIter; - - pIter->base.nData = 0; - pIter->base.pData = 0; - - for(ii=0; iinIter; ii++){ - Fts5Iter *p = pT->apIter[ii]; - if( p->base.bEof==0 ){ - if( nHit==0 || p->base.iRowidbase.iRowid; - nHit = 1; - pIter->base.pData = p->base.pData; - pIter->base.nData = p->base.nData; - iMin = ii; - }else if( p->base.iRowid==iRowid ){ - nHit++; - } - } - } - - if( nHit==0 ){ - pIter->base.bEof = 1; - }else{ - int eDetail = pIter->pIndex->pConfig->eDetail; - pIter->base.bEof = 0; - pIter->base.iRowid = iRowid; - - if( nHit==1 && eDetail==FTS5_DETAIL_FULL ){ - fts5TokendataIterAppendMap(pIter->pIndex, pT, iMin, iRowid, -1); - }else - if( nHit>1 && eDetail!=FTS5_DETAIL_NONE ){ - int nReader = 0; - int nByte = 0; - i64 iPrev = 0; - - /* Allocate array of iterators if they are not already allocated. */ - if( pT->aPoslistReader==0 ){ - pT->aPoslistReader = (Fts5PoslistReader*)sqlite3Fts5MallocZero( - &pIter->pIndex->rc, - pT->nIter * (sizeof(Fts5PoslistReader) + sizeof(int)) - ); - if( pT->aPoslistReader==0 ) return; - pT->aPoslistToIter = (int*)&pT->aPoslistReader[pT->nIter]; - } - - /* Populate an iterator for each poslist that will be merged */ - for(ii=0; iinIter; ii++){ - Fts5Iter *p = pT->apIter[ii]; - if( iRowid==p->base.iRowid ){ - pT->aPoslistToIter[nReader] = ii; - sqlite3Fts5PoslistReaderInit( - p->base.pData, p->base.nData, &pT->aPoslistReader[nReader++] - ); - nByte += p->base.nData; - } - } - - /* Ensure the output buffer is large enough */ - if( fts5BufferGrow(&pIter->pIndex->rc, &pIter->poslist, nByte+nHit*10) ){ - return; - } - - /* Ensure the token-mapping is large enough */ - if( eDetail==FTS5_DETAIL_FULL && pT->nMapAlloc<(pT->nMap + nByte) ){ - int nNew = (pT->nMapAlloc + nByte) * 2; - Fts5TokenDataMap *aNew = (Fts5TokenDataMap*)sqlite3_realloc( - pT->aMap, nNew*sizeof(Fts5TokenDataMap) - ); - if( aNew==0 ){ - pIter->pIndex->rc = SQLITE_NOMEM; - return; - } - pT->aMap = aNew; - pT->nMapAlloc = nNew; - } - - pIter->poslist.n = 0; - - while( 1 ){ - i64 iMinPos = LARGEST_INT64; - - /* Find smallest position */ - iMin = 0; - for(ii=0; iiaPoslistReader[ii]; - if( pReader->bEof==0 ){ - if( pReader->iPosiPos; - iMin = ii; - } - } - } - - /* If all readers were at EOF, break out of the loop. */ - if( iMinPos==LARGEST_INT64 ) break; - - sqlite3Fts5PoslistSafeAppend(&pIter->poslist, &iPrev, iMinPos); - sqlite3Fts5PoslistReaderNext(&pT->aPoslistReader[iMin]); - - if( eDetail==FTS5_DETAIL_FULL ){ - pT->aMap[pT->nMap].iPos = iMinPos; - pT->aMap[pT->nMap].iIter = pT->aPoslistToIter[iMin]; - pT->aMap[pT->nMap].iRowid = iRowid; - pT->nMap++; - } - } - - pIter->base.pData = pIter->poslist.p; - pIter->base.nData = pIter->poslist.n; - } - } -} - -/* -** The iterator passed as the only argument must be a tokendata=1 iterator -** (pIter->pTokenDataIter!=0). This function advances the iterator. If -** argument bFrom is false, then the iterator is advanced to the next -** entry. Or, if bFrom is true, it is advanced to the first entry with -** a rowid of iFrom or greater. -*/ -static void fts5TokendataIterNext(Fts5Iter *pIter, int bFrom, i64 iFrom){ - int ii; - Fts5TokenDataIter *pT = pIter->pTokenDataIter; - Fts5Index *pIndex = pIter->pIndex; - - for(ii=0; iinIter; ii++){ - Fts5Iter *p = pT->apIter[ii]; - if( p->base.bEof==0 - && (p->base.iRowid==pIter->base.iRowid || (bFrom && p->base.iRowidbase.bEof==0 - && p->base.iRowidrc==SQLITE_OK - ){ - fts5MultiIterNext(pIndex, p, 0, 0); - } - } - } - - if( pIndex->rc==SQLITE_OK ){ - fts5IterSetOutputsTokendata(pIter); - } -} - -/* -** If the segment-iterator passed as the first argument is at EOF, then -** set pIter->term to a copy of buffer pTerm. -*/ -static void fts5TokendataSetTermIfEof(Fts5Iter *pIter, Fts5Buffer *pTerm){ - if( pIter && pIter->aSeg[0].pLeaf==0 ){ - fts5BufferSet(&pIter->pIndex->rc, &pIter->aSeg[0].term, pTerm->n, pTerm->p); - } -} - -/* -** This function sets up an iterator to use for a non-prefix query on a -** tokendata=1 table. -*/ -static Fts5Iter *fts5SetupTokendataIter( - Fts5Index *p, /* FTS index to query */ - const u8 *pToken, /* Buffer containing query term */ - int nToken, /* Size of buffer pToken in bytes */ - Fts5Colset *pColset /* Colset to filter on */ -){ - Fts5Iter *pRet = 0; - Fts5TokenDataIter *pSet = 0; - Fts5Structure *pStruct = 0; - const int flags = FTS5INDEX_QUERY_SCANONETERM | FTS5INDEX_QUERY_SCAN; - - Fts5Buffer bSeek = {0, 0, 0}; - Fts5Buffer *pSmall = 0; - - fts5IndexFlush(p); - pStruct = fts5StructureRead(p); - - while( p->rc==SQLITE_OK ){ - Fts5Iter *pPrev = pSet ? pSet->apIter[pSet->nIter-1] : 0; - Fts5Iter *pNew = 0; - Fts5SegIter *pNewIter = 0; - Fts5SegIter *pPrevIter = 0; - - int iLvl, iSeg, ii; - - pNew = fts5MultiIterAlloc(p, pStruct->nSegment); - if( pSmall ){ - fts5BufferSet(&p->rc, &bSeek, pSmall->n, pSmall->p); - fts5BufferAppendBlob(&p->rc, &bSeek, 1, (const u8*)"\0"); - }else{ - fts5BufferSet(&p->rc, &bSeek, nToken, pToken); - } - if( p->rc ){ - sqlite3Fts5IterClose((Fts5IndexIter*)pNew); - break; - } - - pNewIter = &pNew->aSeg[0]; - pPrevIter = (pPrev ? &pPrev->aSeg[0] : 0); - for(iLvl=0; iLvlnLevel; iLvl++){ - for(iSeg=pStruct->aLevel[iLvl].nSeg-1; iSeg>=0; iSeg--){ - Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg]; - int bDone = 0; - - if( pPrevIter ){ - if( fts5BufferCompare(pSmall, &pPrevIter->term) ){ - memcpy(pNewIter, pPrevIter, sizeof(Fts5SegIter)); - memset(pPrevIter, 0, sizeof(Fts5SegIter)); - bDone = 1; - }else if( pPrevIter->iEndofDoclist>pPrevIter->pLeaf->szLeaf ){ - fts5SegIterNextInit(p,(const char*)bSeek.p,bSeek.n-1,pSeg,pNewIter); - bDone = 1; - } - } - - if( bDone==0 ){ - fts5SegIterSeekInit(p, bSeek.p, bSeek.n, flags, pSeg, pNewIter); - } - - if( pPrevIter ){ - if( pPrevIter->pTombArray ){ - pNewIter->pTombArray = pPrevIter->pTombArray; - pNewIter->pTombArray->nRef++; - } - }else{ - fts5SegIterAllocTombstone(p, pNewIter); - } - - pNewIter++; - if( pPrevIter ) pPrevIter++; - if( p->rc ) break; - } - } - fts5TokendataSetTermIfEof(pPrev, pSmall); - - pNew->bSkipEmpty = 1; - pNew->pColset = pColset; - fts5IterSetOutputCb(&p->rc, pNew); - - /* Loop through all segments in the new iterator. Find the smallest - ** term that any segment-iterator points to. Iterator pNew will be - ** used for this term. Also, set any iterator that points to a term that - ** does not match pToken/nToken to point to EOF */ - pSmall = 0; - for(ii=0; iinSeg; ii++){ - Fts5SegIter *pII = &pNew->aSeg[ii]; - if( 0==fts5IsTokendataPrefix(&pII->term, pToken, nToken) ){ - fts5SegIterSetEOF(pII); - } - if( pII->pLeaf && (!pSmall || fts5BufferCompare(pSmall, &pII->term)>0) ){ - pSmall = &pII->term; - } - } - - /* If pSmall is still NULL at this point, then the new iterator does - ** not point to any terms that match the query. So delete it and break - ** out of the loop - all required iterators have been collected. */ - if( pSmall==0 ){ - sqlite3Fts5IterClose((Fts5IndexIter*)pNew); - break; - } - - /* Append this iterator to the set and continue. */ - pSet = fts5AppendTokendataIter(p, pSet, pNew); - } - - if( p->rc==SQLITE_OK && pSet ){ - int ii; - for(ii=0; iinIter; ii++){ - Fts5Iter *pIter = pSet->apIter[ii]; - int iSeg; - for(iSeg=0; iSegnSeg; iSeg++){ - pIter->aSeg[iSeg].flags |= FTS5_SEGITER_ONETERM; - } - fts5MultiIterFinishSetup(p, pIter); - } - } - - if( p->rc==SQLITE_OK ){ - pRet = fts5MultiIterAlloc(p, 0); - } - if( pRet ){ - pRet->pTokenDataIter = pSet; - if( pSet ){ - fts5IterSetOutputsTokendata(pRet); - }else{ - pRet->base.bEof = 1; - } - }else{ - fts5TokendataIterDelete(pSet); - } - - fts5StructureRelease(pStruct); - fts5BufferFree(&bSeek); - return pRet; -} - /* ** Open a new iterator to iterate though all rowid that match the ** specified token or token prefix. */ @@ -7030,17 +6440,12 @@ assert( (flags & FTS5INDEX_QUERY_SCAN)==0 || flags==FTS5INDEX_QUERY_SCAN ); if( sqlite3Fts5BufferSize(&p->rc, &buf, nToken+1)==0 ){ int iIdx = 0; /* Index to search */ int iPrefixIdx = 0; /* +1 prefix index */ - int bTokendata = pConfig->bTokendata; if( nToken>0 ) memcpy(&buf.p[1], pToken, nToken); - if( flags & (FTS5INDEX_QUERY_NOTOKENDATA|FTS5INDEX_QUERY_SCAN) ){ - bTokendata = 0; - } - /* Figure out which index to search and set iIdx accordingly. If this ** is a prefix query for which there is no prefix index, set iIdx to ** greater than pConfig->nPrefix to indicate that the query will be ** satisfied by scanning multiple terms in the main index. ** @@ -7062,14 +6467,11 @@ if( nIdxChar==nChar ) break; if( nIdxChar==nChar+1 ) iPrefixIdx = iIdx; } } - if( bTokendata && iIdx==0 ){ - buf.p[0] = '0'; - pRet = fts5SetupTokendataIter(p, buf.p, nToken+1, pColset); - }else if( iIdx<=pConfig->nPrefix ){ + if( iIdx<=pConfig->nPrefix ){ /* Straight index lookup */ Fts5Structure *pStruct = fts5StructureRead(p); buf.p[0] = (u8)(FTS5_MAIN_PREFIX + iIdx); if( pStruct ){ fts5MultiIterNew(p, pStruct, flags | FTS5INDEX_QUERY_SKIPEMPTY, @@ -7112,15 +6514,11 @@ ** Move to the next matching rowid. */ int sqlite3Fts5IterNext(Fts5IndexIter *pIndexIter){ Fts5Iter *pIter = (Fts5Iter*)pIndexIter; assert( pIter->pIndex->rc==SQLITE_OK ); - if( pIter->pTokenDataIter ){ - fts5TokendataIterNext(pIter, 0, 0); - }else{ - fts5MultiIterNext(pIter->pIndex, pIter, 0, 0); - } + fts5MultiIterNext(pIter->pIndex, pIter, 0, 0); return fts5IndexReturn(pIter->pIndex); } /* ** Move to the next matching term/rowid. Used by the fts5vocab module. @@ -7149,15 +6547,11 @@ ** definition of "at or after" depends on whether this iterator iterates ** in ascending or descending rowid order. */ int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIndexIter, i64 iMatch){ Fts5Iter *pIter = (Fts5Iter*)pIndexIter; - if( pIter->pTokenDataIter ){ - fts5TokendataIterNext(pIter, 1, iMatch); - }else{ - fts5MultiIterNextFrom(pIter->pIndex, pIter, iMatch); - } + fts5MultiIterNextFrom(pIter->pIndex, pIter, iMatch); return fts5IndexReturn(pIter->pIndex); } /* ** Return the current term. @@ -7168,111 +6562,17 @@ assert_nc( z || n<=1 ); *pn = n-1; return (z ? &z[1] : 0); } -/* -** This is used by xInstToken() to access the token at offset iOff, column -** iCol of row iRowid. The token is returned via output variables *ppOut -** and *pnOut. The iterator passed as the first argument must be a tokendata=1 -** iterator (pIter->pTokenDataIter!=0). -*/ -int sqlite3Fts5IterToken( - Fts5IndexIter *pIndexIter, - i64 iRowid, - int iCol, - int iOff, - const char **ppOut, int *pnOut -){ - Fts5Iter *pIter = (Fts5Iter*)pIndexIter; - Fts5TokenDataIter *pT = pIter->pTokenDataIter; - Fts5TokenDataMap *aMap = pT->aMap; - i64 iPos = (((i64)iCol)<<32) + iOff; - - int i1 = 0; - int i2 = pT->nMap; - int iTest = 0; - - while( i2>i1 ){ - iTest = (i1 + i2) / 2; - - if( aMap[iTest].iRowidiRowid ){ - i2 = iTest; - }else{ - if( aMap[iTest].iPosiPos ){ - i2 = iTest; - }else{ - break; - } - } - } - - if( i2>i1 ){ - Fts5Iter *pMap = pT->apIter[aMap[iTest].iIter]; - *ppOut = (const char*)pMap->aSeg[0].term.p+1; - *pnOut = pMap->aSeg[0].term.n-1; - } - - return SQLITE_OK; -} - -/* -** Clear any existing entries from the token-map associated with the -** iterator passed as the only argument. -*/ -void sqlite3Fts5IndexIterClearTokendata(Fts5IndexIter *pIndexIter){ - Fts5Iter *pIter = (Fts5Iter*)pIndexIter; - if( pIter && pIter->pTokenDataIter ){ - pIter->pTokenDataIter->nMap = 0; - } -} - -/* -** Set a token-mapping for the iterator passed as the first argument. This -** is used in detail=column or detail=none mode when a token is requested -** using the xInstToken() API. In this case the caller tokenizers the -** current row and configures the token-mapping via multiple calls to this -** function. -*/ -int sqlite3Fts5IndexIterWriteTokendata( - Fts5IndexIter *pIndexIter, - const char *pToken, int nToken, - i64 iRowid, int iCol, int iOff -){ - Fts5Iter *pIter = (Fts5Iter*)pIndexIter; - Fts5TokenDataIter *pT = pIter->pTokenDataIter; - Fts5Index *p = pIter->pIndex; - int ii; - - assert( p->pConfig->eDetail!=FTS5_DETAIL_FULL ); - assert( pIter->pTokenDataIter ); - - for(ii=0; iinIter; ii++){ - Fts5Buffer *pTerm = &pT->apIter[ii]->aSeg[0].term; - if( nToken==pTerm->n-1 && memcmp(pToken, pTerm->p+1, nToken)==0 ) break; - } - if( iinIter ){ - fts5TokendataIterAppendMap(p, pT, ii, iRowid, (((i64)iCol)<<32) + iOff); - } - return fts5IndexReturn(p); -} - /* ** Close an iterator opened by an earlier call to sqlite3Fts5IndexQuery(). */ void sqlite3Fts5IterClose(Fts5IndexIter *pIndexIter){ if( pIndexIter ){ Fts5Iter *pIter = (Fts5Iter*)pIndexIter; Fts5Index *pIndex = pIter->pIndex; - fts5TokendataIterDelete(pIter->pTokenDataIter); fts5MultiIterFree(pIter); sqlite3Fts5IndexCloseReader(pIndex); } } @@ -7776,13 +7076,11 @@ u64 *pCksum /* IN/OUT: Checksum value */ ){ int eDetail = p->pConfig->eDetail; u64 cksum = *pCksum; Fts5IndexIter *pIter = 0; - int rc = sqlite3Fts5IndexQuery( - p, z, n, (flags | FTS5INDEX_QUERY_NOTOKENDATA), 0, &pIter - ); + int rc = sqlite3Fts5IndexQuery(p, z, n, flags, 0, &pIter); while( rc==SQLITE_OK && ALWAYS(pIter!=0) && 0==sqlite3Fts5IterEof(pIter) ){ i64 rowid = pIter->iRowid; if( eDetail==FTS5_DETAIL_NONE ){ @@ -7945,20 +7243,20 @@ fts5DataRelease(pLeaf); } } static void fts5IntegrityCheckPgidx(Fts5Index *p, Fts5Data *pLeaf){ - i64 iTermOff = 0; + int iTermOff = 0; int ii; Fts5Buffer buf1 = {0,0,0}; Fts5Buffer buf2 = {0,0,0}; ii = pLeaf->szLeaf; while( iinn && p->rc==SQLITE_OK ){ int res; - i64 iOff; + int iOff; int nIncr; ii += fts5GetVarint32(&pLeaf->p[ii], nIncr); iTermOff += nIncr; iOff = iTermOff; @@ -8476,28 +7774,10 @@ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %lld%s", iRowid, zApp); } } #endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ -#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) -static void fts5BufferAppendTerm(int *pRc, Fts5Buffer *pBuf, Fts5Buffer *pTerm){ - int ii; - fts5BufferGrow(pRc, pBuf, pTerm->n*2 + 1); - if( *pRc==SQLITE_OK ){ - for(ii=0; iin; ii++){ - if( pTerm->p[ii]==0x00 ){ - pBuf->p[pBuf->n++] = '\\'; - pBuf->p[pBuf->n++] = '0'; - }else{ - pBuf->p[pBuf->n++] = pTerm->p[ii]; - } - } - pBuf->p[pBuf->n] = 0x00; - } -} -#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ - #if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** The implementation of user-defined scalar function fts5_decode(). */ static void fts5DecodeFunction( @@ -8601,12 +7881,13 @@ /* Read the term data for the next term*/ iOff += fts5GetVarint32(&a[iOff], nAppend); term.n = nKeep; fts5BufferAppendBlob(&rc, &term, nAppend, &a[iOff]); - sqlite3Fts5BufferAppendPrintf(&rc, &s, " term="); - fts5BufferAppendTerm(&rc, &s, &term); + sqlite3Fts5BufferAppendPrintf( + &rc, &s, " term=%.*s", term.n, (const char*)term.p + ); iOff += nAppend; /* Figure out where the doclist for this term ends */ if( iPgidxOffnOrderBy==1 ){ int iSort = pInfo->aOrderBy[0].iColumn; if( iSort==(pConfig->nCol+1) && bSeenMatch ){ idxFlags |= FTS5_BI_ORDER_RANK; - }else if( iSort==-1 && (!pInfo->aOrderBy[0].desc || !pConfig->bTokendata) ){ + }else if( iSort==-1 ){ idxFlags |= FTS5_BI_ORDER_ROWID; } if( BitFlagTest(idxFlags, FTS5_BI_ORDER_RANK|FTS5_BI_ORDER_ROWID) ){ pInfo->orderByConsumed = 1; if( pInfo->aOrderBy[0].desc ){ @@ -914,20 +911,10 @@ assert( (pCsr->ePlan<3)== (pCsr->ePlan==FTS5_PLAN_MATCH || pCsr->ePlan==FTS5_PLAN_SOURCE) ); assert( !CsrFlagTest(pCsr, FTS5CSR_EOF) ); - /* If this cursor uses FTS5_PLAN_MATCH and this is a tokendata=1 table, - ** clear any token mappings accumulated at the fts5_index.c level. In - ** other cases, specifically FTS5_PLAN_SOURCE and FTS5_PLAN_SORTED_MATCH, - ** we need to retain the mappings for the entire query. */ - if( pCsr->ePlan==FTS5_PLAN_MATCH - && ((Fts5Table*)pCursor->pVtab)->pConfig->bTokendata - ){ - sqlite3Fts5ExprClearTokens(pCsr->pExpr); - } - if( pCsr->ePlan<3 ){ int bSkip = 0; if( (rc = fts5CursorReseek(pCsr, &bSkip)) || bSkip ) return rc; rc = sqlite3Fts5ExprNext(pCsr->pExpr, pCsr->iLastRowid); CsrFlagSet(pCsr, sqlite3Fts5ExprEof(pCsr->pExpr)); @@ -1584,14 +1571,11 @@ pConfig->bPrefixIndex = sqlite3_value_int(pVal); #endif }else if( 0==sqlite3_stricmp("flush", zCmd) ){ rc = sqlite3Fts5FlushToDisk(&pTab->p); }else{ - rc = sqlite3Fts5FlushToDisk(&pTab->p); - if( rc==SQLITE_OK ){ - rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex); - } + rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex); if( rc==SQLITE_OK ){ rc = sqlite3Fts5ConfigSetValue(pTab->p.pConfig, zCmd, pVal, &bError); } if( rc==SQLITE_OK ){ if( bError ){ @@ -1912,14 +1896,11 @@ const char **pz, int *pn ){ int rc = SQLITE_OK; Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; - Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab); - if( iCol<0 || iCol>=pTab->pConfig->nCol ){ - rc = SQLITE_RANGE; - }else if( fts5IsContentless((Fts5FullTable*)(pCsr->base.pVtab)) + if( fts5IsContentless((Fts5FullTable*)(pCsr->base.pVtab)) || pCsr->ePlan==FTS5_PLAN_SPECIAL ){ *pz = 0; *pn = 0; }else{ @@ -1940,13 +1921,12 @@ ){ Fts5Config *pConfig = ((Fts5Table*)(pCsr->base.pVtab))->pConfig; int rc = SQLITE_OK; int bLive = (pCsr->pSorter==0); - if( iPhrase<0 || iPhrase>=sqlite3Fts5ExprPhraseCount(pCsr->pExpr) ){ - rc = SQLITE_RANGE; - }else if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_POSLIST) ){ + if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_POSLIST) ){ + if( pConfig->eDetail!=FTS5_DETAIL_FULL ){ Fts5PoslistPopulator *aPopulator; int i; aPopulator = sqlite3Fts5ExprClearPoslists(pCsr->pExpr, bLive); if( aPopulator==0 ) rc = SQLITE_NOMEM; @@ -1966,24 +1946,18 @@ } } CsrFlagClear(pCsr, FTS5CSR_REQUIRE_POSLIST); } - if( rc==SQLITE_OK ){ - if( pCsr->pSorter && pConfig->eDetail==FTS5_DETAIL_FULL ){ - Fts5Sorter *pSorter = pCsr->pSorter; - int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]); - *pn = pSorter->aIdx[iPhrase] - i1; - *pa = &pSorter->aPoslist[i1]; - }else{ - *pn = sqlite3Fts5ExprPoslist(pCsr->pExpr, iPhrase, pa); - } - }else{ - *pa = 0; - *pn = 0; - } - + if( pCsr->pSorter && pConfig->eDetail==FTS5_DETAIL_FULL ){ + Fts5Sorter *pSorter = pCsr->pSorter; + int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]); + *pn = pSorter->aIdx[iPhrase] - i1; + *pa = &pSorter->aPoslist[i1]; + }else{ + *pn = sqlite3Fts5ExprPoslist(pCsr->pExpr, iPhrase, pa); + } return rc; } /* @@ -2087,10 +2061,16 @@ if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_INST)==0 || SQLITE_OK==(rc = fts5CacheInstArray(pCsr)) ){ if( iIdx<0 || iIdx>=pCsr->nInstCount ){ rc = SQLITE_RANGE; +#if 0 + }else if( fts5IsOffsetless((Fts5Table*)pCsr->base.pVtab) ){ + *piPhrase = pCsr->aInst[iIdx*3]; + *piCol = pCsr->aInst[iIdx*3 + 2]; + *piOff = -1; +#endif }else{ *piPhrase = pCsr->aInst[iIdx*3]; *piCol = pCsr->aInst[iIdx*3 + 1]; *piOff = pCsr->aInst[iIdx*3 + 2]; } @@ -2341,60 +2321,17 @@ } return rc; } -/* -** xQueryToken() API implemenetation. -*/ -static int fts5ApiQueryToken( - Fts5Context* pCtx, - int iPhrase, - int iToken, - const char **ppOut, - int *pnOut -){ - Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; - return sqlite3Fts5ExprQueryToken(pCsr->pExpr, iPhrase, iToken, ppOut, pnOut); -} - -/* -** xInstToken() API implemenetation. -*/ -static int fts5ApiInstToken( - Fts5Context *pCtx, - int iIdx, - int iToken, - const char **ppOut, int *pnOut -){ - Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; - int rc = SQLITE_OK; - if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_INST)==0 - || SQLITE_OK==(rc = fts5CacheInstArray(pCsr)) - ){ - if( iIdx<0 || iIdx>=pCsr->nInstCount ){ - rc = SQLITE_RANGE; - }else{ - int iPhrase = pCsr->aInst[iIdx*3]; - int iCol = pCsr->aInst[iIdx*3 + 1]; - int iOff = pCsr->aInst[iIdx*3 + 2]; - i64 iRowid = fts5CursorRowid(pCsr); - rc = sqlite3Fts5ExprInstToken( - pCsr->pExpr, iRowid, iPhrase, iCol, iOff, iToken, ppOut, pnOut - ); - } - } - return rc; -} - static int fts5ApiQueryPhrase(Fts5Context*, int, void*, int(*)(const Fts5ExtensionApi*, Fts5Context*, void*) ); static const Fts5ExtensionApi sFts5Api = { - 3, /* iVersion */ + 2, /* iVersion */ fts5ApiUserData, fts5ApiColumnCount, fts5ApiRowCount, fts5ApiColumnTotalSize, fts5ApiTokenize, @@ -2410,12 +2347,10 @@ fts5ApiGetAuxdata, fts5ApiPhraseFirst, fts5ApiPhraseNext, fts5ApiPhraseFirstColumn, fts5ApiPhraseNextColumn, - fts5ApiQueryToken, - fts5ApiInstToken }; /* ** Implementation of API function xQueryPhrase(). */ @@ -2678,11 +2613,13 @@ sqlite3_vtab *pVtab, /* Virtual table handle */ const char *zName /* New name of table */ ){ int rc; Fts5FullTable *pTab = (Fts5FullTable*)pVtab; + pTab->bInSavepoint = 1; rc = sqlite3Fts5StorageRename(pTab->pStorage, zName); + pTab->bInSavepoint = 0; return rc; } int sqlite3Fts5FlushToDisk(Fts5Table *pTab){ fts5TripCursors((Fts5FullTable*)pTab); @@ -2695,16 +2632,30 @@ ** Flush the contents of the pending-terms table to disk. */ static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){ Fts5FullTable *pTab = (Fts5FullTable*)pVtab; int rc = SQLITE_OK; - + char *zSql = 0; fts5CheckTransactionState(pTab, FTS5_SAVEPOINT, iSavepoint); - rc = sqlite3Fts5FlushToDisk((Fts5Table*)pVtab); - if( rc==SQLITE_OK ){ - pTab->iSavepoint = iSavepoint+1; + + 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; } /* ** The xRelease() method. @@ -2732,12 +2683,12 @@ static int fts5RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){ Fts5FullTable *pTab = (Fts5FullTable*)pVtab; int rc = SQLITE_OK; fts5CheckTransactionState(pTab, FTS5_ROLLBACKTO, iSavepoint); fts5TripCursors(pTab); + pTab->p.pConfig->pgsz = 0; if( (iSavepoint+1)<=pTab->iSavepoint ){ - pTab->p.pConfig->pgsz = 0; rc = sqlite3Fts5StorageRollback(pTab->pStorage); } return rc; } @@ -2961,35 +2912,40 @@ /* ** 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 fts5IntegrityMethod( +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); - rc = sqlite3Fts5StorageIntegrity(pTab->pStorage, 0); + zSql = sqlite3_mprintf( + "INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');", + zSchema, zTabname, pConfig->zName); + if( zSql==0 ) return SQLITE_NOMEM; + rc = sqlite3_exec(pConfig->db, zSql, 0, 0, &zErr); + sqlite3_free(zSql); if( (rc&0xff)==SQLITE_CORRUPT ){ *pzErr = sqlite3_mprintf("malformed inverted index for FTS5 table %s.%s", zSchema, zTabname); - rc = (*pzErr) ? SQLITE_OK : SQLITE_NOMEM; }else if( rc!=SQLITE_OK ){ *pzErr = sqlite3_mprintf("unable to validate the inverted index for" " FTS5 table %s.%s: %s", - zSchema, zTabname, sqlite3_errstr(rc)); + zSchema, zTabname, zErr); } - sqlite3Fts5IndexCloseReader(pTab->p.pIndex); - - return rc; + sqlite3_free(zErr); + return SQLITE_OK; } static int fts5Init(sqlite3 *db){ static const sqlite3_module fts5Mod = { /* iVersion */ 4, @@ -3014,11 +2970,11 @@ /* xRename */ fts5RenameMethod, /* xSavepoint */ fts5SavepointMethod, /* xRelease */ fts5ReleaseMethod, /* xRollbackTo */ fts5RollbackToMethod, /* xShadowName */ fts5ShadowName, - /* xIntegrity */ fts5IntegrityMethod + /* xIntegrity */ fts5Integrity }; int rc; Fts5Global *pGlobal = 0; Index: ext/fts5/fts5_storage.c ================================================================== --- ext/fts5/fts5_storage.c +++ ext/fts5/fts5_storage.c @@ -671,11 +671,11 @@ if( rc==SQLITE_OK ){ rc = fts5StorageLoadTotals(p, 1); } if( rc==SQLITE_OK ){ - rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, pConfig->pzErrmsg); + rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, 0); } while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pScan) ){ i64 iRowid = sqlite3_column_int64(pScan, 0); Index: ext/fts5/fts5_tcl.c ================================================================== --- ext/fts5/fts5_tcl.c +++ ext/fts5/fts5_tcl.c @@ -242,13 +242,10 @@ { "xGetAuxdata", 1, "CLEAR" }, /* 13 */ { "xSetAuxdataInt", 1, "INTEGER" }, /* 14 */ { "xGetAuxdataInt", 1, "CLEAR" }, /* 15 */ { "xPhraseForeach", 4, "IPHRASE COLVAR OFFVAR SCRIPT" }, /* 16 */ { "xPhraseColumnForeach", 3, "IPHRASE COLVAR SCRIPT" }, /* 17 */ - - { "xQueryToken", 2, "IPHRASE ITERM" }, /* 18 */ - { "xInstToken", 2, "IDX ITERM" }, /* 19 */ { 0, 0, 0} }; int rc; int iSub = 0; @@ -498,42 +495,10 @@ if( rc==TCL_BREAK ) rc = TCL_OK; break; } } - break; - } - - CASE(18, "xQueryToken") { - const char *pTerm = 0; - int nTerm = 0; - int iPhrase = 0; - int iTerm = 0; - - if( Tcl_GetIntFromObj(interp, objv[2], &iPhrase) ) return TCL_ERROR; - if( Tcl_GetIntFromObj(interp, objv[3], &iTerm) ) return TCL_ERROR; - rc = p->pApi->xQueryToken(p->pFts, iPhrase, iTerm, &pTerm, &nTerm); - if( rc==SQLITE_OK ){ - Tcl_SetObjResult(interp, Tcl_NewStringObj(pTerm, nTerm)); - } - - break; - } - - CASE(19, "xInstToken") { - const char *pTerm = 0; - int nTerm = 0; - int iIdx = 0; - int iTerm = 0; - - if( Tcl_GetIntFromObj(interp, objv[2], &iIdx) ) return TCL_ERROR; - if( Tcl_GetIntFromObj(interp, objv[3], &iTerm) ) return TCL_ERROR; - rc = p->pApi->xInstToken(p->pFts, iIdx, iTerm, &pTerm, &nTerm); - if( rc==SQLITE_OK ){ - Tcl_SetObjResult(interp, Tcl_NewStringObj(pTerm, nTerm)); - } - break; } default: assert( 0 ); @@ -1150,180 +1115,10 @@ return TCL_ERROR; } return TCL_OK; } -typedef struct OriginTextCtx OriginTextCtx; -struct OriginTextCtx { - sqlite3 *db; - fts5_api *pApi; -}; - -typedef struct OriginTextTokenizer OriginTextTokenizer; -struct OriginTextTokenizer { - Fts5Tokenizer *pTok; /* Underlying tokenizer object */ - fts5_tokenizer tokapi; /* API implementation for pTok */ -}; - -/* -** Delete the OriginTextCtx object indicated by the only argument. -*/ -static void f5tOrigintextTokenizerDelete(void *pCtx){ - OriginTextCtx *p = (OriginTextCtx*)pCtx; - ckfree((char*)p); -} - -static int f5tOrigintextCreate( - void *pCtx, - const char **azArg, - int nArg, - Fts5Tokenizer **ppOut -){ - OriginTextCtx *p = (OriginTextCtx*)pCtx; - OriginTextTokenizer *pTok = 0; - void *pTokCtx = 0; - int rc = SQLITE_OK; - - pTok = (OriginTextTokenizer*)sqlite3_malloc(sizeof(OriginTextTokenizer)); - if( pTok==0 ){ - rc = SQLITE_NOMEM; - }else if( nArg<1 ){ - rc = SQLITE_ERROR; - }else{ - /* Locate the underlying tokenizer */ - rc = p->pApi->xFindTokenizer(p->pApi, azArg[0], &pTokCtx, &pTok->tokapi); - } - - /* Create the new tokenizer instance */ - if( rc==SQLITE_OK ){ - rc = pTok->tokapi.xCreate(pTokCtx, &azArg[1], nArg-1, &pTok->pTok); - } - - if( rc!=SQLITE_OK ){ - sqlite3_free(pTok); - pTok = 0; - } - *ppOut = (Fts5Tokenizer*)pTok; - return rc; -} - -static void f5tOrigintextDelete(Fts5Tokenizer *pTokenizer){ - OriginTextTokenizer *p = (OriginTextTokenizer*)pTokenizer; - if( p->pTok ){ - p->tokapi.xDelete(p->pTok); - } - sqlite3_free(p); -} - -typedef struct OriginTextCb OriginTextCb; -struct OriginTextCb { - void *pCtx; - const char *pText; - int nText; - int (*xToken)(void *, int, const char *, int, int, int); - - char *aBuf; /* Buffer to use */ - int nBuf; /* Allocated size of aBuf[] */ -}; - -static int xOriginToken( - void *pCtx, /* Copy of 2nd argument to xTokenize() */ - int tflags, /* Mask of FTS5_TOKEN_* flags */ - const char *pToken, /* Pointer to buffer containing token */ - int nToken, /* Size of token in bytes */ - int iStart, /* Byte offset of token within input text */ - int iEnd /* Byte offset of end of token within input */ -){ - OriginTextCb *p = (OriginTextCb*)pCtx; - int ret = 0; - - if( nToken==(iEnd-iStart) && 0==memcmp(pToken, &p->pText[iStart], nToken) ){ - /* Token exactly matches document text. Pass it through as is. */ - ret = p->xToken(p->pCtx, tflags, pToken, nToken, iStart, iEnd); - }else{ - int nReq = nToken + 1 + (iEnd-iStart); - if( nReq>p->nBuf ){ - sqlite3_free(p->aBuf); - p->aBuf = sqlite3_malloc(nReq*2); - if( p->aBuf==0 ) return SQLITE_NOMEM; - p->nBuf = nReq*2; - } - - memcpy(p->aBuf, pToken, nToken); - p->aBuf[nToken] = '\0'; - memcpy(&p->aBuf[nToken+1], &p->pText[iStart], iEnd-iStart); - ret = p->xToken(p->pCtx, tflags, p->aBuf, nReq, iStart, iEnd); - } - - return ret; -} - - -static int f5tOrigintextTokenize( - Fts5Tokenizer *pTokenizer, - void *pCtx, - int flags, /* Mask of FTS5_TOKENIZE_* flags */ - const char *pText, int nText, - int (*xToken)(void *, int, const char *, int, int, int) -){ - OriginTextTokenizer *p = (OriginTextTokenizer*)pTokenizer; - OriginTextCb cb; - int ret; - - memset(&cb, 0, sizeof(cb)); - cb.pCtx = pCtx; - cb.pText = pText; - cb.nText = nText; - cb.xToken = xToken; - - ret = p->tokapi.xTokenize(p->pTok,(void*)&cb,flags,pText,nText,xOriginToken); - sqlite3_free(cb.aBuf); - return ret; -} - -/* -** sqlite3_fts5_register_origintext DB -** -** Description... -*/ -static int SQLITE_TCLAPI f5tRegisterOriginText( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - sqlite3 *db = 0; - fts5_api *pApi = 0; - int rc; - fts5_tokenizer tok = {0, 0, 0}; - OriginTextCtx *pCtx = 0; - - if( objc!=2 ){ - Tcl_WrongNumArgs(interp, 1, objv, "DB"); - return TCL_ERROR; - } - if( f5tDbAndApi(interp, objv[1], &db, &pApi) ) return TCL_ERROR; - - pCtx = (OriginTextCtx*)ckalloc(sizeof(OriginTextCtx)); - pCtx->db = db; - pCtx->pApi = pApi; - - tok.xCreate = f5tOrigintextCreate; - tok.xDelete = f5tOrigintextDelete; - tok.xTokenize = f5tOrigintextTokenize; - rc = pApi->xCreateTokenizer( - pApi, "origintext", (void*)pCtx, &tok, f5tOrigintextTokenizerDelete - ); - - Tcl_ResetResult(interp); - if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), 0); - return TCL_ERROR; - } - return TCL_OK; -} - /* ** Entry point. */ int Fts5tcl_Init(Tcl_Interp *interp){ static struct Cmd { @@ -1336,12 +1131,11 @@ { "sqlite3_fts5_tokenize", f5tTokenize, 0 }, { "sqlite3_fts5_create_function", f5tCreateFunction, 0 }, { "sqlite3_fts5_may_be_corrupt", f5tMayBeCorrupt, 0 }, { "sqlite3_fts5_token_hash", f5tTokenHash, 0 }, { "sqlite3_fts5_register_matchinfo", f5tRegisterMatchinfo, 0 }, - { "sqlite3_fts5_register_fts5tokenize", f5tRegisterTok, 0 }, - { "sqlite3_fts5_register_origintext",f5tRegisterOriginText, 0 } + { "sqlite3_fts5_register_fts5tokenize", f5tRegisterTok, 0 } }; int i; F5tTokenizerContext *pContext; pContext = (F5tTokenizerContext*)ckalloc(sizeof(F5tTokenizerContext)); Index: ext/fts5/fts5_tokenize.c ================================================================== --- ext/fts5/fts5_tokenize.c +++ ext/fts5/fts5_tokenize.c @@ -227,16 +227,10 @@ } \ } #endif /* ifndef SQLITE_AMALGAMATION */ -#define FTS5_SKIP_UTF8(zIn) { \ - if( ((unsigned char)(*(zIn++)))>=0xc0 ){ \ - while( (((unsigned char)*zIn) & 0xc0)==0x80 ){ zIn++; } \ - } \ -} - typedef struct Unicode61Tokenizer Unicode61Tokenizer; struct Unicode61Tokenizer { unsigned char aTokenChar[128]; /* ASCII range token characters */ char *aFold; /* Buffer to fold text into */ int nFold; /* Size of aFold[] in bytes */ @@ -1268,11 +1262,10 @@ ** Start of trigram implementation. */ typedef struct TrigramTokenizer TrigramTokenizer; struct TrigramTokenizer { int bFold; /* True to fold to lower-case */ - int iFoldParam; /* Parameter to pass to Fts5UnicodeFold() */ }; /* ** Free a trigram tokenizer. */ @@ -1295,34 +1288,22 @@ if( pNew==0 ){ rc = SQLITE_NOMEM; }else{ int i; pNew->bFold = 1; - pNew->iFoldParam = 0; for(i=0; rc==SQLITE_OK && ibFold = (zArg[0]=='0'); } - }else if( 0==sqlite3_stricmp(azArg[i], "remove_diacritics") ){ - if( (zArg[0]!='0' && zArg[0]!='1' && zArg[0]!='2') || zArg[1] ){ - rc = SQLITE_ERROR; - }else{ - pNew->iFoldParam = (zArg[0]!='0') ? 2 : 0; - } }else{ rc = SQLITE_ERROR; } } - - if( pNew->iFoldParam!=0 && pNew->bFold==0 ){ - rc = SQLITE_ERROR; - } - if( rc!=SQLITE_OK ){ fts5TriDelete((Fts5Tokenizer*)pNew); pNew = 0; } } @@ -1341,66 +1322,44 @@ int (*xToken)(void*, int, const char*, int, int, int) ){ TrigramTokenizer *p = (TrigramTokenizer*)pTok; int rc = SQLITE_OK; char aBuf[32]; - char *zOut = aBuf; - int ii; const unsigned char *zIn = (const unsigned char*)pText; const unsigned char *zEof = &zIn[nText]; u32 iCode; - int aStart[3]; /* Input offset of each character in aBuf[] */ UNUSED_PARAM(unusedFlags); - - /* Populate aBuf[] with the characters for the first trigram. */ - for(ii=0; ii<3; ii++){ - do { - aStart[ii] = zIn - (const unsigned char*)pText; - READ_UTF8(zIn, zEof, iCode); - if( iCode==0 ) return SQLITE_OK; - if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam); - }while( iCode==0 ); - WRITE_UTF8(zOut, iCode); - } - - /* At the start of each iteration of this loop: - ** - ** aBuf: Contains 3 characters. The 3 characters of the next trigram. - ** zOut: Points to the byte following the last character in aBuf. - ** aStart[3]: Contains the byte offset in the input text corresponding - ** to the start of each of the three characters in the buffer. - */ - assert( zIn<=zEof ); - while( 1 ){ - int iNext; /* Start of character following current tri */ - const char *z1; - - /* Read characters from the input up until the first non-diacritic */ - do { - iNext = zIn - (const unsigned char*)pText; + while( 1 ){ + char *zOut = aBuf; + int iStart = zIn - (const unsigned char*)pText; + const unsigned char *zNext; + + READ_UTF8(zIn, zEof, iCode); + if( iCode==0 ) break; + zNext = zIn; + if( zInbFold ) iCode = sqlite3Fts5UnicodeFold(iCode, 0); + WRITE_UTF8(zOut, iCode); + READ_UTF8(zIn, zEof, iCode); + if( iCode==0 ) break; + }else{ + break; + } + if( zInbFold ) iCode = sqlite3Fts5UnicodeFold(iCode, 0); + WRITE_UTF8(zOut, iCode); READ_UTF8(zIn, zEof, iCode); if( iCode==0 ) break; - if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam); - }while( iCode==0 ); - - /* Pass the current trigram back to fts5 */ - rc = xToken(pCtx, 0, aBuf, zOut-aBuf, aStart[0], iNext); - if( iCode==0 || rc!=SQLITE_OK ) break; - - /* Remove the first character from buffer aBuf[]. Append the character - ** with codepoint iCode. */ - z1 = aBuf; - FTS5_SKIP_UTF8(z1); - memmove(aBuf, z1, zOut - z1); - zOut -= (z1 - aBuf); - WRITE_UTF8(zOut, iCode); - - /* Update the aStart[] array */ - aStart[0] = aStart[1]; - aStart[1] = aStart[2]; - aStart[2] = iNext; + if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, 0); + WRITE_UTF8(zOut, iCode); + }else{ + break; + } + rc = xToken(pCtx, 0, aBuf, zOut-aBuf, iStart, iStart + zOut-aBuf); + if( rc!=SQLITE_OK ) break; + zIn = zNext; } return rc; } @@ -1419,13 +1378,11 @@ int (*xCreate)(void*, const char**, int, Fts5Tokenizer**), Fts5Tokenizer *pTok ){ if( xCreate==fts5TriCreate ){ TrigramTokenizer *p = (TrigramTokenizer*)pTok; - if( p->iFoldParam==0 ){ - return p->bFold ? FTS5_PATTERN_LIKE : FTS5_PATTERN_GLOB; - } + return p->bFold ? FTS5_PATTERN_LIKE : FTS5_PATTERN_GLOB; } return FTS5_PATTERN_NONE; } /* Index: ext/fts5/fts5_vocab.c ================================================================== --- ext/fts5/fts5_vocab.c +++ ext/fts5/fts5_vocab.c @@ -627,11 +627,11 @@ if( idxNum & FTS5_VOCAB_TERM_LE ) pLe = apVal[iVal++]; if( pEq ){ zTerm = (const char *)sqlite3_value_text(pEq); nTerm = sqlite3_value_bytes(pEq); - f = FTS5INDEX_QUERY_NOTOKENDATA; + f = 0; }else{ if( pGe ){ zTerm = (const char *)sqlite3_value_text(pGe); nTerm = sqlite3_value_bytes(pGe); } Index: ext/fts5/test/fts5_common.tcl ================================================================== --- ext/fts5/test/fts5_common.tcl +++ ext/fts5/test/fts5_common.tcl @@ -59,28 +59,18 @@ } set res } -proc fts5_collist {cmd iPhrase} { - set res [list] - $cmd xPhraseColumnForeach $iPhrase c { lappend res $c } - set res -} - proc fts5_test_columnsize {cmd} { set res [list] for {set i 0} {$i < [$cmd xColumnCount]} {incr i} { lappend res [$cmd xColumnSize $i] } set res } -proc fts5_columntext {cmd iCol} { - $cmd xColumnText $iCol -} - proc fts5_test_columntext {cmd} { set res [list] for {set i 0} {$i < [$cmd xColumnCount]} {incr i} { lappend res [$cmd xColumnText $i] } @@ -133,17 +123,10 @@ lappend res $cnt } set res } -proc fts5_queryphrase {cmd iPhrase} { - set cnt [list] - for {set j 0} {$j < [$cmd xColumnCount]} {incr j} { lappend cnt 0 } - $cmd xQueryPhrase $iPhrase [list test_queryphrase_cb cnt] - set cnt -} - proc fts5_test_phrasecount {cmd} { $cmd xPhraseCount } proc fts5_test_all {cmd} { @@ -169,13 +152,10 @@ fts5_test_rowcount fts5_test_all fts5_test_queryphrase fts5_test_phrasecount - fts5_columntext - fts5_queryphrase - fts5_collist } { sqlite3_fts5_create_function $db $f $f } } @@ -456,24 +436,10 @@ } proc detail_is_none {} { detail_check ; expr {$::detail == "none"} } proc detail_is_col {} { detail_check ; expr {$::detail == "col" } } proc detail_is_full {} { detail_check ; expr {$::detail == "full"} } -proc foreach_tokenizer_mode {prefix script} { - set saved $::testprefix - foreach {d mapping} { - "" {} - "-origintext" {, tokenize="origintext unicode61", tokendata=1} - } { - set s [string map [list %TOKENIZER% $mapping] $script] - set ::testprefix "$prefix$d" - reset_db - sqlite3_fts5_register_origintext db - uplevel $s - } - set ::testprefix $saved -} #------------------------------------------------------------------------- # Convert a poslist of the type returned by fts5_test_poslist() to a # collist as returned by fts5_test_collist(). # Index: ext/fts5/test/fts5aa.test ================================================================== --- ext/fts5/test/fts5aa.test +++ ext/fts5/test/fts5aa.test @@ -20,11 +20,10 @@ finish_test return } foreach_detail_mode $::testprefix { -foreach_tokenizer_mode $::testprefix { do_execsql_test 1.0 { CREATE VIRTUAL TABLE t1 USING fts5(a, b, c); SELECT name, sql FROM sqlite_master; } { @@ -43,11 +42,11 @@ #------------------------------------------------------------------------- # do_execsql_test 2.0 { - CREATE VIRTUAL TABLE t1 USING fts5(x, y, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE t1 USING fts5(x, y, detail=%DETAIL%); } do_execsql_test 2.1 { INSERT INTO t1 VALUES('a b c', 'd e f'); } @@ -72,13 +71,12 @@ #------------------------------------------------------------------------- # reset_db -sqlite3_fts5_register_origintext db do_execsql_test 3.0 { - CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL%); } foreach {i x y} { 1 {g f d b f} {h h e i a} 2 {f i g j e} {i j c f f} 3 {e e i f a} {e h f d f} @@ -97,13 +95,12 @@ } #------------------------------------------------------------------------- # reset_db -sqlite3_fts5_register_origintext db do_execsql_test 4.0 { - CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL%); INSERT INTO t1(t1, rank) VALUES('pgsz', 32); } foreach {i x y} { 1 {g f d b f} {h h e i a} 2 {f i g j e} {i j c f f} @@ -122,13 +119,12 @@ } #------------------------------------------------------------------------- # reset_db -sqlite3_fts5_register_origintext db do_execsql_test 5.0 { - CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL%); INSERT INTO t1(t1, rank) VALUES('pgsz', 32); } foreach {i x y} { 1 {dd abc abc abc abcde} {aaa dd ddd ddd aab} 2 {dd aab d aaa b} {abcde c aaa aaa aaa} @@ -147,13 +143,12 @@ } #------------------------------------------------------------------------- # reset_db -sqlite3_fts5_register_origintext db do_execsql_test 6.0 { - CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL%); INSERT INTO t1(t1, rank) VALUES('pgsz', 32); } do_execsql_test 6.1 { INSERT INTO t1(rowid, x, y) VALUES(22, 'a b c', 'c b a'); @@ -184,11 +179,10 @@ } #------------------------------------------------------------------------- # reset_db -sqlite3_fts5_register_origintext db expr srand(0) do_execsql_test 7.0 { CREATE VIRTUAL TABLE t1 USING fts5(x,y,z); INSERT INTO t1(t1, rank) VALUES('pgsz', 32); } @@ -226,11 +220,10 @@ } #------------------------------------------------------------------------- # reset_db -sqlite3_fts5_register_origintext db do_execsql_test 8.0 { CREATE VIRTUAL TABLE t1 USING fts5(x, prefix="1,2,3"); INSERT INTO t1(t1, rank) VALUES('pgsz', 32); } @@ -241,11 +234,10 @@ #------------------------------------------------------------------------- # reset_db -sqlite3_fts5_register_origintext db expr srand(0) do_execsql_test 9.0 { CREATE VIRTUAL TABLE t1 USING fts5(x,y,z, prefix="1,2,3"); @@ -286,13 +278,12 @@ #------------------------------------------------------------------------- # reset_db -sqlite3_fts5_register_origintext db do_execsql_test 10.0 { - CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE t1 USING fts5(x,y, detail=%DETAIL%); } set d10 { 1 {g f d b f} {h h e i a} 2 {f i g j e} {i j c f f} 3 {e e i f a} {e h f d f} @@ -321,23 +312,23 @@ do_execsql_test 10.4.2 { INSERT INTO t1(t1) VALUES('integrity-check') } #------------------------------------------------------------------------- # do_catchsql_test 11.1 { - CREATE VIRTUAL TABLE t2 USING fts5(a, b, c, rank, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE t2 USING fts5(a, b, c, rank, detail=%DETAIL%); } {1 {reserved fts5 column name: rank}} do_catchsql_test 11.2 { - CREATE VIRTUAL TABLE rank USING fts5(a, b, c, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE rank USING fts5(a, b, c, detail=%DETAIL%); } {1 {reserved fts5 table name: rank}} do_catchsql_test 11.3 { - CREATE VIRTUAL TABLE t2 USING fts5(a, b, c, rowid, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE t2 USING fts5(a, b, c, rowid, detail=%DETAIL%); } {1 {reserved fts5 column name: rowid}} #------------------------------------------------------------------------- # do_execsql_test 12.1 { - CREATE VIRTUAL TABLE t2 USING fts5(x,y, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE t2 USING fts5(x,y, detail=%DETAIL%); } {} do_catchsql_test 12.2 { SELECT t2 FROM t2 WHERE t2 MATCH '*stuff' } {1 {unknown special query: stuff}} @@ -348,13 +339,12 @@ } {1} #------------------------------------------------------------------------- # reset_db -sqlite3_fts5_register_origintext db do_execsql_test 13.1 { - CREATE VIRTUAL TABLE t1 USING fts5(x, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE t1 USING fts5(x, detail=%DETAIL%); INSERT INTO t1(rowid, x) VALUES(1, 'o n e'), (2, 't w o'); } {} do_execsql_test 13.2 { SELECT rowid FROM t1 WHERE t1 MATCH 'o'; @@ -373,13 +363,12 @@ } {} #------------------------------------------------------------------------- # reset_db -sqlite3_fts5_register_origintext db do_execsql_test 14.1 { - CREATE VIRTUAL TABLE t1 USING fts5(x, y, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE t1 USING fts5(x, y, detail=%DETAIL%); INSERT INTO t1(t1, rank) VALUES('pgsz', 32); WITH d(x,y) AS ( SELECT NULL, 'xyz xyz xyz xyz xyz xyz' UNION ALL SELECT NULL, 'xyz xyz xyz xyz xyz xyz' FROM d @@ -458,13 +447,12 @@ # {1 {SQL logic error}} #------------------------------------------------------------------------- # reset_db -sqlite3_fts5_register_origintext db do_execsql_test 17.1 { - CREATE VIRTUAL TABLE b2 USING fts5(x, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE b2 USING fts5(x, detail=%DETAIL%); INSERT INTO b2 VALUES('a'); INSERT INTO b2 VALUES('b'); INSERT INTO b2 VALUES('c'); } @@ -476,24 +464,22 @@ set res } {{a b c} {a b c} {a b c}} if {[string match n* %DETAIL%]==0} { reset_db - sqlite3_fts5_register_origintext db do_execsql_test 17.3 { - CREATE VIRTUAL TABLE c2 USING fts5(x, y, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE c2 USING fts5(x, y, detail=%DETAIL%); INSERT INTO c2 VALUES('x x x', 'x x x'); SELECT rowid FROM c2 WHERE c2 MATCH 'y:x'; } {1} } #------------------------------------------------------------------------- # reset_db -sqlite3_fts5_register_origintext db do_execsql_test 17.1 { - CREATE VIRTUAL TABLE uio USING fts5(ttt, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE uio USING fts5(ttt, detail=%DETAIL%); INSERT INTO uio VALUES(NULL); INSERT INTO uio SELECT NULL FROM uio; INSERT INTO uio SELECT NULL FROM uio; INSERT INTO uio SELECT NULL FROM uio; INSERT INTO uio SELECT NULL FROM uio; @@ -536,12 +522,12 @@ } {-9223372036854775808 9 10} #-------------------------------------------------------------------- # do_execsql_test 18.1 { - CREATE VIRTUAL TABLE t1 USING fts5(a, b, detail=%DETAIL% %TOKENIZER%); - CREATE VIRTUAL TABLE t2 USING fts5(c, d, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE t1 USING fts5(a, b, detail=%DETAIL%); + CREATE VIRTUAL TABLE t2 USING fts5(c, d, detail=%DETAIL%); INSERT INTO t1 VALUES('abc*', NULL); INSERT INTO t2 VALUES(1, 'abcdefg'); } do_execsql_test 18.2 { SELECT t1.rowid, t2.rowid FROM t1, t2 WHERE t2 MATCH t1.a AND t1.rowid = t2.c @@ -552,25 +538,23 @@ #-------------------------------------------------------------------- # fts5 table in the temp schema. # reset_db -sqlite3_fts5_register_origintext db do_execsql_test 19.0 { - CREATE VIRTUAL TABLE temp.t1 USING fts5(x, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE temp.t1 USING fts5(x, detail=%DETAIL%); INSERT INTO t1 VALUES('x y z'); INSERT INTO t1 VALUES('w x 1'); SELECT rowid FROM t1 WHERE t1 MATCH 'x'; } {1 2} #-------------------------------------------------------------------- # Test that 6 and 7 byte varints can be read. # reset_db -sqlite3_fts5_register_origintext db do_execsql_test 20.0 { - CREATE VIRTUAL TABLE temp.tmp USING fts5(x, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE temp.tmp USING fts5(x, detail=%DETAIL%); } set ::ids [list \ 0 [expr 1<<36] [expr 2<<36] [expr 1<<43] [expr 2<<43] ] do_test 20.1 { @@ -584,11 +568,11 @@ # Test that a DROP TABLE may be executed within a transaction that # writes to an FTS5 table. # do_execsql_test 21.0 { CREATE TEMP TABLE t8(a, b); - CREATE VIRTUAL TABLE ft USING fts5(x, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE ft USING fts5(x, detail=%DETAIL%); } do_execsql_test 21.1 { BEGIN; INSERT INTO ft VALUES('a b c'); @@ -595,11 +579,11 @@ DROP TABLE t8; COMMIT; } do_execsql_test 22.0 { - CREATE VIRTUAL TABLE t9 USING fts5(x, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE t9 USING fts5(x, detail=%DETAIL%); INSERT INTO t9(rowid, x) VALUES(2, 'bbb'); BEGIN; INSERT INTO t9(rowid, x) VALUES(1, 'aaa'); DELETE FROM t9 WHERE rowid = 2; INSERT INTO t9(rowid, x) VALUES(3, 'bbb'); @@ -610,11 +594,11 @@ SELECT rowid FROM t9('a*') } {1} #------------------------------------------------------------------------- do_execsql_test 23.0 { - CREATE VIRTUAL TABLE t10 USING fts5(x, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE t10 USING fts5(x, detail=%DETAIL%); CREATE TABLE t11(x); } do_execsql_test 23.1 { SELECT * FROM t11, t10 WHERE t11.x = t10.x AND t10.rowid IS NULL; } @@ -622,33 +606,30 @@ SELECT * FROM t11, t10 WHERE t10.rowid IS NULL; } #------------------------------------------------------------------------- do_execsql_test 24.0 { - CREATE VIRTUAL TABLE t12 USING fts5(x, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE t12 USING fts5(x, detail=%DETAIL%); INSERT INTO t12 VALUES('aaaa'); } do_execsql_test 24.1 { BEGIN; DELETE FROM t12 WHERE rowid=1; SELECT * FROM t12('aaaa'); INSERT INTO t12 VALUES('aaaa'); END; } -execsql_pp { - SELECT rowid, hex(block) FROM t12_data -} do_execsql_test 24.2 { INSERT INTO t12(t12) VALUES('integrity-check'); } do_execsql_test 24.3 { SELECT * FROM t12('aaaa'); } {aaaa} #------------------------------------------------------------------------- do_execsql_test 25.0 { - CREATE VIRTUAL TABLE t13 USING fts5(x, detail=%DETAIL% %TOKENIZER%); + CREATE VIRTUAL TABLE t13 USING fts5(x, detail=%DETAIL%); } do_execsql_test 25.1 { BEGIN; INSERT INTO t13 VALUES('AAAA'); SELECT * FROM t13('BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB*'); @@ -655,10 +636,9 @@ END; } -} } expand_all_sql db finish_test Index: ext/fts5/test/fts5aux.test ================================================================== --- ext/fts5/test/fts5aux.test +++ ext/fts5/test/fts5aux.test @@ -332,49 +332,6 @@ do_execsql_test 11.2 { SELECT fts5_hitcount(x1) FROM x1('one') LIMIT 1; } {5} -#------------------------------------------------------------------------- -# Test that xColumnText returns SQLITE_RANGE when it should. -# -reset_db -fts5_aux_test_functions db -do_execsql_test 12.0 { - CREATE VIRTUAL TABLE t1 USING fts5(a, b, c); - INSERT INTO t1 VALUES('one', 'two', 'three'); - INSERT INTO t1 VALUES('one', 'one', 'one'); - INSERT INTO t1 VALUES('two', 'two', 'two'); - INSERT INTO t1 VALUES('three', 'three', 'three'); -} - -do_catchsql_test 12.1.1 { - SELECT fts5_columntext(t1, -1) FROM t1('two'); -} {1 SQLITE_RANGE} -do_catchsql_test 12.1.2 { - SELECT fts5_columntext(t1, 3) FROM t1('two'); -} {1 SQLITE_RANGE} -do_catchsql_test 12.1.2 { - SELECT fts5_columntext(t1, 1) FROM t1('one AND two'); -} {0 two} - -do_catchsql_test 12.2.1 { - SELECT fts5_queryphrase(t1, -1) FROM t1('one AND two'); -} {1 SQLITE_RANGE} -do_catchsql_test 12.2.2 { - SELECT fts5_queryphrase(t1, 2) FROM t1('one AND two'); -} {1 SQLITE_RANGE} -do_catchsql_test 12.2.3 { - SELECT fts5_queryphrase(t1, 1) FROM t1('one AND two'); -} {0 {{1 2 1}}} - -do_catchsql_test 12.3.1 { - SELECT fts5_collist(t1, -1) FROM t1('one AND two'); -} {1 SQLITE_RANGE} -do_catchsql_test 12.3.2 { - SELECT fts5_collist(t1, 2) FROM t1('one AND two'); -} {1 SQLITE_RANGE} -do_catchsql_test 12.3.3 { - SELECT fts5_collist(t1, 1) FROM t1('one AND two'); -} {0 1} - finish_test Index: ext/fts5/test/fts5content.test ================================================================== --- ext/fts5/test/fts5content.test +++ ext/fts5/test/fts5content.test @@ -291,41 +291,6 @@ } {1 {recursively defined fts5 content table}} do_catchsql_test 7.2.5 { SELECT * FROM t1('abc') ORDER BY rank; } {1 {recursively defined fts5 content table}} -#--------------------------------------------------------------------------- -# Check that if the content table is a view, and that view contains an -# error, a reasonable error message is returned if the user tries to -# read from the view via the fts5 table. -# -reset_db -do_execsql_test 8.1 { - CREATE VIEW a1 AS - SELECT 1 AS r, text_value(1) AS t - UNION ALL - SELECT 2 AS r, text_value(2) AS t; - - CREATE VIRTUAL TABLE t1 USING fts5(t, content='a1', content_rowid='r'); -} - -foreach {tn sql} { - 1 "SELECT * FROM t1" - 2 "INSERT INTO t1(t1) VALUES('rebuild')" - 3 "SELECT * FROM t1 WHERE rowid=1" -} { - do_catchsql_test 8.2.$tn $sql {1 {no such function: text_value}} -} - -proc text_value {i} { - if {$i==1} { return "one" } - if {$i==2} { return "two" } - return "many" -} -db func text_value text_value - -do_execsql_test 8.3.1 { SELECT * FROM t1 } {one two} -do_execsql_test 8.3.2 { INSERT INTO t1(t1) VALUES('rebuild') } -do_execsql_test 8.3.3 { SELECT * FROM t1 WHERE rowid=1 } {one} -do_execsql_test 8.3.4 { SELECT rowid FROM t1('two') } {2} - finish_test Index: ext/fts5/test/fts5corrupt5.test ================================================================== --- ext/fts5/test/fts5corrupt5.test +++ ext/fts5/test/fts5corrupt5.test @@ -878,579 +878,9 @@ } do_catchsql_test 5.4 { UPDATE t1 SET content=randomblob(500); } {1 {database disk image is malformed}} -#------------------------------------------------------------------------- -reset_db -do_test 6.0 { - sqlite3 db {} - db deserialize [decode_hexdb { -.open --hexdb -| size 32768 pagesize 4096 filename crash-42fa37b694d45a.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 .....@ ........ -| 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 49 20 4b 45 59 2c 20 63 R PRIMARI 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 f0 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 10 00 1e 06 30 61 62 61 ......N.....0aba -| 4048: 63 6c 01 02 02 04 02 66 74 02 5f 02 04 04 6e 64 cl.....ft._...nd -| 4064: 6f 6e 02 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 01 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 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 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... -| page 8 offset 28672 -| 0: 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ -| end crash-42fa37b694d45a.db -}]} {} - -do_execsql_test 6.1 { - INSERT INTO t1(t1,rank) VALUES('secure-delete',1); -} -do_catchsql_test 6.2 { - UPDATE t1 SET content=randomblob(500) WHERE t1; -} {1 {constraint failed}} - -#------------------------------------------------------------------------- -reset_db -do_test 7.0 { - sqlite3 db {} - db deserialize [decode_hexdb { -.open --hexdb -| size 40960 pagesize 4096 filename crash-d8b4a99207c10b.db -| page 1 offset 0 -| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. -| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 0a .....@ ........ -| 32: 00 00 00 00 00 00 00 00 00 00 00 0d 00 00 00 04 ................ -| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ -| 96: 00 00 00 00 0d 00 00 00 0d 0b 62 00 0f 97 0f 40 ..........b....@ -| 112: 0e d5 0e 75 0e 18 0d c0 0d 66 0d 0f 0c a4 0c 44 ...u.....f.....D -| 128: 0b ec 0b a7 0b 62 00 00 00 00 00 00 00 00 00 00 .....b.......... -| 2912: 00 00 43 0d 06 17 11 11 08 75 74 61 62 6c 65 74 ..C......utablet -| 2928: 34 74 34 43 52 45 41 54 45 20 56 49 52 54 55 41 4t4CREATE VIRTUA -| 2944: 4c 20 54 41 42 4c 45 20 74 34 20 55 53 49 4e 47 L TABLE t4 USING -| 2960: 20 66 74 73 35 76 6f 63 61 62 28 27 74 32 27 2c fts5vocab('t2', -| 2976: 20 27 72 6f 77 27 29 43 0c 06 17 11 11 08 75 74 'row')C......ut -| 2992: 61 62 6c 65 74 33 74 33 43 52 45 41 54 45 20 56 ablet3t3CREATE V -| 3008: 49 52 54 55 41 4c 20 54 41 42 4c 45 20 74 33 20 IRTUAL TABLE t3 -| 3024: 55 53 49 4e 47 20 66 74 73 35 76 6f 63 61 62 28 USING fts5vocab( -| 3040: 27 74 31 27 2c 20 27 72 6f 77 27 29 56 0b 06 17 't1', 'row')V... -| 3056: 1f 1f 01 7d 74 61 62 6c 65 74 32 5f 63 6f 6e 66 ....tablet2_conf -| 3072: 69 67 74 32 5f 63 6f 6e 66 69 67 0a 43 52 45 41 igt2_config.CREA -| 3088: 54 45 20 54 41 42 4c 45 20 27 74 32 5f 63 6f 6e TE TABLE 't2_con -| 3104: 66 69 67 27 28 6b 20 50 52 49 4d 41 52 59 20 4b fig'(k PRIMARY K -| 3120: 45 59 2c 20 76 29 20 57 49 54 48 4f 55 54 20 52 EY, v) WITHOUT R -| 3136: 4f 57 49 44 5e 0a 07 17 21 21 01 81 07 74 61 62 OWID^...!!...tab -| 3152: 6c 65 74 32 5f 63 6f 6e 74 65 6e 74 74 32 5f 63 let2_contentt2_c -| 3168: 6f 6e 74 65 6e 74 09 43 52 45 41 54 45 20 54 41 ontent.CREATE TA -| 3184: 42 4c 45 20 27 74 32 5f 63 6f 6e 74 65 6e 74 27 BLE 't2_content' -| 3200: 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d (id INTEGER PRIM -| 3216: 41 52 59 20 4b 45 59 2c 20 63 30 2c 20 63 31 2c ARY KEY, c0, c1, -| 3232: 20 63 32 29 69 09 07 17 19 19 01 81 2d 74 61 62 c2)i.......-tab -| 3248: 6c 65 74 32 5f 69 64 78 74 32 5f 69 64 78 08 43 let2_idxt2_idx.C -| 3264: 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 32 5f REATE TABLE 't2_ -| 3280: 69 64 78 27 28 73 65 67 69 64 2c 20 74 65 72 6d idx'(segid, term -| 3296: 2c 20 70 67 6e 6f 2c 20 50 52 49 4d 41 52 59 20 , pgno, PRIMARY -| 3312: 4b 45 59 28 73 65 67 69 64 2c 20 74 65 72 6d 29 KEY(segid, term) -| 3328: 29 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 55 ) WITHOUT ROWIDU -| 3344: 08 07 17 1b 1b 01 81 01 74 61 62 6c 65 74 32 5f ........tablet2_ -| 3360: 64 61 74 61 74 32 5f 64 61 74 61 07 43 52 45 41 datat2_data.CREA -| 3376: 54 45 20 54 41 42 4c 45 20 27 74 32 5f 64 61 74 TE TABLE 't2_dat -| 3392: 61 27 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 a'(id INTEGER PR -| 3408: 49 4d 41 52 59 20 4b 45 59 2c 20 62 6c 6f 63 6b IMARY KEY, block -| 3424: 20 42 4c 4f 42 29 58 07 07 17 11 11 08 81 1d 74 BLOB)X........t -| 3440: 61 62 6c 65 74 32 74 32 43 52 45 41 54 45 20 56 ablet2t2CREATE V -| 3456: 49 52 54 55 41 4c 20 54 41 42 4c 45 20 74 32 20 IRTUAL TABLE t2 -| 3472: 55 53 49 4e 47 20 66 74 73 35 28 27 61 27 2c 5b USING fts5('a',[ -| 3488: 62 5d 2c 22 63 22 2c 64 65 74 61 69 6c 3d 6e 6f b],.c.,detail=no -| 3504: 6e 65 2c 63 6f 6c 75 6d 6e 73 69 7a 65 3d 30 29 ne,columnsize=0) -| 3520: 56 06 06 17 1f 1f 01 7d 74 61 62 6c 65 74 31 5f V.......tablet1_ -| 3536: 63 6f 6e 66 69 67 74 31 5f 63 6f 6e 66 69 67 06 configt1_config. -| 3552: 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 CREATE TABLE 't1 -| 3568: 5f 63 6f 6e 66 69 67 27 28 6b 20 50 52 49 4d 41 _config'(k PRIMA -| 3584: 52 59 20 4b 45 59 2c 20 76 29 20 57 49 54 48 4f RY KEY, v) WITHO -| 3600: 55 54 20 52 4f 57 49 44 5b 05 07 17 21 21 01 81 UT ROWID[...!!.. -| 3616: 01 74 61 62 6c 65 74 31 5f 64 6f 63 73 69 7a 65 .tablet1_docsize -| 3632: 74 31 5f 64 6f 63 73 69 7a 65 05 43 52 45 41 54 t1_docsize.CREAT -| 3648: 45 20 54 41 42 4c 45 20 27 74 31 5f 64 6f 63 73 E TABLE 't1_docs -| 3664: 69 7a 65 27 28 69 64 20 49 4e 54 45 47 45 52 20 ize'(id INTEGER -| 3680: 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 73 7a 20 PRIMARY KEY, sz -| 3696: 42 4c 4f 42 29 5e 04 07 17 21 21 01 81 07 74 61 BLOB)^...!!...ta -| 3712: 62 6c 65 74 31 5f 63 6f 6e 74 65 6e 74 74 31 5f blet1_contentt1_ -| 3728: 63 6f 6e 74 65 6e 74 04 43 52 45 41 54 45 20 54 content.CREATE T -| 3744: 41 42 4c 45 20 27 74 31 5f 63 6f 6e 74 65 6e 74 ABLE 't1_content -| 3760: 27 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 '(id INTEGER PRI -| 3776: 4d 41 52 59 20 4b 45 59 2c 20 63 30 2c 20 63 31 MARY KEY, c0, c1 -| 3792: 2c 20 63 32 29 69 03 07 17 19 19 01 81 2d 74 61 , c2)i.......-ta -| 3808: 62 6c 65 74 31 5f 69 64 78 74 31 5f 69 64 78 03 blet1_idxt1_idx. -| 3824: 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 CREATE TABLE 't1 -| 3840: 5f 69 64 78 27 28 73 65 67 69 64 2c 20 74 65 72 _idx'(segid, ter -| 3856: 6d 2c 20 70 67 6e 6f 2c 20 50 52 49 4d 41 52 59 m, pgno, PRIMARY -| 3872: 20 4b 45 59 28 73 65 67 69 64 2c 20 74 65 72 6d KEY(segid, term -| 3888: 29 29 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 )) WITHOUT ROWID -| 3904: 55 02 07 17 1b 1b 01 81 01 74 61 62 6c 65 74 31 U........tablet1 -| 3920: 5f 64 61 74 61 74 31 5f 64 61 74 61 02 43 52 45 _datat1_data.CRE -| 3936: 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 64 61 ATE TABLE 't1_da -| 3952: 74 61 27 28 69 64 20 49 4e 54 45 47 45 52 20 50 ta'(id INTEGER P -| 3968: 52 49 4d 41 52 59 20 4b 45 59 2c 20 62 6c 6f 63 RIMARY KEY, bloc -| 3984: 6b 20 42 4c 4f 42 29 67 01 07 17 11 11 08 81 3b k BLOB)g.......; -| 4000: 74 61 62 6c 65 74 31 74 31 43 52 45 41 54 45 20 tablet1t1CREATE -| 4016: 56 49 52 54 55 41 4c 20 54 41 42 4c 45 20 74 31 VIRTUAL TABLE t1 -| 4032: 20 55 53 49 4e 47 20 66 74 73 35 28 61 2c 62 20 USING fts5(a,b -| 4048: 75 6e 69 6e 64 65 78 65 64 2c 63 2c 74 6f 6b 65 unindexed,c,toke -| 4064: 6e 69 7a 65 3d 22 70 6f 72 74 65 72 20 61 73 63 nize=.porter asc -| 4080: 69 69 22 2c 74 6f 6b 65 6e 64 61 74 61 3d 31 29 ii.,tokendata=1) -| page 2 offset 4096 -| 0: 0d 0f 68 00 05 0f 13 00 0f e6 0f 13 0f a8 0f 7c ..h............| -| 16: 0f 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .*.............. -| 3856: 00 00 00 15 0a 03 00 30 00 00 00 00 01 03 03 00 .......0........ -| 3872: 03 01 01 01 02 01 01 03 01 01 37 8c 80 80 80 80 ..........7..... -| 3888: 01 03 00 74 00 00 00 2e 02 30 61 03 02 02 01 01 ...t.....0a..... -| 3904: 62 03 02 03 01 01 63 03 02 04 01 01 67 03 06 01 b.....c.....g... -| 3920: 02 02 01 01 68 03 06 01 02 03 01 01 69 03 06 01 ....h.......i... -| 3936: 02 04 04 06 06 06 08 08 0f ef 00 14 2a 00 00 00 ............*... -| 3952: 00 01 02 02 00 02 01 01 01 02 01 01 25 88 80 80 ............%... -| 3968: 80 80 01 03 00 50 00 00 00 1f 02 30 67 02 08 02 .....P.....0g... -| 3984: 01 02 02 01 01 68 02 08 03 01 02 03 01 01 69 02 .....h........i. -| 4000: 08 04 01 02 04 04 09 09 37 84 80 80 80 7f f1 03 ........7....... -| 4016: 00 74 00 00 00 2e 02 30 61 01 02 02 01 01 62 01 .t.....0a.....b. -| 4032: 02 03 01 01 63 01 02 04 01 01 67 01 06 01 02 02 ....c.....g..... -| 4048: 01 01 68 01 06 01 02 03 01 01 69 01 06 01 02 04 ..h.......i..... -| 4064: 04 06 06 06 08 08 07 01 03 00 14 03 09 00 09 00 ................ -| 4080: 00 00 11 24 00 00 00 00 01 01 01 00 01 01 01 01 ...$............ -| page 3 offset 8192 -| 0: 0a 00 00 00 03 0f ec 00 0f fa 0f f3 0f ec 00 00 ................ -| 4064: 00 00 00 00 00 00 00 00 00 00 00 00 06 04 01 0c ................ -| 4080: 01 03 02 06 04 01 0c 01 02 02 05 04 09 0c 01 02 ................ -| page 4 offset 12288 -| 0: 0d 00 00 00 03 0f be 00 0f ea 0f d4 0f be 00 00 ................ -| 4016: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 14 03 ................ -| 4032: 05 00 17 17 17 61 20 62 20 63 67 20 68 20 69 67 .....a b cg h ig -| 4048: 20 68 20 69 14 02 05 00 17 17 17 67 20 68 20 69 h i.......g h i -| 4064: 61 20 62 20 63 67 20 68 20 69 14 01 05 00 17 17 a b cg h i...... -| 4080: 17 61 20 62 20 63 64 20 65 20 66 67 20 68 20 69 .a b cd e fg h i -| page 5 offset 16384 -| 0: 0d 00 00 00 03 0f e8 00 0f f8 0f f0 0f e8 00 00 ................ -| 4064: 00 00 00 00 00 00 00 00 06 03 03 00 12 03 00 03 ................ -| 4080: 06 02 03 00 12 03 00 03 06 01 03 00 12 03 00 03 ................ -| page 6 offset 20480 -| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................ -| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version. -| page 7 offset 24576 -| 0: 0d 00 00 00 03 0f 9e 00 0f e6 0f ef 0f 9e 00 00 ................ -| 3984: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 41 84 ..............A. -| 4000: 80 80 80 80 01 04 00 81 06 00 00 00 34 02 30 61 ............4.0a -| 4016: 01 01 01 01 01 62 01 01 01 01 01 63 01 01 01 01 .....b.....c.... -| 4032: 01 64 01 01 01 65 01 01 01 66 01 01 01 67 01 01 .d...e...f...g.. -| 4048: 01 01 01 68 01 01 01 01 01 69 01 01 01 04 06 06 ...h.....i...... -| 4064: 06 04 04 04 06 06 07 01 03 00 14 03 09 09 09 0f ................ -| 4080: 0a 03 00 24 00 00 00 00 01 01 01 00 01 01 01 01 ...$............ -| page 8 offset 28672 -| 0: 0a 00 00 00 01 0f fa 00 0f fa 00 00 00 00 00 00 ................ -| 4080: 00 00 00 00 00 00 00 00 00 00 05 04 09 0c 01 02 ................ -| page 9 offset 32768 -| 0: 0d 00 00 00 03 0f be 00 0f ea 0f d4 0f be 00 00 ................ -| 4016: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 14 03 ................ -| 4032: 05 00 17 17 17 61 20 62 20 63 67 20 68 20 69 67 .....a b cg h ig -| 4048: 20 68 20 69 14 02 05 00 17 17 17 67 20 68 20 69 h i.......g h i -| 4064: 61 20 62 20 63 67 20 68 20 69 14 01 05 00 17 17 a b cg h i...... -| 4080: 17 61 20 62 20 63 64 20 65 20 66 67 20 68 20 69 .a b cd e fg h i -| page 10 offset 36864 -| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................ -| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version. -| end crash-d8b4a99207c10b.db -}]} {} - -do_catchsql_test 7.1 { - SELECT snippet(t1, -1, '.', '..', '[', ']'), - highlight(t1, 2, '[', ']') - FROM t1('g + h') - WHERE rank MATCH 'bm25(1.0, 1.0)' ORDER BY rank; -} {1 {database disk image is malformed}} - -#------------------------------------------------------------------------- -reset_db -do_test 8.0 { - sqlite3 db {} - db deserialize [decode_hexdb { -.open --hexdb -| size 20480 pagesize 4096 filename crash-d57c01958e48ab.db -| page 1 offset 0 -| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. -| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 05 .....@ ........ -| 32: 00 00 00 00 00 00 00 00 00 00 00 05 00 00 00 04 ................ -| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ -| 96: 00 00 00 00 0d 00 00 00 05 0e 10 00 0f 97 0f 40 ...............@ -| 112: 0e d5 0e 68 0e 10 01 00 00 00 00 00 00 00 00 00 ...h............ -| 3600: 56 05 06 17 1f 1f 01 7d 74 61 62 6c 65 74 31 5f V.......tablet1_ -| 3616: 63 6f 6e 66 69 67 74 31 5f 63 6f 6e 66 69 67 05 configt1_config. -| 3632: 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 CREATE TABLE 't1 -| 3648: 5f 63 6f 6e 66 69 67 27 28 6b 20 50 52 49 4d 41 _config'(k PRIMA -| 3664: 52 59 20 4b 45 59 2c 20 76 29 20 57 49 54 48 4f RY KEY, v) WITHO -| 3680: 55 54 20 52 4f 57 49 44 6b 04 07 17 21 21 01 81 UT ROWIDk...!!.. -| 3696: 21 74 61 62 6c 65 74 31 5f 64 6f 63 73 69 7a 65 !tablet1_docsize -| 3712: 74 31 5f 64 6f 63 73 69 7a 65 04 43 52 45 41 54 t1_docsize.CREAT -| 3728: 45 20 54 41 42 4c 45 20 27 74 31 5f 64 6f 63 73 E TABLE 't1_docs -| 3744: 69 7a 65 27 28 69 64 20 49 4e 54 45 47 45 52 20 ize'(id INTEGER -| 3760: 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 73 7a 20 PRIMARY KEY, sz -| 3776: 42 4c 4f 42 2c 20 6f 72 69 67 69 6e 20 49 4e 54 BLOB, origin INT -| 3792: 45 47 45 52 29 69 03 07 17 19 19 01 81 2d 74 61 EGER)i.......-ta -| 3808: 62 6c 65 74 31 5f 69 64 78 74 31 5f 69 64 78 03 blet1_idxt1_idx. -| 3824: 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 CREATE TABLE 't1 -| 3840: 5f 69 64 78 27 28 73 65 67 69 64 2c 20 74 65 72 _idx'(segid, ter -| 3856: 6d 2c 20 70 67 6e 6f 2c 20 50 52 49 4d 41 52 59 m, pgno, PRIMARY -| 3872: 20 4b 45 59 28 73 65 67 69 64 2c 20 74 65 72 6d KEY(segid, term -| 3888: 29 29 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 )) WITHOUT ROWID -| 3904: 55 02 07 17 1b 1b 01 81 01 74 61 62 6c 65 74 31 U........tablet1 -| 3920: 5f 64 61 74 61 74 31 5f 64 61 74 61 02 43 52 45 _datat1_data.CRE -| 3936: 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 64 61 ATE TABLE 't1_da -| 3952: 74 61 27 28 69 64 20 49 4e 54 45 47 45 52 20 50 ta'(id INTEGER P -| 3968: 52 49 4d 41 52 59 20 4b 45 59 2c 20 62 6c 6f 63 RIMARY KEY, bloc -| 3984: 6b 20 42 4c 4f 42 29 67 01 07 17 11 11 08 81 3b k BLOB)g.......; -| 4000: 74 61 62 6c 65 74 31 74 31 43 52 45 41 54 45 20 tablet1t1CREATE -| 4016: 56 49 52 54 55 41 4c 20 54 41 42 4c 45 20 74 31 VIRTUAL TABLE t1 -| 4032: 20 55 53 49 4e 47 20 66 74 73 35 28 61 2c 20 62 USING fts5(a, b -| 4048: 2c 20 63 6f 6e 74 65 6e 74 3d 27 27 2c 20 63 6f , content='', co -| 4064: 6e 74 65 6e 74 6c 65 73 73 5f 64 65 6c 65 74 65 ntentless_delete -| 4080: 3d 31 2c 20 74 6f 6b 65 6e 64 61 74 61 3d 31 29 =1, tokendata=1) -| page 2 offset 4096 -| 0: 0d 0f eb 00 03 0e 17 00 0f e2 0e 17 0e 31 00 00 .............1.. -| 16: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ -| 3600: 00 00 00 00 00 00 00 18 0a 03 00 36 00 00 00 00 ...........6.... -| 3616: ff 00 00 01 01 01 01 00 01 01 01 01 01 01 00 00 ................ -| 3632: 07 83 29 84 80 80 80 80 01 04 00 86 56 00 00 01 ..).........V... -| 3648: 96 04 30 61 61 61 01 02 02 01 04 02 04 01 08 02 ..0aaa.......... -| 3664: 04 04 04 01 10 02 04 04 04 04 04 04 04 01 20 02 .............. . -| 3680: 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 01 ................ -| 3696: 40 02 04 04 04 04 04 04 04 04 04 04 04 04 04 04 @............... -| 3712: 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 ................ -| 3728: 04 01 81 00 02 04 04 04 04 04 04 04 04 04 04 04 ................ -| 3744: 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 ................ -| 3760: 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 ................ -| 3776: 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 ................ -| 3792: 04 04 04 04 02 02 62 63 01 06 01 01 02 01 03 62 ......bc.......b -| 3808: 62 62 02 02 03 01 04 03 06 01 08 03 06 06 06 01 bb.............. -| 3824: 10 03 06 06 06 06 06 06 06 01 20 03 06 06 06 06 .......... ..... -| 3840: 06 06 06 06 06 06 06 06 06 06 06 01 40 03 06 06 ............@... -| 3856: 06 06 06 06 06 06 06 06 06 06 06 06 06 06 06 06 ................ -| 3872: 06 06 06 06 06 06 06 06 06 06 16 06 06 02 02 63 ...............c -| 3888: 64 02 06 01 01 02 01 03 63 63 63 03 02 05 01 04 d.......ccc..... -| 3904: 05 0a 01 08 05 0a 0a 0a 01 10 05 0a 0a 0a 0a 0a ................ -| 3920: 0a 0a 01 20 05 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a ... ............ -| 3936: 0a 0a 0a 0a 02 02 64 65 03 06 01 01 02 01 03 64 ......de.......d -| 3952: 64 64 04 02 09 01 04 09 12 01 08 09 12 12 12 01 dd.............. -| 3968: 10 09 12 12 12 12 12 12 12 02 02 65 66 04 06 01 ...........ef... -| 3984: 01 02 01 03 65 65 65 05 02 11 01 04 11 22 01 08 ....eee......... -| 4000: 11 22 22 22 02 02 66 67 05 06 01 01 02 01 03 66 ......fg.......f -| 4016: 56 66 06 02 21 01 04 21 42 02 02 67 68 06 06 01 Vf..!..!B..gh... -| 4032: 01 02 cb 03 67 67 67 07 02 41 02 02 68 69 07 06 ....ggg..A..hi.. -| 4048: 01 01 02 04 81 13 09 50 09 2e 09 1c 09 12 09 0c .......P........ -| 4064: 09 08 07 01 03 00 14 07 81 77 07 00 00 00 15 22 .........w...... -| 4080: 00 00 00 00 ff 00 00 01 00 00 00 00 00 00 05 0c ................ -| page 3 offset 8192 -| 0: 0a 00 00 00 01 0f fa 00 0f fa 00 00 00 00 00 00 ................ -| 4080: 00 00 00 00 00 00 00 00 00 00 05 04 09 0c 01 02 ................ -| page 4 offset 12288 -| 0: 0d 00 00 00 07 0f c8 00 0f f8 0f f0 0f e8 0f e0 ................ -| 16: 0f d8 0f d0 0f c8 00 00 00 00 00 00 00 00 00 00 ................ -| 4032: 00 00 00 00 00 00 00 00 06 07 04 00 10 09 7f 01 ................ -| 4048: 06 06 04 00 10 09 3f 01 06 05 04 00 10 09 1f 01 ......?......... -| 4064: 06 04 04 00 10 09 0f 01 06 03 04 00 10 09 07 01 ................ -| 4080: 06 02 04 00 10 09 03 01 06 01 04 00 10 09 01 01 ................ -| page 5 offset 16384 -| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................ -| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version. -| end crash-d57c01958e48ab.db -}]} {} - -do_catchsql_test 8.1 { - SELECT rowid FROM t1('a* NOT ý‘') ; -} {0 {1 2 3 4 5 6 7}} - -#------------------------------------------------------------------------- -reset_db -do_test 9.0 { - sqlite3 db {} - db deserialize [decode_hexdb { -.open --hexdb -| size 32768 pagesize 4096 filename crash-c76a16c24c8ba6.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 08 .....@ ........ -| 32: 00 00 00 02 00 00 00 01 00 00 00 09 00 00 00 04 ................ -| 96: 00 00 00 00 0d 0f c7 00 07 0d 92 00 0f 8d 0f 36 ...............6 -| 112: 0e cb 0e 6b 0e 0e 0d b6 0d 92 0d 92 00 00 00 00 ...k............ -| 3472: 00 00 22 08 06 17 11 11 01 31 74 61 62 6c 65 74 .........1tablet -| 3488: 32 74 32 08 43 52 45 41 54 45 20 54 41 42 4c 45 2t2.CREATE TABLE -| 3504: 20 74 32 28 78 29 56 07 06 17 1f 1f 01 7d 74 61 t2(x)V.......ta -| 3520: 62 6c 65 74 31 5f 63 6f 6e 66 69 67 74 31 5f 63 blet1_configt1_c -| 3536: 6f 6e 66 69 67 07 43 52 45 41 54 45 20 54 41 42 onfig.CREATE TAB -| 3552: 4c 45 20 27 74 31 5f 63 6f 6e 66 69 67 27 28 6b LE 't1_config'(k -| 3568: 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 76 29 PRIMARY KEY, v) -| 3584: 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 5b 06 WITHOUT ROWID[. -| 3600: 07 17 21 21 01 81 01 74 61 62 6c 65 74 31 5f 64 ..!!...tablet1_d -| 3616: 6f 63 73 69 7a 65 74 31 5f 64 6f 63 73 69 7a 65 ocsizet1_docsize -| 3632: 06 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 .CREATE TABLE 't -| 3648: 31 5f 64 6f 63 73 69 7a 65 27 28 69 64 20 49 4e 1_docsize'(id IN -| 3664: 54 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 TEGER PRIMARY KE -| 3680: 59 2c 20 73 7a 20 42 4c 4f 42 29 5e 05 07 17 21 Y, sz BLOB)^...! -| 3696: 21 01 81 07 74 61 62 6c 65 74 31 5f 63 6f 6e 74 !...tablet1_cont -| 3712: 65 6e 74 74 31 5f 63 6f 6e 74 65 6e 74 05 43 52 entt1_content.CR -| 3728: 45 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 63 EATE TABLE 't1_c -| 3744: 6f 6e 74 65 6e 74 27 28 69 64 20 49 4e 54 45 47 ontent'(id INTEG -| 3760: 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 ER PRIMARY KEY, -| 3776: 63 30 2c 20 63 31 2c 20 63 32 29 69 04 07 17 19 c0, c1, c2)i.... -| 3792: 19 01 81 2d 74 61 62 6c 65 74 31 5f 69 64 78 74 ...-tablet1_idxt -| 3808: 31 5f 69 64 78 04 43 52 45 41 54 45 20 54 41 42 1_idx.CREATE TAB -| 3824: 4c 45 20 27 74 31 5f 69 64 78 27 28 73 65 67 69 LE 't1_idx'(segi -| 3840: 64 2c 20 74 65 72 6d 2c 20 70 67 6e 6f 2c 20 50 d, term, pgno, P -| 3856: 52 49 4d 41 52 59 20 4b 45 59 28 73 65 67 69 64 RIMARY KEY(segid -| 3872: 2c 20 74 65 72 6d 29 29 20 57 49 54 48 4f 55 54 , term)) WITHOUT -| 3888: 20 52 4f 57 49 44 55 03 07 17 1b 1b 01 81 01 74 ROWIDU........t -| 3904: 61 62 6c 65 74 31 5f 64 61 74 61 74 31 5f 64 61 ablet1_datat1_da -| 3920: 74 61 03 43 52 45 41 54 45 20 54 41 42 4c 45 20 ta.CREATE TABLE -| 3936: 27 74 31 5f 64 61 74 61 27 28 69 64 20 49 4e 54 't1_data'(id INT -| 3952: 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 EGER PRIMARY KEY -| 3968: 2c 20 62 6c 6f 63 6b 20 42 4c 4f 42 29 38 02 06 , block BLOB)8.. -| 3984: 17 11 11 08 5f 74 61 62 6c 65 74 31 74 31 43 52 ...._tablet1t1CR -| 4000: 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 42 EATE VIRTUAL TAB -| 4016: 4c 45 20 74 31 20 55 53 49 4e 47 20 66 74 73 35 LE t1 USING fts5 -| 4032: 28 61 2c 62 2c 63 29 00 00 00 00 00 00 00 00 00 (a,b,c)......... -| page 3 offset 8192 -| 0: 0d 00 00 00 03 0c 94 00 0f e6 0f ef 0c 94 00 00 ................ -| 3216: 00 00 00 00 86 4a 84 80 80 80 80 01 04 00 8d 18 .....J.......... -| 3232: 00 00 03 2b 02 30 30 01 02 06 01 02 06 01 02 06 ...+.00......... -| 3248: 1f 02 03 01 02 03 01 02 03 01 08 32 30 31 36 30 ...........20160 -| 3264: 36 30 39 01 02 07 01 02 07 01 02 07 01 01 34 01 609...........4. -| 3280: 02 05 01 02 05 01 02 05 01 01 35 01 02 04 01 02 ..........5..... -| 3296: 04 01 02 04 02 07 30 30 30 30 30 30 30 1c 02 04 ......0000000... -| 3312: 01 02 04 01 02 04 01 06 62 69 6e 61 72 79 03 06 ........binary.. -| 3328: 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 ................ -| 3344: 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 ................ -| 3360: 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 02 ................ -| 3376: 03 06 01 02 02 03 06 01 02 02 01 08 63 6f 6d 70 ............comp -| 3392: 69 6c 65 72 01 02 02 01 02 02 01 02 02 01 06 64 iler...........d -| 3408: 62 73 74 61 74 07 02 03 01 02 03 01 02 03 02 04 bstat........... -| 3424: 65 62 75 67 04 02 02 01 02 02 01 02 02 01 06 65 ebug...........e -| 3440: 6e 61 62 6c 65 07 02 02 01 02 02 01 02 02 01 02 nable........... -| 3456: 02 01 02 02 01 02 02 01 02 02 01 02 02 01 02 02 ................ -| 3472: 01 02 02 01 02 02 01 02 02 01 02 02 01 02 02 01 ................ -| 3488: 02 02 01 02 02 01 02 02 01 02 02 01 02 02 01 02 ................ -| 3504: 02 01 02 02 02 08 78 74 65 6e 73 69 6f 6e 1f 02 ......xtension.. -| 3520: 04 01 02 04 01 02 04 01 04 66 74 73 34 0a 02 03 .........fts4... -| 3536: 01 02 03 01 02 03 04 01 35 0d 02 03 01 02 03 01 ........5....... -| 3552: 02 03 01 03 67 63 63 01 02 03 01 02 03 01 02 03 ....gcc......... -| 3568: 02 06 65 6f 70 6f 6c 79 10 02 03 01 02 03 01 02 ..eopoly........ -| 3584: 03 01 05 6a 73 6f 6e 31 13 02 03 01 02 03 01 02 ...json1........ -| 3600: 03 01 04 6c 6f 61 64 1f 02 03 01 02 03 01 02 03 ...load......... -| 3616: 01 03 6d 61 78 1c 02 02 01 02 02 01 02 02 02 05 ..max........... -| 3632: 65 6d 6f 72 79 1c 02 03 01 02 03 01 02 03 04 04 emory........... -| 3648: 73 79 73 35 16 02 03 01 02 03 01 02 03 01 06 6e sys5...........n -| 3664: 6f 63 61 73 65 02 06 01 02 02 03 06 01 02 02 03 ocase........... -| 3680: 06 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 ................ -| 3696: 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 ................ -| 3712: 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 ................ -| 3728: 02 01 04 6f 6d 69 74 1f 02 02 01 02 02 01 02 02 ...omit......... -| 3744: 01 05 72 74 72 65 65 19 02 03 01 02 03 01 02 03 ..rtree......... -| 3760: 04 02 69 6d 01 06 01 02 02 03 06 01 02 02 03 06 ..im............ -| 3776: 01 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 ................ -| 3792: 02 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 ................ -| 3808: 02 03 06 01 02 02 03 06 01 02 02 03 06 01 02 02 ................ -| 3824: 01 0a 74 68 72 65 61 64 73 61 66 65 03 57 34 56 ..threadsafe.W4V -| 3840: 94 64 91 46 85 84 04 76 74 61 62 07 02 04 01 02 .d.F...vtab..... -| 3856: 04 01 02 04 01 01 78 01 06 01 01 02 01 06 01 01 ......x......... -| 3872: 02 01 06 01 01 02 01 06 01 01 02 01 06 01 10 02 ................ -| 3888: 01 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 ................ -| 3904: 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 ................ -| 3920: 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 ................ -| 3936: 01 02 01 06 01 01 10 01 06 01 01 02 01 06 01 01 ................ -| 3952: 02 01 06 01 01 02 01 06 01 01 02 01 06 01 01 02 ................ -| 3968: 01 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 ................ -| 3984: 06 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 ................ -| 4000: 01 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 ................ -| 4016: 01 02 01 06 01 01 02 01 06 01 01 02 01 06 01 01 ................ -| 4032: 02 01 06 01 01 02 01 06 01 01 02 04 15 13 0c 0c ................ -| 4048: 12 44 13 11 0f 47 13 0f 0c 0e 11 10 0f 0e 10 0f .D...G.......... -| 4064: 44 0f 10 40 15 0f 07 01 03 00 14 24 5a 24 24 0f D..@.......$Z$$. -| 4080: 0a 03 00 24 00 00 00 00 01 01 01 00 01 01 01 01 ...$............ -| page 4 offset 12288 -| 0: 0a 00 00 00 01 0f fa 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 5 offset 16384 -| 0: 0d 00 00 00 24 0c 0a 00 0f d8 0f af 0f 86 0f 74 ....$..........t -| 16: 0f 61 0f 4e 0f 2f 0f 0f 0e ef 0e d7 0e be 0e a5 .a.N./.......... -| 32: 0e 8d 0e 74 0e 5b 0e 40 0e 24 0e 08 0d ef 0d d5 ...t.[.@.$...... -| 48: 0d bb 0d a0 0d 84 0d 68 0d 4f 0d 35 0d 1b 0c fb .......h.O.5.... -| 64: 0c da 0c b9 0c 99 0c 78 0c 57 0c 3e 0c 24 0c 0a .......x.W.>.$.. -| 3072: 00 00 00 00 00 00 00 00 00 00 18 24 05 00 25 0f ...........$..%. -| 3088: 19 54 48 52 45 41 44 53 41 46 45 3d 30 58 42 49 .THREADSAFE=0XBI -| 3104: 4e 41 52 59 18 23 05 00 25 0f 19 54 48 52 45 41 NARY.#..%..THREA -| 3120: 44 53 41 46 45 3d 30 58 4e 4f 43 41 53 45 17 22 DSAFE=0XNOCASE.. -| 3136: 05 00 25 0f 17 54 48 52 45 41 44 53 31 46 45 3d ..%..THREADS1FE= -| 3152: 30 58 52 64 52 49 4d 1f 21 05 00 33 0f 19 4f 4d 0XRdRIM.!..3..OM -| 3168: 49 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 49 4f IT LOAD EXTENSIO -| 3184: 4e 58 42 49 4e 41 52 59 1f 20 05 00 33 0f 19 4f NXBINARY. ..3..O -| 3200: 4d 49 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 49 MIT LOAD EXTENSI -| 3216: 4f 4e 58 4e 4f 43 41 53 45 1e 1f 05 00 33 0f 17 ONXNOCASE....3.. -| 3232: 4f 4d 49 54 20 4c 4f 41 44 20 45 58 54 45 4e 53 OMIT LOAD EXTENS -| 3248: 49 4f 4e 58 52 54 52 49 4d 1f 1e 05 00 33 0f 19 IONXRTRIM....3.. -| 3264: 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 30 30 MAX MEMORY=50000 -| 3280: 30 30 30 58 42 49 4e 41 52 59 1f 1d 05 00 33 0f 000XBINARY....3. -| 3296: 19 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 30 .MAX MEMORY=5000 -| 3312: 30 30 30 30 58 4e 4f 43 41 53 45 1e 1c 05 00 33 0000XNOCASE....3 -| 3328: 0f 17 4d 41 58 20 4d 45 4d 4f 52 59 3d 35 30 30 ..MAX MEMORY=500 -| 3344: 30 30 30 30 30 58 52 54 52 49 4d 18 1b 05 00 25 00000XRTRIM....% -| 3360: 0f 19 45 4e 41 42 4c 45 20 52 54 52 45 45 58 42 ..ENABLE RTREEXB -| 3376: 49 4e 41 52 59 18 1a 05 00 25 0f 19 45 4e 41 42 INARY....%..ENAB -| 3392: 4c 45 20 52 54 52 45 45 58 4e 4f 43 41 53 45 17 LE RTREEXNOCASE. -| 3408: 19 05 00 25 0f 17 45 4e 41 42 4c 45 20 52 54 52 ...%..ENABLE RTR -| 3424: 45 45 58 52 54 52 49 4d 1a 18 05 00 29 0f 19 45 EEXRTRIM....)..E -| 3440: 4e 41 42 4b 45 20 4d 45 4d 53 59 53 35 58 42 49 NABKE MEMSYS5XBI -| 3456: 4e 41 52 59 1a 17 05 00 29 0f 19 45 4e 41 42 4c NARY....)..ENABL -| 3472: 42 60 2d 45 4d 53 59 53 35 58 4e 4f 43 41 53 45 B`-EMSYS5XNOCASE -| 3488: 19 16 05 00 29 0f 17 45 4e 41 42 4c 45 20 4d 45 ....)..ENABLE ME -| 3504: 4d 53 59 53 35 58 52 54 52 49 4d 18 15 05 00 25 MSYS5XRTRIM....% -| 3520: 0f 19 45 4e 41 42 4c 45 20 4a 53 4f 4e 31 58 42 ..ENABLE JSON1XB -| 3536: 49 4e 41 52 59 18 14 05 00 25 0f 19 45 4e 41 42 INARY....%..ENAB -| 3552: 4c 45 20 4a 53 4f 4e 31 58 4e 4f 43 41 53 45 17 LE JSON1XNOCASE. -| 3568: 13 05 00 25 0f 17 45 4e 41 42 4c 45 20 4a 53 4f ...%..ENABLE JSO -| 3584: 4e 31 58 52 54 52 49 4d 1a 12 05 00 29 0f 19 45 N1XRTRIM....)..E -| 3600: 4e 41 42 4c 45 20 47 45 4f 50 4f 4c 59 58 42 49 NABLE GEOPOLYXBI -| 3616: 4e 41 52 59 1a 11 05 00 39 0f 19 45 4e 41 42 4c NARY....9..ENABL -| 3632: 45 20 47 45 4f 50 4f 4c 59 58 4e 4f 43 41 53 45 E GEOPOLYXNOCASE -| 3648: 19 10 05 00 29 0f 17 45 4e 41 42 4c 45 20 47 45 ....)..ENABLE GE -| 3664: 4f 50 4f 4c 59 58 52 54 52 49 4d 17 0f 05 00 23 OPOLYXRTRIM....# -| 3680: 0f 19 45 4e 41 42 4c 45 20 46 54 53 35 58 42 49 ..ENABLE FTS5XBI -| 3696: 4e 41 52 59 17 0e 05 00 23 0f 19 45 4e 41 42 4c NARY....#..ENABL -| 3712: 45 20 46 54 53 35 58 4e 4f 43 41 53 45 16 0d 05 E FTS5XNOCASE... -| 3728: 00 23 0f 17 45 4e 41 42 4c 45 20 46 54 53 35 58 .#..ENABLE FTS5X -| 3744: 52 54 52 49 4d 17 0c 05 00 23 0f 19 45 4e 41 42 RTRIM....#..ENAB -| 3760: 4c 45 20 46 54 53 34 58 42 49 4e 41 52 59 17 0b LE FTS4XBINARY.. -| 3776: 05 00 23 0f 19 45 4e 41 42 4c 45 20 46 54 53 34 ..#..ENABLE FTS4 -| 3792: 58 4e 4f 43 41 53 45 16 0a 05 00 23 0f 17 45 4e XNOCASE....#..EN -| 3808: 41 42 4c 45 20 46 54 53 34 58 52 54 52 49 4d 1e ABLE FTS4XRTRIM. -| 3824: 09 05 00 31 0f 19 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS -| 3840: 54 41 54 20 56 54 41 42 58 42 49 4e 41 52 59 1e TAT VTABXBINARY. -| 3856: 08 05 00 31 0f 19 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS -| 3872: 54 41 54 20 56 54 24 15 48 4e 4f 43 41 53 45 1d TAT VT$.HNOCASE. -| 3888: 07 05 00 31 0f 17 45 4e 41 42 4c 45 20 44 42 53 ...1..ENABLE DBS -| 3904: 54 41 54 20 56 54 41 42 58 52 54 52 49 4d 11 06 TAT VTABXRTRIM.. -| 3920: 05 00 17 0f 19 44 45 42 55 47 58 42 49 4e 41 52 .....DEBUGXBINAR -| 3936: 59 11 05 05 00 17 0f 19 44 45 42 55 47 58 4e 4f Y.......DEBUGXNO -| 3952: 43 41 53 45 10 04 05 00 17 0f 17 44 45 42 55 47 CASE.......DEBUG -| 3968: 58 52 54 52 49 4d 27 03 05 00 43 0f 19 43 4f 4d XRTRIM'...C..COM -| 3984: 50 49 4c 45 52 3d 67 63 63 2d 35 2e 34 2e 30 20 PILER=gcc-5.4.0 -| 4000: 32 30 31 36 30 36 30 39 58 42 49 4e 41 52 59 27 20160609XBINARY' -| 4016: 02 05 00 43 0f 19 43 4f 4d 50 49 4c 45 52 3c 67 ...C..COMPILER100; } {101} - -do_execsql_test 1.9 { - DELETE FROM ft; - INSERT INTO ft(ft) VALUES('optimize'); - SELECT count(*) FROM ft_data; -} {2} -do_execsql_test 1.10 { - BEGIN; - INSERT INTO ft VALUES('Hello'); - INSERT INTO ft VALUES('hello'); - INSERT INTO ft VALUES('HELLO'); - INSERT INTO ft VALUES('today'); - INSERT INTO ft VALUES('today'); - INSERT INTO ft VALUES('today'); - INSERT INTO ft VALUES('World'); - INSERT INTO ft VALUES('world'); - INSERT INTO ft VALUES('WORLD'); -} - -do_execsql_test 1.11 { SELECT rowid FROM ft('hello'); } {1 2 3} -do_execsql_test 1.12 { SELECT rowid FROM ft('today'); } {4 5 6} -do_execsql_test 1.13 { SELECT rowid FROM ft('world'); } {7 8 9} -do_execsql_test 1.14 { SELECT rowid FROM ft('hello') ORDER BY rank; } {1 2 3} - -#------------------------------------------------------------------------ -reset_db -sqlite3_fts5_register_origintext db -proc tokens {cmd} { - set ret [list] - for {set iTok 0} {$iTok < [$cmd xInstCount]} {incr iTok} { - set txt [$cmd xInstToken $iTok 0] - set txt [string map [list "\0" "."] $txt] - lappend ret $txt - } - set ret -} -sqlite3_fts5_create_function db tokens tokens - -do_execsql_test 2.0 { - CREATE VIRTUAL TABLE x1 USING fts5( - v, tokenize="origintext unicode61", tokendata=1, detail=none - ); - - INSERT INTO x1 VALUES('xxx Xxx XXX yyy YYY yyy'); - INSERT INTO x1 VALUES('xxx yyy xxx yyy yyy yyy'); -} - -do_execsql_test 2.1 { - SELECT tokens(x1) FROM x1('xxx'); -} { - {xxx xxx.Xxx xxx.XXX} {xxx xxx} -} - -do_execsql_test 2.2 { - UPDATE x1_content SET c0 = 'xxx xxX xxx yyy yyy yyy' WHERE id=1; -} - -do_execsql_test 2.3 { - SELECT tokens(x1) FROM x1('xxx'); -} { - {xxx {} xxx} {xxx xxx} -} - -finish_test - DELETED ext/fts5/test/fts5origintext3.test Index: ext/fts5/test/fts5origintext3.test ================================================================== --- ext/fts5/test/fts5origintext3.test +++ /dev/null @@ -1,101 +0,0 @@ -# 2023 November 22 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# -# Tests focused on phrase queries. -# - -source [file join [file dirname [info script]] fts5_common.tcl] -set testprefix fts5origintext3 - -# If SQLITE_ENABLE_FTS5 is defined, omit this file. -ifcapable !fts5 { - finish_test - return -} - -foreach_detail_mode $testprefix { - reset_db - - sqlite3_fts5_register_origintext db - fts5_aux_test_functions db - proc insttoken {cmd iIdx iToken} { - set txt [$cmd xInstToken $iIdx $iToken] - string map [list "\0" "."] $txt - } - sqlite3_fts5_create_function db insttoken insttoken - - do_execsql_test 1.0 { - CREATE VIRTUAL TABLE ft USING fts5( - x, tokenize="origintext unicode61", tokendata=1, detail=%DETAIL% - ); - } - - do_execsql_test 1.1 { - INSERT INTO ft VALUES('Hello world HELLO WORLD hello'); - } - - do_execsql_test 1.2 { - SELECT fts5_test_poslist(ft) FROM ft('hello'); - } {{0.0.0 0.0.2 0.0.4}} - - do_execsql_test 1.3 { - SELECT - insttoken(ft, 0, 0), - insttoken(ft, 1, 0), - insttoken(ft, 2, 0) - FROM ft('hello'); - } {hello.Hello hello.HELLO hello} - - do_execsql_test 1.4 { - SELECT - insttoken(ft, 0, 0), - insttoken(ft, 1, 0), - insttoken(ft, 2, 0) - FROM ft('hello') ORDER BY rank; - } {hello.Hello hello.HELLO hello} - - do_execsql_test 1.5 { - CREATE VIRTUAL TABLE ft2 USING fts5( - x, tokenize="origintext unicode61", tokendata=1, detail=%DETAIL% - ); - INSERT INTO ft2(rowid, x) VALUES(1, 'ONE one two three ONE'); - INSERT INTO ft2(rowid, x) VALUES(2, 'TWO one two three TWO'); - INSERT INTO ft2(rowid, x) VALUES(3, 'THREE one two three THREE'); - } - - do_execsql_test 1.6 { - SELECT insttoken(ft2, 0, 0), rowid FROM ft2('three') ORDER BY rank; - } {three.THREE 3 three 1 three 2} - - do_execsql_test 1.7 { - INSERT INTO ft2(rowid, x) VALUES(10, 'aaa bbb BBB'); - INSERT INTO ft2(rowid, x) VALUES(12, 'bbb bbb bbb'); - INSERT INTO ft2(rowid, x) VALUES(13, 'bbb bbb bbb'); - INSERT INTO ft2(rowid, x) VALUES(14, 'bbb BBB bbb'); - INSERT INTO ft2(rowid, x) VALUES(15, 'bbb bbb bbb'); - INSERT INTO ft2(rowid, x) VALUES(16, 'bbb bbb bbb'); - INSERT INTO ft2(rowid, x) VALUES(17, 'bbb bbb bbb'); - INSERT INTO ft2(rowid, x) VALUES(18, 'bbb bbb bbb'); - INSERT INTO ft2(rowid, x) VALUES(19, 'bbb bbb bbb'); - INSERT INTO ft2(rowid, x) VALUES(20, 'bbb bbb bbb'); - INSERT INTO ft2(rowid, x) VALUES(21, 'bbb bbb bbb'); - INSERT INTO ft2(rowid, x) VALUES(22, 'bbb bbb bbb'); - INSERT INTO ft2(rowid, x) VALUES(23, 'bbb bbb bbb'); - INSERT INTO ft2(rowid, x) VALUES(24, 'aaa bbb BBB'); - } - - do_execsql_test 1.8 { SELECT rowid FROM ft2('aaa AND bbb'); } {10 24} - do_execsql_test 1.9 { SELECT rowid FROM ft2('bbb AND aaa'); } {10 24} - -} - -finish_test - DELETED ext/fts5/test/fts5origintext4.test Index: ext/fts5/test/fts5origintext4.test ================================================================== --- ext/fts5/test/fts5origintext4.test +++ /dev/null @@ -1,80 +0,0 @@ -# 2023 November 22 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# -# Tests focused on phrase queries. -# - -source [file join [file dirname [info script]] fts5_common.tcl] -set testprefix fts5origintext4 - -# If SQLITE_ENABLE_FTS5 is defined, omit this file. -ifcapable !fts5 { - finish_test - return -} - -# The tests below verify that a doclist-index is used to limit the number -# of pages loaded into the cache. It does this by querying sqlite3_db_status() -# for the amount of memory used by the pager cache. -# -# memsubsys1 effectively limits the page-cache to 24 pages. Which masks -# the effect tested by the tests in this file. And "mmap" prevents the -# cache from being used, also preventing these tests from working. -# -if {[permutation]=="memsubsys1" || [permutation]=="mmap"} { - finish_test - return -} - -sqlite3_fts5_register_origintext db -do_execsql_test 1.0 { - PRAGMA page_size = 4096; - CREATE VIRTUAL TABLE ft USING fts5( - x, tokenize="origintext unicode61", tokendata=1 - ); -} - -do_execsql_test 1.1 { - BEGIN; - INSERT INTO ft SELECT 'the first thing'; - - WITH s(i) AS ( - SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<90000 - ) - INSERT INTO ft SELECT 'The second thing' FROM s; - - INSERT INTO ft SELECT 'the first thing'; - COMMIT; - INSERT INTO ft(ft) VALUES('optimize'); -} - -foreach {tn sql expr} { - 1 { SELECT rowid FROM ft('the') } {$mem > 250000} - 2 { SELECT rowid FROM ft('first') } {$mem < 50000} - 3 { SELECT rowid FROM ft('the first') } {$mem < 50000} -} { - db close - sqlite3 db test.db - sqlite3_fts5_register_origintext db - - execsql $sql - do_test 1.2.$tn { - set mem [lindex [sqlite3_db_status db CACHE_USED 0] 1] - expr $expr - } 1 -} - -proc b {x} { string map [list "\0" "."] $x } -db func b b -# execsql_pp { SELECT segid, b(term), pgno from ft_idx } - -finish_test - DELETED ext/fts5/test/fts5origintext5.test Index: ext/fts5/test/fts5origintext5.test ================================================================== --- ext/fts5/test/fts5origintext5.test +++ /dev/null @@ -1,273 +0,0 @@ -# 2023 Dec 04 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# -# Tests for tables that use both tokendata=1 and contentless_delete=1. -# - -source [file join [file dirname [info script]] fts5_common.tcl] -set testprefix fts5origintext - -# If SQLITE_ENABLE_FTS5 is defined, omit this file. -ifcapable !fts5 { - finish_test - return -} - -# Return a random integer between 0 and n-1. -# -proc random {n} { expr {abs(int(rand()*$n))} } - -# Select an element of the list passed as the only argument at random and -# return it. -# -proc select_one {list} { - set n [llength $list] - lindex $list [random $n] -} - -# Given a term that consists entirely of alphabet characters, return all -# permutations of the term using upper and lower case characters. e.g. -# -# "abc" -> {CBA cBA CbA cbA CBa cBa Cba cba} -# -proc casify {term {lRet {{}}}} { - if {$term==""} { return $lRet } - set t [string range $term 1 end] - set f1 [string toupper [string range $term 0 0]] - set f2 [string tolower [string range $term 0 0]] - set ret [list] - foreach x $lRet { - lappend ret "$x$f1" - lappend ret "$x$f2" - } - return [casify $t $ret] -} - -proc vocab {} { - list abc def ghi jkl mno pqr stu vwx yza -} - -# Return a random 3 letter term. -# -proc term {} { - if {[info exists ::expanded_vocab]==0} { - foreach v [vocab] { lappend ::expanded_vocab {*}[casify $v] } - } - - select_one $::expanded_vocab -} - -# Return a document - between 3 and 10 terms. -# -proc document {} { - set nTerm [expr [random 3] + 7] - set doc "" - for {set ii 0} {$ii < $nTerm} {incr ii} { - lappend doc [term] - } - set doc -} -db func document document - -#------------------------------------------------------------------------- - -expr srand(6) - -set NDOC 200 -set NLOOP 50 - -sqlite3_fts5_register_origintext db - -proc tokens {cmd} { - set ret [list] - for {set iTok 0} {$iTok < [$cmd xInstCount]} {incr iTok} { - set txt [$cmd xInstToken $iTok 0] - set txt [string map [list "\0" "."] $txt] - lappend ret $txt - } - set ret -} -sqlite3_fts5_create_function db tokens tokens - -proc rankfunc {cmd} { - $cmd xRowid -} -sqlite3_fts5_create_function db rankfunc rankfunc - -proc ctrl_tokens {term args} { - set ret [list] - set term [string tolower $term] - foreach doc $args { - foreach a $doc { - if {[string tolower $a]==$term} { - if {$a==$term} { - lappend ret $a - } else { - lappend ret [string tolower $a].$a - } - } - } - } - set ret -} -db func ctrl_tokens ctrl_tokens - -proc do_all_vocab_test {tn} { - foreach ::v [concat [vocab] nnn] { - set answer [execsql { - SELECT id, ctrl_tokens($::v, x) FROM ctrl WHERE x LIKE '%' || $::v || '%' - }] - do_execsql_test $tn.$::v.1 { - SELECT rowid, tokens(ft) FROM ft($::v) - } $answer - do_execsql_test $tn.$::v.2 { - SELECT rowid, tokens(ft) FROM ft($::v) ORDER BY rank - } $answer - } -} - -do_execsql_test 1.0 { - CREATE VIRTUAL TABLE ft USING fts5( - x, tokenize="origintext unicode61", content=, contentless_delete=1, - tokendata=1 - ); - - CREATE TABLE ctrl(id INTEGER PRIMARY KEY, x TEXT); - INSERT INTO ft(ft, rank) VALUES('pgsz', 64); - INSERT INTO ft(ft, rank) VALUES('rank', 'rankfunc()'); -} -do_test 1.1 { - for {set ii 0} {$ii < $NDOC} {incr ii} { - set doc [document] - execsql { - INSERT INTO ft(rowid, x) VALUES($ii, $doc); - INSERT INTO ctrl(id, x) VALUES($ii, $doc); - } - } -} {} - -#execsql_pp { SELECT * FROM ctrl } -#execsql_pp { SELECT * FROM ft } -#fts5_aux_test_functions db -#execsql_pp { SELECT rowid, tokens(ft), fts5_test_poslist(ft) FROM ft('ghi'); } - -do_all_vocab_test 1.2 - -for {set ii 0} {$ii < $NLOOP} {incr ii} { - set lRowid [execsql { SELECT id FROM ctrl WHERE random() % 2 }] - foreach r $lRowid { - execsql { DELETE FROM ft WHERE rowid = $r } - execsql { DELETE FROM ctrl WHERE rowid = $r } - - set doc [document] - execsql { INSERT INTO ft(rowid, x) VALUES($r, $doc) } - execsql { INSERT INTO ctrl(id, x) VALUES($r, $doc) } - } - do_all_vocab_test 1.3.$ii -} - -#------------------------------------------------------------------------- - -do_execsql_test 2.0 { - CREATE VIRTUAL TABLE ft2 USING fts5( - x, y, tokenize="origintext unicode61", content=, contentless_delete=1, - tokendata=1 - ); - - CREATE TABLE ctrl2(id INTEGER PRIMARY KEY, x TEXT, y TEXT); - INSERT INTO ft2(ft2, rank) VALUES('pgsz', 64); - INSERT INTO ft2(ft2, rank) VALUES('rank', 'rankfunc()'); -} -do_test 2.1 { - for {set ii 0} {$ii < $NDOC} {incr ii} { - set doc1 [document] - set doc2 [document] - execsql { - INSERT INTO ft2(rowid, x, y) VALUES($ii, $doc, $doc2); - INSERT INTO ctrl2(id, x, y) VALUES($ii, $doc, $doc2); - } - } -} {} - -proc do_all_vocab_test2 {tn} { - foreach ::v [vocab] { - set answer [execsql { - SELECT id, ctrl_tokens($::v, x, y) FROM ctrl2 - WHERE x LIKE '%' || $::v || '%' OR y LIKE '%' || $::v || '%'; - }] - do_execsql_test $tn.$::v.1 { - SELECT rowid, tokens(ft2) FROM ft2($::v) - } $answer - do_execsql_test $tn.$::v.2 { - SELECT rowid, tokens(ft2) FROM ft2($::v) ORDER BY rank - } $answer - } -} - -do_all_vocab_test2 2.2 - -for {set ii 0} {$ii < $NLOOP} {incr ii} { - set lRowid [execsql { SELECT id FROM ctrl2 WHERE random() % 2 }] - foreach r $lRowid { - execsql { DELETE FROM ft2 WHERE rowid = $r } - execsql { DELETE FROM ctrl2 WHERE rowid = $r } - - set doc1 [document] - set doc2 [document] - execsql { INSERT INTO ft2(rowid, x, y) VALUES($r, $doc, $doc1) } - execsql { INSERT INTO ctrl2(id, x, y) VALUES($r, $doc, $doc2) } - } - do_all_vocab_test 2.3.$ii -} - -#------------------------------------------------------------------------- - -unset -nocomplain ::expanded_vocab -proc vocab {} { - list abcde fghij klmno -} - -proc do_all_vocab_test3 {tn} { - foreach ::v [concat [vocab] nnn] { - set answer [execsql { - SELECT rowid, ctrl_tokens($::v, w) FROM ctrl3 WHERE w LIKE '%' || $::v || '%' - }] - do_execsql_test $tn.$::v.1 { - SELECT rowid, tokens(ft3) FROM ft3($::v) - } $answer - do_execsql_test $tn.$::v.2 { - SELECT rowid, tokens(ft3) FROM ft3($::v) ORDER BY rank - } $answer - } -} - -do_execsql_test 3.0 { - CREATE VIRTUAL TABLE ft3 USING fts5( - w, tokenize="origintext unicode61", content=, contentless_delete=1, - tokendata=1 - ); - INSERT INTO ft3(ft3, rank) VALUES('rank', 'rankfunc()'); - CREATE TABLE ctrl3(w); -} - -do_execsql_test 3.1 { - WITH s(i) AS ( - SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<2 - ) - INSERT INTO ctrl3 SELECT document() FROM s; - INSERT INTO ft3(rowid, w) SELECT rowid, w FROM ctrl3; -} - -do_all_vocab_test3 3.2 - - -finish_test - Index: ext/fts5/test/fts5secure3.test ================================================================== --- ext/fts5/test/fts5secure3.test +++ ext/fts5/test/fts5secure3.test @@ -84,80 +84,75 @@ #------------------------------------------------------------------------- # Tests with large/small rowid values. # -foreach {tn cfg} { - 1 "" - 2 "INSERT INTO fff(fff, rank) VALUES('secure-delete', 1)" -} { - reset_db - - expr srand(0) - - set vocab { - Popper Poppins Popsicle Porfirio Porrima Porsche - Porter Portia Portland Portsmouth Portugal Portuguese - Poseidon Post PostgreSQL Potemkin Potomac Potsdam - Pottawatomie Potter Potts Pound Poussin Powell - PowerPC PowerPoint Powers Powhatan Poznan Prada - Prado Praetorian Prague Praia Prakrit Pratchett - Pratt Pravda Praxiteles Preakness Precambrian Preminger - Premyslid Prensa Prentice Pres Presbyterian Presbyterianism - } - proc newdoc {} { - for {set i 0} {$i<8} {incr i} { - lappend ret [lindex $::vocab [expr int(abs(rand()) * [llength $::vocab])]] - } - set ret - } - db func newdoc newdoc - - do_execsql_test 3.$tn.0 { - CREATE VIRTUAL TABLE fff USING fts5(y); - INSERT INTO fff(fff, rank) VALUES('pgsz', 64); - - WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 ) - INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s; - - WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 ) - INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s; - - WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 ) - INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s; - } - - execsql $cfg - - proc lshuffle {in} { - set out [list] - while {[llength $in]>0} { - set idx [expr int(abs(rand()) * [llength $in])] - lappend out [lindex $in $idx] - set in [lreplace $in $idx $idx] - } - set out - } - - #dump fff - - set iTest 1 - foreach ii [lshuffle [db eval {SELECT rowid FROM fff}]] { - #if {$iTest==1} { dump fff } - #if {$iTest==1} { breakpoint } - do_execsql_test 3.$tn.1.$iTest.$ii { - DELETE FROM fff WHERE rowid=$ii; - } - #if {$iTest==1} { dump fff } - if {($iTest % 20)==0} { - do_execsql_test 3.$tn.1.$iTest.$ii.ic { - INSERT INTO fff(fff) VALUES('integrity-check'); - } - } - #if {$iTest==1} { break } - incr iTest - } +reset_db + +expr srand(0) + +set vocab { + Popper Poppins Popsicle Porfirio Porrima Porsche + Porter Portia Portland Portsmouth Portugal Portuguese + Poseidon Post PostgreSQL Potemkin Potomac Potsdam + Pottawatomie Potter Potts Pound Poussin Powell + PowerPC PowerPoint Powers Powhatan Poznan Prada + Prado Praetorian Prague Praia Prakrit Pratchett + Pratt Pravda Praxiteles Preakness Precambrian Preminger + Premyslid Prensa Prentice Pres Presbyterian Presbyterianism +} +proc newdoc {} { + for {set i 0} {$i<8} {incr i} { + lappend ret [lindex $::vocab [expr int(abs(rand()) * [llength $::vocab])]] + } + set ret +} +db func newdoc newdoc + +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE fff USING fts5(y); + INSERT INTO fff(fff, rank) VALUES('pgsz', 64); + + WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 ) + INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s; + + WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 ) + INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s; + + WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 ) + INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s; + + INSERT INTO fff(fff, rank) VALUES('secure-delete', 1); +} + +proc lshuffle {in} { + set out [list] + while {[llength $in]>0} { + set idx [expr int(abs(rand()) * [llength $in])] + lappend out [lindex $in $idx] + set in [lreplace $in $idx $idx] + } + set out +} + +#dump fff + +set iTest 1 +foreach ii [lshuffle [db eval {SELECT rowid FROM fff}]] { + #if {$iTest==1} { dump fff } + #if {$iTest==1} { breakpoint } + do_execsql_test 3.1.$iTest.$ii { + DELETE FROM fff WHERE rowid=$ii; + } + #if {$iTest==1} { dump fff } + if {($iTest % 20)==0} { + do_execsql_test 3.1.$iTest.$ii.ic { + INSERT INTO fff(fff) VALUES('integrity-check'); + } + } + #if {$iTest==1} { break } + incr iTest } #execsql_pp { SELECT rowid FROM fff('post') ORDER BY rowid ASC } #breakpoint #execsql_pp { DELETED ext/fts5/test/fts5secure8.test Index: ext/fts5/test/fts5secure8.test ================================================================== --- ext/fts5/test/fts5secure8.test +++ /dev/null @@ -1,51 +0,0 @@ -# 2023 Nov 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. -# -#************************************************************************* -# - -source [file join [file dirname [info script]] fts5_common.tcl] -ifcapable !fts5 { finish_test ; return } -set ::testprefix fts5secure8 - -proc sql_repeat {txt n} { - string repeat $txt $n -} -db func repeat sql_repeat - -do_execsql_test 1.0 { - CREATE VIRTUAL TABLE ft USING fts5(x); - - INSERT INTO ft(ft, rank) VALUES('pgsz', 64); - - INSERT INTO ft(rowid, x) VALUES(100, 'hello world'); - INSERT INTO ft(rowid, x) VALUES(200, 'one day'); - - BEGIN; - INSERT INTO ft(rowid, x) VALUES(45, 'one two three'); - UPDATE ft SET x = repeat('hello world ', 500) WHERE rowid=100; - COMMIT -} - -do_execsql_test 1.1 { - INSERT INTO ft(ft, rank) VALUES('secure-delete', 1); - DELETE FROM ft WHERE rowid=100; -} - -do_execsql_test 1.2 { - PRAGMA integrity_check; -} {ok} - - - - - -finish_test - - Index: ext/fts5/test/fts5simple2.test ================================================================== --- ext/fts5/test/fts5simple2.test +++ ext/fts5/test/fts5simple2.test @@ -341,13 +341,11 @@ INSERT INTO t2 VALUES('a aa aaa', 'b bb bbb'); INSERT INTO t2 VALUES('a aa aaa', 'b bb bbb'); INSERT INTO t2 VALUES('a aa aaa', 'b bb bbb'); COMMIT; } -do_execsql_test 17.1 { - SELECT * FROM t2('y:a*') WHERE rowid BETWEEN 10 AND 20 -} +do_execsql_test 17.1 { SELECT * FROM t2('y:a*') WHERE rowid BETWEEN 10 AND 20 } do_execsql_test 17.2 { BEGIN; INSERT INTO t2 VALUES('a aa aaa', 'b bb bbb'); SELECT * FROM t2('y:a*') WHERE rowid BETWEEN 10 AND 20 ; } Index: ext/fts5/test/fts5synonym2.test ================================================================== --- ext/fts5/test/fts5synonym2.test +++ ext/fts5/test/fts5synonym2.test @@ -40,11 +40,11 @@ list [sort_poslist $PL] $CL } sqlite3_fts5_create_function db fts5_test_bothlist fts5_test_bothlist -proc fts5_rowid {cmd} { expr [$cmd xRowid] } +proc fts5_rowid {cmd} { expr [$cmd xColumnText -1] } sqlite3_fts5_create_function db fts5_rowid fts5_rowid do_execsql_test 1.$tok.0.1 " CREATE VIRTUAL TABLE ss USING fts5(a, b, tokenize='tclnum $tok', detail=%DETAIL%); DELETED ext/fts5/test/fts5tokenizer2.test Index: ext/fts5/test/fts5tokenizer2.test ================================================================== --- ext/fts5/test/fts5tokenizer2.test +++ /dev/null @@ -1,89 +0,0 @@ -# 2023 Nov 03 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# -# Tests focusing on the built-in fts5 tokenizers. -# - -source [file join [file dirname [info script]] fts5_common.tcl] -set testprefix fts5tokenizer2 - -# If SQLITE_ENABLE_FTS5 is defined, omit this file. -ifcapable !fts5 { - finish_test - return -} - -sqlite3_fts5_create_tokenizer db tst get_tst_tokenizer -proc get_tst_tokenizer {args} { - return "tst_tokenizer" -} -proc tst_tokenizer {flags txt} { - set token "" - set lTok [list] - - foreach c [split $txt {}] { - if {$token==""} { - append token $c - } else { - set t1 [string is upper $token] - set t2 [string is upper $c] - - if {$t1!=$t2} { - lappend lTok $token - set token "" - } - append token $c - } - } - if {$token!=""} { lappend lTok $token } - - set iOff 0 - foreach t $lTok { - set n [string length $t] - sqlite3_fts5_token $t $iOff [expr $iOff+$n] - incr iOff $n - } -} - -do_execsql_test 1.0 { - CREATE VIRTUAL TABLE t1 USING fts5(t, tokenize=tst); -} - -do_execsql_test 1.1 { - INSERT INTO t1 VALUES('AAdontBBmess'); -} - -do_execsql_test 1.2 { - SELECT snippet(t1, 0, '>', '<', '...', 4) FROM t1('BB'); -} {AAdont>BB', '<') FROM t1('BB'); -} {AAdont>BB', '<') FROM t1('AA'); -} {>AA', '<') FROM t1('dont'); -} {AA>dont', '<') FROM t1('mess'); -} {AAdontBB>mess<} - -do_execsql_test 1.7 { - SELECT highlight(t1, 0, '>', '<') FROM t1('BB mess'); -} {AAdont>BBmess<} - - -finish_test DELETED ext/fts5/test/fts5trigram2.test Index: ext/fts5/test/fts5trigram2.test ================================================================== --- ext/fts5/test/fts5trigram2.test +++ /dev/null @@ -1,109 +0,0 @@ -# 2023 October 24 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#************************************************************************* -# -# Tests for the fts5 "trigram" tokenizer. -# - -source [file join [file dirname [info script]] fts5_common.tcl] -ifcapable !fts5 { finish_test ; return } -set ::testprefix fts5trigram2 - -do_execsql_test 1.0 " - CREATE VIRTUAL TABLE t1 USING fts5(y, tokenize='trigram remove_diacritics 1'); - INSERT INTO t1 VALUES('abc\u0303defghijklm'); - INSERT INTO t1 VALUES('a\u0303b\u0303c\u0303defghijklm'); -" - -do_execsql_test 1.1 { - SELECT highlight(t1, 0, '(', ')') FROM t1('abc'); -} [list \ - "(abc\u0303)defghijklm" \ - "(a\u0303b\u0303c\u0303)defghijklm" \ -] - -do_execsql_test 1.2 { - SELECT highlight(t1, 0, '(', ')') FROM t1('bcde'); -} [list \ - "a(bc\u0303de)fghijklm" \ - "a\u0303(b\u0303c\u0303de)fghijklm" \ -] - -do_execsql_test 1.3 { - SELECT highlight(t1, 0, '(', ')') FROM t1('cdef'); -} [list \ - "ab(c\u0303def)ghijklm" \ - "a\u0303b\u0303(c\u0303def)ghijklm" \ -] - -do_execsql_test 1.4 { - SELECT highlight(t1, 0, '(', ')') FROM t1('def'); -} [list \ - "abc\u0303(def)ghijklm" \ - "a\u0303b\u0303c\u0303(def)ghijklm" \ -] - - -#------------------------------------------------------------------------- -do_catchsql_test 2.0 { - CREATE VIRTUAL TABLE t2 USING fts5( - z, tokenize='trigram case_sensitive 1 remove_diacritics 1' - ); -} {1 {error in tokenizer constructor}} - -do_execsql_test 2.1 { - CREATE VIRTUAL TABLE t2 USING fts5( - z, tokenize='trigram case_sensitive 0 remove_diacritics 1' - ); -} -do_execsql_test 2.2 " - INSERT INTO t2 VALUES('\u00E3bcdef'); - INSERT INTO t2 VALUES('b\u00E3cdef'); - INSERT INTO t2 VALUES('bc\u00E3def'); - INSERT INTO t2 VALUES('bcd\u00E3ef'); -" - -do_execsql_test 2.3 { - SELECT highlight(t2, 0, '(', ')') FROM t2('abc'); -} "(\u00E3bc)def" -do_execsql_test 2.4 { - SELECT highlight(t2, 0, '(', ')') FROM t2('bac'); -} "(b\u00E3c)def" -do_execsql_test 2.5 { - SELECT highlight(t2, 0, '(', ')') FROM t2('bca'); -} "(bc\u00E3)def" -do_execsql_test 2.6 " - SELECT highlight(t2, 0, '(', ')') FROM t2('\u00E3bc'); -" "(\u00E3bc)def" - -#------------------------------------------------------------------------- -do_execsql_test 3.0 { - CREATE VIRTUAL TABLE t3 USING fts5( - z, tokenize='trigram remove_diacritics 1' - ); -} {} -do_execsql_test 3.1 " - INSERT INTO t3 VALUES ('\u0303abc\u0303'); -" -do_execsql_test 3.2 { - SELECT highlight(t3, 0, '(', ')') FROM t3('abc'); -} "\u0303(abc\u0303)" - -#------------------------------------------------------------------------- -do_execsql_test 4.0 { - CREATE VIRTUAL TABLE t4 USING fts5(z, tokenize=trigram); -} {} - -breakpoint -do_execsql_test 4.1 { - INSERT INTO t4 VALUES('ABCD'); -} {} - -finish_test Index: ext/fts5/test/fts5vocab2.test ================================================================== --- ext/fts5/test/fts5vocab2.test +++ ext/fts5/test/fts5vocab2.test @@ -278,33 +278,9 @@ do_catchsql_test 5.2 { DELETE FROM t1 WHERE rowid>100; INSERT INTO t1 SELECT randomblob(3000) FROM v1 } {1 {query aborted}} -#------------------------------------------------------------------------- -reset_db -sqlite3_fts5_may_be_corrupt 1 - -do_execsql_test 6.0 { - BEGIN TRANSACTION; - CREATE VIRTUAL TABLE t1 USING fts5(a,b unindexed,c,tokenize="porter ascii",tokendata=1); - REPLACE INTO t1_data VALUES(1,X'03090009'); - REPLACE INTO t1_data VALUES(10,X'000000000103030003010101020101030101'); - REPLACE INTO t1_data VALUES(137438953473,X'0000002e023061010202010162010203010163010204010167010601020201016801060102030101690106010204040606060808'); - REPLACE INTO t1_data VALUES(274877906945,X'0000001f013067020802010202010168020803010203010169020804010204040909'); - REPLACE INTO t1_data VALUES(412316860417,X'0000002e023061030202010162030203010163030204010167030601020201016803060102030101690306010204040606060808'); - COMMIT; -} - -do_execsql_test 6.1 { - CREATE VIRTUAL TABLE t3 USING fts5vocab('t1', 'row'); -} - -do_catchsql_test 6.2 { - SELECT * FROM t3; -} {1 {database disk image is malformed}} - -sqlite3_fts5_may_be_corrupt 0 finish_test DELETED ext/intck/intck1.test Index: ext/intck/intck1.test ================================================================== --- ext/intck/intck1.test +++ /dev/null @@ -1,332 +0,0 @@ -# 2008 Feb 19 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# -# The focus of this file is testing the incremental integrity check -# (intck) extension. -# - -source [file join [file dirname [info script]] intck_common.tcl] -set testprefix intck1 -return_if_no_intck - -foreach {tn sql} { - 1 "CREATE TABLE t1(a PRIMARY KEY, b)" - 2 "CREATE TABLE t2(a PRIMARY KEY, b) WITHOUT ROWID " - 3 "CREATE TABLE t3(a PRIMARY KEY, b) WITHOUT rowID;" - 4 "CREATE TABLE t4(a PRIMARY KEY, ROWID)" - 5 {CREATE TABLE t5(a PRIMARY KEY, ROWID) WITHOUT ROWID - } -} { - do_test 1.1.$tn { - db eval $sql - set {} {} - } {} -} - -set space " \n\v\t\r\f" - -do_execsql_test 1.2 { - SELECT name, (rtrim(sql, $space) LIKE '%rowid') - FROM sqlite_schema WHERE type='table' - ORDER BY 1 -} { - t1 0 - t2 1 - t3 1 - t4 0 - t5 1 -} - -do_execsql_test 1.3 { - CREATE TABLE x1(a COLLATE nocase, b INTEGER, c BLOB); - INSERT INTO x1 VALUES('lEtTeRs', 1234, 1234); -} -do_execsql_test 1.3.1 { - WITH wrapper(c1, c2, c3) AS ( - SELECT a, b, c FROM x1 - ) - SELECT * FROM wrapper WHERE c1='letters'; -} {lEtTeRs 1234 1234} -do_execsql_test 1.3.2 { - WITH wrapper(c1, c2, c3) AS ( - SELECT a, b, c FROM x1 - ) - SELECT * FROM wrapper WHERE c2='1234'; -} {lEtTeRs 1234 1234} -do_execsql_test 1.3.2 { - WITH wrapper(c1, c2, c3) AS ( - SELECT a, b, c FROM x1 - ) - SELECT * FROM wrapper WHERE c3='1234'; -} {} - -do_execsql_test 1.4 { - CREATE TABLE z1(a, b); - CREATE INDEX z1ab ON z1(a+b COLLATE nocase); -} -do_execsql_test 1.4.1 { - SELECT * FROM z1 INDEXED BY z1ab -} - -do_catchsql_test 1.5.1 { - CREATE INDEX z1b ON z1(b ASC NULLS LAST); -} {1 {unsupported use of NULLS LAST}} -do_catchsql_test 1.5.2 { - CREATE INDEX z1b ON z1(b DESC NULLS LAST); -} {1 {unsupported use of NULLS LAST}} -do_catchsql_test 1.5.3 { - CREATE INDEX z1b ON z1(b ASC NULLS FIRST); -} {1 {unsupported use of NULLS FIRST}} -do_catchsql_test 1.5.4 { - CREATE INDEX z1b ON z1(b DESC NULLS FIRST); -} {1 {unsupported use of NULLS FIRST}} - - -reset_db -do_execsql_test 1.6.1 { - CREATE TABLE t1(i INTEGER PRIMARY KEY, b, c); - CREATE INDEX i1 ON t1(b); - ANALYZE; - INSERT INTO sqlite_stat1 VALUES('t1', 'i1', '10000 10000'); - ANALYZE sqlite_schema; -} {} -do_eqp_test 1.6.2 { - SELECT 1 FROM t1 INDEXED BY i1 WHERE (b, i) IS (?, ?); -} {SEARCH} - - - -#------------------------------------------------------------------------- -reset_db - -do_test 2.0 { - set ic [sqlite3_intck db main] - $ic close -} {} - -do_execsql_test 2.1 { - CREATE TABLE t1(a, b); - INSERT INTO t1 VALUES(1, 2); - INSERT INTO t1 VALUES(3, 4); - - CREATE INDEX i1 ON t1(a COLLATE nocase); - CREATE INDEX i2 ON t1(b, a); - CREATE INDEX i3 ON t1(b + a COLLATE nocase) WHERE a!=1; -} - -do_intck_test 2.2 { -} - -# Delete a row from each of the i1 and i2 indexes using the imposter -# table interface. -# -do_test 2.3 { - db eval {SELECT name, rootpage FROM sqlite_schema} { - set R($name) $rootpage - } - sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $R(i1) - db eval { CREATE TABLE imp1(a PRIMARY KEY, rowid) WITHOUT ROWID; } - sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $R(i2) - db eval { CREATE TABLE imp2(b, a, rowid, PRIMARY KEY(b, a)) WITHOUT ROWID; } - sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 0 0 - - db eval { - DELETE FROM imp1 WHERE rowid=1; - DELETE FROM imp2 WHERE rowid=2; - } - - db close - sqlite3 db test.db -} {} - -do_intck_test 2.4 { - {entry (1,1) missing from index i1} - {entry (4,3,2) missing from index i2} -} - -#------------------------------------------------------------------------- -reset_db -do_execsql_test 3.0 { - CREATE TABLE x1(a, b, c, PRIMARY KEY(c, b)) WITHOUT ROWID; - CREATE INDEX x1a ON x1(a COLLATE nocase); - - INSERT INTO x1 VALUES(1, 2, 'three'); - INSERT INTO x1 VALUES(4, 5, 'six'); - INSERT INTO x1 VALUES(7, 8, 'nine'); -} - -do_intck_test 3.1 { } - -do_test 3.2 { - db eval {SELECT name, rootpage FROM sqlite_schema} { - set R($name) $rootpage - } - sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $R(x1a) - db eval { CREATE TABLE imp1(c, b, a, PRIMARY KEY(c, b)) WITHOUT ROWID } - sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 0 0 - - db eval { - DELETE FROM imp1 WHERE a=5; - } - execsql_pp { - } - - db close - sqlite3 db test.db -} {} - -do_intck_test 3.3 { - {entry (4,'six',5) missing from index x1a} -} - -#------------------------------------------------------------------------- -reset_db -do_execsql_test 4.0 { - CREATE TABLE www(x, y, z); - CREATE INDEX w1 ON www( (x+1), z ); - INSERT INTO www VALUES(1, 1, 1), (2, 2, 2); -} - -do_intck_test 4.1 { } - -#------------------------------------------------------------------------- -reset_db -do_execsql_test 5.0 { - CREATE TABLE t1(a, b); - CREATE INDEX i1 ON t1(a COLLATE NOCASE); - INSERT INTO t1 VALUES(1, 1); - INSERT INTO t1 VALUES(2, 2); -} - -do_test 5.1 { - set ic [sqlite3_intck db nosuchdb] - $ic step -} {SQLITE_ERROR} - -do_test 5.2 { - $ic close - set ic [sqlite3_intck db {}] - while {[$ic step]=="SQLITE_OK"} {} - set res [$ic error] - $ic close - set res -} {SQLITE_OK {}} - -do_test 5.3 { test_do_intck db "main" } {} - -do_test 5.4 { - set ret {} - set ic [sqlite3_intck db main] - db eval [$ic test_sql t1] { - if {$error_message!=""} { lappend ret $error_message } - } - $ic close - set ret -} {} - -do_test 5.5 { - set ret {} - set ic [sqlite3_intck db main] - db eval [$ic test_sql {}] { - if {$error_message!=""} { lappend ret $error_message } - } - $ic close - set ret -} {} - -db cache flush - -do_test 5.6 { - set ret {} - set ic [sqlite3_intck db main] - $ic step - db eval [$ic test_sql {}] { - if {$error_message!=""} { lappend ret $error_message } - } - $ic close - set ret -} {} - -#------------------------------------------------------------------------- -reset_db - -do_execsql_test 6.0 { - CREATE TABLE t1(x, y, PRIMARY KEY(x)) WITHOUT ROWID; - CREATE INDEX i1 ON t1(y, x); - INSERT INTO t1 VALUES(X'0000', X'1111'); -} - -do_intck_test 6.1 {} - -do_execsql_test 6.2.1 { - PRAGMA writable_schema = 1; - UPDATE sqlite_schema SET sql = 'CREATE INDEX i1' WHERE name='i1'; -} {} -do_intck_test 6.2.2 {} - -do_execsql_test 6.3.1 { - UPDATE sqlite_schema SET sql = 'CREATE INDEX i1(y' WHERE name='i1'; -} {} -do_intck_test 6.3.2 {} - -do_execsql_test 6.4.1 { - UPDATE sqlite_schema - SET sql = 'CREATE INDEX i1(y) hello world' - WHERE name='i1'; -} {} -do_intck_test 6.4.2 {} - -do_execsql_test 6.5.1 { - UPDATE sqlite_schema - SET sql = 'CREATE INDEX i1(y, x) WHERE 1 ' - WHERE name='i1'; -} {} -do_intck_test 6.5.2 {} - -do_execsql_test 6.6.1 { - UPDATE sqlite_schema - SET sql = 'CREATE INDEX i1( , ) WHERE 1 ' - WHERE name='i1'; -} {} - -do_test 6.7.2 { - set ic [sqlite3_intck db main] - $ic step -} {SQLITE_ERROR} -do_test 6.5.3 { - $ic error -} {SQLITE_ERROR {near "AS": syntax error}} -$ic close - -do_execsql_test 6.6.1 { - UPDATE sqlite_schema - SET sql = 'CREATE INDEX i1([y' - WHERE name='i1'; -} {} -do_intck_test 6.6.2 {} - -#------------------------------------------------------------------------- -reset_db -do_execsql_test 7.0 { - CREATE TABLE x1("1", "22", "3333", four); - CREATE INDEX i1 ON x1( "1" , "22", NULL); - INSERT INTO x1 VALUES(1, 22, 3333, NULL); - INSERT INTO x1 VALUES(1, 22, 3333, NULL); -} -do_execsql_test 7.1 " CREATE INDEX i2 ON x1( \"1\"\r\n\t ) " -do_execsql_test 7.2 { CREATE INDEX i3 ON x1( "22" || 'abc''def' || `1` ) } -do_execsql_test 7.3 { CREATE INDEX i4 ON x1( [22] + [1] ) } -do_execsql_test 7.4 { CREATE INDEX i5 ON x1( four||'hello' ) } - -do_intck_test 7.5 {} - - -finish_test DELETED ext/intck/intck2.test Index: ext/intck/intck2.test ================================================================== --- ext/intck/intck2.test +++ /dev/null @@ -1,177 +0,0 @@ -# 2024 Feb 19 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# -# The focus of this file is testing the incremental integrity check -# (intck) extension. -# - -source [file join [file dirname [info script]] intck_common.tcl] -set testprefix intck2 -return_if_no_intck - - -do_execsql_test 1.0 { - CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT); - INSERT INTO t1 VALUES(1, 'one'); - INSERT INTO t1 VALUES(2, 'two'); - INSERT INTO t1 VALUES(3, 'three'); - CREATE INDEX i1 ON t1(b); -} - -proc imposter_edit {obj create sql} { - sqlite3 xdb test.db - set pgno [xdb one {SELECT rootpage FROM sqlite_schema WHERE name=$obj}] - - sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER xdb main 1 $pgno - xdb eval $create - sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER xdb main 0 0 - xdb eval $sql - xdb close -} - -imposter_edit i1 { - CREATE TABLE imp(b, a, PRIMARY KEY(b)) WITHOUT ROWID; -} { - DELETE FROM imp WHERE b='two'; - INSERT INTO imp(b, a) VALUES('four', 4); -} - -do_intck_test 1.1 { - {surplus entry ('four',4) in index i1} - {entry ('two',2) missing from index i1} -} - -#------------------------------------------------------------------------- -reset_db -do_execsql_test 2.0 { - CREATE TABLE x1(a, b, "c d"); - CREATE INDEX x1a ON x1(a COLLATE nocase DESC , b ASC); - CREATE INDEX x1b ON x1( a || b || ' "''" ' COLLATE binary ASC ); - CREATE INDEX x1c ON x1( format('%s', a)ASC, format('%d', "c d" ) ); - INSERT INTO x1 VALUES('one', 2, 3); - INSERT INTO x1 VALUES('One', 4, 5); - INSERT INTO x1 VALUES('ONE', 6, 7); - INSERT INTO x1 VALUES(NULL, NULL, NULL); -} - -do_intck_test 2.1 {} - -imposter_edit x1 { - CREATE TABLE imp(a, b, c); -} { - DELETE FROM imp WHERE c=7; -} -do_intck_test 2.2 { - {surplus entry ('ONE',6,3) in index x1a} - {surplus entry ('ONE6 "''" ',3) in index x1b} - {surplus entry ('ONE','7',3) in index x1c} -} - -#------------------------------------------------------------------------- -reset_db -do_execsql_test 3.0 { - CREATE TABLE x1(a, b, c); - CREATE INDEX x1all ON x1(a DESC, b ASC, c DESC); - INSERT INTO x1 VALUES(2, 1, 2); - INSERT INTO x1 VALUES(2, 1, 1); - INSERT INTO x1 VALUES(2, 2, 2); - INSERT INTO x1 VALUES(2, 2, 1); - INSERT INTO x1 VALUES(1, 1, 2); - INSERT INTO x1 VALUES(1, 1, 1); - INSERT INTO x1 VALUES(1, 2, 2); - INSERT INTO x1 VALUES(1, 2, 1); -} - -do_intck_test 3.1 { -} - -imposter_edit x1 { - CREATE TABLE imp(a, b, c); -} { - DELETE FROM imp WHERE 1; -} - -db close -sqlite3 db test.db - -do_intck_test 3.2 { - {surplus entry (2,1,2,1) in index x1all} - {surplus entry (2,1,1,2) in index x1all} - {surplus entry (2,2,2,3) in index x1all} - {surplus entry (2,2,1,4) in index x1all} - {surplus entry (1,1,2,5) in index x1all} - {surplus entry (1,1,1,6) in index x1all} - {surplus entry (1,2,2,7) in index x1all} - {surplus entry (1,2,1,8) in index x1all} -} - -do_execsql_test 3.3 { - DELETE FROM x1; - INSERT INTO x1 VALUES(NULL, NULL, NULL); - INSERT INTO x1 VALUES(NULL, NULL, NULL); - INSERT INTO x1 VALUES(NULL, NULL, NULL); - INSERT INTO x1 VALUES(NULL, NULL, NULL); -} - -do_intck_test 3.4 { -} - -imposter_edit x1 { - CREATE TABLE imp(a, b, c); -} { - DELETE FROM imp WHERE 1; - INSERT INTO imp(rowid) VALUES(-123); - INSERT INTO imp(rowid) VALUES(456); -} - -db close -sqlite3 db test.db - -do_intck_test 3.5 { - {entry (NULL,NULL,NULL,-123) missing from index x1all} - {entry (NULL,NULL,NULL,456) missing from index x1all} - {surplus entry (NULL,NULL,NULL,1) in index x1all} - {surplus entry (NULL,NULL,NULL,2) in index x1all} - {surplus entry (NULL,NULL,NULL,3) in index x1all} - {surplus entry (NULL,NULL,NULL,4) in index x1all} -} - -reset_db - -do_execsql_test 3.6 { - CREATE TABLE w1(a PRIMARY KEY, b, c); - INSERT INTO w1 VALUES(1.0, NULL, NULL); - INSERT INTO w1 VALUES(33.0, NULL, NULL); - INSERT INTO w1 VALUES(100.0, NULL, NULL); - CREATE INDEX w1bc ON w1(b, c); -} - -do_intck_test 3.7 { -} - -imposter_edit w1 { - CREATE TABLE imp(a, b, c); -} { - DELETE FROM imp WHERE a=33; - INSERT INTO imp(a) VALUES(1234.5); - INSERT INTO imp(a) VALUES(-1234.5); -} - -do_intck_test 3.8 { - {surplus entry (33.0,2) in index sqlite_autoindex_w1_1} - {entry (1234.5,4) missing from index sqlite_autoindex_w1_1} - {entry (NULL,NULL,4) missing from index w1bc} - {entry (-1234.5,5) missing from index sqlite_autoindex_w1_1} - {entry (NULL,NULL,5) missing from index w1bc} - {surplus entry (NULL,NULL,2) in index w1bc} -} - -finish_test DELETED ext/intck/intck_common.tcl Index: ext/intck/intck_common.tcl ================================================================== --- ext/intck/intck_common.tcl +++ /dev/null @@ -1,66 +0,0 @@ -# 2024 Feb 18 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# - -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl - -ifcapable !vtab||!pragma { - proc return_if_no_intck {} { - finish_test - return -code return - } - return -} else { - proc return_if_no_intck {} {} -} - -proc do_intck {db {bSuspend 0}} { - set ic [sqlite3_intck $db main] - - set ret [list] - while {"SQLITE_OK"==[$ic step]} { - set msg [$ic message] - if {$msg!=""} { - lappend ret $msg - } - if {$bSuspend} { - $ic unlock - #puts "SQL: [$ic test_sql {}]" - #execsql_pp "EXPLAIN query plan [$ic test_sql {}]" - #explain_i [$ic test_sql {}] - } - } - - set err [$ic error] - if {[lindex $err 0]!="SQLITE_OK"} { - error $err - } - $ic close - - return $ret -} - -proc intck_sql {db tbl} { - set ic [sqlite3_intck $db main] - set sql [$ic test_sql $tbl] - $ic close - return $sql -} - -proc do_intck_test {tn expect} { - uplevel [list do_test $tn.a [list do_intck db] [list {*}$expect]] - uplevel [list do_test $tn.b [list do_intck db 1] [list {*}$expect]] -} - - DELETED ext/intck/intckbusy.test Index: ext/intck/intckbusy.test ================================================================== --- ext/intck/intckbusy.test +++ /dev/null @@ -1,49 +0,0 @@ -# 2024 February 24 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# - -source [file join [file dirname [info script]] intck_common.tcl] -set testprefix intckbusy -return_if_no_intck - - - -do_execsql_test 1.0 { - CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); - INSERT INTO t1 VALUES(1, 2, 3); - INSERT INTO t1 VALUES(2, 'two', 'three'); - INSERT INTO t1 VALUES(3, NULL, NULL); - CREATE INDEX i1 ON t1(b, c); -} - -sqlite3 db2 test.db - -do_execsql_test -db db2 1.1 { - BEGIN EXCLUSIVE; - INSERT INTO t1 VALUES(4, 5, 6); -} - -do_test 1.2 { - set ic [sqlite3_intck db main] - $ic step -} {SQLITE_BUSY} -do_test 1.3 { - $ic unlock -} {SQLITE_BUSY} -do_test 1.4 { - $ic error -} {SQLITE_BUSY {database is locked}} -do_test 1.4 { - $ic close -} {} - -finish_test - DELETED ext/intck/intckcorrupt.test Index: ext/intck/intckcorrupt.test ================================================================== --- ext/intck/intckcorrupt.test +++ /dev/null @@ -1,236 +0,0 @@ -# 2024 Feb 21 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# -# The focus of this file is testing the intck extensions response -# to corruption at the b-tree level. -# - -source [file join [file dirname [info script]] intck_common.tcl] -set testprefix intckcorrupt -return_if_no_intck - -#------------------------------------------------------------------------- -reset_db -do_test 1.0 { - sqlite3 db {} - db deserialize [decode_hexdb { -| size 356352 pagesize 4096 filename crash-acaae0347204ae.db -| page 1 offset 0 -| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. -| 16: 10 00 01 01 00 40 20 20 00 00 00 00 d0 00 00 00 .....@ ........ -| 32: 40 00 ea 00 00 00 00 00 00 40 00 00 00 40 00 00 @........@...@.. -| 96: 00 00 00 00 0d 00 00 00 04 0e 9c 00 0f ad 0f 4f ...............O -| 112: 0e fc 0e 9c 00 00 00 00 00 00 00 00 00 00 00 00 ................ -| 3728: 00 00 00 00 00 00 00 00 00 00 00 00 5e 04 07 17 ............^... -| 3744: 1f 1f 01 81 0b 74 61 62 6c 65 74 31 5f 70 61 72 .....tablet1_par -| 3760: 65 6e 74 74 31 5f 70 61 72 65 6e 74 04 43 52 45 entt1_parent.CRE -| 3776: 41 54 45 20 54 41 42 4c 45 20 22 74 31 5f 70 61 ATE TABLE .t1_pa -| 3792: 72 65 6e 74 22 28 6e 6f 64 65 6e 6f 20 49 4e 54 rent.(nodeno INT -| 3808: 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 EGER PRIMARY KEY -| 3824: 2c 70 61 72 65 6e 74 6e 6f 64 65 29 51 03 06 17 ,parentnode)Q... -| 3840: 1b 1b 01 7b 74 61 62 6c 65 74 31 5f 6e 6f 64 65 ....tablet1_node -| 3856: 74 31 5f 6e 6f 64 65 03 43 52 45 41 54 45 20 54 t1_node.CREATE T -| 3872: 41 42 4c 45 20 22 74 31 5f 6e 6f 64 65 22 28 6e ABLE .t1_node.(n -| 3888: 6f 64 65 6e 6f 20 49 4e 54 45 47 45 52 20 50 52 odeno INTEGER PR -| 3904: 49 4d 41 52 59 20 4b 45 59 2c 64 61 74 61 29 5c IMARY KEY,data). -| 3920: 02 07 17 1d 1d 01 81 0b 74 61 62 6c 65 74 31 5f ........tablet1_ -| 3936: 72 6f 77 69 64 74 31 5f 72 6f 77 69 64 02 43 52 rowidt1_rowid.CR -| 3952: 45 41 54 45 20 54 41 42 4c 45 20 22 74 31 5f 72 EATE TABLE .t1_r -| 3968: 6f 77 69 64 22 28 72 6f 77 69 64 20 49 4e 54 45 owid.(rowid INTE -| 3984: 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c GER PRIMARY KEY, -| 4000: 6e 6f 64 65 6e 6f 2c 61 30 2c 61 31 29 51 01 07 nodeno,a0,a1)Q.. -| 4016: 17 11 11 08 81 0f 74 61 62 6c 65 74 31 74 31 43 ......tablet1t1C -| 4032: 52 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 REATE VIRTUAL TA -| 4048: 42 4c 45 20 74 31 20 55 53 49 4e 47 20 72 74 72 BLE t1 USING rtr -| 4064: 65 65 28 69 64 2c 78 30 20 50 52 49 4d 41 52 59 ee(id,x0 PRIMARY -| 4080: 20 4b 45 59 2c 70 61 72 65 6e 74 6e 6f 64 65 29 KEY,parentnode) -| page 2 offset 4096 -| 0: 51 03 06 17 1b 1b 01 7b 74 61 62 6c 65 74 31 5f Q.......tablet1_ -| 16: 6e 6f 64 65 74 31 5f 6e 6f 64 65 03 43 52 45 41 nodet1_node.CREA -| 32: 54 45 20 54 41 42 4c 45 20 22 74 31 5f 6e 6f 64 TE TABLE .t1_nod -| 48: 65 22 28 6e 6f 64 65 6e 6f 20 49 4e 54 45 47 45 e.(nodeno INTEGE -| 64: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 64 61 R PRIMARY KEY,da -| 80: 74 61 29 5c 02 07 17 1d 1d 01 81 0b 74 61 62 6c ta).........tabl -| 96: 65 74 31 5f 72 6f 77 69 64 74 31 5f 72 6f 77 69 et1_rowidt1_rowi -| 112: 64 02 43 52 45 41 54 45 20 54 41 42 4c 45 00 00 d.CREATE TABLE.. -| 128: 01 0a 02 00 00 00 01 0e 0d 00 00 00 00 24 0e 0d .............$.. -| 144: 0c 1a 06 85 50 46 60 27 70 08 00 00 00 00 00 00 ....PF`'p....... -| 3824: 00 00 00 00 00 00 00 0d 0e 05 00 09 1d 00 74 6f ..............to -| 3840: 79 20 68 61 6c 66 10 0d 05 00 09 23 00 62 6f 74 y half.....#.bot -| 3856: 74 6f 6d 20 68 61 6c 66 0f 0c 05 00 09 21 00 72 tom half.....!.r -| 3872: 69 67 68 74 20 68 61 6c 66 0e 0b 05 00 09 1f 00 ight half....... -| 3888: 6c 65 66 74 20 43 15 f6 e6 f6 46 50 34 35 24 54 left C....FP45$T -| 3904: 15 44 52 05 44 14 24 c4 52 02 27 43 15 f6 e6 f6 .DR.D.$.R.'C.... -| 3920: 46 52 22 8e 6f 64 65 6e 6f 20 49 4e 54 45 47 45 FR..odeno INTEGE -| 3936: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 64 61 R PRIMARY KEY,da -| 3952: 74 61 29 5c 02 07 17 1d 1d 01 81 0b 74 61 62 6c ta).........tabl -| 3968: 65 74 31 5f 72 6f 74 74 6f 6d 20 65 64 67 65 0f et1_rottom edge. -| 3984: 07 05 00 09 21 00 72 69 67 68 74 20 65 64 67 65 ....!.right edge -| 4000: 0e 06 05 00 09 1f 00 6c 65 66 74 20 65 64 67 65 .......left edge -| 4016: 0b 05 05 00 09 19 00 63 65 6e 74 65 72 17 04 05 .......center... -| 4032: 00 09 31 00 75 70 70 65 72 2d 72 69 67 68 74 20 ..1.upper-right -| 4048: 63 6f 72 6e 65 72 17 03 05 00 09 31 00 6c 6f 77 corner.....1.low -| 4064: 65 72 2d 72 69 67 68 74 20 63 6f 72 6e 65 72 16 er-right corner. -| 4080: 02 05 00 09 2f 00 75 70 70 65 72 2d 6c 65 66 74 ..../.upper-left -| page 3 offset 8192 -| 0: 20 63 6f 72 6e 65 72 16 01 05 00 09 2f 01 8c 6f corner...../..o -| 16: 77 65 72 2d 6c 53 51 4c 69 74 65 20 66 6f 72 6d wer-lSQLite form -| 32: 61 74 20 33 00 10 00 01 01 00 40 20 20 00 00 00 at 3......@ ... -| 48: 00 00 00 00 2f 00 00 0d eb 13 00 00 00 03 00 00 ..../........... -| 64: 00 04 00 00 00 00 00 00 00 06 00 00 00 01 00 00 ................ -| 80: 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 ................ -| page 6 offset 20480 -| 128: 00 00 00 00 00 00 00 00 97 3d 04 ae 7c 01 00 00 .........=..|... -| 624: 00 00 00 00 00 00 21 97 3d 04 ae 7c 01 00 00 00 ......!.=..|.... -| 1120: 00 00 00 00 00 20 97 3d 04 ae 7c 01 00 00 00 00 ..... .=..|..... -| 1616: 00 00 00 00 1f 97 3d 04 ae 7c 01 00 00 00 00 00 ......=..|...... -| 2112: 00 00 00 1e 97 3d 04 ae 7c 01 00 00 00 00 00 00 .....=..|....... -| 2608: 00 00 1d 97 d3 d0 4a e7 c0 00 00 00 00 00 00 00 ......J......... -| 3088: 00 00 00 00 00 00 00 00 00 00 00 00 01 f3 00 00 ................ -| 3600: 23 97 3d 04 ae 7c 01 00 00 00 00 00 00 00 00 00 #.=..|.......... -| 4080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 26 ...............& -| page 8 offset 28672 -| 0: 0d 00 00 00 01 04 30 00 04 30 00 00 00 00 00 00 ......0..0...... -| 1072: 97 4d 1e 14 00 ae 7c 00 00 00 00 00 00 00 00 00 .M....|......... -| 1088: 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 ................ -| 4080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 ................ -| page 10 offset 36864 -| 0: 0d 00 00 00 01 04 30 00 04 30 00 00 00 00 00 00 ......0..0...... -| 1072: 9a ee c1 80 fd 78 1f ce 1b ae eb b4 00 00 00 00 .....x.......... -| 1088: 13 20 ff 20 00 70 00 00 00 60 50 00 00 00 11 e0 . . .p...`P..... -| 1104: 00 00 00 70 00 00 00 60 50 05 35 14 c6 97 46 52 ...p...`P.5...FR -| 1120: 06 66 f7 26 d6 17 42 03 30 01 00 00 10 10 04 02 .f.&..B.0....... -| 1136: 02 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 .........@...... -| 1152: 00 00 00 00 00 40 00 00 00 40 00 00 00 00 00 00 .....@...@...... -| 4080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 05 ................ -| page 12 offset 45056 -| 0: 0d 00 00 00 01 04 30 00 04 30 e1 b4 30 97 4d 46 ......0..0..0.MF -| 16: 14 00 ae 7c 00 00 00 00 00 00 00 03 00 00 43 00 ...|..........C. -| page 47 offset 188416 -| 2512: 00 00 00 00 00 00 00 00 be 00 00 00 00 00 00 00 ................ -| page 87 offset 352256 -| 2512: 00 00 00 00 00 00 00 00 aa 00 00 00 00 00 00 00 ................ -| end crash-acaae0347204ae.db -}]} {} - -do_intck_test 1.1 { - {corruption found while reading database schema} -} - -#------------------------------------------------------------------------- -reset_db -do_test 2.0 { - sqlite3 db {} - db deserialize [decode_hexdb { -| size 28672 pagesize 4096 filename crash-3afa1ca9e9c1bd.db -| page 1 offset 0 -| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. -| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 07 .....@ ........ -| 32: 00 00 00 00 00 00 00 00 00 00 00 06 00 00 00 04 ................ -| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ -| 96: 00 00 00 00 0d 00 00 00 06 0e 88 00 0f b8 0f 6d ...............m -| 112: 0f 3a 0f 0b 0e d5 0e 88 01 00 00 00 00 00 00 00 .:.............. -| 3712: 00 00 00 00 00 00 00 00 4b 06 06 17 25 25 01 5b ........K...%%.[ -| 3728: 74 61 62 6c 65 73 71 6c 69 74 65 5f 73 74 61 74 tablesqlite_stat -| 3744: 31 73 71 6c 69 74 65 5f 73 74 61 74 31 07 43 52 1sqlite_stat1.CR -| 3760: 45 41 54 45 20 54 41 42 4c 45 20 73 71 6c 69 74 EATE TABLE sqlit -| 3776: 65 5f 73 74 61 74 31 28 74 62 6c 2c 69 64 78 2c e_stat1(tbl,idx, -| 3792: 73 74 61 74 29 34 05 06 17 13 11 01 53 69 6e 64 stat)4......Sind -| 3808: 65 78 63 31 63 63 31 06 43 52 45 41 54 45 20 55 exc1cc1.CREATE U -| 3824: 4e 49 51 55 45 20 49 4e 44 45 58 20 63 31 63 20 NIQUE INDEX c1c -| 3840: 4f 4e 20 63 31 28 63 2c 20 62 29 2d 04 06 17 13 ON c1(c, b)-.... -| 3856: 11 01 45 69 6e 64 65 78 63 31 64 63 31 05 43 52 ..Eindexc1dc1.CR -| 3872: 45 41 54 45 20 49 4e 44 45 58 20 63 31 64 20 4f EATE INDEX c1d O -| 3888: 4e 20 63 31 28 64 2c 20 62 29 31 03 06 17 13 11 N c1(d, b)1..... -| 3904: 01 4d 69 6e 64 65 78 62 31 63 62 31 05 43 52 45 .Mindexb1cb1.CRE -| 3920: 41 54 45 20 55 4e 49 51 55 45 20 49 4e 44 45 58 ATE UNIQUE INDEX -| 3936: 20 62 31 63 20 4f 4e 20 62 31 28 63 29 49 02 06 b1c ON b1(c)I.. -| 3952: 17 11 11 0f 7f 74 61 62 6c 65 63 31 63 31 03 43 .....tablec1c1.C -| 3968: 52 45 41 54 45 20 54 41 42 4c 45 20 63 31 28 61 REATE TABLE c1(a -| 3984: 20 49 4e 54 20 50 52 49 4d 41 52 59 20 4b 45 59 INT PRIMARY KEY -| 4000: 2c 20 62 2c 20 63 2c 20 64 29 20 57 49 54 48 4f , b, c, d) WITHO -| 4016: 55 54 20 52 4f 57 49 44 46 01 06 17 11 11 01 79 UT ROWIDF......y -| 4032: 74 61 62 6c 65 62 31 62 31 02 43 52 45 41 54 45 tableb1b1.CREATE -| 4048: 20 54 41 42 4c 45 20 62 31 28 61 20 49 4e 54 20 TABLE b1(a INT -| 4064: 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 62 2c 20 PRIMARY KEY, b, -| 4080: 63 29 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 c) WITHOUT ROWID -| page 2 offset 4096 -| 0: 0a 00 00 00 07 0f ca 00 0f fa 0f f2 0f ea 0f e2 ................ -| 16: 0f da 00 00 00 01 00 00 00 00 00 00 00 00 00 00 ................ -| 4032: 00 00 00 00 00 00 00 00 00 00 07 04 01 0f 01 06 ................ -| 4048: 67 07 07 04 01 0f 01 06 66 06 07 04 01 0f 01 05 g.......f....... -| 4064: 65 05 07 04 01 0f 01 04 64 04 07 04 01 0f 01 03 e.......d....... -| 4080: 63 03 07 04 01 0f 01 02 62 0f 05 04 09 0f 09 61 c.......b......a -| page 3 offset 8192 -| 0: 0a 00 00 00 07 0f bd 00 0f f9 0f ef 0f e5 0f db ................ -| 16: 0f d1 0f c7 0f bd 00 00 00 00 01 00 00 00 00 00 ................ -| 4016: 00 00 00 00 00 00 00 00 00 00 00 00 00 09 05 01 ................ -| 4032: 0f 01 01 07 61 07 07 09 05 01 0f 01 01 06 61 06 ....a.........a. -| 4048: 06 09 05 01 0f 01 01 05 61 05 05 09 05 01 0f 01 ........a....... -| 4064: 01 04 61 04 04 09 05 01 0f 01 01 03 61 03 03 09 ..a.........a... -| 4080: 05 01 0f 01 01 02 61 0f 02 06 05 09 0f 09 09 61 ......a........a -| page 4 offset 12288 -| 0: 0a 00 00 00 07 0f d8 00 0f fc 0f f0 0f ea 0f e4 ................ -| 16: 0f de 0f d8 0f f6 00 00 00 00 00 00 00 00 00 00 ................ -| 4048: 00 00 00 00 00 00 00 00 05 03 01 01 07 07 05 03 ................ -| 4064: 01 01 06 06 05 03 01 01 05 05 05 03 01 01 04 04 ................ -| 4080: 05 03 01 01 03 03 05 03 01 01 0f 02 03 03 09 09 ................ -| page 5 offset 16384 -| 0: 0a 00 00 00 07 0f ca 00 0f fa 0f f2 0f ea 0f 00 ................ -| 4032: 00 00 00 00 00 00 00 00 00 00 07 04 01 0f 01 07 ................ -| 4048: 61 07 07 04 01 0f 01 06 61 06 07 04 01 0f 01 05 a.......a....... -| 4064: 61 05 07 04 01 1f 01 04 61 04 07 04 01 0f 01 03 a.......a....... -| 4080: 61 03 07 04 01 0f 01 02 61 02 05 04 09 0f 09 61 a.......a......a -| page 6 offset 20480 -| 0: 0a 00 00 00 07 0f ca 00 0f fa 0f ea 0f e2 00 00 ................ -| 4032: 00 00 00 00 00 00 00 00 00 00 07 04 01 0f 01 07 ................ -| 4048: 61 07 07 04 01 0f 01 06 61 06 07 04 01 0f 01 05 a.......a....... -| 4064: 61 05 07 04 01 0f 01 04 61 04 07 04 01 0f 01 03 a.......a....... -| 4080: 61 03 07 04 01 0f 01 0f 61 02 05 04 09 0f 09 61 a.......a......a -| page 7 offset 24576 -| 0: 0d 00 00 00 05 0f 1c 00 0f f0 0f e0 0f d3 0f c5 ................ -| 16: 0f b8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ -| 4016: 00 00 00 00 00 00 00 00 0b 05 04 11 11 13 62 31 ..............b1 -| 4032: 62 31 37 20 31 0c 04 04 11 13 13 62 31 62 31 63 b17 1......b1b1c -| 4048: 37 20 31 0b 03 04 11 11 13 63 31 63 31 37 20 31 7 1......c1c17 1 -| 4064: 0e 02 04 11 13 07 63 31 63 31 64 37 20 31 20 31 ......c1c1d7 1 1 -| 4080: 0e 01 04 11 13 17 63 31 63 31 63 37 20 31 00 00 ......c1c1c7 1.. -| end crash-3afa1ca9e9c1bd.db -}]} {} - -do_intck_test 2.1 { - {corruption found while reading database schema} -} - -#------------------------------------------------------------------------- -reset_db -do_execsql_test 3.0 { - PRAGMA page_size = 1024; - CREATE TABLE t1(a, b); - CREATE INDEX i1 ON t1(a); - INSERT INTO t1 VALUES(1, 1), (2, 2), (3, 3); -} - -do_test 3.1 { - set pgno [db one {SELECT rootpage FROM sqlite_schema WHERE name='t1'}] - db close - hexio_write test.db [expr ($pgno-1)*1024] 0000 -} {2} - -sqlite3 db test.db -do_intck_test 3.2 { - {corruption found while scanning database object i1} - {corruption found while scanning database object t1} -} - -finish_test - - DELETED ext/intck/intckfault.test Index: ext/intck/intckfault.test ================================================================== --- ext/intck/intckfault.test +++ /dev/null @@ -1,42 +0,0 @@ -# 2024 February 24 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# - -source [file join [file dirname [info script]] intck_common.tcl] -set testprefix intckfault -return_if_no_intck - -do_execsql_test 1.0 { - CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); - INSERT INTO t1 VALUES(1, 2, 3); - INSERT INTO t1 VALUES(2, 'two', 'three'); - INSERT INTO t1 VALUES(3, NULL, NULL); - CREATE INDEX i1 ON t1(b, c); -} - -do_faultsim_test 1 -faults oom-t* -prep { -} -body { - set ::ic [sqlite3_intck db main] - set nStep 0 - while {"SQLITE_OK"==[$::ic step]} { - incr nStep - if {$nStep==3} { $::ic unlock } - } - set res [$::ic error] - $::ic close - set res -} -test { - catch { $::ic close } - faultsim_test_result {0 {SQLITE_OK {}}} {0 {SQLITE_NOMEM {}}} {0 {SQLITE_NOMEM {out of memory}}} -} - -finish_test - DELETED ext/intck/sqlite3intck.c Index: ext/intck/sqlite3intck.c ================================================================== --- ext/intck/sqlite3intck.c +++ /dev/null @@ -1,940 +0,0 @@ -/* -** 2024-02-08 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -*/ - -#include "sqlite3intck.h" -#include -#include - -#include -#include - -/* -** nKeyVal: -** The number of values that make up the 'key' for the current pCheck -** statement. -** -** rc: -** Error code returned by most recent sqlite3_intck_step() or -** sqlite3_intck_unlock() call. This is set to SQLITE_DONE when -** the integrity-check operation is finished. -** -** zErr: -** If the object has entered the error state, this is the error message. -** Is freed using sqlite3_free() when the object is deleted. -** -** zTestSql: -** The value returned by the most recent call to sqlite3_intck_testsql(). -** Each call to testsql() frees the previous zTestSql value (using -** sqlite3_free()) and replaces it with the new value it will return. -*/ -struct sqlite3_intck { - sqlite3 *db; - const char *zDb; /* Copy of zDb parameter to _open() */ - char *zObj; /* Current object. Or NULL. */ - - sqlite3_stmt *pCheck; /* Current check statement */ - char *zKey; - int nKeyVal; - - char *zMessage; - int bCorruptSchema; - - int rc; /* Error code */ - char *zErr; /* Error message */ - char *zTestSql; /* Returned by sqlite3_intck_test_sql() */ -}; - - -/* -** Some error has occurred while using database p->db. Save the error message -** and error code currently held by the database handle in p->rc and p->zErr. -*/ -static void intckSaveErrmsg(sqlite3_intck *p){ - p->rc = sqlite3_errcode(p->db); - sqlite3_free(p->zErr); - p->zErr = sqlite3_mprintf("%s", sqlite3_errmsg(p->db)); -} - -/* -** If the handle passed as the first argument is already in the error state, -** then this function is a no-op (returns NULL immediately). Otherwise, if an -** error occurs within this function, it leaves an error in said handle. -** -** Otherwise, this function attempts to prepare SQL statement zSql and -** return the resulting statement handle to the user. -*/ -static sqlite3_stmt *intckPrepare(sqlite3_intck *p, const char *zSql){ - sqlite3_stmt *pRet = 0; - if( p->rc==SQLITE_OK ){ - p->rc = sqlite3_prepare_v2(p->db, zSql, -1, &pRet, 0); - if( p->rc!=SQLITE_OK ){ - intckSaveErrmsg(p); - assert( pRet==0 ); - } - } - return pRet; -} - -/* -** If the handle passed as the first argument is already in the error state, -** then this function is a no-op (returns NULL immediately). Otherwise, if an -** error occurs within this function, it leaves an error in said handle. -** -** Otherwise, this function treats argument zFmt as a printf() style format -** string. It formats it according to the trailing arguments and then -** attempts to prepare the results and return the resulting prepared -** statement. -*/ -static sqlite3_stmt *intckPrepareFmt(sqlite3_intck *p, const char *zFmt, ...){ - sqlite3_stmt *pRet = 0; - va_list ap; - char *zSql = 0; - va_start(ap, zFmt); - zSql = sqlite3_vmprintf(zFmt, ap); - if( p->rc==SQLITE_OK && zSql==0 ){ - p->rc = SQLITE_NOMEM; - } - pRet = intckPrepare(p, zSql); - sqlite3_free(zSql); - va_end(ap); - return pRet; -} - -/* -** Finalize SQL statement pStmt. If an error occurs and the handle passed -** as the first argument does not already contain an error, store the -** error in the handle. -*/ -static void intckFinalize(sqlite3_intck *p, sqlite3_stmt *pStmt){ - int rc = sqlite3_finalize(pStmt); - if( p->rc==SQLITE_OK && rc!=SQLITE_OK ){ - intckSaveErrmsg(p); - } -} - -/* -** If there is already an error in handle p, return it. Otherwise, call -** sqlite3_step() on the statement handle and return that value. -*/ -static int intckStep(sqlite3_intck *p, sqlite3_stmt *pStmt){ - if( p->rc ) return p->rc; - return sqlite3_step(pStmt); -} - -/* -** Execute SQL statement zSql. There is no way to obtain any results -** returned by the statement. This function uses the sqlite3_intck error -** code convention. -*/ -static void intckExec(sqlite3_intck *p, const char *zSql){ - sqlite3_stmt *pStmt = 0; - pStmt = intckPrepare(p, zSql); - intckStep(p, pStmt); - intckFinalize(p, pStmt); -} - -/* -** A wrapper around sqlite3_mprintf() that uses the sqlite3_intck error -** code convention. -*/ -static char *intckMprintf(sqlite3_intck *p, const char *zFmt, ...){ - va_list ap; - char *zRet = 0; - va_start(ap, zFmt); - zRet = sqlite3_vmprintf(zFmt, ap); - if( p->rc==SQLITE_OK ){ - if( zRet==0 ){ - p->rc = SQLITE_NOMEM; - } - }else{ - sqlite3_free(zRet); - zRet = 0; - } - return zRet; -} - -/* -** This is used by sqlite3_intck_unlock() to save the vector key value -** required to restart the current pCheck query as a nul-terminated string -** in p->zKey. -*/ -static void intckSaveKey(sqlite3_intck *p){ - int ii; - char *zSql = 0; - sqlite3_stmt *pStmt = 0; - sqlite3_stmt *pXinfo = 0; - const char *zDir = 0; - - assert( p->pCheck ); - assert( p->zKey==0 ); - - pXinfo = intckPrepareFmt(p, - "SELECT group_concat(desc, '') FROM %Q.sqlite_schema s, " - "pragma_index_xinfo(%Q, %Q) " - "WHERE s.type='index' AND s.name=%Q", - p->zDb, p->zObj, p->zDb, p->zObj - ); - if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXinfo) ){ - zDir = (const char*)sqlite3_column_text(pXinfo, 0); - } - - if( zDir==0 ){ - /* Object is a table, not an index. This is the easy case,as there are - ** no DESC columns or NULL values in a primary key. */ - const char *zSep = "SELECT '(' || "; - for(ii=0; iinKeyVal; ii++){ - zSql = intckMprintf(p, "%z%squote(?)", zSql, zSep); - zSep = " || ', ' || "; - } - zSql = intckMprintf(p, "%z || ')'", zSql); - }else{ - - /* Object is an index. */ - assert( p->nKeyVal>1 ); - for(ii=p->nKeyVal; ii>0; ii--){ - int bLastIsDesc = zDir[ii-1]=='1'; - int bLastIsNull = sqlite3_column_type(p->pCheck, ii)==SQLITE_NULL; - const char *zLast = sqlite3_column_name(p->pCheck, ii); - char *zLhs = 0; - char *zRhs = 0; - char *zWhere = 0; - - if( bLastIsNull ){ - if( bLastIsDesc ) continue; - zWhere = intckMprintf(p, "'%s IS NOT NULL'", zLast); - }else{ - const char *zOp = bLastIsDesc ? "<" : ">"; - zWhere = intckMprintf(p, "'%s %s ' || quote(?%d)", zLast, zOp, ii); - } - - if( ii>1 ){ - const char *zLhsSep = ""; - const char *zRhsSep = ""; - int jj; - for(jj=0; jjpCheck,jj+1); - zLhs = intckMprintf(p, "%z%s%s", zLhs, zLhsSep, zAlias); - zRhs = intckMprintf(p, "%z%squote(?%d)", zRhs, zRhsSep, jj+1); - zLhsSep = ","; - zRhsSep = " || ',' || "; - } - - zWhere = intckMprintf(p, - "'(%z) IS (' || %z || ') AND ' || %z", - zLhs, zRhs, zWhere); - } - zWhere = intckMprintf(p, "'WHERE ' || %z", zWhere); - - zSql = intckMprintf(p, "%z%s(quote( %z ) )", - zSql, - (zSql==0 ? "VALUES" : ",\n "), - zWhere - ); - } - zSql = intckMprintf(p, - "WITH wc(q) AS (\n%z\n)" - "SELECT 'VALUES' || group_concat('(' || q || ')', ',\n ') FROM wc" - , zSql - ); - } - - pStmt = intckPrepare(p, zSql); - if( p->rc==SQLITE_OK ){ - for(ii=0; iinKeyVal; ii++){ - sqlite3_bind_value(pStmt, ii+1, sqlite3_column_value(p->pCheck, ii+1)); - } - if( SQLITE_ROW==sqlite3_step(pStmt) ){ - p->zKey = intckMprintf(p,"%s",(const char*)sqlite3_column_text(pStmt, 0)); - } - intckFinalize(p, pStmt); - } - - sqlite3_free(zSql); - intckFinalize(p, pXinfo); -} - -/* -** Find the next database object (table or index) to check. If successful, -** set sqlite3_intck.zObj to point to a nul-terminated buffer containing -** the object's name before returning. -*/ -static void intckFindObject(sqlite3_intck *p){ - sqlite3_stmt *pStmt = 0; - char *zPrev = p->zObj; - p->zObj = 0; - - assert( p->rc==SQLITE_OK ); - assert( p->pCheck==0 ); - - pStmt = intckPrepareFmt(p, - "WITH tables(table_name) AS (" - " SELECT name" - " FROM %Q.sqlite_schema WHERE (type='table' OR type='index') AND rootpage" - " UNION ALL " - " SELECT 'sqlite_schema'" - ")" - "SELECT table_name FROM tables " - "WHERE ?1 IS NULL OR table_name%s?1 " - "ORDER BY 1" - , p->zDb, (p->zKey ? ">=" : ">") - ); - - if( p->rc==SQLITE_OK ){ - sqlite3_bind_text(pStmt, 1, zPrev, -1, SQLITE_TRANSIENT); - if( sqlite3_step(pStmt)==SQLITE_ROW ){ - p->zObj = intckMprintf(p,"%s",(const char*)sqlite3_column_text(pStmt, 0)); - } - } - intckFinalize(p, pStmt); - - /* If this is a new object, ensure the previous key value is cleared. */ - if( sqlite3_stricmp(p->zObj, zPrev) ){ - sqlite3_free(p->zKey); - p->zKey = 0; - } - - sqlite3_free(zPrev); -} - -/* -** Return the size in bytes of the first token in nul-terminated buffer z. -** For the purposes of this call, a token is either: -** -** * a quoted SQL string, -* * a contiguous series of ascii alphabet characters, or -* * any other single byte. -*/ -static int intckGetToken(const char *z){ - char c = z[0]; - int iRet = 1; - if( c=='\'' || c=='"' || c=='`' ){ - while( 1 ){ - if( z[iRet]==c ){ - iRet++; - if( z[iRet]!=c ) break; - } - iRet++; - } - } - else if( c=='[' ){ - while( z[iRet++]!=']' && z[iRet] ); - } - else if( (c>='A' && c<='Z') || (c>='a' && c<='z') ){ - while( (z[iRet]>='A' && z[iRet]<='Z') || (z[iRet]>='a' && z[iRet]<='z') ){ - iRet++; - } - } - - return iRet; -} - -/* -** Return true if argument c is an ascii whitespace character. -*/ -static int intckIsSpace(char c){ - return (c==' ' || c=='\t' || c=='\n' || c=='\r'); -} - -/* -** Argument z points to the text of a CREATE INDEX statement. This function -** identifies the part of the text that contains either the index WHERE -** clause (if iCol<0) or the iCol'th column of the index. -** -** If (iCol<0), the identified fragment does not include the "WHERE" keyword, -** only the expression that follows it. If (iCol>=0) then the identified -** fragment does not include any trailing sort-order keywords - "ASC" or -** "DESC". -** -** If the CREATE INDEX statement does not contain the requested field or -** clause, NULL is returned and (*pnByte) is set to 0. Otherwise, a pointer to -** the identified fragment is returned and output parameter (*pnByte) set -** to its size in bytes. -*/ -static const char *intckParseCreateIndex(const char *z, int iCol, int *pnByte){ - int iOff = 0; - int iThisCol = 0; - int iStart = 0; - int nOpen = 0; - - const char *zRet = 0; - int nRet = 0; - - int iEndOfCol = 0; - - /* Skip forward until the first "(" token */ - while( z[iOff]!='(' ){ - iOff += intckGetToken(&z[iOff]); - if( z[iOff]=='\0' ) return 0; - } - assert( z[iOff]=='(' ); - - nOpen = 1; - iOff++; - iStart = iOff; - while( z[iOff] ){ - const char *zToken = &z[iOff]; - int nToken = 0; - - /* Check if this is the end of the current column - either a "," or ")" - ** when nOpen==1. */ - if( nOpen==1 ){ - if( z[iOff]==',' || z[iOff]==')' ){ - if( iCol==iThisCol ){ - int iEnd = iEndOfCol ? iEndOfCol : iOff; - nRet = (iEnd - iStart); - zRet = &z[iStart]; - break; - } - iStart = iOff+1; - while( intckIsSpace(z[iStart]) ) iStart++; - iThisCol++; - } - if( z[iOff]==')' ) break; - } - if( z[iOff]=='(' ) nOpen++; - if( z[iOff]==')' ) nOpen--; - nToken = intckGetToken(zToken); - - if( (nToken==3 && 0==sqlite3_strnicmp(zToken, "ASC", nToken)) - || (nToken==4 && 0==sqlite3_strnicmp(zToken, "DESC", nToken)) - ){ - iEndOfCol = iOff; - }else if( 0==intckIsSpace(zToken[0]) ){ - iEndOfCol = 0; - } - - iOff += nToken; - } - - /* iStart is now the byte offset of 1 byte passed the final ')' in the - ** CREATE INDEX statement. Try to find a WHERE clause to return. */ - while( zRet==0 && z[iOff] ){ - int n = intckGetToken(&z[iOff]); - if( n==5 && 0==sqlite3_strnicmp(&z[iOff], "where", 5) ){ - zRet = &z[iOff+5]; - nRet = (int)strlen(zRet); - } - iOff += n; - } - - /* Trim any whitespace from the start and end of the returned string. */ - if( zRet ){ - while( intckIsSpace(zRet[0]) ){ - nRet--; - zRet++; - } - while( nRet>0 && intckIsSpace(zRet[nRet-1]) ) nRet--; - } - - *pnByte = nRet; - return zRet; -} - -/* -** User-defined SQL function wrapper for intckParseCreateIndex(): -** -** SELECT parse_create_index(, ); -*/ -static void intckParseCreateIndexFunc( - sqlite3_context *pCtx, - int nVal, - sqlite3_value **apVal -){ - const char *zSql = (const char*)sqlite3_value_text(apVal[0]); - int idx = sqlite3_value_int(apVal[1]); - const char *zRes = 0; - int nRes = 0; - - assert( nVal==2 ); - if( zSql ){ - zRes = intckParseCreateIndex(zSql, idx, &nRes); - } - sqlite3_result_text(pCtx, zRes, nRes, SQLITE_TRANSIENT); -} - -/* -** Return true if sqlite3_intck.db has automatic indexes enabled, false -** otherwise. -*/ -static int intckGetAutoIndex(sqlite3_intck *p){ - int bRet = 0; - sqlite3_stmt *pStmt = 0; - pStmt = intckPrepare(p, "PRAGMA automatic_index"); - if( SQLITE_ROW==intckStep(p, pStmt) ){ - bRet = sqlite3_column_int(pStmt, 0); - } - intckFinalize(p, pStmt); - return bRet; -} - -/* -** Return true if zObj is an index, or false otherwise. -*/ -static int intckIsIndex(sqlite3_intck *p, const char *zObj){ - int bRet = 0; - sqlite3_stmt *pStmt = 0; - pStmt = intckPrepareFmt(p, - "SELECT 1 FROM %Q.sqlite_schema WHERE name=%Q AND type='index'", - p->zDb, zObj - ); - if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ - bRet = 1; - } - intckFinalize(p, pStmt); - return bRet; -} - -/* -** Return a pointer to a nul-terminated buffer containing the SQL statement -** used to check database object zObj (a table or index) for corruption. -** If parameter zPrev is not NULL, then it must be a string containing the -** vector key required to restart the check where it left off last time. -** If pnKeyVal is not NULL, then (*pnKeyVal) is set to the number of -** columns in the vector key value for the specified object. -** -** This function uses the sqlite3_intck error code convention. -*/ -static char *intckCheckObjectSql( - sqlite3_intck *p, /* Integrity check object */ - const char *zObj, /* Object (table or index) to scan */ - const char *zPrev, /* Restart key vector, if any */ - int *pnKeyVal /* OUT: Number of key-values for this scan */ -){ - char *zRet = 0; - sqlite3_stmt *pStmt = 0; - int bAutoIndex = 0; - int bIsIndex = 0; - - const char *zCommon = - /* Relation without_rowid also contains just one row. Column "b" is - ** set to true if the table being examined is a WITHOUT ROWID table, - ** or false otherwise. */ - ", without_rowid(b) AS (" - " SELECT EXISTS (" - " SELECT 1 FROM tabname, pragma_index_list(tab, db) AS l" - " WHERE origin='pk' " - " AND NOT EXISTS (SELECT 1 FROM sqlite_schema WHERE name=l.name)" - " )" - ")" - "" - /* Table idx_cols contains 1 row for each column in each index on the - ** table being checked. Columns are: - ** - ** idx_name: Name of the index. - ** idx_ispk: True if this index is the PK of a WITHOUT ROWID table. - ** col_name: Name of indexed column, or NULL for index on expression. - ** col_expr: Indexed expression, including COLLATE clause. - ** col_alias: Alias used for column in 'intck_wrapper' table. - */ - ", idx_cols(idx_name, idx_ispk, col_name, col_expr, col_alias) AS (" - " SELECT l.name, (l.origin=='pk' AND w.b), i.name, COALESCE((" - " SELECT parse_create_index(sql, i.seqno) FROM " - " sqlite_schema WHERE name = l.name" - " ), format('\"%w\"', i.name) || ' COLLATE ' || quote(i.coll))," - " 'c' || row_number() OVER ()" - " FROM " - " tabname t," - " without_rowid w," - " pragma_index_list(t.tab, t.db) l," - " pragma_index_xinfo(l.name) i" - " WHERE i.key" - " UNION ALL" - " SELECT '', 1, '_rowid_', '_rowid_', 'r1' FROM without_rowid WHERE b=0" - ")" - "" - "" - /* - ** For a PK declared as "PRIMARY KEY(a, b) ... WITHOUT ROWID", where - ** the intck_wrapper aliases of "a" and "b" are "c1" and "c2": - ** - ** o_pk: "o.c1, o.c2" - ** i_pk: "i.'a', i.'b'" - ** ... - ** n_pk: 2 - */ - ", tabpk(db, tab, idx, o_pk, i_pk, q_pk, eq_pk, ps_pk, pk_pk, n_pk) AS (" - " WITH pkfields(f, a) AS (" - " SELECT i.col_name, i.col_alias FROM idx_cols i WHERE i.idx_ispk" - " )" - " SELECT t.db, t.tab, t.idx, " - " group_concat(a, ', '), " - " group_concat('i.'||quote(f), ', '), " - " group_concat('quote(o.'||a||')', ' || '','' || '), " - " format('(%s)==(%s)'," - " group_concat('o.'||a, ', '), " - " group_concat(format('\"%w\"', f), ', ')" - " )," - " group_concat('%s', ',')," - " group_concat('quote('||a||')', ', '), " - " count(*)" - " FROM tabname t, pkfields" - ")" - "" - ", idx(name, match_expr, partial, partial_alias, idx_ps, idx_idx) AS (" - " SELECT idx_name," - " format('(%s,%s) IS (%s,%s)', " - " group_concat(i.col_expr, ', '), i_pk," - " group_concat('o.'||i.col_alias, ', '), o_pk" - " ), " - " parse_create_index(" - " (SELECT sql FROM sqlite_schema WHERE name=idx_name), -1" - " )," - " 'cond' || row_number() OVER ()" - " , group_concat('%s', ',')" - " , group_concat('quote('||i.col_alias||')', ', ')" - " FROM tabpk t, " - " without_rowid w," - " idx_cols i" - " WHERE i.idx_ispk==0 " - " GROUP BY idx_name" - ")" - "" - ", wrapper_with(s) AS (" - " SELECT 'intck_wrapper AS (\n SELECT\n ' || (" - " WITH f(a, b) AS (" - " SELECT col_expr, col_alias FROM idx_cols" - " UNION ALL " - " SELECT partial, partial_alias FROM idx WHERE partial IS NOT NULL" - " )" - " SELECT group_concat(format('%s AS %s', a, b), ',\n ') FROM f" - " )" - " || format('\n FROM %Q.%Q ', t.db, t.tab)" - /* If the object being checked is a table, append "NOT INDEXED". - ** Otherwise, append "INDEXED BY ", and then, if the index - ** is a partial index " WHERE ". */ - " || CASE WHEN t.idx IS NULL THEN " - " 'NOT INDEXED'" - " ELSE" - " format('INDEXED BY %Q%s', t.idx, ' WHERE '||i.partial)" - " END" - " || '\n)'" - " FROM tabname t LEFT JOIN idx i ON (i.name=t.idx)" - ")" - "" - ; - - bAutoIndex = intckGetAutoIndex(p); - if( bAutoIndex ) intckExec(p, "PRAGMA automatic_index = 0"); - - bIsIndex = intckIsIndex(p, zObj); - if( bIsIndex ){ - pStmt = intckPrepareFmt(p, - /* Table idxname contains a single row. The first column, "db", contains - ** the name of the db containing the table (e.g. "main") and the second, - ** "tab", the name of the table itself. */ - "WITH tabname(db, tab, idx) AS (" - " SELECT %Q, (SELECT tbl_name FROM %Q.sqlite_schema WHERE name=%Q), %Q " - ")" - "" - ", whereclause(w_c) AS (%s)" - "" - "%s" /* zCommon */ - "" - ", case_statement(c) AS (" - " SELECT " - " 'CASE WHEN (' || group_concat(col_alias, ', ') || ', 1) IS (\n' " - " || ' SELECT ' || group_concat(col_expr, ', ') || ', 1 FROM '" - " || format('%%Q.%%Q NOT INDEXED WHERE %%s\n', t.db, t.tab, p.eq_pk)" - " || ' )\n THEN NULL\n '" - " || 'ELSE format(''surplus entry ('" - " || group_concat('%%s', ',') || ',' || p.ps_pk" - " || ') in index ' || t.idx || ''', ' " - " || group_concat('quote('||i.col_alias||')', ', ') || ', ' || p.pk_pk" - " || ')'" - " || '\n END AS error_message'" - " FROM tabname t, tabpk p, idx_cols i WHERE i.idx_name=t.idx" - ")" - "" - ", thiskey(k, n) AS (" - " SELECT group_concat(i.col_alias, ', ') || ', ' || p.o_pk, " - " count(*) + p.n_pk " - " FROM tabpk p, idx_cols i WHERE i.idx_name=p.idx" - ")" - "" - ", main_select(m, n) AS (" - " SELECT format(" - " 'WITH %%s\n' ||" - " ', idx_checker AS (\n' ||" - " ' SELECT %%s,\n' ||" - " ' %%s\n' || " - " ' FROM intck_wrapper AS o\n' ||" - " ')\n'," - " ww.s, c, t.k" - " ), t.n" - " FROM case_statement, wrapper_with ww, thiskey t" - ")" - - "SELECT m || " - " group_concat('SELECT * FROM idx_checker ' || w_c, ' UNION ALL '), n" - " FROM " - "main_select, whereclause " - , p->zDb, p->zDb, zObj, zObj - , zPrev ? zPrev : "VALUES('')", zCommon - ); - }else{ - pStmt = intckPrepareFmt(p, - /* Table tabname contains a single row. The first column, "db", contains - ** the name of the db containing the table (e.g. "main") and the second, - ** "tab", the name of the table itself. */ - "WITH tabname(db, tab, idx, prev) AS (SELECT %Q, %Q, NULL, %Q)" - "" - "%s" /* zCommon */ - - /* expr(e) contains one row for each index on table zObj. Value e - ** is set to an expression that evaluates to NULL if the required - ** entry is present in the index, or an error message otherwise. */ - ", expr(e, p) AS (" - " SELECT format('CASE WHEN EXISTS \n" - " (SELECT 1 FROM %%Q.%%Q AS i INDEXED BY %%Q WHERE %%s%%s)\n" - " THEN NULL\n" - " ELSE format(''entry (%%s,%%s) missing from index %%s'', %%s, %%s)\n" - " END\n'" - " , t.db, t.tab, i.name, i.match_expr, ' AND (' || partial || ')'," - " i.idx_ps, t.ps_pk, i.name, i.idx_idx, t.pk_pk)," - " CASE WHEN partial IS NULL THEN NULL ELSE i.partial_alias END" - " FROM tabpk t, idx i" - ")" - - ", numbered(ii, cond, e) AS (" - " SELECT 0, 'n.ii=0', 'NULL'" - " UNION ALL " - " SELECT row_number() OVER ()," - " '(n.ii='||row_number() OVER ()||COALESCE(' AND '||p||')', ')'), e" - " FROM expr" - ")" - - ", counter_with(w) AS (" - " SELECT 'WITH intck_counter(ii) AS (\n ' || " - " group_concat('SELECT '||ii, ' UNION ALL\n ') " - " || '\n)' FROM numbered" - ")" - "" - ", case_statement(c) AS (" - " SELECT 'CASE ' || " - " group_concat(format('\n WHEN %%s THEN (%%s)', cond, e), '') ||" - " '\nEND AS error_message'" - " FROM numbered" - ")" - "" - - /* This table contains a single row consisting of a single value - - ** the text of an SQL expression that may be used by the main SQL - ** statement to output an SQL literal that can be used to resume - ** the scan if it is suspended. e.g. for a rowid table, an expression - ** like: - ** - ** format('(%d,%d)', _rowid_, n.ii) - */ - ", thiskey(k, n) AS (" - " SELECT o_pk || ', ii', n_pk+1 FROM tabpk" - ")" - "" - ", whereclause(w_c) AS (" - " SELECT CASE WHEN prev!='' THEN " - " '\nWHERE (' || o_pk ||', n.ii) > ' || prev" - " ELSE ''" - " END" - " FROM tabpk, tabname" - ")" - "" - ", main_select(m, n) AS (" - " SELECT format(" - " '%%s, %%s\nSELECT %%s,\n%%s\nFROM intck_wrapper AS o" - ", intck_counter AS n%%s\nORDER BY %%s', " - " w, ww.s, c, thiskey.k, whereclause.w_c, t.o_pk" - " ), thiskey.n" - " FROM case_statement, tabpk t, counter_with, " - " wrapper_with ww, thiskey, whereclause" - ")" - - "SELECT m, n FROM main_select", - p->zDb, zObj, zPrev, zCommon - ); - } - - while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ - zRet = intckMprintf(p, "%s", (const char*)sqlite3_column_text(pStmt, 0)); - if( pnKeyVal ){ - *pnKeyVal = sqlite3_column_int(pStmt, 1); - } - } - intckFinalize(p, pStmt); - - if( bAutoIndex ) intckExec(p, "PRAGMA automatic_index = 1"); - return zRet; -} - -/* -** Open a new integrity-check object. -*/ -int sqlite3_intck_open( - sqlite3 *db, /* Database handle to operate on */ - const char *zDbArg, /* "main", "temp" etc. */ - sqlite3_intck **ppOut /* OUT: New integrity-check handle */ -){ - sqlite3_intck *pNew = 0; - int rc = SQLITE_OK; - const char *zDb = zDbArg ? zDbArg : "main"; - int nDb = (int)strlen(zDb); - - pNew = (sqlite3_intck*)sqlite3_malloc(sizeof(*pNew) + nDb + 1); - if( pNew==0 ){ - rc = SQLITE_NOMEM; - }else{ - memset(pNew, 0, sizeof(*pNew)); - pNew->db = db; - pNew->zDb = (const char*)&pNew[1]; - memcpy(&pNew[1], zDb, nDb+1); - rc = sqlite3_create_function(db, "parse_create_index", - 2, SQLITE_UTF8, 0, intckParseCreateIndexFunc, 0, 0 - ); - if( rc!=SQLITE_OK ){ - sqlite3_intck_close(pNew); - pNew = 0; - } - } - - *ppOut = pNew; - return rc; -} - -/* -** Free the integrity-check object. -*/ -void sqlite3_intck_close(sqlite3_intck *p){ - if( p ){ - sqlite3_finalize(p->pCheck); - sqlite3_create_function( - p->db, "parse_create_index", 1, SQLITE_UTF8, 0, 0, 0, 0 - ); - sqlite3_free(p->zObj); - sqlite3_free(p->zKey); - sqlite3_free(p->zTestSql); - sqlite3_free(p->zErr); - sqlite3_free(p->zMessage); - sqlite3_free(p); - } -} - -/* -** Step the integrity-check object. -*/ -int sqlite3_intck_step(sqlite3_intck *p){ - if( p->rc==SQLITE_OK ){ - - if( p->zMessage ){ - sqlite3_free(p->zMessage); - p->zMessage = 0; - } - - if( p->bCorruptSchema ){ - p->rc = SQLITE_DONE; - }else - if( p->pCheck==0 ){ - intckFindObject(p); - if( p->rc==SQLITE_OK ){ - if( p->zObj ){ - char *zSql = 0; - zSql = intckCheckObjectSql(p, p->zObj, p->zKey, &p->nKeyVal); - p->pCheck = intckPrepare(p, zSql); - sqlite3_free(zSql); - sqlite3_free(p->zKey); - p->zKey = 0; - }else{ - p->rc = SQLITE_DONE; - } - }else if( p->rc==SQLITE_CORRUPT ){ - p->rc = SQLITE_OK; - p->zMessage = intckMprintf(p, "%s", - "corruption found while reading database schema" - ); - p->bCorruptSchema = 1; - } - } - - if( p->pCheck ){ - assert( p->rc==SQLITE_OK ); - if( sqlite3_step(p->pCheck)==SQLITE_ROW ){ - /* Normal case, do nothing. */ - }else{ - intckFinalize(p, p->pCheck); - p->pCheck = 0; - p->nKeyVal = 0; - if( p->rc==SQLITE_CORRUPT ){ - p->rc = SQLITE_OK; - p->zMessage = intckMprintf(p, - "corruption found while scanning database object %s", p->zObj - ); - } - } - } - } - - return p->rc; -} - -/* -** Return a message describing the corruption encountered by the most recent -** call to sqlite3_intck_step(), or NULL if no corruption was encountered. -*/ -const char *sqlite3_intck_message(sqlite3_intck *p){ - assert( p->pCheck==0 || p->zMessage==0 ); - if( p->zMessage ){ - return p->zMessage; - } - if( p->pCheck ){ - return (const char*)sqlite3_column_text(p->pCheck, 0); - } - return 0; -} - -/* -** Return the error code and message. -*/ -int sqlite3_intck_error(sqlite3_intck *p, const char **pzErr){ - if( pzErr ) *pzErr = p->zErr; - return (p->rc==SQLITE_DONE ? SQLITE_OK : p->rc); -} - -/* -** Close any read transaction the integrity-check object is holding open -** on the database. -*/ -int sqlite3_intck_unlock(sqlite3_intck *p){ - if( p->rc==SQLITE_OK && p->pCheck ){ - assert( p->zKey==0 && p->nKeyVal>0 ); - intckSaveKey(p); - intckFinalize(p, p->pCheck); - p->pCheck = 0; - } - return p->rc; -} - -/* -** Return the SQL statement used to check object zObj. Or, if zObj is -** NULL, the current SQL statement. -*/ -const char *sqlite3_intck_test_sql(sqlite3_intck *p, const char *zObj){ - sqlite3_free(p->zTestSql); - if( zObj ){ - p->zTestSql = intckCheckObjectSql(p, zObj, 0, 0); - }else{ - if( p->zObj ){ - p->zTestSql = intckCheckObjectSql(p, p->zObj, p->zKey, 0); - }else{ - sqlite3_free(p->zTestSql); - p->zTestSql = 0; - } - } - return p->zTestSql; -} DELETED ext/intck/sqlite3intck.h Index: ext/intck/sqlite3intck.h ================================================================== --- ext/intck/sqlite3intck.h +++ /dev/null @@ -1,171 +0,0 @@ -/* -** 2024-02-08 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -*/ - -/* -** Incremental Integrity-Check Extension -** ------------------------------------- -** -** This module contains code to check whether or not an SQLite database -** is well-formed or corrupt. This is the same task as performed by SQLite's -** built-in "PRAGMA integrity_check" command. This module differs from -** "PRAGMA integrity_check" in that: -** -** + It is less thorough - this module does not detect certain types -** of corruption that are detected by the PRAGMA command. However, -** it does detect all kinds of corruption that are likely to cause -** errors in SQLite applications. -** -** + It is slower. Sometimes up to three times slower. -** -** + It allows integrity-check operations to be split into multiple -** transactions, so that the database does not need to be read-locked -** for the duration of the integrity-check. -** -** One way to use the API to run integrity-check on the "main" database -** of handle db is: -** -** int rc = SQLITE_OK; -** sqlite3_intck *p = 0; -** -** sqlite3_intck_open(db, "main", &p); -** while( SQLITE_OK==sqlite3_intck_step(p) ){ -** const char *zMsg = sqlite3_intck_message(p); -** if( zMsg ) printf("corruption: %s\n", zMsg); -** } -** rc = sqlite3_intck_error(p, &zErr); -** if( rc!=SQLITE_OK ){ -** printf("error occured (rc=%d), (errmsg=%s)\n", rc, zErr); -** } -** sqlite3_intck_close(p); -** -** Usually, the sqlite3_intck object opens a read transaction within the -** first call to sqlite3_intck_step() and holds it open until the -** integrity-check is complete. However, if sqlite3_intck_unlock() is -** called, the read transaction is ended and a new read transaction opened -** by the subsequent call to sqlite3_intck_step(). -*/ - -#ifndef _SQLITE_INTCK_H -#define _SQLITE_INTCK_H - -#include "sqlite3.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* -** An ongoing incremental integrity-check operation is represented by an -** opaque pointer of the following type. -*/ -typedef struct sqlite3_intck sqlite3_intck; - -/* -** Open a new incremental integrity-check object. If successful, populate -** output variable (*ppOut) with the new object handle and return SQLITE_OK. -** Or, if an error occurs, set (*ppOut) to NULL and return an SQLite error -** code (e.g. SQLITE_NOMEM). -** -** The integrity-check will be conducted on database zDb (which must be "main", -** "temp", or the name of an attached database) of database handle db. Once -** this function has been called successfully, the caller should not use -** database handle db until the integrity-check object has been destroyed -** using sqlite3_intck_close(). -*/ -int sqlite3_intck_open( - sqlite3 *db, /* Database handle */ - const char *zDb, /* Database name ("main", "temp" etc.) */ - sqlite3_intck **ppOut /* OUT: New sqlite3_intck handle */ -); - -/* -** Close and release all resources associated with a handle opened by an -** earlier call to sqlite3_intck_open(). The results of using an -** integrity-check handle after it has been passed to this function are -** undefined. -*/ -void sqlite3_intck_close(sqlite3_intck *pCk); - -/* -** Do the next step of the integrity-check operation specified by the handle -** passed as the only argument. This function returns SQLITE_DONE if the -** integrity-check operation is finished, or an SQLite error code if -** an error occurs, or SQLITE_OK if no error occurs but the integrity-check -** is not finished. It is not considered an error if database corruption -** is encountered. -** -** Following a successful call to sqlite3_intck_step() (one that returns -** SQLITE_OK), sqlite3_intck_message() returns a non-NULL value if -** corruption was detected in the db. -** -** If an error occurs and a value other than SQLITE_OK or SQLITE_DONE is -** returned, then the integrity-check handle is placed in an error state. -** In this state all subsequent calls to sqlite3_intck_step() or -** sqlite3_intck_unlock() will immediately return the same error. The -** sqlite3_intck_error() method may be used to obtain an English language -** error message in this case. -*/ -int sqlite3_intck_step(sqlite3_intck *pCk); - -/* -** If the previous call to sqlite3_intck_step() encountered corruption -** within the database, then this function returns a pointer to a buffer -** containing a nul-terminated string describing the corruption in -** English. If the previous call to sqlite3_intck_step() did not encounter -** corruption, or if there was no previous call, this function returns -** NULL. -*/ -const char *sqlite3_intck_message(sqlite3_intck *pCk); - -/* -** Close any read-transaction opened by an earlier call to -** sqlite3_intck_step(). Any subsequent call to sqlite3_intck_step() will -** open a new transaction. Return SQLITE_OK if successful, or an SQLite error -** code otherwise. -** -** If an error occurs, then the integrity-check handle is placed in an error -** state. In this state all subsequent calls to sqlite3_intck_step() or -** sqlite3_intck_unlock() will immediately return the same error. The -** sqlite3_intck_error() method may be used to obtain an English language -** error message in this case. -*/ -int sqlite3_intck_unlock(sqlite3_intck *pCk); - -/* -** If an error has occurred in an earlier call to sqlite3_intck_step() -** or sqlite3_intck_unlock(), then this method returns the associated -** SQLite error code. Additionally, if pzErr is not NULL, then (*pzErr) -** may be set to point to a nul-terminated string containing an English -** language error message. Or, if no error message is available, to -** NULL. -** -** If no error has occurred within sqlite3_intck_step() or -** sqlite_intck_unlock() calls on the handle passed as the first argument, -** then SQLITE_OK is returned and (*pzErr) set to NULL. -*/ -int sqlite3_intck_error(sqlite3_intck *pCk, const char **pzErr); - -/* -** This API is used for testing only. It returns the full-text of an SQL -** statement used to test object zObj, which may be a table or index. -** The returned buffer is valid until the next call to either this function -** or sqlite3_intck_close() on the same sqlite3_intck handle. -*/ -const char *sqlite3_intck_test_sql(sqlite3_intck *pCk, const char *zObj); - - -#ifdef __cplusplus -} /* end of the 'extern "C"' block */ -#endif - -#endif /* ifndef _SQLITE_INTCK_H */ DELETED ext/intck/test_intck.c Index: ext/intck/test_intck.c ================================================================== --- ext/intck/test_intck.c +++ /dev/null @@ -1,238 +0,0 @@ -/* -** 2010 August 28 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** Code for testing all sorts of SQLite interfaces. This code -** is not included in the SQLite library. -*/ - -#include "sqlite3.h" -#include "sqlite3intck.h" - -#if defined(INCLUDE_SQLITE_TCL_H) -# include "sqlite_tcl.h" -#else -# include "tcl.h" -#endif - -#include -#include - -/* In test1.c */ -int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb); -const char *sqlite3ErrName(int); - -typedef struct TestIntck TestIntck; -struct TestIntck { - sqlite3_intck *intck; -}; - -static int testIntckCmd( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - struct Subcmd { - const char *zName; - int nArg; - const char *zExpect; - } aCmd[] = { - {"close", 0, ""}, /* 0 */ - {"step", 0, ""}, /* 1 */ - {"message", 0, ""}, /* 2 */ - {"error", 0, ""}, /* 3 */ - {"unlock", 0, ""}, /* 4 */ - {"test_sql", 1, ""}, /* 5 */ - {0 , 0} - }; - int rc = TCL_OK; - int iIdx = -1; - TestIntck *p = (TestIntck*)clientData; - - if( objc<2 ){ - Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND ..."); - return TCL_ERROR; - } - - rc = Tcl_GetIndexFromObjStruct( - interp, objv[1], aCmd, sizeof(aCmd[0]), "SUB-COMMAND", 0, &iIdx - ); - if( rc ) return rc; - - if( objc!=2+aCmd[iIdx].nArg ){ - Tcl_WrongNumArgs(interp, 2, objv, aCmd[iIdx].zExpect); - return TCL_ERROR; - } - - switch( iIdx ){ - case 0: assert( 0==strcmp("close", aCmd[iIdx].zName) ); { - Tcl_DeleteCommand(interp, Tcl_GetStringFromObj(objv[0], 0)); - break; - } - - case 1: assert( 0==strcmp("step", aCmd[iIdx].zName) ); { - rc = sqlite3_intck_step(p->intck); - Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); - break; - } - - case 2: assert( 0==strcmp("message", aCmd[iIdx].zName) ); { - const char *z = sqlite3_intck_message(p->intck); - Tcl_SetObjResult(interp, Tcl_NewStringObj(z ? z : "", -1)); - break; - } - - case 3: assert( 0==strcmp("error", aCmd[iIdx].zName) ); { - const char *zErr = 0; - rc = sqlite3_intck_error(p->intck, 0); - Tcl_Obj *pRes = Tcl_NewObj(); - Tcl_ListObjAppendElement( - interp, pRes, Tcl_NewStringObj(sqlite3ErrName(rc), -1) - ); - sqlite3_intck_error(p->intck, &zErr); - Tcl_ListObjAppendElement( - interp, pRes, Tcl_NewStringObj(zErr ? zErr : 0, -1) - ); - Tcl_SetObjResult(interp, pRes); - break; - } - - case 4: assert( 0==strcmp("unlock", aCmd[iIdx].zName) ); { - rc = sqlite3_intck_unlock(p->intck); - Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); - break; - } - - case 5: assert( 0==strcmp("test_sql", aCmd[iIdx].zName) ); { - const char *zObj = Tcl_GetString(objv[2]); - const char *zSql = sqlite3_intck_test_sql(p->intck, zObj[0] ? zObj : 0); - Tcl_SetObjResult(interp, Tcl_NewStringObj(zSql, -1)); - break; - } - } - - return TCL_OK; -} - -/* -** Destructor for commands created by test_sqlite3_intck(). -*/ -static void testIntckFree(void *clientData){ - TestIntck *p = (TestIntck*)clientData; - sqlite3_intck_close(p->intck); - ckfree(p); -} - -/* -** tclcmd: sqlite3_intck DB DBNAME -*/ -static int test_sqlite3_intck( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - char zName[64]; - int iName = 0; - Tcl_CmdInfo info; - TestIntck *p = 0; - sqlite3 *db = 0; - const char *zDb = 0; - int rc = SQLITE_OK; - - if( objc!=3 ){ - Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME"); - return TCL_ERROR; - } - - p = (TestIntck*)ckalloc(sizeof(TestIntck)); - memset(p, 0, sizeof(TestIntck)); - - if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){ - return TCL_ERROR; - } - zDb = Tcl_GetString(objv[2]); - if( zDb[0]=='\0' ) zDb = 0; - - rc = sqlite3_intck_open(db, zDb, &p->intck); - if( rc!=SQLITE_OK ){ - ckfree(p); - Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3_errstr(rc), -1)); - return TCL_ERROR; - } - - do { - sprintf(zName, "intck%d", iName++); - }while( Tcl_GetCommandInfo(interp, zName, &info)!=0 ); - Tcl_CreateObjCommand(interp, zName, testIntckCmd, (void*)p, testIntckFree); - Tcl_SetObjResult(interp, Tcl_NewStringObj(zName, -1)); - - return TCL_OK; -} - -/* -** tclcmd: test_do_intck DB DBNAME -*/ -static int test_do_intck( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - sqlite3 *db = 0; - const char *zDb = 0; - int rc = SQLITE_OK; - sqlite3_intck *pCk = 0; - Tcl_Obj *pRet = 0; - const char *zErr = 0; - - if( objc!=3 ){ - Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME"); - return TCL_ERROR; - } - if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){ - return TCL_ERROR; - } - zDb = Tcl_GetString(objv[2]); - - pRet = Tcl_NewObj(); - Tcl_IncrRefCount(pRet); - - rc = sqlite3_intck_open(db, zDb, &pCk); - if( rc==SQLITE_OK ){ - while( sqlite3_intck_step(pCk)==SQLITE_OK ){ - const char *zMsg = sqlite3_intck_message(pCk); - if( zMsg ){ - Tcl_ListObjAppendElement(interp, pRet, Tcl_NewStringObj(zMsg, -1)); - } - } - rc = sqlite3_intck_error(pCk, &zErr); - } - if( rc!=SQLITE_OK ){ - if( zErr ){ - Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1)); - }else{ - Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); - } - }else{ - Tcl_SetObjResult(interp, pRet); - } - Tcl_DecrRefCount(pRet); - sqlite3_intck_close(pCk); - sqlite3_intck_close(0); - return rc ? TCL_ERROR : TCL_OK; -} - -int Sqlitetestintck_Init(Tcl_Interp *interp){ - Tcl_CreateObjCommand(interp, "sqlite3_intck", test_sqlite3_intck, 0, 0); - Tcl_CreateObjCommand(interp, "test_do_intck", test_do_intck, 0, 0); - return TCL_OK; -} Index: ext/jni/GNUmakefile ================================================================== --- ext/jni/GNUmakefile +++ ext/jni/GNUmakefile @@ -33,12 +33,10 @@ $(dir.bld.c): $(mkdir) $@ javac.flags ?= -Xlint:unchecked -Xlint:deprecation java.flags ?= -javac.flags += -encoding utf8 -# -------------^^^^^^^^^^^^^^ required for Windows builds jnicheck ?= 1 ifeq (1,$(jnicheck)) java.flags += -Xcheck:jni endif @@ -79,11 +77,10 @@ $(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/%,\ - Experimental.java \ NotNull.java \ Nullable.java \ ) $(patsubst %,$(dir.src.capi)/%,\ AbstractCollationCallback.java \ AggregateFunction.java \ @@ -92,11 +89,11 @@ BusyHandlerCallback.java \ CollationCallback.java \ CollationNeededCallback.java \ CommitHookCallback.java \ ConfigLogCallback.java \ - ConfigSqlLogCallback.java \ + ConfigSqllogCallback.java \ NativePointerHolder.java \ OutputPointer.java \ PrepareMultiCallback.java \ PreupdateHookCallback.java \ ProgressHandlerCallback.java \ @@ -111,11 +108,10 @@ UpdateHookCallback.java \ ValueHolder.java \ WindowFunction.java \ XDestroyCallback.java \ sqlite3.java \ - sqlite3_blob.java \ sqlite3_context.java \ sqlite3_stmt.java \ sqlite3_value.java \ ) $(patsubst %,$(dir.src.jni)/wrapper1/%,\ AggregateFunction.java \ @@ -122,11 +118,10 @@ ScalarFunction.java \ SqlFunction.java \ Sqlite.java \ SqliteException.java \ ValueHolder.java \ - WindowFunction.java \ ) JAVA_FILES.unittest := $(patsubst %,$(dir.src.jni)/%,\ capi/Tester1.java \ wrapper1/Tester2.java \ @@ -162,17 +157,16 @@ endif CLASS_FILES := define CLASSFILE_DEPS all: $(1).class -$(1).class: $(1).java CLASS_FILES += $(1).class endef $(foreach B,$(basename \ $(JAVA_FILES.main) $(JAVA_FILES.unittest) $(JAVA_FILES.tester)),\ $(eval $(call CLASSFILE_DEPS,$(B)))) -$(CLASS_FILES): $(MAKEFILE) +$(CLASS_FILES): $(JAVA_FILES) $(MAKEFILE) $(bin.javac) $(javac.flags) -h $(dir.bld.c) -cp $(classpath) $(JAVA_FILES) #.PHONY: classfiles ######################################################################## @@ -230,12 +224,11 @@ -DSQLITE_ENABLE_DBSTAT_VTAB \ -DSQLITE_ENABLE_BYTECODE_VTAB \ -DSQLITE_ENABLE_OFFSET_SQL_FUNC \ -DSQLITE_ENABLE_PREUPDATE_HOOK \ -DSQLITE_ENABLE_NORMALIZE \ - -DSQLITE_ENABLE_SQLLOG \ - -DSQLITE_ENABLE_COLUMN_METADATA + -DSQLITE_ENABLE_SQLLOG endif ifeq (1,$(opt.debug)) SQLITE_OPT += -DSQLITE_DEBUG -g -DDEBUG -UNDEBUG else @@ -321,11 +314,11 @@ 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) org.sqlite.jni.capi.Tester1 $(Tester1.flags) -sqllog + $(bin.java) $(test.flags.jvm) -sqllog test-mt: $(test.deps) @echo "Testing in multi-threaded mode:"; $(bin.java) $(test.flags.jvm) org.sqlite.jni.capi.Tester1 \ -t 7 -r 50 -shuffle $(Tester1.flags) $(bin.java) $(test.flags.jvm) org.sqlite.jni.wrapper1.Tester2 \ Index: ext/jni/README.md ================================================================== --- ext/jni/README.md +++ ext/jni/README.md @@ -14,12 +14,13 @@ > **FOREWARNING:** this subproject is very much in development and subject to any number of changes. Please do not rely on any information about its API until this disclaimer is removed. The JNI - bindings released with version 3.43 are a "tech preview." Once - finalized, strong backward compatibility guarantees will apply. + bindings released with version 3.43 are a "tech preview" and 3.44 + will be "final," at which point strong backward compatibility + guarantees will apply. Project goals/requirements: - 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 @@ -40,17 +41,15 @@ Non-goals: - Creation of high-level OO wrapper APIs. Clients are free to create them off of the C-style API. -- Virtual tables are unlikely to be supported due to the amount of - glue code needed to fit them into Java. - - Support for mixed-mode operation, where client code accesses SQLite both via the Java-side API and the C API via their own native - code. Such cases would be a minefield of potential mis-interactions - between this project's JNI bindings and mixed-mode client code. + code. In such cases, proxy functionalities (primarily callback + handler wrappers of all sorts) may fail because the C-side use of + the SQLite APIs will bypass those proxies. Hello World ----------------------------------------------------------------------- @@ -122,17 +121,19 @@ 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 a few cases, Java-specific capabilities have been added in +In some very few cases, Java-specific capabilities have been added in new APIs, all of which have "_java" somewhere in their names. Examples include: - `sqlite3_result_java_object()` - `sqlite3_column_java_object()` +- `sqlite3_column_java_casted()` - `sqlite3_value_java_object()` +- `sqlite3_value_java_casted()` which, as one might surmise, collectively enable the passing of arbitrary Java objects from user-defined SQL functions through to the caller. @@ -147,31 +148,27 @@ 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. -Classes for which it is sensible support Java's `AutoCloseable` -interface so can be used with try-with-resources constructs. - Golden Rule #2: _Never_ Throw from Callbacks (Unless...) ------------------------------------------------------------------------ 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 -may cause a `NullPointerException`. The APIs clearly mark function -parameters which should not be null, but does not generally actively -defend itself against such misuse. Some C-style APIs explicitly accept -`null` as a no-op for usability's sake, and some of the JNI APIs -deliberately return an error code, instead of segfaulting, when passed -a `null`. +shouldn't be used. The APIs clearly mark function parameters which +should not be null, but does not actively defend itself against such +misuse. Some C-style APIs explicitly accept `null` as a no-op for +usability's sake, and some of the JNI APIs deliberately return an +error code, instead of segfaulting, when passed a `null`. Client-defined callbacks _must never throw exceptions_ unless _very -explitly documented_ as being throw-safe. Exceptions are generally +explicitly documented_ as being throw-safe. Exceptions are generally reserved for higher-level bindings which are constructed to specifically deal with them and ensure that they do not leak C-level resources. In some cases, callback handlers are permitted to throw, in which cases they get translated to C-level result codes and/or messages. If a callback which is not permitted to throw throws, its @@ -293,18 +290,18 @@ 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: -- `ScalarFunction` implements simple scalar functions using but a +- `SQLFunction.Scalar` implements simple scalar functions using but a single callback. -- `AggregateFunction` implements aggregate functions using two +- `SQLFunction.Aggregate` implements aggregate functions using two callbacks. -- `WindowFunction` implements window functions using four +- `SQLFunction.Window` implements window functions using four callbacks. -Search [`Tester1.java`](/file/ext/jni/src/org/sqlite/jni/capi/Tester1.java) for +Search [`Tester1.java`](/file/ext/jni/src/org/sqlite/jni/Tester1.java) for `SQLFunction` for how it's used. Reminder: see the disclaimer at the top of this document regarding the in-flux nature of this API. Index: ext/jni/src/c/sqlite3-jni.c ================================================================== --- ext/jni/src/c/sqlite3-jni.c +++ ext/jni/src/c/sqlite3-jni.c @@ -13,18 +13,17 @@ ** org.sqlite.jni.capi.CApi (from which sqlite3-jni.h is generated). */ /* ** If you found this comment by searching the code for -** CallStaticObjectMethod because it appears in console output then -** you're probably the victim of an OpenJDK bug: +** 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. It was -** triggered by this code long before it made any use of -** CallStaticObjectMethod(). +** 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. @@ -88,10 +87,16 @@ #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 @@ -105,16 +110,10 @@ #ifdef SQLITE_OMIT_UTF16 /* UTF16 is required for java */ # undef SQLITE_OMIT_UTF16 1 #endif -/**********************************************************************/ -/* SQLITE_S... */ -#ifndef SQLITE_STRICT_SUBTYPE -# define SQLITE_STRICT_SUBTYPE 1 -#endif - /**********************************************************************/ /* SQLITE_T... */ #ifndef SQLITE_TEMP_STORE # define SQLITE_TEMP_STORE 2 #endif @@ -190,12 +189,10 @@ ** 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). -** -** See also: the notes for LongPtrGet_T. */ #define S3JniCast_L2P(JLongAsPtr) (void*)((intptr_t)(JLongAsPtr)) #define S3JniCast_P2L(PTR) (jlong)((intptr_t)(PTR)) /* @@ -208,12 +205,12 @@ ** 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 * env, jobject jSelf -#define JniArgsEnvClass JNIEnv * env, jclass jKlazz +#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) ) @@ -658,25 +655,10 @@ 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 */; - - /* - ByteBuffer may or may not be supported via JNI on any given - platform: - - https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#nio_support - - We only store a ref to byteBuffer.klazz if JNI support for - ByteBuffer is available (which we determine during static init). - */ - struct { - jclass klazz /* global ref to java.nio.ByteBuffer */; - jmethodID midAlloc /* ByteBuffer.allocateDirect() */; - jmethodID midLimit /* ByteBuffer.limit() */; - } byteBuffer; } g; /* ** The list of Java-side auto-extensions ** (org.sqlite.jni.capi.AutoExtensionCallback objects). */ @@ -879,62 +861,10 @@ #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) -/* -** If jbb is-a java.nio.Buffer object and the JNI environment supports -** it, *pBuf is set to the buffer's memory and *pN is set to its -** limit() (as opposed to its capacity()). If jbb is NULL, not a -** Buffer, or the JNI environment does not support that operation, -** *pBuf is set to 0 and *pN is set to 0. -** -** Note that the length of the buffer can be larger than SQLITE_LIMIT -** but this function does not know what byte range of the buffer is -** required so cannot check for that violation. The caller is required -** to ensure that any to-be-bind()ed range fits within SQLITE_LIMIT. -** -** Sidebar: it is unfortunate that we cannot get ByteBuffer.limit() -** via a JNI method like we can for ByteBuffer.capacity(). We instead -** have to call back into Java to get the limit(). Depending on how -** the ByteBuffer is used, the limit and capacity might be the same, -** but when reusing a buffer, the limit may well change whereas the -** capacity is fixed. The problem with, e.g., read()ing blob data to a -** ByteBuffer's memory based on its capacity is that Java-level code -** is restricted to accessing the range specified in -** ByteBuffer.limit(). If we were to honor only the capacity, we -** could end up writing to, or reading from, parts of a ByteBuffer -** which client code itself cannot access without explicitly modifying -** the limit. The penalty we pay for this correctness is that we must -** call into Java to get the limit() of every ByteBuffer we work with. -** -** An alternative to having to call into ByteBuffer.limit() from here -** would be to add private native impls of all ByteBuffer-using -** methods, each of which adds a jint parameter which _must_ be set to -** theBuffer.limit() by public Java APIs which use those private impls -** to do the real work. -*/ -static void s3jni__get_nio_buffer(JNIEnv * const env, jobject jbb, void **pBuf, jint * pN ){ - *pBuf = 0; - *pN = 0; - if( jbb ){ - *pBuf = (*env)->GetDirectBufferAddress(env, jbb); - if( *pBuf ){ - /* - ** Maintenance reminder: do not use - ** (*env)->GetDirectBufferCapacity(env,jbb), even though it - ** would be much faster, for reasons explained in this - ** function's comments. - */ - *pN = (*env)->CallIntMethod(env, jbb, SJG.g.byteBuffer.midLimit); - S3JniExceptionIsFatal("Error calling ByteBuffer.limit() method."); - } - } -} -#define s3jni_get_nio_buffer(JOBJ,vpOut,jpOut) \ - s3jni__get_nio_buffer(env,(JOBJ),(vpOut),(jpOut)) - /* ** Returns the current JNIEnv object. Fails fatally if it cannot find ** the object. */ static JNIEnv * s3jni_env(void){ @@ -1128,51 +1058,10 @@ ? (*env)->NewString(env, (const jchar *)p, (jsize)(nP/2)) : NULL; s3jni_oom_check( p ? !!rv : 1 ); return rv; } - -/* -** Creates a new ByteBuffer instance with a capacity of n. assert()s -** that SJG.g.byteBuffer.klazz is not 0 and n>0. -*/ -static jobject s3jni__new_ByteBuffer(JNIEnv * const env, int n){ - jobject rv = 0; - assert( SJG.g.byteBuffer.klazz ); - assert( SJG.g.byteBuffer.midAlloc ); - assert( n > 0 ); - rv = (*env)->CallStaticObjectMethod(env, SJG.g.byteBuffer.klazz, - SJG.g.byteBuffer.midAlloc, (jint)n); - S3JniIfThrew { - S3JniExceptionReport; - S3JniExceptionClear; - } - s3jni_oom_check( rv ); - return rv; -} - -/* -** If n>0 and sqlite3_jni_supports_nio() is true then this creates a -** new ByteBuffer object and copies n bytes from p to it. Returns NULL -** if n is 0, sqlite3_jni_supports_nio() is false, or on allocation -** error (unless fatal alloc failures are enabled). -*/ -static jobject s3jni__blob_to_ByteBuffer(JNIEnv * const env, - const void * p, int n){ - jobject rv = NULL; - assert( n >= 0 ); - if( 0==n || !SJG.g.byteBuffer.klazz ){ - return NULL; - } - rv = s3jni__new_ByteBuffer(env, n); - if( rv ){ - void * tgt = (*env)->GetDirectBufferAddress(env, rv); - memcpy(tgt, p, (size_t)n); - } - 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 @@ -1581,42 +1470,33 @@ ** ** 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,JOBJ) (T*)NativePointerHolder_get((JOBJ), S3JniNph(T)) -#define PtrGet_sqlite3(JOBJ) PtrGet_T(sqlite3, (JOBJ)) -#define PtrGet_sqlite3_backup(JOBJ) PtrGet_T(sqlite3_backup, (JOBJ)) -#define PtrGet_sqlite3_blob(JOBJ) PtrGet_T(sqlite3_blob, (JOBJ)) -#define PtrGet_sqlite3_context(JOBJ) PtrGet_T(sqlite3_context, (JOBJ)) -#define PtrGet_sqlite3_stmt(JOBJ) PtrGet_T(sqlite3_stmt, (JOBJ)) -#define PtrGet_sqlite3_value(JOBJ) PtrGet_T(sqlite3_value, (JOBJ)) +#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) /* -** LongPtrGet_T(X,Y) expects X to be an unqualified sqlite3 struct +** 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. -** -** 2023-11-09: testing has not revealed any measurable performance -** difference between the approach of passing type T to C compared to -** passing pointer-to-T to C, and adding support for the latter -** everywhere requires sigificantly more code. As of this writing, the -** older/simpler approach is being applied except for (A) where the -** newer approach has already been applied and (B) hot-spot APIs where -** a difference of microseconds (i.e. below our testing measurement -** threshold) might add up. */ -#define LongPtrGet_T(T,JLongAsPtr) (T*)((intptr_t)((JLongAsPtr))) -#define LongPtrGet_sqlite3(JLongAsPtr) LongPtrGet_T(sqlite3,(JLongAsPtr)) -#define LongPtrGet_sqlite3_backup(JLongAsPtr) LongPtrGet_T(sqlite3_backup,(JLongAsPtr)) -#define LongPtrGet_sqlite3_blob(JLongAsPtr) LongPtrGet_T(sqlite3_blob,(JLongAsPtr)) -#define LongPtrGet_sqlite3_stmt(JLongAsPtr) LongPtrGet_T(sqlite3_stmt,(JLongAsPtr)) -#define LongPtrGet_sqlite3_value(JLongAsPtr) LongPtrGet_T(sqlite3_value,(JLongAsPtr)) +#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 @@ -1671,11 +1551,11 @@ ** 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(LongPtrGet_T(sqlite3,sqlite3PtrAsLong)) + S3JniDb_from_c(S3JniLongPtr_T(sqlite3,sqlite3PtrAsLong)) /* ** Unref any Java-side state in (S3JniAutoExtension*) AX and zero out ** AX. */ @@ -1788,13 +1668,12 @@ default: return 0; } } -/* For use with sqlite3_result_pointer(), sqlite3_value_pointer(), - sqlite3_bind_java_object(), and sqlite3_column_java_object(). */ -static const char * const s3jni__value_jref_key = "org.sqlite.jni.capi.ResultJavaVal"; +/* 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. */ @@ -1999,50 +1878,26 @@ return SQLITE_NOMEM; } /* ** Requires that jCx and jArgv are sqlite3_context -** resp. array-of-sqlite3_value values initialized by udf_args(). The -** latter will be 0-and-NULL for UDF types with no arguments. This +** 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 its context or 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, or which Java-wraps a -** sqlite3_context for use with a UDF(ish) call. It MUST NOT be called -** in any other case. +** 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); - /* - ** There is a potential Java-triggerable case of Undefined - ** Behavior here, but it would require intentional misuse of the - ** API: - ** - ** If a Java UDF grabs an sqlite3_value from its argv and then - ** assigns that element to null, it becomes unreachable to us so - ** we cannot clear out its pointer. That Java-side object's - ** getNativePointer() will then refer to a stale value, so passing - ** it into (e.g.) sqlite3_value_SOMETHING() would invoke UB. - ** - ** High-level wrappers can avoid that possibility if they do not - ** expose sqlite3_value directly to clients (as is the case in - ** org.sqlite.jni.wrapper1.SqlFunction). - ** - ** One potential (but expensive) workaround for this would be to - ** privately store a duplicate argv array in each sqlite3_context - ** wrapper object, and clear the native pointers from that copy. - */ - assert(jsv && "Someone illegally modified a UDF argument array."); - if( jsv ){ - NativePointerHolder_set(S3JniNph(sqlite3_value), jsv, 0); - } + assert(jsv); + NativePointerHolder_set(S3JniNph(sqlite3_value), jsv, 0); } } /* @@ -2127,11 +1982,10 @@ (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx); S3JniIfThrew{ rc = udf_report_exception(env, isFinal, cx, s->zFuncName, zFuncType); } - udf_unargs(env, jcx, 0, 0); S3JniUnrefLocal(jcx); }else{ if( isFinal ) sqlite3_result_error_nomem(cx); rc = SQLITE_NOMEM; } @@ -2201,16 +2055,16 @@ 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(LongPtrGet_sqlite3_stmt(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(LongPtrGet_sqlite3_stmt(jpStmt), (int)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; \ @@ -2217,45 +2071,45 @@ } /** 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(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx), \ + 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(LongPtrGet_sqlite3(jpDb)) ? JNI_TRUE : JNI_FALSE; \ + 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(LongPtrGet_sqlite3(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(LongPtrGet_sqlite3(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(LongPtrGet_sqlite3(jpDb), (int)ndx), \ + 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 = LongPtrGet_sqlite3_value(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 = LongPtrGet_sqlite3_value(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) @@ -2264,15 +2118,13 @@ 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) -#ifdef SQLITE_ENABLE_COLUMN_METADATA 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) -#endif 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) @@ -2327,13 +2179,11 @@ : 0)) : 0; return S3JniCast_P2L(p); } -/* -** Central auto-extension runner for auto-extensions created in Java. -*/ +/* 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; @@ -2466,21 +2316,21 @@ S3JniApi(sqlite3_backup_finish(),jint,1backup_1finish)( JniArgsEnvClass, jlong jpBack ){ int rc = 0; if( jpBack!=0 ){ - rc = sqlite3_backup_finish( LongPtrGet_sqlite3_backup(jpBack) ); + 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 = LongPtrGet_sqlite3(jpDbDest); - sqlite3 * const pSrc = LongPtrGet_sqlite3(jpDbSrc); + 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 ){ @@ -2499,23 +2349,23 @@ } S3JniApi(sqlite3_backup_pagecount(),jint,1backup_1pagecount)( JniArgsEnvClass, jlong jpBack ){ - return sqlite3_backup_pagecount(LongPtrGet_sqlite3_backup(jpBack)); + return sqlite3_backup_pagecount(S3JniLongPtr_sqlite3_backup(jpBack)); } S3JniApi(sqlite3_backup_remaining(),jint,1backup_1remaining)( JniArgsEnvClass, jlong jpBack ){ - return sqlite3_backup_remaining(LongPtrGet_sqlite3_backup(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(LongPtrGet_sqlite3_backup(jpBack), (int)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 ){ @@ -2524,159 +2374,53 @@ int rc; if( pBuf ){ if( nMax>nBA ){ nMax = nBA; } - rc = sqlite3_bind_blob(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, + 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( LongPtrGet_sqlite3_stmt(jpStmt), ndx ); + : sqlite3_bind_null( S3JniLongPtr_sqlite3_stmt(jpStmt), ndx ); } return (jint)rc; } -/** - Helper for use with s3jni_setup_nio_args(). -*/ -struct S3JniNioArgs { - jobject jBuf; /* input - ByteBuffer */ - jint iOffset; /* input - byte offset */ - jint iHowMany; /* input - byte count to bind/read/write */ - jint nBuf; /* output - jBuf's buffer size */ - void * p; /* output - jBuf's buffer memory */ - void * pStart; /* output - offset of p to bind/read/write */ - int nOut; /* output - number of bytes from pStart to bind/read/write */ -}; -typedef struct S3JniNioArgs S3JniNioArgs; -static const S3JniNioArgs S3JniNioArgs_empty = { - 0,0,0,0,0,0,0 -}; - -/* -** Internal helper for sqlite3_bind_nio_buffer(), -** sqlite3_result_nio_buffer(), and similar methods which take a -** ByteBuffer object as either input or output. Populates pArgs and -** returns 0 on success, non-0 if the operation should fail. The -** caller is required to check for SJG.g.byteBuffer.klazz!=0 before calling -** this and reporting it in a way appropriate for that routine. This -** function may assert() that SJG.g.byteBuffer.klazz is not 0. -** -** The (jBuffer, iOffset, iHowMany) arguments are the (ByteBuffer, offset, -** length) arguments to the bind/result method. -** -** If iHowMany is negative then it's treated as "until the end" and -** the calculated slice is trimmed to fit if needed. If iHowMany is -** positive and extends past the end of jBuffer then SQLITE_ERROR is -** returned. -** -** Returns 0 if everything looks to be in order, else some SQLITE_... -** result code -*/ -static int s3jni_setup_nio_args( - JNIEnv *env, S3JniNioArgs * pArgs, - jobject jBuffer, jint iOffset, jint iHowMany -){ - jlong iEnd = 0; - const int bAllowTruncate = iHowMany<0; - *pArgs = S3JniNioArgs_empty; - pArgs->jBuf = jBuffer; - pArgs->iOffset = iOffset; - pArgs->iHowMany = iHowMany; - assert( SJG.g.byteBuffer.klazz ); - if( pArgs->iOffset<0 ){ - return SQLITE_ERROR - /* SQLITE_MISUSE or SQLITE_RANGE would fit better but we use - SQLITE_ERROR for consistency with the code documented for a - negative target blob offset in sqlite3_blob_read/write(). */; - } - s3jni_get_nio_buffer(pArgs->jBuf, &pArgs->p, &pArgs->nBuf); - if( !pArgs->p ){ - return SQLITE_MISUSE; - }else if( pArgs->iOffset>=pArgs->nBuf ){ - pArgs->pStart = 0; - pArgs->nOut = 0; - return 0; - } - assert( pArgs->nBuf > 0 ); - assert( pArgs->iOffset < pArgs->nBuf ); - iEnd = pArgs->iHowMany<0 - ? pArgs->nBuf - pArgs->iOffset - : pArgs->iOffset + pArgs->iHowMany; - if( iEnd>(jlong)pArgs->nBuf ){ - if( bAllowTruncate ){ - iEnd = pArgs->nBuf - pArgs->iOffset; - }else{ - return SQLITE_ERROR - /* again: for consistency with blob_read/write(), though - SQLITE_MISUSE or SQLITE_RANGE would be a better fit. */; - } - } - if( iEnd - pArgs->iOffset > (jlong)SQLITE_MAX_LENGTH ){ - return SQLITE_TOOBIG; - } - assert( pArgs->iOffset >= 0 ); - assert( iEnd > pArgs->iOffset ); - pArgs->pStart = pArgs->p + pArgs->iOffset; - pArgs->nOut = (int)(iEnd - pArgs->iOffset); - assert( pArgs->nOut > 0 ); - assert( (pArgs->pStart + pArgs->nOut) <= (pArgs->p + pArgs->nBuf) ); - return 0; -} - -S3JniApi(sqlite3_bind_nio_buffer(),jint,1bind_1nio_1buffer)( - JniArgsEnvClass, jobject jpStmt, jint ndx, jobject jBuffer, - jint iOffset, jint iN -){ - sqlite3_stmt * pStmt = PtrGet_sqlite3_stmt(jpStmt); - S3JniNioArgs args; - int rc; - if( !pStmt || !SJG.g.byteBuffer.klazz ) return SQLITE_MISUSE; - rc = s3jni_setup_nio_args(env, &args, jBuffer, iOffset, iN); - if(rc){ - return rc; - }else if( !args.pStart || !args.nOut ){ - return sqlite3_bind_null(pStmt, ndx); - } - return sqlite3_bind_blob( pStmt, (int)ndx, args.pStart, - args.nOut, SQLITE_TRANSIENT ); -} - S3JniApi(sqlite3_bind_double(),jint,1bind_1double)( JniArgsEnvClass, jlong jpStmt, jint ndx, jdouble val ){ - return (jint)sqlite3_bind_double(LongPtrGet_sqlite3_stmt(jpStmt), + 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(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)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(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)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 = LongPtrGet_sqlite3_stmt(jpStmt); + 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, s3jni__value_jref_key, + rc = sqlite3_bind_pointer(pStmt, ndx, rv, ResultJavaValuePtrStr, S3Jni_jobject_finalizer); }else if(val){ rc = SQLITE_NOMEM; }else{ rc = sqlite3_bind_null(pStmt, ndx); @@ -2686,26 +2430,26 @@ } S3JniApi(sqlite3_bind_null(),jint,1bind_1null)( JniArgsEnvClass, jlong jpStmt, jint ndx ){ - return (jint)sqlite3_bind_null(LongPtrGet_sqlite3_stmt(jpStmt), (int)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(LongPtrGet_sqlite3_stmt(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(LongPtrGet_sqlite3_stmt(jpStmt), + rc = sqlite3_bind_parameter_index(S3JniLongPtr_sqlite3_stmt(jpStmt), (const char *)pBuf); s3jni_jbyteArray_release(jName, pBuf); } return rc; } @@ -2712,11 +2456,11 @@ S3JniApi(sqlite3_bind_parameter_name(),jstring,1bind_1parameter_1name)( JniArgsEnvClass, jlong jpStmt, jint ndx ){ const char *z = - sqlite3_bind_parameter_name(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx); + 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(). @@ -2734,18 +2478,18 @@ /* 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(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, + ? sqlite3_bind_text16(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, pBuf, (int)nMax, SQLITE_TRANSIENT) - : sqlite3_bind_text(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, + : sqlite3_bind_text(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, (const char *)pBuf, (int)nMax, SQLITE_TRANSIENT); }else{ rc = baData - ? sqlite3_bind_null(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx) + ? sqlite3_bind_null(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx) : SQLITE_NOMEM; } s3jni_jbyteArray_release(baData, pBuf); return (jint)rc; @@ -2765,13 +2509,13 @@ S3JniApi(sqlite3_bind_value(),jint,1bind_1value)( JniArgsEnvClass, jlong jpStmt, jint ndx, jlong jpValue ){ int rc = 0; - sqlite3_stmt * pStmt = LongPtrGet_sqlite3_stmt(jpStmt); + sqlite3_stmt * pStmt = S3JniLongPtr_sqlite3_stmt(jpStmt); if( pStmt ){ - sqlite3_value *v = LongPtrGet_sqlite3_value(jpValue); + 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); } @@ -2782,39 +2526,39 @@ } S3JniApi(sqlite3_bind_zeroblob(),jint,1bind_1zeroblob)( JniArgsEnvClass, jlong jpStmt, jint ndx, jint n ){ - return (jint)sqlite3_bind_zeroblob(LongPtrGet_sqlite3_stmt(jpStmt), + 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(LongPtrGet_sqlite3_stmt(jpStmt), + 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(LongPtrGet_sqlite3_blob(jpBlob)); + return sqlite3_blob_bytes(S3JniLongPtr_sqlite3_blob(jpBlob)); } S3JniApi(sqlite3_blob_close(),jint,1blob_1close)( JniArgsEnvClass, jlong jpBlob ){ - sqlite3_blob * const b = LongPtrGet_sqlite3_blob(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 = LongPtrGet_sqlite3(jpDb); + 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; @@ -2844,11 +2588,11 @@ ){ 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(LongPtrGet_sqlite3_blob(jpBlob), pBa, + 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); @@ -2855,45 +2599,21 @@ } } return rc; } -S3JniApi(sqlite3_blob_read_nio_buffer(),jint,1blob_1read_1nio_1buffer)( - JniArgsEnvClass, jlong jpBlob, jint iSrcOff, jobject jBB, jint iTgtOff, jint iHowMany -){ - sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob); - S3JniNioArgs args; - int rc; - if( !b || !SJG.g.byteBuffer.klazz || iHowMany<0 ){ - return SQLITE_MISUSE; - }else if( iTgtOff<0 || iSrcOff<0 ){ - return SQLITE_ERROR - /* for consistency with underlying sqlite3_blob_read() */; - }else if( 0==iHowMany ){ - return 0; - } - rc = s3jni_setup_nio_args(env, &args, jBB, iTgtOff, iHowMany); - if(rc){ - return rc; - }else if( !args.pStart || !args.nOut ){ - return 0; - } - assert( args.iHowMany>0 ); - return sqlite3_blob_read( b, args.pStart, (int)args.nOut, (int)iSrcOff ); -} - S3JniApi(sqlite3_blob_reopen(),jint,1blob_1reopen)( JniArgsEnvClass, jlong jpBlob, jlong iNewRowId ){ - return (jint)sqlite3_blob_reopen(LongPtrGet_sqlite3_blob(jpBlob), + 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 = LongPtrGet_sqlite3_blob(jpBlob); + 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 ); @@ -2900,33 +2620,10 @@ } s3jni_jbyteArray_release(jBa, pBuf); return (jint)rc; } -S3JniApi(sqlite3_blob_write_nio_buffer(),jint,1blob_1write_1nio_1buffer)( - JniArgsEnvClass, jlong jpBlob, jint iTgtOff, jobject jBB, jint iSrcOff, jint iHowMany -){ - sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob); - S3JniNioArgs args; - int rc; - if( !b || !SJG.g.byteBuffer.klazz ){ - return SQLITE_MISUSE; - }else if( iTgtOff<0 || iSrcOff<0 ){ - return SQLITE_ERROR - /* for consistency with underlying sqlite3_blob_write() */; - }else if( 0==iHowMany ){ - return 0; - } - rc = s3jni_setup_nio_args(env, &args, jBB, iSrcOff, iHowMany); - if(rc){ - return rc; - }else if( !args.pStart || !args.nOut ){ - return 0; - } - return sqlite3_blob_write( b, args.pStart, (int)args.nOut, (int)iTgtOff ); -} - /* 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; @@ -3121,11 +2818,11 @@ 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;)V" + 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 " @@ -3171,45 +2868,10 @@ JniArgsEnvClass, jobject jpStmt, jint ndx ){ return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -S3JniApi(sqlite3_column_java_object(),jobject,1column_1java_1object)( - JniArgsEnvClass, jlong jpStmt, jint ndx -){ - sqlite3_stmt * const stmt = LongPtrGet_sqlite3_stmt(jpStmt); - jobject rv = 0; - if( stmt ){ - sqlite3 * const db = sqlite3_db_handle(stmt); - sqlite3_value * sv; - sqlite3_mutex_enter(sqlite3_db_mutex(db)); - sv = sqlite3_column_value(stmt, (int)ndx); - if( sv ){ - rv = S3JniRefLocal( - sqlite3_value_pointer(sv, s3jni__value_jref_key) - ); - } - sqlite3_mutex_leave(sqlite3_db_mutex(db)); - } - return rv; -} - -S3JniApi(sqlite3_column_nio_buffer(),jobject,1column_1nio_1buffer)( - JniArgsEnvClass, jobject jStmt, jint ndx -){ - sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jStmt); - jobject rv = 0; - if( stmt ){ - const void * const p = sqlite3_column_blob(stmt, (int)ndx); - if( p ){ - const int n = sqlite3_column_bytes(stmt, (int)ndx); - rv = s3jni__blob_to_ByteBuffer(env, p, n); - } - } - return rv; -} - 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; @@ -3261,14 +2923,11 @@ 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, - isCommit - ? "Commit hook callback threw" - : "Rollback hook callback threw"); + rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR, "hook callback threw"); } S3JniHook_localundup(hook); } return rc; } @@ -3367,11 +3026,11 @@ 0==sqlite3_compileoption_used(zUtf8) ? JNI_FALSE : JNI_TRUE; s3jni_mutf8_release(name, zUtf8); return rc; } -S3JniApi(sqlite3_complete(),jint,1complete)( +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; @@ -3383,13 +3042,12 @@ : (jSql ? SQLITE_NOMEM : SQLITE_MISUSE); s3jni_jbyteArray_release(jSql, pBuf); return rc; } -S3JniApi(sqlite3_config() /*for a small subset of options.*/ - sqlite3_config__enable()/* internal name to avoid name-mangling issues*/, - jint,1config_1_1enable)(JniArgsEnvClass, jint n){ +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 ); @@ -3415,13 +3073,12 @@ S3JniHook_localundup(hook); S3JniUnrefLocal(jArg1); } } -S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_LOG */ - sqlite3_config__config_log() /* internal name */, - jint, 1config_1_1CONFIG_1LOG +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; @@ -3491,14 +3148,13 @@ void sqlite3_init_sqllog(void){ sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, 0 ); } #endif -S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_SQLLOG */ - sqlite3_config__SQLLOG() /*internal name*/, - jint, 1config_1_1SQLLOG -)(JniArgsEnvClass, jobject jLog){ +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; @@ -3754,10 +3410,11 @@ if( 0==rc && jOut ){ OutputPointer_set_Int32(env, jOut, pOut); } break; } + case 0: default: rc = SQLITE_MISUSE; } return (jint)rc; } @@ -3816,11 +3473,11 @@ rc = sqlite3_db_readonly(ps ? ps->pDb : 0, zDbName); sqlite3_free(zDbName); return (jint)rc; } -S3JniApi(sqlite3_db_release_memory(),jint,1db_1release_1memory)( +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; } @@ -3857,20 +3514,13 @@ } S3JniApi(sqlite3_errstr(),jstring,1errstr)( JniArgsEnvClass, jint rcCode ){ - jstring rv; - const char * z = sqlite3_errstr((int)rcCode); - if( !z ){ - /* This hypothetically cannot happen, but we'll behave like the - low-level library would in such a case... */ - z = "unknown error"; - } - rv = (*env)->NewStringUTF(env, z) - /* We know these values to be plain ASCII, so pose no MUTF-8 - ** incompatibility */; + 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 @@ -3918,25 +3568,23 @@ #else return 0; #endif } -S3JniApi(sqlite3_extended_result_codes(),jint,1extended_1result_1codes)( +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) - : SQLITE_MISUSE; - return rc; + 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(LongPtrGet_sqlite3_stmt(jpStmt)) + ? sqlite3_finalize(S3JniLongPtr_sqlite3_stmt(jpStmt)) : 0; } S3JniApi(sqlite3_get_auxdata(),jobject,1get_1auxdata)( JniArgsEnvClass, jobject jCx, jint n @@ -3973,42 +3621,17 @@ /* ** Uncaches the current JNIEnv from the S3JniGlobal state, clearing ** any resources owned by that cache entry and making that slot ** available for re-use. */ -S3JniApi(sqlite3_java_uncache_thread(), jboolean, 1java_1uncache_1thread)( - JniArgsEnvClass -){ +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_jni_db_error(), jint, 1jni_1db_1error)( - JniArgsEnvClass, jobject jDb, jint jRc, jstring jStr -){ - S3JniDb * const ps = S3JniDb_from_java(jDb); - int rc = SQLITE_MISUSE; - if( ps ){ - char *zStr; - zStr = jStr - ? s3jni_jstring_to_utf8( jStr, 0) - : NULL; - rc = s3jni_db_error( ps->pDb, (int)jRc, zStr ); - sqlite3_free(zStr); - } - return rc; -} - -S3JniApi(sqlite3_jni_supports_nio(), jboolean,1jni_1supports_1nio)( - JniArgsEnvClass -){ - return SJG.g.byteBuffer.klazz ? JNI_TRUE : JNI_FALSE; -} - S3JniApi(sqlite3_keyword_check(),jboolean,1keyword_1check)( JniArgsEnvClass, jstring jWord ){ int nWord = 0; @@ -4179,19 +3802,18 @@ sqlite3_free(zVfs); return (jint)rc; } /* Proxy for the sqlite3_prepare[_v2/3]() family. */ -static jint sqlite3_jni_prepare_v123( int prepVersion, JNIEnv * const env, - jclass self, - jlong jpDb, jbyteArray baSql, - jint nMax, jint prepFlags, - jobject jOutStmt, jobject outTail){ +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 = LongPtrGet_sqlite3(jpDb); + 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 ){ @@ -4342,15 +3964,15 @@ 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(),jint,1preupdate_1blobwrite)( +S3JniApi(sqlite3_preupdate_blobwrite(),int,1preupdate_1blobwrite)( JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; } -S3JniApi(sqlite3_preupdate_count(),jint,1preupdate_1count)( +S3JniApi(sqlite3_preupdate_count(),int,1preupdate_1count)( JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; } -S3JniApi(sqlite3_preupdate_depth(),jint,1preupdate_1depth)( +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 @@ -4441,11 +4063,11 @@ /* 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 = LongPtrGet_sqlite3(jpDb); + 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; @@ -4662,11 +4284,11 @@ ){ sqlite3_result_double(PtrGet_sqlite3_context(jpCx), v); } S3JniApi(sqlite3_result_error(),void,1result_1error)( - JniArgsEnvClass, jobject jpCx, jbyteArray baMsg, jint eTextRep + 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 ){ @@ -4727,65 +4349,24 @@ if( !pCx ) return; else if( v ){ jobject const rjv = S3JniRefGlobal(v); if( rjv ){ sqlite3_result_pointer(pCx, rjv, - s3jni__value_jref_key, S3Jni_jobject_finalizer); + ResultJavaValuePtrStr, S3Jni_jobject_finalizer); }else{ sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx)); } }else{ sqlite3_result_null(PtrGet_sqlite3_context(jpCx)); } } - -S3JniApi(sqlite3_result_nio_buffer(),void,1result_1nio_1buffer)( - JniArgsEnvClass, jobject jpCtx, jobject jBuffer, - jint iOffset, jint iN -){ - sqlite3_context * pCx = PtrGet_sqlite3_context(jpCtx); - int rc; - S3JniNioArgs args; - if( !pCx ){ - return; - }else if( !SJG.g.byteBuffer.klazz ){ - sqlite3_result_error( - pCx, "This JVM does not support JNI access to ByteBuffers.", -1 - ); - return; - } - rc = s3jni_setup_nio_args(env, &args, jBuffer, iOffset, iN); - if(rc){ - if( iOffset<0 ){ - sqlite3_result_error(pCx, "Start index may not be negative.", -1); - }else if( SQLITE_TOOBIG==rc ){ - sqlite3_result_error_toobig(pCx); - }else{ - sqlite3_result_error( - pCx, "Invalid arguments to sqlite3_result_nio_buffer().", -1 - ); - } - }else if( !args.pStart || !args.nOut ){ - sqlite3_result_null(pCx); - }else{ - sqlite3_result_blob(pCx, args.pStart, args.nOut, SQLITE_TRANSIENT); - } -} - S3JniApi(sqlite3_result_null(),void,1result_1null)( JniArgsEnvClass, jobject jpCx ){ sqlite3_result_null(PtrGet_sqlite3_context(jpCx)); } - -S3JniApi(sqlite3_result_subtype(),void,1result_1subtype)( - JniArgsEnvClass, jobject jpCx, jint v -){ - sqlite3_result_subtype(PtrGet_sqlite3_context(jpCx), (unsigned int)v); -} - S3JniApi(sqlite3_result_text(),void,1result_1text)( JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jint nMax ){ return result_blob_text(0, SQLITE_UTF8, env, @@ -4956,12 +4537,24 @@ S3JniEnv_mutex_enter; { while( SJG.envCache.aHead ){ S3JniEnv_uncache( SJG.envCache.aHead->env ); } } S3JniEnv_mutex_leave; - /* Do not clear S3JniGlobal.jvm or S3JniGlobal.g: it's legal to - ** restart the lib. */ +#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, @@ -4999,11 +4592,11 @@ 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 = s3jni_jbyteArray_bytes(baT); + 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, @@ -5038,17 +4631,17 @@ } return rv; } S3JniApi(sqlite3_step(),jint,1step)( - JniArgsEnvClass, jlong jpStmt + JniArgsEnvClass,jobject jStmt ){ - sqlite3_stmt * const pStmt = LongPtrGet_sqlite3_stmt(jpStmt); + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt); return pStmt ? (jint)sqlite3_step(pStmt) : (jint)SQLITE_MISUSE; } -S3JniApi(sqlite3_table_column_metadata(),jint,1table_1column_1metadata)( +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); @@ -5220,47 +4813,47 @@ S3JniApi(sqlite3_value_blob(),jbyteArray,1value_1blob)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = LongPtrGet_sqlite3_value(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(),jint,1value_1bytes)( +S3JniApi(sqlite3_value_bytes(),int,1value_1bytes)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); + sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); return sv ? sqlite3_value_bytes(sv) : 0; } -S3JniApi(sqlite3_value_bytes16(),jint,1value_1bytes16)( +S3JniApi(sqlite3_value_bytes16(),int,1value_1bytes16)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = LongPtrGet_sqlite3_value(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 = LongPtrGet_sqlite3_value(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 = LongPtrGet_sqlite3_value(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); @@ -5269,58 +4862,43 @@ } S3JniApi(sqlite3_value_free(),void,1value_1free)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = LongPtrGet_sqlite3_value(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 = LongPtrGet_sqlite3_value(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 = LongPtrGet_sqlite3_value(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 = LongPtrGet_sqlite3_value(jpSVal); + sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); return sv - ? sqlite3_value_pointer(sv, s3jni__value_jref_key) + ? sqlite3_value_pointer(sv, ResultJavaValuePtrStr) : 0; } -S3JniApi(sqlite3_value_nio_buffer(),jobject,1value_1nio_1buffer)( - JniArgsEnvClass, jobject jVal -){ - sqlite3_value * const sv = PtrGet_sqlite3_value(jVal); - jobject rv = 0; - if( sv ){ - const void * const p = sqlite3_value_blob(sv); - if( p ){ - const int n = sqlite3_value_bytes(sv); - rv = s3jni__blob_to_ByteBuffer(env, p, n); - } - } - return rv; -} - S3JniApi(sqlite3_value_text(),jbyteArray,1value_1text)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = LongPtrGet_sqlite3_value(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; } @@ -5327,21 +4905,21 @@ #if 0 // this impl might prove useful. S3JniApi(sqlite3_value_text(),jstring,1value_1text)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = LongPtrGet_sqlite3_value(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 = LongPtrGet_sqlite3_value(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; } @@ -5919,11 +5497,11 @@ JniDeclFtsXA(jlong,xRowid)(JniArgsEnvObj,jobject jCtx){ Fts5ExtDecl; return (jlong)ext->xRowid(PtrGet_Fts5Context(jCtx)); } -JniDeclFtsXA(jint,xSetAuxdata)(JniArgsEnvObj,jobject jCtx, jobject jAux){ +JniDeclFtsXA(int,xSetAuxdata)(JniArgsEnvObj,jobject jCtx, jobject jAux){ Fts5ExtDecl; int rc; S3JniFts5AuxData * pAux; pAux = s3jni_malloc( sizeof(*pAux)); @@ -6313,31 +5891,9 @@ #if S3JNI_METRICS_MUTEX SJG.metrics.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); s3jni_oom_fatal( SJG.metrics.mutex ); #endif - { - /* Test whether this JVM supports direct memory access via - ByteBuffer. */ - unsigned char buf[16] = {0}; - jobject bb = (*env)->NewDirectByteBuffer(env, buf, 16); - if( bb ){ - SJG.g.byteBuffer.klazz = S3JniRefGlobal((*env)->GetObjectClass(env, bb)); - SJG.g.byteBuffer.midAlloc = (*env)->GetStaticMethodID( - env, SJG.g.byteBuffer.klazz, "allocateDirect", "(I)Ljava/nio/ByteBuffer;" - ); - S3JniExceptionIsFatal("Error getting ByteBuffer.allocateDirect() method."); - SJG.g.byteBuffer.midLimit = (*env)->GetMethodID( - env, SJG.g.byteBuffer.klazz, "limit", "()I" - ); - S3JniExceptionIsFatal("Error getting ByteBuffer.limit() method."); - S3JniUnrefLocal(bb); - }else{ - SJG.g.byteBuffer.klazz = 0; - SJG.g.byteBuffer.midAlloc = 0; - } - } - sqlite3_shutdown() /* So that it becomes legal for Java-level code to call ** sqlite3_config(). */; } Index: ext/jni/src/c/sqlite3-jni.h ================================================================== --- ext/jni/src/c/sqlite3-jni.h +++ ext/jni/src/c/sqlite3-jni.h @@ -425,10 +425,12 @@ #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 @@ -703,16 +705,12 @@ #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_SUBTYPE -#define org_sqlite_jni_capi_CApi_SQLITE_SUBTYPE 1048576L #undef org_sqlite_jni_capi_CApi_SQLITE_INNOCUOUS #define org_sqlite_jni_capi_CApi_SQLITE_INNOCUOUS 2097152L -#undef org_sqlite_jni_capi_CApi_SQLITE_RESULT_SUBTYPE -#define org_sqlite_jni_capi_CApi_SQLITE_RESULT_SUBTYPE 16777216L #undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_SCAN_UNIQUE #define org_sqlite_jni_capi_CApi_SQLITE_INDEX_SCAN_UNIQUE 1L #undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_EQ #define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_EQ 2L #undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GT @@ -775,26 +773,10 @@ * 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_jni_supports_nio - * Signature: ()Z - */ -JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1supports_1nio - (JNIEnv *, jclass); - -/* - * Class: org_sqlite_jni_capi_CApi - * Method: sqlite3_jni_db_error - * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)I - */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1db_1error - (JNIEnv *, jclass, jobject, jint, jstring); - /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_aggregate_context * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Z)J */ @@ -887,18 +869,10 @@ * 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_nio_buffer - * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;ILjava/nio/ByteBuffer;II)I - */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1nio_1buffer - (JNIEnv *, jclass, jobject, jint, jobject, jint, jint); - /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_bind_null * Signature: (JI)I */ @@ -999,18 +973,10 @@ * 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_read_nio_buffer - * Signature: (JILjava/nio/ByteBuffer;II)I - */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1read_1nio_1buffer - (JNIEnv *, jclass, jlong, jint, jobject, jint, jint); - /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_blob_reopen * Signature: (JJ)I */ @@ -1023,18 +989,10 @@ * 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_blob_write_nio_buffer - * Signature: (JILjava/nio/ByteBuffer;II)I - */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1write_1nio_1buffer - (JNIEnv *, jclass, jlong, jint, jobject, jint, jint); - /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_busy_handler * Signature: (JLorg/sqlite/jni/capi/BusyHandlerCallback;)I */ @@ -1127,18 +1085,10 @@ * 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_database_name - * Signature: (JI)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1database_1name - (JNIEnv *, jclass, jlong, jint); - /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_column_decltype * Signature: (JI)Ljava/lang/String; */ @@ -1167,18 +1117,10 @@ * 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_java_object - * Signature: (JI)Ljava/lang/Object; - */ -JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1java_1object - (JNIEnv *, jclass, jlong, jint); - /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_column_name * Signature: (JI)Ljava/lang/String; */ @@ -1185,15 +1127,15 @@ 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_nio_buffer - * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)Ljava/nio/ByteBuffer; + * Method: sqlite3_column_database_name + * Signature: (JI)Ljava/lang/String; */ -JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1nio_1buffer - (JNIEnv *, jclass, jobject, jint); +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1database_1name + (JNIEnv *, jclass, jlong, jint); /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_column_origin_name * Signature: (JI)Ljava/lang/String; @@ -1281,30 +1223,30 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1complete (JNIEnv *, jclass, jbyteArray); /* * Class: org_sqlite_jni_capi_CApi - * Method: sqlite3_config__enable + * Method: sqlite3_config * Signature: (I)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config_1_1enable +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__I (JNIEnv *, jclass, jint); /* * Class: org_sqlite_jni_capi_CApi - * Method: sqlite3_config__CONFIG_LOG + * 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_1_1CONFIG_1LOG - (JNIEnv *, jclass, jobject); - -/* - * Class: org_sqlite_jni_capi_CApi - * Method: sqlite3_config__SQLLOG - * Signature: (Lorg/sqlite/jni/capi/ConfigSqlLogCallback;)I - */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config_1_1SQLLOG +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__Lorg_sqlite_jni_capi_ConfigLogCallback_2 (JNIEnv *, jclass, jobject); /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_context_db_handle @@ -1450,13 +1392,13 @@ (JNIEnv *, jclass, jlong); /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_extended_result_codes - * Signature: (Lorg/sqlite/jni/capi/sqlite3;Z)I + * Signature: (Lorg/sqlite/jni/capi/sqlite3;Z)Z */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1result_1codes +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1result_1codes (JNIEnv *, jclass, jobject, jboolean); /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_get_autocommit @@ -1735,10 +1677,18 @@ * 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 */ @@ -1759,34 +1709,10 @@ * 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_nio_buffer - * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Ljava/nio/ByteBuffer;II)V - */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1nio_1buffer - (JNIEnv *, jclass, jobject, jobject, jint, jint); - -/* - * Class: org_sqlite_jni_capi_CApi - * Method: sqlite3_result_null - * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V - */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1null - (JNIEnv *, jclass, jobject); - -/* - * Class: org_sqlite_jni_capi_CApi - * Method: sqlite3_result_subtype - * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V - */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1subtype - (JNIEnv *, jclass, jobject, jint); - /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_result_value * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Lorg/sqlite/jni/capi/sqlite3_value;)V */ @@ -1922,14 +1848,14 @@ (JNIEnv *, jclass, jint, jobject, jobject, jboolean); /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_step - * Signature: (J)I + * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1step - (JNIEnv *, jclass, jlong); + (JNIEnv *, jclass, jobject); /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_stmt_busy * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Z @@ -2135,18 +2061,10 @@ * 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_nio_buffer - * Signature: (Lorg/sqlite/jni/capi/sqlite3_value;)Ljava/nio/ByteBuffer; - */ -JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1nio_1buffer - (JNIEnv *, jclass, jobject); - /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_value_nochange * Signature: (J)I */ DELETED ext/jni/src/org/sqlite/jni/annotation/Experimental.java Index: ext/jni/src/org/sqlite/jni/annotation/Experimental.java ================================================================== --- ext/jni/src/org/sqlite/jni/annotation/Experimental.java +++ /dev/null @@ -1,30 +0,0 @@ -/* -** 2023-09-27 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** This file houses the Experimental annotation for the sqlite3 C API. -*/ -package org.sqlite.jni.annotation; -import java.lang.annotation.*; - -/** - This annotation is for flagging methods, constructors, and types - which are expressly experimental and subject to any amount of - change or outright removal. Client code should not rely on such - features. -*/ -@Documented -@Retention(RetentionPolicy.SOURCE) -@Target({ - ElementType.METHOD, - ElementType.CONSTRUCTOR, - ElementType.TYPE -}) -public @interface Experimental{} Index: ext/jni/src/org/sqlite/jni/annotation/NotNull.java ================================================================== --- ext/jni/src/org/sqlite/jni/annotation/NotNull.java +++ ext/jni/src/org/sqlite/jni/annotation/NotNull.java @@ -7,50 +7,38 @@ ** 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 annotation for the sqlite3 C API. +** This file houses the NotNull annotaion for the sqlite3 C API. */ package org.sqlite.jni.annotation; -import java.lang.annotation.*; /** This annotation is for flagging parameters which may not legally be null or point to closed/finalized C-side resources.

    In the case of Java types which map directly to C struct types - (e.g. {@link org.sqlite.jni.capi.sqlite3}, {@link - org.sqlite.jni.capi.sqlite3_stmt}, and {@link - org.sqlite.jni.capi.sqlite3_context}), a closed/finalized resource - is also considered to be null for purposes this annotation because - the C-side effect of passing such a handle is the same as if null - is passed.

    + (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 (see below).

    + 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 (see below). Such values are treated as C pointers in the - JNI layer.

    - -

    Undefined behaviour: the JNI build uses the {@code - SQLITE_ENABLE_API_ARMOR} build flag, meaning that the C code - invoked with invalid NULL pointers and the like will not invoke - undefined behavior in the conventional C sense, but may, for - example, return result codes which are not documented for the - affected APIs or may otherwise behave unpredictably. In no known - cases will such arguments result in C-level code dereferencing a - NULL pointer or accessing out-of-bounds (or otherwise invalid) - memory. In other words, they may cause unexpected behavior but - should never cause an outright crash or security issue.

    + 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.

    @@ -58,14 +46,14 @@

    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.jni package and subpackages, but is made public so that + org.sqlite package and subpackages, but is made public so that javadoc will link to it from the annotated functions. It is not part of the public API and client-level code must not rely on it.

    */ -@Documented -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.PARAMETER) +@java.lang.annotation.Documented +@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) +@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER) public @interface NotNull{} Index: ext/jni/src/org/sqlite/jni/annotation/Nullable.java ================================================================== --- ext/jni/src/org/sqlite/jni/annotation/Nullable.java +++ ext/jni/src/org/sqlite/jni/annotation/Nullable.java @@ -7,14 +7,13 @@ ** 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 annotation for the sqlite3 C API. +** This file houses the Nullable annotaion for the sqlite3 C API. */ package org.sqlite.jni.annotation; -import java.lang.annotation.*; /** This annotation is for flagging parameters which may legally be 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 @@ -25,9 +24,9 @@

    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. */ -@Documented -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.PARAMETER) +@java.lang.annotation.Documented +@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) +@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER) public @interface Nullable{} Index: ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java ================================================================== --- ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java +++ ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java @@ -40,79 +40,13 @@ Optionally override to be notified when the UDF is finalized by SQLite. */ public void xDestroy() {} - /** - 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; - } - } - /** Per-invocation state for the UDF. */ - private final PerContextState map = new PerContextState<>(); + 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 Index: ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java ================================================================== --- ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java +++ ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java @@ -18,12 +18,11 @@ 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. If it throws, the error is - converted to a db-level error and the exception is suppressed. + sqlite3_set_authorizer() callback. */ int call(int opId, @Nullable String s1, @Nullable String s2, @Nullable String s3, @Nullable String s4); } Index: ext/jni/src/org/sqlite/jni/capi/CApi.java ================================================================== --- ext/jni/src/org/sqlite/jni/capi/CApi.java +++ ext/jni/src/org/sqlite/jni/capi/CApi.java @@ -7,11 +7,11 @@ ** 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 the main JNI bindings for the sqlite3 C API. +** 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; @@ -29,10 +29,19 @@

    {@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: @@ -67,11 +76,11 @@ 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 examples. + for "\0" for many examples.

    Further reading: @@ -117,42 +126,14 @@ 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. Its return type and semantics are not considered - stable and may change at any time. + which client-level code can use to make any informed decisions. */ public static native boolean sqlite3_java_uncache_thread(); - /** - Returns true if this JVM has JNI-level support for C-level direct - memory access using java.nio.ByteBuffer, else returns false. - */ - @Experimental - public static native boolean sqlite3_jni_supports_nio(); - - /** - For internal use only. Sets the given db's error code and - (optionally) string. If rc is 0, it defaults to SQLITE_ERROR. - - On success it returns rc. On error it may return a more serious - code, such as SQLITE_NOMEM. Returns SQLITE_MISUSE if db is null. - */ - static native int sqlite3_jni_db_error(@NotNull sqlite3 db, - int rc, @Nullable String msg); - - /** - Convenience overload which uses e.toString() as the error - message. - */ - static int sqlite3_jni_db_error(@NotNull sqlite3 db, - int rc, @NotNull Exception e){ - return sqlite3_jni_db_error(db, rc, e.toString()); - } - ////////////////////////////////////////////////////////////////////// // Maintenance reminder: please keep the sqlite3_.... functions // alphabetized. The SQLITE_... values. on the other hand, are // grouped by category. @@ -190,17 +171,17 @@

    See the AutoExtension class docs for more information. */ public static native int sqlite3_auto_extension(@NotNull AutoExtensionCallback callback); - private static native int sqlite3_backup_finish(@NotNull long ptrToBackup); + static native int sqlite3_backup_finish(@NotNull long ptrToBackup); public static int sqlite3_backup_finish(@NotNull sqlite3_backup b){ - return null==b ? 0 : sqlite3_backup_finish(b.clearNativePointer()); + return sqlite3_backup_finish(b.clearNativePointer()); } - private static native sqlite3_backup sqlite3_backup_init( + 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( @@ -209,37 +190,37 @@ ){ return sqlite3_backup_init( dbDest.getNativePointer(), destTableName, dbSrc.getNativePointer(), srcTableName ); } - private static native int sqlite3_backup_pagecount(@NotNull long ptrToBackup); + 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()); } - private static native int sqlite3_backup_remaining(@NotNull long ptrToBackup); + 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()); } - private static native int sqlite3_backup_step(@NotNull long ptrToBackup, int nPage); + 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); } - private static native int sqlite3_bind_blob( + 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. */ - public static int sqlite3_bind_blob( + 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); } @@ -249,121 +230,42 @@ return (null==data) ? sqlite3_bind_null(stmt.getNativePointer(), ndx) : sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, data.length); } - /** - Convenience overload which is a simple proxy for - sqlite3_bind_nio_buffer(). - */ - @Experimental - /*public*/ static int sqlite3_bind_blob( - @NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data, - int begin, int n - ){ - return sqlite3_bind_nio_buffer(stmt, ndx, data, begin, n); - } - - /** - Convenience overload which is equivalant to passing its arguments - to sqlite3_bind_nio_buffer() with the values 0 and -1 for the - final two arguments. - */ - @Experimental - /*public*/ static int sqlite3_bind_blob( - @NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data - ){ - return sqlite3_bind_nio_buffer(stmt, ndx, data, 0, -1); - } - - private static native int sqlite3_bind_double( + 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); } - private static native int sqlite3_bind_int( + 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); } - private static native int sqlite3_bind_int64( + 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 ); } - private static native int sqlite3_bind_java_object( + static native int sqlite3_bind_java_object( @NotNull long ptrToStmt, int ndx, @Nullable Object o ); - /** - Binds the contents of the given buffer object as a blob. - - The byte range of the buffer may be restricted by providing a - start index and a number of bytes. beginPos may not be negative. - Negative howMany is interpretated as the remainder of the buffer - past the given start position, up to the buffer's limit() (as - opposed its capacity()). - - If beginPos+howMany would extend past the limit() of the buffer - then SQLITE_ERROR is returned. - - If any of the following are true, this function behaves like - sqlite3_bind_null(): the buffer is null, beginPos is at or past - the end of the buffer, howMany is 0, or the calculated slice of - the blob has a length of 0. - - If ndx is out of range, it returns SQLITE_RANGE, as documented - for sqlite3_bind_blob(). If beginPos is negative or if - sqlite3_jni_supports_nio() returns false then SQLITE_MISUSE is - returned. Note that this function is bound (as it were) by the - SQLITE_LIMIT_LENGTH constraint and SQLITE_TOOBIG is returned if - the resulting slice of the buffer exceeds that limit. - - This function does not modify the buffer's streaming-related - cursors. - - If the buffer is modified in a separate thread while this - operation is running, results are undefined and will likely - result in corruption of the bound data or a segmentation fault. - - Design note: this function should arguably take a java.nio.Buffer - instead of ByteBuffer, but it can only operate on "direct" - buffers and the only such class offered by Java is (apparently) - ByteBuffer. - - @see https://docs.oracle.com/javase/8/docs/api/java/nio/Buffer.html - */ - @Experimental - /*public*/ static native int sqlite3_bind_nio_buffer( - @NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data, - int beginPos, int howMany - ); - - /** - Convenience overload which binds the given buffer's entire - contents, up to its limit() (as opposed to its capacity()). - */ - @Experimental - /*public*/ static int sqlite3_bind_nio_buffer( - @NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data - ){ - return sqlite3_bind_nio_buffer(stmt, ndx, data, 0, -1); - } - /** Binds the given object at the given index. If o is null then this behaves like sqlite3_bind_null(). @see #sqlite3_result_java_object @@ -372,17 +274,17 @@ @NotNull sqlite3_stmt stmt, int ndx, @Nullable Object o ){ return sqlite3_bind_java_object(stmt.getNativePointer(), ndx, o); } - private static native int sqlite3_bind_null(@NotNull long ptrToStmt, int ndx); + 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); } - private static native int sqlite3_bind_parameter_count(@NotNull long ptrToStmt); + 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()); } @@ -405,19 +307,19 @@ ){ final byte[] utf8 = nulTerminateUtf8(paramName); return null==utf8 ? 0 : sqlite3_bind_parameter_index(stmt.getNativePointer(), utf8); } - private static native String sqlite3_bind_parameter_name( + 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); } - private static native int sqlite3_bind_text( + 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 @@ -457,11 +359,11 @@ return ( null==utf8 ) ? sqlite3_bind_null(stmt.getNativePointer(), ndx) : sqlite3_bind_text(stmt.getNativePointer(), ndx, utf8, utf8.length); } - private static native int sqlite3_bind_text16( + 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 @@ -498,11 +400,11 @@ return (null == data) ? sqlite3_bind_null(stmt.getNativePointer(), ndx) : sqlite3_bind_text16(stmt.getNativePointer(), ndx, data, data.length); } - private static native int sqlite3_bind_value(@NotNull long ptrToStmt, int ndx, long ptrToValue); + 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. */ @@ -509,37 +411,37 @@ 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()); } - private static native int sqlite3_bind_zeroblob(@NotNull long ptrToStmt, int ndx, int n); + 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); } - private static native int sqlite3_bind_zeroblob64( + 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); } - private static native int sqlite3_blob_bytes(@NotNull long ptrToBlob); + 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()); } - private static native int sqlite3_blob_close(@Nullable long ptrToBlob); + static native int sqlite3_blob_close(@Nullable long ptrToBlob); public static int sqlite3_blob_close(@Nullable sqlite3_blob blob){ - return null==blob ? 0 : sqlite3_blob_close(blob.clearNativePointer()); + return sqlite3_blob_close(blob.clearNativePointer()); } - private static native int sqlite3_blob_open( + 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 ); @@ -563,225 +465,39 @@ sqlite3_blob_open(db.getNativePointer(), dbName, tableName, columnName, iRow, flags, out); return out.take(); }; - private static native int sqlite3_blob_read( - @NotNull long ptrToBlob, @NotNull byte[] target, int srcOffset - ); - - /** - As per C's sqlite3_blob_read(), but writes its output to the - given byte array. Note that the final argument is the offset of - the source buffer, not the target array. - */ - public static int sqlite3_blob_read( - @NotNull sqlite3_blob src, @NotNull byte[] target, int srcOffset - ){ - return sqlite3_blob_read(src.getNativePointer(), target, srcOffset); - } - - /** - An internal level of indirection. - */ - @Experimental - private static native int sqlite3_blob_read_nio_buffer( - @NotNull long ptrToBlob, int srcOffset, - @NotNull java.nio.ByteBuffer tgt, int tgtOffset, int howMany - ); - - /** - Reads howMany bytes from offset srcOffset of src into position - tgtOffset of tgt. - - Returns SQLITE_MISUSE if src is null, tgt is null, or - sqlite3_jni_supports_nio() returns false. Returns SQLITE_ERROR if - howMany or either offset are negative. If argument validation - succeeds, it returns the result of the underlying call to - sqlite3_blob_read() (0 on success). - */ - @Experimental - /*public*/ static int sqlite3_blob_read_nio_buffer( - @NotNull sqlite3_blob src, int srcOffset, - @NotNull java.nio.ByteBuffer tgt, int tgtOffset, int howMany - ){ - return (JNI_SUPPORTS_NIO && src!=null && tgt!=null) - ? sqlite3_blob_read_nio_buffer( - src.getNativePointer(), srcOffset, tgt, tgtOffset, howMany - ) - : SQLITE_MISUSE; - } - - /** - Convenience overload which reads howMany bytes from position - srcOffset of src and returns the result as a new ByteBuffer. - - srcOffset may not be negative. If howMany is negative, it is - treated as all bytes following srcOffset. - - Returns null if sqlite3_jni_supports_nio(), any arguments are - invalid, if the number of bytes to read is 0 or is larger than - the src blob, or the underlying call to sqlite3_blob_read() fails - for any reason. - */ - @Experimental - /*public*/ static java.nio.ByteBuffer sqlite3_blob_read_nio_buffer( - @NotNull sqlite3_blob src, int srcOffset, int howMany - ){ - if( !JNI_SUPPORTS_NIO || src==null ) return null; - else if( srcOffset<0 ) return null; - final int nB = sqlite3_blob_bytes(src); - if( srcOffset>=nB ) return null; - else if( howMany<0 ) howMany = nB - srcOffset; - if( srcOffset + howMany > nB ) return null; - final java.nio.ByteBuffer tgt = - java.nio.ByteBuffer.allocateDirect(howMany); - final int rc = sqlite3_blob_read_nio_buffer( - src.getNativePointer(), srcOffset, tgt, 0, howMany - ); - return 0==rc ? tgt : null; - } - - /** - Overload alias for sqlite3_blob_read_nio_buffer(). - */ - @Experimental - /*public*/ static int sqlite3_blob_read( - @NotNull sqlite3_blob src, int srcOffset, - @NotNull java.nio.ByteBuffer tgt, - int tgtOffset, int howMany - ){ - return sqlite3_blob_read_nio_buffer( - src, srcOffset, tgt, tgtOffset, howMany - ); - } - - /** - Convenience overload which uses 0 for both src and tgt offsets - and reads a number of bytes equal to the smaller of - sqlite3_blob_bytes(src) and tgt.limit(). - - On success it sets tgt.limit() to the number of bytes read. On - error, tgt.limit() is not modified. - - Returns 0 on success. Returns SQLITE_MISUSE is either argument is - null or sqlite3_jni_supports_nio() returns false. Else it returns - the result of the underlying call to sqlite3_blob_read(). - */ - @Experimental - /*public*/ static int sqlite3_blob_read( - @NotNull sqlite3_blob src, - @NotNull java.nio.ByteBuffer tgt - ){ - if(!JNI_SUPPORTS_NIO || src==null || tgt==null) return SQLITE_MISUSE; - final int nSrc = sqlite3_blob_bytes(src); - final int nTgt = tgt.limit(); - final int nRead = nTgt T sqlite3_column_java_object( - @NotNull sqlite3_stmt stmt, int ndx, @NotNull Class type - ){ - final Object o = sqlite3_column_java_object(stmt, ndx); - return type.isInstance(o) ? (T)o : null; - } - - private static native String sqlite3_column_name(@NotNull long ptrToStmt, 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); } - /** - A variant of sqlite3_column_blob() which returns the blob as a - ByteBuffer object. Returns null if its argument is null, if - sqlite3_jni_supports_nio() is false, or if sqlite3_column_blob() - would return null for the same inputs. - */ - @Experimental - /*public*/ static native java.nio.ByteBuffer sqlite3_column_nio_buffer( - @NotNull sqlite3_stmt stmt, int ndx - ); - - private static native String sqlite3_column_origin_name(@NotNull long ptrToStmt, int ndx); - - /** - Only available if built with SQLITE_ENABLE_COLUMN_METADATA. - */ + 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); } - private static native String sqlite3_column_table_name(@NotNull long ptrToStmt, int ndx); + static native String sqlite3_column_table_name(@NotNull long ptrToStmt, int ndx); - /** - Only available if built with SQLITE_ENABLE_COLUMN_METADATA. - */ public static String sqlite3_column_table_name(@NotNull sqlite3_stmt stmt, int ndx){ return sqlite3_column_table_name(stmt.getNativePointer(), ndx); } /** @@ -999,21 +670,21 @@ // } // sqlite3_value_free(v); // return rv; // } - private static native int sqlite3_column_type(@NotNull long ptrToStmt, int ndx); + 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 ); - private static native int sqlite3_collation_needed( + static native int sqlite3_collation_needed( @NotNull long ptrToDb, @Nullable CollationNeededCallback callback ); /** This functions like C's sqlite3_collation_needed16() because @@ -1023,11 +694,11 @@ @NotNull sqlite3 db, @Nullable CollationNeededCallback callback ){ return sqlite3_collation_needed(db.getNativePointer(), callback); } - private static native CommitHookCallback sqlite3_commit_hook( + static native CommitHookCallback sqlite3_commit_hook( @NotNull long ptrToDb, @Nullable CommitHookCallback hook ); public static CommitHookCallback sqlite3_commit_hook( @NotNull sqlite3 db, @Nullable CommitHookCallback hook @@ -1053,28 +724,10 @@ */ public static int sqlite3_complete(@NotNull String sql){ return sqlite3_complete( nulTerminateUtf8(sql) ); } - /** - Internal level of indirection for sqlite3_config(int). - */ - private static native int sqlite3_config__enable(int op); - - /** - Internal level of indirection for sqlite3_config(ConfigLogCallback). - */ - private static native int sqlite3_config__CONFIG_LOG( - @Nullable ConfigLogCallback logger - ); - - /** - Internal level of indirection for sqlite3_config(ConfigSqlLogCallback). - */ - private static native int sqlite3_config__SQLLOG( - @Nullable ConfigSqlLogCallback logger - ); /**

    Works like in the C API with the exception that it only supports the following subset of configution flags: @@ -1087,18 +740,16 @@

    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 int sqlite3_config(int op){ - return sqlite3_config__enable(op); - } + 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_CONFIG_SQLLOG,...). This sets or clears the + 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. @@ -1105,21 +756,17 @@

    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 int sqlite3_config( @Nullable ConfigSqlLogCallback logger ){ - return sqlite3_config__SQLLOG(logger); - } + public static native int sqlite3_config( @Nullable ConfigSqllogCallback logger ); /** The sqlite3_config() overload for handling the SQLITE_CONFIG_LOG option. */ - public static int sqlite3_config( @Nullable ConfigLogCallback logger ){ - return sqlite3_config__CONFIG_LOG(logger); - } + 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). */ @@ -1146,11 +793,11 @@ public static native int sqlite3_create_function( @NotNull sqlite3 db, @NotNull String functionName, int nArg, int eTextRep, @NotNull SQLFunction func ); - private static native int sqlite3_data_count(@NotNull long ptrToStmt); + 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()); } @@ -1158,11 +805,11 @@ 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 - is null (as opposed to invoking UB). + 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 ); @@ -1180,10 +827,11 @@ 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 ); @@ -1200,11 +848,11 @@ public static native int sqlite3_errcode(@NotNull sqlite3 db); public static native String sqlite3_errmsg(@NotNull sqlite3 db); - private static native int sqlite3_error_offset(@NotNull long ptrToDb); + 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. */ @@ -1214,31 +862,31 @@ public static native String sqlite3_errstr(int resultCode); public static native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt); - private static native int sqlite3_extended_errcode(@NotNull long ptrToDb); + 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 int sqlite3_extended_result_codes( - @NotNull sqlite3 db, boolean on + public static native boolean sqlite3_extended_result_codes( + @NotNull sqlite3 db, boolean onoff ); - private static native boolean sqlite3_get_autocommit(@NotNull long ptrToDb); + 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 ); - private static native int sqlite3_finalize(long ptrToStmt); + static native int sqlite3_finalize(long ptrToStmt); public static int sqlite3_finalize(@NotNull sqlite3_stmt stmt){ return null==stmt ? 0 : sqlite3_finalize(stmt.clearNativePointer()); } @@ -1516,53 +1164,45 @@ 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 and p.call()'s result code is - returned. If preparation of any given segment fails, looping - stops and that result code is returned. + continues, else the loop stops. -

    If p.call() throws, the exception is converted to a db-level - error and a non-0 code is returned, in order to retain the - C-style error semantics of the API. +

    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 prepFlags, + 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 && pos0 ){ + if(pos > 0){ sqlChunk = Arrays.copyOfRange(sqlChunk, pos, sqlChunk.length); } if( 0==sqlChunk.length ) break; - rc = sqlite3_prepare_v3(db, sqlChunk, prepFlags, outStmt, oTail); + rc = sqlite3_prepare_v3(db, sqlChunk, preFlags, outStmt, oTail); if( 0!=rc ) break; pos = oTail.value; stmt = outStmt.take(); - if( null==stmt ){ - // empty statement (whitespace/comments) + if( null == stmt ){ + // empty statement was parsed. continue; } - try{ - rc = p.call(stmt); - }catch(Exception e){ - rc = sqlite3_jni_db_error( db, SQLITE_ERROR, e ); - } + rc = p.call(stmt); } return rc; } /** @@ -1614,11 +1254,11 @@ @NotNull sqlite3 db, @NotNull String[] sql, @NotNull PrepareMultiCallback p){ return sqlite3_prepare_multi(db, sql, 0, p); } - private static native int sqlite3_preupdate_blobwrite(@NotNull long ptrToDb); + 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. @@ -1625,11 +1265,11 @@ */ public static int sqlite3_preupdate_blobwrite(@NotNull sqlite3 db){ return sqlite3_preupdate_blobwrite(db.getNativePointer()); } - private static native int sqlite3_preupdate_count(@NotNull long ptrToDb); + 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. @@ -1636,11 +1276,11 @@ */ public static int sqlite3_preupdate_count(@NotNull sqlite3 db){ return sqlite3_preupdate_count(db.getNativePointer()); } - private static native int sqlite3_preupdate_depth(@NotNull long ptrToDb); + 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. @@ -1647,11 +1287,11 @@ */ public static int sqlite3_preupdate_depth(@NotNull sqlite3 db){ return sqlite3_preupdate_depth(db.getNativePointer()); } - private static native PreupdateHookCallback sqlite3_preupdate_hook( + 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 @@ -1662,26 +1302,17 @@ @NotNull sqlite3 db, @Nullable PreupdateHookCallback hook ){ return sqlite3_preupdate_hook(db.getNativePointer(), hook); } - private static native int sqlite3_preupdate_new(@NotNull long ptrToDb, int col, + 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. - - WARNING: client code _must not_ hold a reference to the returned - sqlite3_value object beyond the scope of the preupdate hook in - which this function is called. Doing so will leave the client - holding a stale pointer, the address of which could point to - anything at all after the pre-update hook is complete. This API - has no way to record such objects and clear/invalidate them at - the end of a pre-update hook. We "could" add infrastructure to do - so, but would require significant levels of bookkeeping. */ public static int sqlite3_preupdate_new(@NotNull sqlite3 db, int col, @NotNull OutputPointer.sqlite3_value out){ return sqlite3_preupdate_new(db.getNativePointer(), col, out); } @@ -1694,20 +1325,17 @@ final OutputPointer.sqlite3_value out = new OutputPointer.sqlite3_value(); sqlite3_preupdate_new(db.getNativePointer(), col, out); return out.take(); } - private static native int sqlite3_preupdate_old(@NotNull long ptrToDb, int col, + 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. - - WARNING: see warning in sqlite3_preupdate_new() regarding the - potential for stale sqlite3_value handles. */ public static int sqlite3_preupdate_old(@NotNull sqlite3 db, int col, @NotNull OutputPointer.sqlite3_value out){ return sqlite3_preupdate_old(db.getNativePointer(), col, out); } @@ -1748,11 +1376,11 @@ 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. */ - private static native void sqlite3_result_error( + 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 @@ -1801,10 +1429,14 @@ ); 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 ); @@ -1820,56 +1452,20 @@

    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 ); - /** - Similar to sqlite3_bind_nio_buffer(), this works like - sqlite3_result_blob() but accepts a java.nio.ByteBuffer as its - input source. See sqlite3_bind_nio_buffer() for the semantics of - the second and subsequent arguments. - - If cx is null then this function will silently fail. If - sqlite3_jni_supports_nio() returns false or iBegin is negative, - an error result is set. If (begin+n) extends beyond the end of - the buffer, it is silently truncated to fit. - - If any of the following apply, this function behaves like - sqlite3_result_null(): the blob is null, the resulting slice of - the blob is empty. - - If the resulting slice of the buffer exceeds SQLITE_LIMIT_LENGTH - then this function behaves like sqlite3_result_error_toobig(). - */ - @Experimental - /*public*/ static native void sqlite3_result_nio_buffer( - @NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob, - int begin, int n - ); - - /** - Convenience overload which uses the whole input object - as the result blob content. - */ - @Experimental - /*public*/ static void sqlite3_result_nio_buffer( - @NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob - ){ - sqlite3_result_nio_buffer(cx, blob, 0, -1); - } - - public static native void sqlite3_result_null( - @NotNull sqlite3_context cx - ); - public static void sqlite3_result_set( @NotNull sqlite3_context cx, @NotNull Boolean v ){ sqlite3_result_int(cx, v ? 1 : 0); } @@ -1926,14 +1522,10 @@ ){ if( null==blob ) sqlite3_result_null(cx); else sqlite3_result_blob(cx, blob, blob.length); } - public static native void sqlite3_result_subtype( - @NotNull sqlite3_context cx, int val - ); - public static native void sqlite3_result_value( @NotNull sqlite3_context cx, @NotNull sqlite3_value v ); public static native void sqlite3_result_zeroblob( @@ -1956,33 +1548,10 @@ @NotNull sqlite3_context cx, @Nullable byte[] blob ){ sqlite3_result_blob(cx, blob, (int)(null==blob ? 0 : blob.length)); } - /** - Convenience overload which behaves like - sqlite3_result_nio_buffer(). - */ - @Experimental - /*public*/ static void sqlite3_result_blob( - @NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob, - int begin, int n - ){ - sqlite3_result_nio_buffer(cx, blob, begin, n); - } - - /** - Convenience overload which behaves like the two-argument overload of - sqlite3_result_nio_buffer(). - */ - @Experimental - /*public*/ static void sqlite3_result_blob( - @NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob - ){ - sqlite3_result_nio_buffer(cx, blob); - } - /** Binds the given text using C's sqlite3_result_blob64() unless:

      @@ -2036,12 +1605,11 @@ /** Binds the given text using C's sqlite3_result_text64() unless:
        -
      • text is null: translates to a call to {@link - #sqlite3_result_null}
      • +
      • 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 @@ -2081,11 +1649,11 @@ final byte[] b = text.getBytes(StandardCharsets.UTF_16); sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16); } } - private static native RollbackHookCallback sqlite3_rollback_hook( + static native RollbackHookCallback sqlite3_rollback_hook( @NotNull long ptrToDb, @Nullable RollbackHookCallback hook ); public static RollbackHookCallback sqlite3_rollback_hook( @NotNull sqlite3 db, @Nullable RollbackHookCallback hook @@ -2133,28 +1701,24 @@ public static native int sqlite3_status64( int op, @NotNull OutputPointer.Int64 pCurrent, @NotNull OutputPointer.Int64 pHighwater, boolean reset ); - private static native int sqlite3_step(@NotNull long ptrToStmt); - - public static int sqlite3_step(@NotNull sqlite3_stmt stmt){ - return null==stmt ? SQLITE_MISUSE : sqlite3_step(stmt.getNativePointer()); - } + public static native int sqlite3_step(@NotNull sqlite3_stmt stmt); public static native boolean sqlite3_stmt_busy(@NotNull sqlite3_stmt stmt); - private static native int sqlite3_stmt_explain(@NotNull long ptrToStmt, int op); + static native int sqlite3_stmt_explain(@NotNull long ptrToStmt, int op); public static int sqlite3_stmt_explain(@NotNull sqlite3_stmt stmt, int op){ - return null==stmt ? SQLITE_MISUSE : sqlite3_stmt_explain(stmt.getNativePointer(), op); + return sqlite3_stmt_explain(stmt.getNativePointer(), op); } - private static native int sqlite3_stmt_isexplain(@NotNull long ptrToStmt); + static native int sqlite3_stmt_isexplain(@NotNull long ptrToStmt); public static int sqlite3_stmt_isexplain(@NotNull sqlite3_stmt stmt){ - return null==stmt ? 0 : sqlite3_stmt_isexplain(stmt.getNativePointer()); + return sqlite3_stmt_isexplain(stmt.getNativePointer()); } public static native boolean sqlite3_stmt_readonly(@NotNull sqlite3_stmt stmt); public static native int sqlite3_stmt_status( @@ -2171,11 +1735,11 @@ 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[] nulTerminatedUtf8 + @NotNull byte[] glob, @NotNull byte[] nullTerminatedUtf8 ); public static int sqlite3_strglob( @NotNull String glob, @NotNull String txt ){ @@ -2185,11 +1749,11 @@ /** The LIKE counterpart of the private sqlite3_strglob() method. */ private static native int sqlite3_strlike( - @NotNull byte[] glob, @NotNull byte[] nulTerminatedUtf8, + @NotNull byte[] glob, @NotNull byte[] nullTerminatedUtf8, int escChar ); public static int sqlite3_strlike( @NotNull String glob, @NotNull String txt, char escChar @@ -2197,11 +1761,11 @@ return sqlite3_strlike(nulTerminateUtf8(glob), nulTerminateUtf8(txt), (int)escChar); } - private static native int sqlite3_system_errno(@NotNull long ptrToDb); + static native int sqlite3_system_errno(@NotNull long ptrToDb); public static int sqlite3_system_errno(@NotNull sqlite3 db){ return sqlite3_system_errno(db.getNativePointer()); } @@ -2243,17 +1807,17 @@ ) ? out : null; } public static native int sqlite3_threadsafe(); - private static native int sqlite3_total_changes(@NotNull long ptrToDb); + static native int sqlite3_total_changes(@NotNull long ptrToDb); public static int sqlite3_total_changes(@NotNull sqlite3 db){ return sqlite3_total_changes(db.getNativePointer()); } - private static native long sqlite3_total_changes64(@NotNull long ptrToDb); + static native long sqlite3_total_changes64(@NotNull long ptrToDb); public static long sqlite3_total_changes64(@NotNull sqlite3 db){ return sqlite3_total_changes64(db.getNativePointer()); } @@ -2272,11 +1836,11 @@ public static native int sqlite3_txn_state( @NotNull sqlite3 db, @Nullable String zSchema ); - private static native UpdateHookCallback sqlite3_update_hook( + static native UpdateHookCallback sqlite3_update_hook( @NotNull long ptrToDb, @Nullable UpdateHookCallback hook ); public static UpdateHookCallback sqlite3_update_hook( @NotNull sqlite3 db, @Nullable UpdateHookCallback hook @@ -2292,71 +1856,71 @@ 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(). */ - private static native byte[] sqlite3_value_blob(@NotNull long ptrToValue); + 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()); } - private static native int sqlite3_value_bytes(@NotNull long ptrToValue); + 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()); } - private static native int sqlite3_value_bytes16(@NotNull long ptrToValue); + 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()); } - private static native double sqlite3_value_double(@NotNull long ptrToValue); + 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()); } - private static native sqlite3_value sqlite3_value_dup(@NotNull long ptrToValue); + 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()); } - private static native int sqlite3_value_encoding(@NotNull long ptrToValue); + 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()); } - private static native void sqlite3_value_free(@Nullable long ptrToValue); + static native void sqlite3_value_free(@Nullable long ptrToValue); public static void sqlite3_value_free(@Nullable sqlite3_value v){ - if( null!=v ) sqlite3_value_free(v.clearNativePointer()); + sqlite3_value_free(v.getNativePointer()); } - private static native boolean sqlite3_value_frombind(@NotNull long ptrToValue); + 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()); } - private static native int sqlite3_value_int(@NotNull long ptrToValue); + 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()); } - private static native long sqlite3_value_int64(@NotNull long ptrToValue); + 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()); } - private static native Object sqlite3_value_java_object(@NotNull long ptrToValue); + 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. @@ -2372,46 +1936,35 @@ 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_object(@NotNull sqlite3_value v, + 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; } - /** - A variant of sqlite3_column_blob() which returns the blob as a - ByteBuffer object. Returns null if its argument is null, if - sqlite3_jni_supports_nio() is false, or if sqlite3_value_blob() - would return null for the same input. - */ - @Experimental - /*public*/ static native java.nio.ByteBuffer sqlite3_value_nio_buffer( - @NotNull sqlite3_value v - ); - - private static native int sqlite3_value_nochange(@NotNull long ptrToValue); + 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()); } - private static native int sqlite3_value_numeric_type(@NotNull long ptrToValue); + 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()); } - private static native int sqlite3_value_subtype(@NotNull long ptrToValue); + 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()); } - private static native byte[] sqlite3_value_text(@NotNull long ptrToValue); + 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 @@ -2419,17 +1972,17 @@ */ public static byte[] sqlite3_value_text(@NotNull sqlite3_value v){ return sqlite3_value_text(v.getNativePointer()); } - private static native String sqlite3_value_text16(@NotNull long ptrToValue); + 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()); } - private static native int sqlite3_value_type(@NotNull long ptrToValue); + 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()); } @@ -2700,10 +2253,11 @@ 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; @@ -2855,15 +2409,13 @@ 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_SUBTYPE = 0x000100000; - public static final int SQLITE_INNOCUOUS = 0x000200000; - public static final int SQLITE_RESULT_SUBTYPE = 0x001000000; + 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; @@ -2888,10 +2440,10 @@ 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(); } - /* Must come after static init(). */ - private static final boolean JNI_SUPPORTS_NIO = sqlite3_jni_supports_nio(); } Index: ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java ================================================================== --- ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java +++ ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java @@ -22,13 +22,12 @@ 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 returning error information, all exceptions will - necessarily be suppressed in order to retain the C-style no-throw - semantics and avoid invoking undefined behavior in the C layer. + support, all exceptions will necessarily be suppressed in order to + retain the C-style no-throw semantics.

        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 Index: ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java ================================================================== --- ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java +++ ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java @@ -19,11 +19,10 @@ public interface CollationNeededCallback extends CallbackProxy { /** Has the same semantics as the C-level sqlite3_create_collation() callback. -

        Because the C API has no mechanism for reporting errors - from this callbacks, any exceptions thrown by this callback - are suppressed. +

        If it throws, the exception message is passed on to the db and + the exception is suppressed. */ - void call(sqlite3 db, int eTextRep, String collationName); + int call(sqlite3 db, int eTextRep, String collationName); } Index: ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java ================================================================== --- ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java +++ ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java @@ -17,10 +17,9 @@ 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. If it throws, the exception is translated into - a db-level error. + callback. Must not throw. */ int call(); } DELETED ext/jni/src/org/sqlite/jni/capi/ConfigSqlLogCallback.java Index: ext/jni/src/org/sqlite/jni/capi/ConfigSqlLogCallback.java ================================================================== --- ext/jni/src/org/sqlite/jni/capi/ConfigSqlLogCallback.java +++ /dev/null @@ -1,25 +0,0 @@ -/* -** 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 ); -} ADDED ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java Index: ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java ================================================================== --- /dev/null +++ ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java @@ -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 ); +} Index: ext/jni/src/org/sqlite/jni/capi/OutputPointer.java ================================================================== --- ext/jni/src/org/sqlite/jni/capi/OutputPointer.java +++ ext/jni/src/org/sqlite/jni/capi/OutputPointer.java @@ -226,28 +226,6 @@ /** Returns the current value. */ public final byte[] get(){return value;} /** Sets the current value. */ public final void set(byte[] v){value = v;} } - - /** - Output pointer for use with native routines which return - blobs via java.nio.ByteBuffer. - - See {@link org.sqlite.jni.capi.CApi#sqlite3_jni_supports_nio} - */ - public static final class ByteBuffer { - /** - This is public for ease of use. Accessors are provided for - consistency with the higher-level types. - */ - public java.nio.ByteBuffer value; - /** Initializes with the value null. */ - public ByteBuffer(){this(null);} - /** Initializes with the value v. */ - public ByteBuffer(java.nio.ByteBuffer v){value = v;} - /** Returns the current value. */ - public final java.nio.ByteBuffer get(){return value;} - /** Sets the current value. */ - public final void set(java.nio.ByteBuffer v){value = v;} - } } Index: ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java ================================================================== --- ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java +++ ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java @@ -23,14 +23,11 @@ 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. If it - throws, sqlite3_prepare_multi() will transform the exception into - a db-level error in order to retain the C-style error semantics - of the API. + Must return 0 on success or an SQLITE_... code on error. See the {@link Finalize} class for a wrapper which finalizes the statement after calling a proxy PrepareMultiCallback. */ int call(sqlite3_stmt st); @@ -38,11 +35,11 @@ /** 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 final PrepareMultiCallback p; + private PrepareMultiCallback p; /** p is the proxy to call() when this.call() is called. */ public Finalize( PrepareMultiCallback p ){ this.p = p; Index: ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java ================================================================== --- ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java +++ ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java @@ -17,11 +17,10 @@ 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. If it throws, the exception is translated to a - db-level error and the exception is suppressed. + callback. */ void call(sqlite3 db, int op, String dbName, String dbTable, long iKey1, long iKey2 ); } Index: ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java ================================================================== --- ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java +++ ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java @@ -16,11 +16,10 @@ /** Callback for use with {@link CApi#sqlite3_rollback_hook}. */ public interface RollbackHookCallback extends CallbackProxy { /** - Must function as documented for the C-level sqlite3_rollback_hook() - callback. If it throws, the exception is translated into - a db-level error. + Works as documented for the C-level sqlite3_rollback_hook() + callback. */ void call(); } Index: ext/jni/src/org/sqlite/jni/capi/SQLFunction.java ================================================================== --- ext/jni/src/org/sqlite/jni/capi/SQLFunction.java +++ ext/jni/src/org/sqlite/jni/capi/SQLFunction.java @@ -30,7 +30,74 @@ 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; + } + } } Index: ext/jni/src/org/sqlite/jni/capi/Tester1.java ================================================================== --- ext/jni/src/org/sqlite/jni/capi/Tester1.java +++ ext/jni/src/org/sqlite/jni/capi/Tester1.java @@ -36,27 +36,19 @@ */ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) @interface SingleThreadOnly{} -/** - Annotation for Tester1 tests which must only be run if - sqlite3_jni_supports_nio() is true. -*/ -@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) -@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) -@interface RequiresJniNio{} - public class Tester1 implements Runnable { //! True when running in multi-threaded mode. private static boolean mtMode = false; //! 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 int listRunTests = 0; + private static boolean listRunTests = false; //! True to squelch all out() and outln() output. private static boolean quietMode = false; //! Total number of runTests() calls. private static int nTestRuns = 0; //! List of test*() methods to run. @@ -333,11 +325,11 @@ } } rc = sqlite3_prepare_v3(db, "INSERT INTO t2(a) VALUES(1),(2),(3)", - 0, outStmt); + SQLITE_PREPARE_NORMALIZE, outStmt); affirm(0 == rc); stmt = outStmt.get(); affirm(0 != stmt.getNativePointer()); sqlite3_finalize(stmt); affirm(0 == stmt.getNativePointer() ); @@ -388,19 +380,10 @@ 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) ); - if( sqlite3_compileoption_used("ENABLE_COLUMN_METADATA") ){ - /* Unlike in native C code, JNI won't trigger an - UnsatisfiedLinkError until these are called (on Linux, at - least). */ - affirm("t".equals(sqlite3_column_table_name(stmt,0))); - affirm("main".equals(sqlite3_column_database_name(stmt,0))); - affirm("a".equals(sqlite3_column_origin_name(stmt,0))); - } - int total2 = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ affirm( sqlite3_stmt_busy(stmt) ); total2 += sqlite3_column_int(stmt, 0); sqlite3_value sv = sqlite3_column_value(stmt, 0); @@ -492,11 +475,10 @@ } sqlite3_finalize(stmt); stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); StringBuilder sbuf = new StringBuilder(); n = 0; - final boolean tryNio = sqlite3_jni_supports_nio(); while( SQLITE_ROW == sqlite3_step(stmt) ){ final sqlite3_value sv = sqlite3_value_dup(sqlite3_column_value(stmt,0)); final String txt = sqlite3_column_text16(stmt, 0); sbuf.append( txt ); affirm( txt.equals(new String( @@ -507,19 +489,10 @@ 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)) ); - if( tryNio ){ - java.nio.ByteBuffer bu = sqlite3_value_nio_buffer(sv); - byte ba[] = sqlite3_value_blob(sv); - affirm( ba.length == bu.capacity() ); - int i = 0; - for( byte b : ba ){ - affirm( b == bu.get(i++) ); - } - } sqlite3_value_free(sv); ++n; } sqlite3_finalize(stmt); affirm(3 == n); @@ -573,83 +546,10 @@ affirm(1 == n); affirm(total == 0x32 + 0x33 + 0x34); sqlite3_close_v2(db); } - @RequiresJniNio - private void testBindByteBuffer(){ - /* TODO: these tests need to be much more extensive to check the - begin/end range handling. */ - - java.nio.ByteBuffer zeroCheck = - java.nio.ByteBuffer.allocateDirect(0); - affirm( null != zeroCheck ); - zeroCheck = null; - sqlite3 db = createNewDb(); - execSql(db, "CREATE TABLE t(a)"); - - final java.nio.ByteBuffer buf = java.nio.ByteBuffer.allocateDirect(10); - buf.put((byte)0x31)/*note that we'll skip this one*/ - .put((byte)0x32) - .put((byte)0x33) - .put((byte)0x34) - .put((byte)0x35)/*we'll skip this one too*/; - - final int expectTotal = buf.get(1) + buf.get(2) + buf.get(3); - sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); - affirm( SQLITE_ERROR == sqlite3_bind_blob(stmt, 1, buf, -1, 0), - "Buffer offset may not be negative." ); - affirm( 0 == sqlite3_bind_blob(stmt, 1, buf, 1, 3) ); - affirm( SQLITE_DONE == sqlite3_step(stmt) ); - sqlite3_finalize(stmt); - stmt = prepare(db, "SELECT a FROM t;"); - int total = 0; - affirm( SQLITE_ROW == sqlite3_step(stmt) ); - byte blob[] = sqlite3_column_blob(stmt, 0); - java.nio.ByteBuffer nioBlob = - sqlite3_column_nio_buffer(stmt, 0); - affirm(3 == blob.length); - affirm(blob.length == nioBlob.capacity()); - affirm(blob.length == nioBlob.limit()); - int i = 0; - for(byte b : blob){ - affirm( i<=3 ); - affirm(b == buf.get(1 + i)); - affirm(b == nioBlob.get(i)); - ++i; - total += b; - } - affirm( SQLITE_DONE == sqlite3_step(stmt) ); - sqlite3_finalize(stmt); - affirm(total == expectTotal); - - SQLFunction func = - new ScalarFunction(){ - public void xFunc(sqlite3_context cx, sqlite3_value[] args){ - sqlite3_result_blob(cx, buf, 1, 3); - } - }; - - affirm( 0 == sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func) ); - stmt = prepare(db, "SELECT myfunc()"); - affirm( SQLITE_ROW == sqlite3_step(stmt) ); - blob = sqlite3_column_blob(stmt, 0); - affirm(3 == blob.length); - i = 0; - total = 0; - for(byte b : blob){ - affirm( i<=3 ); - affirm(b == buf.get(1 + i++)); - total += b; - } - affirm( SQLITE_DONE == sqlite3_step(stmt) ); - sqlite3_finalize(stmt); - affirm(total == expectTotal); - - sqlite3_close_v2(db); - } - private void testSql(){ sqlite3 db = createNewDb(); sqlite3_stmt stmt = prepare(db, "SELECT 1"); affirm( "SELECT 1".equals(sqlite3_sql(stmt)) ); sqlite3_finalize(stmt); @@ -691,13 +591,13 @@ ++xDestroyCalled.value; } }; final CollationNeededCallback collLoader = new CollationNeededCallback(){ @Override - public void call(sqlite3 dbArg, int eTextRep, String collationName){ + public int call(sqlite3 dbArg, int eTextRep, String collationName){ affirm(dbArg == db/* as opposed to a temporary object*/); - sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation); + return sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation); } }; int rc = sqlite3_collation_needed(db, collLoader); affirm( 0 == rc ); rc = sqlite3_collation_needed(db, collLoader); @@ -901,21 +801,17 @@ affirm( testResult.value == db ); rc = sqlite3_bind_java_object(stmt, 1, boundObj); affirm( 0==rc ); int n = 0; if( SQLITE_ROW == sqlite3_step(stmt) ){ - affirm( testResult.value == sqlite3_column_java_object(stmt, 0) ); - affirm( testResult.value == sqlite3_column_java_object(stmt, 0, sqlite3.class) ); - affirm( null == sqlite3_column_java_object(stmt, 0, sqlite3_stmt.class) ); - affirm( null == sqlite3_column_java_object(stmt,1) ); final sqlite3_value v = sqlite3_column_value(stmt, 0); affirm( testResult.value == sqlite3_value_java_object(v) ); - affirm( testResult.value == sqlite3_value_java_object(v, sqlite3.class) ); + affirm( testResult.value == sqlite3_value_java_casted(v, sqlite3.class) ); affirm( testResult.value == - sqlite3_value_java_object(v, testResult.value.getClass()) ); - affirm( testResult.value == sqlite3_value_java_object(v, Object.class) ); - affirm( null == sqlite3_value_java_object(v, String.class) ); + sqlite3_value_java_casted(v, testResult.value.getClass()) ); + affirm( testResult.value == sqlite3_value_java_casted(v, Object.class) ); + affirm( null == sqlite3_value_java_casted(v, String.class) ); ++n; } sqlite3_finalize(stmt); affirm( 1 == n ); affirm( 0==sqlite3_db_release_memory(db) ); @@ -926,32 +822,19 @@ 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); - final ValueHolder neverEverDoThisInClientCode = new ValueHolder<>(null); - final ValueHolder neverEverDoThisInClientCode2 = new ValueHolder<>(null); SQLFunction func = new AggregateFunction(){ @Override public void xStep(sqlite3_context cx, sqlite3_value[] args){ - if( null==neverEverDoThisInClientCode.value ){ - /* !!!NEVER!!! hold a reference to an sqlite3_value or - sqlite3_context object like this in client code! They - are ONLY legal for the duration of their single - call. We do it here ONLY to test that the defenses - against clients doing this are working. */ - neverEverDoThisInClientCode.value = args; - } final ValueHolder agg = this.getAggregateState(cx, 0); agg.value += sqlite3_value_int(args[0]); affirm( agg == this.getAggregateState(cx, 0) ); } @Override public void xFinal(sqlite3_context cx){ - if( null==neverEverDoThisInClientCode2.value ){ - neverEverDoThisInClientCode2.value = cx; - } final Integer v = this.takeAggregateState(cx); if(null == v){ xFinalNull.value = true; sqlite3_result_null(cx); }else{ @@ -972,14 +855,10 @@ affirm( 30+v == v2 ); ++n; } affirm( 1==n ); affirm(!xFinalNull.value); - affirm( null!=neverEverDoThisInClientCode.value ); - affirm( null!=neverEverDoThisInClientCode2.value ); - affirm( 0 xBusyCalled = new ValueHolder<>(0); + BusyHandlerCallback handler = new BusyHandlerCallback(){ + @Override public int call(int n){ + //outln("busy handler #"+n); + return n > 2 ? 0 : ++xBusyCalled.value; + } + }; + rc = sqlite3_busy_handler(db2, handler); + affirm(0 == rc); + + // Force a locked condition... + execSql(db1, "BEGIN EXCLUSIVE"); + rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt); + affirm( SQLITE_BUSY == rc); + affirm( null == outStmt.get() ); + affirm( 3 == xBusyCalled.value ); + sqlite3_close_v2(db1); + sqlite3_close_v2(db2); try{ - final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3(); - final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); - - int rc = sqlite3_open(dbName, outDb); - ++metrics.dbOpen; - affirm( 0 == rc ); - final sqlite3 db1 = outDb.get(); - execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)"); - rc = sqlite3_open(dbName, outDb); - ++metrics.dbOpen; - affirm( 0 == rc ); - affirm( outDb.get() != db1 ); - final sqlite3 db2 = outDb.get(); - - affirm( "main".equals( sqlite3_db_name(db1, 0) ) ); - rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo"); - affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) ); - affirm( "foo".equals( sqlite3_db_name(db1, 0) ) ); - affirm( SQLITE_MISUSE == sqlite3_db_config(db1, 0, 0, null) ); - - final ValueHolder xBusyCalled = new ValueHolder<>(0); - BusyHandlerCallback handler = new BusyHandlerCallback(){ - @Override public int call(int n){ - //outln("busy handler #"+n); - return n > 2 ? 0 : ++xBusyCalled.value; - } - }; - rc = sqlite3_busy_handler(db2, handler); - affirm(0 == rc); - - // Force a locked condition... - execSql(db1, "BEGIN EXCLUSIVE"); - rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt); - affirm( SQLITE_BUSY == rc); - affirm( null == outStmt.get() ); - affirm( 3 == xBusyCalled.value ); - sqlite3_close_v2(db1); - sqlite3_close_v2(db2); - }finally{ - try{(new java.io.File(dbName)).delete();} - catch(Exception e){/* ignore */} + final java.io.File f = new java.io.File(dbName); + f.delete(); + }catch(Exception e){ + /* ignore */ } } private void testProgress(){ final sqlite3 db = createNewDb(); @@ -1211,11 +1091,10 @@ sqlite3_close_v2(db); } private void testCommitHook(){ final sqlite3 db = createNewDb(); - sqlite3_extended_result_codes(db, true); final ValueHolder counter = new ValueHolder<>(0); final ValueHolder hookResult = new ValueHolder<>(0); final CommitHookCallback theHook = new CommitHookCallback(){ @Override public int call(){ ++counter.value; @@ -1254,11 +1133,11 @@ 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_COMMITHOOK == rc ); + affirm( SQLITE_CONSTRAINT == rc ); affirm( 6 == counter.value ); sqlite3_close_v2(db); } private void testUpdateHook(){ @@ -1473,13 +1352,10 @@ 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 ); - sqlite3_set_authorizer(db, null); - rc = execSql(db, false, "UPDATE t SET a=2"); - affirm( 0==rc ); // TODO: expand these tests considerably sqlite3_close(db); } @SingleThreadOnly /* because multiple threads legitimately make these @@ -1537,11 +1413,11 @@ affirm( err.getMessage().indexOf(toss.value)>0 ); toss.value = null; val.value = 0; final AutoExtensionCallback ax2 = new AutoExtensionCallback(){ - @Override public int call(sqlite3 db){ + @Override public synchronized int call(sqlite3 db){ ++val.value; return 0; } }; rc = sqlite3_auto_extension( ax2 ); @@ -1748,100 +1624,20 @@ 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), 0); + 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 ); - - if( !sqlite3_jni_supports_nio() ){ - outln("WARNING: skipping tests for ByteBuffer-using sqlite3_blob APIs ", - "because this platform lacks that support."); - sqlite3_close_v2(db); - return; - } - /* Sanity checks for the java.nio.ByteBuffer-taking overloads of - sqlite3_blob_read/write(). */ - execSql(db, "UPDATE t SET a=zeroblob(10)"); - b = sqlite3_blob_open(db, "main", "t", "a", 1, 1); - affirm( null!=b ); - java.nio.ByteBuffer bb = java.nio.ByteBuffer.allocateDirect(10); - for( byte i = 0; i < 10; ++i ){ - bb.put((int)i, (byte)(48+i & 0xff)); - } - rc = sqlite3_blob_write(b, 1, bb, 1, 10); - affirm( rc==SQLITE_ERROR, "b length < (srcOffset + bb length)" ); - rc = sqlite3_blob_write(b, -1, bb); - affirm( rc==SQLITE_ERROR, "Target offset may not be negative" ); - rc = sqlite3_blob_write(b, 0, bb, -1, -1); - affirm( rc==SQLITE_ERROR, "Source offset may not be negative" ); - rc = sqlite3_blob_write(b, 1, bb, 1, 8); - affirm( rc==0 ); - // b's contents: 0 49 50 51 52 53 54 55 56 0 - // ascii: 0 '1' '2' '3' '4' '5' '6' '7' '8' 0 - byte br[] = new byte[10]; - java.nio.ByteBuffer bbr = - java.nio.ByteBuffer.allocateDirect(bb.limit()); - rc = sqlite3_blob_read( b, br, 0 ); - affirm( rc==0 ); - rc = sqlite3_blob_read( b, bbr ); - affirm( rc==0 ); - java.nio.ByteBuffer bbr2 = sqlite3_blob_read_nio_buffer(b, 0, 12); - affirm( null==bbr2, "Read size is too big"); - bbr2 = sqlite3_blob_read_nio_buffer(b, -1, 3); - affirm( null==bbr2, "Source offset is negative"); - bbr2 = sqlite3_blob_read_nio_buffer(b, 5, 6); - affirm( null==bbr2, "Read pos+size is too big"); - bbr2 = sqlite3_blob_read_nio_buffer(b, 4, 7); - affirm( null==bbr2, "Read pos+size is too big"); - bbr2 = sqlite3_blob_read_nio_buffer(b, 4, 6); - affirm( null!=bbr2 ); - java.nio.ByteBuffer bbr3 = - java.nio.ByteBuffer.allocateDirect(2 * bb.limit()); - java.nio.ByteBuffer bbr4 = - java.nio.ByteBuffer.allocateDirect(5); - rc = sqlite3_blob_read( b, bbr3 ); - affirm( rc==0 ); - rc = sqlite3_blob_read( b, bbr4 ); - affirm( rc==0 ); - affirm( sqlite3_blob_bytes(b)==bbr3.limit() ); - affirm( 5==bbr4.limit() ); - sqlite3_blob_close(b); - affirm( 0==br[0] ); - affirm( 0==br[9] ); - affirm( 0==bbr.get(0) ); - affirm( 0==bbr.get(9) ); - affirm( bbr2.limit() == 6 ); - affirm( 0==bbr3.get(0) ); - { - Exception ex = null; - try{ bbr3.get(11); } - catch(Exception e){ex = e;} - affirm( ex instanceof IndexOutOfBoundsException, - "bbr3.limit() was reset by read()" ); - ex = null; - } - affirm( 0==bbr4.get(0) ); - for( int i = 1; i < 9; ++i ){ - affirm( br[i] == 48 + i ); - affirm( br[i] == bbr.get(i) ); - affirm( br[i] == bbr3.get(i) ); - if( i>3 ){ - affirm( br[i] == bbr2.get(i-4) ); - } - if( i < bbr4.limit() ){ - affirm( br[i] == bbr4.get(i) ); - } - } sqlite3_close_v2(db); } private void testPrepareMulti(){ final sqlite3 db = createNewDb(); @@ -1850,30 +1646,22 @@ "; insert into t(a) values(1),(2),(3);", "select a from t;" }; final List liStmt = new ArrayList(); final PrepareMultiCallback proxy = new PrepareMultiCallback.StepAll(); - final ValueHolder toss = new ValueHolder<>(null); PrepareMultiCallback m = new PrepareMultiCallback() { @Override public int call(sqlite3_stmt st){ liStmt.add(st); - if( null!=toss.value ){ - throw new RuntimeException(toss.value); - } return proxy.call(st); } }; int rc = sqlite3_prepare_multi(db, sql, m); affirm( 0==rc ); affirm( liStmt.size() == 3 ); for( sqlite3_stmt st : liStmt ){ sqlite3_finalize(st); } - toss.value = "This is an exception."; - rc = sqlite3_prepare_multi(db, "SELECT 1", m); - affirm( SQLITE_ERROR==rc ); - affirm( sqlite3_errmsg(db).indexOf(toss.value)>0 ); sqlite3_close_v2(db); } /* Copy/paste/rename this to add new tests. */ private void _testTemplate(){ @@ -1908,11 +1696,11 @@ affirm( null!=mlist ); if( shuffle ){ mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) ); java.util.Collections.shuffle(mlist); } - if( (!fromThread && listRunTests>0) || listRunTests>1 ){ + if( listRunTests ){ synchronized(this.getClass()){ if( !fromThread ){ out("Initial test"," list: "); for(java.lang.reflect.Method m : testMethods){ out(m.getName()+" "); @@ -1970,15 +1758,12 @@ -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. In multi-threaded mode, use this twice to - to emit the list run by each thread (which may differ from the initial - list, in particular if -shuffle is used). + which are hard-coded. This is noisy in multi-threaded mode. -fail: forces an exception to be thrown during the test run. Use with -shuffle to make its appearance unpredictable. -v: emit some developer-mode info at the end. @@ -2003,11 +1788,11 @@ }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; + listRunTests = true; }else if(arg.equals("fail")){ forceFail = true; }else if(arg.equals("sqllog")){ sqlLog = true; }else if(arg.equals("configlog")){ @@ -2022,11 +1807,11 @@ } } if( sqlLog ){ if( sqlite3_compileoption_used("ENABLE_SQLLOG") ){ - final ConfigSqlLogCallback log = new ConfigSqlLogCallback() { + final ConfigSqllogCallback log = new ConfigSqllogCallback() { @Override public void call(sqlite3 db, String msg, int op){ switch(op){ case 0: outln("Opening db: ",db); break; case 1: outln("SQL ",db,": ",msg); break; case 2: outln("Closing db: ",db); break; @@ -2033,11 +1818,11 @@ } } }; int rc = sqlite3_config( log ); affirm( 0==rc ); - rc = sqlite3_config( (ConfigSqlLogCallback)null ); + rc = sqlite3_config( (ConfigSqllogCallback)null ); affirm( 0==rc ); rc = sqlite3_config( log ); affirm( 0==rc ); }else{ outln("WARNING: -sqllog is not active because library was built ", @@ -2071,24 +1856,22 @@ final String name = m.getName(); if( name.equals("testFail") ){ if( forceFail ){ testMethods.add(m); } - }else if( m.isAnnotationPresent( RequiresJniNio.class ) - && !sqlite3_jni_supports_nio() ){ - outln("Skipping test for lack of JNI java.nio.ByteBuffer support: ", - name,"()\n"); - ++nSkipped; }else if( !m.isAnnotationPresent( ManualTest.class ) ){ if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){ - out("Skipping test in multi-thread mode: ",name,"()\n"); - ++nSkipped; + if( 0==nSkipped++ ){ + out("Skipping tests in multi-thread mode:"); + } + out(" "+name+"()"); }else if( name.startsWith("test") ){ testMethods.add(m); } } } + if( nSkipped>0 ) out("\n"); } final long timeStart = System.currentTimeMillis(); int nLoop = 0; switch( sqlite3_threadsafe() ){ /* Sanity checking */ @@ -2116,11 +1899,10 @@ } outln("libversion_number: ", sqlite3_libversion_number(),"\n", sqlite3_libversion(),"\n",SQLITE_SOURCE_ID,"\n", "SQLITE_THREADSAFE=",sqlite3_threadsafe()); - outln("JVM NIO support? ",sqlite3_jni_supports_nio() ? "YES" : "NO"); final boolean showLoopCount = (nRepeat>1 && nThread>1); if( showLoopCount ){ outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each."); } if( takeNaps ) outln("Napping between tests is enabled."); Index: ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java ================================================================== --- ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java +++ ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java @@ -17,10 +17,9 @@ 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. If it throws, the exception is translated into - a db-level error. + callback. */ void call(int opId, String dbName, String tableName, long rowId); } Index: ext/jni/src/org/sqlite/jni/capi/ValueHolder.java ================================================================== --- ext/jni/src/org/sqlite/jni/capi/ValueHolder.java +++ ext/jni/src/org/sqlite/jni/capi/ValueHolder.java @@ -7,20 +7,18 @@ ** 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 ValueHolder utility class for the sqlite3 -** JNI bindings. +** This file contains a set of tests for the sqlite3 JNI bindings. */ package org.sqlite.jni.capi; /** A helper class which simply holds a single value. Its primary use is for communicating values out of anonymous classes, as doing so - requires a "final" reference, as well as communicating aggregate - SQL function state across calls to such functions. + requires a "final" reference. */ public class ValueHolder { public T value; public ValueHolder(){} public ValueHolder(T v){value = v;} Index: ext/jni/src/org/sqlite/jni/capi/sqlite3.java ================================================================== --- ext/jni/src/org/sqlite/jni/capi/sqlite3.java +++ ext/jni/src/org/sqlite/jni/capi/sqlite3.java @@ -36,8 +36,8 @@ +"["+((null == fn) ? "" : fn)+"]" ; } @Override public void close(){ - CApi.sqlite3_close_v2(this); + CApi.sqlite3_close_v2(this.clearNativePointer()); } } Index: ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java ================================================================== --- ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java +++ ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java @@ -23,8 +23,9 @@ implements AutoCloseable { // Only invoked from JNI. private sqlite3_blob(){} @Override public void close(){ - CApi.sqlite3_blob_close(this); + CApi.sqlite3_blob_close(this.clearNativePointer()); } + } Index: ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java ================================================================== --- ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java +++ ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java @@ -23,8 +23,8 @@ implements AutoCloseable { // Only invoked from JNI. private sqlite3_stmt(){} @Override public void close(){ - CApi.sqlite3_finalize(this); + CApi.sqlite3_finalize(this.clearNativePointer()); } } Index: ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java ================================================================== --- ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java +++ ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java @@ -623,24 +623,14 @@ ); 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]" ); - boolean threw = false; - try{ - /* columntext() used to return NULLs when given an out-of bounds column - but now results in a range error. */ - do_execsql_test(db, - "SELECT fts5_columntext(ft, 2) FROM ft('x') ORDER BY rowid", - "[null, null, null, null, null, null]" - ); - }catch(Exception e){ - threw = true; - affirm( e.getMessage().matches(".*column index out of range") ); - } - affirm( threw ); - threw = false; + do_execsql_test(db, + "SELECT fts5_columntext(ft, 2) FROM ft('x') ORDER BY rowid", + "[null, null, null, null, null, null]" + ); /* Test fts5_columntotalsize() */ do_execsql_test(db, "SELECT fts5_columntotalsize(ft, 0) FROM ft('x') ORDER BY rowid", "[12, 12, 12, 12, 12, 12]" Index: ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java ================================================================== --- ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java +++ ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java @@ -10,10 +10,14 @@ ** ************************************************************************* ** 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 @@ -45,79 +49,13 @@ Optionally override to be notified when the UDF is finalized by SQLite. */ public void xDestroy() {} - /** - 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; - } - } - /** Per-invocation state for the UDF. */ - private final PerContextState map = new PerContextState<>(); + 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 Index: ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java ================================================================== --- ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java +++ ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java @@ -20,18 +20,10 @@ Base marker interface for SQLite's three types of User-Defined SQL Functions (UDFs): Scalar, Aggregate, and Window functions. */ public interface SqlFunction { - public static final int DETERMINISTIC = CApi.SQLITE_DETERMINISTIC; - public static final int INNOCUOUS = CApi.SQLITE_INNOCUOUS; - public static final int DIRECTONLY = CApi.SQLITE_DIRECTONLY; - public static final int SUBTYPE = CApi.SQLITE_SUBTYPE; - public static final int RESULT_SUBTYPE = CApi.SQLITE_RESULT_SUBTYPE; - public static final int UTF8 = CApi.SQLITE_UTF8; - public static final int UTF16 = CApi.SQLITE_UTF16; - /** The Arguments type is an abstraction on top of the lower-level UDF function argument types. It provides _most_ of the functionality of the lower-level interface, insofar as possible without "leaking" those types into this API. @@ -51,122 +43,16 @@ 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.args = args==null ? new sqlite3_value[0] : args;; this.length = this.args.length; } /** - Returns the sqlite3_value at the given argument index or throws - an IllegalArgumentException exception if ndx is out of range. - */ - private sqlite3_value valueAt(int ndx){ - if(ndx<0 || ndx>=args.length){ - throw new IllegalArgumentException( - "SQL function argument index "+ndx+" is out of range." - ); - } - return args[ndx]; - } - - //! Returns the underlying sqlite3_context for these arguments. - sqlite3_context getContext(){return cx;} - - /** - Returns the Sqlite (db) object associated with this UDF call, - or null if the UDF is somehow called without such an object or - the db has been closed in an untimely manner (e.g. closed by a - UDF call). - */ - public Sqlite getDb(){ - return Sqlite.fromNative( CApi.sqlite3_context_db_handle(cx) ); - } - - public int getArgCount(){ return args.length; } - - public int getInt(int argNdx){return CApi.sqlite3_value_int(valueAt(argNdx));} - public long getInt64(int argNdx){return CApi.sqlite3_value_int64(valueAt(argNdx));} - public double getDouble(int argNdx){return CApi.sqlite3_value_double(valueAt(argNdx));} - public byte[] getBlob(int argNdx){return CApi.sqlite3_value_blob(valueAt(argNdx));} - public byte[] getText(int argNdx){return CApi.sqlite3_value_text(valueAt(argNdx));} - public String getText16(int argNdx){return CApi.sqlite3_value_text16(valueAt(argNdx));} - public int getBytes(int argNdx){return CApi.sqlite3_value_bytes(valueAt(argNdx));} - public int getBytes16(int argNdx){return CApi.sqlite3_value_bytes16(valueAt(argNdx));} - public Object getObject(int argNdx){return CApi.sqlite3_value_java_object(valueAt(argNdx));} - public T getObject(int argNdx, Class type){ - return CApi.sqlite3_value_java_object(valueAt(argNdx), type); - } - - public int getType(int argNdx){return CApi.sqlite3_value_type(valueAt(argNdx));} - public int getSubtype(int argNdx){return CApi.sqlite3_value_subtype(valueAt(argNdx));} - public int getNumericType(int argNdx){return CApi.sqlite3_value_numeric_type(valueAt(argNdx));} - public int getNoChange(int argNdx){return CApi.sqlite3_value_nochange(valueAt(argNdx));} - public boolean getFromBind(int argNdx){return CApi.sqlite3_value_frombind(valueAt(argNdx));} - public int getEncoding(int argNdx){return CApi.sqlite3_value_encoding(valueAt(argNdx));} - - public void resultInt(int v){ CApi.sqlite3_result_int(cx, v); } - public void resultInt64(long v){ CApi.sqlite3_result_int64(cx, v); } - public void resultDouble(double v){ CApi.sqlite3_result_double(cx, v); } - public void resultError(String msg){CApi.sqlite3_result_error(cx, msg);} - public void resultError(Exception e){CApi.sqlite3_result_error(cx, e);} - public void resultErrorTooBig(){CApi.sqlite3_result_error_toobig(cx);} - public void resultErrorCode(int rc){CApi.sqlite3_result_error_code(cx, rc);} - public void resultObject(Object o){CApi.sqlite3_result_java_object(cx, o);} - public void resultNull(){CApi.sqlite3_result_null(cx);} - /** - Analog to sqlite3_result_value(), using the Value object at the - given argument index. - */ - public void resultArg(int argNdx){CApi.sqlite3_result_value(cx, valueAt(argNdx));} - public void resultSubtype(int subtype){CApi.sqlite3_result_subtype(cx, subtype);} - public void resultZeroBlob(long n){ - // Throw on error? If n is too big, - // sqlite3_result_error_toobig() is automatically called. - CApi.sqlite3_result_zeroblob64(cx, n); - } - - public void resultBlob(byte[] blob){CApi.sqlite3_result_blob(cx, blob);} - public void resultText(byte[] utf8){CApi.sqlite3_result_text(cx, utf8);} - public void resultText(String txt){CApi.sqlite3_result_text(cx, txt);} - public void resultText16(byte[] utf16){CApi.sqlite3_result_text16(cx, utf16);} - public void resultText16(String txt){CApi.sqlite3_result_text16(cx, txt);} - - /** - Callbacks should invoke this on OOM errors, instead of throwing - OutOfMemoryError, because the latter cannot be propagated - through the C API. - */ - public void resultNoMem(){CApi.sqlite3_result_error_nomem(cx);} - - /** - Analog to sqlite3_set_auxdata() but throws if argNdx is out of - range. - */ - public void setAuxData(int argNdx, Object o){ - /* From the API docs: https://www.sqlite.org/c3ref/get_auxdata.html - - The value of the N parameter to these interfaces should be - non-negative. Future enhancements may make use of negative N - values to define new kinds of function caching behavior. - */ - valueAt(argNdx); - CApi.sqlite3_set_auxdata(cx, argNdx, o); - } - - /** - Analog to sqlite3_get_auxdata() but throws if argNdx is out of - range. - */ - public Object getAuxData(int argNdx){ - valueAt(argNdx); - return CApi.sqlite3_get_auxdata(cx, argNdx); - } - - /** - Represents a single SqlFunction argument. Primarily intended + Wrapper for a single SqlFunction argument. Primarily intended for use with the Arguments class's Iterable interface. */ public final static class Arg { private final Arguments a; private final int ndx; @@ -184,11 +70,11 @@ 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 getObject(Class type){ return a.getObject(ndx, type); } + 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);} } @@ -199,18 +85,159 @@ 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 { - private final ScalarFunction impl; + final ScalarFunction impl; ScalarAdapter(ScalarFunction impl){ this.impl = impl; } /** Proxies this.impl.xFunc(), adapting the call arguments to that @@ -232,13 +259,12 @@ /** Internal-use adapter for wrapping this package's AggregateFunction for use with the org.sqlite.jni.capi.AggregateFunction interface. */ - static /*cannot be final without duplicating the whole body in WindowAdapter*/ - class AggregateAdapter extends org.sqlite.jni.capi.AggregateFunction { - private final AggregateFunction impl; + static final class AggregateAdapter extends org.sqlite.jni.capi.AggregateFunction { + final AggregateFunction impl; AggregateAdapter(AggregateFunction impl){ this.impl = impl; } /** @@ -254,60 +280,17 @@ 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(). + 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(); - } - } - - /** - Internal-use adapter for wrapping this package's WindowFunction - for use with the org.sqlite.jni.capi.WindowFunction interface. - */ - static final class WindowAdapter extends AggregateAdapter { - private final WindowFunction impl; - WindowAdapter(WindowFunction impl){ - super(impl); - this.impl = impl; - } - - /** - Proxies this.impl.xInverse(), adapting the call arguments to that - function's signature. If the proxied function throws, it is - translated to sqlite_result_error() with the exception's - message. - */ - public void xInverse(sqlite3_context cx, sqlite3_value[] args){ - try{ - impl.xInverse( new SqlFunction.Arguments(cx, args) ); - }catch(Exception e){ - CApi.sqlite3_result_error(cx, e); - } - } - - /** - As for the xValue() argument of the C API's sqlite3_create_window_function(). - If the proxied function throws, it is translated into a sqlite3_result_error(). - */ - public void xValue(sqlite3_context cx){ - try{ - impl.xValue( new SqlFunction.Arguments(cx, null) ); - }catch(Exception e){ CApi.sqlite3_result_error(cx, e); } } public void xDestroy(){ Index: ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java ================================================================== --- ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java +++ ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java @@ -11,17 +11,15 @@ ************************************************************************* ** 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.sqlite3_backup; -import org.sqlite.jni.capi.sqlite3_blob; import org.sqlite.jni.capi.OutputPointer; -import java.nio.ByteBuffer; /** 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 @@ -28,300 +26,16 @@ 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; - private static final boolean JNI_SUPPORTS_NIO = - CApi.sqlite3_jni_supports_nio(); - - // Result codes - public static final int OK = CApi.SQLITE_OK; - public static final int ERROR = CApi.SQLITE_ERROR; - public static final int INTERNAL = CApi.SQLITE_INTERNAL; - public static final int PERM = CApi.SQLITE_PERM; - public static final int ABORT = CApi.SQLITE_ABORT; - public static final int BUSY = CApi.SQLITE_BUSY; - public static final int LOCKED = CApi.SQLITE_LOCKED; - public static final int NOMEM = CApi.SQLITE_NOMEM; - public static final int READONLY = CApi.SQLITE_READONLY; - public static final int INTERRUPT = CApi.SQLITE_INTERRUPT; - public static final int IOERR = CApi.SQLITE_IOERR; - public static final int CORRUPT = CApi.SQLITE_CORRUPT; - public static final int NOTFOUND = CApi.SQLITE_NOTFOUND; - public static final int FULL = CApi.SQLITE_FULL; - public static final int CANTOPEN = CApi.SQLITE_CANTOPEN; - public static final int PROTOCOL = CApi.SQLITE_PROTOCOL; - public static final int EMPTY = CApi.SQLITE_EMPTY; - public static final int SCHEMA = CApi.SQLITE_SCHEMA; - public static final int TOOBIG = CApi.SQLITE_TOOBIG; - public static final int CONSTRAINT = CApi. SQLITE_CONSTRAINT; - public static final int MISMATCH = CApi.SQLITE_MISMATCH; - public static final int MISUSE = CApi.SQLITE_MISUSE; - public static final int NOLFS = CApi.SQLITE_NOLFS; - public static final int AUTH = CApi.SQLITE_AUTH; - public static final int FORMAT = CApi.SQLITE_FORMAT; - public static final int RANGE = CApi.SQLITE_RANGE; - public static final int NOTADB = CApi.SQLITE_NOTADB; - public static final int NOTICE = CApi.SQLITE_NOTICE; - public static final int WARNING = CApi.SQLITE_WARNING; - public static final int ROW = CApi.SQLITE_ROW; - public static final int DONE = CApi.SQLITE_DONE; - public static final int ERROR_MISSING_COLLSEQ = CApi.SQLITE_ERROR_MISSING_COLLSEQ; - public static final int ERROR_RETRY = CApi.SQLITE_ERROR_RETRY; - public static final int ERROR_SNAPSHOT = CApi.SQLITE_ERROR_SNAPSHOT; - public static final int IOERR_READ = CApi.SQLITE_IOERR_READ; - public static final int IOERR_SHORT_READ = CApi.SQLITE_IOERR_SHORT_READ; - public static final int IOERR_WRITE = CApi.SQLITE_IOERR_WRITE; - public static final int IOERR_FSYNC = CApi.SQLITE_IOERR_FSYNC; - public static final int IOERR_DIR_FSYNC = CApi.SQLITE_IOERR_DIR_FSYNC; - public static final int IOERR_TRUNCATE = CApi.SQLITE_IOERR_TRUNCATE; - public static final int IOERR_FSTAT = CApi.SQLITE_IOERR_FSTAT; - public static final int IOERR_UNLOCK = CApi.SQLITE_IOERR_UNLOCK; - public static final int IOERR_RDLOCK = CApi.SQLITE_IOERR_RDLOCK; - public static final int IOERR_DELETE = CApi.SQLITE_IOERR_DELETE; - public static final int IOERR_BLOCKED = CApi.SQLITE_IOERR_BLOCKED; - public static final int IOERR_NOMEM = CApi.SQLITE_IOERR_NOMEM; - public static final int IOERR_ACCESS = CApi.SQLITE_IOERR_ACCESS; - public static final int IOERR_CHECKRESERVEDLOCK = CApi.SQLITE_IOERR_CHECKRESERVEDLOCK; - public static final int IOERR_LOCK = CApi.SQLITE_IOERR_LOCK; - public static final int IOERR_CLOSE = CApi.SQLITE_IOERR_CLOSE; - public static final int IOERR_DIR_CLOSE = CApi.SQLITE_IOERR_DIR_CLOSE; - public static final int IOERR_SHMOPEN = CApi.SQLITE_IOERR_SHMOPEN; - public static final int IOERR_SHMSIZE = CApi.SQLITE_IOERR_SHMSIZE; - public static final int IOERR_SHMLOCK = CApi.SQLITE_IOERR_SHMLOCK; - public static final int IOERR_SHMMAP = CApi.SQLITE_IOERR_SHMMAP; - public static final int IOERR_SEEK = CApi.SQLITE_IOERR_SEEK; - public static final int IOERR_DELETE_NOENT = CApi.SQLITE_IOERR_DELETE_NOENT; - public static final int IOERR_MMAP = CApi.SQLITE_IOERR_MMAP; - public static final int IOERR_GETTEMPPATH = CApi.SQLITE_IOERR_GETTEMPPATH; - public static final int IOERR_CONVPATH = CApi.SQLITE_IOERR_CONVPATH; - public static final int IOERR_VNODE = CApi.SQLITE_IOERR_VNODE; - public static final int IOERR_AUTH = CApi.SQLITE_IOERR_AUTH; - public static final int IOERR_BEGIN_ATOMIC = CApi.SQLITE_IOERR_BEGIN_ATOMIC; - public static final int IOERR_COMMIT_ATOMIC = CApi.SQLITE_IOERR_COMMIT_ATOMIC; - public static final int IOERR_ROLLBACK_ATOMIC = CApi.SQLITE_IOERR_ROLLBACK_ATOMIC; - public static final int IOERR_DATA = CApi.SQLITE_IOERR_DATA; - public static final int IOERR_CORRUPTFS = CApi.SQLITE_IOERR_CORRUPTFS; - public static final int LOCKED_SHAREDCACHE = CApi.SQLITE_LOCKED_SHAREDCACHE; - public static final int LOCKED_VTAB = CApi.SQLITE_LOCKED_VTAB; - public static final int BUSY_RECOVERY = CApi.SQLITE_BUSY_RECOVERY; - public static final int BUSY_SNAPSHOT = CApi.SQLITE_BUSY_SNAPSHOT; - public static final int BUSY_TIMEOUT = CApi.SQLITE_BUSY_TIMEOUT; - public static final int CANTOPEN_NOTEMPDIR = CApi.SQLITE_CANTOPEN_NOTEMPDIR; - public static final int CANTOPEN_ISDIR = CApi.SQLITE_CANTOPEN_ISDIR; - public static final int CANTOPEN_FULLPATH = CApi.SQLITE_CANTOPEN_FULLPATH; - public static final int CANTOPEN_CONVPATH = CApi.SQLITE_CANTOPEN_CONVPATH; - public static final int CANTOPEN_SYMLINK = CApi.SQLITE_CANTOPEN_SYMLINK; - public static final int CORRUPT_VTAB = CApi.SQLITE_CORRUPT_VTAB; - public static final int CORRUPT_SEQUENCE = CApi.SQLITE_CORRUPT_SEQUENCE; - public static final int CORRUPT_INDEX = CApi.SQLITE_CORRUPT_INDEX; - public static final int READONLY_RECOVERY = CApi.SQLITE_READONLY_RECOVERY; - public static final int READONLY_CANTLOCK = CApi.SQLITE_READONLY_CANTLOCK; - public static final int READONLY_ROLLBACK = CApi.SQLITE_READONLY_ROLLBACK; - public static final int READONLY_DBMOVED = CApi.SQLITE_READONLY_DBMOVED; - public static final int READONLY_CANTINIT = CApi.SQLITE_READONLY_CANTINIT; - public static final int READONLY_DIRECTORY = CApi.SQLITE_READONLY_DIRECTORY; - public static final int ABORT_ROLLBACK = CApi.SQLITE_ABORT_ROLLBACK; - public static final int CONSTRAINT_CHECK = CApi.SQLITE_CONSTRAINT_CHECK; - public static final int CONSTRAINT_COMMITHOOK = CApi.SQLITE_CONSTRAINT_COMMITHOOK; - public static final int CONSTRAINT_FOREIGNKEY = CApi.SQLITE_CONSTRAINT_FOREIGNKEY; - public static final int CONSTRAINT_FUNCTION = CApi.SQLITE_CONSTRAINT_FUNCTION; - public static final int CONSTRAINT_NOTNULL = CApi.SQLITE_CONSTRAINT_NOTNULL; - public static final int CONSTRAINT_PRIMARYKEY = CApi.SQLITE_CONSTRAINT_PRIMARYKEY; - public static final int CONSTRAINT_TRIGGER = CApi.SQLITE_CONSTRAINT_TRIGGER; - public static final int CONSTRAINT_UNIQUE = CApi.SQLITE_CONSTRAINT_UNIQUE; - public static final int CONSTRAINT_VTAB = CApi.SQLITE_CONSTRAINT_VTAB; - public static final int CONSTRAINT_ROWID = CApi.SQLITE_CONSTRAINT_ROWID; - public static final int CONSTRAINT_PINNED = CApi.SQLITE_CONSTRAINT_PINNED; - public static final int CONSTRAINT_DATATYPE = CApi.SQLITE_CONSTRAINT_DATATYPE; - public static final int NOTICE_RECOVER_WAL = CApi.SQLITE_NOTICE_RECOVER_WAL; - public static final int NOTICE_RECOVER_ROLLBACK = CApi.SQLITE_NOTICE_RECOVER_ROLLBACK; - public static final int WARNING_AUTOINDEX = CApi.SQLITE_WARNING_AUTOINDEX; - public static final int AUTH_USER = CApi.SQLITE_AUTH_USER; - public static final int OK_LOAD_PERMANENTLY = CApi.SQLITE_OK_LOAD_PERMANENTLY; - - // sqlite3_open() flags - public static final int OPEN_READWRITE = CApi.SQLITE_OPEN_READWRITE; - public static final int OPEN_CREATE = CApi.SQLITE_OPEN_CREATE; - public static final int OPEN_EXRESCODE = CApi.SQLITE_OPEN_EXRESCODE; - - // transaction state - public static final int TXN_NONE = CApi.SQLITE_TXN_NONE; - public static final int TXN_READ = CApi.SQLITE_TXN_READ; - public static final int TXN_WRITE = CApi.SQLITE_TXN_WRITE; - - // sqlite3_status() ops - public static final int STATUS_MEMORY_USED = CApi.SQLITE_STATUS_MEMORY_USED; - public static final int STATUS_PAGECACHE_USED = CApi.SQLITE_STATUS_PAGECACHE_USED; - public static final int STATUS_PAGECACHE_OVERFLOW = CApi.SQLITE_STATUS_PAGECACHE_OVERFLOW; - public static final int STATUS_MALLOC_SIZE = CApi.SQLITE_STATUS_MALLOC_SIZE; - public static final int STATUS_PARSER_STACK = CApi.SQLITE_STATUS_PARSER_STACK; - public static final int STATUS_PAGECACHE_SIZE = CApi.SQLITE_STATUS_PAGECACHE_SIZE; - public static final int STATUS_MALLOC_COUNT = CApi.SQLITE_STATUS_MALLOC_COUNT; - - // sqlite3_db_status() ops - public static final int DBSTATUS_LOOKASIDE_USED = CApi.SQLITE_DBSTATUS_LOOKASIDE_USED; - public static final int DBSTATUS_CACHE_USED = CApi.SQLITE_DBSTATUS_CACHE_USED; - public static final int DBSTATUS_SCHEMA_USED = CApi.SQLITE_DBSTATUS_SCHEMA_USED; - public static final int DBSTATUS_STMT_USED = CApi.SQLITE_DBSTATUS_STMT_USED; - public static final int DBSTATUS_LOOKASIDE_HIT = CApi.SQLITE_DBSTATUS_LOOKASIDE_HIT; - public static final int DBSTATUS_LOOKASIDE_MISS_SIZE = CApi.SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE; - public static final int DBSTATUS_LOOKASIDE_MISS_FULL = CApi.SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL; - public static final int DBSTATUS_CACHE_HIT = CApi.SQLITE_DBSTATUS_CACHE_HIT; - public static final int DBSTATUS_CACHE_MISS = CApi.SQLITE_DBSTATUS_CACHE_MISS; - public static final int DBSTATUS_CACHE_WRITE = CApi.SQLITE_DBSTATUS_CACHE_WRITE; - public static final int DBSTATUS_DEFERRED_FKS = CApi.SQLITE_DBSTATUS_DEFERRED_FKS; - public static final int DBSTATUS_CACHE_USED_SHARED = CApi.SQLITE_DBSTATUS_CACHE_USED_SHARED; - public static final int DBSTATUS_CACHE_SPILL = CApi.SQLITE_DBSTATUS_CACHE_SPILL; - - // Limits - public static final int LIMIT_LENGTH = CApi.SQLITE_LIMIT_LENGTH; - public static final int LIMIT_SQL_LENGTH = CApi.SQLITE_LIMIT_SQL_LENGTH; - public static final int LIMIT_COLUMN = CApi.SQLITE_LIMIT_COLUMN; - public static final int LIMIT_EXPR_DEPTH = CApi.SQLITE_LIMIT_EXPR_DEPTH; - public static final int LIMIT_COMPOUND_SELECT = CApi.SQLITE_LIMIT_COMPOUND_SELECT; - public static final int LIMIT_VDBE_OP = CApi.SQLITE_LIMIT_VDBE_OP; - public static final int LIMIT_FUNCTION_ARG = CApi.SQLITE_LIMIT_FUNCTION_ARG; - public static final int LIMIT_ATTACHED = CApi.SQLITE_LIMIT_ATTACHED; - public static final int LIMIT_LIKE_PATTERN_LENGTH = CApi.SQLITE_LIMIT_LIKE_PATTERN_LENGTH; - public static final int LIMIT_VARIABLE_NUMBER = CApi.SQLITE_LIMIT_VARIABLE_NUMBER; - public static final int LIMIT_TRIGGER_DEPTH = CApi.SQLITE_LIMIT_TRIGGER_DEPTH; - public static final int LIMIT_WORKER_THREADS = CApi.SQLITE_LIMIT_WORKER_THREADS; - - // sqlite3_prepare_v3() flags - public static final int PREPARE_PERSISTENT = CApi.SQLITE_PREPARE_PERSISTENT; - public static final int PREPARE_NO_VTAB = CApi.SQLITE_PREPARE_NO_VTAB; - - // sqlite3_trace_v2() flags - public static final int TRACE_STMT = CApi.SQLITE_TRACE_STMT; - public static final int TRACE_PROFILE = CApi.SQLITE_TRACE_PROFILE; - public static final int TRACE_ROW = CApi.SQLITE_TRACE_ROW; - public static final int TRACE_CLOSE = CApi.SQLITE_TRACE_CLOSE; - public static final int TRACE_ALL = TRACE_STMT | TRACE_PROFILE | TRACE_ROW | TRACE_CLOSE; - - // sqlite3_db_config() ops - public static final int DBCONFIG_ENABLE_FKEY = CApi.SQLITE_DBCONFIG_ENABLE_FKEY; - public static final int DBCONFIG_ENABLE_TRIGGER = CApi.SQLITE_DBCONFIG_ENABLE_TRIGGER; - public static final int DBCONFIG_ENABLE_FTS3_TOKENIZER = CApi.SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER; - public static final int DBCONFIG_ENABLE_LOAD_EXTENSION = CApi.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION; - public static final int DBCONFIG_NO_CKPT_ON_CLOSE = CApi.SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE; - public static final int DBCONFIG_ENABLE_QPSG = CApi.SQLITE_DBCONFIG_ENABLE_QPSG; - public static final int DBCONFIG_TRIGGER_EQP = CApi.SQLITE_DBCONFIG_TRIGGER_EQP; - public static final int DBCONFIG_RESET_DATABASE = CApi.SQLITE_DBCONFIG_RESET_DATABASE; - public static final int DBCONFIG_DEFENSIVE = CApi.SQLITE_DBCONFIG_DEFENSIVE; - public static final int DBCONFIG_WRITABLE_SCHEMA = CApi.SQLITE_DBCONFIG_WRITABLE_SCHEMA; - public static final int DBCONFIG_LEGACY_ALTER_TABLE = CApi.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE; - public static final int DBCONFIG_DQS_DML = CApi.SQLITE_DBCONFIG_DQS_DML; - public static final int DBCONFIG_DQS_DDL = CApi.SQLITE_DBCONFIG_DQS_DDL; - public static final int DBCONFIG_ENABLE_VIEW = CApi.SQLITE_DBCONFIG_ENABLE_VIEW; - public static final int DBCONFIG_LEGACY_FILE_FORMAT = CApi.SQLITE_DBCONFIG_LEGACY_FILE_FORMAT; - public static final int DBCONFIG_TRUSTED_SCHEMA = CApi.SQLITE_DBCONFIG_TRUSTED_SCHEMA; - public static final int DBCONFIG_STMT_SCANSTATUS = CApi.SQLITE_DBCONFIG_STMT_SCANSTATUS; - public static final int DBCONFIG_REVERSE_SCANORDER = CApi.SQLITE_DBCONFIG_REVERSE_SCANORDER; - - // sqlite3_config() ops - public static final int CONFIG_SINGLETHREAD = CApi.SQLITE_CONFIG_SINGLETHREAD; - public static final int CONFIG_MULTITHREAD = CApi.SQLITE_CONFIG_MULTITHREAD; - public static final int CONFIG_SERIALIZED = CApi.SQLITE_CONFIG_SERIALIZED; - - // Encodings - public static final int UTF8 = CApi.SQLITE_UTF8; - public static final int UTF16 = CApi.SQLITE_UTF16; - public static final int UTF16LE = CApi.SQLITE_UTF16LE; - public static final int UTF16BE = CApi.SQLITE_UTF16BE; - /* We elide the UTF16_ALIGNED from this interface because it - is irrelevant for the Java interface. */ - - // SQL data type IDs - public static final int INTEGER = CApi.SQLITE_INTEGER; - public static final int FLOAT = CApi.SQLITE_FLOAT; - public static final int TEXT = CApi.SQLITE_TEXT; - public static final int BLOB = CApi.SQLITE_BLOB; - public static final int NULL = CApi.SQLITE_NULL; - - // Authorizer codes. - public static final int DENY = CApi.SQLITE_DENY; - public static final int IGNORE = CApi.SQLITE_IGNORE; - public static final int CREATE_INDEX = CApi.SQLITE_CREATE_INDEX; - public static final int CREATE_TABLE = CApi.SQLITE_CREATE_TABLE; - public static final int CREATE_TEMP_INDEX = CApi.SQLITE_CREATE_TEMP_INDEX; - public static final int CREATE_TEMP_TABLE = CApi.SQLITE_CREATE_TEMP_TABLE; - public static final int CREATE_TEMP_TRIGGER = CApi.SQLITE_CREATE_TEMP_TRIGGER; - public static final int CREATE_TEMP_VIEW = CApi.SQLITE_CREATE_TEMP_VIEW; - public static final int CREATE_TRIGGER = CApi.SQLITE_CREATE_TRIGGER; - public static final int CREATE_VIEW = CApi.SQLITE_CREATE_VIEW; - public static final int DELETE = CApi.SQLITE_DELETE; - public static final int DROP_INDEX = CApi.SQLITE_DROP_INDEX; - public static final int DROP_TABLE = CApi.SQLITE_DROP_TABLE; - public static final int DROP_TEMP_INDEX = CApi.SQLITE_DROP_TEMP_INDEX; - public static final int DROP_TEMP_TABLE = CApi.SQLITE_DROP_TEMP_TABLE; - public static final int DROP_TEMP_TRIGGER = CApi.SQLITE_DROP_TEMP_TRIGGER; - public static final int DROP_TEMP_VIEW = CApi.SQLITE_DROP_TEMP_VIEW; - public static final int DROP_TRIGGER = CApi.SQLITE_DROP_TRIGGER; - public static final int DROP_VIEW = CApi.SQLITE_DROP_VIEW; - public static final int INSERT = CApi.SQLITE_INSERT; - public static final int PRAGMA = CApi.SQLITE_PRAGMA; - public static final int READ = CApi.SQLITE_READ; - public static final int SELECT = CApi.SQLITE_SELECT; - public static final int TRANSACTION = CApi.SQLITE_TRANSACTION; - public static final int UPDATE = CApi.SQLITE_UPDATE; - public static final int ATTACH = CApi.SQLITE_ATTACH; - public static final int DETACH = CApi.SQLITE_DETACH; - public static final int ALTER_TABLE = CApi.SQLITE_ALTER_TABLE; - public static final int REINDEX = CApi.SQLITE_REINDEX; - public static final int ANALYZE = CApi.SQLITE_ANALYZE; - public static final int CREATE_VTABLE = CApi.SQLITE_CREATE_VTABLE; - public static final int DROP_VTABLE = CApi.SQLITE_DROP_VTABLE; - public static final int FUNCTION = CApi.SQLITE_FUNCTION; - public static final int SAVEPOINT = CApi.SQLITE_SAVEPOINT; - public static final int RECURSIVE = CApi.SQLITE_RECURSIVE; //! Used only by the open() factory functions. private Sqlite(sqlite3 db){ this.db = db; } - /** Maps org.sqlite.jni.capi.sqlite3 to Sqlite instances. */ - private static final java.util.Map nativeToWrapper - = new java.util.HashMap<>(); - - - /** - When any given thread is done using the SQLite library, calling - this will free up any native-side resources which may be - associated specifically with that thread. This is not strictly - necessary, in particular in applications which only use SQLite - from a single thread, but may help free some otherwise errant - resources. - - Calling into SQLite from a given thread after this has been - called in that thread is harmless. The library will simply start - to re-cache certain state for that thread. - - Contrariwise, failing to call this will effectively leak a small - amount of cached state for the thread, which may add up to - significant amounts if the application uses SQLite from many - threads. - - This must never be called while actively using SQLite from this - thread, e.g. from within a query loop or a callback which is - operating on behalf of the library. - */ - static void uncacheThread(){ - CApi.sqlite3_java_uncache_thread(); - } - - /** - Returns the Sqlite object associated with the given sqlite3 - object, or null if there is no such mapping. - */ - static Sqlite fromNative(sqlite3 low){ - synchronized(nativeToWrapper){ - return nativeToWrapper.get(low); - } - } - /** Returns a newly-opened db connection or throws SqliteException if opening fails. All arguments are as documented for sqlite3_open_v2(). @@ -328,172 +42,31 @@ 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 = CApi.sqlite3_open_v2(filename, out, flags, vfsName); + 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; } - final Sqlite rv = new Sqlite(n); - synchronized(nativeToWrapper){ - nativeToWrapper.put(n, rv); - } - runAutoExtensions(rv); - return rv; + 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, OPEN_READWRITE|OPEN_CREATE, null); - } - - public static String libVersion(){ - return CApi.sqlite3_libversion(); - } - - public static int libVersionNumber(){ - return CApi.sqlite3_libversion_number(); - } - - public static String libSourceId(){ - return CApi.sqlite3_sourceid(); - } - - /** - Returns the value of the native library's build-time value of the - SQLITE_THREADSAFE build option. - */ - public static int libThreadsafe(){ - return CApi.sqlite3_threadsafe(); - } - - /** - Analog to sqlite3_compileoption_get(). - */ - public static String compileOptionGet(int n){ - return CApi.sqlite3_compileoption_get(n); - } - - /** - Analog to sqlite3_compileoption_used(). - */ - public static boolean compileOptionUsed(String optName){ - return CApi.sqlite3_compileoption_used(optName); - } - - private static boolean hasNormalizeSql = - compileOptionUsed("ENABLE_NORMALIZE"); - - private static boolean hasSqlLog = - compileOptionUsed("ENABLE_SQLLOG"); - - /** - Throws UnsupportedOperationException if check is false. - flag is expected to be the name of an SQLITE_ENABLE_... - build flag. - */ - private static void checkSupported(boolean check, String flag){ - if( !check ){ - throw new UnsupportedOperationException( - "Library was built without "+flag - ); - } - } - - /** - Analog to sqlite3_complete(). - */ - public static boolean isCompleteStatement(String sql){ - switch(CApi.sqlite3_complete(sql)){ - case 0: return false; - case CApi.SQLITE_MISUSE: - throw new IllegalArgumentException("Input may not be null."); - case CApi.SQLITE_NOMEM: - throw new OutOfMemoryError(); - default: - return true; - } - } - - public static int keywordCount(){ - return CApi.sqlite3_keyword_count(); - } - - public static boolean keywordCheck(String word){ - return CApi.sqlite3_keyword_check(word); - } - - public static String keywordName(int index){ - return CApi.sqlite3_keyword_name(index); - } - - public static boolean strglob(String glob, String txt){ - return 0==CApi.sqlite3_strglob(glob, txt); - } - - public static boolean strlike(String glob, String txt, char escChar){ - return 0==CApi.sqlite3_strlike(glob, txt, escChar); - } - - /** - Output object for use with status() and libStatus(). - */ - public static final class Status { - /** The current value for the requested status() or libStatus() metric. */ - long current; - /** The peak value for the requested status() or libStatus() metric. */ - long peak; - }; - - /** - As per sqlite3_status64(), but returns its current and high-water - results as a Status object. Throws if the first argument is - not one of the STATUS_... constants. - */ - public static Status libStatus(int op, boolean resetStats){ - org.sqlite.jni.capi.OutputPointer.Int64 pCurrent = - new org.sqlite.jni.capi.OutputPointer.Int64(); - org.sqlite.jni.capi.OutputPointer.Int64 pHighwater = - new org.sqlite.jni.capi.OutputPointer.Int64(); - checkRcStatic( CApi.sqlite3_status64(op, pCurrent, pHighwater, resetStats) ); - final Status s = new Status(); - s.current = pCurrent.value; - s.peak = pHighwater.value; - return s; - } - - /** - As per sqlite3_db_status(), but returns its current and - high-water results as a Status object. Throws if the first - argument is not one of the DBSTATUS_... constants or on any other - misuse. - */ - public Status status(int op, boolean resetStats){ - org.sqlite.jni.capi.OutputPointer.Int32 pCurrent = - new org.sqlite.jni.capi.OutputPointer.Int32(); - org.sqlite.jni.capi.OutputPointer.Int32 pHighwater = - new org.sqlite.jni.capi.OutputPointer.Int32(); - checkRc( CApi.sqlite3_db_status(thisDb(), op, pCurrent, pHighwater, resetStats) ); - final Status s = new Status(); - s.current = pCurrent.value; - s.peak = pHighwater.value; - return s; + return open(filename, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, null); } @Override public void close(){ if(null!=this.db){ - synchronized(nativeToWrapper){ - nativeToWrapper.remove(this.db); - } this.db.close(); this.db = null; } } @@ -502,11 +75,11 @@ this instance has been closed. This is very specifically not public. */ sqlite3 nativeHandle(){ return this.db; } - private sqlite3 thisDb(){ + private sqlite3 affirmOpen(){ if( null==db || 0==db.getNativePointer() ){ throw new IllegalArgumentException("This database instance is closed."); } return this.db; } @@ -513,558 +86,52 @@ // private byte[] stringToUtf8(String s){ // return s==null ? null : s.getBytes(StandardCharsets.UTF_8); // } - /** - If rc!=0, throws an SqliteException. If this db is currently - opened and has non-0 sqlite3_errcode(), the error state is - extracted from it, else only the string form of rc is used. It is - the caller's responsibility to filter out non-error codes such as - SQLITE_ROW and SQLITE_DONE before calling this. - - As a special case, if rc is SQLITE_NOMEM, an OutOfMemoryError is - thrown. - */ - private void checkRc(int rc){ - if( 0!=rc ){ - if( CApi.SQLITE_NOMEM==rc ){ - throw new OutOfMemoryError(); - }else if( null==db || 0==CApi.sqlite3_errcode(db) ){ - throw new SqliteException(rc); - }else{ - throw new SqliteException(db); - } - } - } - - /** - Like checkRc() but behaves as if that function were - called with a null db object. - */ - private static void checkRcStatic(int rc){ - if( 0!=rc ){ - if( CApi.SQLITE_NOMEM==rc ){ - throw new OutOfMemoryError(); - }else{ - throw new SqliteException(rc); - } - } - } - - /** - Toggles the use of extended result codes on or off. By default - they are turned off, but they can be enabled by default by - including the OPEN_EXRESCODE flag when opening a database. - - Because this API reports db-side errors using exceptions, - enabling this may change the values returned by - SqliteException.errcode(). - */ - public void useExtendedResultCodes(boolean on){ - checkRc( CApi.sqlite3_extended_result_codes(thisDb(), on) ); - } - - /** - Analog to sqlite3_prepare_v3(), this prepares the first SQL - statement from the given input string and returns it as a - Stmt. It throws an SqliteException if preparation fails or an - IllegalArgumentException if the input is empty (e.g. contains - only comments or whitespace). - - The first argument must be SQL input in UTF-8 encoding. - - prepFlags must be 0 or a bitmask of the PREPARE_... constants. - - For processing multiple statements from a single input, use - prepareMulti(). - - Design note: though the C-level API succeeds with a null - statement object for empty inputs, that approach is cumbersome to - use in higher-level APIs because every prepared statement has to - be checked for null before using it. - */ - public Stmt prepare(byte utf8Sql[], int prepFlags){ - final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt(); - final int rc = CApi.sqlite3_prepare_v3(thisDb(), utf8Sql, prepFlags, out); - checkRc(rc); - final sqlite3_stmt q = out.take(); - if( null==q ){ - /* The C-level API treats input which is devoid of SQL - statements (e.g. all comments or an empty string) as success - but returns a NULL sqlite3_stmt object. In higher-level APIs, - wrapping a "successful NULL" object that way is tedious to - use because it forces clients and/or wrapper-level code to - check for that unusual case. In practice, higher-level - bindings are generally better-served by treating empty SQL - input as an error. */ - throw new IllegalArgumentException("Input contains no SQL statements."); - } - return new Stmt(this, q); - } - - /** - Equivalent to prepare(X, prepFlags), where X is - sql.getBytes(StandardCharsets.UTF_8). - */ - public Stmt prepare(String sql, int prepFlags){ - return prepare( sql.getBytes(StandardCharsets.UTF_8), prepFlags ); - } - - /** - Equivalent to prepare(sql, 0). - */ - public Stmt prepare(String sql){ - return prepare(sql, 0); - } - - - /** - Callback type for use with prepareMulti(). - */ - public interface PrepareMulti { - /** - Gets passed a Stmt which it may handle in arbitrary ways. - Ownership of st is passed to this function. It must throw on - error. - */ - void call(Sqlite.Stmt st); - } - - /** - A PrepareMulti implementation which calls another PrepareMulti - object and then finalizes its statement. - */ - public static class PrepareMultiFinalize implements PrepareMulti { - private final PrepareMulti pm; - /** - Proxies the given PrepareMulti via this object's call() method. - */ - public PrepareMultiFinalize(PrepareMulti proxy){ - this.pm = proxy; - } - /** - Passes st to the call() method of the object this one proxies, - then finalizes st, propagating any exceptions from call() after - finalizing st. - */ - @Override public void call(Stmt st){ - try{ pm.call(st); } - finally{ st.finalizeStmt(); } - } - } - - /** - Equivalent to prepareMulti(sql,0,visitor). - */ - public void prepareMulti(String sql, PrepareMulti visitor){ - prepareMulti( sql, 0, visitor ); - } - - /** - Equivallent to prepareMulti(X,prepFlags,visitor), where X is - sql.getBytes(StandardCharsets.UTF_8). - */ - public void prepareMulti(String sql, int prepFlags, PrepareMulti visitor){ - prepareMulti(sql.getBytes(StandardCharsets.UTF_8), prepFlags, visitor); - } - - /** - A variant of prepare() which can handle multiple SQL statements - in a single input string. For each statement in the given string, - the statement is passed to visitor.call() a single time, passing - ownership of the statement to that function. This function does - not step() or close() statements - those operations are left to - caller or the visitor function. - - Unlike prepare(), this function does not fail if the input - contains only whitespace or SQL comments. In that case it is up - to the caller to arrange for that to be an error (if desired). - - PrepareMultiFinalize offers a proxy which finalizes each - statement after it is passed to another client-defined visitor. - - Be aware that certain legal SQL constructs may fail in the - preparation phase, before the corresponding statement can be - stepped. Most notably, authorizer checks which disallow access to - something in a statement behave that way. - */ - public void prepareMulti(byte sqlUtf8[], int prepFlags, PrepareMulti visitor){ - int pos = 0, n = 1; - byte[] sqlChunk = sqlUtf8; - final org.sqlite.jni.capi.OutputPointer.sqlite3_stmt outStmt = - new org.sqlite.jni.capi.OutputPointer.sqlite3_stmt(); - final org.sqlite.jni.capi.OutputPointer.Int32 oTail = - new org.sqlite.jni.capi.OutputPointer.Int32(); - while( pos < sqlChunk.length ){ - sqlite3_stmt stmt = null; - if( pos>0 ){ - sqlChunk = java.util.Arrays.copyOfRange(sqlChunk, pos, sqlChunk.length); - } - if( 0==sqlChunk.length ) break; - checkRc( - CApi.sqlite3_prepare_v3(db, sqlChunk, prepFlags, outStmt, oTail) - ); - pos = oTail.value; - stmt = outStmt.take(); - if( null==stmt ){ - /* empty statement, e.g. only comments or whitespace, was parsed. */ - continue; - } - visitor.call(new Stmt(this, stmt)); - } - } - - public void createFunction(String name, int nArg, int eTextRep, ScalarFunction f){ - int rc = CApi.sqlite3_create_function(thisDb(), 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(thisDb(), 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); - } - - public void createFunction(String name, int nArg, int eTextRep, WindowFunction f){ - int rc = CApi.sqlite3_create_function(thisDb(), name, nArg, eTextRep, - new SqlFunction.WindowAdapter(f)); - if( 0!=rc ) throw new SqliteException(db); - } - - public void createFunction(String name, int nArg, WindowFunction f){ - this.createFunction(name, nArg, CApi.SQLITE_UTF8, f); - } - - public long changes(){ - return CApi.sqlite3_changes64(thisDb()); - } - - public long totalChanges(){ - return CApi.sqlite3_total_changes64(thisDb()); - } - - public long lastInsertRowId(){ - return CApi.sqlite3_last_insert_rowid(thisDb()); - } - - public void setLastInsertRowId(long rowId){ - CApi.sqlite3_set_last_insert_rowid(thisDb(), rowId); - } - - public void interrupt(){ - CApi.sqlite3_interrupt(thisDb()); - } - - public boolean isInterrupted(){ - return CApi.sqlite3_is_interrupted(thisDb()); - } - - public boolean isAutoCommit(){ - return CApi.sqlite3_get_autocommit(thisDb()); - } - - /** - Analog to sqlite3_txn_state(). Returns one of TXN_NONE, TXN_READ, - or TXN_WRITE to denote this database's current transaction state - for the given schema name (or the most restrictive state of any - schema if zSchema is null). - */ - public int transactionState(String zSchema){ - return CApi.sqlite3_txn_state(thisDb(), zSchema); - } - - /** - Analog to sqlite3_db_name(). Returns null if passed an unknown - index. - */ - public String dbName(int dbNdx){ - return CApi.sqlite3_db_name(thisDb(), dbNdx); - } - - /** - Analog to sqlite3_db_filename(). Returns null if passed an - unknown db name. - */ - public String dbFileName(String dbName){ - return CApi.sqlite3_db_filename(thisDb(), dbName); - } - - /** - Analog to sqlite3_db_config() for the call forms which take one - of the boolean-type db configuration flags (namely the - DBCONFIG_... constants defined in this class). On success it - returns the result of that underlying call. Throws on error. - */ - public boolean dbConfig(int op, boolean on){ - org.sqlite.jni.capi.OutputPointer.Int32 pOut = - new org.sqlite.jni.capi.OutputPointer.Int32(); - checkRc( CApi.sqlite3_db_config(thisDb(), op, on ? 1 : 0, pOut) ); - return pOut.get()!=0; - } - - /** - Analog to the variant of sqlite3_db_config() for configuring the - SQLITE_DBCONFIG_MAINDBNAME option. Throws on error. - */ - public void setMainDbName(String name){ - checkRc( - CApi.sqlite3_db_config(thisDb(), CApi.SQLITE_DBCONFIG_MAINDBNAME, - name) - ); - } - - /** - Analog to sqlite3_db_readonly() but throws an SqliteException - with result code SQLITE_NOTFOUND if given an unknown database - name. - */ - public boolean readOnly(String dbName){ - final int rc = CApi.sqlite3_db_readonly(thisDb(), dbName); - if( 0==rc ) return false; - else if( rc>0 ) return true; - throw new SqliteException(CApi.SQLITE_NOTFOUND); - } - - /** - Analog to sqlite3_db_release_memory(). - */ - public void releaseMemory(){ - CApi.sqlite3_db_release_memory(thisDb()); - } - - /** - Analog to sqlite3_release_memory(). - */ - public static int libReleaseMemory(int n){ - return CApi.sqlite3_release_memory(n); - } - - /** - Analog to sqlite3_limit(). limitId must be one of the - LIMIT_... constants. - - Returns the old limit for the given option. If newLimit is - negative, it returns the old limit without modifying the limit. - - If sqlite3_limit() returns a negative value, this function throws - an SqliteException with the SQLITE_RANGE result code but no - further error info (because that case does not qualify as a - db-level error). Such errors may indicate an invalid argument - value or an invalid range for newLimit (the underlying function - does not differentiate between those). - */ - public int limit(int limitId, int newLimit){ - final int rc = CApi.sqlite3_limit(thisDb(), limitId, newLimit); - if( rc<0 ){ - throw new SqliteException(CApi.SQLITE_RANGE); - } - return rc; - } - - /** - Analog to sqlite3_errstr(). - */ - static String errstr(int resultCode){ - return CApi.sqlite3_errstr(resultCode); - } - - /** - A wrapper object for use with tableColumnMetadata(). They are - created and populated only via that interface. - */ - public final class TableColumnMetadata { - Boolean pNotNull = null; - Boolean pPrimaryKey = null; - Boolean pAutoinc = null; - String pzCollSeq = null; - String pzDataType = null; - - private TableColumnMetadata(){} - - public String getDataType(){ return pzDataType; } - public String getCollation(){ return pzCollSeq; } - public boolean isNotNull(){ return pNotNull; } - public boolean isPrimaryKey(){ return pPrimaryKey; } - public boolean isAutoincrement(){ return pAutoinc; } - } - - /** - Returns data about a database, table, and (optionally) column - (which may be null), as per sqlite3_table_column_metadata(). - Throws if passed invalid arguments, else returns the result as a - new TableColumnMetadata object. - */ - TableColumnMetadata tableColumnMetadata( - String zDbName, String zTableName, String zColumnName - ){ - org.sqlite.jni.capi.OutputPointer.String pzDataType - = new org.sqlite.jni.capi.OutputPointer.String(); - org.sqlite.jni.capi.OutputPointer.String pzCollSeq - = new org.sqlite.jni.capi.OutputPointer.String(); - org.sqlite.jni.capi.OutputPointer.Bool pNotNull - = new org.sqlite.jni.capi.OutputPointer.Bool(); - org.sqlite.jni.capi.OutputPointer.Bool pPrimaryKey - = new org.sqlite.jni.capi.OutputPointer.Bool(); - org.sqlite.jni.capi.OutputPointer.Bool pAutoinc - = new org.sqlite.jni.capi.OutputPointer.Bool(); - final int rc = CApi.sqlite3_table_column_metadata( - thisDb(), zDbName, zTableName, zColumnName, - pzDataType, pzCollSeq, pNotNull, pPrimaryKey, pAutoinc - ); - checkRc(rc); - TableColumnMetadata rv = new TableColumnMetadata(); - rv.pzDataType = pzDataType.value; - rv.pzCollSeq = pzCollSeq.value; - rv.pNotNull = pNotNull.value; - rv.pPrimaryKey = pPrimaryKey.value; - rv.pAutoinc = pAutoinc.value; - return rv; - } - - public interface TraceCallback { - /** - 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 - Sqlite or Sqlite.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 Sqlite.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. - */ - void call(int traceFlag, Object pNative, Object pX); - } - - /** - Analog to sqlite3_trace_v2(). traceMask must be a mask of the - TRACE_... constants. Pass a null callback to remove tracing. - - Throws on error. - */ - public void trace(int traceMask, TraceCallback callback){ - final Sqlite self = this; - final org.sqlite.jni.capi.TraceV2Callback tc = - (null==callback) ? null : new org.sqlite.jni.capi.TraceV2Callback(){ - @SuppressWarnings("unchecked") - @Override public int call(int flag, Object pNative, Object pX){ - switch(flag){ - case TRACE_ROW: - case TRACE_PROFILE: - case TRACE_STMT: - callback.call(flag, Sqlite.Stmt.fromNative((sqlite3_stmt)pNative), pX); - break; - case TRACE_CLOSE: - callback.call(flag, self, pX); - break; - } - return 0; - } - }; - checkRc( CApi.sqlite3_trace_v2(thisDb(), traceMask, tc) ); - }; + 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 static final class Stmt implements AutoCloseable { + 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; - synchronized(nativeToWrapper){ - nativeToWrapper.put(this.stmt, this); - } } sqlite3_stmt nativeHandle(){ return stmt; } - /** Maps org.sqlite.jni.capi.sqlite3_stmt to Stmt instances. */ - private static final java.util.Map nativeToWrapper - = new java.util.HashMap<>(); - - /** - Returns the Stmt object associated with the given sqlite3_stmt - object, or null if there is no such mapping. - */ - static Stmt fromNative(sqlite3_stmt low){ - synchronized(nativeToWrapper){ - return nativeToWrapper.get(low); - } - } - - /** - If this statement is still opened, its low-level handle is - returned, else an IllegalArgumentException is thrown. - */ - private sqlite3_stmt thisStmt(){ + private sqlite3_stmt affirmOpen(){ if( null==stmt || 0==stmt.getNativePointer() ){ throw new IllegalArgumentException("This Stmt has been finalized."); } return stmt; } - /** Throws if n is out of range of this statement's result column - count. Intended to be used by the columnXyz() methods. */ - private sqlite3_stmt checkColIndex(int n){ - if(n<0 || n>=columnCount()){ - throw new IllegalArgumentException("Column index "+n+" is out of range."); - } - return thisStmt(); - } - /** 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, but the result code is an indication that something - went wrong in a prior call into the statement's API, as - documented for sqlite3_finalize(). + finalized. */ public int finalizeStmt(){ int rc = 0; if( null!=stmt ){ - synchronized(nativeToWrapper){ - nativeToWrapper.remove(this.stmt); - } - CApi.sqlite3_finalize(stmt); + sqlite3_finalize(stmt); stmt = null; - _db = null; } return rc; } @Override public void close(){ @@ -1071,921 +138,81 @@ finalizeStmt(); } /** Throws if rc is any value other than 0, SQLITE_ROW, or - SQLITE_DONE, else returns rc. Error state for the exception is - extracted from this statement object (if it's opened) or the - string form of rc. - */ - private int checkRc(int rc){ - switch(rc){ - case 0: - case CApi.SQLITE_ROW: - case CApi.SQLITE_DONE: return rc; - default: - if( null==stmt ) throw new SqliteException(rc); - else throw new SqliteException(this); - } - } - - /** - Works like sqlite3_step() but returns true for SQLITE_ROW, - false for SQLITE_DONE, and throws SqliteException for any other - result. - */ - public boolean step(){ - switch(checkRc(CApi.sqlite3_step(thisStmt()))){ - case CApi.SQLITE_ROW: return true; - case CApi.SQLITE_DONE: return false; - default: - throw new IllegalStateException( - "This \"cannot happen\": all possible result codes were checked already." - ); - } - } - - /** - Works like sqlite3_step(), returning the same result codes as - that function unless throwOnError is true, in which case it - will throw an SqliteException for any result codes other than - Sqlite.ROW or Sqlite.DONE. - - The utility of this overload over the no-argument one is the - ability to handle BUSY and LOCKED errors more easily. - */ - public int step(boolean throwOnError){ - final int rc = (null==stmt) - ? Sqlite.MISUSE - : CApi.sqlite3_step(stmt); - return throwOnError ? checkRc(rc) : rc; - } - - /** - Returns the Sqlite which prepared this statement, or null if - this statement has been finalized. - */ - public Sqlite getDb(){ return this._db; } + 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(CApi.sqlite3_reset(thisStmt())); - } - - public boolean isBusy(){ - return CApi.sqlite3_stmt_busy(thisStmt()); - } - - public boolean isReadOnly(){ - return CApi.sqlite3_stmt_readonly(thisStmt()); - } - - public String sql(){ - return CApi.sqlite3_sql(thisStmt()); - } - - public String expandedSql(){ - return CApi.sqlite3_expanded_sql(thisStmt()); - } - - /** - Analog to sqlite3_stmt_explain() but throws if op is invalid. - */ - public void explain(int op){ - checkRc(CApi.sqlite3_stmt_explain(thisStmt(), op)); - } - - /** - Analog to sqlite3_stmt_isexplain(). - */ - public int isExplain(){ - return CApi.sqlite3_stmt_isexplain(thisStmt()); - } - - /** - Analog to sqlite3_normalized_sql(), but throws - UnsupportedOperationException if the library was built without - the SQLITE_ENABLE_NORMALIZE flag. - */ - public String normalizedSql(){ - Sqlite.checkSupported(hasNormalizeSql, "SQLITE_ENABLE_NORMALIZE"); - return CApi.sqlite3_normalized_sql(thisStmt()); - } - - public void clearBindings(){ - CApi.sqlite3_clear_bindings( thisStmt() ); - } - public void bindInt(int ndx, int val){ - checkRc(CApi.sqlite3_bind_int(thisStmt(), ndx, val)); - } - public void bindInt64(int ndx, long val){ - checkRc(CApi.sqlite3_bind_int64(thisStmt(), ndx, val)); - } - public void bindDouble(int ndx, double val){ - checkRc(CApi.sqlite3_bind_double(thisStmt(), ndx, val)); - } - public void bindObject(int ndx, Object o){ - checkRc(CApi.sqlite3_bind_java_object(thisStmt(), ndx, o)); - } - public void bindNull(int ndx){ - checkRc(CApi.sqlite3_bind_null(thisStmt(), ndx)); - } - public int bindParameterCount(){ - return CApi.sqlite3_bind_parameter_count(thisStmt()); - } - public int bindParameterIndex(String paramName){ - return CApi.sqlite3_bind_parameter_index(thisStmt(), paramName); - } - public String bindParameterName(int ndx){ - return CApi.sqlite3_bind_parameter_name(thisStmt(), ndx); - } - public void bindText(int ndx, byte[] utf8){ - checkRc(CApi.sqlite3_bind_text(thisStmt(), ndx, utf8)); - } - public void bindText(int ndx, String asUtf8){ - checkRc(CApi.sqlite3_bind_text(thisStmt(), ndx, asUtf8)); - } - public void bindText16(int ndx, byte[] utf16){ - checkRc(CApi.sqlite3_bind_text16(thisStmt(), ndx, utf16)); - } - public void bindText16(int ndx, String asUtf16){ - checkRc(CApi.sqlite3_bind_text16(thisStmt(), ndx, asUtf16)); - } - public void bindZeroBlob(int ndx, int n){ - checkRc(CApi.sqlite3_bind_zeroblob(thisStmt(), ndx, n)); - } - public void bindBlob(int ndx, byte[] bytes){ - checkRc(CApi.sqlite3_bind_blob(thisStmt(), ndx, bytes)); - } - - public byte[] columnBlob(int ndx){ - return CApi.sqlite3_column_blob( checkColIndex(ndx), ndx ); - } - public byte[] columnText(int ndx){ - return CApi.sqlite3_column_text( checkColIndex(ndx), ndx ); - } - public String columnText16(int ndx){ - return CApi.sqlite3_column_text16( checkColIndex(ndx), ndx ); - } - public int columnBytes(int ndx){ - return CApi.sqlite3_column_bytes( checkColIndex(ndx), ndx ); - } - public int columnBytes16(int ndx){ - return CApi.sqlite3_column_bytes16( checkColIndex(ndx), ndx ); - } - public int columnInt(int ndx){ - return CApi.sqlite3_column_int( checkColIndex(ndx), ndx ); - } - public long columnInt64(int ndx){ - return CApi.sqlite3_column_int64( checkColIndex(ndx), ndx ); - } - public double columnDouble(int ndx){ - return CApi.sqlite3_column_double( checkColIndex(ndx), ndx ); - } - public int columnType(int ndx){ - return CApi.sqlite3_column_type( checkColIndex(ndx), ndx ); - } - public String columnDeclType(int ndx){ - return CApi.sqlite3_column_decltype( checkColIndex(ndx), ndx ); - } - /** - Analog to sqlite3_column_count() but throws if this statement - has been finalized. - */ - public int columnCount(){ - /* We cannot reliably cache the column count in a class - member because an ALTER TABLE from a separate statement - can invalidate that count and we have no way, short of - installing a COMMIT handler or the like, of knowing when - to re-read it. We cannot install such a handler without - interfering with a client's ability to do so. */ - return CApi.sqlite3_column_count(thisStmt()); - } - public int columnDataCount(){ - return CApi.sqlite3_data_count( thisStmt() ); - } - public Object columnObject(int ndx){ - return CApi.sqlite3_column_java_object( checkColIndex(ndx), ndx ); - } - public T columnObject(int ndx, Class type){ - return CApi.sqlite3_column_java_object( checkColIndex(ndx), ndx, type ); - } - public String columnName(int ndx){ - return CApi.sqlite3_column_name( checkColIndex(ndx), ndx ); - } - public String columnDatabaseName(int ndx){ - return CApi.sqlite3_column_database_name( checkColIndex(ndx), ndx ); - } - public String columnOriginName(int ndx){ - return CApi.sqlite3_column_origin_name( checkColIndex(ndx), ndx ); - } - public String columnTableName(int ndx){ - return CApi.sqlite3_column_table_name( checkColIndex(ndx), ndx ); - } - } /* Stmt class */ - - /** - Interface for auto-extensions, as per the - sqlite3_auto_extension() API. - - Design note: the chicken/egg timing of auto-extension execution - requires that this feature be entirely re-implemented in Java - because the C-level API has no access to the Sqlite type so - cannot pass on an object of that type while the database is being - opened. One side effect of this reimplementation is that this - class's list of auto-extensions is 100% independent of the - C-level list so, e.g., clearAutoExtensions() will have no effect - on auto-extensions added via the C-level API and databases opened - from that level of API will not be passed to this level's - AutoExtension instances. - */ - public interface AutoExtension { - public void call(Sqlite db); - } - - private static final java.util.Set autoExtensions = - new java.util.LinkedHashSet<>(); - - /** - Passes db to all auto-extensions. If any one of them throws, - db.close() is called before the exception is propagated. - */ - private static void runAutoExtensions(Sqlite db){ - AutoExtension list[]; - synchronized(autoExtensions){ - /* Avoid that modifications to the AutoExtension list from within - auto-extensions affect this execution of this list. */ - list = autoExtensions.toArray(new AutoExtension[0]); - } - try { - for( AutoExtension ax : list ) ax.call(db); - }catch(Exception e){ - db.close(); - throw e; - } - } - - /** - Analog to sqlite3_auto_extension(), adds the given object to the - list of auto-extensions if it is not already in that list. The - given object will be run as part of Sqlite.open(), and passed the - being-opened database. If the extension throws then open() will - fail. - - This API does not guaranty whether or not manipulations made to - the auto-extension list from within auto-extension callbacks will - affect the current traversal of the auto-extension list. Whether - or not they do is unspecified and subject to change between - versions. e.g. if an AutoExtension calls addAutoExtension(), - whether or not the new extension will be run on the being-opened - database is undefined. - - Note that calling Sqlite.open() from an auto-extension will - necessarily result in recursion loop and (eventually) a stack - overflow. - */ - public static void addAutoExtension( AutoExtension e ){ - if( null==e ){ - throw new IllegalArgumentException("AutoExtension may not be null."); - } - synchronized(autoExtensions){ - autoExtensions.add(e); - } - } - - /** - Removes the given object from the auto-extension list if it is in - that list, otherwise this has no side-effects beyond briefly - locking that list. - */ - public static void removeAutoExtension( AutoExtension e ){ - synchronized(autoExtensions){ - autoExtensions.remove(e); - } - } - - /** - Removes all auto-extensions which were added via addAutoExtension(). - */ - public static void clearAutoExtensions(){ - synchronized(autoExtensions){ - autoExtensions.clear(); - } - } - - /** - Encapsulates state related to the sqlite3 backup API. Use - Sqlite.initBackup() to create new instances. - */ - public static final class Backup implements AutoCloseable { - private sqlite3_backup b = null; - private Sqlite dbTo = null; - private Sqlite dbFrom = null; - - Backup(Sqlite dbDest, String schemaDest,Sqlite dbSrc, String schemaSrc){ - this.dbTo = dbDest; - this.dbFrom = dbSrc; - b = CApi.sqlite3_backup_init(dbDest.nativeHandle(), schemaDest, - dbSrc.nativeHandle(), schemaSrc); - if(null==b) toss(); - } - - private void toss(){ - int rc = CApi.sqlite3_errcode(dbTo.nativeHandle()); - if(0!=rc) throw new SqliteException(dbTo); - rc = CApi.sqlite3_errcode(dbFrom.nativeHandle()); - if(0!=rc) throw new SqliteException(dbFrom); - throw new SqliteException(CApi.SQLITE_ERROR); - } - - private sqlite3_backup getNative(){ - if( null==b ) throw new IllegalStateException("This Backup is already closed."); - return b; - } - /** - If this backup is still active, this completes the backup and - frees its native resources, otherwise it this is a no-op. - */ - public void finish(){ - if( null!=b ){ - CApi.sqlite3_backup_finish(b); - b = null; - dbTo = null; - dbFrom = null; - } - } - - /** Equivalent to finish(). */ - @Override public void close(){ - this.finish(); - } - - /** - Analog to sqlite3_backup_step(). Returns 0 if stepping succeeds - or, Sqlite.DONE if the end is reached, Sqlite.BUSY if one of - the databases is busy, Sqlite.LOCKED if one of the databases is - locked, and throws for any other result code or if this object - has been closed. Note that BUSY and LOCKED are not necessarily - permanent errors, so do not trigger an exception. - */ - public int step(int pageCount){ - final int rc = CApi.sqlite3_backup_step(getNative(), pageCount); - switch(rc){ - case 0: - case Sqlite.DONE: - case Sqlite.BUSY: - case Sqlite.LOCKED: - return rc; - default: - toss(); - return CApi.SQLITE_ERROR/*not reached*/; - } - } - - /** - Analog to sqlite3_backup_pagecount(). - */ - public int pageCount(){ - return CApi.sqlite3_backup_pagecount(getNative()); - } - - /** - Analog to sqlite3_backup_remaining(). - */ - public int remaining(){ - return CApi.sqlite3_backup_remaining(getNative()); - } - } - - /** - Analog to sqlite3_backup_init(). If schemaSrc is null, "main" is - assumed. Throws if either this db or dbSrc (the source db) are - not opened, if either of schemaDest or schemaSrc are null, or if - the underlying call to sqlite3_backup_init() fails. - - The returned object must eventually be cleaned up by either - arranging for it to be auto-closed (e.g. using - try-with-resources) or by calling its finish() method. - */ - public Backup initBackup(String schemaDest, Sqlite dbSrc, String schemaSrc){ - thisDb(); - dbSrc.thisDb(); - if( null==schemaSrc || null==schemaDest ){ - throw new IllegalArgumentException( - "Neither the source nor destination schema name may be null." - ); - } - return new Backup(this, schemaDest, dbSrc, schemaSrc); - } - - - /** - Callback type for use with createCollation(). - */ - public interface Collation { - /** - Called by the SQLite core to compare inputs. Implementations - must compare its two arguments using memcmp(3) semantics. - - Warning: the SQLite core has no mechanism for reporting errors - from custom collations and its workflow does not accommodate - propagation of exceptions from callbacks. Any exceptions thrown - from collations will be silently supressed and sorting results - will be unpredictable. - */ - int call(byte[] lhs, byte[] rhs); - } - - /** - Analog to sqlite3_create_collation(). - - Throws if name is null or empty, c is null, or the encoding flag - is invalid. The encoding must be one of the UTF8, UTF16, UTF16LE, - or UTF16BE constants. - */ - public void createCollation(String name, int encoding, Collation c){ - thisDb(); - if( null==name || 0==name.length()){ - throw new IllegalArgumentException("Collation name may not be null or empty."); - } - if( null==c ){ - throw new IllegalArgumentException("Collation may not be null."); - } - switch(encoding){ - case UTF8: - case UTF16: - case UTF16LE: - case UTF16BE: - break; - default: - throw new IllegalArgumentException("Invalid Collation encoding."); - } - checkRc( - CApi.sqlite3_create_collation( - thisDb(), name, encoding, new org.sqlite.jni.capi.CollationCallback(){ - @Override public int call(byte[] lhs, byte[] rhs){ - try{return c.call(lhs, rhs);} - catch(Exception e){return 0;} - } - @Override public void xDestroy(){} - } - ) - ); - } - - /** - Callback for use with onCollationNeeded(). - */ - public interface CollationNeeded { - /** - Must behave as documented for the callback for - sqlite3_collation_needed(). - - Warning: the C API has no mechanism for reporting or - propagating errors from this callback, so any exceptions it - throws are suppressed. - */ - void call(Sqlite db, int encoding, String collationName); - } - - /** - Sets up the given object to be called by the SQLite core when it - encounters a collation name which it does not know. Pass a null - object to disconnect the object from the core. This replaces any - existing collation-needed loader, or is a no-op if the given - object is already registered. Throws if registering the loader - fails. - */ - public void onCollationNeeded( CollationNeeded cn ){ - org.sqlite.jni.capi.CollationNeededCallback cnc = null; - if( null!=cn ){ - cnc = new org.sqlite.jni.capi.CollationNeededCallback(){ - @Override public void call(sqlite3 db, int encoding, String collationName){ - final Sqlite xdb = Sqlite.fromNative(db); - if(null!=xdb) cn.call(xdb, encoding, collationName); - } - }; - } - checkRc( CApi.sqlite3_collation_needed(thisDb(), cnc) ); - } - - /** - Callback for use with busyHandler(). - */ - public interface BusyHandler { - /** - Must function as documented for the C-level - sqlite3_busy_handler() callback argument, minus the (void*) - argument the C-level function requires. - - If this function throws, it is translated to a database-level - error. - */ - int call(int n); - } - - /** - Analog to sqlite3_busy_timeout(). - */ - public void setBusyTimeout(int ms){ - checkRc(CApi.sqlite3_busy_timeout(thisDb(), ms)); - } - - /** - Analog to sqlite3_busy_handler(). If b is null then any - current handler is cleared. - */ - public void setBusyHandler( BusyHandler b ){ - org.sqlite.jni.capi.BusyHandlerCallback bhc = null; - if( null!=b ){ - bhc = new org.sqlite.jni.capi.BusyHandlerCallback(){ - @Override public int call(int n){ - return b.call(n); - } - }; - } - checkRc( CApi.sqlite3_busy_handler(thisDb(), bhc) ); - } - - public interface CommitHook { - /** - Must behave as documented for the C-level sqlite3_commit_hook() - callback. If it throws, the exception is translated into - a db-level error. - */ - int call(); - } - - /** - A level of indirection to permit setCommitHook() to have similar - semantics as the C API, returning the previous hook. The caveat - is that if the low-level API is used to install a hook, it will - have a different hook type than Sqlite.CommitHook so - setCommitHook() will return null instead of that object. - */ - private static class CommitHookProxy - implements org.sqlite.jni.capi.CommitHookCallback { - final CommitHook commitHook; - CommitHookProxy(CommitHook ch){ - this.commitHook = ch; - } - @Override public int call(){ - return commitHook.call(); - } - } - - /** - Analog to sqlite3_commit_hook(). Returns the previous hook, if - any (else null). Throws if this db is closed. - - Minor caveat: if a commit hook is set on this object's underlying - db handle using the lower-level SQLite API, this function may - return null when replacing it, despite there being a hook, - because it will have a different callback type. So long as the - handle is only manipulated via the high-level API, this caveat - does not apply. - */ - public CommitHook setCommitHook( CommitHook c ){ - CommitHookProxy chp = null; - if( null!=c ){ - chp = new CommitHookProxy(c); - } - final org.sqlite.jni.capi.CommitHookCallback rv = - CApi.sqlite3_commit_hook(thisDb(), chp); - return (rv instanceof CommitHookProxy) - ? ((CommitHookProxy)rv).commitHook - : null; - } - - - public interface RollbackHook { - /** - Must behave as documented for the C-level sqlite3_rollback_hook() - callback. If it throws, the exception is translated into - a db-level error. - */ - void call(); - } - - /** - A level of indirection to permit setRollbackHook() to have similar - semantics as the C API, returning the previous hook. The caveat - is that if the low-level API is used to install a hook, it will - have a different hook type than Sqlite.RollbackHook so - setRollbackHook() will return null instead of that object. - */ - private static class RollbackHookProxy - implements org.sqlite.jni.capi.RollbackHookCallback { - final RollbackHook rollbackHook; - RollbackHookProxy(RollbackHook ch){ - this.rollbackHook = ch; - } - @Override public void call(){rollbackHook.call();} - } - - /** - Analog to sqlite3_rollback_hook(). Returns the previous hook, if - any (else null). Throws if this db is closed. - - Minor caveat: if a rollback hook is set on this object's underlying - db handle using the lower-level SQLite API, this function may - return null when replacing it, despite there being a hook, - because it will have a different callback type. So long as the - handle is only manipulated via the high-level API, this caveat - does not apply. - */ - public RollbackHook setRollbackHook( RollbackHook c ){ - RollbackHookProxy chp = null; - if( null!=c ){ - chp = new RollbackHookProxy(c); - } - final org.sqlite.jni.capi.RollbackHookCallback rv = - CApi.sqlite3_rollback_hook(thisDb(), chp); - return (rv instanceof RollbackHookProxy) - ? ((RollbackHookProxy)rv).rollbackHook - : null; - } - - public interface UpdateHook { - /** - Must function as described for the C-level sqlite3_update_hook() - callback. - */ - void call(int opId, String dbName, String tableName, long rowId); - } - - /** - A level of indirection to permit setUpdateHook() to have similar - semantics as the C API, returning the previous hook. The caveat - is that if the low-level API is used to install a hook, it will - have a different hook type than Sqlite.UpdateHook so - setUpdateHook() will return null instead of that object. - */ - private static class UpdateHookProxy - implements org.sqlite.jni.capi.UpdateHookCallback { - final UpdateHook updateHook; - UpdateHookProxy(UpdateHook ch){ - this.updateHook = ch; - } - @Override public void call(int opId, String dbName, String tableName, long rowId){ - updateHook.call(opId, dbName, tableName, rowId); - } - } - - /** - Analog to sqlite3_update_hook(). Returns the previous hook, if - any (else null). Throws if this db is closed. - - Minor caveat: if a update hook is set on this object's underlying - db handle using the lower-level SQLite API, this function may - return null when replacing it, despite there being a hook, - because it will have a different callback type. So long as the - handle is only manipulated via the high-level API, this caveat - does not apply. - */ - public UpdateHook setUpdateHook( UpdateHook c ){ - UpdateHookProxy chp = null; - if( null!=c ){ - chp = new UpdateHookProxy(c); - } - final org.sqlite.jni.capi.UpdateHookCallback rv = - CApi.sqlite3_update_hook(thisDb(), chp); - return (rv instanceof UpdateHookProxy) - ? ((UpdateHookProxy)rv).updateHook - : null; - } - - - /** - Callback interface for use with setProgressHandler(). - */ - public interface ProgressHandler { - /** - Must behave as documented for the C-level sqlite3_progress_handler() - callback. If it throws, the exception is translated into - a db-level error. - */ - int call(); - } - - /** - Analog to sqlite3_progress_handler(), sets the current progress - handler or clears it if p is null. - - Note that this API, in contrast to setUpdateHook(), - setRollbackHook(), and setCommitHook(), cannot return the - previous handler. That inconsistency is part of the lower-level C - API. - */ - public void setProgressHandler( int n, ProgressHandler p ){ - org.sqlite.jni.capi.ProgressHandlerCallback phc = null; - if( null!=p ){ - phc = new org.sqlite.jni.capi.ProgressHandlerCallback(){ - @Override public int call(){ return p.call(); } - }; - } - CApi.sqlite3_progress_handler( thisDb(), n, phc ); - } - - - /** - Callback for use with setAuthorizer(). - */ - public interface Authorizer { - /** - Must function as described for the C-level - sqlite3_set_authorizer() callback. If it throws, the error is - converted to a db-level error and the exception is suppressed. - */ - int call(int opId, String s1, String s2, String s3, String s4); - } - - /** - Analog to sqlite3_set_authorizer(), this sets the current - authorizer callback, or clears if it passed null. - */ - public void setAuthorizer( Authorizer a ) { - org.sqlite.jni.capi.AuthorizerCallback ac = null; - if( null!=a ){ - ac = new org.sqlite.jni.capi.AuthorizerCallback(){ - @Override public int call(int opId, String s1, String s2, String s3, String s4){ - return a.call(opId, s1, s2, s3, s4); - } - }; - } - checkRc( CApi.sqlite3_set_authorizer( thisDb(), ac ) ); - } - - /** - Object type for use with blobOpen() - */ - public final class Blob implements AutoCloseable { - private Sqlite db; - private sqlite3_blob b; - Blob(Sqlite db, sqlite3_blob b){ - this.db = db; - this.b = b; - } - - /** - If this blob is still opened, its low-level handle is - returned, else an IllegalArgumentException is thrown. - */ - private sqlite3_blob thisBlob(){ - if( null==b || 0==b.getNativePointer() ){ - throw new IllegalArgumentException("This Blob has been finalized."); - } - return b; - } - - /** - Analog to sqlite3_blob_close(). - */ - @Override public void close(){ - if( null!=b ){ - CApi.sqlite3_blob_close(b); - b = null; - db = null; - } - } - - /** - Throws if the JVM does not have JNI-level support for - ByteBuffer. - */ - private void checkNio(){ - if( !Sqlite.JNI_SUPPORTS_NIO ){ - throw new UnsupportedOperationException( - "This JVM does not support JNI access to ByteBuffer." - ); - } - } - /** - Analog to sqlite3_blob_reopen() but throws on error. - */ - public void reopen(long newRowId){ - db.checkRc( CApi.sqlite3_blob_reopen(thisBlob(), newRowId) ); - } - - /** - Analog to sqlite3_blob_write() but throws on error. - */ - public void write( byte[] bytes, int atOffset ){ - db.checkRc( CApi.sqlite3_blob_write(thisBlob(), bytes, atOffset) ); - } - - /** - Analog to sqlite3_blob_read() but throws on error. - */ - public void read( byte[] dest, int atOffset ){ - db.checkRc( CApi.sqlite3_blob_read(thisBlob(), dest, atOffset) ); - } - - /** - Analog to sqlite3_blob_bytes(). - */ - public int bytes(){ - return CApi.sqlite3_blob_bytes(thisBlob()); - } - } - - /** - Analog to sqlite3_blob_open(). Returns a Blob object for the - given database, table, column, and rowid. The blob is opened for - read-write mode if writeable is true, else it is read-only. - - The returned object must eventually be freed, before this - database is closed, by either arranging for it to be auto-closed - or calling its close() method. - - Throws on error. - */ - public Blob blobOpen(String dbName, String tableName, String columnName, - long iRow, boolean writeable){ - final OutputPointer.sqlite3_blob out = new OutputPointer.sqlite3_blob(); - checkRc( - CApi.sqlite3_blob_open(thisDb(), dbName, tableName, columnName, - iRow, writeable ? 1 : 0, out) - ); - return new Blob(this, out.take()); - } - - /** - Callback for use with libConfigLog(). - */ - public interface ConfigLog { - /** - Must function as described for a C-level callback for - sqlite3_config()'s SQLITE_CONFIG_LOG callback, with the slight - signature change. Any exceptions thrown from this callback are - necessarily suppressed. - */ - void call(int errCode, String msg); - } - - /** - Analog to sqlite3_config() with the SQLITE_CONFIG_LOG option, - this sets or (if log is null) clears the current logger. - */ - public static void libConfigLog(ConfigLog log){ - final org.sqlite.jni.capi.ConfigLogCallback l = - null==log - ? null - : new org.sqlite.jni.capi.ConfigLogCallback() { - @Override public void call(int errCode, String msg){ - log.call(errCode, msg); - } - }; - checkRcStatic(CApi.sqlite3_config(l)); - } - - /** - Callback for use with libConfigSqlLog(). - */ - public interface ConfigSqlLog { - /** - Must function as described for a C-level callback for - sqlite3_config()'s SQLITE_CONFIG_SQLLOG callback, with the - slight signature change. Any exceptions thrown from this - callback are necessarily suppressed. - */ - void call(Sqlite db, String msg, int msgType); - } - - /** - Analog to sqlite3_config() with the SQLITE_CONFIG_SQLLOG option, - this sets or (if log is null) clears the current logger. - - If SQLite is built without SQLITE_ENABLE_SQLLOG defined then this - will throw an UnsupportedOperationException. - */ - public static void libConfigSqlLog(ConfigSqlLog log){ - Sqlite.checkSupported(hasNormalizeSql, "SQLITE_ENABLE_SQLLOG"); - final org.sqlite.jni.capi.ConfigSqlLogCallback l = - null==log - ? null - : new org.sqlite.jni.capi.ConfigSqlLogCallback() { - @Override public void call(sqlite3 db, String msg, int msgType){ - try{ - log.call(fromNative(db), msg, msgType); - }catch(Exception e){ - /* Suppressed */ - } - } - }; - checkRcStatic(CApi.sqlite3_config(l)); - } - - /** - Analog to the C-level sqlite3_config() with one of the - SQLITE_CONFIG_... constants defined as CONFIG_... in this - class. Throws on error, including passing of an unknown option or - if a specified option is not supported by the underlying build of - the SQLite library. - */ - public static void libConfigOp( int op ){ - checkRcStatic(CApi.sqlite3_config(op)); + 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); } } Index: ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java ================================================================== --- ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java +++ ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java @@ -10,24 +10,24 @@ ** ************************************************************************* ** This file is part of the wrapper1 interface for sqlite3. */ package org.sqlite.jni.wrapper1; -import org.sqlite.jni.capi.CApi; +import static org.sqlite.jni.capi.CApi.*; import org.sqlite.jni.capi.sqlite3; /** 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 { - private int errCode = CApi.SQLITE_ERROR; - private int xerrCode = CApi.SQLITE_ERROR; - private int errOffset = -1; - private int sysErrno = 0; + int errCode = SQLITE_ERROR; + int xerrCode = SQLITE_ERROR; + int errOffset = -1; + int sysErrno = 0; /** Records the given error string and uses SQLITE_ERROR for both the error code and extended error code. */ @@ -36,35 +36,32 @@ } /** Uses sqlite3_errstr(sqlite3ResultCode) for the error string and sets both the error code and extended error code to the given - value. This approach includes no database-level information and - systemErrno() will be 0, so is intended only for use with sqlite3 - APIs for which a result code is not an error but which the - higher-level wrapper should treat as one. + value. */ public SqliteException(int sqlite3ResultCode){ - super(CApi.sqlite3_errstr(sqlite3ResultCode)); + super(sqlite3_errstr(sqlite3ResultCode)); errCode = xerrCode = sqlite3ResultCode; } /** Records the current error state of db (which must not be null and - must refer to an opened db object). Note that this does not close + must refer to an opened db object). Note that this does NOT close the db. - Design note: closing the db on error is really only useful during + Design note: closing the db on error is likely only useful during a failed db-open operation, and the place(s) where that can happen are inside this library, not client-level code. */ SqliteException(sqlite3 db){ - super(CApi.sqlite3_errmsg(db)); - errCode = CApi.sqlite3_errcode(db); - xerrCode = CApi.sqlite3_extended_errcode(db); - errOffset = CApi.sqlite3_error_offset(db); - sysErrno = CApi.sqlite3_system_errno(db); + super(sqlite3_errmsg(db)); + errCode = sqlite3_errcode(db); + xerrCode = sqlite3_extended_errcode(db); + errOffset = sqlite3_error_offset(db); + sysErrno = sqlite3_system_errno(db); } /** Records the current error state of db (which must not be null and must refer to an open database). @@ -72,14 +69,14 @@ public SqliteException(Sqlite db){ this(db.nativeHandle()); } public SqliteException(Sqlite.Stmt stmt){ - this(stmt.getDb()); + this( stmt.db() ); } public int errcode(){ return errCode; } public int extendedErrcode(){ return xerrCode; } public int errorOffset(){ return errOffset; } public int systemErrno(){ return sysErrno; } } Index: ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java ================================================================== --- ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java +++ ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java @@ -10,17 +10,18 @@ ** ************************************************************************* ** 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.CApi; +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 @@ -43,11 +44,11 @@ //! 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 int listRunTests = 0; + private static boolean listRunTests = false; //! True to squelch all out() and outln() output. private static boolean quietMode = false; //! Total number of runTests() calls. private static int nTestRuns = 0; //! List of test*() methods to run. @@ -122,143 +123,122 @@ public static void affirm(Boolean v){ affirm(v, "Affirmation failed."); } - public static void execSql(Sqlite db, String sql[]){ + public static void execSql(Sqlite db, String[] sql){ execSql(db, String.join("", sql)); } - /** - Executes all SQL statements in the given string. If throwOnError - is true then it will throw for any prepare/step errors, else it - will return the corresponding non-0 result code. - */ public static int execSql(Sqlite dbw, boolean throwOnError, String sql){ - final ValueHolder rv = new ValueHolder<>(0); - final Sqlite.PrepareMulti pm = new Sqlite.PrepareMulti(){ - @Override public void call(Sqlite.Stmt stmt){ - try{ - while( Sqlite.ROW == (rv.value = stmt.step(throwOnError)) ){} - } - finally{ stmt.finalizeStmt(); } - } - }; - try { - dbw.prepareMulti(sql, pm); - }catch(SqliteException se){ - if( throwOnError ){ - throw se; - }else{ - /* This error (likely) happened in the prepare() phase and we - need to preempt it. */ - rv.value = se.errcode(); - } - } - return (rv.value==Sqlite.DONE) ? 0 : rv.value; + 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(Sqlite.libVersionNumber() == CApi.SQLITE_VERSION_NUMBER); + 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, Sqlite.OPEN_READWRITE| - Sqlite.OPEN_CREATE| - Sqlite.OPEN_EXRESCODE); + 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() ); - affirm( "main".equals( db.dbName(0) ) ); - db.setMainDbName("foo"); - affirm( "foo".equals( db.dbName(0) ) ); - affirm( db.dbConfig(Sqlite.DBCONFIG_DEFENSIVE, true) - /* The underlying 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( !db.dbConfig(Sqlite.DBCONFIG_DEFENSIVE, false) ); - SqliteException ex = null; - try{ db.dbConfig(0, false); } - catch(SqliteException e){ ex = e; } - affirm( null!=ex ); - ex = null; db.close(); affirm( null==db.nativeHandle() ); - try{ db = openDb("/no/such/dir/.../probably"); } - catch(SqliteException e){ ex = e; } + 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"); - Exception e = null; + Sqlite.Stmt stmt = db.prepare("SELECT 1"); affirm( null!=stmt.nativeHandle() ); - affirm( db == stmt.getDb() ); - affirm( 1==stmt.bindParameterCount() ); - affirm( "?1".equals(stmt.bindParameterName(1)) ); - affirm( null==stmt.bindParameterName(2) ); - stmt.bindInt64(1, 1); - stmt.bindDouble(1, 1.1); - stmt.bindObject(1, db); - stmt.bindNull(1); - stmt.bindText(1, new byte[] {32,32,32}); - stmt.bindText(1, "123"); - stmt.bindText16(1, "123".getBytes(StandardCharsets.UTF_16)); - stmt.bindText16(1, "123"); - stmt.bindZeroBlob(1, 8); - stmt.bindBlob(1, new byte[] {1,2,3,4}); - stmt.bindInt(1, 17); - try{ stmt.bindInt(2,1); } - catch(Exception ex){ e = ex; } - affirm( null!=e ); - e = null; - affirm( stmt.step() ); - try{ stmt.columnInt(1); } - catch(Exception ex){ e = ex; } - affirm( null!=e ); - e = null; - affirm( 17 == stmt.columnInt(0) ); - affirm( 17L == stmt.columnInt64(0) ); - affirm( 17.0 == stmt.columnDouble(0) ); - affirm( "17".equals(stmt.columnText16(0)) ); - affirm( !stmt.step() ); + affirm( CApi.SQLITE_ROW == stmt.step() ); + affirm( CApi.SQLITE_DONE == stmt.step() ); stmt.reset(); - affirm( Sqlite.ROW==stmt.step(false) ); - affirm( !stmt.step() ); + affirm( CApi.SQLITE_ROW == stmt.step() ); + affirm( CApi.SQLITE_DONE == stmt.step() ); affirm( 0 == stmt.finalizeStmt() ); affirm( null==stmt.nativeHandle() ); - stmt = db.prepare("SELECT ?"); - stmt.bindObject(1, db); - affirm( Sqlite.ROW == stmt.step(false) ); - affirm( db==stmt.columnObject(0) ); - affirm( db==stmt.columnObject(0, Sqlite.class ) ); - affirm( null==stmt.columnObject(0, Sqlite.Stmt.class ) ); - affirm( 0==stmt.finalizeStmt() ) + 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() ); } } @@ -267,41 +247,32 @@ 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){ - affirm( db == args.getDb() ); for( SqlFunction.Arguments.Arg arg : args ){ vh.value += arg.getInt(); } - args.resultInt(vh.value); } public void xDestroy(){ ++xDestroyCalled.value; } }; db.createFunction("myfunc", -1, f); - Sqlite.Stmt q = db.prepare("select myfunc(1,2,3)"); - affirm( q.step() ); + execSql(db, "select myfunc(1,2,3)"); affirm( 6 == vh.value ); - affirm( 6 == q.columnInt(0) ); - q.finalizeStmt(); - affirm( 0 == xDestroyCalled.value ); vh.value = 0; - q = db.prepare("select myfunc(-1,-2,-3)"); - affirm( q.step() ); + execSql(db, "select myfunc(-1,-2,-3)"); affirm( -6 == vh.value ); - affirm( -6 == q.columnInt(0) ); affirm( 0 == xDestroyCalled.value ); - q.finalizeStmt(); } affirm( 1 == xDestroyCalled.value ); } void testUdfAggregate(){ final ValueHolder xDestroyCalled = new ValueHolder<>(0); - Sqlite.Stmt q = null; + 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); @@ -311,664 +282,32 @@ } 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("summer", 1, f); - q = db.prepare( - "with cte(v) as ("+ - "select 3 union all select 5 union all select 7"+ - ") select summer(v), summer(v+1) from cte" - /* ------------------^^^^^^^^^^^ ensures that we're handling - sqlite3_aggregate_context() properly. */ - ); - affirm( q.step() ); - affirm( 15==q.columnInt(0) ); - q.finalizeStmt(); - q = null; + db.createFunction("myagg", -1, f); + execSql(db, "select myagg(a) from t"); + affirm( 6 == vh.value ); affirm( 0 == xDestroyCalled.value ); - db.createFunction("summerN", -1, f); - - q = db.prepare("select summerN(1,8,9), summerN(2,3,4)"); - affirm( q.step() ); - affirm( 18==q.columnInt(0) ); - affirm( 9==q.columnInt(1) ); - q.finalizeStmt(); - q = null; - - }/*db*/ - finally{ - if( null!=q ) q.finalizeStmt(); - } - affirm( 2 == xDestroyCalled.value - /* because we've bound the same instance twice */ ); - } - - private void testUdfWindow(){ - final Sqlite db = openDb(); - /* Example window function, table, and results taken from: - https://sqlite.org/windowfunctions.html#udfwinfunc */ - final WindowFunction func = new WindowFunction(){ - //! Impl of xStep() and xInverse() - private void xStepInverse(SqlFunction.Arguments args, int v){ - this.getAggregateState(args,0).value += v; - } - @Override public void xStep(SqlFunction.Arguments args){ - this.xStepInverse(args, args.getInt(0)); - } - @Override public void xInverse(SqlFunction.Arguments args){ - this.xStepInverse(args, -args.getInt(0)); - } - //! Impl of xFinal() and xValue() - private void xFinalValue(SqlFunction.Arguments args, Integer v){ - if(null == v) args.resultNull(); - else args.resultInt(v); - } - @Override public void xFinal(SqlFunction.Arguments args){ - xFinalValue(args, this.takeAggregateState(args)); - affirm( null == this.getAggregateState(args,null).value ); - } - @Override public void xValue(SqlFunction.Arguments args){ - xFinalValue(args, this.getAggregateState(args,null).value); - } - }; - db.createFunction("winsumint", 1, func); - 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 Sqlite.Stmt stmt = db.prepare( - "SELECT x, winsumint(y) OVER ("+ - "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING"+ - ") AS sum_y "+ - "FROM twin ORDER BY x;" - ); - int n = 0; - while( stmt.step() ){ - final String s = stmt.columnText16(0); - final int i = stmt.columnInt(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 */ ); - } - } - stmt.close(); - affirm( 5 == n ); - db.close(); - } - - private void testKeyword(){ - final int n = Sqlite.keywordCount(); - affirm( n>0 ); - affirm( !Sqlite.keywordCheck("_nope_") ); - affirm( Sqlite.keywordCheck("seLect") ); - affirm( null!=Sqlite.keywordName(0) ); - affirm( null!=Sqlite.keywordName(n-1) ); - affirm( null==Sqlite.keywordName(n) ); - } - - - private void testExplain(){ - final Sqlite db = openDb(); - Sqlite.Stmt q = db.prepare("SELECT 1"); - affirm( 0 == q.isExplain() ); - q.explain(0); - affirm( 0 == q.isExplain() ); - q.explain(1); - affirm( 1 == q.isExplain() ); - q.explain(2); - affirm( 2 == q.isExplain() ); - Exception ex = null; - try{ - q.explain(-1); - }catch(Exception e){ - ex = e; - } - affirm( ex instanceof SqliteException ); - q.finalizeStmt(); - db.close(); - } - - - private void testTrace(){ - final Sqlite db = openDb(); - 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 = "😃"; - db.trace( - Sqlite.TRACE_ALL, - new Sqlite.TraceCallback(){ - @Override public void 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 Sqlite.Stmt); - //outln("TRACE_STMT sql = "+x); - affirm(x instanceof String); - affirm( ((String)x).indexOf(nonBmpChar) > 0 ); - break; - case Sqlite.TRACE_PROFILE: - affirm(pNative instanceof Sqlite.Stmt); - affirm(x instanceof Long); - //outln("TRACE_PROFILE time = "+x); - break; - case Sqlite.TRACE_ROW: - affirm(pNative instanceof Sqlite.Stmt); - affirm(null == x); - //outln("TRACE_ROW = "+sqlite3_column_text16((sqlite3_stmt)pNative, 0)); - break; - case Sqlite.TRACE_CLOSE: - affirm(pNative instanceof Sqlite); - affirm(null == x); - break; - default: - affirm(false /*cannot happen*/); - break; - } - } - }); - execSql(db, "SELECT coalesce(null,null,'"+nonBmpChar+"'); "+ - "SELECT 'w"+nonBmpChar+"orld'"); - affirm( 6 == counter.value ); - db.close(); - affirm( 7 == counter.value ); - } - - private void testStatus(){ - final Sqlite db = openDb(); - execSql(db, "create table t(a); insert into t values(1),(2),(3)"); - - Sqlite.Status s = Sqlite.libStatus(Sqlite.STATUS_MEMORY_USED, false); - affirm( s.current > 0 ); - affirm( s.peak >= s.current ); - - s = db.status(Sqlite.DBSTATUS_SCHEMA_USED, false); - affirm( s.current > 0 ); - affirm( s.peak == 0 /* always 0 for SCHEMA_USED */ ); - - db.close(); - } - - @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 Sqlite.AutoExtension ax = new Sqlite.AutoExtension(){ - @Override public void call(Sqlite db){ - ++val.value; - if( null!=toss.value ){ - throw new RuntimeException(toss.value); - } - } - }; - Sqlite.addAutoExtension(ax); - openDb().close(); - affirm( 1==val.value ); - openDb().close(); - affirm( 2==val.value ); - Sqlite.clearAutoExtensions(); - openDb().close(); - affirm( 2==val.value ); - - Sqlite.addAutoExtension( ax ); - Sqlite.addAutoExtension( ax ); // Must not add a second entry - Sqlite.addAutoExtension( ax ); // or a third one - openDb().close(); - affirm( 3==val.value ); - - Sqlite db = openDb(); - affirm( 4==val.value ); - execSql(db, "ATTACH ':memory:' as foo"); - affirm( 4==val.value, "ATTACH uses the same connection, not sub-connections." ); - db.close(); - db = null; - - Sqlite.removeAutoExtension(ax); - openDb().close(); - affirm( 4==val.value ); - Sqlite.addAutoExtension(ax); - Exception err = null; - toss.value = "Throwing from auto_extension."; - try{ - openDb(); - }catch(Exception e){ - err = e; - } - affirm( err!=null ); - affirm( err.getMessage().indexOf(toss.value)>=0 ); - toss.value = null; - - val.value = 0; - final Sqlite.AutoExtension ax2 = new Sqlite.AutoExtension(){ - @Override public void call(Sqlite db){ - ++val.value; - } - }; - Sqlite.addAutoExtension(ax2); - openDb().close(); - affirm( 2 == val.value ); - Sqlite.removeAutoExtension(ax); - openDb().close(); - affirm( 3 == val.value ); - Sqlite.addAutoExtension(ax); - openDb().close(); - affirm( 5 == val.value ); - Sqlite.removeAutoExtension(ax2); - openDb().close(); - affirm( 6 == val.value ); - Sqlite.addAutoExtension(ax2); - openDb().close(); - affirm( 8 == val.value ); - - Sqlite.clearAutoExtensions(); - openDb().close(); - affirm( 8 == val.value ); - } - - private void testBackup(){ - final Sqlite dbDest = openDb(); - - try (Sqlite dbSrc = openDb()) { - execSql(dbSrc, new String[]{ - "pragma page_size=512; VACUUM;", - "create table t(a);", - "insert into t(a) values(1),(2),(3);" - }); - Exception e = null; - try { - dbSrc.initBackup("main",dbSrc,"main"); - }catch(Exception x){ - e = x; - } - affirm( e instanceof SqliteException ); - e = null; - try (Sqlite.Backup b = dbDest.initBackup("main",dbSrc,"main")) { - affirm( null!=b ); - int rc; - while( Sqlite.DONE!=(rc = b.step(1)) ){ - affirm( 0==rc ); - } - affirm( b.pageCount() > 0 ); - b.finish(); - } - } - - try (Sqlite.Stmt q = dbDest.prepare("SELECT sum(a) from t")) { - q.step(); - affirm( q.columnInt(0) == 6 ); - } - dbDest.close(); - } - - private void testCollation(){ - final Sqlite db = openDb(); - execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); - final Sqlite.Collation myCollation = new Sqlite.Collation() { - 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; - } - }; - final Sqlite.CollationNeeded collLoader = new Sqlite.CollationNeeded(){ - @Override - public void call(Sqlite dbArg, int eTextRep, String collationName){ - affirm(dbArg == db); - db.createCollation("reversi", eTextRep, myCollation); - } - }; - db.onCollationNeeded(collLoader); - Sqlite.Stmt stmt = db.prepare("SELECT a FROM t ORDER BY a COLLATE reversi"); - int counter = 0; - while( stmt.step() ){ - final String val = stmt.columnText16(0); - ++counter; - 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); - stmt.finalizeStmt(); - stmt = db.prepare("SELECT a FROM t ORDER BY a"); - counter = 0; - while( stmt.step() ){ - final String val = stmt.columnText16(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); - stmt.finalizeStmt(); - db.onCollationNeeded(null); - db.close(); - } - - @SingleThreadOnly /* because threads inherently break this test */ - private void testBusy(){ - final String dbName = "_busy-handler.db"; - try{ - Sqlite db1 = openDb(dbName); - ++metrics.dbOpen; - execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)"); - Sqlite db2 = openDb(dbName); - ++metrics.dbOpen; - - final ValueHolder xBusyCalled = new ValueHolder<>(0); - Sqlite.BusyHandler handler = new Sqlite.BusyHandler(){ - @Override public int call(int n){ - return n > 2 ? 0 : ++xBusyCalled.value; - } - }; - db2.setBusyHandler(handler); - - // Force a locked condition... - execSql(db1, "BEGIN EXCLUSIVE"); - int rc = 0; - SqliteException ex = null; - try{ - db2.prepare("SELECT * from t"); - }catch(SqliteException x){ - ex = x; - } - affirm( null!=ex ); - affirm( Sqlite.BUSY == ex.errcode() ); - affirm( 3 == xBusyCalled.value ); - db1.close(); - db2.close(); - }finally{ - try{(new java.io.File(dbName)).delete();} - catch(Exception e){/* ignore */} - } - } - - private void testCommitHook(){ - final Sqlite db = openDb(); - final ValueHolder counter = new ValueHolder<>(0); - final ValueHolder hookResult = new ValueHolder<>(0); - final Sqlite.CommitHook theHook = new Sqlite.CommitHook(){ - @Override public int call(){ - ++counter.value; - return hookResult.value; - } - }; - Sqlite.CommitHook oldHook = db.setCommitHook(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 = db.setCommitHook(theHook); - affirm( theHook == oldHook ); - execSql(db, "BEGIN; update t set a='e' where a='d'; COMMIT;"); - affirm( 4 == counter.value ); - oldHook = db.setCommitHook(null); - affirm( theHook == oldHook ); - execSql(db, "BEGIN; update t set a='f' where a='e'; COMMIT;"); - affirm( 4 == counter.value ); - oldHook = db.setCommitHook(null); - affirm( null == oldHook ); - execSql(db, "BEGIN; update t set a='g' where a='f'; COMMIT;"); - affirm( 4 == counter.value ); - - final Sqlite.CommitHook newHook = new Sqlite.CommitHook(){ - @Override public int call(){return 0;} - }; - oldHook = db.setCommitHook(newHook); - affirm( null == oldHook ); - execSql(db, "BEGIN; update t set a='h' where a='g'; COMMIT;"); - affirm( 4 == counter.value ); - oldHook = db.setCommitHook(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_COMMITHOOK == rc ); - affirm( 6 == counter.value ); - db.close(); - } - - private void testRollbackHook(){ - final Sqlite db = openDb(); - final ValueHolder counter = new ValueHolder<>(0); - final Sqlite.RollbackHook theHook = new Sqlite.RollbackHook(){ - @Override public void call(){ - ++counter.value; - } - }; - Sqlite.RollbackHook oldHook = db.setRollbackHook(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 Sqlite.RollbackHook newHook = new Sqlite.RollbackHook(){ - @Override public void call(){} - }; - oldHook = db.setRollbackHook(newHook); - affirm( theHook == oldHook ); - execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); - affirm( 1 == counter.value ); - oldHook = db.setRollbackHook(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 ); - db.close(); - } - - private void testUpdateHook(){ - final Sqlite db = openDb(); - final ValueHolder counter = new ValueHolder<>(0); - final ValueHolder expectedOp = new ValueHolder<>(0); - final Sqlite.UpdateHook theHook = new Sqlite.UpdateHook(){ - @Override - public void call(int opId, String dbName, String tableName, long rowId){ - ++counter.value; - if( 0!=expectedOp.value ){ - affirm( expectedOp.value == opId ); - } - } - }; - Sqlite.UpdateHook oldHook = db.setUpdateHook(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 = db.setUpdateHook(theHook); - affirm( theHook == oldHook ); - expectedOp.value = Sqlite.DELETE; - execSql(db, "DELETE FROM t where a='d'"); - affirm( 5 == counter.value ); - oldHook = db.setUpdateHook(null); - affirm( theHook == oldHook ); - execSql(db, "update t set a='e' where a='b';"); - affirm( 5 == counter.value ); - oldHook = db.setUpdateHook(null); - affirm( null == oldHook ); - - final Sqlite.UpdateHook newHook = new Sqlite.UpdateHook(){ - @Override public void call(int opId, String dbName, String tableName, long rowId){ - } - }; - oldHook = db.setUpdateHook(newHook); - affirm( null == oldHook ); - execSql(db, "update t set a='h' where a='a'"); - affirm( 5 == counter.value ); - oldHook = db.setUpdateHook(theHook); - affirm( newHook == oldHook ); - expectedOp.value = Sqlite.UPDATE; - execSql(db, "update t set a='i' where a='h'"); - affirm( 6 == counter.value ); - db.close(); - } - - private void testProgress(){ - final Sqlite db = openDb(); - final ValueHolder counter = new ValueHolder<>(0); - db.setProgressHandler(1, new Sqlite.ProgressHandler(){ - @Override public int call(){ - ++counter.value; - return 0; - } - }); - execSql(db, "SELECT 1; SELECT 2;"); - affirm( counter.value > 0 ); - int nOld = counter.value; - db.setProgressHandler(0, null); - execSql(db, "SELECT 1; SELECT 2;"); - affirm( nOld == counter.value ); - db.close(); - } - - private void testAuthorizer(){ - final Sqlite db = openDb(); - final ValueHolder counter = new ValueHolder<>(0); - final ValueHolder authRc = new ValueHolder<>(0); - final Sqlite.Authorizer auth = new Sqlite.Authorizer(){ - 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')"); - db.setAuthorizer(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 ); - db.setAuthorizer(null); - rc = execSql(db, false, "UPDATE t SET a=2"); - affirm( 0==rc ); - db.close(); - } - - private void testBlobOpen(){ - final Sqlite db = openDb(); - - execSql(db, "CREATE TABLE T(a BLOB);" - +"INSERT INTO t(rowid,a) VALUES(1, 'def'),(2, 'XYZ');" - ); - Sqlite.Blob b = db.blobOpen("main", "t", "a", - db.lastInsertRowId(), true); - affirm( 3==b.bytes() ); - b.write(new byte[] {100, 101, 102 /*"DEF"*/}, 0); - b.close(); - Sqlite.Stmt stmt = db.prepare("SELECT length(a), a FROM t ORDER BY a"); - affirm( stmt.step() ); - affirm( 3 == stmt.columnInt(0) ); - affirm( "def".equals(stmt.columnText16(1)) ); - stmt.finalizeStmt(); - - b = db.blobOpen("main", "t", "a", db.lastInsertRowId(), false); - final byte[] tgt = new byte[3]; - b.read( tgt, 0 ); - affirm( 100==tgt[0] && 101==tgt[1] && 102==tgt[2], "DEF" ); - execSql(db,"UPDATE t SET a=zeroblob(10) WHERE rowid=2"); - b.close(); - b = db.blobOpen("main", "t", "a", db.lastInsertRowId(), true); - byte[] bw = new byte[]{ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 - }; - b.write(bw, 0); - byte[] br = new byte[10]; - b.read(br, 0); - for( int i = 0; i < br.length; ++i ){ - affirm(bw[i] == br[i]); - } - b.close(); - db.close(); - } - - void testPrepareMulti(){ - final ValueHolder fCount = new ValueHolder<>(0); - final ValueHolder mCount = new ValueHolder<>(0); - try (Sqlite db = openDb()) { - execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)"); - db.createFunction("counter", -1, new ScalarFunction(){ - @Override public void xFunc(SqlFunction.Arguments args){ - ++fCount.value; - args.resultNull(); - } - } - ); - final Sqlite.PrepareMulti pm = new Sqlite.PrepareMultiFinalize( - new Sqlite.PrepareMulti() { - @Override public void call(Sqlite.Stmt q){ - ++mCount.value; - while(q.step()){} - } - } - ); - final String sql = "select counter(*) from t;"+ - "select counter(*) from t; /* comment */"+ - "select counter(*) from t; -- comment\n" - ; - db.prepareMulti(sql, pm); - } - affirm( 3 == mCount.value ); - affirm( 9 == fCount.value ); - } - - - /* Copy/paste/rename this to add new tests. */ - private void _testTemplate(){ - try (Sqlite db = openDb()) { - Sqlite.Stmt stmt = db.prepare("SELECT 1"); - stmt.finalizeStmt(); - } + } + 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( (!fromThread && listRunTests>0) || listRunTests>1 ){ + if( listRunTests ){ synchronized(this.getClass()){ if( !fromThread ){ out("Initial test"," list: "); for(java.lang.reflect.Method m : testMethods){ out(m.getName()+" "); @@ -1003,11 +342,12 @@ }catch(Exception e){ synchronized( listErrors ){ listErrors.add(e); } }finally{ - Sqlite.uncacheThread(); + affirm( CApi.sqlite3_java_uncache_thread() ); + affirm( !CApi.sqlite3_java_uncache_thread() ); } } /** Runs the basic sqlite3 JNI binding sanity-check suite. @@ -1026,13 +366,11 @@ -naps: sleep small random intervals between tests in order to add some chaos for cross-thread contention. -list-tests: outputs the list of tests being run, minus some - which are hard-coded. In multi-threaded mode, use this twice to - to emit the list run by each thread (which may differ from the initial - list, in particular if -shuffle is used). + which are hard-coded. This is noisy in multi-threaded mode. -fail: forces an exception to be thrown during the test run. Use with -shuffle to make its appearance unpredictable. -v: emit some developer-mode info at the end. @@ -1057,11 +395,11 @@ }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; + listRunTests = true; }else if(arg.equals("fail")){ forceFail = true; }else if(arg.equals("sqllog")){ sqlLog = true; }else if(arg.equals("configlog")){ @@ -1075,33 +413,43 @@ } } } if( sqlLog ){ - if( Sqlite.compileOptionUsed("ENABLE_SQLLOG") ){ - Sqlite.libConfigSqlLog( new Sqlite.ConfigSqlLog() { - @Override public void call(Sqlite db, String msg, int op){ + 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 ){ - Sqlite.libConfigLog( new Sqlite.ConfigLog() { + final ConfigLogCallback log = new ConfigLogCallback() { @Override public void call(int code, String msg){ - outln("ConfigLog: ",Sqlite.errstr(code),": ", 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 ", @@ -1130,20 +478,43 @@ } 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: ", - Sqlite.libVersionNumber(),"\n", - Sqlite.libVersion(),"\n",Sqlite.libSourceId(),"\n", + 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."); - int nLoop = 0; for( int n = 0; n < nRepeat; ++n ){ ++nLoop; if( showLoopCount ) out((1==nLoop ? "" : " ")+nLoop); if( nThread<=1 ){ new Tester2(0).runTests(false); @@ -1181,11 +552,11 @@ outln("\tAssertions checked: ",affirmCount); outln("\tDatabases opened: ",metrics.dbOpen); if( doSomethingForDev ){ CApi.sqlite3_jni_internal_details(); } - affirm( 0==Sqlite.libReleaseMemory(1) ); + 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 = Index: ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java ================================================================== --- ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java +++ ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java @@ -7,17 +7,17 @@ ** 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 ValueHolder utility class. +** This file contains a set of tests for the sqlite3 JNI bindings. */ package org.sqlite.jni.wrapper1; /** A helper class which simply holds a single value. Its primary use - is for communicating values out of anonymous callbacks, as doing so + is for communicating values out of anonymous classes, as doing so requires a "final" reference. */ public class ValueHolder { public T value; public ValueHolder(){} DELETED ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java Index: ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java ================================================================== --- ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java +++ /dev/null @@ -1,42 +0,0 @@ -/* -** 2023-10-16 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** This file is part of the wrapper1 interface for sqlite3. -*/ -package org.sqlite.jni.wrapper1; - -/** - A SqlFunction implementation for window functions. The T type - represents the type of data accumulated by this function while it - works. e.g. a SUM()-like UDF might use Integer or Long and a - CONCAT()-like UDF might use a StringBuilder or a List. -*/ -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 reported via sqlite3_result_error(). - */ - public abstract void xInverse(SqlFunction.Arguments args); - - /** - As for the xValue() argument of the C API's - sqlite3_create_window_function(). If this function throws, it is - translated into sqlite3_result_error(). - - Note that the passed-in object will not actually contain any - arguments for xValue() but will contain the context object needed - for setting the call's result or error state. - */ - public abstract void xValue(SqlFunction.Arguments args); - -} Index: ext/misc/cksumvfs.c ================================================================== --- ext/misc/cksumvfs.c +++ ext/misc/cksumvfs.c @@ -444,13 +444,13 @@ ** (1) the size indicates that we are dealing with a complete ** database page ** (2) checksum verification is enabled ** (3) we are not in the middle of checkpoint */ - if( iAmt>=512 && (iAmt & (iAmt-1))==0 /* (1) */ - && p->verifyCksm /* (2) */ - && !p->inCkpt /* (3) */ + if( iAmt>=512 /* (1) */ + && p->verifyCksm /* (2) */ + && !p->inCkpt /* (3) */ ){ u8 cksum[8]; cksmCompute((u8*)zBuf, iAmt-8, cksum); if( memcmp((u8*)zBuf+iAmt-8, cksum, 8)!=0 ){ sqlite3_log(SQLITE_IOERR_DATA, Index: ext/misc/noop.c ================================================================== --- ext/misc/noop.c +++ ext/misc/noop.c @@ -36,28 +36,10 @@ ){ assert( argc==1 ); sqlite3_result_value(context, argv[0]); } -/* -** Implementation of the multitype_text() function. -** -** The function returns its argument. The result will always have a -** TEXT value. But if the original input is numeric, it will also -** have that numeric value. -*/ -static void multitypeTextFunc( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - assert( argc==1 ); - (void)argc; - (void)sqlite3_value_text(argv[0]); - sqlite3_result_value(context, argv[0]); -} - #ifdef _WIN32 __declspec(dllexport) #endif int sqlite3_noop_init( sqlite3 *db, @@ -80,11 +62,7 @@ 0, noopfunc, 0, 0); if( rc ) return rc; rc = sqlite3_create_function(db, "noop_nd", 1, SQLITE_UTF8, 0, noopfunc, 0, 0); - if( rc ) return rc; - rc = sqlite3_create_function(db, "multitype_text", 1, - SQLITE_UTF8, - 0, multitypeTextFunc, 0, 0); return rc; } Index: ext/misc/randomjson.c ================================================================== --- ext/misc/randomjson.c +++ ext/misc/randomjson.c @@ -24,18 +24,13 @@ ** ** USING FROM THE CLI: ** ** .load ./randomjson ** SELECT random_json(1); -** SELECT random_json5(1); */ -#ifdef SQLITE_STATIC_RANDOMJSON -# include "sqlite3.h" -#else -# include "sqlite3ext.h" - SQLITE_EXTENSION_INIT1 -#endif +#include "sqlite3ext.h" +SQLITE_EXTENSION_INIT1 #include #include #include /* Pseudo-random number generator */ @@ -54,22 +49,21 @@ p->x = (p->x>>1) ^ ((1+~(p->x&1)) & 0xd0000001); p->y = p->y*1103515245 + 12345; return p->x ^ p->y; } -static char *azJsonAtoms[] = { - /* JSON JSON-5 */ +static const char *azJsonAtoms[] = { + /* JSON /* JSON-5 */ "0", "0", "1", "1", "-1", "-1", "2", "+2", - "3DDDD", "3DDDD", - "2.5DD", "2.5DD", + "3", "3", + "2.5", "2.5", "0.75", ".75", "-4.0e2", "-4.e2", "5.0e-3", "+5e-3", - "6.DDe+0DD", "6.DDe+0DD", "0", "0x0", "512", "0x200", "256", "+0x100", "-2748", "-0xabc", "true", "true", @@ -77,18 +71,16 @@ "null", "null", "9.0e999", "Infinity", "-9.0e999", "-Infinity", "9.0e999", "+Infinity", "null", "NaN", - "-0.0005DD", "-0.0005DD", + "-0.0005123", "-0.0005123", "4.35e-3", "+4.35e-3", "\"gem\\\"hay\"", "\"gem\\\"hay\"", "\"icy'joy\"", "'icy\\'joy\'", "\"keylog\"", "\"key\\\nlog\"", "\"mix\\\\\\tnet\"", "\"mix\\\\\\tnet\"", - "\"oat\\r\\n\"", "\"oat\\r\\n\"", - "\"\\fpan\\b\"", "\"\\fpan\\b\"", "{}", "{}", "[]", "[]", "[]", "[/*empty*/]", "{}", "{//empty\n}", "\"ask\"", "\"ask\"", @@ -95,24 +87,23 @@ "\"bag\"", "\"bag\"", "\"can\"", "\"can\"", "\"day\"", "\"day\"", "\"end\"", "'end'", "\"fly\"", "\"fly\"", - "\"\\u00XX\\u00XX\"", "\"\\xXX\\xXX\"", - "\"y\\uXXXXz\"", "\"y\\uXXXXz\"", "\"\"", "\"\"", }; -static char *azJsonTemplate[] = { +static const char *azJsonTemplate[] = { /* JSON JSON-5 */ - "{\"a\":%,\"b\":%,\"cDD\":%}", "{a:%,b:%,cDD:%}", + "{\"a\":%,\"b\":%,\"c\":%}", "{a:%,b:%,c:%}", "{\"a\":%,\"b\":%,\"c\":%,\"d\":%,\"e\":%}", "{a:%,b:%,c:%,d:%,e:%}", - "{\"a\":%,\"b\":%,\"c\":%,\"d\":%,\"\":%}", "{a:%,b:%,c:%,d:%,'':%}", + "{\"a\":%,\"b\":%,\"c\":%,\"d\":%,\"\":%}", "{a:%,b:%,c:%,d:%,\"\":%}", "{\"d\":%}", "{d:%}", "{\"eeee\":%, \"ffff\":%}", "{eeee:% /*and*/, ffff:%}", - "{\"$g\":%,\"_h_\":%,\"a b c d\":%}", "{$g:%,_h_:%,\"a b c d\":%}", + "{\"$g\":%,\"_h_\":%}", "{$g:%,_h_:%,}", "{\"x\":%,\n \"y\":%}", "{\"x\":%,\n \"y\":%}", - "{\"\\u00XX\":%,\"\\uXXXX\":%}", "{\"\\xXX\":%,\"\\uXXXX\":%}", + "{\"a b c d\":%,\"e\":%,\"f\":%,\"x\":%,\"y\":%}", + "{\"a b c d\":%,e:%,f:%,x:%,y:%}", "{\"Z\":%}", "{Z:%,}", "[%]", "[%,]", "[%,%]", "[%,%]", "[%,%,%]", "[%,%,%,]", "[%,%,%,%]", "[%,%,%,%]", @@ -129,17 +120,19 @@ 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; - char *z; - char *zX; + const char *z; size_t n; - char zBuf[200]; j = 0; - if( zSrc==0 ) zSrc = "%"; + if( zSrc==0 ){ + k = prngInt(p)%(count(azJsonTemplate)/2); + k = k*2 + eType; + zSrc = azJsonTemplate[k]; + } if( strlen(zSrc)>=STRSZ/10 ) r = 0; for(i=0; zSrc[i]; i++){ if( zSrc[i]!='%' ){ if( j>8)&0xff) ) y += 0x100; - while( (y&0xff)==((y>>16)&0xff) || ((y>>8)&0xff)==((y>>16)&0xff) ){ - y += 0x10000; - } - memcpy(zBuf, z, n+1); - z = zBuf; - zX = strstr(z,"XX"); - while( zX!=0 ){ - zX[0] = "0123456789abcdef"[y%16]; y /= 16; - zX[1] = "0123456789abcdef"[y%16]; y /= 16; - zX = strstr(zX, "XX"); - } - }else if( (zX = strstr(z,"DD"))!=0 ){ - unsigned int y = prngInt(p); - memcpy(zBuf, z, n+1); - z = zBuf; - zX = strstr(z,"DD"); - while( zX!=0 ){ - zX[0] = "0123456789"[y%10]; y /= 10; - zX[1] = "0123456789"[y%10]; y /= 10; - zX = strstr(zX, "DD"); - } - } - assert( strstr(z, "XX")==0 ); - assert( strstr(z, "DD")==0 ); if( j+n=mxI64 ){ +static sqlite3_int64 genSeqMember(sqlite3_int64 smBase, + sqlite3_int64 smStep, + sqlite3_uint64 ix){ + if( ix>=(sqlite3_uint64)LLONG_MAX ){ /* Get ix into signed i64 range. */ - ix -= mxI64; + ix -= (sqlite3_uint64)LLONG_MAX; /* With 2's complement ALU, this next can be 1 step, but is split into * 2 for UBSAN's satisfaction (and hypothetical 1's complement ALUs.) */ - smBase += (mxI64/2) * smStep; - smBase += (mxI64 - mxI64/2) * smStep; + smBase += (LLONG_MAX/2) * smStep; + smBase += (LLONG_MAX - LLONG_MAX/2) * smStep; } /* Under UBSAN (or on 1's complement machines), must do this last term * in steps to avoid the dreaded (and harmless) signed multiply overlow. */ if( ix>=2 ){ sqlite3_int64 ix2 = (sqlite3_int64)ix/2; Index: ext/misc/totype.c ================================================================== --- ext/misc/totype.c +++ ext/misc/totype.c @@ -348,24 +348,10 @@ /* return true if number and no extra non-whitespace chracters after */ return z>=zEnd && nDigits>0 && eValid && nonNum==0; } -/* -** Convert a floating point value to an integer. Or, if this cannot be -** done in a way that avoids 'outside the range of representable values' -** warnings from UBSAN, return 0. -** -** This function is a modified copy of internal SQLite function -** sqlite3RealToI64(). -*/ -static sqlite3_int64 totypeDoubleToInt(double r){ - if( r<-9223372036854774784.0 ) return 0; - if( r>+9223372036854774784.0 ) return 0; - return (sqlite3_int64)r; -} - /* ** tointeger(X): If X is any value (integer, double, blob, or string) that ** can be losslessly converted into an integer, then make the conversion and ** return the result. Otherwise, return NULL. */ @@ -377,11 +363,11 @@ assert( argc==1 ); (void)argc; switch( sqlite3_value_type(argv[0]) ){ case SQLITE_FLOAT: { double rVal = sqlite3_value_double(argv[0]); - sqlite3_int64 iVal = totypeDoubleToInt(rVal); + sqlite3_int64 iVal = (sqlite3_int64)rVal; if( rVal==(double)iVal ){ sqlite3_result_int64(context, iVal); } break; } @@ -452,11 +438,11 @@ break; } case SQLITE_INTEGER: { sqlite3_int64 iVal = sqlite3_value_int64(argv[0]); double rVal = (double)iVal; - if( iVal==totypeDoubleToInt(rVal) ){ + if( iVal==(sqlite3_int64)rVal ){ sqlite3_result_double(context, rVal); } break; } case SQLITE_BLOB: { Index: ext/rbu/sqlite3rbu.c ================================================================== --- ext/rbu/sqlite3rbu.c +++ ext/rbu/sqlite3rbu.c @@ -197,11 +197,10 @@ #if !defined(SQLITE_AMALGAMATION) typedef unsigned int u32; typedef unsigned short u16; typedef unsigned char u8; typedef sqlite3_int64 i64; -typedef sqlite3_uint64 u64; #endif /* ** These values must match the values defined in wal.c for the equivalent ** locks. These are not magic numbers as they are part of the SQLite file @@ -884,11 +883,10 @@ pIter->bCleanup = 0; rc = sqlite3_step(pIter->pTblIter); if( rc!=SQLITE_ROW ){ rc = resetAndCollectError(pIter->pTblIter, &p->zErrmsg); pIter->zTbl = 0; - pIter->zDataTbl = 0; }else{ pIter->zTbl = (const char*)sqlite3_column_text(pIter->pTblIter, 0); pIter->zDataTbl = (const char*)sqlite3_column_text(pIter->pTblIter,1); rc = (pIter->zDataTbl && pIter->zTbl) ? SQLITE_OK : SQLITE_NOMEM; } @@ -2979,11 +2977,11 @@ if( p->rc==SQLITE_OK ){ sqlite3_file *pDb = p->pTargetFd->pReal; u32 volatile *ptr; p->rc = pDb->pMethods->xShmMap(pDb, 0, 32*1024, 0, (void volatile**)&ptr); if( p->rc==SQLITE_OK ){ - iRet = (i64)(((u64)ptr[10] << 32) + ptr[11]); + iRet = ((i64)ptr[10] << 32) + ptr[11]; } } return iRet; } Index: ext/recover/dbdata.c ================================================================== --- ext/recover/dbdata.c +++ ext/recover/dbdata.c @@ -580,11 +580,10 @@ /* Load the "byte of payload including overflow" field */ if( bNextPage || iOff>pCsr->nPage ){ bNextPage = 1; }else{ iOff += dbdataGetVarintU32(&pCsr->aPage[iOff], &nPayload); - if( nPayload>0x7fffff00 ) nPayload &= 0x3fff; } /* If this is a leaf intkey cell, load the rowid */ if( bHasRowid && !bNextPage && iOffnPage ){ iOff += dbdataGetVarint(&pCsr->aPage[iOff], &pCsr->iIntkey); Index: ext/recover/test_recover.c ================================================================== --- ext/recover/test_recover.c +++ ext/recover/test_recover.c @@ -234,11 +234,11 @@ } if( getDbPointer(interp, objv[1], &db) ) return TCL_ERROR; zDb = Tcl_GetString(objv[2]); if( zDb[0]=='\0' ) zDb = 0; - pNew = (TestRecover*)ckalloc(sizeof(TestRecover)); + pNew = ckalloc(sizeof(TestRecover)); if( bSql==0 ){ zUri = Tcl_GetString(objv[3]); pNew->p = sqlite3_recover_init(db, zDb, zUri); }else{ pNew->interp = interp; Index: ext/rtree/rtree.c ================================================================== --- ext/rtree/rtree.c +++ ext/rtree/rtree.c @@ -692,13 +692,15 @@ /* ** Clear the Rtree.pNodeBlob object */ static void nodeBlobReset(Rtree *pRtree){ - sqlite3_blob *pBlob = pRtree->pNodeBlob; - pRtree->pNodeBlob = 0; - sqlite3_blob_close(pBlob); + if( pRtree->pNodeBlob && pRtree->inWrTrans==0 && pRtree->nCursor==0 ){ + sqlite3_blob *pBlob = pRtree->pNodeBlob; + pRtree->pNodeBlob = 0; + sqlite3_blob_close(pBlob); + } } /* ** Obtain a reference to an r-tree node. */ @@ -713,11 +715,11 @@ /* Check if the requested node is already in the hash table. If so, ** increase its reference count and return it. */ if( (pNode = nodeHashLookup(pRtree, iNode))!=0 ){ - if( pParent && ALWAYS(pParent!=pNode->pParent) ){ + if( pParent && pParent!=pNode->pParent ){ RTREE_IS_CORRUPT(pRtree); return SQLITE_CORRUPT_VTAB; } pNode->nRef++; *ppNode = pNode; @@ -738,10 +740,11 @@ rc = sqlite3_blob_open(pRtree->db, pRtree->zDb, pRtree->zNodeName, "data", iNode, 0, &pRtree->pNodeBlob); } if( rc ){ + nodeBlobReset(pRtree); *ppNode = 0; /* If unable to open an sqlite3_blob on the desired row, that can only ** be because the shadow tables hold erroneous data. */ if( rc==SQLITE_ERROR ){ rc = SQLITE_CORRUPT_VTAB; @@ -797,11 +800,10 @@ rc = SQLITE_CORRUPT_VTAB; RTREE_IS_CORRUPT(pRtree); } *ppNode = pNode; }else{ - nodeBlobReset(pRtree); if( pNode ){ pRtree->nNodeRef--; sqlite3_free(pNode); } *ppNode = 0; @@ -942,11 +944,10 @@ RtreeNode *pNode, /* The node from which to extract a coordinate */ int iCell, /* The index of the cell within the node */ int iCoord, /* Which coordinate to extract */ RtreeCoord *pCoord /* OUT: Space to write result to */ ){ - assert( iCellzData[12 + pRtree->nBytesPerCell*iCell + 4*iCoord], pCoord); } /* ** Deserialize cell iCell of node pNode. Populate the structure pointed @@ -1132,13 +1133,11 @@ assert( pRtree->nCursor>0 ); resetCursor(pCsr); sqlite3_finalize(pCsr->pReadAux); sqlite3_free(pCsr); pRtree->nCursor--; - if( pRtree->nCursor==0 && pRtree->inWrTrans==0 ){ - nodeBlobReset(pRtree); - } + nodeBlobReset(pRtree); return SQLITE_OK; } /* ** Rtree virtual table module xEof method. @@ -1719,15 +1718,11 @@ RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor; RtreeSearchPoint *p = rtreeSearchPointFirst(pCsr); int rc = SQLITE_OK; RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc); if( rc==SQLITE_OK && ALWAYS(p) ){ - if( p->iCell>=NCELL(pNode) ){ - rc = SQLITE_ABORT; - }else{ - *pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell); - } + *pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell); } return rc; } /* @@ -1741,11 +1736,10 @@ int rc = SQLITE_OK; RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc); if( rc ) return rc; if( NEVER(p==0) ) return SQLITE_OK; - if( p->iCell>=NCELL(pNode) ) return SQLITE_ABORT; if( i==0 ){ sqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell)); }else if( i<=pRtree->nDim2 ){ nodeGetCoord(pRtree, pNode, p->iCell, i-1, &c); #ifndef SQLITE_RTREE_INT_ONLY @@ -1839,12 +1833,10 @@ } pCons->pInfo = pInfo; return SQLITE_OK; } -int sqlite3IntFloatCompare(i64,double); - /* ** Rtree virtual table module xFilter method. */ static int rtreeFilter( sqlite3_vtab_cursor *pVtabCursor, @@ -1870,12 +1862,11 @@ RtreeSearchPoint *p; /* Search point for the leaf */ i64 iRowid = sqlite3_value_int64(argv[0]); i64 iNode = 0; int eType = sqlite3_value_numeric_type(argv[0]); if( eType==SQLITE_INTEGER - || (eType==SQLITE_FLOAT - && 0==sqlite3IntFloatCompare(iRowid,sqlite3_value_double(argv[0]))) + || (eType==SQLITE_FLOAT && sqlite3_value_double(argv[0])==iRowid) ){ rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode); }else{ rc = SQLITE_OK; pLeaf = 0; @@ -3227,11 +3218,11 @@ ** Called when a transaction starts. */ static int rtreeBeginTransaction(sqlite3_vtab *pVtab){ Rtree *pRtree = (Rtree *)pVtab; assert( pRtree->inWrTrans==0 ); - pRtree->inWrTrans = 1; + pRtree->inWrTrans++; return SQLITE_OK; } /* ** Called when a transaction completes (either by COMMIT or ROLLBACK). @@ -3241,13 +3232,10 @@ Rtree *pRtree = (Rtree *)pVtab; pRtree->inWrTrans = 0; nodeBlobReset(pRtree); return SQLITE_OK; } -static int rtreeRollback(sqlite3_vtab *pVtab){ - return rtreeEndTransaction(pVtab); -} /* ** The xRename method for rtree module virtual tables. */ static int rtreeRename(sqlite3_vtab *pVtab, const char *zNewName){ @@ -3362,11 +3350,11 @@ rtreeRowid, /* xRowid - read data */ rtreeUpdate, /* xUpdate - write data */ rtreeBeginTransaction, /* xBegin - begin transaction */ rtreeEndTransaction, /* xSync - sync transaction */ rtreeEndTransaction, /* xCommit - commit transaction */ - rtreeRollback, /* xRollback - rollback transaction */ + rtreeEndTransaction, /* xRollback - rollback transaction */ 0, /* xFindFunction - function overloading */ rtreeRename, /* xRename - rename the table */ rtreeSavepoint, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ @@ -3462,11 +3450,11 @@ }else{ rc = SQLITE_NOMEM; } sqlite3_free(zSql); } - if( pRtree->nAux && rc!=SQLITE_NOMEM ){ + if( pRtree->nAux ){ pRtree->zReadAuxSql = sqlite3_mprintf( "SELECT * FROM \"%w\".\"%w_rowid\" WHERE rowid=?1", zDb, zPrefix); if( pRtree->zReadAuxSql==0 ){ rc = SQLITE_NOMEM; @@ -4151,17 +4139,19 @@ check.db = db; check.zDb = zDb; check.zTab = zTab; /* Find the number of auxiliary columns */ - pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.'%q_rowid'", zDb, zTab); - if( pStmt ){ - nAux = sqlite3_column_count(pStmt) - 2; - sqlite3_finalize(pStmt); - }else - if( check.rc!=SQLITE_NOMEM ){ - check.rc = SQLITE_OK; + if( check.rc==SQLITE_OK ){ + pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.'%q_rowid'", zDb, zTab); + if( pStmt ){ + nAux = sqlite3_column_count(pStmt) - 2; + sqlite3_finalize(pStmt); + }else + if( check.rc!=SQLITE_NOMEM ){ + check.rc = SQLITE_OK; + } } /* Find number of dimensions in the rtree table. */ pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.%Q", zDb, zTab); if( pStmt ){ @@ -4212,11 +4202,10 @@ 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); - if( (*pzErr)==0 ) rc = SQLITE_NOMEM; } return rc; } /* Index: ext/rtree/rtree1.test ================================================================== --- ext/rtree/rtree1.test +++ ext/rtree/rtree1.test @@ -795,24 +795,6 @@ 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} -reset_db -do_execsql_test 24.0 { - CREATE VIRTUAL TABLE rt1 USING rtree_i32(rid, c1, c2); - INSERT INTO rt1(rid, c1, c2) VALUES (9223372036854775807, 10, 18); -} - -do_execsql_test 24.1 { - SELECT (rid = (CAST (9223372036854775807 AS REAL))) - FROM rt1 WHERE - (rid = (CAST (9223372036854775807 AS REAL))); -} - -do_execsql_test 24.2 { - DELETE FROM rt1; - INSERT INTO rt1(rid, c1, c2) VALUES(1,2,3); - SELECT * FROM rt1 WHERE rid=1.005; -} {} - finish_test DELETED ext/rtree/rtreeJ.test Index: ext/rtree/rtreeJ.test ================================================================== --- ext/rtree/rtreeJ.test +++ /dev/null @@ -1,273 +0,0 @@ -# 2024-02-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. -# -#*********************************************************************** -# -# ROLLBACK in the middle of an RTREE query -# -if {![info exists testdir]} { - set testdir [file join [file dirname [info script]] .. .. test] -} -source $testdir/tester.tcl -set testprefix rtreeJ -ifcapable !rtree { finish_test ; return } - -do_execsql_test 1.0 { - CREATE VIRTUAL TABLE t1 USING rtree(id, x1, x2); - INSERT INTO t1 VALUES(1, 1, 1), (2, 2, 2); -} {} - -do_execsql_test 1.1 { - SELECT * FROM t1 -} {1 1.0 1.0 2 2.0 2.0} - -# If a ROLLBACK occurs that backs out changes to the RTREE, then -# all pending queries to the RTREE are aborted. -# -do_test 1.2 { - db eval { - BEGIN; - INSERT INTO t1 VALUES(3, 3, 3); - INSERT INTO t1 VALUES(4, 4, 4); - } - set rc [catch { - db eval { SELECT * FROM t1 } { - if {$id==1} { - db eval { ROLLBACK } - } - lappend res $id $x1 $x2 - } - } msg] - list $rc $msg -} {1 {query aborted}} - -do_execsql_test 1.3 { - SELECT * FROM t1; -} {1 1.0 1.0 2 2.0 2.0} - -# A COMMIT of changes to the RTREE does not affect pending queries -# -do_test 1.4 { - set res {} - db eval { - BEGIN; - INSERT INTO t1 VALUES(5, 5, 5); - INSERT INTO t1 VALUES(6, 6, 6); - } - db eval { SELECT * FROM t1 } { - if {$id==1} { - db eval { COMMIT } - } - lappend res $id $x1 $x2 - } - set res -} {1 1.0 1.0 2 2.0 2.0 5 5.0 5.0 6 6.0 6.0} - -do_execsql_test 1.5 { - SELECT * FROM t1; -} {1 1.0 1.0 2 2.0 2.0 5 5.0 5.0 6 6.0 6.0} - -do_execsql_test 1.6 { - DELETE FROM t1; - INSERT INTO t1 VALUES(1,1,1),(2,2,2),(3,3,3),(4,4,4); - CREATE TABLE t2(x); - SELECT * FROM t1; -} {1 1.0 1.0 2 2.0 2.0 3 3.0 3.0 4 4.0 4.0} - -# A rollback that does not affect the rtree table because -# the rtree table has not been written to does not cause -# a query abort. -# -do_test 1.7 { - set res {} - db eval { - BEGIN; - INSERT INTO t2(x) VALUES(12345); - } - db eval { SELECT * FROM t1 } { - if {$id==1} { - db eval { ROLLBACK } - } - lappend res $id $x1 $x2 - } - set res -} {1 1.0 1.0 2 2.0 2.0 3 3.0 3.0 4 4.0 4.0} - -# ROLLBACK TO that affects the RTREE does cause a query abort. -# -do_test 1.8 { - db eval { - DELETE FROM t1 WHERE rowid>1; - BEGIN; - DELETE FROM t2; - INSERT INTO t2(x) VALUES(23456); - SAVEPOINT 'one'; - INSERT INTO t1 VALUES(2,2,2),(3,3,3); - } - set rc [catch { - db eval { SELECT * FROM t1 } { - if {$id==1} { - db eval { ROLLBACK TO 'one'; } - } - lappend res $id $x1 $x2 - } - } msg] - list $rc $msg -} {1 {query aborted}} - -do_execsql_test 1.9 { - COMMIT; - SELECT * FROM t1; -} {1 1.0 1.0} - -# ROLLBACK TO that does not affect the RTREE does not cause a query abort. -# -do_execsql_test 1.10 { - DELETE FROM t1; - INSERT INTO t1 VALUES(1,1,1),(2,2,2),(3,3,3); - BEGIN; - DELETE FROM t2; - INSERT INTO t2(x) VALUES(34567); - SAVEPOINT 'one'; - INSERT INTO t2(x) VALUES('a string'); - SELECT * FROM t1; -} {1 1.0 1.0 2 2.0 2.0 3 3.0 3.0} -do_test 1.11 { - set rc [catch { - set res {} - db eval { SELECT * FROM t1 } { - if {$id==2} { - # db eval { ROLLBACK TO 'one'; } - } - lappend res $id $x1 $x2 - } - set res - } msg] - list $rc $msg -} {0 {1 1.0 1.0 2 2.0 2.0 3 3.0 3.0}} - -do_execsql_test 1.12 { - COMMIT; - SELECT * FROM t1; -} {1 1.0 1.0 2 2.0 2.0 3 3.0 3.0} - -#---------------------------------------------------------------------- - -reset_db -do_execsql_test 2.0 { - CREATE VIRTUAL TABLE t1 USING rtree(id, x1, x2); - INSERT INTO t1 VALUES(1, 1, 1), (2, 2, 2); - CREATE TABLE t2(x); -} {} - -do_test 2.1 { - db eval { - BEGIN; - INSERT INTO t1 VALUES(3, 3, 3); - PRAGMA writable_schema = RESET; - } - - set rc [catch { - db eval { SELECT x1, x2 FROM t1 } { - if {$x1==1} { - db eval { ROLLBACK } - } - lappend res $x1 $x2 - } - } msg] - list $rc $msg -} {1 {query aborted}} - -do_execsql_test 2.1 { - CREATE TABLE bak_node(nodeno, data); - CREATE TABLE bak_parent(nodeno, parentnode); - CREATE TABLE bak_rowid(rowid, nodeno); -} -proc save_t1 {} { - db eval { - DELETE FROM bak_node; - DELETE FROM bak_parent; - DELETE FROM bak_rowid; - INSERT INTO bak_node SELECT * FROM t1_node; - INSERT INTO bak_parent SELECT * FROM t1_parent; - INSERT INTO bak_rowid SELECT * FROM t1_rowid; - } -} -proc restore_t1 {} { - db eval { - DELETE FROM t1_node; - DELETE FROM t1_parent; - DELETE FROM t1_rowid; - INSERT INTO t1_node SELECT * FROM bak_node; - INSERT INTO t1_parent SELECT * FROM bak_parent; - INSERT INTO t1_rowid SELECT * FROM bak_rowid; - } -} - -do_test 2.3 { - save_t1 - db eval { - INSERT INTO t1 VALUES(3, 3, 3); - } - set rc [catch { - db eval { SELECT rowid, x1, x2 FROM t1 } { - if {$x1==1} { - restore_t1 - } - lappend res $x1 $x2 - } - } msg] - list $rc $msg -} {1 {query aborted}} -do_execsql_test 2.4 { - SELECT * FROM t1 -} {1 1.0 1.0 2 2.0 2.0} - -do_test 2.5 { - save_t1 - db eval { - INSERT INTO t1 VALUES(3, 3, 3); - } - set rc [catch { - db eval { SELECT x1 FROM t1 } { - if {$x1==1} { - restore_t1 - } - lappend res $x1 $x2 - } - } msg] - list $rc $msg -} {1 {query aborted}} -do_execsql_test 2.6 { - SELECT * FROM t1 -} {1 1.0 1.0 2 2.0 2.0} - -do_test 2.7 { - save_t1 - db eval { - INSERT INTO t1 VALUES(3, 3, 3); - } - set ::res [list] - set rc [catch { - db eval { SELECT 'abc' FROM t1 } { - if {$::res==[list]} { - restore_t1 - set ::bDone 1 - } - lappend res abc - } - } msg] - set res -} {abc abc abc} -do_execsql_test 2.6 { - SELECT * FROM t1 -} {1 1.0 1.0 2 2.0 2.0} - - -finish_test Index: ext/session/sessionstat1.test ================================================================== --- ext/session/sessionstat1.test +++ ext/session/sessionstat1.test @@ -90,11 +90,11 @@ ANALYZE; } } {} do_execsql_test -db db2 2.2 { - SELECT * FROM sqlite_stat1 ORDER BY tbl, idx + SELECT * FROM sqlite_stat1 } { t1 sqlite_autoindex_t1_1 {32 1} t1 t1b {32 4} t1 t1c {32 16} } @@ -102,11 +102,11 @@ do_test 2.3 { do_then_apply_sql -ignorenoop { DROP INDEX t1c } } {} do_execsql_test -db db2 2.4 { - SELECT * FROM sqlite_stat1 ORDER BY tbl, idx; + SELECT * FROM sqlite_stat1 } { t1 sqlite_autoindex_t1_1 {32 1} t1 t1b {32 4} } Index: ext/session/sqlite3session.c ================================================================== --- ext/session/sqlite3session.c +++ ext/session/sqlite3session.c @@ -2346,11 +2346,13 @@ /* Delete all attached table objects. And the contents of their ** associated hash-tables. */ sessionDeleteTable(pSession, pSession->pTable); - /* Free the session object. */ + /* Assert that all allocations have been freed and then free the + ** session object itself. */ + // assert( pSession->nMalloc==0 ); sqlite3_free(pSession); } /* ** Set a table filter on a Session Object. Index: ext/userauth/user-auth.txt ================================================================== --- ext/userauth/user-auth.txt +++ ext/userauth/user-auth.txt @@ -1,17 +1,5 @@ -*********************************** NOTICE ************************************ -* This extension is deprecated. The SQLite developers do not maintain this * -* extension. At some point in the future, it might disappear from the source * -* tree. * -* * -* If you are using this extension and think it should be supported moving * -* forward, visit the SQLite Forum (https://sqlite.org/forum) and argue your * -* case there. * -* * -* This deprecation notice was added on 2024-01-22. * -******************************************************************************* - Activate the user authentication logic by including the ext/userauth/userauth.c source code file in the build and adding the -DSQLITE_USER_AUTHENTICATION compile-time option. The ext/userauth/sqlite3userauth.h header file is available to applications to define the interface. Index: ext/wasm/GNUmakefile ================================================================== --- ext/wasm/GNUmakefile +++ ext/wasm/GNUmakefile @@ -40,55 +40,22 @@ # limited to: # # 1) Consolidate the code generation for sqlite3*.*js into a script # which generates the makefile code, rather than using $(call) and # $(eval), or at least centralize the setup of the numerous vars -# related to each build variant $(JS_BUILD_MODES). (Update: an -# external script was attempted but generating properly-escaped -# makefile code from within a shell script is even less legible -# than the $(eval) indirection going on in this file.) +# related to each build variant $(JS_BUILD_MODES). # default: all #default: quick SHELL := $(shell which bash 2>/dev/null) MAKEFILE := $(lastword $(MAKEFILE_LIST)) CLEAN_FILES := DISTCLEAN_FILES := ./--dummy-- release: oz - -######################################################################## -# JS_BUILD_NAMES exists for documentation purposes only. It enumerates -# the core build styles: -# -# - sqlite3 = canonical library build -# -# - sqlite3-wasmfs = WASMFS-capable library build -# -JS_BUILD_NAMES := sqlite3 sqlite3-wasmfs - -######################################################################## -# JS_BUILD_MODES exists for documentation purposes only. It enumerates -# the various "flavors" of build, each of which requires slight -# customization of the output: -# -# - vanilla = plain-vanilla JS for use in browsers. This is the -# canonical build mode. -# -# - esm = ES6 module, a.k.a. ESM, for use in browsers. -# -# - bundler-friendly = esm slightly tweaked for "bundler" -# tools. Bundlers are invariably based on node.js, so these builds -# are intended to be read at build-time by node.js but with a final -# target of browsers. -# -# - node = for use by node.js for node.js, as opposed to by node.js on -# behalf o browser-side code (use bundler-friendly for that). Note -# that persistent storage (OPFS) is not available in these builds. -# +# JS_BUILD_MODES exists solely to reduce repetition in documentation +# below. JS_BUILD_MODES := vanilla esm bunder-friendly node - -######################################################################## # Emscripten SDK home dir and related binaries... EMSDK_HOME ?= $(word 1,$(wildcard $(HOME)/emsdk $(HOME)/src/emsdk)) emcc.bin ?= $(word 1,$(wildcard $(EMSDK_HOME)/upstream/emscripten/emcc) $(shell which emcc)) ifeq (,$(emcc.bin)) $(error Cannot find emcc.) @@ -124,18 +91,15 @@ maybe-wasm-strip = echo "not wasm-stripping" else maybe-wasm-strip = $(wasm-strip) endif -######################################################################## -# dir.top = the top dir of the canonical build tree, where -# sqlite3.[ch] live. dir.top := ../.. -# Maintenance reminder: some Emscripten flags require absolute paths -# but we want relative paths for most stuff simply to reduce -# noise. The $(abspath...) GNU make function can transform relative -# paths to absolute. +# Reminder: some Emscripten flags require absolute paths but we want +# relative paths for most stuff simply to reduce noise. The +# $(abspath...) GNU make function can transform relative paths to +# absolute. dir.wasm := $(patsubst %/,%,$(dir $(MAKEFILE))) dir.api := api dir.jacc := jaccwabyt dir.common := common dir.fiddle := fiddle @@ -177,18 +141,16 @@ ######################################################################## # Set up sqlite3.c and sqlite3.h... # # To build with SEE (https://sqlite.org/see), either put sqlite3-see.c -# in $(dir.top) or pass sqlite3.c=PATH_TO_sqlite3-see.c to the $(MAKE) -# invocation. 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. Note, however, that distributing an SEE build -# of the WASM on a public site is in violation of the SEE license -# because it effectively provides a usable copy of the SEE build to -# all visitors. +# 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. @@ -229,14 +191,10 @@ # (and thus sqlite3_js_vfs_create_file()). Those functions are # deprecated and alternatives are in place, but this crash behavior # can be used to find errant uses of sqlite3_js_vfs_create_file() # in client code. -########################################################################@ -# It's important that sqlite3.h be built to completion before any -# other parts of the build run, thus we use .NOTPARALLEL to disable -# parallel build of that file and its dependants. .NOTPARALLEL: $(sqlite3.h) $(sqlite3.h): $(MAKE) -C $(dir.top) sqlite3.c $(sqlite3.c): $(sqlite3.h) @@ -282,36 +240,33 @@ ifneq (,$(sqlite3_wasm_extra_init.c)) $(info Enabling SQLITE_EXTRA_INIT via $(sqlite3_wasm_extra_init.c).) cflags.wasm_extra_init := -DSQLITE_WASM_EXTRA_INIT endif -######################################################################### # bin.version-info = binary to output various sqlite3 version info for # embedding in the JS files and in building the distribution zip file. # It must NOT be in $(dir.tmp) because we need it to survive the # cleanup process for the dist build to work properly. 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 -######################################################################### # bin.stripcomments is used for stripping C/C++-style comments from JS # files. The JS files contain large chunks of documentation which we # don't need for all builds. That app's -k flag is of particular # importance here, as it allows us to retain the opening comment -# block(s), which contain the license header and version info. +# blocks, which contain the license header and version info. bin.stripccomments := $(dir.tool)/stripccomments $(bin.stripccomments): $(bin.stripccomments).c $(MAKEFILE) $(CC) -o $@ $< DISTCLEAN_FILES += $(bin.stripccomments) ######################################################################## -# C-PP.FILTER: a $(call)able to transform $(1) to $(2) via: -# -# ./c-pp -f $(1) -o $(2) $(3) +# C-PP.FILTER: a $(call)able to transform $(1) to $(2) via ./c-pp -f +# $(1) ... # # Historical notes: # # - We first attempted to use gcc and/or clang to preprocess JS files # in the same way we would normally do C files, but C-specific quirks @@ -330,13 +285,10 @@ # seems likely to. # # c-pp.c was written specifically for the sqlite project's JavaScript # builds but is maintained as a standalone project: # https://fossil.wanderinghorse.net/r/c-pp -# -# Note that the SQLITE_... build flags used here have NO EFFECT on the -# JS/WASM build. They are solely for use with $(bin.c-pp) itself. bin.c-pp := ./c-pp $(bin.c-pp): c-pp.c $(sqlite3.c) $(MAKEFILE) $(CC) -O0 -o $@ c-pp.c $(sqlite3.c) '-DCMPP_DEFAULT_DELIM="//#"' -I$(dir.top) \ -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_UTF16 \ -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_WAL -DSQLITE_THREADSAFE=0 \ @@ -393,11 +345,10 @@ # Much practice has demonstrated that -O2 consistently gives the best # runtime speeds, but not by a large enough factor to rule out use of # -Oz when small deliverable size is a priority. ######################################################################## -######################################################################## # EXPORTED_FUNCTIONS.* = files for use with Emscripten's # -sEXPORTED_FUNCTION flag. EXPORTED_FUNCTIONS.api.main := $(abspath $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api) EXPORTED_FUNCTIONS.api.in := $(EXPORTED_FUNCTIONS.api.main) ifeq (1,$(SQLITE_C_IS_SEE)) @@ -405,11 +356,10 @@ endif EXPORTED_FUNCTIONS.api := $(dir.tmp)/EXPORTED_FUNCTIONS.api $(EXPORTED_FUNCTIONS.api): $(EXPORTED_FUNCTIONS.api.in) $(sqlite3.c) $(MAKEFILE) cat $(EXPORTED_FUNCTIONS.api.in) > $@ -######################################################################## # sqlite3-license-version.js = generated JS file with the license # header and version info. sqlite3-license-version.js := $(dir.tmp)/sqlite3-license-version.js # sqlite3-license-version-header.js = JS file containing only the # license header. @@ -418,50 +368,32 @@ # sqlite3.version object using $(bin.version-info). sqlite3-api-build-version.js := $(dir.tmp)/sqlite3-api-build-version.js # sqlite3-api.jses = the list of JS files which make up # $(sqlite3-api.js.in), in the order they need to be assembled. sqlite3-api.jses := $(sqlite3-license-version.js) -# sqlite3-api-prologue.js: initial boostrapping bits: sqlite3-api.jses += $(dir.api)/sqlite3-api-prologue.js -# whwhasm.js and jaccwabyt.js: Low-level utils, mostly replacing -# Emscripten glue: sqlite3-api.jses += $(dir.common)/whwasmutil.js sqlite3-api.jses += $(dir.jacc)/jaccwabyt.js -# sqlite3-api-glue.js Glues the previous part together: sqlite3-api.jses += $(dir.api)/sqlite3-api-glue.js -# $(sqlite3-api-build-version.js) = library version info sqlite3-api.jses += $(sqlite3-api-build-version.js) -# sqlite3-api-oo1.js = the oo1 API: sqlite3-api.jses += $(dir.api)/sqlite3-api-oo1.js -# sqlite3-api-worker.js = the Worker1 API: sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.js -# sqlite3-vfs-helper = helper APIs for VFSes: -sqlite3-api.jses += $(dir.api)/sqlite3-vfs-helper.c-pp.js -# sqlite3-vtab-helper = helper APIs for VTABLEs: -sqlite3-api.jses += $(dir.api)/sqlite3-vtab-helper.c-pp.js -# sqlite3-vfs-opfs.c-pp.js = the first OPFS VFS: +sqlite3-api.jses += $(dir.api)/sqlite3-v-helper.js sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js -# sqlite3-vfs-opfs-sahpool.c-pp.js = the second OPFS VFS: sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs-sahpool.c-pp.js -# sqlite3-api-cleanup.js = "finalizes" the build and cleans up -# any extraneous global symbols which are needed temporarily -# by the previous files. sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js -######################################################################## # SOAP.js is an external API file which is part of our distribution -# but not part of the sqlite3-api.js amalgamation. It's a component of -# the first OPFS VFS and necessarily an external file. +# but not part of the sqlite3-api.js amalgamation. SOAP.js := $(dir.api)/sqlite3-opfs-async-proxy.js SOAP.js.bld := $(dir.dout)/$(notdir $(SOAP.js)) -# -# $(sqlite3-api.ext.jses) = API-related files which are standalone files, -# not part of the amalgamation. -# -sqlite3-api.ext.jses := $(SOAP.js.bld) +sqlite3-api.ext.jses += $(SOAP.js.bld) $(SOAP.js.bld): $(SOAP.js) cp $< $@ + +all quick: $(sqlite3-api.ext.jses) +q: quick ######################################################################## # $(sqlite3-api*.*js) contain the core library code but not the # Emscripten-related glue which deals with loading sqlite3.wasm. In # theory they can be used by arbitrary build environments and WASM @@ -504,13 +436,11 @@ ######################################################################## # emcc flags for .c/.o. emcc.cflags := emcc.cflags += -std=c99 -fPIC -# -------------^^^^^^^^ we need c99 for $(sqlite3-wasm.c), primarily -# for variadic macros and snprintf() to implement -# sqlite3_wasm_enum_json(). +# -------------^^^^^^^^ we need c99 for $(sqlite3-wasm.c). emcc.cflags += -I. -I$(dir.top) ######################################################################## # emcc flags specific to building .js/.wasm files... emcc.jsflags := -fPIC emcc.jsflags += --minify 0 @@ -527,24 +457,18 @@ emcc.jsflags += -sUSE_CLOSURE_COMPILER=0 emcc.jsflags += -sIMPORTED_MEMORY emcc.jsflags += -sSTRICT_JS=0 # STRICT_JS disabled due to: # https://github.com/emscripten-core/emscripten/issues/18610 -# TL;DR: does not work with MODULARIZE or EXPORT_ES6 as of version -# 3.1.31. The fix for that in newer emcc's is to throw a built-time -# error if STRICT_JS is used together with those options. - -# emcc.jsflags += -sSTRICT=1 -# -sSTRICT=1 Causes failures about unknown symbols which the build -# tools should be installing, e.g. __syscall_geteuid32 +# TL;DR: does not work with MODULARIZE or EXPORT_ES6 as of version 3.1.31. # -sENVIRONMENT values for the various build modes: emcc.environment.vanilla := web,worker emcc.environment.bundler-friendly := $(emcc.environment.vanilla) emcc.environment.esm := $(emcc.environment.vanilla) emcc.environment.node := node -# Note that adding ",node" to the list for the other builds causes +# Note that adding "node" to the list for the other builds causes # Emscripten to generate code which confuses node: it cannot reliably # determine whether the build is for a browser or for node. ######################################################################## # -sINITIAL_MEMORY: How much memory we need to start with is governed @@ -555,15 +479,10 @@ # workloads MAY suffer considerably if we start small and have to grow # at runtime. e.g. OPFS-backed (speedtest1 --size 75) take MAY take X # time with 16mb+ memory and 3X time when starting with 8MB. However, # such test results are inconsistent due to browser internals which # are opaque to us. -# -# 2024-03-04: emsdk 3.1.55 replaces INITIAL_MEMORY with INITIAL_HEAP, -# but also says (in its changelog): "Note that it is currently not -# supported in all configurations (#21071)." -# https://github.com/emscripten-core/emscripten/blob/main/ChangeLog.md emcc.jsflags += -sALLOW_MEMORY_GROWTH emcc.INITIAL_MEMORY.128 := 134217728 emcc.INITIAL_MEMORY.96 := 100663296 emcc.INITIAL_MEMORY.64 := 67108864 emcc.INITIAL_MEMORY.32 := 33554432 @@ -597,18 +516,17 @@ # name "sqlite3InitModule" is the one which gets exposed via the # resulting JS files. That can be accomplished via # extern-post-js.js. However... using a temporary symbol name here # and then adding sqlite3InitModule() ourselves results in 2 global # symbols: we cannot "delete" the Emscripten-defined -# $(sqlite3.js.init-func) from vanilla builds (as opposed to ESM -# builds) because it's declared with "var". +# $(sqlite3.js.init-func) because it's declared with "var". sqlite3.js.init-func := sqlite3InitModule emcc.jsflags += -sEXPORT_NAME=$(sqlite3.js.init-func) emcc.jsflags += -sGLOBAL_BASE=4096 # HYPOTHETICALLY keep func table indexes from overlapping w/ heap addr. #emcc.jsflags += -sSTRICT # fails due to missing __syscall_...() #emcc.jsflags += -sALLOW_UNIMPLEMENTED_SYSCALLS -#emcc.jsflags += -sFILESYSTEM=0 # only for experimentation. fiddle needs the FS API +#emcc.jsflags += -sFILESYSTEM=0 # only for experimentation. sqlite3 needs the FS API #emcc.jsflags += -sABORTING_MALLOC # only for experimentation emcc.jsflags += -sALLOW_TABLE_GROWTH # ^^^^ -sALLOW_TABLE_GROWTH is required for installing new SQL UDFs emcc.jsflags += -Wno-limited-postlink-optimizations # ^^^^ emcc likes to warn when we have "limited optimizations" via the @@ -648,51 +566,37 @@ # cannot wasm-strip the binary before it gets encoded into the JS # file. The result is that the generated JS file is, because of the # -g3 debugging info, _huge_. ######################################################################## -######################################################################## -# $(sqlite3-api-build-version.js) injects the build version info into -# the bundle in JSON form. $(sqlite3-api-build-version.js): $(bin.version-info) $(MAKEFILE) @echo "Making $@..." @{ \ - echo 'globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){'; \ - echo -n ' sqlite3.version = '; \ - $(bin.version-info) --json; \ - echo ';'; \ - echo '});'; \ + echo 'globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){'; \ + echo -n ' sqlite3.version = '; \ + $(bin.version-info) --json; \ + echo ';'; \ + echo '});'; \ } > $@ - -######################################################################## -# $(sqlite3-license-version.js) contains the license header and -# in-comment build version info. -# -# Maintenance reminder: there are awk binaries out there which do not -# support -e SCRIPT. $(sqlite3-license-version.js): $(sqlite3.h) $(sqlite3-license-version-header.js) \ $(MAKEFILE) @echo "Making $@..."; { \ cat $(sqlite3-license-version-header.js); \ echo '/*'; \ echo '** This code was built from sqlite3 version...'; \ echo "**"; \ - awk '/define SQLITE_VERSION/{$$1=""; print "**" $$0}' $(sqlite3.h); \ - awk '/define SQLITE_SOURCE_ID/{$$1=""; print "**" $$0}' $(sqlite3.h); \ + awk -e '/define SQLITE_VERSION/{$$1=""; print "**" $$0}' \ + -e '/define SQLITE_SOURCE_ID/{$$1=""; print "**" $$0}' $(sqlite3.h); \ echo "**"; \ echo "** Using the Emscripten SDK version $(emcc.version)."; \ echo '*/'; \ } > $@ ######################################################################## # --post-js and --pre-js are emcc flags we use to append/prepend JS to # the generated emscripten module file. These rules set up the core -# pre/post files for use by the various builds. --pre-js is used to -# inject code which needs to run as part of the pre-WASM-load phase. -# --post-js injects code which runs after the WASM module is loaded -# and includes the entirety of the library plus some -# Emscripten-specific post-bootstrapping code. +# pre/post files for use by the various builds. pre-js.js.in := $(dir.api)/pre-js.c-pp.js post-js.js.in := $(dir.tmp)/post-js.c-pp.js post-jses.js := \ $(dir.api)/post-js-header.js \ $(sqlite3-api.js.in) \ @@ -706,30 +610,22 @@ done > $@ ######################################################################## # call-make-pre-post is a $(call)able which creates rules for -# pre-js.$(1)-$(2).js. $1 = the base name of the JS file on whose -# behalf this pre-js is for (one of: $(JS_BUILD_NAMES)). $2 is +# pre-js-$(1)-$(2).js. $1 = the base name of the JS file on whose +# behalf this pre-js is for (one of: sqlite3, sqlite3-wasmfs). $2 is # the build mode: one of $(JS_BUILD_MODES). This sets up # --[extern-][pre/post]-js flags in $(pre-post-$(1)-$(2).flags) and # dependencies in $(pre-post-$(1)-$(2).deps). The resulting files get # filtered using $(C-PP.FILTER). Any flags necessary for such # filtering need to be set in $(c-pp.D.$(1)-$(2)) before $(call)ing # this. -# -# Maintenance note: a shell script was written to generate these rules -# with the hope that it would make them more legible and maintainable, -# but embedding makefile code in another language makes it even less -# legible than having the level of $(eval) indirection which we have -# here. define call-make-pre-post pre-post-$(1)-$(2).flags ?= -pre-js.js.$(1)-$(2).intermediary := $$(dir.tmp)/pre-js.$(1)-$(2).intermediary.js -pre-js.js.$(1)-$(2) := $$(dir.tmp)/pre-js.$(1)-$(2).js -#$$(error $$(pre-js.js.$(1)-$(2).intermediary) $$(pre-js.js.$(1)-$(2))) -$$(eval $$(call C-PP.FILTER,$$(pre-js.js.in),$$(pre-js.js.$(1)-$(2).intermediary),$$(c-pp.D.$(1)-$(2)))) +pre-js.js.$(1)-$(2) := $$(dir.tmp)/pre-js.$(1)-$(2).intermediary.js +$$(eval $$(call C-PP.FILTER,$$(pre-js.js.in),$$(pre-js.js.$(1)-$(2)),$$(c-pp.D.$(1)-$(2)))) post-js.js.$(1)-$(2) := $$(dir.tmp)/post-js.$(1)-$(2).js $$(eval $$(call C-PP.FILTER,$$(post-js.js.in),$$(post-js.js.$(1)-$(2)),$$(c-pp.D.$(1)-$(2)))) extern-post-js.js.$(1)-$(2) := $$(dir.tmp)/extern-post-js.$(1)-$(2).js $$(eval $$(call C-PP.FILTER,$$(extern-post-js.js.in),$$(extern-post-js.js.$(1)-$(2)),$$(c-pp.D.$(1)-$(2)))) pre-post-common.flags.$(1)-$(2) := \ @@ -736,23 +632,23 @@ $$(pre-post-common.flags) \ --post-js=$$(post-js.js.$(1)-$(2)) \ --extern-post-js=$$(extern-post-js.js.$(1)-$(2)) pre-post-jses.$(1)-$(2).deps := $$(pre-post-jses.deps.common) \ $$(post-js.js.$(1)-$(2)) $$(extern-post-js.js.$(1)-$(2)) -$$(pre-js.js.$(1)-$(2)): $$(pre-js.js.$(1)-$(2).intermediary) $$(MAKEFILE) - cp $$(pre-js.js.$(1)-$(2).intermediary) $$@ +$$(dir.tmp)/pre-js-$(1)-$(2).js: $$(pre-js.js.$(1)-$(2)) $$(MAKEFILE) + cp $$(pre-js.js.$(1)-$(2)) $$@ @if [ sqlite3-wasmfs = $(1) ]; then \ echo "delete Module[xNameOfInstantiateWasm] /*for WASMFS build*/;"; \ elif [ sqlite3 != $(1) ]; then \ echo "Module[xNameOfInstantiateWasm].uri = '$(1).wasm';"; \ fi >> $$@ pre-post-$(1)-$(2).deps := \ $$(pre-post-jses.$(1)-$(2).deps) \ - $$(dir.tmp)/pre-js.$(1)-$(2).js + $$(dir.tmp)/pre-js-$(1)-$(2).js pre-post-$(1)-$(2).flags += \ $$(pre-post-common.flags.$(1)-$(2)) \ - --pre-js=$$(dir.tmp)/pre-js.$(1)-$(2).js + --pre-js=$$(dir.tmp)/pre-js-$(1)-$(2).js endef # /post-js and pre-js ######################################################################## # Undocumented Emscripten feature: if the target file extension is @@ -785,12 +681,12 @@ # instance (only) of /^export default/. # # Upstream RFE: # https://github.com/emscripten-core/emscripten/issues/18237 # -# Maintenance reminder: Mac sed works differently than GNU sed, so we -# use awk instead of sed for this. +# Maintenance reminder: Mac sed works differently than GNU sed, so +# don't use sed for this. define SQLITE3.xJS.ESM-EXPORT-DEFAULT if [ x1 = x$(1) ]; then \ echo "Fragile workaround for emscripten/issues/18237. See SQLITE3.xJS.RECIPE."; \ {\ awk '/^export default/ && !f{f=1; next} 1' $@ > $@.tmp && mv $@.tmp $@; \ @@ -802,11 +698,10 @@ fi; \ fi; \ fi endef -######################################################################## # extern-post-js* and extern-pre-js* are files for use with # Emscripten's --extern-pre-js and --extern-post-js flags. extern-pre-js.js := $(dir.api)/extern-pre-js.js extern-post-js.js.in := $(dir.api)/extern-post-js.c-pp.js # Emscripten flags for --[extern-][pre|post]-js=... for the @@ -814,28 +709,26 @@ pre-post-common.flags := \ --extern-pre-js=$(sqlite3-license-version.js) # pre-post-jses.deps.* = a list of dependencies for the # --[extern-][pre/post]-js files. pre-post-jses.deps.common := $(extern-pre-js.js) $(sqlite3-license-version.js) - ######################################################################## # SETUP_LIB_BUILD_MODE is a $(call)'able which sets up numerous pieces # for one of the build modes. # -# $1 = one of: $(JS_BUILD_NAMES) +# $1 = one of: sqlite3, sqlite3-wasmfs # $2 = build mode name: one of $(JS_BUILD_MODES) # $3 = 1 for ESM build mode, else 0 # $4 = resulting sqlite-api JS/MJS file # $5 = resulting JS/MJS file # $6 = -D... flags for $(bin.c-pp) -# $7 = optional extra flags for emcc +# $7 = emcc -sXYZ flags (CURRENTLY UNUSED - was factored out) # # Maintenance reminder: be careful not to introduce spaces around args # ($1, $2), otherwise string concatenation will malfunction. # -# Before calling this, emcc.environment.$(2) must be set to a value -# for emcc's -sENVIRONMENT flag. +# emcc.environment.$(2) must be set to a value for the -sENVIRONMENT flag. # # $(cflags.$(1)) and $(cflags.$(1).$(2)) may be defined to append # CFLAGS to a given build mode. # # $(emcc.flags.$(1)) and $(emcc.flags.$(1).$(2)) may be defined to @@ -886,11 +779,12 @@ sqlite3-node.mjs := $(dir.dout)/sqlite3-node.mjs #$(info $(call SETUP_LIB_BUILD_MODE,sqlite3,vanilla,0, $(sqlite3-api.js), $(sqlite3.js))) $(eval $(call SETUP_LIB_BUILD_MODE,sqlite3,vanilla,0,\ $(sqlite3-api.js), $(sqlite3.js))) $(eval $(call SETUP_LIB_BUILD_MODE,sqlite3,esm,1,\ - $(sqlite3-api.mjs), $(sqlite3.mjs), -Dtarget=es6-module)) + $(sqlite3-api.mjs), $(sqlite3.mjs), \ + -Dtarget=es6-module, -sEXPORT_ES6 -sUSE_ES6_IMPORT_META)) $(eval $(call SETUP_LIB_BUILD_MODE,sqlite3,bundler-friendly,1,\ $(sqlite3-api-bundler-friendly.mjs),$(sqlite3-bundler-friendly.mjs),\ $(c-pp.D.sqlite3-esm) -Dtarget=es6-bundler-friendly)) $(eval $(call SETUP_LIB_BUILD_MODE,sqlite3,node,1,\ $(sqlite3-api-node.mjs),$(sqlite3-node.mjs),\ @@ -902,11 +796,11 @@ # -Dtarget=node: for node.js builds # # -Dtarget=es6-module -Dtarget=es6-bundler-friendly: intended for # "bundler-friendly" ESM module build. These have some restrictions # on how URL() objects are constructed in some contexts: URLs which -# refer to files which are part of this project must be referenced +# refer to files which are part of this project must be references # as string literals so that bundlers' static-analysis tools can # find those files and include them in their bundles. # # -Dtarget=es6-module -Dtarget=es6-bundler-friendly -Dtarget=node: is # intended for use by node.js for node.js, as opposed to by @@ -938,52 +832,31 @@ # copy. sqlite3-worker1.js.in := $(dir.api)/sqlite3-worker1.c-pp.js sqlite3-worker1-promiser.js.in := $(dir.api)/sqlite3-worker1-promiser.c-pp.js sqlite3-worker1.js := $(dir.dout)/sqlite3-worker1.js sqlite3-worker1-promiser.js := $(dir.dout)/sqlite3-worker1-promiser.js -sqlite3-worker1-promiser.mjs := $(dir.dout)/sqlite3-worker1-promiser.mjs -sqlite3-worker1-bundler-friendly.mjs := $(dir.dout)/sqlite3-worker1-bundler-friendly.mjs +sqlite3-worker1-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-bundler-friendly.mjs sqlite3-worker1-promiser-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-promiser-bundler-friendly.js $(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1.js))) -$(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1-bundler-friendly.mjs),\ +$(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1-bundler-friendly.js),\ $(c-pp.D.sqlite3-bundler-friendly))) $(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),$(sqlite3-worker1-promiser.js))) $(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),\ $(sqlite3-worker1-promiser-bundler-friendly.js),\ $(c-pp.D.sqlite3-bundler-friendly))) -$(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),$(sqlite3-worker1-promiser.mjs),\ - -Dtarget=es6-module -Dtarget=es6-bundler-friendly)) -$(sqlite3-bundler-friendly.mjs): $(sqlite3-worker1-bundler-friendly.mjs) \ +$(sqlite3-bundler-friendly.mjs): $(sqlite3-worker1-bundler-friendly.js) \ $(sqlite3-worker1-promiser-bundler-friendly.js) -$(eval $(call C-PP.FILTER,demo-worker1-promiser.c-pp.js,demo-worker1-promiser.js)) -$(eval $(call C-PP.FILTER,demo-worker1-promiser.c-pp.js,demo-worker1-promiser.mjs,\ - -Dtarget=es6-module)) -$(eval $(call C-PP.FILTER,demo-worker1-promiser.c-pp.html,demo-worker1-promiser.html)) -$(eval $(call C-PP.FILTER,demo-worker1-promiser.c-pp.html,demo-worker1-promiser-esm.html,\ - -Dtarget=es6-module)) -all: $(sqlite3-worker1.js) \ - $(sqlite3-worker1-promiser.js) $(sqlite3-worker1-promiser.mjs) - -demo-worker1-promiser.html: $(sqlite3-worker1-promiser.js) demo-worker1-promiser.js -demo-worker1-promiser-esm.html: $(sqlite3-worker1-promiser.mjs) demo-worker1-promiser.mjs -all: demo-worker1-promiser.html demo-worker1-promiser-esm.html - -sqlite3-api.ext.jses += \ - $(sqlite3-worker1-promiser.mjs) \ - $(sqlite3-worker1-bundler-friendly.mjs) \ - $(sqlite3-worker1.js) -all quick: $(sqlite3-api.ext.jses) -q: quick +$(sqlite3.js) $(sqlite3.mjs): $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js) ######################################################################## # batch-runner.js is part of one of the test apps which reads in SQL # dumps generated by $(speedtest1) and executes them. dir.sql := sql speedtest1 := ../../speedtest1 speedtest1.c := ../../test/speedtest1.c speedtest1.sql := $(dir.sql)/speedtest1.sql -speedtest1.cliflags := --size 10 --big-transactions +speedtest1.cliflags := --size 25 --big-transactions $(speedtest1): $(MAKE) -C ../.. speedtest1 $(speedtest1.sql): $(speedtest1) $(MAKEFILE) $(speedtest1) $(speedtest1.cliflags) --script $@ batch-runner.list: $(MAKEFILE) $(speedtest1.sql) $(dir.sql)/000-mandelbrot.sql @@ -1011,13 +884,10 @@ emcc.speedtest1.common += --no-entry emcc.speedtest1.common += -sABORTING_MALLOC emcc.speedtest1.common += -sSTRICT_JS=0 emcc.speedtest1.common += -sMODULARIZE emcc.speedtest1.common += -Wno-limited-postlink-optimizations -emcc.speedtest1.common += -Wno-unused-main -# ^^^^ -Wno-unused-main is for emcc 3.1.52+. speedtest1 has a wasm_main() which is -# exported and called by the JS code. EXPORTED_FUNCTIONS.speedtest1 := $(abspath $(dir.tmp)/EXPORTED_FUNCTIONS.speedtest1) emcc.speedtest1.common += -sSTACK_SIZE=512KB emcc.speedtest1.common += -sEXPORTED_FUNCTIONS=@$(EXPORTED_FUNCTIONS.speedtest1) emcc.speedtest1.common += $(emcc.exportedRuntimeMethods) emcc.speedtest1.common += -sALLOW_TABLE_GROWTH @@ -1215,6 +1085,6 @@ include dist.make endif # Run local web server for the test/demo pages. httpd: - althttpd -max-age 1 -enable-sab 1 -page index.html + althttpd -max-age 1 -enable-sab -page index.html Index: ext/wasm/SQLTester/SQLTester.mjs ================================================================== --- ext/wasm/SQLTester/SQLTester.mjs +++ ext/wasm/SQLTester/SQLTester.mjs @@ -172,21 +172,15 @@ //! "Special" characters - we have to escape output if it contains any. special: /[\x00-\x20\x22\x5c\x7b\x7d]/, squiggly: /[{}]/ }); - - const Util = newObj({ toss, - unlink: function f(fn){ - if(!f.unlink){ - f.unlink = sqlite3.wasm.xWrap('sqlite3__wasm_vfs_unlink','int', - ['*','string']); - } - return 0==f.unlink(0,fn); + unlink: function(fn){ + return 0==sqlite3.wasm.sqlite3_wasm_vfs_unlink(0,fn); }, argvToString: (list)=>{ const m = [...list]; m.shift() /* strip command name */; @@ -201,11 +195,11 @@ ); }, utf8Encode: (str)=>__utf8Encoder.encode(str), - strglob: sqlite3.wasm.xWrap('sqlite3__wasm_SQLTester_strglob','int', + strglob: sqlite3.wasm.xWrap('sqlite3_wasm_SQLTester_strglob','int', ['string','string']) })/*Util*/; class Outer { #lnBuf = []; Index: ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api ================================================================== --- ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api +++ ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api @@ -61,11 +61,10 @@ _sqlite3_extended_result_codes _sqlite3_file_control _sqlite3_finalize _sqlite3_free _sqlite3_get_auxdata -_sqlite3_get_autocommit _sqlite3_initialize _sqlite3_keyword_count _sqlite3_keyword_name _sqlite3_keyword_check _sqlite3_last_insert_rowid Index: ext/wasm/api/README.md ================================================================== --- ext/wasm/api/README.md +++ ext/wasm/api/README.md @@ -76,16 +76,14 @@ - **`sqlite3-worker1-promiser.js`**\ Is likewise not part of the amalgamated sources and provides a Promise-based interface into the Worker #1 API. This is a far user-friendlier way to interface with databases running in a Worker thread. -- **`sqlite3-vfs-helper.js`**\ - Installs the `sqlite3.vfs` namespace, which contain helpers for use - by downstream code which creates `sqlite3_vfs` implementations. -- **`sqlite3-vtab-helper.js`**\ - Installs the `sqlite3.vtab` namespace, which contain helpers for use - by downstream code which creates `sqlite3_module` implementations. +- **`sqlite3-v-helper.js`**\ + Installs `sqlite3.vfs` and `sqlite3.vtab`, namespaces which contain + helpers for use by downstream code which creates `sqlite3_vfs` + and `sqlite3_module` implementations. - **`sqlite3-vfs-opfs.c-pp.js`**\ is an sqlite3 VFS implementation which supports the Origin-Private FileSystem (OPFS) as a storage layer to provide persistent storage for database files in a browser. It requires... - **`sqlite3-opfs-async-proxy.js`**\ Index: ext/wasm/api/post-js-header.js ================================================================== --- ext/wasm/api/post-js-header.js +++ ext/wasm/api/post-js-header.js @@ -17,12 +17,10 @@ - common/whwasmutil.js => Replacements for much of Emscripten's glue - jaccwaby/jaccwabyt.js => Jaccwabyt (C/JS struct binding) - sqlite3-api-glue.js => glues previous parts together - sqlite3-api-oo.js => SQLite3 OO API #1 - sqlite3-api-worker1.js => Worker-based API - - sqlite3-vfs-helper.c-pp.js => Utilities for VFS impls - - sqlite3-vtab-helper.c-pp.js => Utilities for virtual table impls - - sqlite3-vfs-opfs.c-pp.js => OPFS VFS - - sqlite3-vfs-opfs-sahpool.c-pp.js => OPFS SAHPool VFS + - sqlite3-vfs-helper.js => Internal-use utilities for... + - sqlite3-vfs-opfs.js => OPFS VFS - sqlite3-api-cleanup.js => final API cleanup - post-js-footer.js => closes this postRun() function */ Index: ext/wasm/api/sqlite3-api-glue.js ================================================================== --- ext/wasm/api/sqlite3-api-glue.js +++ ext/wasm/api/sqlite3-api-glue.js @@ -12,12 +12,11 @@ This file glues together disparate pieces of JS which are loaded in previous steps of the sqlite3-api.js bootstrapping process: sqlite3-api-prologue.js, whwasmutil.js, and jaccwabyt.js. It initializes the main API pieces so that the downstream components - (e.g. sqlite3-api-oo1.js) have all of the infrastructure that they - need. + (e.g. sqlite3-api-oo1.js) have all that they need. */ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 'use strict'; const toss = (...args)=>{throw new Error(args.join(' '))}; const toss3 = sqlite3.SQLite3Error.toss; @@ -187,11 +186,10 @@ ["sqlite3_extended_errcode", "int", "sqlite3*"], ["sqlite3_extended_result_codes", "int", "sqlite3*", "int"], ["sqlite3_file_control", "int", "sqlite3*", "string", "int", "*"], ["sqlite3_finalize", "int", "sqlite3_stmt*"], ["sqlite3_free", undefined,"*"], - ["sqlite3_get_autocommit", "int", "sqlite3*"], ["sqlite3_get_auxdata", "*", "sqlite3_context*", "int"], ["sqlite3_initialize", undefined], /*["sqlite3_interrupt", undefined, "sqlite3*" ^^^ we cannot actually currently support this because JS is single-threaded and we don't have a portable way to access a DB @@ -328,18 +326,10 @@ optional features into account. */ wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]); } if(wasm.exports.sqlite3_activate_see instanceof Function){ - /** - This code is capable of using an SEE build but note that an SEE - WASM build is generally incompatible with SEE's license - conditions. It is permitted for use internally in organizations - which have licensed SEE, but not for public sites because - exposing an SEE build of sqlite3.wasm effectively provides all - clients with a working copy of the commercial SEE code. - */ wasm.bindingSignatures.push( ["sqlite3_key", "int", "sqlite3*", "string", "int"], ["sqlite3_key_v2","int","sqlite3*","string","*","int"], ["sqlite3_rekey", "int", "sqlite3*", "string", "int"], ["sqlite3_rekey_v2", "int", "sqlite3*", "string", "*", "int"], @@ -348,12 +338,10 @@ } /** Functions which require BigInt (int64) support are separated from the others because we need to conditionally bind them or apply dummy impls, depending on the capabilities of the environment. - (That said: we never actually build without BigInt support, - and such builds are untested.) Note that not all of these functions directly require int64 but are only for use with APIs which require int64. For example, the vtab-related functions. */ @@ -368,14 +356,11 @@ ["sqlite3_declare_vtab", "int", ["sqlite3*", "string:flexible"]], ["sqlite3_deserialize", "int", "sqlite3*", "string", "*", "i64", "i64", "int"] /* Careful! Short version: de/serialize() are problematic because they might use a different allocator than the user for managing the deserialized block. de/serialize() are ONLY safe to use with - sqlite3_malloc(), sqlite3_free(), and its 64-bit variants. Because - of this, the canonical builds of sqlite3.wasm/js guarantee that - sqlite3.wasm.alloc() and friends use those allocators. Custom builds - may not guarantee that, however. */, + sqlite3_malloc(), sqlite3_free(), and its 64-bit variants. */, ["sqlite3_drop_modules", "int", ["sqlite3*", "**"]], ["sqlite3_last_insert_rowid", "i64", ["sqlite3*"]], ["sqlite3_malloc64", "*","i64"], ["sqlite3_msize", "i64", "*"], ["sqlite3_overload_function", "int", ["sqlite3*","string","int"]], @@ -434,10 +419,12 @@ ["sqlite3_vtab_rhs_value","int", "sqlite3_index_info*", "int", "**"] ]; // Add session/changeset APIs... if(wasm.bigIntEnabled && !!wasm.exports.sqlite3changegroup_add){ + /* ACHTUNG: 2022-12-23: the session/changeset API bindings are + COMPLETELY UNTESTED. */ /** FuncPtrAdapter options for session-related callbacks with the native signature "i(ps)". This proxy converts the 2nd argument from a C string to a JS string before passing the arguments on to the client-provided JS callback. @@ -611,25 +598,20 @@ }/*session/changeset APIs*/ /** Functions which are intended solely for API-internal use by the WASM components, not client code. These get installed into - sqlite3.util. Some of them get exposed to clients via variants - in sqlite3_js_...(). - - 2024-01-11: these were renamed, with two underscores in the - prefix, to ensure that clients do not accidentally depend on - them. They have always been documented as internal-use-only, so - no clients "should" be depending on the old names. + sqlite3.wasm. Some of them get exposed to clients via variants + named sqlite3_js_...(). */ - wasm.bindingSignatures.wasmInternal = [ - ["sqlite3__wasm_db_reset", "int", "sqlite3*"], - ["sqlite3__wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"], - ["sqlite3__wasm_vfs_create_file", "int", + wasm.bindingSignatures.wasm = [ + ["sqlite3_wasm_db_reset", "int", "sqlite3*"], + ["sqlite3_wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"], + ["sqlite3_wasm_vfs_create_file", "int", "sqlite3_vfs*","string","*", "int"], - ["sqlite3__wasm_posix_create_file", "int", "string","*", "int"], - ["sqlite3__wasm_vfs_unlink", "int", "sqlite3_vfs*","string"] + ["sqlite3_wasm_posix_create_file", "int", "string","*", "int"], + ["sqlite3_wasm_vfs_unlink", "int", "sqlite3_vfs*","string"] ]; /** Install JS<->C struct bindings for the non-opaque struct types we need... */ @@ -667,11 +649,11 @@ call and all future calls which are passed a string-equivalent argument. Use case: sqlite3_bind_pointer() and sqlite3_result_pointer() call for "a static string and preferably a string - literal." This converter is used to ensure that the string + literal". This converter is used to ensure that the string value seen by those functions is long-lived and behaves as they need it to. */ wasm.xWrap.argAdapter( 'string:static', @@ -689,19 +671,18 @@ wasm.bindingSignatures and (B) provide automatic conversion from higher-level representations, e.g. capi.sqlite3_vfs to `sqlite3_vfs*` via capi.sqlite3_vfs.pointer. */ const __xArgPtr = wasm.xWrap.argAdapter('*'); - const nilType = function(){ - /*a class which no value can ever be an instance of*/ - }; + const nilType = function(){}/*a class no value can ever be an instance of*/; wasm.xWrap.argAdapter('sqlite3_filename', __xArgPtr) ('sqlite3_context*', __xArgPtr) ('sqlite3_value*', __xArgPtr) ('void*', __xArgPtr) ('sqlite3_changegroup*', __xArgPtr) ('sqlite3_changeset_iter*', __xArgPtr) + //('sqlite3_rebaser*', __xArgPtr) ('sqlite3_session*', __xArgPtr) ('sqlite3_stmt*', (v)=> __xArgPtr((v instanceof (sqlite3?.oo1?.Stmt || nilType)) ? v.pointer : v)) ('sqlite3*', (v)=> @@ -758,12 +739,12 @@ ); } for(const e of wasm.bindingSignatures){ capi[e[0]] = wasm.xWrap.apply(null, e); } - for(const e of wasm.bindingSignatures.wasmInternal){ - util[e[0]] = wasm.xWrap.apply(null, e); + for(const e of wasm.bindingSignatures.wasm){ + wasm[e[0]] = wasm.xWrap.apply(null, e); } /* For C API functions which cannot work properly unless wasm.bigIntEnabled is true, install a bogus impl which throws if called when bigIntEnabled is false. The alternative would be @@ -781,13 +762,13 @@ /* There's no need to expose bindingSignatures to clients, implicitly making it part of the public interface. */ delete wasm.bindingSignatures; - if(wasm.exports.sqlite3__wasm_db_error){ + if(wasm.exports.sqlite3_wasm_db_error){ const __db_err = wasm.xWrap( - 'sqlite3__wasm_db_error', 'int', 'sqlite3*', 'int', 'string' + 'sqlite3_wasm_db_error', 'int', 'sqlite3*', 'int', 'string' ); /** Sets the given db's error state. Accepts: - (sqlite3*, int code, string msg) @@ -801,11 +782,11 @@ exception, the message string defaults to theError.message. Returns the resulting code. Pass (pDb,0,0) to clear the error state. */ - util.sqlite3__wasm_db_error = function(pDb, resultCode, message){ + util.sqlite3_wasm_db_error = function(pDb, resultCode, message){ if(resultCode instanceof sqlite3.WasmAllocError){ resultCode = capi.SQLITE_NOMEM; message = 0 /*avoid allocating message string*/; }else if(resultCode instanceof Error){ message = message || ''+resultCode; @@ -812,21 +793,21 @@ resultCode = (resultCode.resultCode || capi.SQLITE_ERROR); } return pDb ? __db_err(pDb, resultCode, message) : resultCode; }; }else{ - util.sqlite3__wasm_db_error = function(pDb,errCode,msg){ - console.warn("sqlite3__wasm_db_error() is not exported.",arguments); + util.sqlite3_wasm_db_error = function(pDb,errCode,msg){ + console.warn("sqlite3_wasm_db_error() is not exported.",arguments); return errCode; }; } }/*xWrap() bindings*/ {/* Import C-level constants and structs... */ - const cJson = wasm.xCall('sqlite3__wasm_enum_json'); + const cJson = wasm.xCall('sqlite3_wasm_enum_json'); if(!cJson){ - toss("Maintenance required: increase sqlite3__wasm_enum_json()'s", + toss("Maintenance required: increase sqlite3_wasm_enum_json()'s", "static buffer size!"); } //console.debug('wasm.ctype length =',wasm.cstrlen(cJson)); wasm.ctype = JSON.parse(wasm.cstrToJs(cJson)); // Groups of SQLITE_xyz macros... @@ -893,11 +874,11 @@ 'sqlite3_index_constraint_usage']){ capi.sqlite3_index_info[k] = capi[k]; delete capi[k]; } capi.sqlite3_vtab_config = wasm.xWrap( - 'sqlite3__wasm_vtab_config','int',[ + 'sqlite3_wasm_vtab_config','int',[ 'sqlite3*', 'int', 'int'] ); }/* end vtab-related setup */ }/*end C constant and struct imports*/ @@ -905,20 +886,20 @@ Internal helper to assist in validating call argument counts in the hand-written sqlite3_xyz() wrappers. We do this only for consistency with non-special-case wrappings. */ const __dbArgcMismatch = (pDb,f,n)=>{ - return util.sqlite3__wasm_db_error(pDb, capi.SQLITE_MISUSE, + return util.sqlite3_wasm_db_error(pDb, capi.SQLITE_MISUSE, f+"() requires "+n+" argument"+ (1===n?"":'s')+"."); }; /** Code duplication reducer for functions which take an encoding argument and require SQLITE_UTF8. Sets the db error code to SQLITE_FORMAT and returns that code. */ const __errEncoding = (pDb)=>{ - return util.sqlite3__wasm_db_error( + return util.sqlite3_wasm_db_error( pDb, capi.SQLITE_FORMAT, "SQLITE_UTF8 is the only supported encoding." ); }; /** @@ -1144,11 +1125,11 @@ if(0===rc && xCompare instanceof Function){ __dbCleanupMap.addCollation(pDb, zName); } return rc; }catch(e){ - return util.sqlite3__wasm_db_error(pDb, e); + return util.sqlite3_wasm_db_error(pDb, e); } }; capi.sqlite3_create_collation = (pDb,zName,eTextRep,pArg,xCompare)=>{ return (5===arguments.length) @@ -1270,11 +1251,11 @@ __dbCleanupMap.addFunction(pDb, funcName, nArg); } return rc; }catch(e){ console.error("sqlite3_create_function_v2() setup threw:",e); - return util.sqlite3__wasm_db_error(pDb, e, "Creation of UDF threw: "+e); + return util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e); } }; /* Documented in the api object's initializer. */ capi.sqlite3_create_function = function f( @@ -1315,11 +1296,11 @@ __dbCleanupMap.addWindowFunc(pDb, funcName, nArg); } return rc; }catch(e){ console.error("sqlite3_create_window_function() setup threw:",e); - return util.sqlite3__wasm_db_error(pDb, e, "Creation of UDF threw: "+e); + return util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e); } }; /** A _deprecated_ alias for capi.sqlite3_result_js() which predates the addition of that function in the public API. @@ -1410,11 +1391,11 @@ const [xSql, xSqlLen] = __flexiString(sql, sqlLen); switch(typeof xSql){ case 'string': return __prepare.basic(pDb, xSql, xSqlLen, prepFlags, ppStmt, null); case 'number': return __prepare.full(pDb, xSql, xSqlLen, prepFlags, ppStmt, pzTail); default: - return util.sqlite3__wasm_db_error( + return util.sqlite3_wasm_db_error( pDb, capi.SQLITE_MISUSE, "Invalid SQL argument type for sqlite3_prepare_v2/v3()." ); } }; @@ -1454,19 +1435,19 @@ p = wasm.allocFromTypedArray(text); n = text.byteLength; }else if('string'===typeof text){ [p, n] = wasm.allocCString(text); }else{ - return util.sqlite3__wasm_db_error( + return util.sqlite3_wasm_db_error( capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE, "Invalid 3rd argument type for sqlite3_bind_text()." ); } return __bindText(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC); }catch(e){ wasm.dealloc(p); - return util.sqlite3__wasm_db_error( + return util.sqlite3_wasm_db_error( capi.sqlite3_db_handle(pStmt), e ); } }/*sqlite3_bind_text()*/; @@ -1488,19 +1469,19 @@ p = wasm.allocFromTypedArray(pMem); n = nMem>=0 ? nMem : pMem.byteLength; }else if('string'===typeof pMem){ [p, n] = wasm.allocCString(pMem); }else{ - return util.sqlite3__wasm_db_error( + return util.sqlite3_wasm_db_error( capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE, "Invalid 3rd argument type for sqlite3_bind_blob()." ); } return __bindBlob(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC); }catch(e){ wasm.dealloc(p); - return util.sqlite3__wasm_db_error( + return util.sqlite3_wasm_db_error( capi.sqlite3_db_handle(pStmt), e ); } }/*sqlite3_bind_blob()*/; @@ -1520,15 +1501,15 @@ case capi.SQLITE_CONFIG_MEMSTATUS:// 9 /* boolean */ case capi.SQLITE_CONFIG_SMALL_MALLOC: // 27 /* boolean */ case capi.SQLITE_CONFIG_SORTERREF_SIZE: // 28 /* int nByte */ case capi.SQLITE_CONFIG_STMTJRNL_SPILL: // 26 /* int nByte */ case capi.SQLITE_CONFIG_URI:// 17 /* int */ - return wasm.exports.sqlite3__wasm_config_i(op, args[0]); + return wasm.exports.sqlite3_wasm_config_i(op, args[0]); case capi.SQLITE_CONFIG_LOOKASIDE: // 13 /* int int */ - return wasm.exports.sqlite3__wasm_config_ii(op, args[0], args[1]); + return wasm.exports.sqlite3_wasm_config_ii(op, args[0], args[1]); case capi.SQLITE_CONFIG_MEMDB_MAXSIZE: // 29 /* sqlite3_int64 */ - return wasm.exports.sqlite3__wasm_config_j(op, args[0]); + return wasm.exports.sqlite3_wasm_config_j(op, args[0]); case capi.SQLITE_CONFIG_GETMALLOC: // 5 /* sqlite3_mem_methods* */ case capi.SQLITE_CONFIG_GETMUTEX: // 11 /* sqlite3_mutex_methods* */ case capi.SQLITE_CONFIG_GETPCACHE2: // 19 /* sqlite3_pcache_methods2* */ case capi.SQLITE_CONFIG_GETPCACHE: // 15 /* no-op */ case capi.SQLITE_CONFIG_HEAP: // 8 /* void*, int nByte, int min */ @@ -1590,24 +1571,24 @@ const pKvvfs = capi.sqlite3_vfs_find("kvvfs"); if( pKvvfs ){/* kvvfs-specific glue */ if(util.isUIThread()){ const kvvfsMethods = new capi.sqlite3_kvvfs_methods( - wasm.exports.sqlite3__wasm_kvvfs_methods() + wasm.exports.sqlite3_wasm_kvvfs_methods() ); delete capi.sqlite3_kvvfs_methods; - const kvvfsMakeKey = wasm.exports.sqlite3__wasm_kvvfsMakeKeyOnPstack, + const kvvfsMakeKey = wasm.exports.sqlite3_wasm_kvvfsMakeKeyOnPstack, pstack = wasm.pstack; const kvvfsStorage = (zClass)=> ((115/*=='s'*/===wasm.peek(zClass)) ? sessionStorage : localStorage); /** Implementations for members of the object referred to by - sqlite3__wasm_kvvfs_methods(). We swap out the native + sqlite3_wasm_kvvfs_methods(). We swap out the native implementations with these, which use localStorage or sessionStorage for their backing store. */ const kvvfsImpls = { xRead: (zClass, zKey, zBuf, nBuf)=>{ @@ -1683,183 +1664,7 @@ be used that way but it's not really intended to be. */ capi.sqlite3_vfs_unregister(pKvvfs); } }/*pKvvfs*/ - /* Warn if client-level code makes use of FuncPtrAdapter. */ wasm.xWrap.FuncPtrAdapter.warnOnUse = true; - - const StructBinder = sqlite3.StructBinder - /* we require a local alias b/c StructBinder is removed from the sqlite3 - object during the final steps of the API cleanup. */; - /** - Installs a StructBinder-bound function pointer member of the - given name and function in the given StructBinder.StructType - target object. - - It creates a WASM proxy for the given function and arranges for - that proxy to be cleaned up when tgt.dispose() is called. Throws - on the slightest hint of error, e.g. tgt is-not-a StructType, - name does not map to a struct-bound member, etc. - - As a special case, if the given function is a pointer, then - `wasm.functionEntry()` is used to validate that it is a known - function. If so, it is used as-is with no extra level of proxying - or cleanup, else an exception is thrown. It is legal to pass a - value of 0, indicating a NULL pointer, with the caveat that 0 - _is_ a legal function pointer in WASM but it will not be accepted - as such _here_. (Justification: the function at address zero must - be one which initially came from the WASM module, not a method we - want to bind to a virtual table or VFS.) - - This function returns a proxy for itself which is bound to tgt - and takes 2 args (name,func). That function returns the same - thing as this one, permitting calls to be chained. - - If called with only 1 arg, it has no side effects but returns a - func with the same signature as described above. - - ACHTUNG: because we cannot generically know how to transform JS - exceptions into result codes, the installed functions do no - automatic catching of exceptions. It is critical, to avoid - undefined behavior in the C layer, that methods mapped via - this function do not throw. The exception, as it were, to that - rule is... - - If applyArgcCheck is true then each JS function (as opposed to - function pointers) gets wrapped in a proxy which asserts that it - is passed the expected number of arguments, throwing if the - argument count does not match expectations. That is only intended - for dev-time usage for sanity checking, and may leave the C - environment in an undefined state. - */ - const installMethod = function callee( - tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck - ){ - if(!(tgt instanceof StructBinder.StructType)){ - toss("Usage error: target object is-not-a StructType."); - }else if(!(func instanceof Function) && !wasm.isPtr(func)){ - toss("Usage errror: expecting a Function or WASM pointer to one."); - } - if(1===arguments.length){ - return (n,f)=>callee(tgt, n, f, applyArgcCheck); - } - if(!callee.argcProxy){ - callee.argcProxy = function(tgt, funcName, func,sig){ - return function(...args){ - if(func.length!==arguments.length){ - toss("Argument mismatch for", - tgt.structInfo.name+"::"+funcName - +": Native signature is:",sig); - } - return func.apply(this, args); - } - }; - /* An ondispose() callback for use with - StructBinder-created types. */ - callee.removeFuncList = function(){ - if(this.ondispose.__removeFuncList){ - this.ondispose.__removeFuncList.forEach( - (v,ndx)=>{ - if('number'===typeof v){ - try{wasm.uninstallFunction(v)} - catch(e){/*ignore*/} - } - /* else it's a descriptive label for the next number in - the list. */ - } - ); - delete this.ondispose.__removeFuncList; - } - }; - }/*static init*/ - const sigN = tgt.memberSignature(name); - if(sigN.length<2){ - toss("Member",name,"does not have a function pointer signature:",sigN); - } - const memKey = tgt.memberKey(name); - const fProxy = (applyArgcCheck && !wasm.isPtr(func)) - /** This middle-man proxy is only for use during development, to - confirm that we always pass the proper number of - arguments. We know that the C-level code will always use the - correct argument count. */ - ? callee.argcProxy(tgt, memKey, func, sigN) - : func; - if(wasm.isPtr(fProxy)){ - if(fProxy && !wasm.functionEntry(fProxy)){ - toss("Pointer",fProxy,"is not a WASM function table entry."); - } - tgt[memKey] = fProxy; - }else{ - const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true)); - tgt[memKey] = pFunc; - if(!tgt.ondispose || !tgt.ondispose.__removeFuncList){ - tgt.addOnDispose('ondispose.__removeFuncList handler', - callee.removeFuncList); - tgt.ondispose.__removeFuncList = []; - } - tgt.ondispose.__removeFuncList.push(memKey, pFunc); - } - return (n,f)=>callee(tgt, n, f, applyArgcCheck); - }/*installMethod*/; - installMethod.installMethodArgcCheck = false; - - /** - Installs methods into the given StructBinder.StructType-type - instance. Each entry in the given methods object must map to a - known member of the given StructType, else an exception will be - triggered. See installMethod() for more details, including the - semantics of the 3rd argument. - - As an exception to the above, if any two or more methods in the - 2nd argument are the exact same function, installMethod() is - _not_ called for the 2nd and subsequent instances, and instead - those instances get assigned the same method pointer which is - created for the first instance. This optimization is primarily to - accommodate special handling of sqlite3_module::xConnect and - xCreate methods. - - On success, returns its first argument. Throws on error. - */ - const installMethods = function( - structInstance, methods, applyArgcCheck = installMethod.installMethodArgcCheck - ){ - const seen = new Map /* map of */; - for(const k of Object.keys(methods)){ - const m = methods[k]; - const prior = seen.get(m); - if(prior){ - const mkey = structInstance.memberKey(k); - structInstance[mkey] = structInstance[structInstance.memberKey(prior)]; - }else{ - installMethod(structInstance, k, m, applyArgcCheck); - seen.set(m, k); - } - } - return structInstance; - }; - - /** - Equivalent to calling installMethod(this,...arguments) with a - first argument of this object. If called with 1 or 2 arguments - and the first is an object, it's instead equivalent to calling - installMethods(this,...arguments). - */ - StructBinder.StructType.prototype.installMethod = function callee( - name, func, applyArgcCheck = installMethod.installMethodArgcCheck - ){ - return (arguments.length < 3 && name && 'object'===typeof name) - ? installMethods(this, ...arguments) - : installMethod(this, ...arguments); - }; - - /** - Equivalent to calling installMethods() with a first argument - of this object. - */ - StructBinder.StructType.prototype.installMethods = function( - methods, applyArgcCheck = installMethod.installMethodArgcCheck - ){ - return installMethods(this, methods, applyArgcCheck); - }; - }); Index: ext/wasm/api/sqlite3-api-oo1.js ================================================================== --- ext/wasm/api/sqlite3-api-oo1.js +++ ext/wasm/api/sqlite3-api-oo1.js @@ -1,6 +1,5 @@ -//#ifnot omit-oo1 /* 2022-07-22 The author disclaims copyright to this source code. In place of a legal notice, here is a blessing: @@ -1939,8 +1938,6 @@ return jdb.storageSize(affirmDbOpen(this).filename); }; }/*main-window-only bits*/ }); -//#else -/* Built with the omit-oo1 flag. */ -//#endif ifnot omit-oo1 + Index: ext/wasm/api/sqlite3-api-prologue.js ================================================================== --- ext/wasm/api/sqlite3-api-prologue.js +++ ext/wasm/api/sqlite3-api-prologue.js @@ -35,11 +35,11 @@ for use in creating bundles configured for specific WASM environments. This function expects a configuration object, intended to abstract away details specific to any given WASM environment, primarily so - that it can be used without any direct dependency on + that it can be used without any _direct_ dependency on Emscripten. (Note the default values for the config object!) The config object is only honored the first time this is called. Subsequent calls ignore the argument and return the same (configured) object which gets initialized by the first call. This function will throw if any of the required config options are @@ -96,59 +96,28 @@ this function calls that function to fetch the value, enabling delayed evaluation. The returned object is the top-level sqlite3 namespace object. - - Client code may optionally assign sqlite3ApiBootstrap.defaultConfig - an object-type value before calling sqlite3ApiBootstrap() (without - arguments) in order to tell that call to use this object as its - default config value. The intention of this is to provide - downstream clients with a reasonably flexible approach for plugging - in an environment-suitable configuration without having to define a - new global-scope symbol. - - However, because clients who access this library via an - Emscripten-hosted module will not have an opportunity to call - sqlite3ApiBootstrap() themselves, nor to access it before it is - called, an alternative option for setting the configuration is to - define globalThis.sqlite3ApiConfig to an object. If it is set, it - is used instead of sqlite3ApiBootstrap.defaultConfig if - sqlite3ApiBootstrap() is called without arguments. - - Both sqlite3ApiBootstrap.defaultConfig and - globalThis.sqlite3ApiConfig get deleted by sqlite3ApiBootstrap() - because any changes to them made after that point would have no - useful effect. */ 'use strict'; globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( apiConfig = (globalThis.sqlite3ApiConfig || sqlite3ApiBootstrap.defaultConfig) ){ if(sqlite3ApiBootstrap.sqlite3){ /* already initalized */ - (sqlite3ApiBootstrap.sqlite3.config || console).warn( - "sqlite3ApiBootstrap() called multiple times.", - "Config and external initializers are ignored on calls after the first." - ); + console.warn("sqlite3ApiBootstrap() called multiple times.", + "Config and external initializers are ignored on calls after the first."); return sqlite3ApiBootstrap.sqlite3; } const config = Object.assign(Object.create(null),{ exports: undefined, memory: undefined, bigIntEnabled: (()=>{ if('undefined'!==typeof Module){ /* Emscripten module will contain HEAPU64 when built with - -sWASM_BIGINT=1, else it will not. - - As of emsdk 3.1.55, when building in strict mode, HEAPxyz - are only available if _explicitly_ included in the exports, - else they are not. We do not (as of 2024-03-04) use -sSTRICT - for the canonical builds. - */ - if( !!Module.HEAPU64 ) return true; - /* Else fall through and hope for the best. Nobody _really_ - builds this without BigInt support, do they? */ + -sWASM_BIGINT=1, else it will not. */ + return !!Module.HEAPU64; } return !!globalThis.BigInt64Array; })(), debug: console.debug.bind(console), warn: console.warn.bind(console), @@ -178,19 +147,10 @@ ].forEach((k)=>{ if('function' === typeof config[k]){ config[k] = config[k](); } }); - - /** - Eliminate any confusion about whether these config objects may - be used after library initialization by eliminating the outward-facing - objects... - */ - delete globalThis.sqlite3ApiConfig; - delete sqlite3ApiBootstrap.defaultConfig; - /** The main sqlite3 binding API gets installed into this object, mimicking the C API as closely as we can. The numerous members names with prefixes 'sqlite3_' and 'SQLITE_' behave, insofar as possible, identically to the C-native counterparts, as documented at: @@ -1099,11 +1059,11 @@ /** Sets the current pstack position to the given pointer. Results are undefined if the passed-in value did not come from this.pointer. */ - restore: wasm.exports.sqlite3__wasm_pstack_restore, + restore: wasm.exports.sqlite3_wasm_pstack_restore, /** Attempts to allocate the given number of bytes from the pstack. On success, it zeroes out a block of memory of the given size, adjusts the pstack pointer, and returns a pointer to the memory. On error, throws a WasmAllocError. The @@ -1121,11 +1081,11 @@ */ alloc: function(n){ if('string'===typeof n && !(n = wasm.sizeofIR(n))){ WasmAllocError.toss("Invalid value for pstack.alloc(",arguments[0],")"); } - return wasm.exports.sqlite3__wasm_pstack_alloc(n) + return wasm.exports.sqlite3_wasm_pstack_alloc(n) || WasmAllocError.toss("Could not allocate",n, "bytes from the pstack."); }, /** alloc()'s n chunks, each sz bytes, as a single memory block and @@ -1201,31 +1161,31 @@ first reserving it via wasm.pstack.alloc() and friends, leads to undefined results. */ pointer: { configurable: false, iterable: true, writeable: false, - get: wasm.exports.sqlite3__wasm_pstack_ptr + get: wasm.exports.sqlite3_wasm_pstack_ptr //Whether or not a setter as an alternative to restore() is //clearer or would just lead to confusion is unclear. - //set: wasm.exports.sqlite3__wasm_pstack_restore + //set: wasm.exports.sqlite3_wasm_pstack_restore }, /** sqlite3.wasm.pstack.quota to the total number of bytes available in the pstack, including any space which is currently allocated. This value is a compile-time constant. */ quota: { configurable: false, iterable: true, writeable: false, - get: wasm.exports.sqlite3__wasm_pstack_quota + get: wasm.exports.sqlite3_wasm_pstack_quota }, /** sqlite3.wasm.pstack.remaining resolves to the amount of space remaining in the pstack. */ remaining: { configurable: false, iterable: true, writeable: false, - get: wasm.exports.sqlite3__wasm_pstack_remaining + get: wasm.exports.sqlite3_wasm_pstack_remaining } })/*wasm.pstack properties*/; capi.sqlite3_randomness = (...args)=>{ if(1===args.length && util.isTypedArray(args[0]) @@ -1294,18 +1254,18 @@ || !globalThis.FileSystemFileHandle){ return __wasmfsOpfsDir = ""; } try{ if(pdir && 0===wasm.xCallWrapped( - 'sqlite3__wasm_init_wasmfs', 'i32', ['string'], pdir + 'sqlite3_wasm_init_wasmfs', 'i32', ['string'], pdir )){ return __wasmfsOpfsDir = pdir; }else{ return __wasmfsOpfsDir = ""; } }catch(e){ - // sqlite3__wasm_init_wasmfs() is not available + // sqlite3_wasm_init_wasmfs() is not available return __wasmfsOpfsDir = ""; } }; /** @@ -1403,11 +1363,11 @@ memory boundary! */ const zSchema = schema ? (wasm.isPtr(schema) ? schema : wasm.scopedAllocCString(''+schema)) : 0; - let rc = wasm.exports.sqlite3__wasm_db_serialize( + let rc = wasm.exports.sqlite3_wasm_db_serialize( pDb, zSchema, ppOut, pSize, 0 ); if(rc){ toss3("Database serialization failed with code", sqlite3.capi.sqlite3_js_rc_str(rc)); @@ -1429,11 +1389,11 @@ C-string pointer, which may be 0), returns a pointer to the sqlite3_vfs responsible for it. If the given db name is null/0, or not provided, then "main" is assumed. */ capi.sqlite3_js_db_vfs = - (dbPointer, dbName=0)=>util.sqlite3__wasm_db_vfs(dbPointer, dbName); + (dbPointer, dbName=0)=>wasm.sqlite3_wasm_db_vfs(dbPointer, dbName); /** A thin wrapper around capi.sqlite3_aggregate_context() which behaves the same except that it throws a WasmAllocError if that function returns 0. As a special case, if n is falsy it does @@ -1487,11 +1447,11 @@ } try{ if(!util.isInt32(dataLen) || dataLen<0){ SQLite3Error.toss("Invalid 3rd argument for sqlite3_js_posix_create_file()."); } - const rc = util.sqlite3__wasm_posix_create_file(filename, pData, dataLen); + const rc = wasm.sqlite3_wasm_posix_create_file(filename, pData, dataLen); if(rc) SQLite3Error.toss("Creation of file failed with sqlite3 result code", capi.sqlite3_js_rc_str(rc)); }finally{ wasm.dealloc(pData); } @@ -1589,11 +1549,11 @@ if(!util.isInt32(dataLen) || dataLen<0){ wasm.dealloc(pData); SQLite3Error.toss("Invalid 4th argument for sqlite3_js_vfs_create_file()."); } try{ - const rc = util.sqlite3__wasm_vfs_create_file(vfs, filename, pData, dataLen); + const rc = wasm.sqlite3_wasm_vfs_create_file(vfs, filename, pData, dataLen); if(rc) SQLite3Error.toss("Creation of file failed with sqlite3 result code", capi.sqlite3_js_rc_str(rc)); }finally{ wasm.dealloc(pData); } @@ -1710,16 +1670,16 @@ The variants which take `(int, int*)` arguments treat a missing or falsy pointer argument as 0. */ capi.sqlite3_db_config = function(pDb, op, ...args){ if(!this.s){ - this.s = wasm.xWrap('sqlite3__wasm_db_config_s','int', + this.s = wasm.xWrap('sqlite3_wasm_db_config_s','int', ['sqlite3*', 'int', 'string:static'] /* MAINDBNAME requires a static string */); - this.pii = wasm.xWrap('sqlite3__wasm_db_config_pii', 'int', + this.pii = wasm.xWrap('sqlite3_wasm_db_config_pii', 'int', ['sqlite3*', 'int', '*','int', 'int']); - this.ip = wasm.xWrap('sqlite3__wasm_db_config_ip','int', + this.ip = wasm.xWrap('sqlite3_wasm_db_config_ip','int', ['sqlite3*', 'int', 'int','*']); } switch(op){ case capi.SQLITE_DBCONFIG_ENABLE_FKEY: case capi.SQLITE_DBCONFIG_ENABLE_TRIGGER: Index: ext/wasm/api/sqlite3-api-worker1.js ================================================================== --- ext/wasm/api/sqlite3-api-worker1.js +++ ext/wasm/api/sqlite3-api-worker1.js @@ -1,6 +1,5 @@ -//#ifnot omit-oo1 /** 2022-07-22 The author disclaims copyright to this source code. In place of a legal notice, here is a blessing: @@ -61,11 +60,11 @@ Each message posted to the worker has an operation-independent envelope and operation-dependent arguments: ``` { - type: string, // one of: 'open', 'close', 'exec', 'export', 'config-get' + type: string, // one of: 'open', 'close', 'exec', 'config-get' messageId: OPTIONAL arbitrary value. The worker will copy it as-is into response messages to assist in client-side dispatching. dbId: a db identifier string (returned by 'open') which tells the @@ -324,44 +323,12 @@ The response is the input options object (or a synthesized one if passed only a string), noting that options.resultRows and options.columnNames may be populated by the call to db.exec(). - - ==================================================================== - "export" the current db - - To export the underlying database as a byte array... - - Message format: - - ``` - { - type: "export", - messageId: ...as above..., - dbId: ...as above... - } - ``` - - Response: - - ``` - { - type: "export", - messageId: ...as above..., - dbId: ...as above... - result: { - byteArray: Uint8Array (as per sqlite3_js_db_export()), - filename: the db filename, - mimetype: "application/x-sqlite3" - } - } - ``` - */ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ -const util = sqlite3.util; sqlite3.initWorker1API = function(){ 'use strict'; const toss = (...args)=>{throw new Error(args.join(' '))}; if(!(globalThis.WorkerGlobalScope instanceof Function)){ toss("initWorker1API() must be run from a Worker thread."); @@ -408,16 +375,16 @@ }, close: function(db,alsoUnlink){ if(db){ delete this.dbs[getDbId(db)]; const filename = db.filename; - const pVfs = util.sqlite3__wasm_db_vfs(db.pointer, 0); + const pVfs = sqlite3.wasm.sqlite3_wasm_db_vfs(db.pointer, 0); db.close(); const ddNdx = this.dbList.indexOf(db); if(ddNdx>=0) this.dbList.splice(ddNdx, 1); if(alsoUnlink && filename && pVfs){ - util.sqlite3__wasm_vfs_unlink(pVfs, filename); + sqlite3.wasm.sqlite3_wasm_vfs_unlink(pVfs, filename); } } }, /** Posts the given worker message value. If xferList is provided, @@ -494,16 +461,16 @@ byteArray = args.byteArray; if(byteArray) pVfs = guessVfs(args.filename); } if(pVfs){ /* 2022-11-02: this feature is as-yet untested except that - sqlite3__wasm_vfs_create_file() has been tested from the + sqlite3_wasm_vfs_create_file() has been tested from the browser dev console. */ let pMem; try{ pMem = sqlite3.wasm.allocFromTypedArray(byteArray); - const rc = util.sqlite3__wasm_vfs_create_file( + const rc = sqlite3.wasm.sqlite3_wasm_vfs_create_file( pVfs, oargs.filename, pMem, byteArray.byteLength ); if(rc) sqlite3.SQLite3Error.toss(rc); }catch(e){ throw new sqlite3.SQLite3Error( @@ -689,8 +656,5 @@ }, wState.xfer); }; globalThis.postMessage({type:'sqlite3-api',result:'worker1-ready'}); }.bind({sqlite3}); }); -//#else -/* Built with the omit-oo1 flag. */ -//#endif ifnot omit-oo1 Index: ext/wasm/api/sqlite3-opfs-async-proxy.js ================================================================== --- ext/wasm/api/sqlite3-opfs-async-proxy.js +++ ext/wasm/api/sqlite3-opfs-async-proxy.js @@ -49,11 +49,11 @@ versions (approximately) 104-107 are extinct) should change our usage of those methods to remove the "await". */ "use strict"; const wPost = (type,...args)=>postMessage({type, payload:args}); -const installAsyncProxy = function(){ +const installAsyncProxy = function(self){ const toss = function(...args){throw new Error(args.join(' '))}; if(globalThis.window === globalThis){ toss("This code cannot run from the main thread.", "Load it as a Worker from a separate Worker."); }else if(!navigator?.storage?.getDirectory){ @@ -560,18 +560,10 @@ storeAndNotify(opName, state.sq3Codes.SQLITE_NOTFOUND); mTimeEnd(); wTimeEnd(); return; } - if( state.opfsFlags.OPFS_UNLINK_BEFORE_OPEN & opfsFlags ){ - try{ - await hDir.removeEntry(filenamePart); - }catch(e){ - /* ignoring */ - //warn("Ignoring failed Unlink of",filename,":",e); - } - } const hFile = await hDir.getFileHandle(filenamePart, {create}); wTimeEnd(); const fh = Object.assign(Object.create(null),{ fid: fid, filenameAbs: filename, @@ -917,7 +909,7 @@ !globalThis.FileSystemFileHandle || !globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle || !navigator?.storage?.getDirectory){ wPost('opfs-unavailable',"Missing required OPFS APIs."); }else{ - installAsyncProxy(); + installAsyncProxy(self); } ADDED ext/wasm/api/sqlite3-v-helper.js Index: ext/wasm/api/sqlite3-v-helper.js ================================================================== --- /dev/null +++ ext/wasm/api/sqlite3-v-helper.js @@ -0,0 +1,718 @@ +/* +** 2022-11-30 +** +** The author disclaims copyright to this source code. In place of a +** legal notice, here is a blessing: +** +** * May you do good and not evil. +** * May you find forgiveness for yourself and forgive others. +** * May you share freely, never taking more than you give. +*/ + +/** + This file installs sqlite3.vfs, and object which exists to assist + in the creation of JavaScript implementations of sqlite3_vfs, along + with its virtual table counterpart, sqlite3.vtab. +*/ +'use strict'; +globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ + const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3; + const vfs = Object.create(null), vtab = Object.create(null); + + const StructBinder = sqlite3.StructBinder + /* we require a local alias b/c StructBinder is removed from the sqlite3 + object during the final steps of the API cleanup. */; + sqlite3.vfs = vfs; + sqlite3.vtab = vtab; + + const sii = capi.sqlite3_index_info; + /** + If n is >=0 and less than this.$nConstraint, this function + returns either a WASM pointer to the 0-based nth entry of + this.$aConstraint (if passed a truthy 2nd argument) or an + sqlite3_index_info.sqlite3_index_constraint object wrapping that + address (if passed a falsy value or no 2nd argument). Returns a + falsy value if n is out of range. + */ + sii.prototype.nthConstraint = function(n, asPtr=false){ + if(n<0 || n>=this.$nConstraint) return false; + const ptr = this.$aConstraint + ( + sii.sqlite3_index_constraint.structInfo.sizeof * n + ); + return asPtr ? ptr : new sii.sqlite3_index_constraint(ptr); + }; + + /** + Works identically to nthConstraint() but returns state from + this.$aConstraintUsage, so returns an + sqlite3_index_info.sqlite3_index_constraint_usage instance + if passed no 2nd argument or a falsy 2nd argument. + */ + sii.prototype.nthConstraintUsage = function(n, asPtr=false){ + if(n<0 || n>=this.$nConstraint) return false; + const ptr = this.$aConstraintUsage + ( + sii.sqlite3_index_constraint_usage.structInfo.sizeof * n + ); + return asPtr ? ptr : new sii.sqlite3_index_constraint_usage(ptr); + }; + + /** + If n is >=0 and less than this.$nOrderBy, this function + returns either a WASM pointer to the 0-based nth entry of + this.$aOrderBy (if passed a truthy 2nd argument) or an + sqlite3_index_info.sqlite3_index_orderby object wrapping that + address (if passed a falsy value or no 2nd argument). Returns a + falsy value if n is out of range. + */ + sii.prototype.nthOrderBy = function(n, asPtr=false){ + if(n<0 || n>=this.$nOrderBy) return false; + const ptr = this.$aOrderBy + ( + sii.sqlite3_index_orderby.structInfo.sizeof * n + ); + return asPtr ? ptr : new sii.sqlite3_index_orderby(ptr); + }; + + /** + Installs a StructBinder-bound function pointer member of the + given name and function in the given StructType target object. + + It creates a WASM proxy for the given function and arranges for + that proxy to be cleaned up when tgt.dispose() is called. Throws + on the slightest hint of error, e.g. tgt is-not-a StructType, + name does not map to a struct-bound member, etc. + + As a special case, if the given function is a pointer, then + `wasm.functionEntry()` is used to validate that it is a known + function. If so, it is used as-is with no extra level of proxying + or cleanup, else an exception is thrown. It is legal to pass a + value of 0, indicating a NULL pointer, with the caveat that 0 + _is_ a legal function pointer in WASM but it will not be accepted + as such _here_. (Justification: the function at address zero must + be one which initially came from the WASM module, not a method we + want to bind to a virtual table or VFS.) + + This function returns a proxy for itself which is bound to tgt + and takes 2 args (name,func). That function returns the same + thing as this one, permitting calls to be chained. + + If called with only 1 arg, it has no side effects but returns a + func with the same signature as described above. + + ACHTUNG: because we cannot generically know how to transform JS + exceptions into result codes, the installed functions do no + automatic catching of exceptions. It is critical, to avoid + undefined behavior in the C layer, that methods mapped via + this function do not throw. The exception, as it were, to that + rule is... + + If applyArgcCheck is true then each JS function (as opposed to + function pointers) gets wrapped in a proxy which asserts that it + is passed the expected number of arguments, throwing if the + argument count does not match expectations. That is only intended + for dev-time usage for sanity checking, and will leave the C + environment in an undefined state. + */ + const installMethod = function callee( + tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck + ){ + if(!(tgt instanceof StructBinder.StructType)){ + toss("Usage error: target object is-not-a StructType."); + }else if(!(func instanceof Function) && !wasm.isPtr(func)){ + toss("Usage errror: expecting a Function or WASM pointer to one."); + } + if(1===arguments.length){ + return (n,f)=>callee(tgt, n, f, applyArgcCheck); + } + if(!callee.argcProxy){ + callee.argcProxy = function(tgt, funcName, func,sig){ + return function(...args){ + if(func.length!==arguments.length){ + toss("Argument mismatch for", + tgt.structInfo.name+"::"+funcName + +": Native signature is:",sig); + } + return func.apply(this, args); + } + }; + /* An ondispose() callback for use with + StructBinder-created types. */ + callee.removeFuncList = function(){ + if(this.ondispose.__removeFuncList){ + this.ondispose.__removeFuncList.forEach( + (v,ndx)=>{ + if('number'===typeof v){ + try{wasm.uninstallFunction(v)} + catch(e){/*ignore*/} + } + /* else it's a descriptive label for the next number in + the list. */ + } + ); + delete this.ondispose.__removeFuncList; + } + }; + }/*static init*/ + const sigN = tgt.memberSignature(name); + if(sigN.length<2){ + toss("Member",name,"does not have a function pointer signature:",sigN); + } + const memKey = tgt.memberKey(name); + const fProxy = (applyArgcCheck && !wasm.isPtr(func)) + /** This middle-man proxy is only for use during development, to + confirm that we always pass the proper number of + arguments. We know that the C-level code will always use the + correct argument count. */ + ? callee.argcProxy(tgt, memKey, func, sigN) + : func; + if(wasm.isPtr(fProxy)){ + if(fProxy && !wasm.functionEntry(fProxy)){ + toss("Pointer",fProxy,"is not a WASM function table entry."); + } + tgt[memKey] = fProxy; + }else{ + const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true)); + tgt[memKey] = pFunc; + if(!tgt.ondispose || !tgt.ondispose.__removeFuncList){ + tgt.addOnDispose('ondispose.__removeFuncList handler', + callee.removeFuncList); + tgt.ondispose.__removeFuncList = []; + } + tgt.ondispose.__removeFuncList.push(memKey, pFunc); + } + return (n,f)=>callee(tgt, n, f, applyArgcCheck); + }/*installMethod*/; + installMethod.installMethodArgcCheck = false; + + /** + Installs methods into the given StructType-type instance. Each + entry in the given methods object must map to a known member of + the given StructType, else an exception will be triggered. See + installMethod() for more details, including the semantics of the + 3rd argument. + + As an exception to the above, if any two or more methods in the + 2nd argument are the exact same function, installMethod() is + _not_ called for the 2nd and subsequent instances, and instead + those instances get assigned the same method pointer which is + created for the first instance. This optimization is primarily to + accommodate special handling of sqlite3_module::xConnect and + xCreate methods. + + On success, returns its first argument. Throws on error. + */ + const installMethods = function( + structInstance, methods, applyArgcCheck = installMethod.installMethodArgcCheck + ){ + const seen = new Map /* map of */; + for(const k of Object.keys(methods)){ + const m = methods[k]; + const prior = seen.get(m); + if(prior){ + const mkey = structInstance.memberKey(k); + structInstance[mkey] = structInstance[structInstance.memberKey(prior)]; + }else{ + installMethod(structInstance, k, m, applyArgcCheck); + seen.set(m, k); + } + } + return structInstance; + }; + + /** + Equivalent to calling installMethod(this,...arguments) with a + first argument of this object. If called with 1 or 2 arguments + and the first is an object, it's instead equivalent to calling + installMethods(this,...arguments). + */ + StructBinder.StructType.prototype.installMethod = function callee( + name, func, applyArgcCheck = installMethod.installMethodArgcCheck + ){ + return (arguments.length < 3 && name && 'object'===typeof name) + ? installMethods(this, ...arguments) + : installMethod(this, ...arguments); + }; + + /** + Equivalent to calling installMethods() with a first argument + of this object. + */ + StructBinder.StructType.prototype.installMethods = function( + methods, applyArgcCheck = installMethod.installMethodArgcCheck + ){ + return installMethods(this, methods, applyArgcCheck); + }; + + /** + Uses sqlite3_vfs_register() to register this + sqlite3.capi.sqlite3_vfs. This object must have already been + filled out properly. If the first argument is truthy, the VFS is + registered as the default VFS, else it is not. + + On success, returns this object. Throws on error. + */ + capi.sqlite3_vfs.prototype.registerVfs = function(asDefault=false){ + if(!(this instanceof sqlite3.capi.sqlite3_vfs)){ + toss("Expecting a sqlite3_vfs-type argument."); + } + const rc = capi.sqlite3_vfs_register(this, asDefault ? 1 : 0); + if(rc){ + toss("sqlite3_vfs_register(",this,") failed with rc",rc); + } + if(this.pointer !== capi.sqlite3_vfs_find(this.$zName)){ + toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS", + this); + } + return this; + }; + + /** + A wrapper for installMethods() or registerVfs() to reduce + installation of a VFS and/or its I/O methods to a single + call. + + Accepts an object which contains the properties "io" and/or + "vfs", each of which is itself an object with following properties: + + - `struct`: an sqlite3.StructType-type struct. This must be a + populated (except for the methods) object of type + sqlite3_io_methods (for the "io" entry) or sqlite3_vfs (for the + "vfs" entry). + + - `methods`: an object mapping sqlite3_io_methods method names + (e.g. 'xClose') to JS implementations of those methods. The JS + implementations must be call-compatible with their native + counterparts. + + For each of those object, this function passes its (`struct`, + `methods`, (optional) `applyArgcCheck`) properties to + installMethods(). + + If the `vfs` entry is set then: + + - Its `struct` property's registerVfs() is called. The + `vfs` entry may optionally have an `asDefault` property, which + gets passed as the argument to registerVfs(). + + - If `struct.$zName` is falsy and the entry has a string-type + `name` property, `struct.$zName` is set to the C-string form of + that `name` value before registerVfs() is called. That string + gets added to the on-dispose state of the struct. + + On success returns this object. Throws on error. + */ + vfs.installVfs = function(opt){ + let count = 0; + const propList = ['io','vfs']; + for(const key of propList){ + const o = opt[key]; + if(o){ + ++count; + installMethods(o.struct, o.methods, !!o.applyArgcCheck); + if('vfs'===key){ + if(!o.struct.$zName && 'string'===typeof o.name){ + o.struct.addOnDispose( + o.struct.$zName = wasm.allocCString(o.name) + ); + } + o.struct.registerVfs(!!o.asDefault); + } + } + } + if(!count) toss("Misuse: installVfs() options object requires at least", + "one of:", propList); + return this; + }; + + /** + Internal factory function for xVtab and xCursor impls. + */ + const __xWrapFactory = function(methodName,StructType){ + return function(ptr,removeMapping=false){ + if(0===arguments.length) ptr = new StructType; + if(ptr instanceof StructType){ + //T.assert(!this.has(ptr.pointer)); + this.set(ptr.pointer, ptr); + return ptr; + }else if(!wasm.isPtr(ptr)){ + sqlite3.SQLite3Error.toss("Invalid argument to",methodName+"()"); + } + let rc = this.get(ptr); + if(removeMapping) this.delete(ptr); + return rc; + }.bind(new Map); + }; + + /** + A factory function which implements a simple lifetime manager for + mappings between C struct pointers and their JS-level wrappers. + The first argument must be the logical name of the manager + (e.g. 'xVtab' or 'xCursor'), which is only used for error + reporting. The second must be the capi.XYZ struct-type value, + e.g. capi.sqlite3_vtab or capi.sqlite3_vtab_cursor. + + Returns an object with 4 methods: create(), get(), unget(), and + dispose(), plus a StructType member with the value of the 2nd + argument. The methods are documented in the body of this + function. + */ + const StructPtrMapper = function(name, StructType){ + const __xWrap = __xWrapFactory(name,StructType); + /** + This object houses a small API for managing mappings of (`T*`) + to StructType objects, specifically within the lifetime + requirements of sqlite3_module methods. + */ + return Object.assign(Object.create(null),{ + /** The StructType object for this object's API. */ + StructType, + /** + Creates a new StructType object, writes its `pointer` + value to the given output pointer, and returns that + object. Its intended usage depends on StructType: + + sqlite3_vtab: to be called from sqlite3_module::xConnect() + or xCreate() implementations. + + sqlite3_vtab_cursor: to be called from xOpen(). + + This will throw if allocation of the StructType instance + fails or if ppOut is not a pointer-type value. + */ + create: (ppOut)=>{ + const rc = __xWrap(); + wasm.pokePtr(ppOut, rc.pointer); + return rc; + }, + /** + Returns the StructType object previously mapped to the + given pointer using create(). Its intended usage depends + on StructType: + + sqlite3_vtab: to be called from sqlite3_module methods which + take a (sqlite3_vtab*) pointer _except_ for + xDestroy()/xDisconnect(), in which case unget() or dispose(). + + sqlite3_vtab_cursor: to be called from any sqlite3_module methods + which take a `sqlite3_vtab_cursor*` argument except xClose(), + in which case use unget() or dispose(). + + Rule to remember: _never_ call dispose() on an instance + returned by this function. + */ + get: (pCObj)=>__xWrap(pCObj), + /** + Identical to get() but also disconnects the mapping between the + given pointer and the returned StructType object, such that + future calls to this function or get() with the same pointer + will return the undefined value. Its intended usage depends + on StructType: + + sqlite3_vtab: to be called from sqlite3_module::xDisconnect() or + xDestroy() implementations or in error handling of a failed + xCreate() or xConnect(). + + sqlite3_vtab_cursor: to be called from xClose() or during + cleanup in a failed xOpen(). + + Calling this method obligates the caller to call dispose() on + the returned object when they're done with it. + */ + unget: (pCObj)=>__xWrap(pCObj,true), + /** + Works like unget() plus it calls dispose() on the + StructType object. + */ + dispose: (pCObj)=>{ + const o = __xWrap(pCObj,true); + if(o) o.dispose(); + } + }); + }; + + /** + A lifetime-management object for mapping `sqlite3_vtab*` + instances in sqlite3_module methods to capi.sqlite3_vtab + objects. + + The API docs are in the API-internal StructPtrMapper(). + */ + vtab.xVtab = StructPtrMapper('xVtab', capi.sqlite3_vtab); + + /** + A lifetime-management object for mapping `sqlite3_vtab_cursor*` + instances in sqlite3_module methods to capi.sqlite3_vtab_cursor + objects. + + The API docs are in the API-internal StructPtrMapper(). + */ + vtab.xCursor = StructPtrMapper('xCursor', capi.sqlite3_vtab_cursor); + + /** + Convenience form of creating an sqlite3_index_info wrapper, + intended for use in xBestIndex implementations. Note that the + caller is expected to call dispose() on the returned object + before returning. Though not _strictly_ required, as that object + does not own the pIdxInfo memory, it is nonetheless good form. + */ + vtab.xIndexInfo = (pIdxInfo)=>new capi.sqlite3_index_info(pIdxInfo); + + /** + Given an error object, this function returns + sqlite3.capi.SQLITE_NOMEM if (e instanceof + sqlite3.WasmAllocError), else it returns its + second argument. Its intended usage is in the methods + of a sqlite3_vfs or sqlite3_module: + + ``` + try{ + let rc = ... + return rc; + }catch(e){ + return sqlite3.vtab.exceptionToRc(e, sqlite3.capi.SQLITE_XYZ); + // where SQLITE_XYZ is some call-appropriate result code. + } + ``` + */ + /**vfs.exceptionToRc = vtab.exceptionToRc = + (e, defaultRc=capi.SQLITE_ERROR)=>( + (e instanceof sqlite3.WasmAllocError) + ? capi.SQLITE_NOMEM + : defaultRc + );*/ + + /** + Given an sqlite3_module method name and error object, this + function returns sqlite3.capi.SQLITE_NOMEM if (e instanceof + sqlite3.WasmAllocError), else it returns its second argument. Its + intended usage is in the methods of a sqlite3_vfs or + sqlite3_module: + + ``` + try{ + let rc = ... + return rc; + }catch(e){ + return sqlite3.vtab.xError( + 'xColumn', e, sqlite3.capi.SQLITE_XYZ); + // where SQLITE_XYZ is some call-appropriate result code. + } + ``` + + If no 3rd argument is provided, its default depends on + the error type: + + - An sqlite3.WasmAllocError always resolves to capi.SQLITE_NOMEM. + + - If err is an SQLite3Error then its `resultCode` property + is used. + + - If all else fails, capi.SQLITE_ERROR is used. + + If xError.errorReporter is a function, it is called in + order to report the error, else the error is not reported. + If that function throws, that exception is ignored. + */ + vtab.xError = function f(methodName, err, defaultRc){ + if(f.errorReporter instanceof Function){ + try{f.errorReporter("sqlite3_module::"+methodName+"(): "+err.message);} + catch(e){/*ignored*/} + } + let rc; + if(err instanceof sqlite3.WasmAllocError) rc = capi.SQLITE_NOMEM; + else if(arguments.length>2) rc = defaultRc; + else if(err instanceof sqlite3.SQLite3Error) rc = err.resultCode; + return rc || capi.SQLITE_ERROR; + }; + vtab.xError.errorReporter = 1 ? console.error.bind(console) : false; + + /** + "The problem" with this is that it introduces an outer function with + a different arity than the passed-in method callback. That means we + cannot do argc validation on these. Additionally, some methods (namely + xConnect) may have call-specific error handling. It would be a shame to + hard-coded that per-method support in this function. + */ + /** vtab.methodCatcher = function(methodName, method, defaultErrRc=capi.SQLITE_ERROR){ + return function(...args){ + try { method(...args); } + }catch(e){ return vtab.xError(methodName, e, defaultRc) } + }; + */ + + /** + A helper for sqlite3_vtab::xRowid() and xUpdate() + implementations. It must be passed the final argument to one of + those methods (an output pointer to an int64 row ID) and the + value to store at the output pointer's address. Returns the same + as wasm.poke() and will throw if the 1st or 2nd arguments + are invalid for that function. + + Example xRowid impl: + + ``` + const xRowid = (pCursor, ppRowid64)=>{ + const c = vtab.xCursor(pCursor); + vtab.xRowid(ppRowid64, c.myRowId); + return 0; + }; + ``` + */ + vtab.xRowid = (ppRowid64, value)=>wasm.poke(ppRowid64, value, 'i64'); + + /** + A helper to initialize and set up an sqlite3_module object for + later installation into individual databases using + sqlite3_create_module(). Requires an object with the following + properties: + + - `methods`: an object containing a mapping of properties with + the C-side names of the sqlite3_module methods, e.g. xCreate, + xBestIndex, etc., to JS implementations for those functions. + Certain special-case handling is performed, as described below. + + - `catchExceptions` (default=false): if truthy, the given methods + are not mapped as-is, but are instead wrapped inside wrappers + which translate exceptions into result codes of SQLITE_ERROR or + SQLITE_NOMEM, depending on whether the exception is an + sqlite3.WasmAllocError. In the case of the xConnect and xCreate + methods, the exception handler also sets the output error + string to the exception's error string. + + - OPTIONAL `struct`: a sqlite3.capi.sqlite3_module() instance. If + not set, one will be created automatically. If the current + "this" is-a sqlite3_module then it is unconditionally used in + place of `struct`. + + - OPTIONAL `iVersion`: if set, it must be an integer value and it + gets assigned to the `$iVersion` member of the struct object. + If it's _not_ set, and the passed-in `struct` object's `$iVersion` + is 0 (the default) then this function attempts to define a value + for that property based on the list of methods it has. + + If `catchExceptions` is false, it is up to the client to ensure + that no exceptions escape the methods, as doing so would move + them through the C API, leading to undefined + behavior. (vtab.xError() is intended to assist in reporting + such exceptions.) + + Certain methods may refer to the same implementation. To simplify + the definition of such methods: + + - If `methods.xConnect` is `true` then the value of + `methods.xCreate` is used in its place, and vice versa. sqlite + treats xConnect/xCreate functions specially if they are exactly + the same function (same pointer value). + + - If `methods.xDisconnect` is true then the value of + `methods.xDestroy` is used in its place, and vice versa. + + This is to facilitate creation of those methods inline in the + passed-in object without requiring the client to explicitly get a + reference to one of them in order to assign it to the other + one. + + The `catchExceptions`-installed handlers will account for + identical references to the above functions and will install the + same wrapper function for both. + + The given methods are expected to return integer values, as + expected by the C API. If `catchExceptions` is truthy, the return + value of the wrapped function will be used as-is and will be + translated to 0 if the function returns a falsy value (e.g. if it + does not have an explicit return). If `catchExceptions` is _not_ + active, the method implementations must explicitly return integer + values. + + Throws on error. On success, returns the sqlite3_module object + (`this` or `opt.struct` or a new sqlite3_module instance, + depending on how it's called). + */ + vtab.setupModule = function(opt){ + let createdMod = false; + const mod = (this instanceof capi.sqlite3_module) + ? this : (opt.struct || (createdMod = new capi.sqlite3_module())); + try{ + const methods = opt.methods || toss("Missing 'methods' object."); + for(const e of Object.entries({ + // -----^ ==> [k,v] triggers a broken code transformation in + // some versions of the emsdk toolchain. + xConnect: 'xCreate', xDisconnect: 'xDestroy' + })){ + // Remap X=true to X=Y for certain X/Y combinations + const k = e[0], v = e[1]; + if(true === methods[k]) methods[k] = methods[v]; + else if(true === methods[v]) methods[v] = methods[k]; + } + if(opt.catchExceptions){ + const fwrap = function(methodName, func){ + if(['xConnect','xCreate'].indexOf(methodName) >= 0){ + return function(pDb, pAux, argc, argv, ppVtab, pzErr){ + try{return func(...arguments) || 0} + catch(e){ + if(!(e instanceof sqlite3.WasmAllocError)){ + wasm.dealloc(wasm.peekPtr(pzErr)); + wasm.pokePtr(pzErr, wasm.allocCString(e.message)); + } + return vtab.xError(methodName, e); + } + }; + }else{ + return function(...args){ + try{return func(...args) || 0} + catch(e){ + return vtab.xError(methodName, e); + } + }; + } + }; + const mnames = [ + 'xCreate', 'xConnect', 'xBestIndex', 'xDisconnect', + 'xDestroy', 'xOpen', 'xClose', 'xFilter', 'xNext', + 'xEof', 'xColumn', 'xRowid', 'xUpdate', + 'xBegin', 'xSync', 'xCommit', 'xRollback', + 'xFindFunction', 'xRename', 'xSavepoint', 'xRelease', + 'xRollbackTo', 'xShadowName' + ]; + const remethods = Object.create(null); + for(const k of mnames){ + const m = methods[k]; + if(!(m instanceof Function)) continue; + else if('xConnect'===k && methods.xCreate===m){ + remethods[k] = methods.xCreate; + }else if('xCreate'===k && methods.xConnect===m){ + remethods[k] = methods.xConnect; + }else{ + remethods[k] = fwrap(k, m); + } + } + installMethods(mod, remethods, false); + }else{ + // No automatic exception handling. Trust the client + // to not throw. + installMethods( + mod, methods, !!opt.applyArgcCheck/*undocumented option*/ + ); + } + if(0===mod.$iVersion){ + let v; + if('number'===typeof opt.iVersion) v = opt.iVersion; + else if(mod.$xShadowName) v = 3; + else if(mod.$xSavePoint || mod.$xRelease || mod.$xRollbackTo) v = 2; + else v = 1; + mod.$iVersion = v; + } + }catch(e){ + if(createdMod) createdMod.dispose(); + throw e; + } + return mod; + }/*setupModule()*/; + + /** + Equivalent to calling vtab.setupModule() with this sqlite3_module + object as the call's `this`. + */ + capi.sqlite3_module.prototype.setupModule = function(opt){ + return vtab.setupModule.call(this, opt); + }; +}/*sqlite3ApiBootstrap.initializers.push()*/); DELETED ext/wasm/api/sqlite3-vfs-helper.c-pp.js Index: ext/wasm/api/sqlite3-vfs-helper.c-pp.js ================================================================== --- ext/wasm/api/sqlite3-vfs-helper.c-pp.js +++ /dev/null @@ -1,103 +0,0 @@ -/* -** 2022-11-30 -** -** The author disclaims copyright to this source code. In place of a -** legal notice, here is a blessing: -** -** * May you do good and not evil. -** * May you find forgiveness for yourself and forgive others. -** * May you share freely, never taking more than you give. -*/ - -/** - This file installs sqlite3.vfs, a namespace of helpers for use in - the creation of JavaScript implementations of sqlite3_vfs. -*/ -'use strict'; -globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ - const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3; - const vfs = Object.create(null); - sqlite3.vfs = vfs; - - /** - Uses sqlite3_vfs_register() to register this - sqlite3.capi.sqlite3_vfs instance. This object must have already - been filled out properly. If the first argument is truthy, the - VFS is registered as the default VFS, else it is not. - - On success, returns this object. Throws on error. - */ - capi.sqlite3_vfs.prototype.registerVfs = function(asDefault=false){ - if(!(this instanceof sqlite3.capi.sqlite3_vfs)){ - toss("Expecting a sqlite3_vfs-type argument."); - } - const rc = capi.sqlite3_vfs_register(this, asDefault ? 1 : 0); - if(rc){ - toss("sqlite3_vfs_register(",this,") failed with rc",rc); - } - if(this.pointer !== capi.sqlite3_vfs_find(this.$zName)){ - toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS", - this); - } - return this; - }; - - /** - A wrapper for - sqlite3.StructBinder.StructType.prototype.installMethods() or - registerVfs() to reduce installation of a VFS and/or its I/O - methods to a single call. - - Accepts an object which contains the properties "io" and/or - "vfs", each of which is itself an object with following properties: - - - `struct`: an sqlite3.StructBinder.StructType-type struct. This - must be a populated (except for the methods) object of type - sqlite3_io_methods (for the "io" entry) or sqlite3_vfs (for the - "vfs" entry). - - - `methods`: an object mapping sqlite3_io_methods method names - (e.g. 'xClose') to JS implementations of those methods. The JS - implementations must be call-compatible with their native - counterparts. - - For each of those object, this function passes its (`struct`, - `methods`, (optional) `applyArgcCheck`) properties to - installMethods(). - - If the `vfs` entry is set then: - - - Its `struct` property's registerVfs() is called. The - `vfs` entry may optionally have an `asDefault` property, which - gets passed as the argument to registerVfs(). - - - If `struct.$zName` is falsy and the entry has a string-type - `name` property, `struct.$zName` is set to the C-string form of - that `name` value before registerVfs() is called. That string - gets added to the on-dispose state of the struct. - - On success returns this object. Throws on error. - */ - vfs.installVfs = function(opt){ - let count = 0; - const propList = ['io','vfs']; - for(const key of propList){ - const o = opt[key]; - if(o){ - ++count; - o.struct.installMethods(o.methods, !!o.applyArgcCheck); - if('vfs'===key){ - if(!o.struct.$zName && 'string'===typeof o.name){ - o.struct.addOnDispose( - o.struct.$zName = wasm.allocCString(o.name) - ); - } - o.struct.registerVfs(!!o.asDefault); - } - } - } - if(!count) toss("Misuse: installVfs() options object requires at least", - "one of:", propList); - return this; - }; -}/*sqlite3ApiBootstrap.initializers.push()*/); Index: ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js ================================================================== --- ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js +++ ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js @@ -1135,13 +1135,12 @@ Imports the contents of an SQLite database, provided as a byte array or ArrayBuffer, under the given name, overwriting any existing content. Throws if the pool has no available file slots, on I/O error, or if the input does not appear to be a database. In the latter case, only a cursory examination is made. - Results are undefined if the given db name refers to an opened - db. Note that this routine is _only_ for importing database - files, not arbitrary files, the reason being that this VFS will + Note that this routine is _only_ for importing database files, + not arbitrary files, the reason being that this VFS will automatically clean up any non-database files so importing them is pointless. If passed a function for its second argument, its behavior changes to asynchronous and it imports its data in chunks fed to Index: ext/wasm/api/sqlite3-vfs-opfs.c-pp.js ================================================================== --- ext/wasm/api/sqlite3-vfs-opfs.c-pp.js +++ ext/wasm/api/sqlite3-vfs-opfs.c-pp.js @@ -243,12 +243,11 @@ : null /* dVfs will be null when sqlite3 is built with SQLITE_OS_OTHER. */; opfsIoMethods.$iVersion = 1; opfsVfs.$iVersion = 2/*yes, two*/; opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof; - opfsVfs.$mxPathname = 1024/* sure, why not? The OPFS name length limit - is undocumented/unspecified. */; + opfsVfs.$mxPathname = 1024/*sure, why not?*/; opfsVfs.$zName = wasm.allocCString("opfs"); // All C-side memory of opfsVfs is zeroed out, but just to be explicit: opfsVfs.$xDlOpen = opfsVfs.$xDlError = opfsVfs.$xDlSym = opfsVfs.$xDlClose = null; opfsVfs.addOnDispose( '$zName', opfsVfs.$zName, @@ -421,30 +420,15 @@ toss("Maintenance required: not found:",k); } }); state.opfsFlags = Object.assign(Object.create(null),{ /** - Flag for use with xOpen(). URI flag "opfs-unlock-asap=1" - enables this. See defaultUnlockAsap, below. + Flag for use with xOpen(). "opfs-unlock-asap=1" enables + this. See defaultUnlockAsap, below. */ OPFS_UNLOCK_ASAP: 0x01, /** - Flag for use with xOpen(). URI flag "delete-before-open=1" - tells the VFS to delete the db file before attempting to open - it. This can be used, e.g., to replace a db which has been - corrupted (without forcing us to expose a delete/unlink() - function in the public API). - - Failure to unlink the file is ignored but may lead to - downstream errors. An unlink can fail if, e.g., another tab - has the handle open. - - It goes without saying that deleting a file out from under another - instance results in Undefined Behavior. - */ - OPFS_UNLINK_BEFORE_OPEN: 0x02, - /** If true, any async routine which implicitly acquires a sync access handle (i.e. an OPFS lock) will release that locks at the end of the call which acquires it. If false, such "autolocks" are not released until the VFS is idle for some brief amount of time. @@ -888,21 +872,17 @@ xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){ mTimeStart('xOpen'); let opfsFlags = 0; if(0===zName){ zName = randomFilename(); - }else if(wasm.isPtr(zName)){ + }else if('number'===typeof zName){ if(capi.sqlite3_uri_boolean(zName, "opfs-unlock-asap", 0)){ /* -----------------------^^^^^ MUST pass the untranslated C-string here. */ opfsFlags |= state.opfsFlags.OPFS_UNLOCK_ASAP; } - if(capi.sqlite3_uri_boolean(zName, "delete-before-open", 0)){ - opfsFlags |= state.opfsFlags.OPFS_UNLINK_BEFORE_OPEN; - } zName = wasm.cstrToJs(zName); - //warn("xOpen zName =",zName, "opfsFlags =",opfsFlags); } const fh = Object.create(null); fh.fid = pFile; fh.filename = zName; fh.sab = new SharedArrayBuffer(state.fileBufferSize); @@ -1010,10 +990,31 @@ Generates a random ASCII string, intended for use as a temporary file name. Its argument is the length of the string, defaulting to 16. */ opfsUtil.randomFilename = randomFilename; + + /** + Re-registers the OPFS VFS. This is intended only for odd use + cases which have to call sqlite3_shutdown() as part of their + initialization process, which will unregister the VFS + registered by installOpfsVfs(). If passed a truthy value, the + OPFS VFS is registered as the default VFS, else it is not made + the default. Returns the result of the the + sqlite3_vfs_register() call. + + Design note: the problem of having to re-register things after + a shutdown/initialize pair is more general. How to best plug + that in to the library is unclear. In particular, we cannot + hook in to any C-side calls to sqlite3_initialize(), so we + cannot add an after-initialize callback mechanism. + */ + opfsUtil.registerVfs = (asDefault=false)=>{ + return wasm.exports.sqlite3_vfs_register( + opfsVfs.pointer, asDefault ? 1 : 0 + ); + }; /** Returns a promise which resolves to an object which represents all files and directories in the OPFS tree. The top-most object has two properties: `dirs` is an array of directory entries @@ -1210,22 +1211,20 @@ /** Asynchronously imports the given bytes (a byte array or ArrayBuffer) into the given database file. - Results are undefined if the given db name refers to an opened - db. - If passed a function for its second argument, its behaviour - changes: imports its data in chunks fed to it by the given - callback function. It calls the callback (which may be async) - repeatedly, expecting either a Uint8Array or ArrayBuffer (to - denote new input) or undefined (to denote EOF). For so long as - the callback continues to return non-undefined, it will append - incoming data to the given VFS-hosted database file. When - called this way, the resolved value of the returned Promise is - the number of bytes written to the target file. + changes to async and it imports its data in chunks fed to it by + the given callback function. It calls the callback (which may + be async) repeatedly, expecting either a Uint8Array or + ArrayBuffer (to denote new input) or undefined (to denote + EOF). For so long as the callback continues to return + non-undefined, it will append incoming data to the given + VFS-hosted database file. When called this way, the resolved + value of the returned Promise is the number of bytes written to + the target file. It very specifically requires the input to be an SQLite3 database and throws if that's not the case. It does so in order to prevent this function from taking on a larger scope than it is specifically intended to. i.e. we do not want it to DELETED ext/wasm/api/sqlite3-vtab-helper.c-pp.js Index: ext/wasm/api/sqlite3-vtab-helper.c-pp.js ================================================================== --- ext/wasm/api/sqlite3-vtab-helper.c-pp.js +++ /dev/null @@ -1,423 +0,0 @@ -/* -** 2022-11-30 -** -** The author disclaims copyright to this source code. In place of a -** legal notice, here is a blessing: -** -** * May you do good and not evil. -** * May you find forgiveness for yourself and forgive others. -** * May you share freely, never taking more than you give. -*/ - -/** - This file installs sqlite3.vtab, a namespace of helpers for use in - the creation of JavaScript implementations virtual tables. -*/ -'use strict'; -globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ - const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3; - const vtab = Object.create(null); - sqlite3.vtab = vtab; - - const sii = capi.sqlite3_index_info; - /** - If n is >=0 and less than this.$nConstraint, this function - returns either a WASM pointer to the 0-based nth entry of - this.$aConstraint (if passed a truthy 2nd argument) or an - sqlite3_index_info.sqlite3_index_constraint object wrapping that - address (if passed a falsy value or no 2nd argument). Returns a - falsy value if n is out of range. - */ - sii.prototype.nthConstraint = function(n, asPtr=false){ - if(n<0 || n>=this.$nConstraint) return false; - const ptr = this.$aConstraint + ( - sii.sqlite3_index_constraint.structInfo.sizeof * n - ); - return asPtr ? ptr : new sii.sqlite3_index_constraint(ptr); - }; - - /** - Works identically to nthConstraint() but returns state from - this.$aConstraintUsage, so returns an - sqlite3_index_info.sqlite3_index_constraint_usage instance - if passed no 2nd argument or a falsy 2nd argument. - */ - sii.prototype.nthConstraintUsage = function(n, asPtr=false){ - if(n<0 || n>=this.$nConstraint) return false; - const ptr = this.$aConstraintUsage + ( - sii.sqlite3_index_constraint_usage.structInfo.sizeof * n - ); - return asPtr ? ptr : new sii.sqlite3_index_constraint_usage(ptr); - }; - - /** - If n is >=0 and less than this.$nOrderBy, this function - returns either a WASM pointer to the 0-based nth entry of - this.$aOrderBy (if passed a truthy 2nd argument) or an - sqlite3_index_info.sqlite3_index_orderby object wrapping that - address (if passed a falsy value or no 2nd argument). Returns a - falsy value if n is out of range. - */ - sii.prototype.nthOrderBy = function(n, asPtr=false){ - if(n<0 || n>=this.$nOrderBy) return false; - const ptr = this.$aOrderBy + ( - sii.sqlite3_index_orderby.structInfo.sizeof * n - ); - return asPtr ? ptr : new sii.sqlite3_index_orderby(ptr); - }; - - /** - Internal factory function for xVtab and xCursor impls. - */ - const __xWrapFactory = function(methodName,StructType){ - return function(ptr,removeMapping=false){ - if(0===arguments.length) ptr = new StructType; - if(ptr instanceof StructType){ - //T.assert(!this.has(ptr.pointer)); - this.set(ptr.pointer, ptr); - return ptr; - }else if(!wasm.isPtr(ptr)){ - sqlite3.SQLite3Error.toss("Invalid argument to",methodName+"()"); - } - let rc = this.get(ptr); - if(removeMapping) this.delete(ptr); - return rc; - }.bind(new Map); - }; - - /** - A factory function which implements a simple lifetime manager for - mappings between C struct pointers and their JS-level wrappers. - The first argument must be the logical name of the manager - (e.g. 'xVtab' or 'xCursor'), which is only used for error - reporting. The second must be the capi.XYZ struct-type value, - e.g. capi.sqlite3_vtab or capi.sqlite3_vtab_cursor. - - Returns an object with 4 methods: create(), get(), unget(), and - dispose(), plus a StructType member with the value of the 2nd - argument. The methods are documented in the body of this - function. - */ - const StructPtrMapper = function(name, StructType){ - const __xWrap = __xWrapFactory(name,StructType); - /** - This object houses a small API for managing mappings of (`T*`) - to StructType objects, specifically within the lifetime - requirements of sqlite3_module methods. - */ - return Object.assign(Object.create(null),{ - /** The StructType object for this object's API. */ - StructType, - /** - Creates a new StructType object, writes its `pointer` - value to the given output pointer, and returns that - object. Its intended usage depends on StructType: - - sqlite3_vtab: to be called from sqlite3_module::xConnect() - or xCreate() implementations. - - sqlite3_vtab_cursor: to be called from xOpen(). - - This will throw if allocation of the StructType instance - fails or if ppOut is not a pointer-type value. - */ - create: (ppOut)=>{ - const rc = __xWrap(); - wasm.pokePtr(ppOut, rc.pointer); - return rc; - }, - /** - Returns the StructType object previously mapped to the - given pointer using create(). Its intended usage depends - on StructType: - - sqlite3_vtab: to be called from sqlite3_module methods which - take a (sqlite3_vtab*) pointer _except_ for - xDestroy()/xDisconnect(), in which case unget() or dispose(). - - sqlite3_vtab_cursor: to be called from any sqlite3_module methods - which take a `sqlite3_vtab_cursor*` argument except xClose(), - in which case use unget() or dispose(). - - Rule to remember: _never_ call dispose() on an instance - returned by this function. - */ - get: (pCObj)=>__xWrap(pCObj), - /** - Identical to get() but also disconnects the mapping between the - given pointer and the returned StructType object, such that - future calls to this function or get() with the same pointer - will return the undefined value. Its intended usage depends - on StructType: - - sqlite3_vtab: to be called from sqlite3_module::xDisconnect() or - xDestroy() implementations or in error handling of a failed - xCreate() or xConnect(). - - sqlite3_vtab_cursor: to be called from xClose() or during - cleanup in a failed xOpen(). - - Calling this method obligates the caller to call dispose() on - the returned object when they're done with it. - */ - unget: (pCObj)=>__xWrap(pCObj,true), - /** - Works like unget() plus it calls dispose() on the - StructType object. - */ - dispose: (pCObj)=>{ - const o = __xWrap(pCObj,true); - if(o) o.dispose(); - } - }); - }; - - /** - A lifetime-management object for mapping `sqlite3_vtab*` - instances in sqlite3_module methods to capi.sqlite3_vtab - objects. - - The API docs are in the API-internal StructPtrMapper(). - */ - vtab.xVtab = StructPtrMapper('xVtab', capi.sqlite3_vtab); - - /** - A lifetime-management object for mapping `sqlite3_vtab_cursor*` - instances in sqlite3_module methods to capi.sqlite3_vtab_cursor - objects. - - The API docs are in the API-internal StructPtrMapper(). - */ - vtab.xCursor = StructPtrMapper('xCursor', capi.sqlite3_vtab_cursor); - - /** - Convenience form of creating an sqlite3_index_info wrapper, - intended for use in xBestIndex implementations. Note that the - caller is expected to call dispose() on the returned object - before returning. Though not _strictly_ required, as that object - does not own the pIdxInfo memory, it is nonetheless good form. - */ - vtab.xIndexInfo = (pIdxInfo)=>new capi.sqlite3_index_info(pIdxInfo); - - /** - Given an sqlite3_module method name and error object, this - function returns sqlite3.capi.SQLITE_NOMEM if (e instanceof - sqlite3.WasmAllocError), else it returns its second argument. Its - intended usage is in the methods of a sqlite3_vfs or - sqlite3_module: - - ``` - try{ - let rc = ... - return rc; - }catch(e){ - return sqlite3.vtab.xError( - 'xColumn', e, sqlite3.capi.SQLITE_XYZ); - // where SQLITE_XYZ is some call-appropriate result code. - } - ``` - - If no 3rd argument is provided, its default depends on - the error type: - - - An sqlite3.WasmAllocError always resolves to capi.SQLITE_NOMEM. - - - If err is an SQLite3Error then its `resultCode` property - is used. - - - If all else fails, capi.SQLITE_ERROR is used. - - If xError.errorReporter is a function, it is called in - order to report the error, else the error is not reported. - If that function throws, that exception is ignored. - */ - vtab.xError = function f(methodName, err, defaultRc){ - if(f.errorReporter instanceof Function){ - try{f.errorReporter("sqlite3_module::"+methodName+"(): "+err.message);} - catch(e){/*ignored*/} - } - let rc; - if(err instanceof sqlite3.WasmAllocError) rc = capi.SQLITE_NOMEM; - else if(arguments.length>2) rc = defaultRc; - else if(err instanceof sqlite3.SQLite3Error) rc = err.resultCode; - return rc || capi.SQLITE_ERROR; - }; - vtab.xError.errorReporter = 1 ? console.error.bind(console) : false; - - /** - A helper for sqlite3_vtab::xRowid() and xUpdate() - implementations. It must be passed the final argument to one of - those methods (an output pointer to an int64 row ID) and the - value to store at the output pointer's address. Returns the same - as wasm.poke() and will throw if the 1st or 2nd arguments - are invalid for that function. - - Example xRowid impl: - - ``` - const xRowid = (pCursor, ppRowid64)=>{ - const c = vtab.xCursor(pCursor); - vtab.xRowid(ppRowid64, c.myRowId); - return 0; - }; - ``` - */ - vtab.xRowid = (ppRowid64, value)=>wasm.poke(ppRowid64, value, 'i64'); - - /** - A helper to initialize and set up an sqlite3_module object for - later installation into individual databases using - sqlite3_create_module(). Requires an object with the following - properties: - - - `methods`: an object containing a mapping of properties with - the C-side names of the sqlite3_module methods, e.g. xCreate, - xBestIndex, etc., to JS implementations for those functions. - Certain special-case handling is performed, as described below. - - - `catchExceptions` (default=false): if truthy, the given methods - are not mapped as-is, but are instead wrapped inside wrappers - which translate exceptions into result codes of SQLITE_ERROR or - SQLITE_NOMEM, depending on whether the exception is an - sqlite3.WasmAllocError. In the case of the xConnect and xCreate - methods, the exception handler also sets the output error - string to the exception's error string. - - - OPTIONAL `struct`: a sqlite3.capi.sqlite3_module() instance. If - not set, one will be created automatically. If the current - "this" is-a sqlite3_module then it is unconditionally used in - place of `struct`. - - - OPTIONAL `iVersion`: if set, it must be an integer value and it - gets assigned to the `$iVersion` member of the struct object. - If it's _not_ set, and the passed-in `struct` object's `$iVersion` - is 0 (the default) then this function attempts to define a value - for that property based on the list of methods it has. - - If `catchExceptions` is false, it is up to the client to ensure - that no exceptions escape the methods, as doing so would move - them through the C API, leading to undefined - behavior. (vtab.xError() is intended to assist in reporting - such exceptions.) - - Certain methods may refer to the same implementation. To simplify - the definition of such methods: - - - If `methods.xConnect` is `true` then the value of - `methods.xCreate` is used in its place, and vice versa. sqlite - treats xConnect/xCreate functions specially if they are exactly - the same function (same pointer value). - - - If `methods.xDisconnect` is true then the value of - `methods.xDestroy` is used in its place, and vice versa. - - This is to facilitate creation of those methods inline in the - passed-in object without requiring the client to explicitly get a - reference to one of them in order to assign it to the other - one. - - The `catchExceptions`-installed handlers will account for - identical references to the above functions and will install the - same wrapper function for both. - - The given methods are expected to return integer values, as - expected by the C API. If `catchExceptions` is truthy, the return - value of the wrapped function will be used as-is and will be - translated to 0 if the function returns a falsy value (e.g. if it - does not have an explicit return). If `catchExceptions` is _not_ - active, the method implementations must explicitly return integer - values. - - Throws on error. On success, returns the sqlite3_module object - (`this` or `opt.struct` or a new sqlite3_module instance, - depending on how it's called). - */ - vtab.setupModule = function(opt){ - let createdMod = false; - const mod = (this instanceof capi.sqlite3_module) - ? this : (opt.struct || (createdMod = new capi.sqlite3_module())); - try{ - const methods = opt.methods || toss("Missing 'methods' object."); - for(const e of Object.entries({ - // -----^ ==> [k,v] triggers a broken code transformation in - // some versions of the emsdk toolchain. - xConnect: 'xCreate', xDisconnect: 'xDestroy' - })){ - // Remap X=true to X=Y for certain X/Y combinations - const k = e[0], v = e[1]; - if(true === methods[k]) methods[k] = methods[v]; - else if(true === methods[v]) methods[v] = methods[k]; - } - if(opt.catchExceptions){ - const fwrap = function(methodName, func){ - if(['xConnect','xCreate'].indexOf(methodName) >= 0){ - return function(pDb, pAux, argc, argv, ppVtab, pzErr){ - try{return func(...arguments) || 0} - catch(e){ - if(!(e instanceof sqlite3.WasmAllocError)){ - wasm.dealloc(wasm.peekPtr(pzErr)); - wasm.pokePtr(pzErr, wasm.allocCString(e.message)); - } - return vtab.xError(methodName, e); - } - }; - }else{ - return function(...args){ - try{return func(...args) || 0} - catch(e){ - return vtab.xError(methodName, e); - } - }; - } - }; - const mnames = [ - 'xCreate', 'xConnect', 'xBestIndex', 'xDisconnect', - 'xDestroy', 'xOpen', 'xClose', 'xFilter', 'xNext', - 'xEof', 'xColumn', 'xRowid', 'xUpdate', - 'xBegin', 'xSync', 'xCommit', 'xRollback', - 'xFindFunction', 'xRename', 'xSavepoint', 'xRelease', - 'xRollbackTo', 'xShadowName' - ]; - const remethods = Object.create(null); - for(const k of mnames){ - const m = methods[k]; - if(!(m instanceof Function)) continue; - else if('xConnect'===k && methods.xCreate===m){ - remethods[k] = methods.xCreate; - }else if('xCreate'===k && methods.xConnect===m){ - remethods[k] = methods.xConnect; - }else{ - remethods[k] = fwrap(k, m); - } - } - mod.installMethods(remethods, false); - }else{ - // No automatic exception handling. Trust the client - // to not throw. - mod.installMethods( - methods, !!opt.applyArgcCheck/*undocumented option*/ - ); - } - if(0===mod.$iVersion){ - let v; - if('number'===typeof opt.iVersion) v = opt.iVersion; - else if(mod.$xShadowName) v = 3; - else if(mod.$xSavePoint || mod.$xRelease || mod.$xRollbackTo) v = 2; - else v = 1; - mod.$iVersion = v; - } - }catch(e){ - if(createdMod) createdMod.dispose(); - throw e; - } - return mod; - }/*setupModule()*/; - - /** - Equivalent to calling vtab.setupModule() with this sqlite3_module - object as the call's `this`. - */ - capi.sqlite3_module.prototype.setupModule = function(opt){ - return vtab.setupModule.call(this, opt); - }; -}/*sqlite3ApiBootstrap.initializers.push()*/); Index: ext/wasm/api/sqlite3-wasm.c ================================================================== --- ext/wasm/api/sqlite3-wasm.c +++ ext/wasm/api/sqlite3-wasm.c @@ -145,16 +145,10 @@ #endif #ifndef SQLITE_OS_KV_OPTIONAL # define SQLITE_OS_KV_OPTIONAL 1 #endif -/**********************************************************************/ -/* SQLITE_S... */ -#ifndef SQLITE_STRICT_SUBTYPE -# define SQLITE_STRICT_SUBTYPE 1 -#endif - /**********************************************************************/ /* SQLITE_T... */ #ifndef SQLITE_TEMP_STORE # define SQLITE_TEMP_STORE 2 #endif @@ -236,40 +230,40 @@ ** to work just fine. ** ** Another option is to malloc() a chunk of our own and call that our ** "stack". */ -SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_end(void){ +SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_end(void){ extern void __heap_base /* see https://stackoverflow.com/questions/10038964 */; return &__heap_base; } -SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_begin(void){ +SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_begin(void){ extern void __data_end; return &__data_end; } static void * pWasmStackPtr = 0; -SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_ptr(void){ - if(!pWasmStackPtr) pWasmStackPtr = sqlite3__wasm_stack_end(); +SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_ptr(void){ + if(!pWasmStackPtr) pWasmStackPtr = sqlite3_wasm_stack_end(); return pWasmStackPtr; } -SQLITE_WASM_EXPORT void sqlite3__wasm_stack_restore(void * p){ +SQLITE_WASM_EXPORT void sqlite3_wasm_stack_restore(void * p){ pWasmStackPtr = p; } -SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_alloc(int n){ +SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_alloc(int n){ if(n<=0) return 0; n = (n + 7) & ~7 /* align to 8-byte boundary */; - unsigned char * const p = (unsigned char *)sqlite3__wasm_stack_ptr(); - unsigned const char * const b = (unsigned const char *)sqlite3__wasm_stack_begin(); + unsigned char * const p = (unsigned char *)sqlite3_wasm_stack_ptr(); + unsigned const char * const b = (unsigned const char *)sqlite3_wasm_stack_begin(); if(b + n >= p || b + n < b/*overflow*/) return 0; return pWasmStackPtr = p - n; } #endif /* stack allocator experiment */ /* ** State for the "pseudo-stack" allocator implemented in -** sqlite3__wasm_pstack_xyz(). In order to avoid colliding with +** sqlite3_wasm_pstack_xyz(). In order to avoid colliding with ** Emscripten-controled stack space, it carves out a bit of stack ** memory to use for that purpose. This memory ends up in the ** WASM-managed memory, such that routines which manipulate the wasm ** heap can also be used to manipulate this memory. ** @@ -289,18 +283,18 @@ &PStack_mem[0] + sizeof(PStack_mem) }; /* ** Returns the current pstack position. */ -SQLITE_WASM_EXPORT void * sqlite3__wasm_pstack_ptr(void){ +SQLITE_WASM_EXPORT void * sqlite3_wasm_pstack_ptr(void){ return PStack.pPos; } /* ** Sets the pstack position poitner to p. Results are undefined if the -** given value did not come from sqlite3__wasm_pstack_ptr(). +** given value did not come from sqlite3_wasm_pstack_ptr(). */ -SQLITE_WASM_EXPORT void sqlite3__wasm_pstack_restore(unsigned char * p){ +SQLITE_WASM_EXPORT void sqlite3_wasm_pstack_restore(unsigned char * p){ assert(p>=PStack.pBegin && p<=PStack.pEnd && p>=PStack.pPos); assert(0==((unsigned long long)p & 0x7)); if(p>=PStack.pBegin && p<=PStack.pEnd /*&& p>=PStack.pPos*/){ PStack.pPos = p; } @@ -311,11 +305,11 @@ ** is always adjusted to be a multiple of 8 and returned memory is ** always zeroed out before returning (because this keeps the client ** JS code from having to do so, and most uses of the pstack will ** call for doing so). */ -SQLITE_WASM_EXPORT void * sqlite3__wasm_pstack_alloc(int n){ +SQLITE_WASM_EXPORT void * sqlite3_wasm_pstack_alloc(int n){ if( n<=0 ) return 0; //if( n & 0x7 ) n += 8 - (n & 0x7) /* align to 8-byte boundary */; n = (n + 7) & ~7 /* align to 8-byte boundary */; if( PStack.pBegin + n > PStack.pPos /*not enough space left*/ || PStack.pBegin + n <= PStack.pBegin /*overflow*/ ) return 0; @@ -322,13 +316,13 @@ memset((PStack.pPos = PStack.pPos - n), 0, (unsigned int)n); return PStack.pPos; } /* ** Return the number of bytes left which can be -** sqlite3__wasm_pstack_alloc()'d. +** sqlite3_wasm_pstack_alloc()'d. */ -SQLITE_WASM_EXPORT int sqlite3__wasm_pstack_remaining(void){ +SQLITE_WASM_EXPORT int sqlite3_wasm_pstack_remaining(void){ assert(PStack.pPos >= PStack.pBegin); assert(PStack.pPos <= PStack.pEnd); return (int)(PStack.pPos - PStack.pBegin); } @@ -335,11 +329,11 @@ /* ** Return the total number of bytes available in the pstack, including ** any space which is currently allocated. This value is a ** compile-time constant. */ -SQLITE_WASM_EXPORT int sqlite3__wasm_pstack_quota(void){ +SQLITE_WASM_EXPORT int sqlite3_wasm_pstack_quota(void){ return (int)(PStack.pEnd - PStack.pBegin); } /* ** This function is NOT part of the sqlite3 public API. It is strictly @@ -354,11 +348,11 @@ ** from client code. ** ** Returns err_code. */ SQLITE_WASM_EXPORT -int sqlite3__wasm_db_error(sqlite3*db, int err_code, const char *zMsg){ +int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){ if( db!=0 ){ if( 0!=zMsg ){ const int nMsg = sqlite3Strlen30(zMsg); sqlite3_mutex_enter(sqlite3_db_mutex(db)); sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg); @@ -378,11 +372,11 @@ int64_t v8; void (*xFunc)(void*); }; typedef struct WasmTestStruct WasmTestStruct; SQLITE_WASM_EXPORT -void sqlite3__wasm_test_struct(WasmTestStruct * s){ +void sqlite3_wasm_test_struct(WasmTestStruct * s){ if(s){ s->v4 *= 2; s->v8 = s->v4 * 2; s->ppV = s; s->cstr = __FILE__; @@ -406,11 +400,11 @@ ** If this function returns NULL then it means that the internal ** buffer is not large enough for the generated JSON and needs to be ** increased. In debug builds that will trigger an assert(). */ SQLITE_WASM_EXPORT -const char * sqlite3__wasm_enum_json(void){ +const char * sqlite3_wasm_enum_json(void){ static char aBuffer[1024 * 20] = {0} /* where the JSON goes */; int n = 0, nChildren = 0, nStruct = 0 /* output counters for figuring out where commas go */; char * zPos = &aBuffer[1] /* skip first byte for now to help protect ** against a small race condition */; @@ -423,11 +417,11 @@ ** instance might return and use the string before the 1st instance ** is done filling it. */ /* Core output macros... */ #define lenCheck assert(zPos < zEnd - 128 \ - && "sqlite3__wasm_enum_json() buffer is too small."); \ + && "sqlite3_wasm_enum_json() buffer is too small."); \ if( zPos >= zEnd - 128 ) return 0 #define outf(format,...) \ zPos += snprintf(zPos, ((size_t)(zEnd - zPos)), format, __VA_ARGS__); \ lenCheck #define out(TXT) outf("%s",TXT) @@ -1101,11 +1095,11 @@ M(xRollbackTo, "i(pi)"); // ^^^ v2. v3+ follows... M(xShadowName, "i(s)"); } _StructBinder; #undef CurrentStruct - + /** ** Workaround: in order to map the various inner structs from ** sqlite3_index_info, we have to uplift those into constructs we ** can access by type name. These structs _must_ match their ** in-sqlite3_index_info counterparts byte for byte. @@ -1218,11 +1212,11 @@ ** zName is NULL, no default VFS is found, or it has no xDelete ** method, SQLITE_MISUSE is returned, else the result of the xDelete() ** call is returned. */ SQLITE_WASM_EXPORT -int sqlite3__wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){ +int sqlite3_wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){ int rc = SQLITE_MISUSE /* ??? */; if( 0==pVfs && 0!=zName ) pVfs = sqlite3_vfs_find(0); if( zName && pVfs && pVfs->xDelete ){ rc = pVfs->xDelete(pVfs, zName, 1); } @@ -1236,11 +1230,11 @@ ** Returns a pointer to the given DB's VFS for the given DB name, ** defaulting to "main" if zDbName is 0. Returns 0 if no db with the ** given name is open. */ SQLITE_WASM_EXPORT -sqlite3_vfs * sqlite3__wasm_db_vfs(sqlite3 *pDb, const char *zDbName){ +sqlite3_vfs * sqlite3_wasm_db_vfs(sqlite3 *pDb, const char *zDbName){ sqlite3_vfs * pVfs = 0; sqlite3_file_control(pDb, zDbName ? zDbName : "main", SQLITE_FCNTL_VFS_POINTER, &pVfs); return pVfs; } @@ -1259,11 +1253,11 @@ ** ** Returns 0 on success, an SQLITE_xxx code on error. Returns ** SQLITE_MISUSE if pDb is NULL. */ SQLITE_WASM_EXPORT -int sqlite3__wasm_db_reset(sqlite3 *pDb){ +int sqlite3_wasm_db_reset(sqlite3 *pDb){ int rc = SQLITE_MISUSE; if( pDb ){ sqlite3_table_column_metadata(pDb, "main", 0, 0, 0, 0, 0, 0, 0); rc = sqlite3_db_config(pDb, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); if( 0==rc ){ @@ -1286,15 +1280,15 @@ ** code from the callback. Note that this is not thread-friendly: it ** expects that it will be the only thread reading the db file and ** takes no measures to ensure that is the case. ** ** This implementation appears to work fine, but -** sqlite3__wasm_db_serialize() is arguably the better way to achieve +** sqlite3_wasm_db_serialize() is arguably the better way to achieve ** this. */ SQLITE_WASM_EXPORT -int sqlite3__wasm_db_export_chunked( sqlite3* pDb, +int sqlite3_wasm_db_export_chunked( sqlite3* pDb, int (*xCallback)(unsigned const char *zOut, int n) ){ sqlite3_int64 nSize = 0; sqlite3_int64 nPos = 0; sqlite3_file * pFile = 0; unsigned char buf[1024 * 8]; @@ -1341,11 +1335,11 @@ ** ** If `*pOut` is not NULL, the caller is responsible for passing it to ** sqlite3_free() to free it. */ SQLITE_WASM_EXPORT -int sqlite3__wasm_db_serialize( sqlite3 *pDb, const char *zSchema, +int sqlite3_wasm_db_serialize( sqlite3 *pDb, const char *zSchema, unsigned char **pOut, sqlite3_int64 *nOut, unsigned int mFlags ){ unsigned char * z; if( !pDb || !pOut ) return SQLITE_MISUSE; if( nOut ) *nOut = 0; @@ -1364,11 +1358,11 @@ ** ** ACHTUNG: it was discovered on 2023-08-11 that, with SQLITE_DEBUG, ** this function's out-of-scope use of the sqlite3_vfs/file/io_methods ** APIs leads to triggering of assertions in the core library. Its use ** is now deprecated and VFS-specific APIs for importing files need to -** be found to replace it. sqlite3__wasm_posix_create_file() is +** be found to replace it. sqlite3_wasm_posix_create_file() is ** suitable for the "unix" family of VFSes. ** ** Creates a new file using the I/O API of the given VFS, containing ** the given number of bytes of the given data. If the file exists, it ** is truncated to the given length and populated with the given @@ -1405,11 +1399,11 @@ ** Design note: nData is an integer, instead of int64, for WASM ** portability, so that the API can still work in builds where BigInt ** support is disabled or unavailable. */ SQLITE_WASM_EXPORT -int sqlite3__wasm_vfs_create_file( sqlite3_vfs *pVfs, +int sqlite3_wasm_vfs_create_file( sqlite3_vfs *pVfs, const char *zFilename, const unsigned char * pData, int nData ){ int rc; sqlite3_file *pFile = 0; @@ -1495,11 +1489,11 @@ ** i.e. Emscripten's virtual filesystem. Creates or truncates ** zFilename, appends pData bytes to it, and returns 0 on success or ** SQLITE_IOERR on error. */ SQLITE_WASM_EXPORT -int sqlite3__wasm_posix_create_file( const char *zFilename, +int sqlite3_wasm_posix_create_file( const char *zFilename, const unsigned char * pData, int nData ){ int rc; FILE * pFile = 0; int fileExisted = 0; @@ -1518,21 +1512,21 @@ /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. ** ** Allocates sqlite3KvvfsMethods.nKeySize bytes from -** sqlite3__wasm_pstack_alloc() and returns 0 if that allocation fails, +** sqlite3_wasm_pstack_alloc() and returns 0 if that allocation fails, ** else it passes that string to kvstorageMakeKey() and returns a ** NUL-terminated pointer to that string. It is up to the caller to -** use sqlite3__wasm_pstack_restore() to free the returned pointer. +** use sqlite3_wasm_pstack_restore() to free the returned pointer. */ SQLITE_WASM_EXPORT -char * sqlite3__wasm_kvvfsMakeKeyOnPstack(const char *zClass, +char * sqlite3_wasm_kvvfsMakeKeyOnPstack(const char *zClass, const char *zKeyIn){ assert(sqlite3KvvfsMethods.nKeySize>24); char *zKeyOut = - (char *)sqlite3__wasm_pstack_alloc(sqlite3KvvfsMethods.nKeySize); + (char *)sqlite3_wasm_pstack_alloc(sqlite3KvvfsMethods.nKeySize); if(zKeyOut){ kvstorageMakeKey(zClass, zKeyIn, zKeyOut); } return zKeyOut; } @@ -1543,11 +1537,11 @@ ** ** Returns the pointer to the singleton object which holds the kvvfs ** I/O methods and associated state. */ SQLITE_WASM_EXPORT -sqlite3_kvvfs_methods * sqlite3__wasm_kvvfs_methods(void){ +sqlite3_kvvfs_methods * sqlite3_wasm_kvvfs_methods(void){ return &sqlite3KvvfsMethods; } /* ** This function is NOT part of the sqlite3 public API. It is strictly @@ -1558,11 +1552,11 @@ ** value of its 2nd argument. Returns the result of ** sqlite3_vtab_config(), or SQLITE_MISUSE if the 2nd arg is not a ** valid value. */ SQLITE_WASM_EXPORT -int sqlite3__wasm_vtab_config(sqlite3 *pDb, int op, int arg){ +int sqlite3_wasm_vtab_config(sqlite3 *pDb, int op, int arg){ switch(op){ case SQLITE_VTAB_DIRECTONLY: case SQLITE_VTAB_INNOCUOUS: return sqlite3_vtab_config(pDb, op); case SQLITE_VTAB_CONSTRAINT_SUPPORT: @@ -1578,11 +1572,11 @@ ** ** Wrapper for the variants of sqlite3_db_config() which take ** (int,int*) variadic args. */ SQLITE_WASM_EXPORT -int sqlite3__wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){ +int sqlite3_wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){ switch(op){ case SQLITE_DBCONFIG_ENABLE_FKEY: case SQLITE_DBCONFIG_ENABLE_TRIGGER: case SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: case SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: @@ -1611,11 +1605,11 @@ ** ** Wrapper for the variants of sqlite3_db_config() which take ** (void*,int,int) variadic args. */ SQLITE_WASM_EXPORT -int sqlite3__wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, int arg3){ +int sqlite3_wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, int arg3){ switch(op){ case SQLITE_DBCONFIG_LOOKASIDE: return sqlite3_db_config(pDb, op, pArg1, arg2, arg3); default: return SQLITE_MISUSE; } @@ -1627,11 +1621,11 @@ ** ** Wrapper for the variants of sqlite3_db_config() which take ** (const char *) variadic args. */ SQLITE_WASM_EXPORT -int sqlite3__wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){ +int sqlite3_wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){ switch(op){ case SQLITE_DBCONFIG_MAINDBNAME: return sqlite3_db_config(pDb, op, zArg); default: return SQLITE_MISUSE; } @@ -1644,11 +1638,11 @@ ** ** Binding for combinations of sqlite3_config() arguments which take ** a single integer argument. */ SQLITE_WASM_EXPORT -int sqlite3__wasm_config_i(int op, int arg){ +int sqlite3_wasm_config_i(int op, int arg){ return sqlite3_config(op, arg); } /* ** This function is NOT part of the sqlite3 public API. It is strictly @@ -1656,11 +1650,11 @@ ** ** Binding for combinations of sqlite3_config() arguments which take ** two int arguments. */ SQLITE_WASM_EXPORT -int sqlite3__wasm_config_ii(int op, int arg1, int arg2){ +int sqlite3_wasm_config_ii(int op, int arg1, int arg2){ return sqlite3_config(op, arg1, arg2); } /* ** This function is NOT part of the sqlite3 public API. It is strictly @@ -1668,11 +1662,11 @@ ** ** Binding for combinations of sqlite3_config() arguments which take ** a single i64 argument. */ SQLITE_WASM_EXPORT -int sqlite3__wasm_config_j(int op, sqlite3_int64 arg){ +int sqlite3_wasm_config_j(int op, sqlite3_int64 arg){ return sqlite3_config(op, arg); } #if 0 // Pending removal after verification of a workaround discussed in the @@ -1687,21 +1681,21 @@ ** sqlite3.wasm.exports.sqlite3_free. i.e. from a dev console where ** sqlite3 is exported globally, the following must be true: ** ** ``` ** sqlite3.wasm.functionEntry( -** sqlite3.wasm.exports.sqlite3__wasm_ptr_to_sqlite3_free() +** sqlite3.wasm.exports.sqlite3_wasm_ptr_to_sqlite3_free() ** ) === sqlite3.wasm.exports.sqlite3_free ** ``` ** ** Using a function to return this pointer, as opposed to exporting it -** via sqlite3__wasm_enum_json(), is an attempt to work around a +** via sqlite3_wasm_enum_json(), is an attempt to work around a ** Safari-specific quirk covered at ** https://sqlite.org/forum/info/e5b20e1feb37a19a. **/ SQLITE_WASM_EXPORT -void * sqlite3__wasm_ptr_to_sqlite3_free(void){ +void * sqlite3_wasm_ptr_to_sqlite3_free(void){ return (void*)sqlite3_free; } #endif #if defined(__EMSCRIPTEN__) && defined(SQLITE_ENABLE_WASMFS) @@ -1727,11 +1721,11 @@ ** object fails, SQLITE_IOERR if mkdir() of the zMountPoint dir in ** the virtual FS fails. In builds compiled without SQLITE_ENABLE_WASMFS ** defined, SQLITE_NOTFOUND is returned without side effects. */ SQLITE_WASM_EXPORT -int sqlite3__wasm_init_wasmfs(const char *zMountPoint){ +int sqlite3_wasm_init_wasmfs(const char *zMountPoint){ static backend_t pOpfs = 0; if( !zMountPoint || !*zMountPoint ) zMountPoint = "/opfs"; if( !pOpfs ){ pOpfs = wasmfs_create_opfs_backend(); } @@ -1747,65 +1741,65 @@ } return pOpfs ? 0 : SQLITE_NOMEM; } #else SQLITE_WASM_EXPORT -int sqlite3__wasm_init_wasmfs(const char *zUnused){ +int sqlite3_wasm_init_wasmfs(const char *zUnused){ //emscripten_console_warn("WASMFS OPFS is not compiled in."); if(zUnused){/*unused*/} return SQLITE_NOTFOUND; } #endif /* __EMSCRIPTEN__ && SQLITE_ENABLE_WASMFS */ #if SQLITE_WASM_TESTS SQLITE_WASM_EXPORT -int sqlite3__wasm_test_intptr(int * p){ +int sqlite3_wasm_test_intptr(int * p){ return *p = *p * 2; } SQLITE_WASM_EXPORT -void * sqlite3__wasm_test_voidptr(void * p){ +void * sqlite3_wasm_test_voidptr(void * p){ return p; } SQLITE_WASM_EXPORT -int64_t sqlite3__wasm_test_int64_max(void){ +int64_t sqlite3_wasm_test_int64_max(void){ return (int64_t)0x7fffffffffffffff; } SQLITE_WASM_EXPORT -int64_t sqlite3__wasm_test_int64_min(void){ - return ~sqlite3__wasm_test_int64_max(); +int64_t sqlite3_wasm_test_int64_min(void){ + return ~sqlite3_wasm_test_int64_max(); } SQLITE_WASM_EXPORT -int64_t sqlite3__wasm_test_int64_times2(int64_t x){ +int64_t sqlite3_wasm_test_int64_times2(int64_t x){ return x * 2; } SQLITE_WASM_EXPORT -void sqlite3__wasm_test_int64_minmax(int64_t * min, int64_t *max){ - *max = sqlite3__wasm_test_int64_max(); - *min = sqlite3__wasm_test_int64_min(); +void sqlite3_wasm_test_int64_minmax(int64_t * min, int64_t *max){ + *max = sqlite3_wasm_test_int64_max(); + *min = sqlite3_wasm_test_int64_min(); /*printf("minmax: min=%lld, max=%lld\n", *min, *max);*/ } SQLITE_WASM_EXPORT -int64_t sqlite3__wasm_test_int64ptr(int64_t * p){ - /*printf("sqlite3__wasm_test_int64ptr( @%lld = 0x%llx )\n", (int64_t)p, *p);*/ +int64_t sqlite3_wasm_test_int64ptr(int64_t * p){ + /*printf("sqlite3_wasm_test_int64ptr( @%lld = 0x%llx )\n", (int64_t)p, *p);*/ return *p = *p * 2; } SQLITE_WASM_EXPORT -void sqlite3__wasm_test_stack_overflow(int recurse){ - if(recurse) sqlite3__wasm_test_stack_overflow(recurse); +void sqlite3_wasm_test_stack_overflow(int recurse){ + if(recurse) sqlite3_wasm_test_stack_overflow(recurse); } /* For testing the 'string:dealloc' whwasmutil.xWrap() conversion. */ SQLITE_WASM_EXPORT -char * sqlite3__wasm_test_str_hello(int fail){ +char * sqlite3_wasm_test_str_hello(int fail){ char * s = fail ? 0 : (char *)sqlite3_malloc(6); if(s){ memcpy(s, "hello", 5); s[5] = 0; } @@ -1836,16 +1830,16 @@ ** ** '#' 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 sqlite3__wasm_SQLTester_strnotglob(const char *zGlob, const char *z){ +static int sqlite3_wasm_SQLTester_strnotglob(const char *zGlob, const char *z){ int c, c2; int invert; int seen; typedef int (*recurse_f)(const char *,const char *); - static const recurse_f recurse = sqlite3__wasm_SQLTester_strnotglob; + static const recurse_f recurse = sqlite3_wasm_SQLTester_strnotglob; while( (c = (*(zGlob++)))!=0 ){ if( c=='*' ){ while( (c=(*(zGlob++))) == '*' || c=='?' ){ if( c=='?' && (*(z++))==0 ) return 0; @@ -1916,12 +1910,13 @@ } return *z==0; } SQLITE_WASM_EXPORT -int sqlite3__wasm_SQLTester_strglob(const char *zGlob, const char *z){ - return !sqlite3__wasm_SQLTester_strnotglob(zGlob, z); +int sqlite3_wasm_SQLTester_strglob(const char *zGlob, const char *z){ + return !sqlite3_wasm_SQLTester_strnotglob(zGlob, z); } + #endif /* SQLITE_WASM_TESTS */ #undef SQLITE_WASM_EXPORT Index: ext/wasm/api/sqlite3-worker1-promiser.c-pp.js ================================================================== --- ext/wasm/api/sqlite3-worker1-promiser.c-pp.js +++ ext/wasm/api/sqlite3-worker1-promiser.c-pp.js @@ -1,6 +1,5 @@ -//#ifnot omit-oo1 /* 2022-08-24 The author disclaims copyright to this source code. In place of a legal notice, here is a blessing: @@ -40,17 +39,13 @@ function, enabling delayed instantiation of a Worker. - `onready` (optional, but...): this callback is called with no arguments when the worker fires its initial 'sqlite3-api'/'worker1-ready' message, which it does when - sqlite3.initWorker1API() completes its initialization. This is the - simplest way to tell the worker to kick off work at the earliest - opportunity, and the only way to know when the worker module has - completed loading. The irony of using a callback for this, instead - of returning a promise from sqlite3Worker1Promiser() is not lost on - the developers: see sqlite3Worker1Promiser.v2() which uses a - Promise instead. + sqlite3.initWorker1API() completes its initialization. This is + the simplest way to tell the worker to kick off work at the + earliest opportunity. - `onunhandled` (optional): a callback which gets passed the message event object for any worker.onmessage() events which are not handled by this proxy. Ideally that "should" never happen, as this proxy aims to handle all known message types. @@ -158,19 +153,18 @@ }; const toss = (...args)=>{throw new Error(args.join(' '))}; if(!config.worker) config.worker = callee.defaultConfig.worker; if('function'===typeof config.worker) config.worker = config.worker(); let dbId; - let promiserFunc; config.worker.onmessage = function(ev){ ev = ev.data; debug('worker1.onmessage',ev); let msgHandler = handlerMap[ev.messageId]; if(!msgHandler){ if(ev && 'sqlite3-api'===ev.type && 'worker1-ready'===ev.result) { /*fired one time when the Worker1 API initializes*/ - if(config.onready) config.onready(promiserFunc); + if(config.onready) config.onready(); return; } msgHandler = handlerMap[ev.type] /* check for exec per-row callback */; if(msgHandler && msgHandler.onrow){ msgHandler.onrow(ev); @@ -195,23 +189,22 @@ break; } try {msgHandler.resolve(ev)} catch(e){msgHandler.reject(e)} }/*worker.onmessage()*/; - return promiserFunc = function(/*(msgType, msgArgs) || (msgEnvelope)*/){ + return function(/*(msgType, msgArgs) || (msgEnvelope)*/){ let msg; if(1===arguments.length){ msg = arguments[0]; }else if(2===arguments.length){ msg = Object.create(null); msg.type = arguments[0]; msg.args = arguments[1]; - msg.dbId = msg.args.dbId; }else{ toss("Invalid arugments for sqlite3Worker1Promiser()-created factory."); } - if(!msg.dbId && msg.type!=='open') msg.dbId = dbId; + if(!msg.dbId) msg.dbId = dbId; msg.messageId = genMsgId(msg); msg.departureTime = performance.now(); const proxy = Object.create(null); proxy.message = msg; let rowCallbackId /* message handler ID for exec on-row callback proxy */; @@ -249,16 +242,16 @@ }); if(rowCallbackId) p = p.finally(()=>delete handlerMap[rowCallbackId]); return p; }; }/*sqlite3Worker1Promiser()*/; - globalThis.sqlite3Worker1Promiser.defaultConfig = { worker: function(){ -//#if target=es6-module - return new Worker(new URL("sqlite3-worker1-bundler-friendly.mjs", import.meta.url),{ - type: 'module' +//#if target=es6-bundler-friendly + return new Worker("sqlite3-worker1-bundler-friendly.mjs",{ + type: 'module' /* Noting that neither Firefox nor Safari suppor this, + as of this writing. */ }); //#else let theJs = "sqlite3-worker1.js"; if(this.currentScript){ const src = this.currentScript.src.split('/'); @@ -272,75 +265,10 @@ theJs = urlParams.get('sqlite3.dir') + '/' + theJs; } } return new Worker(theJs + globalThis.location.search); //#endif - } -//#ifnot target=es6-module - .bind({ + }.bind({ currentScript: globalThis?.document?.currentScript - }) -//#endif - , + }), onerror: (...args)=>console.error('worker1 promiser error',...args) -}/*defaultConfig*/; - -/** - sqlite3Worker1Promiser.v2() works identically to - sqlite3Worker1Promiser() except that it returns a Promise instead - of relying an an onready callback in the config object. The Promise - resolves to the same factory function which - sqlite3Worker1Promiser() returns. - - If config is-a function or is an object which contains an onready - function, that function is replaced by a proxy which will resolve - after calling the original function and will reject if that - function throws. -*/ -sqlite3Worker1Promiser.v2 = function(config){ - let oldFunc; - if( 'function' == typeof config ){ - oldFunc = config; - config = {}; - }else if('function'===typeof config?.onready){ - oldFunc = config.onready; - delete config.onready; - } - const promiseProxy = Object.create(null); - config = Object.assign((config || Object.create(null)),{ - onready: async function(func){ - try { - if( oldFunc ) await oldFunc(func); - promiseProxy.resolve(func); - } - catch(e){promiseProxy.reject(e)} - } - }); - const p = new Promise(function(resolve,reject){ - promiseProxy.resolve = resolve; - promiseProxy.reject = reject; - }); - try{ - this.original(config); - }catch(e){ - promiseProxy.reject(e); - } - return p; -}.bind({ - /* We do this because clients are - recommended to delete globalThis.sqlite3Worker1Promiser. */ - original: sqlite3Worker1Promiser -}); - -//#if target=es6-module -/** - When built as a module, we export sqlite3Worker1Promiser.v2() - instead of sqlite3Worker1Promise() because (A) its interface is more - conventional for ESM usage and (B) the ESM option export option for - this API did not exist until v2 was created, so there's no backwards - incompatibility. -*/ -export default sqlite3Worker1Promiser.v2; -//#endif /* target=es6-module */ -//#else -/* Built with the omit-oo1 flag. */ -//#endif ifnot omit-oo1 +}; Index: ext/wasm/api/sqlite3-worker1.c-pp.js ================================================================== --- ext/wasm/api/sqlite3-worker1.c-pp.js +++ ext/wasm/api/sqlite3-worker1.c-pp.js @@ -1,6 +1,5 @@ -//#ifnot omit-oo1 /* 2022-05-23 The author disclaims copyright to this source code. In place of a legal notice, here is a blessing: @@ -47,8 +46,5 @@ //console.warn("worker1 theJs =",theJs); importScripts(theJs); } //#endif sqlite3InitModule().then(sqlite3 => sqlite3.initWorker1API()); -//#else -/* Built with the omit-oo1 flag. */ -//#endif ifnot omit-oo1 DELETED ext/wasm/batch-runner-sahpool.html Index: ext/wasm/batch-runner-sahpool.html ================================================================== --- ext/wasm/batch-runner-sahpool.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - sqlite3-api batch SQL runner for the SAHPool VFS - - -

        sqlite3-api batch SQL runner for the SAHPool VFS
        -
        - - - - -
        -
        - - - - DELETED ext/wasm/batch-runner-sahpool.js Index: ext/wasm/batch-runner-sahpool.js ================================================================== --- ext/wasm/batch-runner-sahpool.js +++ /dev/null @@ -1,341 +0,0 @@ -/* - 2023-11-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. - - *********************************************************************** - - A basic batch SQL runner for the SAHPool VFS. This file must be run in - a worker thread. This is not a full-featured app, just a way to get some - measurements for batch execution of SQL for the OPFS SAH Pool VFS. -*/ -'use strict'; - -const wMsg = function(msgType,...args){ - postMessage({ - type: msgType, - data: args - }); -}; -const toss = function(...args){throw new Error(args.join(' '))}; -const warn = (...args)=>{ wMsg('warn',...args); }; -const error = (...args)=>{ wMsg('error',...args); }; -const log = (...args)=>{ wMsg('stdout',...args); } -let sqlite3; -const urlParams = new URL(globalThis.location.href).searchParams; -const cacheSize = (()=>{ - if(urlParams.has('cachesize')) return +urlParams.get('cachesize'); - return 200; -})(); - - -/** Throws if the given sqlite3 result code is not 0. */ -const checkSqliteRc = (dbh,rc)=>{ - if(rc) toss("Prepare failed:",sqlite3.capi.sqlite3_errmsg(dbh)); -}; - -const sqlToDrop = [ - "SELECT type,name FROM sqlite_schema ", - "WHERE name NOT LIKE 'sqlite\\_%' escape '\\' ", - "AND name NOT LIKE '\\_%' escape '\\'" -].join(''); - -const clearDbSqlite = function(db){ - // This would be SO much easier with the oo1 API, but we specifically want to - // inject metrics we can't get via that API, and we cannot reliably (OPFS) - // open the same DB twice to clear it using that API, so... - const rc = sqlite3.wasm.exports.sqlite3_wasm_db_reset(db.handle); - log("reset db rc =",rc,db.id, db.filename); -}; - -const App = { - db: undefined, - cache:Object.create(null), - log: log, - warn: warn, - error: error, - metrics: { - fileCount: 0, - runTimeMs: 0, - prepareTimeMs: 0, - stepTimeMs: 0, - stmtCount: 0, - strcpyMs: 0, - sqlBytes: 0 - }, - fileList: undefined, - execSql: async function(name,sql){ - const db = this.db; - const banner = "========================================"; - this.log(banner, - "Running",name,'('+sql.length,'bytes)'); - const capi = this.sqlite3.capi, wasm = this.sqlite3.wasm; - let pStmt = 0, pSqlBegin; - const metrics = db.metrics = Object.create(null); - metrics.prepTotal = metrics.stepTotal = 0; - metrics.stmtCount = 0; - metrics.malloc = 0; - metrics.strcpy = 0; - if(this.gotErr){ - this.error("Cannot run SQL: error cleanup is pending."); - return; - } - // Run this async so that the UI can be updated for the above header... - const endRun = ()=>{ - metrics.evalSqlEnd = performance.now(); - metrics.evalTimeTotal = (metrics.evalSqlEnd - metrics.evalSqlStart); - this.log("metrics:",JSON.stringify(metrics, undefined, ' ')); - this.log("prepare() count:",metrics.stmtCount); - this.log("Time in prepare_v2():",metrics.prepTotal,"ms", - "("+(metrics.prepTotal / metrics.stmtCount),"ms per prepare())"); - this.log("Time in step():",metrics.stepTotal,"ms", - "("+(metrics.stepTotal / metrics.stmtCount),"ms per step())"); - this.log("Total runtime:",metrics.evalTimeTotal,"ms"); - this.log("Overhead (time - prep - step):", - (metrics.evalTimeTotal - metrics.prepTotal - metrics.stepTotal)+"ms"); - this.log(banner,"End of",name); - this.metrics.prepareTimeMs += metrics.prepTotal; - this.metrics.stepTimeMs += metrics.stepTotal; - this.metrics.stmtCount += metrics.stmtCount; - this.metrics.strcpyMs += metrics.strcpy; - this.metrics.sqlBytes += sql.length; - }; - - const runner = function(resolve, reject){ - ++this.metrics.fileCount; - metrics.evalSqlStart = performance.now(); - const stack = wasm.scopedAllocPush(); - try { - let t, rc; - let sqlByteLen = sql.byteLength; - const [ppStmt, pzTail] = wasm.scopedAllocPtr(2); - t = performance.now(); - pSqlBegin = wasm.scopedAlloc( sqlByteLen + 1/*SQL + NUL*/) || toss("alloc(",sqlByteLen,") failed"); - metrics.malloc = performance.now() - t; - metrics.byteLength = sqlByteLen; - let pSql = pSqlBegin; - const pSqlEnd = pSqlBegin + sqlByteLen; - t = performance.now(); - wasm.heap8().set(sql, pSql); - wasm.poke(pSql + sqlByteLen, 0); - //log("SQL:",wasm.cstrToJs(pSql)); - metrics.strcpy = performance.now() - t; - let breaker = 0; - while(pSql && wasm.peek8(pSql)){ - wasm.pokePtr(ppStmt, 0); - wasm.pokePtr(pzTail, 0); - t = performance.now(); - rc = capi.sqlite3_prepare_v2( - db.handle, pSql, sqlByteLen, ppStmt, pzTail - ); - metrics.prepTotal += performance.now() - t; - checkSqliteRc(db.handle, rc); - pStmt = wasm.peekPtr(ppStmt); - pSql = wasm.peekPtr(pzTail); - sqlByteLen = pSqlEnd - pSql; - if(!pStmt) continue/*empty statement*/; - ++metrics.stmtCount; - t = performance.now(); - rc = capi.sqlite3_step(pStmt); - capi.sqlite3_finalize(pStmt); - pStmt = 0; - metrics.stepTotal += performance.now() - t; - switch(rc){ - case capi.SQLITE_ROW: - case capi.SQLITE_DONE: break; - default: checkSqliteRc(db.handle, rc); toss("Not reached."); - } - } - resolve(this); - }catch(e){ - if(pStmt) capi.sqlite3_finalize(pStmt); - this.gotErr = e; - reject(e); - }finally{ - capi.sqlite3_exec(db.handle,"rollback;",0,0,0); - wasm.scopedAllocPop(stack); - } - }.bind(this); - const p = new Promise(runner); - return p.catch( - (e)=>this.error("Error via execSql("+name+",...):",e.message) - ).finally(()=>{ - endRun(); - }); - }, - - /** - Loads batch-runner.list and populates the selection list from - it. Returns a promise which resolves to nothing in particular - when it completes. Only intended to be run once at the start - of the app. - */ - loadSqlList: async function(){ - const infile = 'batch-runner.list'; - this.log("Loading list of SQL files:", infile); - let txt; - try{ - const r = await fetch(infile); - if(404 === r.status){ - toss("Missing file '"+infile+"'."); - } - if(!r.ok) toss("Loading",infile,"failed:",r.statusText); - txt = await r.text(); - }catch(e){ - this.error(e.message); - throw e; - } - App.fileList = txt.split(/\n+/).filter(x=>!!x); - this.log("Loaded",infile); - }, - - /** Fetch ./fn and return its contents as a Uint8Array. */ - fetchFile: async function(fn, cacheIt=false){ - if(cacheIt && this.cache[fn]) return this.cache[fn]; - this.log("Fetching",fn,"..."); - let sql; - try { - const r = await fetch(fn); - if(!r.ok) toss("Fetch failed:",r.statusText); - sql = new Uint8Array(await r.arrayBuffer()); - }catch(e){ - this.error(e.message); - throw e; - } - this.log("Fetched",sql.length,"bytes from",fn); - if(cacheIt) this.cache[fn] = sql; - return sql; - }/*fetchFile()*/, - - /** - Converts this.metrics() to a form which is suitable for easy conversion to - CSV. It returns an array of arrays. The first sub-array is the column names. - The 2nd and subsequent are the values, one per test file (only the most recent - metrics are kept for any given file). - */ - metricsToArrays: function(){ - const rc = []; - Object.keys(this.dbs).sort().forEach((k)=>{ - const d = this.dbs[k]; - const m = d.metrics; - delete m.evalSqlStart; - delete m.evalSqlEnd; - const mk = Object.keys(m).sort(); - if(!rc.length){ - rc.push(['db', ...mk]); - } - const row = [k.split('/').pop()/*remove dir prefix from filename*/]; - rc.push(row); - row.push(...mk.map((kk)=>m[kk])); - }); - return rc; - }, - - metricsToBlob: function(colSeparator='\t'){ - const ar = [], ma = this.metricsToArrays(); - if(!ma.length){ - this.error("Metrics are empty. Run something."); - return; - } - ma.forEach(function(row){ - ar.push(row.join(colSeparator),'\n'); - }); - return new Blob(ar); - }, - - /** - Fetch file fn and eval it as an SQL blob. This is an async - operation and returns a Promise which resolves to this - object on success. - */ - evalFile: async function(fn){ - const sql = await this.fetchFile(fn); - return this.execSql(fn,sql); - }/*evalFile()*/, - - /** - Fetches the handle of the db associated with - this.e.selImpl.value, opening it if needed. - */ - initDb: function(){ - const capi = this.sqlite3.capi, wasm = this.sqlite3.wasm; - const stack = wasm.scopedAllocPush(); - let pDb = 0; - const d = Object.create(null); - d.filename = "/batch.db"; - try{ - const oFlags = capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE; - const ppDb = wasm.scopedAllocPtr(); - const rc = capi.sqlite3_open_v2(d.filename, ppDb, oFlags, this.PoolUtil.vfsName); - pDb = wasm.peekPtr(ppDb) - if(rc) toss("sqlite3_open_v2() failed with code",rc); - capi.sqlite3_exec(pDb, "PRAGMA cache_size="+cacheSize, 0, 0, 0); - this.log("cache_size =",cacheSize); - }catch(e){ - if(pDb) capi.sqlite3_close_v2(pDb); - throw e; - }finally{ - wasm.scopedAllocPop(stack); - } - d.handle = pDb; - this.log("Opened db:",d.filename,'@',d.handle); - return d; - }, - - closeDb: function(){ - if(this.db.handle){ - this.sqlite3.capi.sqlite3_close_v2(this.db.handle); - this.db.handle = undefined; - } - }, - - run: async function(sqlite3){ - delete this.run; - this.sqlite3 = sqlite3; - const capi = sqlite3.capi, wasm = sqlite3.wasm; - this.log("Loaded module:",capi.sqlite3_libversion(), capi.sqlite3_sourceid()); - this.log("WASM heap size =",wasm.heap8().length); - let timeStart; - sqlite3.installOpfsSAHPoolVfs({ - clearOnInit: true, initialCapacity: 4, - name: 'batch-sahpool', - verbosity: 2 - }).then(PoolUtil=>{ - App.PoolUtil = PoolUtil; - App.db = App.initDb(); - }) - .then(async ()=>this.loadSqlList()) - .then(async ()=>{ - timeStart = performance.now(); - for(let i = 0; i < App.fileList.length; ++i){ - const fn = App.fileList[i]; - await App.evalFile(fn); - if(App.gotErr) throw App.gotErr; - } - }) - .then(()=>{ - App.metrics.runTimeMs = performance.now() - timeStart; - App.log("total metrics:",JSON.stringify(App.metrics, undefined, ' ')); - App.log("Reload the page to run this again."); - App.closeDb(); - App.PoolUtil.removeVfs(); - }) - .catch(e=>this.error("ERROR:",e)); - }/*run()*/ -}/*App*/; - -let sqlite3Js = 'sqlite3.js'; -if(urlParams.has('sqlite3.dir')){ - sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js; -} -importScripts(sqlite3Js); -globalThis.sqlite3InitModule().then(async function(sqlite3_){ - log("Done initializing. Running batch runner..."); - sqlite3 = sqlite3_; - App.run(sqlite3_); -}); Index: ext/wasm/batch-runner.js ================================================================== --- ext/wasm/batch-runner.js +++ ext/wasm/batch-runner.js @@ -70,10 +70,11 @@ // open the same DB twice to clear it using that API, so... const rc = sqlite3.wasm.exports.sqlite3_wasm_db_reset(db.handle); App.logHtml("reset db rc =",rc,db.id, db.filename); }; + const E = (s)=>document.querySelector(s); const App = { e: { output: E('#test-output'), selSql: E('#sql-select'), @@ -88,19 +89,10 @@ fsToolbar: E('#toolbar') }, db: Object.create(null), dbs: Object.create(null), cache:{}, - metrics: { - fileCount: 0, - runTimeMs: 0, - prepareTimeMs: 0, - stepTimeMs: 0, - stmtCount: 0, - strcpyMs: 0, - sqlBytes: 0 - }, log: console.log.bind(console), warn: console.warn.bind(console), cls: function(){this.e.output.innerHTML = ''}, logHtml2: function(cssClass,...args){ const ln = document.createElement('div'); @@ -123,10 +115,11 @@ const banner = "========================================"; this.logHtml(banner, "Running",name,'('+sql.length,'bytes) using',db.id); const capi = this.sqlite3.capi, wasm = this.sqlite3.wasm; let pStmt = 0, pSqlBegin; + const stack = wasm.scopedAllocPush(); const metrics = db.metrics = Object.create(null); metrics.prepTotal = metrics.stepTotal = 0; metrics.stmtCount = 0; metrics.malloc = 0; metrics.strcpy = 0; @@ -147,15 +140,10 @@ "("+(metrics.stepTotal / metrics.stmtCount),"ms per step())"); this.logHtml("Total runtime:",metrics.evalTimeTotal,"ms"); this.logHtml("Overhead (time - prep - step):", (metrics.evalTimeTotal - metrics.prepTotal - metrics.stepTotal)+"ms"); this.logHtml(banner,"End of",name); - this.metrics.prepareTimeMs += metrics.prepTotal; - this.metrics.stepTimeMs += metrics.stepTotal; - this.metrics.stmtCount += metrics.stmtCount; - this.metrics.strcpyMs += metrics.strcpy; - this.metrics.sqlBytes += sql.length; }; let runner; if('websql'===db.id){ const who = this; @@ -224,13 +212,11 @@ //reject(e); } }.bind(this); }else{/*sqlite3 db...*/ runner = function(resolve, reject){ - ++this.metrics.fileCount; metrics.evalSqlStart = performance.now(); - const stack = wasm.scopedAllocPush(); try { let t; let sqlByteLen = sql.byteLength; const [ppStmt, pzTail] = wasm.scopedAllocPtr(2); t = performance.now(); @@ -281,11 +267,11 @@ }.bind(this); } let p; if(1){ p = new Promise(function(res,rej){ - setTimeout(()=>runner(res, rej), 0)/*give UI a chance to output the "running" banner*/; + setTimeout(()=>runner(res, rej), 50)/*give UI a chance to output the "running" banner*/; }); }else{ p = new Promise(runner); } return p.catch( @@ -413,11 +399,11 @@ ma.forEach(function(row){ ar.push(row.join(colSeparator),'\n'); }); return new Blob(ar); }, - + downloadMetrics: function(){ const b = this.metricsToBlob(); if(!b) return; const url = URL.createObjectURL(b); const a = document.createElement('a'); @@ -588,12 +574,10 @@ v = who.e.selSql.value; } const timeTotal = performance.now() - timeStart; who.logHtml("Run-remaining time:",timeTotal,"ms ("+(timeTotal/1000/60)+" minute(s))"); who.clearStorage(); - App.metrics.runTimeMs = timeTotal; - who.logHtml("Total metrics:",JSON.stringify(App.metrics,undefined,' ')); }, false); }/*run()*/ }/*App*/; self.sqlite3TestModule.initSqlite3().then(function(sqlite3_){ Index: ext/wasm/demo-123.js ================================================================== --- ext/wasm/demo-123.js +++ ext/wasm/demo-123.js @@ -18,11 +18,11 @@ Set up our output channel differently depending on whether we are running in a worker thread or the main (UI) thread. */ let logHtml; - if(globalThis.window === globalThis /* UI thread */){ + if(self.window === self /* UI thread */){ console.log("Running demo from main UI thread."); logHtml = function(cssClass,...args){ const ln = document.createElement('div'); if(cssClass) ln.classList.add(cssClass); ln.append(document.createTextNode(args.join(' '))); @@ -248,11 +248,11 @@ - getParamIndex(name) */ }/*demo1()*/; log("Loading and initializing sqlite3 module..."); - if(globalThis.window!==globalThis) /*worker thread*/{ + if(self.window!==self) /*worker thread*/{ /* If sqlite3.js is in a directory other than this script, in order to get sqlite3.js to resolve sqlite3.wasm properly, we have to explicitly tell it where sqlite3.js is being loaded from. We do that by passing the `sqlite3.dir=theDirName` URL argument to @@ -260,24 +260,23 @@ loader and it will adjust the sqlite3.wasm path accordingly. If sqlite3.js/.wasm are in the same directory as this script then that's not needed. URL arguments passed as part of the filename via importScripts() - are simply lost, and such scripts see the globalThis.location of + are simply lost, and such scripts see the self.location of _this_ script. */ let sqlite3Js = 'sqlite3.js'; - const urlParams = new URL(globalThis.location.href).searchParams; + const urlParams = new URL(self.location.href).searchParams; if(urlParams.has('sqlite3.dir')){ sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js; } importScripts(sqlite3Js); } - globalThis.sqlite3InitModule({ - /* We can redirect any stdout/stderr from the module like so, but - note that doing so makes use of Emscripten-isms, not - well-defined sqlite APIs. */ + self.sqlite3InitModule({ + // We can redirect any stdout/stderr from the module + // like so... print: log, printErr: error }).then(function(sqlite3){ //console.log('sqlite3 =',sqlite3); log("Done initializing. Running demo..."); DELETED ext/wasm/demo-worker1-promiser.c-pp.html Index: ext/wasm/demo-worker1-promiser.c-pp.html ================================================================== --- ext/wasm/demo-worker1-promiser.c-pp.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - -//#if target=es6-module - worker-promise (via ESM) tests -//#else - worker-promise tests -//#endif - - -
        worker-promise tests
        - -
        -
        -
        Initializing app...
        -
        - On a slow internet connection this may take a moment. If this - message displays for "a long time", intialization may have - failed and the JavaScript console may contain clues as to why. -
        -
        -
        Downloading...
        -
        - -
        -
        Most stuff on this page happens in the dev console.
        -
        -
        - -//#if target=es6-module - -//#else - - -//#endif - - DELETED ext/wasm/demo-worker1-promiser.c-pp.js Index: ext/wasm/demo-worker1-promiser.c-pp.js ================================================================== --- ext/wasm/demo-worker1-promiser.c-pp.js +++ /dev/null @@ -1,285 +0,0 @@ -/* - 2022-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. - - *********************************************************************** - - Demonstration of the sqlite3 Worker API #1 Promiser: a Promise-based - proxy for for the sqlite3 Worker #1 API. -*/ -//#if target=es6-module -import {default as promiserFactory} from "./jswasm/sqlite3-worker1-promiser.mjs"; -//#else -"use strict"; -const promiserFactory = globalThis.sqlite3Worker1Promiser.v2; -delete globalThis.sqlite3Worker1Promiser; -//#endif -(async function(){ - const T = globalThis.SqliteTestUtil; - const eOutput = document.querySelector('#test-output'); - const warn = console.warn.bind(console); - const error = console.error.bind(console); - const log = console.log.bind(console); - const logHtml = async function(cssClass,...args){ - log.apply(this, args); - const ln = document.createElement('div'); - if(cssClass) ln.classList.add(cssClass); - ln.append(document.createTextNode(args.join(' '))); - eOutput.append(ln); - }; - - let startTime; - const testCount = async ()=>{ - logHtml("","Total test count:",T.counter+". Total time =",(performance.now() - startTime),"ms"); - }; - - const promiserConfig = { -//#ifnot target=es6-module - /** - The v1 interfaces uses an onready function. The v2 interface optionally - accepts one but does not require it. If provided, it is called _before_ - the promise is resolved, and the promise is rejected if onready() throws. - */ - onready: function(f){ - /* f === the function returned by promiserFactory(). - Ostensibly (f === workerPromise) but this function is - called before the promiserFactory() Promise resolves, so - before workerPromise is set. */ - console.warn("This is the v2 interface - you don't need an onready() function."); - }, -//#endif - debug: 1 ? undefined : (...args)=>console.debug('worker debug',...args), - onunhandled: function(ev){ - error("Unhandled worker message:",ev.data); - }, - onerror: function(ev){ - error("worker1 error:",ev); - } - }; - const workerPromise = await promiserFactory(promiserConfig) - .then((func)=>{ - console.log("Init complete. Starting tests momentarily."); - globalThis.sqlite3TestModule.setStatus(null)/*hide the HTML-side is-loading spinner*/; - return func; - }); - - const wtest = async function(msgType, msgArgs, callback){ - if(2===arguments.length && 'function'===typeof msgArgs){ - callback = msgArgs; - msgArgs = undefined; - } - const p = 1 - ? workerPromise({type: msgType, args:msgArgs}) - : workerPromise(msgType, msgArgs); - return callback ? p.then(callback).finally(testCount) : p; - }; - - let sqConfig; - const runTests = async function(){ - const dbFilename = '/testing2.sqlite3'; - startTime = performance.now(); - - await wtest('config-get', (ev)=>{ - const r = ev.result; - log('sqlite3.config subset:', r); - T.assert('boolean' === typeof r.bigIntEnabled); - sqConfig = r; - }); - logHtml('', - "Sending 'open' message and waiting for its response before continuing..."); - - await wtest('open', { - filename: dbFilename, - simulateError: 0 /* if true, fail the 'open' */, - }, function(ev){ - const r = ev.result; - log("then open result",r); - T.assert(ev.dbId === r.dbId) - .assert(ev.messageId) - .assert('string' === typeof r.vfs); - promiserConfig.dbId = ev.dbId; - }).then(runTests2); - }; - - const runTests2 = async function(){ - const mustNotReach = ()=>toss("This is not supposed to be reached."); - - await wtest('exec',{ - sql: ["create table t(a,b)", - "insert into t(a,b) values(1,2),(3,4),(5,6)" - ].join(';'), - resultRows: [], columnNames: [], - countChanges: sqConfig.bigIntEnabled ? 64 : true - }, function(ev){ - ev = ev.result; - T.assert(0===ev.resultRows.length) - .assert(0===ev.columnNames.length) - .assert(sqConfig.bigIntEnabled - ? (3n===ev.changeCount) - : (3===ev.changeCount)); - }); - - await wtest('exec',{ - sql: 'select a a, b b from t order by a', - resultRows: [], columnNames: [], - }, function(ev){ - ev = ev.result; - T.assert(3===ev.resultRows.length) - .assert(1===ev.resultRows[0][0]) - .assert(6===ev.resultRows[2][1]) - .assert(2===ev.columnNames.length) - .assert('b'===ev.columnNames[1]); - }); - - await wtest('exec',{ - sql: 'select a a, b b from t order by a', - resultRows: [], columnNames: [], - rowMode: 'object', - countChanges: true - }, function(ev){ - ev = ev.result; - T.assert(3===ev.resultRows.length) - .assert(1===ev.resultRows[0].a) - .assert(6===ev.resultRows[2].b) - .assert(0===ev.changeCount); - }); - - await wtest( - 'exec', - {sql:'intentional_error'}, - mustNotReach - ).catch((e)=>{ - warn("Intentional error:",e); - }); - - await wtest('exec',{ - sql:'select 1 union all select 3', - resultRows: [] - }, function(ev){ - ev = ev.result; - T.assert(2 === ev.resultRows.length) - .assert(1 === ev.resultRows[0][0]) - .assert(3 === ev.resultRows[1][0]) - .assert(undefined === ev.changeCount); - }); - - const resultRowTest1 = function f(ev){ - if(undefined === f.counter) f.counter = 0; - if(null === ev.rowNumber){ - /* End of result set. */ - T.assert(undefined === ev.row) - .assert(2===ev.columnNames.length) - .assert('a'===ev.columnNames[0]) - .assert('B'===ev.columnNames[1]); - }else{ - T.assert(ev.rowNumber > 0); - ++f.counter; - } - log("exec() result row:",ev); - T.assert(null === ev.rowNumber || 'number' === typeof ev.row.B); - }; - await wtest('exec',{ - sql: 'select a a, b B from t order by a limit 3', - callback: resultRowTest1, - rowMode: 'object' - }, function(ev){ - T.assert(3===resultRowTest1.counter); - resultRowTest1.counter = 0; - }); - - const resultRowTest2 = function f(ev){ - if(null === ev.rowNumber){ - /* End of result set. */ - T.assert(undefined === ev.row) - .assert(1===ev.columnNames.length) - .assert('a'===ev.columnNames[0]) - }else{ - T.assert(ev.rowNumber > 0); - f.counter = ev.rowNumber; - } - log("exec() result row:",ev); - T.assert(null === ev.rowNumber || 'number' === typeof ev.row); - }; - await wtest('exec',{ - sql: 'select a a from t limit 3', - callback: resultRowTest2, - rowMode: 0 - }, function(ev){ - T.assert(3===resultRowTest2.counter); - }); - - const resultRowTest3 = function f(ev){ - if(null === ev.rowNumber){ - T.assert(3===ev.columnNames.length) - .assert('foo'===ev.columnNames[0]) - .assert('bar'===ev.columnNames[1]) - .assert('baz'===ev.columnNames[2]); - }else{ - f.counter = ev.rowNumber; - T.assert('number' === typeof ev.row); - } - }; - await wtest('exec',{ - sql: "select 'foo' foo, a bar, 'baz' baz from t limit 2", - callback: resultRowTest3, - columnNames: [], - rowMode: '$bar' - }, function(ev){ - log("exec() result row:",ev); - T.assert(2===resultRowTest3.counter); - }); - - await wtest('exec',{ - sql:[ - 'pragma foreign_keys=0;', - // ^^^ arbitrary query with no result columns - 'select a, b from t order by a desc; select a from t;' - // exec() only honors SELECT results from the first - // statement with result columns (regardless of whether - // it has any rows). - ], - rowMode: 1, - resultRows: [] - },function(ev){ - const rows = ev.result.resultRows; - T.assert(3===rows.length). - assert(6===rows[0]); - }); - - await wtest('exec',{sql: 'delete from t where a>3'}); - - await wtest('exec',{ - sql: 'select count(a) from t', - resultRows: [] - },function(ev){ - ev = ev.result; - T.assert(1===ev.resultRows.length) - .assert(2===ev.resultRows[0][0]); - }); - - await wtest('export', function(ev){ - ev = ev.result; - T.assert('string' === typeof ev.filename) - .assert(ev.byteArray instanceof Uint8Array) - .assert(ev.byteArray.length > 1024) - .assert('application/x-sqlite3' === ev.mimetype); - }); - - /***** close() tests must come last. *****/ - await wtest('close',{},function(ev){ - T.assert('string' === typeof ev.result.filename); - }); - - await wtest('close', (ev)=>{ - T.assert(undefined === ev.result.filename); - }).finally(()=>logHtml('',"That's all, folks!")); - }/*runTests2()*/; - - runTests(); -})(); ADDED ext/wasm/demo-worker1-promiser.html Index: ext/wasm/demo-worker1-promiser.html ================================================================== --- /dev/null +++ ext/wasm/demo-worker1-promiser.html @@ -0,0 +1,34 @@ + + + + + + + + + worker-promise tests + + +
        worker-promise tests
        + +
        +
        +
        Initializing app...
        +
        + On a slow internet connection this may take a moment. If this + message displays for "a long time", intialization may have + failed and the JavaScript console may contain clues as to why. +
        +
        +
        Downloading...
        +
        + +
        +
        Most stuff on this page happens in the dev console.
        +
        +
        + + + + + ADDED ext/wasm/demo-worker1-promiser.js Index: ext/wasm/demo-worker1-promiser.js ================================================================== --- /dev/null +++ ext/wasm/demo-worker1-promiser.js @@ -0,0 +1,275 @@ +/* + 2022-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. + + *********************************************************************** + + Demonstration of the sqlite3 Worker API #1 Promiser: a Promise-based + proxy for for the sqlite3 Worker #1 API. +*/ +'use strict'; +(function(){ + const T = self.SqliteTestUtil; + const eOutput = document.querySelector('#test-output'); + const warn = console.warn.bind(console); + const error = console.error.bind(console); + const log = console.log.bind(console); + const logHtml = async function(cssClass,...args){ + log.apply(this, args); + const ln = document.createElement('div'); + if(cssClass) ln.classList.add(cssClass); + ln.append(document.createTextNode(args.join(' '))); + eOutput.append(ln); + }; + + let startTime; + const testCount = async ()=>{ + logHtml("","Total test count:",T.counter+". Total time =",(performance.now() - startTime),"ms"); + }; + + //why is this triggered even when we catch() a Promise? + //window.addEventListener('unhandledrejection', function(event) { + // warn('unhandledrejection',event); + //}); + + const promiserConfig = { + worker: ()=>{ + const w = new Worker("jswasm/sqlite3-worker1.js"); + w.onerror = (event)=>error("worker.onerror",event); + return w; + }, + debug: 1 ? undefined : (...args)=>console.debug('worker debug',...args), + onunhandled: function(ev){ + error("Unhandled worker message:",ev.data); + }, + onready: function(){ + self.sqlite3TestModule.setStatus(null)/*hide the HTML-side is-loading spinner*/; + runTests(); + }, + onerror: function(ev){ + error("worker1 error:",ev); + } + }; + const workerPromise = self.sqlite3Worker1Promiser(promiserConfig); + delete self.sqlite3Worker1Promiser; + + const wtest = async function(msgType, msgArgs, callback){ + if(2===arguments.length && 'function'===typeof msgArgs){ + callback = msgArgs; + msgArgs = undefined; + } + const p = 1 + ? workerPromise({type: msgType, args:msgArgs}) + : workerPromise(msgType, msgArgs); + return callback ? p.then(callback).finally(testCount) : p; + }; + + let sqConfig; + const runTests = async function(){ + const dbFilename = '/testing2.sqlite3'; + startTime = performance.now(); + + await wtest('config-get', (ev)=>{ + const r = ev.result; + log('sqlite3.config subset:', r); + T.assert('boolean' === typeof r.bigIntEnabled); + sqConfig = r; + }); + logHtml('', + "Sending 'open' message and waiting for its response before continuing..."); + + await wtest('open', { + filename: dbFilename, + simulateError: 0 /* if true, fail the 'open' */, + }, function(ev){ + const r = ev.result; + log("then open result",r); + T.assert(ev.dbId === r.dbId) + .assert(ev.messageId) + .assert('string' === typeof r.vfs); + promiserConfig.dbId = ev.dbId; + }).then(runTests2); + }; + + const runTests2 = async function(){ + const mustNotReach = ()=>toss("This is not supposed to be reached."); + + await wtest('exec',{ + sql: ["create table t(a,b)", + "insert into t(a,b) values(1,2),(3,4),(5,6)" + ].join(';'), + resultRows: [], columnNames: [], + countChanges: sqConfig.bigIntEnabled ? 64 : true + }, function(ev){ + ev = ev.result; + T.assert(0===ev.resultRows.length) + .assert(0===ev.columnNames.length) + .assert(sqConfig.bigIntEnabled + ? (3n===ev.changeCount) + : (3===ev.changeCount)); + }); + + await wtest('exec',{ + sql: 'select a a, b b from t order by a', + resultRows: [], columnNames: [], + }, function(ev){ + ev = ev.result; + T.assert(3===ev.resultRows.length) + .assert(1===ev.resultRows[0][0]) + .assert(6===ev.resultRows[2][1]) + .assert(2===ev.columnNames.length) + .assert('b'===ev.columnNames[1]); + }); + + await wtest('exec',{ + sql: 'select a a, b b from t order by a', + resultRows: [], columnNames: [], + rowMode: 'object', + countChanges: true + }, function(ev){ + ev = ev.result; + T.assert(3===ev.resultRows.length) + .assert(1===ev.resultRows[0].a) + .assert(6===ev.resultRows[2].b) + .assert(0===ev.changeCount); + }); + + await wtest( + 'exec', + {sql:'intentional_error'}, + mustNotReach + ).catch((e)=>{ + warn("Intentional error:",e); + }); + + await wtest('exec',{ + sql:'select 1 union all select 3', + resultRows: [] + }, function(ev){ + ev = ev.result; + T.assert(2 === ev.resultRows.length) + .assert(1 === ev.resultRows[0][0]) + .assert(3 === ev.resultRows[1][0]) + .assert(undefined === ev.changeCount); + }); + + const resultRowTest1 = function f(ev){ + if(undefined === f.counter) f.counter = 0; + if(null === ev.rowNumber){ + /* End of result set. */ + T.assert(undefined === ev.row) + .assert(2===ev.columnNames.length) + .assert('a'===ev.columnNames[0]) + .assert('B'===ev.columnNames[1]); + }else{ + T.assert(ev.rowNumber > 0); + ++f.counter; + } + log("exec() result row:",ev); + T.assert(null === ev.rowNumber || 'number' === typeof ev.row.B); + }; + await wtest('exec',{ + sql: 'select a a, b B from t order by a limit 3', + callback: resultRowTest1, + rowMode: 'object' + }, function(ev){ + T.assert(3===resultRowTest1.counter); + resultRowTest1.counter = 0; + }); + + const resultRowTest2 = function f(ev){ + if(null === ev.rowNumber){ + /* End of result set. */ + T.assert(undefined === ev.row) + .assert(1===ev.columnNames.length) + .assert('a'===ev.columnNames[0]) + }else{ + T.assert(ev.rowNumber > 0); + f.counter = ev.rowNumber; + } + log("exec() result row:",ev); + T.assert(null === ev.rowNumber || 'number' === typeof ev.row); + }; + await wtest('exec',{ + sql: 'select a a from t limit 3', + callback: resultRowTest2, + rowMode: 0 + }, function(ev){ + T.assert(3===resultRowTest2.counter); + }); + + const resultRowTest3 = function f(ev){ + if(null === ev.rowNumber){ + T.assert(3===ev.columnNames.length) + .assert('foo'===ev.columnNames[0]) + .assert('bar'===ev.columnNames[1]) + .assert('baz'===ev.columnNames[2]); + }else{ + f.counter = ev.rowNumber; + T.assert('number' === typeof ev.row); + } + }; + await wtest('exec',{ + sql: "select 'foo' foo, a bar, 'baz' baz from t limit 2", + callback: resultRowTest3, + columnNames: [], + rowMode: '$bar' + }, function(ev){ + log("exec() result row:",ev); + T.assert(2===resultRowTest3.counter); + }); + + await wtest('exec',{ + sql:[ + 'pragma foreign_keys=0;', + // ^^^ arbitrary query with no result columns + 'select a, b from t order by a desc; select a from t;' + // exec() only honors SELECT results from the first + // statement with result columns (regardless of whether + // it has any rows). + ], + rowMode: 1, + resultRows: [] + },function(ev){ + const rows = ev.result.resultRows; + T.assert(3===rows.length). + assert(6===rows[0]); + }); + + await wtest('exec',{sql: 'delete from t where a>3'}); + + await wtest('exec',{ + sql: 'select count(a) from t', + resultRows: [] + },function(ev){ + ev = ev.result; + T.assert(1===ev.resultRows.length) + .assert(2===ev.resultRows[0][0]); + }); + + await wtest('export', function(ev){ + ev = ev.result; + T.assert('string' === typeof ev.filename) + .assert(ev.byteArray instanceof Uint8Array) + .assert(ev.byteArray.length > 1024) + .assert('application/x-sqlite3' === ev.mimetype); + }); + + /***** close() tests must come last. *****/ + await wtest('close',{},function(ev){ + T.assert('string' === typeof ev.result.filename); + }); + + await wtest('close', (ev)=>{ + T.assert(undefined === ev.result.filename); + }).finally(()=>logHtml('',"That's all, folks!")); + }/*runTests2()*/; + + log("Init complete, but async init bits may still be running."); +})(); Index: ext/wasm/dist.make ================================================================== --- ext/wasm/dist.make +++ ext/wasm/dist.make @@ -47,22 +47,16 @@ demo-123.html demo-123-worker.html demo-123.js \ tester1.html tester1-worker.html tester1-esm.html \ tester1.js tester1.mjs \ demo-jsstorage.html demo-jsstorage.js \ demo-worker1.html demo-worker1.js \ - demo-worker1-promiser.html demo-worker1-promiser.js \ - demo-worker1-promiser-esm.html demo-worker1-promiser.mjs -dist.jswasm.extras := $(sqlite3.wasm) \ - $(sqlite3-api.ext.jses) + demo-worker1-promiser.html demo-worker1-promiser.js +dist.jswasm.extras := $(sqlite3-api.ext.jses) $(sqlite3.wasm) dist.common.extras := \ $(wildcard $(dir.common)/*.css) \ $(dir.common)/SqliteTestUtil.js -#$(info sqlite3-worker1-promiser.mjs = $(sqlite3-worker1-promiser.mjs)) -#$(info sqlite3-worker1.js = $(sqlite3-worker1.js)) -#$(info sqlite3-api.ext.jses = $(sqlite3-api.ext.jses)) -#$(info dist.jswasm.extras = $(dist.jswasm.extras)) .PHONY: dist snapshot # DIST_STRIP_COMMENTS $(call)able to be used in stripping C-style # from the dist copies of certain files. # # $1 = source js file @@ -71,12 +65,11 @@ $(bin.stripccomments) $(2) < $(1) > $(dist-dir.jswasm)/$(notdir $(1)) || exit; endef # STRIP_K1.js = list of JS files which need to be passed through # $(bin.stripcomments) with a single -k flag. STRIP_K1.js := $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js) \ - $(sqlite3-worker1-bundler-friendly.js) \ - $(sqlite3-api.ext.jses) + $(sqlite3-worker1-bundler-friendly.js) $(sqlite3-worker1-promiser-bundler-friendly.js) # STRIP_K2.js = list of JS files which need to be passed through # $(bin.stripcomments) with two -k flags. STRIP_K2.js := $(sqlite3.js) $(sqlite3.mjs) \ $(sqlite3-bundler-friendly.mjs) $(sqlite3-node.mjs) ######################################################################## @@ -93,11 +86,10 @@ # dist file's name, so cannot (without a recursive make) have the # target name equal to the archive name. dist: \ $(bin.stripccomments) $(bin.version-info) \ $(dist.build) $(STRIP_K1.js) $(STRIP_K2.js) \ - $(dist.jswasm.extras) $(dist.common.extras) \ $(MAKEFILE) $(MAKEFILE.dist) @echo "Making end-user deliverables..." @rm -fr $(dist-dir.top) @mkdir -p $(dist-dir.jswasm) $(dist-dir.common) @cp -p $(dist.top.extras) $(dist-dir.top) Index: ext/wasm/fiddle.make ================================================================== --- ext/wasm/fiddle.make +++ ext/wasm/fiddle.make @@ -7,22 +7,20 @@ ######################################################################## # shell.c and its build flags... make-np-0 := make -C $(dir.top) -n -p make-np-1 := sed -e 's/(TOP)/(dir.top)/g' -# Extract SHELL_OPT and SHELL_DEP from the top-most makefile and import -# them as vars here... $(eval $(shell $(make-np-0) | grep -e '^SHELL_OPT ' | $(make-np-1))) -$(eval $(shell $(make-np-0) | grep -e '^SHELL_DEP ' | $(make-np-1))) +$(eval $(shell $(make-np-0) | grep -e '^SHELL_SRC ' | $(make-np-1))) # ^^^ can't do that in 1 invocation b/c newlines get stripped ifeq (,$(SHELL_OPT)) $(error Could not parse SHELL_OPT from $(dir.top)/Makefile.) endif -ifeq (,$(SHELL_DEP)) -$(error Could not parse SHELL_DEP from $(dir.top)/Makefile.) +ifeq (,$(SHELL_SRC)) +$(error Could not parse SHELL_SRC from $(dir.top)/Makefile.) endif -$(dir.top)/shell.c: $(SHELL_DEP) $(dir.top)/tool/mkshellc.tcl $(sqlite3.c) +$(dir.top)/shell.c: $(SHELL_SRC) $(dir.top)/tool/mkshellc.tcl $(sqlite3.c) $(MAKE) -C $(dir.top) shell.c # /shell.c ######################################################################## EXPORTED_FUNCTIONS.fiddle := $(dir.tmp)/EXPORTED_FUNCTIONS.fiddle Index: ext/wasm/fiddle/fiddle-worker.js ================================================================== --- ext/wasm/fiddle/fiddle-worker.js +++ ext/wasm/fiddle/fiddle-worker.js @@ -164,14 +164,15 @@ return false; } stdout("SQLite version", capi.sqlite3_libversion(), capi.sqlite3_sourceid().substr(0,19)); stdout('Welcome to the "fiddle" shell.'); - if(capi.sqlite3_vfs_find("opfs")){ + if(sqlite3.opfs){ stdout("\nOPFS is available. To open a persistent db, use:\n\n", " .open file:name?vfs=opfs\n\nbut note that some", "features (e.g. upload) do not yet work with OPFS."); + sqlite3.opfs.registerVfs(); } stdout('\nEnter ".help" for usage hints.'); this.exec([ // initialization commands... '.nullvalue NULL', '.headers on' @@ -314,11 +315,11 @@ return; } }; console.warn("Unknown fiddle-worker message type:",ev); }; - + /** emscripten module for use with build mode -sMODULARIZE. */ const fiddleModule = { print: stdout, @@ -371,11 +372,13 @@ sqlite3 = _sqlite3; console.warn("Installing sqlite3 module globally (in Worker)", "for use in the dev console.", sqlite3); globalThis.sqlite3 = sqlite3; const dbVfs = sqlite3.wasm.xWrap('fiddle_db_vfs', "*", ['string']); - fiddleModule.fsUnlink = (fn)=>fiddleModule.FS.unlink(fn); + fiddleModule.fsUnlink = (fn)=>{ + return sqlite3.wasm.sqlite3_wasm_vfs_unlink(dbVfs(0), fn); + }; wMsg('fiddle-ready'); }).catch(e=>{ console.error("Fiddle worker init failed:",e); }); })(); Index: ext/wasm/fiddle/fiddle.js ================================================================== --- ext/wasm/fiddle/fiddle.js +++ ext/wasm/fiddle/fiddle.js @@ -401,14 +401,12 @@ // Unhide all elements which start out hidden EAll('.initially-hidden').forEach((e)=>e.classList.remove('initially-hidden')); E('#btn-reset').addEventListener('click',()=>SF.resetDb()); const taInput = E('#input'); const btnClearIn = E('#btn-clear'); - const selectExamples = E('#select-examples'); btnClearIn.addEventListener('click',function(){ taInput.value = ''; - selectExamples.selectedIndex = 0; },false); // Ctrl-enter and shift-enter both run the current SQL. taInput.addEventListener('keydown',function(ev){ if((ev.ctrlKey || ev.shiftKey) && 13 === ev.keyCode){ ev.preventDefault(); @@ -733,19 +731,20 @@ "-- ================================================\n", ".help\n" ]}, //{name: "Timer on", sql: ".timer on"}, // ^^^ re-enable if emscripten re-enables getrusage() - {name: "Box Mode", sql: ".mode box"}, {name: "Setup table T", sql:[ ".nullvalue NULL\n", "CREATE TABLE t(a,b);\n", "INSERT INTO t(a,b) VALUES('abc',123),('def',456),(NULL,789),('ghi',012);\n", "SELECT * FROM t;\n" ]}, - {name: "sqlite_schema", sql: "select * from sqlite_schema"}, - {name: "Mandelbrot", sql:[ + {name: "Table list", sql: ".tables"}, + {name: "Box Mode", sql: ".mode box"}, + {name: "JSON Mode", sql: ".mode json"}, + {name: "Mandlebrot", sql:[ "WITH RECURSIVE", " xaxis(x) AS (VALUES(-2.0) UNION ALL SELECT x+0.05 FROM xaxis WHERE x<1.2),\n", " yaxis(y) AS (VALUES(-1.0) UNION ALL SELECT y+0.1 FROM yaxis WHERE y<1.0),\n", " m(iter, cx, cy, x, y) AS (\n", " SELECT 0, x, y, 0.0, 0.0 FROM xaxis, yaxis\n", @@ -759,17 +758,11 @@ " a(t) AS (\n", " SELECT group_concat( substr(' .+*#', 1+min(iter/7,4), 1), '') \n", " FROM m2 GROUP BY cy\n", " )\n", "SELECT group_concat(rtrim(t),x'0a') as Mandelbrot FROM a;\n", - ]}, - {name: "JSON pretty-print", - sql: "select json_pretty(json_object('ex',json('[52,3.14159]')))" - }, - {name: "JSON pretty-print (with tabs)", - sql: "select json_pretty(json_object('ex',json('[52,3.14159]')),char(0x09))" - } + ]} ]; const newOpt = function(lbl,val){ const o = document.createElement('option'); if(Array.isArray(val)) val = val.join(''); o.value = val; Index: ext/wasm/index-dist.html ================================================================== --- ext/wasm/index-dist.html +++ ext/wasm/index-dist.html @@ -44,19 +44,22 @@
      • All of these pages must be served via an HTTP server. Browsers do not support loading WASM files via file:// URLs.
      • Any OPFS-related pages or tests require:
          -
        • An OPFS-capable browser released after February - 2023. Some tests will work with Chromium-based browsers - going back to around v102.
        • That the web server emit the so-called COOP and COEP headers. althttpd requires the -enable-sab flag for that. +
        • +
        • A very recent version of a Chromium-based browser + (v102 at least, possibly newer). OPFS support in the + other major browsers is pending. Development and testing + is currently done against a dev-channel release of + Chrome (v111 as of 2023-02-10).
      @@ -95,12 +98,10 @@
    • demo-worker1: Worker-based wrapper of the OO API #1. Its Promise-based wrapper is significantly easier to use, however.
    • demo-worker1-promiser: a demo of the Promise-based wrapper of the Worker1 API.
    • -
    • demo-worker1-promiser-esm: - same as the previous demo except loads the promiser from an ESM module.