diff --git a/Makefile.in b/Makefile.in index 2e081c7f68..e496e28bf4 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1316,6 +1316,9 @@ testrunner: testfixture$(TEXE) # devtest: testfixture$(TEXE) fuzztest testrunner +mdevtest: + $(TCLSH_CMD) $(TOP)/test/testrunner.tcl mdevtest + # Testing for a release # releasetest: testfixture$(TEXE) diff --git a/Makefile.msc b/Makefile.msc index a6cb8e2c30..cb5d5d1ed0 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -56,7 +56,7 @@ USE_STDCALL = 0 # in the core library. # !IFNDEF USE_SEH -USE_SEH = 0 +USE_SEH = 1 !ENDIF # Set this non-0 to have the shell executable link against the core dynamic @@ -225,6 +225,12 @@ WIN32HEAP = 0 OSTRACE = 0 !ENDIF +# enable address sanitizer using ASAN=1 on the command-line. +# +!IFNDEF ASAN +ASAN = 0 +!ENDIF + # Set this to one of the following values to enable various debugging # features. Each level includes the debugging options from the previous # levels. Currently, the recognized values for DEBUG are: @@ -891,6 +897,13 @@ RCC = $(RCC) -DSQLITE_WIN32_MALLOC_VALIDATE=1 !ENDIF !ENDIF + +# Address sanitizer if ASAN=1 +# +!IF $(ASAN)>0 +TCC = $(TCC) /fsanitize=address +!ENDIF + # <> # The locations of the Tcl header and library files. Also, the library that # non-stubs enabled programs using Tcl must link against. These variables @@ -2506,6 +2519,9 @@ testrunner: testfixture.exe # devtest: testfixture.exe fuzztest testrunner +mdevtest: + $(TCLSH_CMD) $(TOP)\test\testrunner.tcl mdevtest + # Testing for a release # releasetest: testfixture.exe fuzztest diff --git a/README.md b/README.md index 10262653e7..9c221fff29 100644 --- a/README.md +++ b/README.md @@ -104,9 +104,9 @@ For example: mkdir bld ;# Build will occur in a sibling directory cd bld ;# Change to the build directory ../sqlite/configure ;# Run the configure script - make ;# Run the makefile. + make ;# Builds the "sqlite3" command-line tool make sqlite3.c ;# Build the "amalgamation" source file - make test ;# Run some tests (requires Tcl) + make devtest ;# Run some tests (requires Tcl) See the makefile for additional targets. @@ -119,29 +119,30 @@ show what changes are needed. ## Using MSVC for Windows systems On Windows, all applicable build products can be compiled with MSVC. -First open the command prompt window associated with the desired compiler -version (e.g. "Developer Command Prompt for VS2013"). Next, use NMAKE -with the provided "Makefile.msc" to build one of the supported targets. +You will also need a working installation of TCL. +See the [compile-for-windows.md](doc/compile-for-windows.md) document for +additional information about how to install MSVC and TCL and configure your +build environment. -For example, from the parent directory of the source subtree named "sqlite": +If you want to run tests, you need to let SQLite know the location of your +TCL library, using a command like this: - mkdir bld - cd bld - nmake /f ..\sqlite\Makefile.msc TOP=..\sqlite - nmake /f ..\sqlite\Makefile.msc sqlite3.c TOP=..\sqlite - nmake /f ..\sqlite\Makefile.msc sqlite3.dll TOP=..\sqlite - nmake /f ..\sqlite\Makefile.msc sqlite3.exe TOP=..\sqlite - nmake /f ..\sqlite\Makefile.msc test TOP=..\sqlite + set TCLDIR=c:\Tcl -There are several build options that can be set via the NMAKE command -line. For example, to build for WinRT, simply add "FOR_WINRT=1" argument -to the "sqlite3.dll" command line above. When debugging into the SQLite -code, adding the "DEBUG=1" argument to one of the above command lines is -recommended. +SQLite uses "tclsh.exe" as part of the build process, and so that utility +program will need to be somewhere on your %PATH%. The finished SQLite library +does not contain any TCL code, but it does use TCL to help with the build process +and to run tests. -SQLite does not require [Tcl](http://www.tcl.tk/) to run, but a Tcl installation -is required by the makefiles (including those for MSVC). SQLite contains -a lot of generated code and Tcl is used to do much of that code generation. +Build using Makefile.msc. Example: + + nmake /f Makefile.msc + nmake /f Makefile.msc sqlite3.c + nmake /f Makefile.msc devtest + nmake /f Makefile.msc releasetest + +There are many other makefile targets. See comments in Makefile.msc for +details. ## Source Code Tour diff --git a/autoconf/Makefile.am b/autoconf/Makefile.am index 694419b27d..1eaa560ff8 100644 --- a/autoconf/Makefile.am +++ b/autoconf/Makefile.am @@ -9,7 +9,7 @@ sqlite3_SOURCES = shell.c sqlite3.h EXTRA_sqlite3_SOURCES = sqlite3.c sqlite3_LDADD = @EXTRA_SHELL_OBJ@ @READLINE_LIBS@ sqlite3_DEPENDENCIES = @EXTRA_SHELL_OBJ@ -sqlite3_CFLAGS = $(AM_CFLAGS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ENABLE_STMTVTAB -DSQLITE_ENABLE_DBSTAT_VTAB $(SHELL_CFLAGS) +sqlite3_CFLAGS = $(AM_CFLAGS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_DQS=0 -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ENABLE_STMTVTAB -DSQLITE_ENABLE_DBSTAT_VTAB $(SHELL_CFLAGS) include_HEADERS = sqlite3.h sqlite3ext.h diff --git a/autoconf/Makefile.msc b/autoconf/Makefile.msc index 24154dd5d8..261ac61bd5 100644 --- a/autoconf/Makefile.msc +++ b/autoconf/Makefile.msc @@ -56,7 +56,7 @@ USE_STDCALL = 0 # in the core library. # !IFNDEF USE_SEH -USE_SEH = 0 +USE_SEH = 1 !ENDIF # Set this non-0 to have the shell executable link against the core dynamic diff --git a/doc/compile-for-windows.md b/doc/compile-for-windows.md new file mode 100644 index 0000000000..84b0abe49e --- /dev/null +++ b/doc/compile-for-windows.md @@ -0,0 +1,85 @@ +# Notes On Compiling SQLite On Windows 11 + +Here are step-by-step instructions on how to build SQLite from +canonical source on a new Windows 11 PC, as of 2023-08-16: + + 1. Install Microsoft Visual Studio. The free "community edition" + will work fine. Do a standard install for C++ development. + SQLite only needs the + "cl" compiler and the "nmake" build tool. + + 2. Under the "Start" menu, find "All Apps" then go to "Visual Studio 20XX" + and find "x64 Native Tools Command Prompt for VS 20XX". Pin that + application to your task bar, as you will use it a lot. Bring up + an instance of this command prompt and do all of the subsequent steps + in that "x64 Native Tools" command prompt. (Or use "x86" if you want + a 32-bit build.) The subsequent steps will not work in a vanilla + DOS prompt. Nor will they work in PowerShell. + + 3. Install TCL development libraries. This note assumes that you wil + install the TCL development libraries in the "`c:\Tcl`" directory. + Make adjustments + if you want TCL installed somewhere else. SQLite needs both the + "tclsh.exe" command-line tool as part of the build process, and + the "tcl86.lib" library in order to run tests. You will need + TCL version 8.6 or later. +
    +
  1. Get the TCL source archive, perhaps from + . +
  2. Untar or unzip the source archive. CD into the "win/" subfolder + of the source tree. +
  3. Run: `nmake /f makefile.vc release` +
  4. Run: `nmake /f makefile.vc INSTALLDIR=c:\Tcl install` +
  5. CD to c:\\Tcl\\lib. In that subfolder make a copy of the + "`tcl86t.lib`" file to the alternative name "`tcl86.lib`" + (omitting the second 't'). Leave the copy in the same directory + as the original. +
  6. CD to c:\\Tcl\\bin. Make a copy of the "`tclsh86t.exe`" + file into "`tclsh.exe`" (without the "86t") in the same directory. +
  7. Add c:\\Tcl\\bin to your %PATH%. To do this, go to Settings + and search for "path". Select "edit environment variables for + your account" and modify your default PATH accordingly. + You will need to close and reopen your command prompts after + making this change. +
+ + 4. Download the SQLite source tree and unpack it. CD into the + toplevel directory of the source tree. + + 5. Set the TCLDIR environment variable to point to your TCL installation. + Like this: + + + 6. Run the "`Makefile.msc`" makefile with an appropriate target. + Examples: + + +## 32-bit Builds + +Doing a 32-bit build is just like doing a 64-bit build with the +following minor changes: + + 1. Use the "x86 Native Tools Command Prompt" instead of + "x64 Native Tools Command Prompt". "**x86**" instead of "**x64**". + + 2. Use a different installation directory for TCL. + The recommended directory is `c:\tcl32`. Thus you end up + with two TCL builds: + + + 3. Ensure that c:\\tcl32\\bin comes before c:\\tcl\\bin on + your PATH environment variable. You can achieve this using + a command like: + diff --git a/ext/fts5/fts5.h b/ext/fts5/fts5.h index 2309460f02..323d73a28f 100644 --- a/ext/fts5/fts5.h +++ b/ext/fts5/fts5.h @@ -492,8 +492,8 @@ struct Fts5ExtensionApi { ** as separate queries of the FTS index are required for each synonym. ** ** When using methods (2) or (3), it is important that the tokenizer only -** provide synonyms when tokenizing document text (method (2)) or query -** text (method (3)), not both. Doing so will not cause any errors, but is +** provide synonyms when tokenizing document text (method (3)) or query +** text (method (2)), not both. Doing so will not cause any errors, but is ** inefficient. */ typedef struct Fts5Tokenizer Fts5Tokenizer; diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c index 0e07b9246c..f5101ba065 100644 --- a/ext/fts5/fts5_expr.c +++ b/ext/fts5/fts5_expr.c @@ -2583,6 +2583,8 @@ static char *fts5ExprPrintTcl( if( zRet==0 ) return 0; } + }else if( pExpr->eType==0 ){ + zRet = sqlite3_mprintf("{}"); }else{ char const *zOp = 0; int i; diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index 9d80e79e56..2a5bf056e2 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -5421,7 +5421,8 @@ static void fts5FlushOneHash(Fts5Index *p){ writer.bFirstRowidInPage = 0; fts5WriteDlidxAppend(p, &writer, iRowid); }else{ - pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid-iPrev); + u64 iDelta = (u64)iRowid - (u64)iPrev; + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iDelta); } if( p->rc!=SQLITE_OK ) break; assert( pBuf->n<=pBuf->nSpace ); @@ -7815,7 +7816,7 @@ static void fts5DecodeFunction( fts5DecodeRowidList(&rc, &s, &a[4], iTermOff-4); iOff = iTermOff; - while( iOffszLeaf ){ + rc = FTS5_CORRUPT; + }else{ + fts5DecodeRowidList(&rc, &s, &a[iOff], iTermOff-iOff); + } iOff = iTermOff; if( iOff/dev/null)) +# SQLITE_C_IS_SEE := 0 +#else +# SQLITE_C_IS_SEE := 1 +# $(info This is an SEE build.) +#endif + +.NOTPARALLEL: $(sqlite3.h) +$(sqlite3.h): + $(MAKE) -C $(dir.top) sqlite3.c +$(sqlite3.c): $(sqlite3.h) + +SQLITE_OPT = \ + -DSQLITE_ENABLE_RTREE \ + -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ + -DSQLITE_ENABLE_STMTVTAB \ + -DSQLITE_ENABLE_DBPAGE_VTAB \ + -DSQLITE_ENABLE_DBSTAT_VTAB \ + -DSQLITE_ENABLE_BYTECODE_VTAB \ + -DSQLITE_ENABLE_OFFSET_SQL_FUNC \ + -DSQLITE_OMIT_LOAD_EXTENSION \ + -DSQLITE_OMIT_DEPRECATED \ + -DSQLITE_OMIT_SHARED_CACHE \ + -DSQLITE_THREADSAFE=0 \ + -DSQLITE_TEMP_STORE=2 \ + -DSQLITE_USE_URI=1 \ + -DSQLITE_C=$(sqlite3.c) +# -DSQLITE_DEBUG +# -DSQLITE_DEBUG is just to work around a -Wall warning +# for a var which gets set in all builds but only read +# via assert(). + +SQLITE_OPT += -g -DDEBUG -UNDEBUG + +ifeq (1,$(enable.fts5)) + SQLITE_OPT += -DSQLITE_ENABLE_FTS5 +endif + +sqlite3-jni.c := $(dir.src.c)/sqlite3-jni.c +sqlite3-jni.o := $(dir.bld.c)/sqlite3-jni.o +sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h +sqlite3-jni.dll := $(dir.bld.c)/libsqlite3-jni.so +# All javac-generated .h files must be listed in $(sqlite3-jni.h.in): +sqlite3-jni.h.in := +define ADD_JNI_H +sqlite3-jni.h.in += $$(dir.bld.c)/org_sqlite_jni_$(1).h +$$(dir.bld.c)/org_sqlite_jni_$(1).h: $$(dir.src.jni)/$(1).java +endef +$(eval $(call ADD_JNI_H,SQLite3Jni)) +ifeq (1,$(enable.fts5)) + $(eval $(call ADD_JNI_H,Fts5ExtensionApi)) + $(eval $(call ADD_JNI_H,fts5_api)) + $(eval $(call ADD_JNI_H,fts5_tokenizer)) +endif +ifeq (1,$(enable.tester)) + sqlite3-jni.h.in += $(dir.bld.c)/org_sqlite_jni_tester_SQLTester.h + $(dir.bld.c)/org_sqlite_jni_tester_SQLTester.h: $(dir.src.jni.tester)/SQLTester.java +endif +#sqlite3-jni.dll.cfiles := $(dir.src.c) +sqlite3-jni.dll.cflags = \ + -fPIC \ + -I. \ + -I$(dir $(sqlite3.h)) \ + -I$(dir.src.c) \ + -I$(JDK_HOME)/include \ + $(patsubst %,-I%,$(patsubst %.h,,$(wildcard $(JDK_HOME)/include/*))) \ + -Wall +# Using (-Wall -Wextra) triggers an untennable number of +# gcc warnings from sqlite3.c for mundane things like +# unused parameters. +# +# The gross $(patsubst...) above is to include the platform-specific +# subdir which lives under $(JDK_HOME)/include and is a required +# include path for client-level code. +######################################################################## +ifeq (1,$(enable.tester)) + sqlite3-jni.dll.cflags += -DS3JNI_ENABLE_SQLTester +endif +$(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE) + cat $(sqlite3-jni.h.in) > $@ +$(sqlite3-jni.dll): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h) +$(sqlite3-jni.dll): $(dir.bld.c) $(sqlite3-jni.c) $(SQLite3Jni.java) $(MAKEFILE) + $(CC) $(sqlite3-jni.dll.cflags) $(SQLITE_OPT) \ + $(sqlite3-jni.c) -shared -o $@ +all: $(sqlite3-jni.dll) + +.PHONY: test +test.flags ?= -v +test: $(SQLite3Jni.class) $(sqlite3-jni.dll) + $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ + $(java.flags) -cp $(classpath) \ + org.sqlite.jni.Tester1 $(if $(test.flags),-- $(test.flags),) + +tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test)) +tester.flags ?= # --verbose +.PHONY: tester tester-local tester-ext +ifeq (1,$(enable.tester)) +tester-local: $(CLASS_FILES.tester) $(sqlite3-jni.dll) + $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ + $(java.flags) -cp $(classpath) \ + org.sqlite.jni.tester.SQLTester $(tester.flags) $(tester.scripts) +tester: tester-local +else +tester: + @echo "SQLTester support is disabled. Build with enable.tester=1 to enable it." +endif + +tester.extdir.default := src/tests/ext +tester.extdir ?= $(tester.extdir.default) +tester.extern-scripts := $(wildcard $(tester.extdir)/*.test) +ifneq (,$(tester.extern-scripts)) +tester-ext: + $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ + $(java.flags) -cp $(classpath) \ + org.sqlite.jni.tester.SQLTester $(tester.flags) $(tester.extern-scripts) +else +tester-ext: + @echo "******************************************************"; \ + echo "*** Include the out-of-tree test suite in the 'tester'"; \ + echo "*** target by either symlinking its directory to"; \ + echo "*** $(tester.extdir.default) or passing it to make"; \ + echo "*** as tester.extdir=/path/to/that/dir."; \ + echo "******************************************************"; +endif + +tester-ext: tester-local +tester: tester-ext +tests: test tester +package.jar.in := $(abspath $(dir.src)/jar.in) +CLEAN_FILES += $(package.jar.in) +$(package.jar.in): $(MAKEFILE) $(CLASS_FILES.main) + cd $(dir.src); ls -1 org/sqlite/jni/*.java org/sqlite/jni/*.class > $@ + @ls -la $@ + @echo "To use this jar you will need the -Djava.library.path=DIR/WITH/libsqlite3-jni.so flag." + @echo "e.g. java -jar $@ -Djava.library.path=bld" + +$(package.jar): $(CLASS_FILES) $(MAKEFILE) $(package.jar.in) + rm -f $(dir.src)/c/*~ $(dir.src.jni)/*~ + cd $(dir.src); $(bin.jar) -cfe ../$@ org.sqlite.jni.Tester1 @$(package.jar.in) + +jar: $(package.jar) + +CLEAN_FILES += $(dir.bld.c)/* \ + $(dir.src.jni)/*.class \ + $(dir.src.jni.tester)/*.class \ + $(sqlite3-jni.dll) \ + hs_err_pid*.log + +.PHONY: clean distclean +clean: + -rm -f $(CLEAN_FILES) +distclean: clean + -rm -f $(DISTCLEAN_FILES) + -rm -fr $(dir.bld.c) + +######################################################################## +# disttribution bundle rules... + +ifeq (,$(filter snapshot,$(MAKECMDGOALS))) +dist-name-prefix := sqlite-jni +else +dist-name-prefix := sqlite-jni-snapshot-$(shell /usr/bin/date +%Y%m%d) +endif +dist-name := $(dist-name-prefix)-TEMP + + +dist-dir.top := $(dist-name) +dist-dir.src := $(dist-dir.top)/src +dist.top.extras := \ + README.md + +.PHONY: dist snapshot + +dist: \ + $(bin.version-info) $(sqlite3.canonical.c) \ + $(package.jar) $(MAKEFILE) + @echo "Making end-user deliverables..." + @rm -fr $(dist-dir.top) + @mkdir -p $(dist-dir.src) + @cp -p $(dist.top.extras) $(dist-dir.top)/. + @cp -p jar-dist.make $(dist-dir.top)/Makefile + @cp -p $(dir.src.c)/*.[ch] $(dist-dir.src)/. + @cp -p $(sqlite3.canonical.c) $(sqlite3.canonical.h) $(dist-dir.src)/. + @set -e; \ + vnum=$$($(bin.version-info) --download-version); \ + vjar=$$($(bin.version-info) --version); \ + vdir=$(dist-name-prefix)-$$vnum; \ + arczip=$$vdir.zip; \ + cp -p $(package.jar) $(dist-dir.top)/sqlite3-jni-$${vjar}.jar; \ + echo "Making $$arczip ..."; \ + rm -fr $$arczip $$vdir; \ + mv $(dist-dir.top) $$vdir; \ + zip -qr $$arczip $$vdir; \ + rm -fr $$vdir; \ + ls -la $$arczip; \ + set +e; \ + unzip -lv $$arczip || echo "Missing unzip app? Not fatal." + +snapshot: dist + +.PHONY: dist-clean +clean: dist-clean +dist-clean: + rm -fr $(dist-name) $(wildcard sqlite-jni-*.zip) diff --git a/ext/jni/README.md b/ext/jni/README.md new file mode 100644 index 0000000000..cb51a21cd3 --- /dev/null +++ b/ext/jni/README.md @@ -0,0 +1,234 @@ +SQLite3 via JNI +======================================================================== + +This directory houses a Java Native Interface (JNI) binding for the +sqlite3 API. If you are reading this from the distribution ZIP file, +links to resources in the canonical source tree will note work. The +canonical copy of this file can be browsed at: + + + +Technical support is available in the forum: + + + + +> **FOREWARNING:** this subproject is very much in development and + subject to any number of changes. Please do not rely on any + information about its API until this disclaimer is removed. + +Project goals/requirements: + +- A [1-to-1(-ish) mapping of the C API](#1to1ish) to Java via JNI, + insofar as cross-language semantics allow for. A closely-related + goal is that [the C documentation](https://sqlite.org/c3ref/intro.html) + should be usable as-is, insofar as possible, for the JNI binding. + +- Support Java as far back as version 8 (2014). + +- Environment-independent. Should work everywhere both Java + and SQLite3 do. + +- No 3rd-party dependencies beyond the JDK. That includes no + build-level dependencies for specific IDEs and toolchains. We + welcome the addition of build files for arbitrary environments + insofar as they neither interfere with each other nor become + a maintenance burden for the sqlite developers. + +Non-goals: + +- Creation of high-level OO wrapper APIs. Clients are free to create + them off of the C-style API. + + +Significant TODOs +======================================================================== + +- The initial beta release with version 3.43 has severe threading + limitations. Namely, two threads cannot call into the JNI-bound API + at once. This limitation will be remove in a subsequent release. + + +Building +======================================================================== + +The canonical builds assumes a Linux-like environment and requires: + +- GNU Make +- A JDK supporting Java 8 or higher +- A modern C compiler. gcc and clang should both work. + +Put simply: + +``` +$ export JAVA_HOME=/path/to/jdk/root +$ make +$ make test +$ make clean +``` + + +One-to-One(-ish) Mapping to C +======================================================================== + +This JNI binding aims to provide as close to a 1-to-1 experience with +the C API as cross-language semantics allow. Exceptions are +necessarily made where cross-language semantics do not allow a 1-to-1, +and judiciously made where a 1-to-1 mapping would be unduly cumbersome +to use in Java. + +Golden Rule: _Never_ Throw from Callbacks +------------------------------------------------------------------------ + +JNI bindings which accept client-defined functions _must never throw +exceptions_ unless _very explicitly documented_ as being +throw-safe. Exceptions are generally reserved for higher-level +bindings which are constructed to specifically deal with them and +ensure that they do not leak C-level resources. Some of the JNI +bindings are provided as Java functions which expect this rule to +always hold. + +UTF-8(-ish) +------------------------------------------------------------------------ + +SQLite internally uses UTF-8 encoding, whereas Java natively uses +UTF-16. Java JNI has routines for converting to and from UTF-8, _but_ +Java uses what its docs call "[modified UTF-8][modutf8]." Care must be +taken when converting Java strings to UTF-8 to ensure that the proper +conversion is performed. In short, +`String.getBytes(StandardCharsets.UTF_8)` performs the proper +conversion in Java, and there is no JNI C API for that conversion +(JNI's `NewStringUTF()` returns MUTF-8). + +Known consequences and limitations of this discrepancy include: + +- Names of databases, tables, and collations must not contain + characters which differ in MUTF-8 and UTF-8, or certain APIs will + mis-translate them on their way between languages. APIs which + transfer other client-side data to Java take extra care to + convert the data at the cost of performance. + +[modutf8]: https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8 + + +Unwieldy Constructs are Re-mapped +------------------------------------------------------------------------ + +Some constructs, when modelled 1-to-1 from C to Java, are unduly +clumsy to work with in Java because they try to shoehorn C's way of +doing certain things into Java's wildly different ways. The following +subsections cover those, starting with a verbose explanation and +demonstration of where such changes are "really necessary"... + +### Custom Collations + +A prime example of where interface changes for Java are necessary for +usability is [registration of a custom +collation](https://sqlite.org/c3ref/create_collation.html): + +``` +// C: +int sqlite3_create_collation(sqlite3 * db, const char * name, int eTextRep, + void *pUserData, + int (*xCompare)(void*,int,void const *,int,void const *)); + +int sqlite3_create_collation_v2(sqlite3 * db, const char * name, int eTextRep, + void *pUserData, + int (*xCompare)(void*,int,void const *,int,void const *), + void (*xDestroy)(void*)); +``` + +The `pUserData` object is optional client-defined state for the +`xCompare()` and/or `xDestroy()` callback functions, both of which are +passed that object as their first argument. That data is passed around +"externally" in C because that's how C models the world. If we were to +bind that part as-is to Java, the result would be awkward to use (^Yes, +we tried this.): + +``` +// Java: +int sqlite3_create_collation(sqlite3 db, String name, int eTextRep, + Object pUserData, xCompareType xCompare); + +int sqlite3_create_collation_v2(sqlite3 db, String name, int eTextRep, + Object pUserData, + xCompareType xCompare, xDestroyType xDestroy); +``` + +The awkwardness comes from (A) having two distinctly different objects +for callbacks and (B) having their internal state provided separately, +which is ill-fitting in Java. For the sake of usability, C APIs which +follow that pattern use a slightly different Java interface: + +``` +int sqlite3_create_collation(sqlite3 db, String name, int eTextRep, + Collation collation); +``` + +Where the `Collation` class has an abstract `xCompare()` method and +no-op `xDestroy()` method which can be overridden if needed, leading to +a much more Java-esque usage: + +``` +int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new Collation(){ + + // Required comparison function: + @Override public int xCompare(byte[] lhs, byte[] rhs){ ... } + + // Optional finalizer function: + @Override public void xDestroy(){ ... } + + // Optional local state: + private String localState1 = + "This is local state. There are many like it, but this one is mine."; + private MyStateType localState2 = new MyStateType(); + ... +}); +``` + +Noting that: + +- It is still possible to bind in call-scope-local state via closures, + if desired. + +- No capabilities of the C API are lost or unduly obscured via the + above API reshaping, so power users need not make any compromises. + +- In the specific example above, `sqlite3_create_collation_v2()` + becomes superfluous because the provided interface effectively + provides both the v1 and v2 interfaces, the difference being that + overriding the `xDestroy()` method effectively gives it v2 + semantics. + +### User-defined SQL Functions (a.k.a. UDFs) + +The [`sqlite3_create_function()`](https://sqlite.org/c3ref/create_function.html) +family of APIs make heavy use of function pointers to provide +client-defined callbacks, necessitating interface changes in the JNI +binding. The Java API has only one core function-registration function: + +``` +int sqlite3_create_function(sqlite3 db, String funcName, int nArgs, + int encoding, SQLFunction func); +``` + +> Design question: does the encoding argument serve any purpose in JS? + +`SQLFunction` is not used directly, but is instead instantiated via +one of its three subclasses: + +- `SQLFunction.Scalar` implements simple scalar functions using but a + single callback. +- `SQLFunction.Aggregate` implements aggregate functions using two + callbacks. +- `SQLFunction.Window` implements window functions using four + callbacks. + +Search [`Tester1.java`](/file/ext/jni/src/org/sqlite/jni/Tester1.java) for +`SQLFunction` for how it's used. + +Reminder: see the disclaimer at the top of this document regarding the +in-flux nature of this API. + +[jsrc]: /file/ +[www]: https://sqlite.org diff --git a/ext/jni/jar-dist.make b/ext/jni/jar-dist.make new file mode 100644 index 0000000000..23a26e4a87 --- /dev/null +++ b/ext/jni/jar-dist.make @@ -0,0 +1,55 @@ +#!/this/is/make +#^^^^ help emacs out +# +# This is a POSIX-make-compatible makefile for building the sqlite3 +# JNI library from "dist" zip file. It must be edited to set the +# proper top-level JDK directory and, depending on the platform, add a +# platform-specific -I directory. It should build as-is with any +# 2020s-era version of gcc or clang. It requires JDK version 8 or +# higher. + +default: all + +JAVA_HOME = /usr/lib/jvm/java-1.8.0-openjdk-amd64 +CFLAGS = \ + -fPIC \ + -Isrc \ + -I$(JAVA_HOME)/include \ + -I$(JAVA_HOME)/include/linux \ + -I$(JAVA_HOME)/include/apple \ + -I$(JAVA_HOME)/include/bsd \ + -Wall + +SQLITE_OPT = \ + -DSQLITE_ENABLE_RTREE \ + -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ + -DSQLITE_ENABLE_STMTVTAB \ + -DSQLITE_ENABLE_DBPAGE_VTAB \ + -DSQLITE_ENABLE_DBSTAT_VTAB \ + -DSQLITE_ENABLE_BYTECODE_VTAB \ + -DSQLITE_ENABLE_OFFSET_SQL_FUNC \ + -DSQLITE_OMIT_LOAD_EXTENSION \ + -DSQLITE_OMIT_DEPRECATED \ + -DSQLITE_OMIT_SHARED_CACHE \ + -DSQLITE_THREADSAFE=0 \ + -DSQLITE_TEMP_STORE=2 \ + -DSQLITE_USE_URI=1 \ + -DSQLITE_ENABLE_FTS5 + +sqlite3-jni.dll = libsqlite3-jni.so +$(sqlite3-jni.dll): + @echo "************************************************************************"; \ + echo "*** If this fails to build, be sure to edit this makefile ***"; \ + echo "*** to configure it for your system. ***"; \ + echo "************************************************************************" + $(CC) $(CFLAGS) $(SQLITE_OPT) \ + src/sqlite3-jni.c -shared -o $@ + @echo "Now try running it with: make test" + +test: $(sqlite3-jni.dll) + java -jar -Djava.library.path=. sqlite3-jni-*.jar + +clean: + -rm -f $(sqlite3-jni.dll) + +all: $(sqlite3-jni.dll) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c new file mode 100644 index 0000000000..b28ea71144 --- /dev/null +++ b/ext/jni/src/c/sqlite3-jni.c @@ -0,0 +1,4420 @@ +/* +** 2023-07-21 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file implements the JNI bindings declared in +** org.sqlite.jni.SQLiteJni (from which sqlite3-jni.h is generated). +*/ + +/** + If you found this comment by searching the code for + CallStaticObjectMethod then you're the victim of an OpenJDK bug: + + https://bugs.openjdk.org/browse/JDK-8130659 + + It's known to happen with OpenJDK v8 but not with v19. + + This code does not use JNI's CallStaticObjectMethod(). +*/ + +/* +** Define any SQLITE_... config defaults we want if they aren't +** overridden by the builder. Please keep these alphabetized. +*/ + +/**********************************************************************/ +/* SQLITE_D... */ +#ifndef SQLITE_DEFAULT_CACHE_SIZE +# define SQLITE_DEFAULT_CACHE_SIZE -16384 +#endif +#if !defined(SQLITE_DEFAULT_PAGE_SIZE) +# define SQLITE_DEFAULT_PAGE_SIZE 8192 +#endif +#ifndef SQLITE_DQS +# define SQLITE_DQS 0 +#endif + +/**********************************************************************/ +/* SQLITE_ENABLE_... */ +#ifndef SQLITE_ENABLE_BYTECODE_VTAB +# define SQLITE_ENABLE_BYTECODE_VTAB 1 +#endif +#ifndef SQLITE_ENABLE_DBPAGE_VTAB +# define SQLITE_ENABLE_DBPAGE_VTAB 1 +#endif +#ifndef SQLITE_ENABLE_DBSTAT_VTAB +# define SQLITE_ENABLE_DBSTAT_VTAB 1 +#endif +#ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS +# define SQLITE_ENABLE_EXPLAIN_COMMENTS 1 +#endif +#ifndef SQLITE_ENABLE_MATH_FUNCTIONS +# define SQLITE_ENABLE_MATH_FUNCTIONS 1 +#endif +#ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC +# define SQLITE_ENABLE_OFFSET_SQL_FUNC 1 +#endif +#ifndef SQLITE_ENABLE_PREUPDATE_HOOK +# define SQLITE_ENABLE_PREUPDATE_HOOK 1 /*required by session extension*/ +#endif +#ifndef SQLITE_ENABLE_RTREE +# define SQLITE_ENABLE_RTREE 1 +#endif +//#ifndef SQLITE_ENABLE_SESSION +//# define SQLITE_ENABLE_SESSION 1 +//#endif +#ifndef SQLITE_ENABLE_STMTVTAB +# define SQLITE_ENABLE_STMTVTAB 1 +#endif +//#ifndef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION +//# define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION +//#endif + +/**********************************************************************/ +/* SQLITE_M... */ +#ifndef SQLITE_MAX_ALLOCATION_SIZE +# define SQLITE_MAX_ALLOCATION_SIZE 0x1fffffff +#endif + +/**********************************************************************/ +/* SQLITE_O... */ +#ifndef SQLITE_OMIT_DEPRECATED +# define SQLITE_OMIT_DEPRECATED 1 +#endif +#ifndef SQLITE_OMIT_LOAD_EXTENSION +# define SQLITE_OMIT_LOAD_EXTENSION 1 +#endif +#ifndef SQLITE_OMIT_SHARED_CACHE +# define SQLITE_OMIT_SHARED_CACHE 1 +#endif +#ifdef SQLITE_OMIT_UTF16 +/* UTF16 is required for java */ +# undef SQLITE_OMIT_UTF16 1 +#endif + +/**********************************************************************/ +/* SQLITE_T... */ +#ifndef SQLITE_TEMP_STORE +# define SQLITE_TEMP_STORE 2 +#endif +#ifndef SQLITE_THREADSAFE +# define SQLITE_THREADSAFE 0 +#endif + +/**********************************************************************/ +/* SQLITE_USE_... */ +#ifndef SQLITE_USE_URI +# define SQLITE_USE_URI 1 +#endif + + +/* +** Which sqlite3.c we're using needs to be configurable to enable +** building against a custom copy, e.g. the SEE variant. We have to +** include sqlite3.c, as opposed to sqlite3.h, in order to get access +** to SQLITE_MAX_... and friends. This increases the rebuild time +** considerably but we need this in order to keep the exported values +** of SQLITE_MAX_... and SQLITE_LIMIT_... in sync with the C build. +*/ +#ifndef SQLITE_C +# define SQLITE_C sqlite3.c +#endif +#define INC__STRINGIFY_(f) #f +#define INC__STRINGIFY(f) INC__STRINGIFY_(f) +#include INC__STRINGIFY(SQLITE_C) +#undef INC__STRINGIFY_ +#undef INC__STRINGIFY +#undef SQLITE_C + +#include "sqlite3-jni.h" +#include /* only for testing/debugging */ +#include + +/* Only for debugging */ +#define MARKER(pfexp) \ + do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ + printf pfexp; \ + } while(0) + +/* Creates a verbose JNI function name. */ +#define JFuncName(Suffix) \ + Java_org_sqlite_jni_SQLite3Jni_sqlite3_ ## Suffix + +/* Prologue for JNI functions. */ +#define JDECL(ReturnType,Suffix) \ + JNIEXPORT ReturnType JNICALL \ + JFuncName(Suffix) +/** + Shortcuts for the first 2 parameters to all JNI bindings. + + The type of the jSelf arg differs, but no docs seem to mention + this: for static methods it's of type jclass and for non-static + it's jobject. jobject actually works for all funcs, in the sense + that it compiles and runs so long as we don't use jSelf (which is + only rarely needed in this code), but to be pedantically correct we + need the proper type in the signature. + + Not even the official docs mention this discrepancy: + + https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers +*/ +#define JENV_OSELF JNIEnv * const env, jobject jSelf +#define JENV_CSELF JNIEnv * const env, jclass jKlazz +/* Helpers to account for -Xcheck:jni warnings about not having + checked for exceptions. */ +#define IFTHREW if((*env)->ExceptionCheck(env)) +#define EXCEPTION_IGNORE (void)((*env)->ExceptionCheck(env)) +#define EXCEPTION_CLEAR (*env)->ExceptionClear(env) +#define EXCEPTION_REPORT (*env)->ExceptionDescribe(env) +#define EXCEPTION_WARN_CALLBACK_THREW(STR) \ + MARKER(("WARNING: " STR " MUST NOT THROW.\n")); \ + (*env)->ExceptionDescribe(env) +#define IFTHREW_REPORT IFTHREW EXCEPTION_REPORT +#define IFTHREW_CLEAR IFTHREW EXCEPTION_CLEAR + +/** To be used for cases where we're _really_ not expecting an + exception, e.g. looking up well-defined Java class members. */ +#define EXCEPTION_IS_FATAL(MSG) IFTHREW {\ + EXCEPTION_REPORT; EXCEPTION_CLEAR; \ + (*env)->FatalError(env, MSG); \ + } + +/** Helpers for extracting pointers from jobjects, noting that the + corresponding Java interfaces have already done the type-checking. + */ +#define PtrGet_sqlite3(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3) +#define PtrGet_sqlite3_stmt(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_stmt) +#define PtrGet_sqlite3_value(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_value) +#define PtrGet_sqlite3_context(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_context) +/* Helpers for Java value reference management. */ +static inline jobject new_global_ref(JNIEnv * const env, jobject const v){ + return v ? (*env)->NewGlobalRef(env, v) : NULL; +} +static inline void delete_global_ref(JNIEnv * const env, jobject const v){ + if(v) (*env)->DeleteGlobalRef(env, v); +} +static inline void delete_local_ref(JNIEnv * const env, jobject const v){ + if(v) (*env)->DeleteLocalRef(env, v); +} +#define REF_G(VAR) new_global_ref(env, (VAR)) +#define REF_L(VAR) (*env)->NewLocalRef(env, VAR) +#define UNREF_G(VAR) delete_global_ref(env,(VAR)) +#define UNREF_L(VAR) delete_local_ref(env,(VAR)) + +/** + Constant string class names used as keys for S3JniGlobal_nph_cache(), +S3Jni + and + friends. +*/ +static const struct { + const char * const sqlite3; + const char * const sqlite3_stmt; + const char * const sqlite3_context; + const char * const sqlite3_value; + const char * const OutputPointer_Int32; + const char * const OutputPointer_Int64; + const char * const OutputPointer_String; + const char * const OutputPointer_ByteArray; + const char * const OutputPointer_sqlite3; + const char * const OutputPointer_sqlite3_stmt; +#ifdef SQLITE_ENABLE_FTS5 + const char * const Fts5Context; + const char * const Fts5ExtensionApi; + const char * const fts5_api; + const char * const fts5_tokenizer; + const char * const Fts5Tokenizer; +#endif +} S3JniClassNames = { + "org/sqlite/jni/sqlite3", + "org/sqlite/jni/sqlite3_stmt", + "org/sqlite/jni/sqlite3_context", + "org/sqlite/jni/sqlite3_value", + "org/sqlite/jni/OutputPointer$Int32", + "org/sqlite/jni/OutputPointer$Int64", + "org/sqlite/jni/OutputPointer$String", + "org/sqlite/jni/OutputPointer$ByteArray", + "org/sqlite/jni/OutputPointer$sqlite3", + "org/sqlite/jni/OutputPointer$sqlite3_stmt", +#ifdef SQLITE_ENABLE_FTS5 + "org/sqlite/jni/Fts5Context", + "org/sqlite/jni/Fts5ExtensionApi", + "org/sqlite/jni/fts5_api", + "org/sqlite/jni/fts5_tokenizer", + "org/sqlite/jni/Fts5Tokenizer" +#endif +}; + +/** Create a trivial JNI wrapper for (int CName(void)). */ +#define WRAP_INT_VOID(JniNameSuffix,CName) \ + JDECL(jint,JniNameSuffix)(JENV_CSELF){ \ + return (jint)CName(); \ + } + +/** Create a trivial JNI wrapper for (int CName(int)). */ +#define WRAP_INT_INT(JniNameSuffix,CName) \ + JDECL(jint,JniNameSuffix)(JENV_CSELF, jint arg){ \ + return (jint)CName((int)arg); \ + } + +/** Create a trivial JNI wrapper for (const mutf8_string * + CName(void)). This is only valid for functions which are known to + return ASCII or text which is equivalent in UTF-8 and MUTF-8. */ +#define WRAP_MUTF8_VOID(JniNameSuffix,CName) \ + JDECL(jstring,JniNameSuffix)(JENV_CSELF){ \ + return (*env)->NewStringUTF( env, CName() ); \ + } +/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*)). */ +#define WRAP_INT_STMT(JniNameSuffix,CName) \ + JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject jpStmt){ \ + jint const rc = (jint)CName(PtrGet_sqlite3_stmt(jpStmt)); \ + EXCEPTION_IGNORE /* squelch -Xcheck:jni */; \ + return rc; \ + } +/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*,int)). */ +#define WRAP_INT_STMT_INT(JniNameSuffix,CName) \ + JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject pStmt, jint n){ \ + return (jint)CName(PtrGet_sqlite3_stmt(pStmt), (int)n); \ + } +/** Create a trivial JNI wrapper for (jstring CName(sqlite3_stmt*,int)). */ +#define WRAP_STR_STMT_INT(JniNameSuffix,CName) \ + JDECL(jstring,JniNameSuffix)(JENV_CSELF, jobject pStmt, jint ndx){ \ + return (*env)->NewStringUTF(env, CName(PtrGet_sqlite3_stmt(pStmt), (int)ndx)); \ + } +/** Create a trivial JNI wrapper for (int CName(sqlite3*)). */ +#define WRAP_INT_DB(JniNameSuffix,CName) \ + JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject pDb){ \ + return (jint)CName(PtrGet_sqlite3(pDb)); \ + } +/** Create a trivial JNI wrapper for (int64 CName(sqlite3*)). */ +#define WRAP_INT64_DB(JniNameSuffix,CName) \ + JDECL(jlong,JniNameSuffix)(JENV_CSELF, jobject pDb){ \ + return (jlong)CName(PtrGet_sqlite3(pDb)); \ + } +/** Create a trivial JNI wrapper for (int CName(sqlite3_value*)). */ +#define WRAP_INT_SVALUE(JniNameSuffix,CName) \ + JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject jpSValue){ \ + return (jint)CName(PtrGet_sqlite3_value(jpSValue)); \ + } + +/* Helpers for jstring and jbyteArray. */ +#define JSTR_TOC(ARG) (*env)->GetStringUTFChars(env, ARG, NULL) +#define JSTR_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseStringUTFChars(env, ARG, VAR) +#define JBA_TOC(ARG) (*env)->GetByteArrayElements(env,ARG, NULL) +#define JBA_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseByteArrayElements(env, ARG, VAR, JNI_ABORT) + +/* Marker for code which needs(?) to be made thread-safe. REASON is a + terse reminder about why that function requires a mutex. +*/ +#define FIXME_THREADING(REASON) + +enum { + /** + Size of the NativePointerHolder cache. Need enough space for + (only) the library's NativePointerHolder types, a fixed count + known at build-time. If we add more than this a fatal error will + be triggered with a reminder to increase this. This value needs + to be exactly the number of entries in the S3JniClassNames + object. The S3JniClassNames entries are the keys for this particular + cache. + */ + NphCache_SIZE = sizeof(S3JniClassNames) / sizeof(char const *) +}; + +/** + Cache entry for NativePointerHolder lookups. +*/ +typedef struct S3JniNphCache S3JniNphCache; +struct S3JniNphCache { + const char * zClassName /* "full/class/Name". Must be a static + string pointer from the S3JniClassNames + struct. */; + jclass klazz /* global ref to the concrete + NativePointerHolder subclass represented by + zClassName */; + jmethodID midCtor /* klazz's no-arg constructor. Used by + new_NativePointerHolder_object(). */; + jfieldID fidValue /* NativePointerHolder.nativePointer and + OutputPointer.X.value */; + jfieldID fidSetAgg /* sqlite3_context::aggregateContext. Used only + by the sqlite3_context binding. */; +}; + +/** + Cache for per-JNIEnv data. + + Potential TODO: move the jclass entries to global space because, + per https://developer.android.com/training/articles/perf-jni: + + > once you have a valid jclass global reference you can use it from + any attached thread. + + Whereas we cache new refs for each thread. +*/ +typedef struct S3JniEnvCache S3JniEnvCache; +struct S3JniEnvCache { + JNIEnv *env /* env in which this cache entry was created */; + //! The various refs to global classes might be cacheable a single + // time globally. Information online seems inconsistent on that + // point. + struct { + jclass cObj /* global ref to java.lang.Object */; + jclass cLong /* global ref to java.lang.Long */; + jclass cString /* global ref to java.lang.String */; + jobject oCharsetUtf8 /* global ref to StandardCharset.UTF_8 */; + jmethodID ctorLong1 /* the Long(long) constructor */; + jmethodID ctorStringBA /* the String(byte[],Charset) constructor */; + jmethodID stringGetBytes /* the String.getBytes(Charset) method */; + } g /* refs to global Java state */; +#ifdef SQLITE_ENABLE_FTS5 + jobject jFtsExt /* Global ref to Java singleton for the + Fts5ExtensionApi instance. */; + struct { + jclass klazz; + jfieldID fidA; + jfieldID fidB; + } jPhraseIter; +#endif + S3JniEnvCache * pPrev /* Previous entry in the linked list */; + S3JniEnvCache * pNext /* Next entry in the linked list */; + /** TODO?: S3JniNphCache *pNphHit; + + and always set it to the most recent cache search result. + + The intent would be to help fast-track cache lookups and would + speed up, e.g., the sqlite3_value-to-Java-array loop in a + multi-threaded app. + */ + S3JniNphCache nph[NphCache_SIZE]; +}; + +static void S3JniNphCache_clear(JNIEnv * const env, S3JniNphCache * const p){ + UNREF_G(p->klazz); + memset(p, 0, sizeof(S3JniNphCache)); +} + +#define S3JNI_ENABLE_AUTOEXT 1 +#if S3JNI_ENABLE_AUTOEXT +/* + Whether auto extensions are feasible here is currently unknown due + to... + + 1) JNIEnv/threading issues. A db instance is mapped to a specific + JNIEnv object but auto extensions may be added from any thread. In + such contexts, which JNIEnv do we use for the JNI APIs? + + 2) a chicken/egg problem involving the Java/C mapping of the db: + when auto extensions are run, the db has not yet been connected to + Java. If we do that during the auto-ext, sqlite3_open(_v2)() will not behave + properly because they have a different jobject and the API + guarantees the user that _that_ object is the one the API will bind + the native to. + + If we change the open(_v2()) interfaces to use OutputPointer.sqlite3 + instead of the client passing in an instance, we could work around + (2). +*/ +typedef struct S3JniAutoExtension S3JniAutoExtension; +typedef void (*S3JniAutoExtension_xEntryPoint)(sqlite3*); +struct S3JniAutoExtension { + jobject jObj; + jmethodID midFunc; + S3JniAutoExtension_xEntryPoint xEntryPoint; + S3JniAutoExtension *pNext /* next linked-list entry */; + S3JniAutoExtension *pPrev /* previous linked-list entry */; +}; +#endif + +/** State for various hook callbacks. */ +typedef struct S3JniHook S3JniHook; +struct S3JniHook{ + jobject jObj /* global ref to Java instance */; + jmethodID midCallback /* callback method. Signature depends on + jObj's type */; + jclass klazz /* global ref to jObj's class. Only needed + by hooks which have an xDestroy() method, + as lookup of that method is deferred + until the object requires cleanup. */; +}; + +/** + Per-(sqlite3*) state for various JNI bindings. This state is + allocated as needed, cleaned up in sqlite3_close(_v2)(), and + recycled when possible. It is freed during sqlite3_shutdown(). +*/ +typedef struct S3JniDb S3JniDb; +struct S3JniDb { + JNIEnv *env /* The associated JNIEnv handle */; + sqlite3 *pDb /* The associated db handle */; + jobject jDb /* A global ref of the object which was passed to + sqlite3_open(_v2)(). We need this in order to have + an object to pass to sqlite3_collation_needed()'s + callback, or else we have to dynamically create one + for that purpose, which would be fine except that + it would be a different instance (and maybe even a + different class) than the one the user may expect + to receive. */; + char * zMainDbName /* Holds any string allocated on behave of + SQLITE_DBCONFIG_MAINDBNAME. */; + S3JniHook busyHandler; + S3JniHook collation; + S3JniHook collationNeeded; + S3JniHook commitHook; + S3JniHook progress; + S3JniHook rollbackHook; + S3JniHook trace; + S3JniHook updateHook; + S3JniHook authHook; +#ifdef SQLITE_ENABLE_FTS5 + jobject jFtsApi /* global ref to s3jni_fts5_api_from_db() */; +#endif + S3JniDb * pNext /* Next entry in the available/free list */; + S3JniDb * pPrev /* Previous entry in the available/free list */; +}; + +/** + Global state, e.g. caches and metrics. +*/ +static struct { + /** + According to: https://developer.ibm.com/articles/j-jni/ + + > A thread can get a JNIEnv by calling GetEnv() using the JNI + invocation interface through a JavaVM object. The JavaVM object + itself can be obtained by calling the JNI GetJavaVM() method + using a JNIEnv object and can be cached and shared across + threads. Caching a copy of the JavaVM object enables any thread + with access to the cached object to get access to its own + JNIEnv when necessary. + */ + JavaVM * jvm; + struct { + S3JniEnvCache * aHead /* Linked list of in-use instances */; + S3JniEnvCache * aFree /* Linked list of free instances */; + } envCache; + struct { + S3JniDb * aUsed /* Linked list of in-use instances */; + S3JniDb * aFree /* Linked list of free instances */; + } perDb; + struct { + unsigned nphCacheHits; + unsigned nphCacheMisses; + unsigned envCacheHits; + unsigned envCacheMisses; + unsigned nDestroy /* xDestroy() calls across all types */; + struct { + /* Number of calls for each type of UDF callback. */ + unsigned nFunc; + unsigned nStep; + unsigned nFinal; + unsigned nValue; + unsigned nInverse; + } udf; + } metrics; +#if S3JNI_ENABLE_AUTOEXT + struct { + S3JniAutoExtension *pHead /* Head of the auto-extension list */; + S3JniDb * psOpening /* handle to the being-opened db. We + need this so that auto extensions + can have a consistent view of the + cross-language db connection and + behave property if they call further + db APIs. */; + int isRunning /* True while auto extensions are + running. This is used to prohibit + manipulation of the auto-extension + list while extensions are + running. */; + } autoExt; +#endif +} S3JniGlobal; + +#define OOM_CHECK(VAR) if(!(VAR)) s3jni_oom(env) +static void s3jni_oom(JNIEnv * const env){ + (*env)->FatalError(env, "Out of memory.") /* does not return */; +} + +/** + sqlite3_malloc() proxy which fails fatally on OOM. This should + only be used for routines which manage global state and have no + recovery strategy for OOM. For sqlite3 API which can reasonably + return SQLITE_NOMEM, sqlite3_malloc() should be used instead. +*/ +static void * s3jni_malloc(JNIEnv * const env, size_t n){ + void * const rv = sqlite3_malloc(n); + if(n && !rv) s3jni_oom(env); + return rv; +} + +/** + Fetches the S3JniGlobal.envCache row for the given env, allocing + a row if needed. When a row is allocated, its state is initialized + insofar as possible. Calls (*env)->FatalError() if allocation of + an entry fails. That's hypothetically possible but "shouldn't happen." +*/ +FIXME_THREADING(S3JniEnvCache) +static S3JniEnvCache * S3JniGlobal_env_cache(JNIEnv * const env){ + struct S3JniEnvCache * row = S3JniGlobal.envCache.aHead; + for( ; row; row = row->pNext ){ + if( row->env == env ){ + ++S3JniGlobal.metrics.envCacheHits; + return row; + } + } + ++S3JniGlobal.metrics.envCacheMisses; + row = S3JniGlobal.envCache.aFree; + if( row ){ + assert(!row->pPrev); + S3JniGlobal.envCache.aFree = row->pNext; + if( row->pNext ) row->pNext->pPrev = 0; + }else{ + row = sqlite3_malloc(sizeof(S3JniEnvCache)); + if( !row ){ + (*env)->FatalError(env, "Maintenance required: S3JniEnvCache is full.") + /* Does not return, but cc doesn't know that */; + return NULL; + } + } + memset(row, 0, sizeof(*row)); + row->pNext = S3JniGlobal.envCache.aHead; + if(row->pNext) row->pNext->pPrev = row; + S3JniGlobal.envCache.aHead = row; + row->env = env; + + /* Grab references to various global classes and objects... */ + row->g.cObj = REF_G((*env)->FindClass(env,"java/lang/Object")); + EXCEPTION_IS_FATAL("Error getting reference to Object class."); + + row->g.cLong = REF_G((*env)->FindClass(env,"java/lang/Long")); + EXCEPTION_IS_FATAL("Error getting reference to Long class."); + row->g.ctorLong1 = (*env)->GetMethodID(env, row->g.cLong, + "", "(J)V"); + EXCEPTION_IS_FATAL("Error getting reference to Long constructor."); + + row->g.cString = REF_G((*env)->FindClass(env,"java/lang/String")); + EXCEPTION_IS_FATAL("Error getting reference to String class."); + row->g.ctorStringBA = + (*env)->GetMethodID(env, row->g.cString, + "", "([BLjava/nio/charset/Charset;)V"); + EXCEPTION_IS_FATAL("Error getting reference to String(byte[],Charset) ctor."); + row->g.stringGetBytes = + (*env)->GetMethodID(env, row->g.cString, + "getBytes", "(Ljava/nio/charset/Charset;)[B"); + EXCEPTION_IS_FATAL("Error getting reference to String.getBytes(Charset)."); + + { /* StandardCharsets.UTF_8 */ + jfieldID fUtf8; + jclass const klazzSC = + (*env)->FindClass(env,"java/nio/charset/StandardCharsets"); + EXCEPTION_IS_FATAL("Error getting reference to StndardCharsets class."); + fUtf8 = (*env)->GetStaticFieldID(env, klazzSC, "UTF_8", + "Ljava/nio/charset/Charset;"); + EXCEPTION_IS_FATAL("Error getting StndardCharsets.UTF_8 field."); + row->g.oCharsetUtf8 = + REF_G((*env)->GetStaticObjectField(env, klazzSC, fUtf8)); + EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets.UTF_8."); + } + return row; +} + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own Java/JNI bindings. +** +** For purposes of certain hand-crafted JNI function bindings, we +** need a way of reporting errors which is consistent with the rest of +** the C API, as opposed to throwing JS exceptions. To that end, this +** internal-use-only function is a thin proxy around +** sqlite3ErrorWithMessage(). The intent is that it only be used from +** JNI bindings such as sqlite3_prepare_v2/v3(), and definitely not +** from client code. +** +** Returns err_code. +*/ +static int s3jni_db_error(sqlite3* const db, int err_code, const char * const zMsg){ + if( db!=0 ){ + if( 0==zMsg ){ + sqlite3Error(db, err_code); + }else{ + const int nMsg = sqlite3Strlen30(zMsg); + sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg); + } + } + return err_code; +} + +/** + Creates a new jByteArray of length nP, copies p's contents into it, and + returns that byte array. + */ +static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env, const unsigned char * const p, int nP){ + jbyteArray jba = (*env)->NewByteArray(env, (jint)nP); + if(jba){ + (*env)->SetByteArrayRegion(env, jba, 0, (jint)nP, (const jbyte*)p); + } + return jba; +} + +/** + Uses the java.lang.String(byte[],Charset) constructor to create a + new String from UTF-8 string z. n is the number of bytes to + copy. If n<0 then sqlite3Strlen30() is used to calculate it. + + Returns NULL if z is NULL or on OOM, else returns a new jstring + owned by the caller. + + Sidebar: this is a painfully inefficient way to convert from + standard UTF-8 to a Java string, but JNI offers only algorithms for + working with MUTF-8, not UTF-8. +*/ +static jstring s3jni_utf8_to_jstring(S3JniEnvCache * const jc, + const char * const z, int n){ + jstring rv = NULL; + JNIEnv * const env = jc->env; + if( 0==n || (n<0 && z && !z[0]) ){ + /* Fast-track the empty-string case via the MUTF-8 API. We could + hypothetically do this for any strings where n<4 and z is + NUL-terminated and none of z[0..3] are NUL bytes. */ + rv = (*env)->NewStringUTF(env, ""); + }else if( z ){ + jbyteArray jba; + if( n<0 ) n = sqlite3Strlen30(z); + jba = s3jni_new_jbyteArray(env, (unsigned const char *)z, (jsize)n); + if( jba ){ + rv = (*env)->NewObject(env, jc->g.cString, jc->g.ctorStringBA, + jba, jc->g.oCharsetUtf8); + UNREF_L(jba); + } + } + return rv; +} + +/** + Converts the given java.lang.String object into a NUL-terminated + UTF-8 C-string by calling jstr.getBytes(StandardCharset.UTF_8). + Returns NULL if jstr is NULL or on allocation error. If jstr is not + NULL and nLen is not NULL then nLen is set to the length of the + returned string, not including the terminating NUL. If jstr is not + NULL and it returns NULL, this indicates an allocation error. In + that case, if nLen is not NULL then it is either set to 0 (if + fetching of jstr's bytes fails to allocate) or set to what would + have been the length of the string had C-string allocation + succeeded. + + The returned memory is allocated from sqlite3_malloc() and + ownership is transferred to the caller. +*/ +static char * s3jni_jstring_to_utf8(S3JniEnvCache * const jc, + jstring jstr, int *nLen){ + JNIEnv * const env = jc->env; + jbyteArray jba; + jsize nBa; + char *rv; + + if(!jstr) return 0; + jba = (*env)->CallObjectMethod(env, jstr, jc->g.stringGetBytes, + jc->g.oCharsetUtf8); + if( (*env)->ExceptionCheck(env) || !jba + /* order of these checks is significant for -Xlint:jni */ ) { + EXCEPTION_REPORT; + if( nLen ) *nLen = 0; + return 0; + } + nBa = (*env)->GetArrayLength(env, jba); + if( nLen ) *nLen = (int)nBa; + rv = sqlite3_malloc( nBa + 1 ); + if( rv ){ + (*env)->GetByteArrayRegion(env, jba, 0, nBa, (jbyte*)rv); + rv[nBa] = 0; + } + UNREF_L(jba); + return rv; +} + +/** + Expects to be passed a pointer from sqlite3_column_text16() or + sqlite3_value_text16() and a byte-length value from + sqlite3_column_bytes16() or sqlite3_value_bytes16(). It creates a + Java String of exactly half that character length, returning NULL + if !p or (*env)->NewString() fails. +*/ +static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, int nP){ + return p + ? (*env)->NewString(env, (const jchar *)p, (jsize)(nP/2)) + : NULL; +} + +/** + Requires jx to be a Throwable. Calls its toString() method and + returns its value converted to a UTF-8 string. The caller owns the + returned string and must eventually sqlite3_free() it. Returns 0 + if there is a problem fetching the info or on OOM. + + Design note: we use toString() instead of getMessage() because the + former includes the exception type's name: + + Exception e = new RuntimeException("Hi"); + System.out.println(e.toString()); // java.lang.RuntimeException: Hi + System.out.println(e.getMessage()); // Hi + } +*/ +FIXME_THREADING(S3JniEnvCache) +static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){ + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + jmethodID mid; + jstring msg; + char * zMsg; + jclass const klazz = (*env)->GetObjectClass(env, jx); + mid = (*env)->GetMethodID(env, klazz, "toString", "()Ljava/lang/String;"); + IFTHREW{ + EXCEPTION_REPORT; + EXCEPTION_CLEAR; + return 0; + } + msg = (*env)->CallObjectMethod(env, jx, mid); + IFTHREW{ + EXCEPTION_REPORT; + EXCEPTION_CLEAR; + return 0; + } + zMsg = s3jni_jstring_to_utf8(jc, msg, 0); + UNREF_L(msg); + return zMsg; +} + +/** + Extracts the current JNI exception, sets ps->pDb's error message to + its message string, and clears the exception. If errCode is non-0, + it is used as-is, else SQLITE_ERROR is assumed. If there's a + problem extracting the exception's message, it's treated as + non-fatal and zDfltMsg is used in its place. + + This must only be called if a JNI exception is pending. + + Returns errCode unless it is 0, in which case SQLITE_ERROR is + returned. +*/ +static int s3jni_db_exception(JNIEnv * const env, S3JniDb * const ps, + int errCode, const char *zDfltMsg){ + jthrowable const ex = (*env)->ExceptionOccurred(env); + + if( 0==errCode ) errCode = SQLITE_ERROR; + if( ex ){ + char * zMsg; + EXCEPTION_CLEAR; + zMsg = s3jni_exception_error_msg(env, ex); + s3jni_db_error(ps->pDb, errCode, zMsg ? zMsg : zDfltMsg); + sqlite3_free(zMsg); + UNREF_L(ex); + } + return errCode; +} + +/** + Extracts the (void xDestroy()) method from the given jclass and + applies it to jobj. If jObj is NULL, this is a no-op. If klazz is + NULL then it's derived from jobj. The lack of an xDestroy() method + is silently ignored and any exceptions thrown by the method trigger + a warning to stdout or stderr and then the exception is suppressed. +*/ +static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ + if(jObj){ + jmethodID method; + if(!klazz){ + klazz = (*env)->GetObjectClass(env, jObj); + assert(klazz); + } + method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V"); + //MARKER(("jObj=%p, klazz=%p, method=%p\n", jObj, klazz, method)); + if(method){ + ++S3JniGlobal.metrics.nDestroy; + (*env)->CallVoidMethod(env, jObj, method); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW("xDestroy() callback"); + EXCEPTION_CLEAR; + } + }else{ + EXCEPTION_CLEAR; + } + } +} + +/** + Removes any Java references from s and clears its state. If + doXDestroy is true and s->klazz and s->jObj are not NULL, s->jObj's + s is passed to s3jni_call_xDestroy() before any references are + cleared. It is legal to call this when the object has no Java + references. +*/ +static void S3JniHook_unref(JNIEnv * const env, S3JniHook * const s, int doXDestroy){ + if(doXDestroy && s->klazz && s->jObj){ + s3jni_call_xDestroy(env, s->jObj, s->klazz); + } + UNREF_G(s->jObj); + UNREF_G(s->klazz); + memset(s, 0, sizeof(*s)); +} + +/** + Clears s's state and moves it to the free-list. +*/ +FIXME_THREADING(perDb) +static void S3JniDb_set_aside(S3JniDb * const s){ + if(s){ + JNIEnv * const env = s->env; + assert(s->pDb && "Else this object is already in the free-list."); + //MARKER(("state@%p for db@%p setting aside\n", s, s->pDb)); + assert(s->pPrev != s); + assert(s->pNext != s); + assert(s->pPrev ? (s->pPrev!=s->pNext) : 1); + if(s->pNext) s->pNext->pPrev = s->pPrev; + if(s->pPrev) s->pPrev->pNext = s->pNext; + else if(S3JniGlobal.perDb.aUsed == s){ + assert(!s->pPrev); + S3JniGlobal.perDb.aUsed = s->pNext; + } + sqlite3_free( s->zMainDbName ); +#define UNHOOK(MEMBER,XDESTROY) S3JniHook_unref(env, &s->MEMBER, XDESTROY) + UNHOOK(trace, 0); + UNHOOK(progress, 0); + UNHOOK(commitHook, 0); + UNHOOK(rollbackHook, 0); + UNHOOK(updateHook, 0); + UNHOOK(authHook, 0); + UNHOOK(collation, 1); + UNHOOK(collationNeeded, 1); + UNHOOK(busyHandler, 1); +#undef UNHOOK + UNREF_G(s->jDb); +#ifdef SQLITE_ENABLE_FTS5 + UNREF_G(s->jFtsApi); +#endif + memset(s, 0, sizeof(S3JniDb)); + s->pNext = S3JniGlobal.perDb.aFree; + if(s->pNext) s->pNext->pPrev = s; + S3JniGlobal.perDb.aFree = s; + //MARKER(("%p->pPrev@%p, pNext@%p\n", s, s->pPrev, s->pNext)); + //if(s->pNext) MARKER(("next: %p->pPrev@%p\n", s->pNext, s->pNext->pPrev)); + } +} + +/** + Requires that p has been snipped from any linked list it is + in. Clears all Java refs p holds and zeroes out p. +*/ +static void S3JniEnvCache_clear(S3JniEnvCache * const p){ + JNIEnv * const env = p->env; + if(env){ + int i; + UNREF_G(p->g.cObj); + UNREF_G(p->g.cLong); + UNREF_G(p->g.cString); + UNREF_G(p->g.oCharsetUtf8); +#ifdef SQLITE_ENABLE_FTS5 + UNREF_G(p->jFtsExt); + UNREF_G(p->jPhraseIter.klazz); +#endif + for( i = 0; i < NphCache_SIZE; ++i ){ + S3JniNphCache_clear(env, &p->nph[i]); + } + memset(p, 0, sizeof(S3JniEnvCache)); + } +} + +/** + Cleans up all state in S3JniGlobal.perDb for th given JNIEnv. + Results are undefined if a Java-side db uses the API + from the given JNIEnv after this call. +*/ +FIXME_THREADING(perDb) +static void S3JniDb_free_for_env(JNIEnv *env){ + S3JniDb * ps = S3JniGlobal.perDb.aUsed; + S3JniDb * pNext = 0; + for( ; ps; ps = pNext ){ + pNext = ps->pNext; + if(ps->env == env){ + S3JniDb * const pPrev = ps->pPrev; + S3JniDb_set_aside(ps); + assert(pPrev ? pPrev->pNext!=ps : 1); + pNext = pPrev; + } + } +} + +/** + Uncache any state for the given JNIEnv, clearing all Java + references the cache owns. Returns true if env was cached and false + if it was not found in the cache. + + Also passes env to S3JniDb_free_for_env() to free up + what would otherwise be stale references. +*/ +static int S3JniGlobal_env_uncache(JNIEnv * const env){ + struct S3JniEnvCache * row = S3JniGlobal.envCache.aHead; + for( ; row; row = row->pNext ){ + if( row->env == env ){ + break; + } + } + if( !row ) return 0; + if( row->pNext ) row->pNext->pPrev = row->pPrev; + if( row->pPrev ) row->pPrev->pNext = row->pNext; + if( S3JniGlobal.envCache.aHead == row ){ + assert( !row->pPrev ); + S3JniGlobal.envCache.aHead = row->pNext; + } + S3JniEnvCache_clear(row); + assert( !row->pNext ); + assert( !row->pPrev ); + row->pNext = S3JniGlobal.envCache.aFree; + if( row->pNext ) row->pNext->pPrev = row; + S3JniGlobal.envCache.aFree = row; + S3JniDb_free_for_env(env); + return 1; +} + +static void S3JniGlobal_S3JniEnvCache_clear(void){ + while( S3JniGlobal.envCache.aHead ){ + S3JniGlobal_env_uncache( S3JniGlobal.envCache.aHead->env ); + } +} + +/** + Searches the NativePointerHolder cache for the given combination. + If it finds one, it returns it as-is. If it doesn't AND the cache + has a free slot, it populates that slot with (env, zClassName, + klazz) and returns it. If the cache is full with no match it + returns NULL. + + It is up to the caller to populate the other members of the returned + object if needed. + + zClassName must be a static string so we can use its address as a + cache key. + + This simple cache catches >99% of searches in the current + (2023-07-31) tests. +*/ +FIXME_THREADING(S3JniEnvCache) +static S3JniNphCache * S3JniGlobal_nph_cache(JNIEnv * const env, const char *zClassName){ + /** + According to: + + https://developer.ibm.com/articles/j-jni/ + + > ... the IDs returned for a given class don't change for the + lifetime of the JVM process. But the call to get the field or + method can require significant work in the JVM, because + fields and methods might have been inherited from + superclasses, making the JVM walk up the class hierarchy to + find them. Because the IDs are the same for a given class, + you should look them up once and then reuse them. Similarly, + looking up class objects can be expensive, so they should be + cached as well. + */ + struct S3JniEnvCache * const envRow = S3JniGlobal_env_cache(env); + S3JniNphCache * freeSlot = 0; + S3JniNphCache * pCache = 0; + int i; + assert(envRow); + for( i = 0; i < NphCache_SIZE; ++i ){ + pCache = &envRow->nph[i]; + if(zClassName == pCache->zClassName){ + ++S3JniGlobal.metrics.nphCacheHits; +#define DUMP_NPH_CACHES 0 +#if DUMP_NPH_CACHES + MARKER(("Cache hit #%u %s klazz@%p nativePointer field@%p, ctor@%p\n", + S3JniGlobal.metrics.nphCacheHits, zClassName, pCache->klazz, pCache->fidValue, + pCache->midCtor)); +#endif + assert(pCache->klazz); + return pCache; + }else if(!freeSlot && !pCache->zClassName){ + freeSlot = pCache; + } + } + if(freeSlot){ + freeSlot->zClassName = zClassName; + freeSlot->klazz = (*env)->FindClass(env, zClassName); + EXCEPTION_IS_FATAL("FindClass() unexpectedly threw"); + freeSlot->klazz = REF_G(freeSlot->klazz); + ++S3JniGlobal.metrics.nphCacheMisses; +#if DUMP_NPH_CACHES + static unsigned int cacheMisses = 0; + MARKER(("Cache miss #%u %s klazz@%p nativePointer field@%p, ctor@%p\n", + S3JniGlobal.metrics.nphCacheMisses, zClassName, freeSlot->klazz, + freeSlot->fidValue, freeSlot->midCtor)); +#endif +#undef DUMP_NPH_CACHES + }else{ + (*env)->FatalError(env, "MAINTENANCE REQUIRED: NphCache_SIZE is too low."); + } + return freeSlot; +} + +/** + Returns the ID of the "nativePointer" field from the given + NativePointerHolder class. + */ +static jfieldID NativePointerHolder_getField(JNIEnv * const env, jclass klazz){ + jfieldID rv = (*env)->GetFieldID(env, klazz, "nativePointer", "J"); + EXCEPTION_IS_FATAL("Code maintenance required: missing nativePointer field."); + return rv; +} + +/** + Sets a native ptr value in NativePointerHolder object ppOut. + zClassName must be a static string so we can use its address + as a cache key. +*/ +static void NativePointerHolder_set(JNIEnv * env, jobject ppOut, const void * p, + const char *zClassName){ + jfieldID setter = 0; + S3JniNphCache * const pCache = S3JniGlobal_nph_cache(env, zClassName); + if(pCache && pCache->klazz && pCache->fidValue){ + assert(zClassName == pCache->zClassName); + setter = pCache->fidValue; + assert(setter); + }else{ + jclass const klazz = + pCache ? pCache->klazz : (*env)->GetObjectClass(env, ppOut); + setter = NativePointerHolder_getField(env, klazz); + if(pCache){ + assert(pCache->klazz); + assert(!pCache->fidValue); + assert(zClassName == pCache->zClassName); + pCache->fidValue = setter; + } + } + (*env)->SetLongField(env, ppOut, setter, (jlong)p); + EXCEPTION_IS_FATAL("Could not set NativePointerHolder.nativePointer."); +} + +/** + Fetches a native ptr value from NativePointerHolder object ppOut. + zClassName must be a static string so we can use its address as a + cache key. +*/ +static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, const char *zClassName){ + if( pObj ){ + jfieldID getter = 0; + void * rv = 0; + S3JniNphCache * const pCache = S3JniGlobal_nph_cache(env, zClassName); + if(pCache && pCache->fidValue){ + getter = pCache->fidValue; + }else{ + jclass const klazz = + pCache ? pCache->klazz : (*env)->GetObjectClass(env, pObj); + getter = NativePointerHolder_getField(env, klazz); + if(pCache){ + assert(pCache->klazz); + assert(zClassName == pCache->zClassName); + pCache->fidValue = getter; + } + } + rv = (void*)(*env)->GetLongField(env, pObj, getter); + IFTHREW_REPORT; + return rv; + }else{ + return 0; + } +} + +/** + Extracts the new S3JniDb instance from the free-list, or + allocates one if needed, associats it with pDb, and returns. + Returns NULL on OOM. pDb MUST be associated with jDb via + NativePointerHolder_set(). +*/ +static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb, + jobject jDb){ + S3JniDb * rv; + if(S3JniGlobal.perDb.aFree){ + rv = S3JniGlobal.perDb.aFree; + //MARKER(("state@%p for db allocating for db@%p from free-list\n", rv, pDb)); + //MARKER(("%p->pPrev@%p, pNext@%p\n", rv, rv->pPrev, rv->pNext)); + S3JniGlobal.perDb.aFree = rv->pNext; + assert(rv->pNext != rv); + assert(rv->pPrev != rv); + assert(rv->pPrev ? (rv->pPrev!=rv->pNext) : 1); + if(rv->pNext){ + assert(rv->pNext->pPrev == rv); + assert(rv->pPrev ? (rv->pNext == rv->pPrev->pNext) : 1); + rv->pNext->pPrev = 0; + rv->pNext = 0; + } + }else{ + rv = s3jni_malloc(env, sizeof(S3JniDb)); + //MARKER(("state@%p for db allocating for db@%p from heap\n", rv, pDb)); + if(rv){ + memset(rv, 0, sizeof(S3JniDb)); + } + } + if(rv){ + rv->pNext = S3JniGlobal.perDb.aUsed; + S3JniGlobal.perDb.aUsed = rv; + if(rv->pNext){ + assert(!rv->pNext->pPrev); + rv->pNext->pPrev = rv; + } + rv->jDb = REF_G(jDb); + rv->pDb = pDb; + rv->env = env; + } + return rv; +} + +#if 0 +static void S3JniDb_dump(S3JniDb *s){ + MARKER(("S3JniDb->env @ %p\n", s->env)); + MARKER(("S3JniDb->pDb @ %p\n", s->pDb)); + MARKER(("S3JniDb->trace.jObj @ %p\n", s->trace.jObj)); + MARKER(("S3JniDb->progress.jObj @ %p\n", s->progress.jObj)); + MARKER(("S3JniDb->commitHook.jObj @ %p\n", s->commitHook.jObj)); + MARKER(("S3JniDb->rollbackHook.jObj @ %p\n", s->rollbackHook.jObj)); + MARKER(("S3JniDb->busyHandler.jObj @ %p\n", s->busyHandler.jObj)); + MARKER(("S3JniDb->env @ %p\n", s->env)); +} +#endif + +/** + Returns the S3JniDb object for the given db. If allocIfNeeded is + true then a new instance will be allocated if no mapping currently + exists, else NULL is returned if no mapping is found. + + The 3rd and 4th args should normally only be non-0 for + sqlite3_open(_v2)(). For most other cases, they must be 0, in which + case the db handle will be fished out of the jDb object and NULL is + returned if jDb does not have any associated S3JniDb. + + If called with a NULL jDb and non-NULL pDb then allocIfNeeded MUST + be false and it will look for a matching db object. That usage is + required for functions, like sqlite3_context_db_handle(), which + return a (sqlite3*) but do not take one as an argument. +*/ +FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(perDb) +static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, + sqlite3 *pDb, int allocIfNeeded){ + S3JniDb * s = S3JniGlobal.perDb.aUsed; + if(!jDb){ + if(pDb){ + assert(!allocIfNeeded); + }else{ + return 0; + } + } + assert(allocIfNeeded ? !!pDb : 1); + if(!allocIfNeeded && !pDb){ + pDb = PtrGet_sqlite3(jDb); + } + for( ; pDb && s; s = s->pNext){ + if(s->pDb == pDb) return s; + } + if(allocIfNeeded){ + s = S3JniDb_alloc(env, pDb, jDb); + } + return s; +} + +#if 0 +/** + An alternative form which searches for the S3JniDb instance for + pDb with no JNIEnv-specific info. This can be (but _should_ it be?) + called from the context of a separate JNIEnv than the one mapped + to in the returned object. Returns 0 if no match is found. +*/ +FIXME_THREADING(perDb) +static S3JniDb * S3JniDb_for_db2(sqlite3 *pDb){ + S3JniDb * s = S3JniGlobal.perDb.aUsed; + for( ; pDb && s; s = s->pNext){ + if(s->pDb == pDb) return s; + } + return 0; +} +#endif + +#if S3JNI_ENABLE_AUTOEXT +/** + Unlink ax from S3JniGlobal.autoExt and free it. +*/ +static void S3JniAutoExtension_free(JNIEnv * const env, + S3JniAutoExtension * const ax){ + if( ax ){ + if( ax->pNext ) ax->pNext->pPrev = ax->pPrev; + if( ax == S3JniGlobal.autoExt.pHead ){ + assert( !ax->pNext ); + S3JniGlobal.autoExt.pHead = ax->pNext; + }else if( ax->pPrev ){ + ax->pPrev->pNext = ax->pNext; + } + ax->pNext = ax->pPrev = 0; + UNREF_G(ax->jObj); + sqlite3_free(ax); + } +} + +/** + Allocates a new auto extension and plugs it in to S3JniGlobal.autoExt. + Returns 0 on OOM or if there is an error collecting the required + state from jAutoExt (which must be an AutoExtension object). +*/ +static S3JniAutoExtension * S3JniAutoExtension_alloc(JNIEnv *const env, + jobject const jAutoExt){ + S3JniAutoExtension * const ax = sqlite3_malloc(sizeof(*ax)); + if( ax ){ + jclass klazz; + memset(ax, 0, sizeof(*ax)); + klazz = (*env)->GetObjectClass(env, jAutoExt); + if(!klazz){ + S3JniAutoExtension_free(env, ax); + return 0; + } + ax->midFunc = (*env)->GetMethodID(env, klazz, "xEntryPoint", + "(Lorg/sqlite/jni/sqlite3;)I"); + if(!ax->midFunc){ + MARKER(("Error getting xEntryPoint(sqlite3) from object.")); + S3JniAutoExtension_free(env, ax); + return 0; + } + ax->jObj = REF_G(jAutoExt); + ax->pNext = S3JniGlobal.autoExt.pHead; + if( ax->pNext ) ax->pNext->pPrev = ax; + S3JniGlobal.autoExt.pHead = ax; + } + return ax; +} +#endif /* S3JNI_ENABLE_AUTOEXT */ + +/** + Requires that jCx be a Java-side sqlite3_context wrapper for pCx. + This function calls sqlite3_aggregate_context() to allocate a tiny + sliver of memory, the address of which is set in + jCx->aggregateContext. The memory is only used as a key for + mapping client-side results of aggregate result sets across + calls to the UDF's callbacks. + + isFinal must be 1 for xFinal() calls and 0 for all others, the + difference being that the xFinal() invocation will not allocate + new memory if it was not already, resulting in a value of 0 + for jCx->aggregateContext. + + Returns 0 on success. Returns SQLITE_NOMEM on allocation error, + noting that it will not allocate when isFinal is true. It returns + SQLITE_ERROR if there's a serious internal error in dealing with + the JNI state. +*/ +static int udf_setAggregateContext(JNIEnv * env, jobject jCx, + sqlite3_context * pCx, + int isFinal){ + jfieldID member; + void * pAgg; + int rc = 0; + S3JniNphCache * const pCache = + S3JniGlobal_nph_cache(env, S3JniClassNames.sqlite3_context); + if(pCache && pCache->klazz && pCache->fidSetAgg){ + member = pCache->fidSetAgg; + assert(member); + }else{ + jclass const klazz = + pCache ? pCache->klazz : (*env)->GetObjectClass(env, jCx); + member = (*env)->GetFieldID(env, klazz, "aggregateContext", "J"); + if( !member ){ + IFTHREW{ EXCEPTION_REPORT; EXCEPTION_CLEAR; } + return s3jni_db_error(sqlite3_context_db_handle(pCx), + SQLITE_ERROR, + "Internal error: cannot find " + "sqlite3_context::aggregateContext field."); + } + if(pCache){ + assert(pCache->klazz); + assert(!pCache->fidSetAgg); + pCache->fidSetAgg = member; + } + } + pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : 4); + if( pAgg || isFinal ){ + (*env)->SetLongField(env, jCx, member, (jlong)pAgg); + }else{ + assert(!pAgg); + rc = SQLITE_NOMEM; + } + return rc; +} + +/** + Common init for OutputPointer_set_Int32() and friends. zClassName must be a + pointer from S3JniClassNames. jOut must be an instance of that + class. Fetches the jfieldID for jOut's [value] property, which must + be of the type represented by the JNI type signature zTypeSig, and + stores it in pFieldId. Fails fatally if the property is not found, + as that presents a serious internal misuse. + + Property lookups are cached on a per-zClassName basis. Do not use + this routine with the same zClassName but different zTypeSig: it + will misbehave. +*/ +static void setupOutputPointer(JNIEnv * const env, const char *zClassName, + const char * const zTypeSig, + jobject const jOut, jfieldID * const pFieldId){ + jfieldID setter = 0; + S3JniNphCache * const pCache = + S3JniGlobal_nph_cache(env, zClassName); + if(pCache && pCache->klazz && pCache->fidValue){ + setter = pCache->fidValue; + }else{ + const jclass klazz = (*env)->GetObjectClass(env, jOut); + /*MARKER(("%s => %s\n", zClassName, zTypeSig));*/ + setter = (*env)->GetFieldID(env, klazz, "value", zTypeSig); + EXCEPTION_IS_FATAL("setupOutputPointer() could not find OutputPointer.*.value"); + if(pCache){ + assert(!pCache->fidValue); + pCache->fidValue = setter; + } + } + *pFieldId = setter; +} + +/* Sets the value property of the OutputPointer.Int32 jOut object + to v. */ +static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut, int v){ + jfieldID setter = 0; + setupOutputPointer(env, S3JniClassNames.OutputPointer_Int32, "I", jOut, &setter); + (*env)->SetIntField(env, jOut, setter, (jint)v); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int32.value"); +} + +/* Sets the value property of the OutputPointer.Int64 jOut object + to v. */ +static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, jlong v){ + jfieldID setter = 0; + setupOutputPointer(env, S3JniClassNames.OutputPointer_Int64, "J", jOut, &setter); + (*env)->SetLongField(env, jOut, setter, v); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int64.value"); +} + +static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut, + jobject jDb){ + jfieldID setter = 0; + setupOutputPointer(env, S3JniClassNames.OutputPointer_sqlite3, + "Lorg/sqlite/jni/sqlite3;", jOut, &setter); + (*env)->SetObjectField(env, jOut, setter, jDb); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3.value"); +} + +static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOut, + jobject jStmt){ + jfieldID setter = 0; + setupOutputPointer(env, S3JniClassNames.OutputPointer_sqlite3_stmt, + "Lorg/sqlite/jni/sqlite3_stmt;", jOut, &setter); + (*env)->SetObjectField(env, jOut, setter, jStmt); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3_stmt.value"); +} + +#ifdef SQLITE_ENABLE_FTS5 +#if 0 +/* Sets the value property of the OutputPointer.ByteArray jOut object + to v. */ +static void OutputPointer_set_ByteArray(JNIEnv * const env, jobject const jOut, + jbyteArray const v){ + jfieldID setter = 0; + setupOutputPointer(env, S3JniClassNames.OutputPointer_ByteArray, "[B", + jOut, &setter); + (*env)->SetObjectField(env, jOut, setter, v); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.ByteArray.value"); +} +#endif +/* Sets the value property of the OutputPointer.String jOut object + to v. */ +static void OutputPointer_set_String(JNIEnv * const env, jobject const jOut, + jstring const v){ + jfieldID setter = 0; + setupOutputPointer(env, S3JniClassNames.OutputPointer_String, + "Ljava/lang/String;", jOut, &setter); + (*env)->SetObjectField(env, jOut, setter, v); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.String.value"); +} +#endif /* SQLITE_ENABLE_FTS5 */ + +static int encodingTypeIsValid(int eTextRep){ + switch(eTextRep){ + case SQLITE_UTF8: case SQLITE_UTF16: + case SQLITE_UTF16LE: case SQLITE_UTF16BE: + return 1; + default: + return 0; + } +} + +static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs, + int nRhs, const void *rhs){ + S3JniDb * const ps = pArg; + JNIEnv * env = ps->env; + jint rc = 0; + jbyteArray jbaLhs = (*env)->NewByteArray(env, (jint)nLhs); + jbyteArray jbaRhs = jbaLhs ? (*env)->NewByteArray(env, (jint)nRhs) : NULL; + //MARKER(("native xCompare nLhs=%d nRhs=%d\n", nLhs, nRhs)); + if(!jbaRhs){ + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + return 0; + //(*env)->FatalError(env, "Out of memory. Cannot allocate arrays for collation."); + } + (*env)->SetByteArrayRegion(env, jbaLhs, 0, (jint)nLhs, (const jbyte*)lhs); + (*env)->SetByteArrayRegion(env, jbaRhs, 0, (jint)nRhs, (const jbyte*)rhs); + rc = (*env)->CallIntMethod(env, ps->collation.jObj, ps->collation.midCallback, + jbaLhs, jbaRhs); + EXCEPTION_IGNORE; + UNREF_L(jbaLhs); + UNREF_L(jbaRhs); + return (int)rc; +} + +/* Collation finalizer for use by the sqlite3 internals. */ +static void CollationState_xDestroy(void *pArg){ + S3JniDb * const ps = pArg; + S3JniHook_unref( ps->env, &ps->collation, 1 ); +} + +/* State for sqlite3_result_java_object() and + sqlite3_value_java_object(). */ +typedef struct { + /* The JNI docs say: + + https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html + + > The VM is guaranteed to pass the same interface pointer to a + native method when it makes multiple calls to the native method + from the same Java thread. + + Per the accompanying diagram, the "interface pointer" is the + pointer-to-pointer which is passed to all JNI calls + (`JNIEnv *env`), implying that we need to be caching that. The + verbiage "interface pointer" implies, however, that we should be + storing the dereferenced `(*env)` pointer. + + This posts claims it's unsafe to cache JNIEnv at all, even when + it's always used in the same thread: + + https://stackoverflow.com/questions/12420463 + + And this one seems to contradict that: + + https://stackoverflow.com/questions/13964608 + + For later reference: + + https://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/design.html#wp1242 + + https://developer.android.com/training/articles/perf-jni + + The later has the following say about caching: + + > If performance is important, it's useful to look the + [class/method ID] values up once and cache the results in your + native code. Because there is a limit of one JavaVM per + process, it's reasonable to store this data in a static local + structure. ... The class references, field IDs, and method IDs + are guaranteed valid until the class is unloaded. Classes are + only unloaded if all classes associated with a ClassLoader can + be garbage collected, which is rare but will not be impossible + in Android. Note however that the jclass is a class reference + and must be protected with a call to NewGlobalRef (see the next + section). + */ + JNIEnv * env; + jobject jObj; +} ResultJavaVal; + +/* For use with sqlite3_result/value_pointer() */ +#define RESULT_JAVA_VAL_STRING "ResultJavaVal" + +static ResultJavaVal * ResultJavaVal_alloc(JNIEnv * const env, jobject jObj){ + ResultJavaVal * rv = sqlite3_malloc(sizeof(ResultJavaVal)); + if(rv){ + rv->env = env; + rv->jObj = jObj ? REF_G(jObj) : 0; + } + return rv; +} + +static void ResultJavaVal_finalizer(void *v){ + if(v){ + ResultJavaVal * const rv = (ResultJavaVal*)v; + if(rv->jObj) (*(rv->env))->DeleteGlobalRef(rv->env, rv->jObj); + sqlite3_free(rv); + } +} + + + +/** + Returns a new Java instance of the class named by zClassName, which + MUST be interface-compatible with NativePointerHolder and MUST have + a no-arg constructor. The NativePointerHolder_set() method is + passed the new Java object and pNative. Hypothetically returns NULL + if Java fails to allocate, but the JNI docs are not entirely clear + on that detail. + + Always use a static string pointer from S3JniClassNames for the 2nd + argument so that we can use its address as a cache key. +*/ +static jobject new_NativePointerHolder_object(JNIEnv * const env, const char *zClassName, + const void * pNative){ + jobject rv = 0; + jclass klazz = 0; + jmethodID ctor = 0; + S3JniNphCache * const pCache = + S3JniGlobal_nph_cache(env, zClassName); + if(pCache && pCache->midCtor){ + assert( pCache->klazz ); + klazz = pCache->klazz; + ctor = pCache->midCtor; + }else{ + klazz = pCache + ? pCache->klazz + : (*env)->FindClass(env, zClassName); + ctor = klazz ? (*env)->GetMethodID(env, klazz, "", "()V") : 0; + EXCEPTION_IS_FATAL("Cannot find constructor for class."); + if(pCache){ + assert(zClassName == pCache->zClassName); + assert(pCache->klazz); + assert(!pCache->midCtor); + pCache->midCtor = ctor; + } + } + assert(klazz); + assert(ctor); + rv = (*env)->NewObject(env, klazz, ctor); + EXCEPTION_IS_FATAL("No-arg constructor threw."); + if(rv) NativePointerHolder_set(env, rv, pNative, zClassName); + return rv; +} + +static inline jobject new_sqlite3_wrapper(JNIEnv * const env, sqlite3 *sv){ + return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3, sv); +} +static inline jobject new_sqlite3_context_wrapper(JNIEnv * const env, sqlite3_context *sv){ + return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_context, sv); +} +static inline jobject new_sqlite3_stmt_wrapper(JNIEnv * const env, sqlite3_stmt *sv){ + return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_stmt, sv); +} +static inline jobject new_sqlite3_value_wrapper(JNIEnv * const env, sqlite3_value *sv){ + return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_value, sv); +} + +enum UDFType { + UDF_SCALAR = 1, + UDF_AGGREGATE, + UDF_WINDOW, + UDF_UNKNOWN_TYPE/*for error propagation*/ +}; + +typedef void (*udf_xFunc_f)(sqlite3_context*,int,sqlite3_value**); +typedef void (*udf_xStep_f)(sqlite3_context*,int,sqlite3_value**); +typedef void (*udf_xFinal_f)(sqlite3_context*); +/*typedef void (*udf_xValue_f)(sqlite3_context*);*/ +/*typedef void (*udf_xInverse_f)(sqlite3_context*,int,sqlite3_value**);*/ + +/** + State for binding Java-side UDFs. +*/ +typedef struct S3JniUdf S3JniUdf; +struct S3JniUdf { + JNIEnv * env; /* env registered from */; + jobject jObj /* SQLFunction instance */; + jclass klazz /* jObj's class */; + char * zFuncName /* Only for error reporting and debug logging */; + enum UDFType type; + /** Method IDs for the various UDF methods. */ + jmethodID jmidxFunc; + jmethodID jmidxStep; + jmethodID jmidxFinal; + jmethodID jmidxValue; + jmethodID jmidxInverse; +}; + +static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){ + S3JniUdf * const s = sqlite3_malloc(sizeof(S3JniUdf)); + if(s){ + const char * zFSI = /* signature for xFunc, xStep, xInverse */ + "(Lorg/sqlite/jni/sqlite3_context;[Lorg/sqlite/jni/sqlite3_value;)V"; + const char * zFV = /* signature for xFinal, xValue */ + "(Lorg/sqlite/jni/sqlite3_context;)V"; + memset(s, 0, sizeof(S3JniUdf)); + s->env = env; + s->jObj = REF_G(jObj); + s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); +#define FGET(FuncName,FuncType,Field) \ + s->Field = (*env)->GetMethodID(env, s->klazz, FuncName, FuncType); \ + if(!s->Field) (*env)->ExceptionClear(env) + FGET("xFunc", zFSI, jmidxFunc); + FGET("xStep", zFSI, jmidxStep); + FGET("xFinal", zFV, jmidxFinal); + FGET("xValue", zFV, jmidxValue); + FGET("xInverse", zFSI, jmidxInverse); +#undef FGET + if(s->jmidxFunc) s->type = UDF_SCALAR; + else if(s->jmidxStep && s->jmidxFinal){ + s->type = s->jmidxValue ? UDF_WINDOW : UDF_AGGREGATE; + }else{ + s->type = UDF_UNKNOWN_TYPE; + } + } + return s; +} + +static void S3JniUdf_free(S3JniUdf * s){ + JNIEnv * const env = s->env; + if(env){ + //MARKER(("UDF cleanup: %s\n", s->zFuncName)); + s3jni_call_xDestroy(env, s->jObj, s->klazz); + UNREF_G(s->jObj); + UNREF_G(s->klazz); + } + sqlite3_free(s->zFuncName); + sqlite3_free(s); +} + +static void S3JniUdf_finalizer(void * s){ + //MARKER(("UDF finalizer @ %p\n", s)); + if(s) S3JniUdf_free((S3JniUdf*)s); +} + +/** + Helper for processing args to UDF handlers + with signature (sqlite3_context*,int,sqlite3_value**). +*/ +typedef struct { + jobject jcx; + jobjectArray jargv; +} udf_jargs; + +/** + Converts the given (cx, argc, argv) into arguments for the given + UDF, placing the result in the final argument. Returns 0 on + success, SQLITE_NOMEM on allocation error. + + TODO: see what we can do to optimize the + new_sqlite3_value_wrapper() call. e.g. find the ctor a single time + and call it here, rather than looking it up repeatedly. +*/ +static int udf_args(JNIEnv *env, + sqlite3_context * const cx, + int argc, sqlite3_value**argv, + jobject * jCx, jobjectArray *jArgv){ + jobjectArray ja = 0; + jobject jcx = new_sqlite3_context_wrapper(env, cx); + jint i; + *jCx = 0; + *jArgv = 0; + if(!jcx) goto error_oom; + ja = (*env)->NewObjectArray(env, argc, + S3JniGlobal_env_cache(env)->g.cObj, + NULL); + if(!ja) goto error_oom; + for(i = 0; i < argc; ++i){ + jobject jsv = new_sqlite3_value_wrapper(env, argv[i]); + if(!jsv) goto error_oom; + (*env)->SetObjectArrayElement(env, ja, i, jsv); + UNREF_L(jsv)/*array has a ref*/; + } + *jCx = jcx; + *jArgv = ja; + return 0; +error_oom: + sqlite3_result_error_nomem(cx); + UNREF_L(jcx); + UNREF_L(ja); + return SQLITE_NOMEM; +} + +static int udf_report_exception(sqlite3_context * cx, + const char *zFuncName, + const char *zFuncType){ + int rc; + char * z = + sqlite3_mprintf("Client-defined function %s.%s() threw. It should " + "not do that.", + zFuncName ? zFuncName : "", zFuncType); + if(z){ + sqlite3_result_error(cx, z, -1); + sqlite3_free(z); + rc = SQLITE_ERROR; + }else{ + sqlite3_result_error_nomem(cx); + rc = SQLITE_NOMEM; + } + return rc; +} + +/** + Sets up the state for calling a Java-side xFunc/xStep/xInverse() + UDF, calls it, and returns 0 on success. +*/ +static int udf_xFSI(sqlite3_context* pCx, int argc, + sqlite3_value** argv, + S3JniUdf * s, + jmethodID xMethodID, + const char * zFuncType){ + JNIEnv * const env = s->env; + udf_jargs args = {0,0}; + int rc = udf_args(s->env, pCx, argc, argv, &args.jcx, &args.jargv); + //MARKER(("%s.%s() pCx = %p\n", s->zFuncName, zFuncType, pCx)); + if(rc) return rc; + //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType)); + if( UDF_SCALAR != s->type ){ + rc = udf_setAggregateContext(env, args.jcx, pCx, 0); + } + if( 0 == rc ){ + (*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv); + IFTHREW{ + rc = udf_report_exception(pCx, s->zFuncName, zFuncType); + } + } + UNREF_L(args.jcx); + UNREF_L(args.jargv); + return rc; +} + +/** + Sets up the state for calling a Java-side xFinal/xValue() UDF, + calls it, and returns 0 on success. +*/ +static int udf_xFV(sqlite3_context* cx, S3JniUdf * s, + jmethodID xMethodID, + const char *zFuncType){ + JNIEnv * const env = s->env; + jobject jcx = new_sqlite3_context_wrapper(s->env, cx); + int rc = 0; + //MARKER(("%s.%s() cx = %p\n", s->zFuncName, zFuncType, cx)); + if(!jcx){ + sqlite3_result_error_nomem(cx); + return SQLITE_NOMEM; + } + //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType)); + if( UDF_SCALAR != s->type ){ + rc = udf_setAggregateContext(env, jcx, cx, 1); + } + if( 0 == rc ){ + (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx); + IFTHREW{ + rc = udf_report_exception(cx,s->zFuncName, zFuncType); + } + } + UNREF_L(jcx); + return rc; +} + +static void udf_xFunc(sqlite3_context* cx, int argc, + sqlite3_value** argv){ + S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); + ++S3JniGlobal.metrics.udf.nFunc; + udf_xFSI(cx, argc, argv, s, s->jmidxFunc, "xFunc"); +} +static void udf_xStep(sqlite3_context* cx, int argc, + sqlite3_value** argv){ + S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); + ++S3JniGlobal.metrics.udf.nStep; + udf_xFSI(cx, argc, argv, s, s->jmidxStep, "xStep"); +} +static void udf_xFinal(sqlite3_context* cx){ + S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); + ++S3JniGlobal.metrics.udf.nFinal; + udf_xFV(cx, s, s->jmidxFinal, "xFinal"); +} +static void udf_xValue(sqlite3_context* cx){ + S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); + ++S3JniGlobal.metrics.udf.nValue; + udf_xFV(cx, s, s->jmidxValue, "xValue"); +} +static void udf_xInverse(sqlite3_context* cx, int argc, + sqlite3_value** argv){ + S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); + ++S3JniGlobal.metrics.udf.nInverse; + udf_xFSI(cx, argc, argv, s, s->jmidxInverse, "xInverse"); +} + + +//////////////////////////////////////////////////////////////////////// +// What follows is the JNI/C bindings. They are in alphabetical order +// except for this macro-generated subset which are kept together here +// at the front... +//////////////////////////////////////////////////////////////////////// +WRAP_INT_STMT(1bind_1parameter_1count, sqlite3_bind_parameter_count) +WRAP_INT_DB(1changes, sqlite3_changes) +WRAP_INT64_DB(1changes64, sqlite3_changes64) +WRAP_INT_STMT(1clear_1bindings, sqlite3_clear_bindings) +WRAP_INT_STMT_INT(1column_1bytes, sqlite3_column_bytes) +WRAP_INT_STMT_INT(1column_1bytes16, sqlite3_column_bytes16) +WRAP_INT_STMT(1column_1count, sqlite3_column_count) +WRAP_STR_STMT_INT(1column_1decltype, sqlite3_column_decltype) +WRAP_STR_STMT_INT(1column_1name, sqlite3_column_name) +WRAP_STR_STMT_INT(1column_1database_1name, sqlite3_column_database_name) +WRAP_STR_STMT_INT(1column_1origin_1name, sqlite3_column_origin_name) +WRAP_STR_STMT_INT(1column_1table_1name, sqlite3_column_table_name) +WRAP_INT_STMT_INT(1column_1type, sqlite3_column_type) +WRAP_INT_STMT(1data_1count, sqlite3_data_count) +WRAP_INT_DB(1error_1offset, sqlite3_error_offset) +WRAP_INT_DB(1extended_1errcode, sqlite3_extended_errcode) +WRAP_MUTF8_VOID(1libversion, sqlite3_libversion) +WRAP_INT_VOID(1libversion_1number, sqlite3_libversion_number) +WRAP_INT_INT(1sleep, sqlite3_sleep) +WRAP_MUTF8_VOID(1sourceid, sqlite3_sourceid) +WRAP_INT_VOID(1threadsafe, sqlite3_threadsafe) +WRAP_INT_DB(1total_1changes, sqlite3_total_changes) +WRAP_INT64_DB(1total_1changes64, sqlite3_total_changes64) +WRAP_INT_SVALUE(1value_1bytes, sqlite3_value_bytes) +WRAP_INT_SVALUE(1value_1bytes16, sqlite3_value_bytes16) +WRAP_INT_SVALUE(1value_1encoding, sqlite3_value_encoding) +WRAP_INT_SVALUE(1value_1frombind, sqlite3_value_frombind) +WRAP_INT_SVALUE(1value_1nochange, sqlite3_value_nochange) +WRAP_INT_SVALUE(1value_1numeric_1type, sqlite3_value_numeric_type) +WRAP_INT_SVALUE(1value_1subtype, sqlite3_value_subtype) +WRAP_INT_SVALUE(1value_1type, sqlite3_value_type) + +#if S3JNI_ENABLE_AUTOEXT +/* Central auto-extension handler. */ +FIXME_THREADING(autoExt) +static int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr, + const struct sqlite3_api_routines *ignored){ + S3JniAutoExtension const * pAX = S3JniGlobal.autoExt.pHead; + int rc; + JNIEnv * env = 0; + S3JniDb * const ps = S3JniGlobal.autoExt.psOpening; + //MARKER(("auto-extension on open()ing ps@%p db@%p\n", ps, pDb)); + S3JniGlobal.autoExt.psOpening = 0; + if( !pAX ){ + assert( 0==S3JniGlobal.autoExt.isRunning ); + return 0; + } + else if( S3JniGlobal.autoExt.isRunning ){ + /* Necessary to avoid certain endless loop/stack overflow cases. */ + *pzErr = sqlite3_mprintf("Auto-extensions must not be triggered while " + "auto-extensions are running."); + return SQLITE_MISUSE; + } + else if(!ps){ + MARKER(("Internal error: cannot find S3JniDb for auto-extension\n")); + return SQLITE_ERROR; + }else if( (*S3JniGlobal.jvm)->GetEnv(S3JniGlobal.jvm, (void **)&env, JNI_VERSION_1_8) ){ + assert(!"Cannot get JNIEnv"); + *pzErr = sqlite3_mprintf("Could not get current JNIEnv."); + return SQLITE_ERROR; + } + assert( !ps->pDb /* it's still being opened */ ); + ps->pDb = pDb; + assert( ps->jDb ); + NativePointerHolder_set(env, ps->jDb, pDb, S3JniClassNames.sqlite3); + ++S3JniGlobal.autoExt.isRunning; + for( ; pAX; pAX = pAX->pNext ){ + rc = (*env)->CallIntMethod(env, pAX->jObj, pAX->midFunc, ps->jDb); + IFTHREW { + jthrowable const ex = (*env)->ExceptionOccurred(env); + char * zMsg; + EXCEPTION_CLEAR; + zMsg = s3jni_exception_error_msg(env, ex); + UNREF_L(ex); + *pzErr = sqlite3_mprintf("auto-extension threw: %s", zMsg); + sqlite3_free(zMsg); + rc = rc ? rc : SQLITE_ERROR; + break; + }else if( rc ){ + break; + } + } + --S3JniGlobal.autoExt.isRunning; + return rc; +} + +FIXME_THREADING(autoExt) +JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){ + static int once = 0; + S3JniAutoExtension * ax; + + if( !jAutoExt ) return SQLITE_MISUSE; + else if( 0==once && ++once ){ + sqlite3_auto_extension( (void(*)(void))s3jni_auto_extension ); + } + ax = S3JniGlobal.autoExt.pHead; + for( ; ax; ax = ax->pNext ){ + if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ + return 0 /* C API treats this as a no-op. */; + } + } + return S3JniAutoExtension_alloc(env, jAutoExt) ? 0 : SQLITE_NOMEM; +} +#endif /* S3JNI_ENABLE_AUTOEXT */ + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt, + jint ndx, jbyteArray baData, jint nMax){ + int rc; + if(!baData){ + rc = sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), ndx); + }else{ + jbyte * const pBuf = JBA_TOC(baData); + rc = sqlite3_bind_blob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, pBuf, (int)nMax, + SQLITE_TRANSIENT); + JBA_RELEASE(baData,pBuf); + } + return (jint)rc; +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1bind_1double)(JENV_CSELF, jobject jpStmt, + jint ndx, jdouble val){ + return (jint)sqlite3_bind_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (double)val); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1bind_1int)(JENV_CSELF, jobject jpStmt, + jint ndx, jint val){ + return (jint)sqlite3_bind_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)val); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1bind_1int64)(JENV_CSELF, jobject jpStmt, + jint ndx, jlong val){ + return (jint)sqlite3_bind_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1bind_1null)(JENV_CSELF, jobject jpStmt, + jint ndx){ + return (jint)sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1bind_1parameter_1index)(JENV_CSELF, jobject jpStmt, jbyteArray jName){ + int rc = 0; + jbyte * const pBuf = JBA_TOC(jName); + if(pBuf){ + rc = sqlite3_bind_parameter_index(PtrGet_sqlite3_stmt(jpStmt), + (const char *)pBuf); + JBA_RELEASE(jName, pBuf); + } + return rc; +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1bind_1text)(JENV_CSELF, jobject jpStmt, + jint ndx, jbyteArray baData, jint nMax){ + if(baData){ + jbyte * const pBuf = JBA_TOC(baData); + int rc = sqlite3_bind_text(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (const char *)pBuf, + (int)nMax, SQLITE_TRANSIENT); + JBA_RELEASE(baData, pBuf); + return (jint)rc; + }else{ + return sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); + } +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1bind_1zeroblob)(JENV_CSELF, jobject jpStmt, + jint ndx, jint n){ + return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)n); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1bind_1zeroblob64)(JENV_CSELF, jobject jpStmt, + jint ndx, jlong n){ + return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_uint64)n); +} + +static int s3jni_busy_handler(void* pState, int n){ + S3JniDb * const ps = (S3JniDb *)pState; + int rc = 0; + if( ps->busyHandler.jObj ){ + JNIEnv * const env = ps->env; + rc = (*env)->CallIntMethod(env, ps->busyHandler.jObj, + ps->busyHandler.midCallback, (jint)n); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW("busy-handler callback"); + EXCEPTION_CLEAR; + rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "busy-handle callback threw."); + } + } + return rc; +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + int rc = 0; + if(!ps) return (jint)SQLITE_NOMEM; + if(jBusy){ + S3JniHook * const pHook = &ps->busyHandler; + if(pHook->jObj && (*env)->IsSameObject(env, pHook->jObj, jBusy)){ + /* Same object - this is a no-op. */ + return 0; + } + S3JniHook_unref(env, pHook, 1); + pHook->jObj = REF_G(jBusy); + pHook->klazz = REF_G((*env)->GetObjectClass(env, jBusy)); + pHook->midCallback = (*env)->GetMethodID(env, pHook->klazz, "xCallback", "(I)I"); + IFTHREW { + S3JniHook_unref(env, pHook, 0); + rc = SQLITE_ERROR; + } + if(rc){ + return rc; + } + }else{ + S3JniHook_unref(env, &ps->busyHandler, 1); + } + return jBusy + ? sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps) + : sqlite3_busy_handler(ps->pDb, 0, 0); +} + +FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(perDb) +JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + if( ps ){ + S3JniHook_unref(env, &ps->busyHandler, 1); + return sqlite3_busy_timeout(ps->pDb, (int)ms); + } + return SQLITE_MISUSE; +} + +#if S3JNI_ENABLE_AUTOEXT +FIXME_THREADING(autoExt) +JDECL(jboolean,1cancel_1auto_1extension)(JENV_CSELF, jobject jAutoExt){ + S3JniAutoExtension * ax;; + if( S3JniGlobal.autoExt.isRunning ) return JNI_FALSE; + for( ax = S3JniGlobal.autoExt.pHead; ax; ax = ax->pNext ){ + if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ + S3JniAutoExtension_free(env, ax); + return JNI_TRUE; + } + } + return JNI_FALSE; +} +#endif /* S3JNI_ENABLE_AUTOEXT */ + + +/** + Wrapper for sqlite3_close(_v2)(). +*/ +static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ + int rc = 0; + S3JniDb * ps = 0; + assert(version == 1 || version == 2); + ps = S3JniDb_for_db(env, jDb, 0, 0); + if(ps){ + //MARKER(("close()ing db@%p\n", ps->pDb)); + rc = 1==version ? (jint)sqlite3_close(ps->pDb) : (jint)sqlite3_close_v2(ps->pDb); + S3JniDb_set_aside(ps) + /* MUST come after close() because of ps->trace. */; + NativePointerHolder_set(env, jDb, 0, S3JniClassNames.sqlite3); + } + return (jint)rc; +} + +FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(perDb) +JDECL(jint,1close_1v2)(JENV_CSELF, jobject pDb){ + return s3jni_close_db(env, pDb, 2); +} + +FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(perDb) +JDECL(jint,1close)(JENV_CSELF, jobject pDb){ + return s3jni_close_db(env, pDb, 1); +} + +/** + Assumes z is an array of unsigned short and returns the index in + that array of the first element with the value 0. +*/ +static unsigned int s3jni_utf16_strlen(void const * z){ + unsigned int i = 0; + const unsigned short * p = z; + while( p[i] ) ++i; + return i; +} + +/** + sqlite3_collation_needed16() hook impl. + */ +static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, + int eTextRep, const void * z16Name){ + S3JniDb * const ps = pState; + JNIEnv * const env = ps->env; + unsigned int const nName = s3jni_utf16_strlen(z16Name); + jstring jName = (*env)->NewString(env, (jchar const *)z16Name, nName); + IFTHREW{ + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + EXCEPTION_CLEAR; + }else{ + (*env)->CallVoidMethod(env, ps->collationNeeded.jObj, + ps->collationNeeded.midCallback, + ps->jDb, (jint)eTextRep, jName); + IFTHREW{ + s3jni_db_exception(env, ps, 0, "collation-needed callback threw"); + } + } + UNREF_L(jName); +} + +FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(perDb) +JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + jclass klazz; + jobject pOld = 0; + jmethodID xCallback; + S3JniHook * const pHook = &ps->collationNeeded; + int rc; + + if( !ps ) return SQLITE_MISUSE; + pOld = pHook->jObj; + if(pOld && jHook && + (*env)->IsSameObject(env, pOld, jHook)){ + return 0; + } + if( !jHook ){ + UNREF_G(pOld); + memset(pHook, 0, sizeof(S3JniHook)); + sqlite3_collation_needed(ps->pDb, 0, 0); + return 0; + } + klazz = (*env)->GetObjectClass(env, jHook); + xCallback = (*env)->GetMethodID(env, klazz, "xCollationNeeded", + "(Lorg/sqlite/jni/sqlite3;ILjava/lang/String;)I"); + IFTHREW { + rc = s3jni_db_exception(env, ps, SQLITE_MISUSE, + "Cannot not find matching callback on " + "collation-needed hook object."); + }else{ + pHook->midCallback = xCallback; + pHook->jObj = REF_G(jHook); + UNREF_G(pOld); + rc = sqlite3_collation_needed16(ps->pDb, ps, s3jni_collation_needed_impl16); + } + return rc; +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jbyteArray,1column_1blob)(JENV_CSELF, jobject jpStmt, + jint ndx){ + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); + void const * const p = sqlite3_column_blob(pStmt, (int)ndx); + int const n = p ? sqlite3_column_bytes(pStmt, (int)ndx) : 0; + if( 0==p ) return NULL; + else{ + jbyteArray const jba = (*env)->NewByteArray(env, n); + (*env)->SetByteArrayRegion(env, jba, 0, n, (const jbyte *)p); + return jba; + } +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jdouble,1column_1double)(JENV_CSELF, jobject jpStmt, + jint ndx){ + return (jdouble)sqlite3_column_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jint,1column_1int)(JENV_CSELF, jobject jpStmt, + jint ndx){ + return (jint)sqlite3_column_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jlong,1column_1int64)(JENV_CSELF, jobject jpStmt, + jint ndx){ + return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jbyteArray,1column_1text)(JENV_CSELF, jobject jpStmt, + jint ndx){ + sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); + const int n = sqlite3_column_bytes(stmt, (int)ndx); + const unsigned char * const p = sqlite3_column_text(stmt, (int)ndx); + return s3jni_new_jbyteArray(env, p, n); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jstring,1column_1text16)(JENV_CSELF, jobject jpStmt, + jint ndx){ + sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); + const int n = sqlite3_column_bytes16(stmt, (int)ndx); + const void * const p = sqlite3_column_text16(stmt, (int)ndx); + return s3jni_text16_to_jstring(env, p, n); +} + +FIXME_THREADING(S3JniEnvCache) +JDECL(jobject,1column_1value)(JENV_CSELF, jobject jpStmt, + jint ndx){ + sqlite3_value * const sv = sqlite3_column_value(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); + return new_sqlite3_value_wrapper(env, sv); +} + + +static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){ + JNIEnv * const env = ps->env; + int rc = isCommit + ? (int)(*env)->CallIntMethod(env, ps->commitHook.jObj, + ps->commitHook.midCallback) + : (int)((*env)->CallVoidMethod(env, ps->rollbackHook.jObj, + ps->rollbackHook.midCallback), 0); + IFTHREW{ + EXCEPTION_CLEAR; + rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "hook callback threw."); + } + return rc; +} + +static int s3jni_commit_hook_impl(void *pP){ + return s3jni_commit_rollback_hook_impl(1, pP); +} + +static void s3jni_rollback_hook_impl(void *pP){ + (void)s3jni_commit_rollback_hook_impl(0, pP); +} + +FIXME_THREADING(perDb) +static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,jobject jDb, + jobject jHook){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + jclass klazz; + jobject pOld = 0; + jmethodID xCallback; + S3JniHook * const pHook = isCommit ? &ps->commitHook : &ps->rollbackHook; + if(!ps){ + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + return 0; + } + pOld = pHook->jObj; + if(pOld && jHook && + (*env)->IsSameObject(env, pOld, jHook)){ + return pOld; + } + if( !jHook ){ + if(pOld){ + jobject tmp = REF_L(pOld); + UNREF_G(pOld); + pOld = tmp; + } + memset(pHook, 0, sizeof(S3JniHook)); + if( isCommit ) sqlite3_commit_hook(ps->pDb, 0, 0); + else sqlite3_rollback_hook(ps->pDb, 0, 0); + return pOld; + } + klazz = (*env)->GetObjectClass(env, jHook); + xCallback = (*env)->GetMethodID(env, klazz, + isCommit ? "xCommitHook" : "xRollbackHook", + isCommit ? "()I" : "()V"); + IFTHREW { + EXCEPTION_REPORT; + EXCEPTION_CLEAR; + s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Cannot not find matching callback on " + "hook object."); + }else{ + pHook->midCallback = xCallback; + pHook->jObj = REF_G(jHook); + if( isCommit ) sqlite3_commit_hook(ps->pDb, s3jni_commit_hook_impl, ps); + else sqlite3_rollback_hook(ps->pDb, s3jni_rollback_hook_impl, ps); + if(pOld){ + jobject tmp = REF_L(pOld); + UNREF_G(pOld); + pOld = tmp; + } + } + return pOld; +} + +JDECL(jobject,1commit_1hook)(JENV_CSELF,jobject jDb, jobject jHook){ + return s3jni_commit_rollback_hook(1, env, jDb, jHook); +} + + +JDECL(jstring,1compileoption_1get)(JENV_CSELF, jint n){ + return (*env)->NewStringUTF( env, sqlite3_compileoption_get(n) ); +} + +JDECL(jboolean,1compileoption_1used)(JENV_CSELF, jstring name){ + const char *zUtf8 = JSTR_TOC(name); + const jboolean rc = + 0==sqlite3_compileoption_used(zUtf8) ? JNI_FALSE : JNI_TRUE; + JSTR_RELEASE(name, zUtf8); + return rc; +} + +FIXME_THREADING(perDb) +JDECL(jobject,1context_1db_1handle)(JENV_CSELF, jobject jpCx){ + sqlite3 * const pDb = sqlite3_context_db_handle(PtrGet_sqlite3_context(jpCx)); + S3JniDb * const ps = pDb ? S3JniDb_for_db(env, 0, pDb, 0) : 0; + return ps ? ps->jDb : 0; +} + +JDECL(jint,1create_1collation)(JENV_CSELF, jobject jDb, + jstring name, jint eTextRep, + jobject oCollation){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + jclass klazz; + int rc; + const char *zName; + S3JniHook * pHook; + if(!ps) return (jint)SQLITE_NOMEM; + pHook = &ps->collation; + klazz = (*env)->GetObjectClass(env, oCollation); + pHook->midCallback = (*env)->GetMethodID(env, klazz, "xCompare", + "([B[B)I"); + IFTHREW{ + EXCEPTION_REPORT; + return s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Could not get xCompare() method for object."); + } + zName = JSTR_TOC(name); + rc = sqlite3_create_collation_v2(ps->pDb, zName, (int)eTextRep, + ps, CollationState_xCompare, + CollationState_xDestroy); + JSTR_RELEASE(name, zName); + if( 0==rc ){ + pHook->jObj = REF_G(oCollation); + pHook->klazz = REF_G(klazz); + }else{ + S3JniHook_unref(env, pHook, 1); + } + return (jint)rc; +} + +static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName, + jint nArg, jint eTextRep, jobject jFunctor){ + S3JniUdf * s = 0; + int rc; + sqlite3 * const pDb = PtrGet_sqlite3(jDb); + const char * zFuncName = 0; + + if( !encodingTypeIsValid(eTextRep) ){ + return s3jni_db_error(pDb, SQLITE_FORMAT, + "Invalid function encoding option."); + } + s = S3JniUdf_alloc(env, jFunctor); + if( !s ) return SQLITE_NOMEM; + else if( UDF_UNKNOWN_TYPE==s->type ){ + rc = s3jni_db_error(pDb, SQLITE_MISUSE, + "Cannot unambiguously determine function type."); + S3JniUdf_free(s); + goto error_cleanup; + } + zFuncName = JSTR_TOC(jFuncName); + if(!zFuncName){ + rc = SQLITE_NOMEM; + S3JniUdf_free(s); + goto error_cleanup; + } + if( UDF_WINDOW == s->type ){ + rc = sqlite3_create_window_function(pDb, zFuncName, nArg, eTextRep, s, + udf_xStep, udf_xFinal, udf_xValue, + udf_xInverse, S3JniUdf_finalizer); + }else{ + udf_xFunc_f xFunc = 0; + udf_xStep_f xStep = 0; + udf_xFinal_f xFinal = 0; + if( UDF_SCALAR == s->type ){ + xFunc = udf_xFunc; + }else{ + assert( UDF_AGGREGATE == s->type ); + xStep = udf_xStep; + xFinal = udf_xFinal; + } + rc = sqlite3_create_function_v2(pDb, zFuncName, nArg, eTextRep, s, + xFunc, xStep, xFinal, S3JniUdf_finalizer); + } + if( 0==rc ){ + s->zFuncName = sqlite3_mprintf("%s", zFuncName) + /* OOM here is non-fatal. Ignore it. Handling it would require + re-calling the appropriate create_function() func with 0 + for all xAbc args so that s would be finalized. */; + } +error_cleanup: + JSTR_RELEASE(jFuncName, zFuncName); + /* on create_function() error, s will be destroyed via create_function() */ + return (jint)rc; +} + +JDECL(jint,1create_1function)(JENV_CSELF, jobject jDb, jstring jFuncName, + jint nArg, jint eTextRep, jobject jFunctor){ + return create_function(env, jDb, jFuncName, nArg, eTextRep, jFunctor); +} + +/* sqlite3_db_config() for (int,const char *) */ +JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( + JENV_CSELF, jobject jDb, jint op, jstring jStr +){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + int rc; + char *zStr; + + switch( (ps && jStr) ? op : 0 ){ + case SQLITE_DBCONFIG_MAINDBNAME: + zStr = s3jni_jstring_to_utf8(S3JniGlobal_env_cache(env), jStr, 0); + if( zStr ){ + rc = sqlite3_db_config(ps->pDb, (int)op, zStr); + if( rc ){ + sqlite3_free( zStr ); + }else{ + sqlite3_free( ps->zMainDbName ); + ps->zMainDbName = zStr; + } + }else{ + rc = SQLITE_NOMEM; + } + break; + default: + rc = SQLITE_MISUSE; + } + return rc; +} + +FIXME_THREADING(perDb) +/* sqlite3_db_config() for (int,int*) */ +/* ACHTUNG: openjdk v19 creates a different mangled name for this + function than openjdk v8 does. */ +JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)( + JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut +){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + int rc; + switch( ps ? op : 0 ){ + case SQLITE_DBCONFIG_ENABLE_FKEY: + case SQLITE_DBCONFIG_ENABLE_TRIGGER: + case SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: + case SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: + case SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: + case SQLITE_DBCONFIG_ENABLE_QPSG: + case SQLITE_DBCONFIG_TRIGGER_EQP: + case SQLITE_DBCONFIG_RESET_DATABASE: + case SQLITE_DBCONFIG_DEFENSIVE: + case SQLITE_DBCONFIG_WRITABLE_SCHEMA: + case SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: + case SQLITE_DBCONFIG_DQS_DML: + case SQLITE_DBCONFIG_DQS_DDL: + case SQLITE_DBCONFIG_ENABLE_VIEW: + case SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: + case SQLITE_DBCONFIG_TRUSTED_SCHEMA: + case SQLITE_DBCONFIG_STMT_SCANSTATUS: + case SQLITE_DBCONFIG_REVERSE_SCANORDER: { + int pOut = 0; + rc = sqlite3_db_config( ps->pDb, (int)op, onOff, &pOut ); + if( 0==rc && jOut ){ + OutputPointer_set_Int32(env, jOut, pOut); + } + break; + } + default: + rc = SQLITE_MISUSE; + } + return (jint)rc; +} + +/** + This is a workaround for openjdk v19 (and possibly others) encoding + this function's name differently than JDK v8 does. If we do not + install both names for this function then Java will not be able to + find the function in both environments. +*/ +JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_00024Int32_2)( + JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut +){ + return JFuncName(1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)( + env, jKlazz, jDb, op, onOff, jOut + ); +} + +JDECL(jstring,1db_1filename)(JENV_CSELF, jobject jDb, jstring jDbName){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + char *zDbName; + jstring jRv = 0; + int nStr = 0; + + if( !ps || !jDbName ){ + return 0; + } + zDbName = s3jni_jstring_to_utf8(jc, jDbName, &nStr); + if( zDbName ){ + char const * zRv = sqlite3_db_filename(ps->pDb, zDbName); + sqlite3_free(zDbName); + if( zRv ){ + jRv = s3jni_utf8_to_jstring(jc, zRv, -1); + } + } + return jRv; +} + + +JDECL(jint,1db_1status)(JENV_CSELF, jobject jDb, jint op, jobject jOutCurrent, + jobject jOutHigh, jboolean reset ){ + int iCur = 0, iHigh = 0; + sqlite3 * const pDb = PtrGet_sqlite3(jDb); + int rc = sqlite3_db_status( pDb, op, &iCur, &iHigh, reset ); + if( 0==rc ){ + OutputPointer_set_Int32(env, jOutCurrent, iCur); + OutputPointer_set_Int32(env, jOutHigh, iHigh); + } + return (jint)rc; +} + + +JDECL(jint,1errcode)(JENV_CSELF, jobject jpDb){ + sqlite3 * const pDb = PtrGet_sqlite3(jpDb); + return pDb ? sqlite3_errcode(pDb) : SQLITE_MISUSE; +} + +JDECL(jstring,1errmsg)(JENV_CSELF, jobject jpDb){ + sqlite3 * const pDb = PtrGet_sqlite3(jpDb); + S3JniEnvCache * const jc = pDb ? S3JniGlobal_env_cache(env) : 0; + return jc ? s3jni_utf8_to_jstring(jc, sqlite3_errmsg(pDb), -1) : 0; +} + +JDECL(jstring,1errstr)(JENV_CSELF, jint rcCode){ + return (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode)) + /* We know these values to be plain ASCII, so pose no + MUTF-8 incompatibility */; +} + +JDECL(jstring,1expanded_1sql)(JENV_CSELF, jobject jpStmt){ + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); + jstring rv = 0; + if( pStmt ){ + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + char * zSql = sqlite3_expanded_sql(pStmt); + OOM_CHECK(zSql); + if( zSql ){ + rv = s3jni_utf8_to_jstring(jc, zSql, -1); + sqlite3_free(zSql); + } + } + return rv; +} + +JDECL(jboolean,1extended_1result_1codes)(JENV_CSELF, jobject jpDb, + jboolean onoff){ + int const rc = sqlite3_extended_result_codes(PtrGet_sqlite3(jpDb), onoff ? 1 : 0); + return rc ? JNI_TRUE : JNI_FALSE; +} + +JDECL(jint,1initialize)(JENV_CSELF){ + return sqlite3_initialize(); +} + +JDECL(jint,1finalize)(JENV_CSELF, jobject jpStmt){ + int rc = 0; + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); + if( pStmt ){ + rc = sqlite3_finalize(pStmt); + NativePointerHolder_set(env, jpStmt, 0, S3JniClassNames.sqlite3_stmt); + } + return rc; +} + + +JDECL(jlong,1last_1insert_1rowid)(JENV_CSELF, jobject jpDb){ + return (jlong)sqlite3_last_insert_rowid(PtrGet_sqlite3(jpDb)); +} + +//! Pre-open() code common to sqlite3_open(_v2)(). +static int s3jni_open_pre(JNIEnv * const env, S3JniEnvCache **jc, + jstring jDbName, char **zDbName, + S3JniDb ** ps, jobject *jDb){ + *jc = S3JniGlobal_env_cache(env); + if(!*jc) return SQLITE_NOMEM; + *zDbName = jDbName ? s3jni_jstring_to_utf8(*jc, jDbName, 0) : 0; + if(jDbName && !*zDbName) return SQLITE_NOMEM; + *jDb = new_sqlite3_wrapper(env, 0); + if( !*jDb ){ + sqlite3_free(*zDbName); + *zDbName = 0; + return SQLITE_NOMEM; + } + *ps = S3JniDb_alloc(env, 0, *jDb); +#if S3JNI_ENABLE_AUTOEXT + if(*ps){ + assert(!S3JniGlobal.autoExt.psOpening); + S3JniGlobal.autoExt.psOpening = *ps; + } +#endif + //MARKER(("pre-open ps@%p\n", *ps)); + return *ps ? 0 : SQLITE_NOMEM; +} + +/** + Post-open() code common to both the sqlite3_open() and + sqlite3_open_v2() bindings. ps->jDb must be the + org.sqlite.jni.sqlite3 object which will hold the db's native + pointer. theRc must be the result code of the open() op. If + *ppDb is NULL then ps is set aside and its state cleared, + else ps is associated with *ppDb. If *ppDb is not NULL then + ps->jDb is stored in jOut (an OutputPointer.sqlite3 instance). + + Returns theRc. +*/ +static int s3jni_open_post(JNIEnv * const env, S3JniDb * ps, + sqlite3 **ppDb, jobject jOut, int theRc){ + //MARKER(("post-open() ps@%p db@%p\n", ps, *ppDb)); +#if S3JNI_ENABLE_AUTOEXT + assert( S3JniGlobal.autoExt.pHead ? ps!=S3JniGlobal.autoExt.psOpening : 1 ); + S3JniGlobal.autoExt.psOpening = 0; +#endif + if(*ppDb){ + assert(ps->jDb); +#if S3JNI_ENABLE_AUTOEXT + //MARKER(("*autoExt.pHead=%p, ppDb=%p, ps->pDb=%p\n", S3JniGlobal.autoExt.pHead, *ppDb, ps->pDb)); + // invalid when an autoext triggers another open(): + // assert( S3JniGlobal.autoExt.pHead ? *ppDb==ps->pDb : 0==ps->pDb ); +#endif + ps->pDb = *ppDb; + NativePointerHolder_set(env, ps->jDb, *ppDb, S3JniClassNames.sqlite3); + }else{ + S3JniDb_set_aside(ps); + ps = 0; + } + OutputPointer_set_sqlite3(env, jOut, ps ? ps->jDb : 0); + return theRc; +} + +JDECL(jint,1open)(JENV_CSELF, jstring strName, jobject jOut){ + sqlite3 * pOut = 0; + char *zName = 0; + jobject jDb = 0; + S3JniDb * ps = 0; + S3JniEnvCache * jc = 0; + S3JniDb * const prevOpening = S3JniGlobal.autoExt.psOpening; + int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); + if( 0==rc ){ + rc = sqlite3_open(zName, &pOut); + //MARKER(("env=%p, *env=%p\n", env, *env)); + //MARKER(("open() ps@%p db@%p\n", ps, pOut)); + rc = s3jni_open_post(env, ps, &pOut, jOut, rc); + assert(rc==0 ? pOut!=0 : 1); + sqlite3_free(zName); + } + S3JniGlobal.autoExt.psOpening = prevOpening; + return (jint)rc; +} + +JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName, + jobject jOut, jint flags, jstring strVfs){ + sqlite3 * pOut = 0; + char *zName = 0; + jobject jDb = 0; + S3JniDb * ps = 0; + S3JniEnvCache * jc = 0; + char *zVfs = 0; + S3JniDb * const prevOpening = S3JniGlobal.autoExt.psOpening; + int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); + if( 0==rc && strVfs ){ + zVfs = s3jni_jstring_to_utf8(jc, strVfs, 0); + if( !zVfs ){ + rc = SQLITE_NOMEM; + } + } + if( 0==rc ){ + rc = sqlite3_open_v2(zName, &pOut, (int)flags, zVfs); + } + //MARKER(("open_v2() ps@%p db@%p\n", ps, pOut)); + /*MARKER(("zName=%s, zVfs=%s, pOut=%p, flags=%d, nrc=%d\n", + zName, zVfs, pOut, (int)flags, nrc));*/ + rc = s3jni_open_post(env, ps, &pOut, jOut, rc); + assert(rc==0 ? pOut!=0 : 1); + sqlite3_free(zName); + sqlite3_free(zVfs); + S3JniGlobal.autoExt.psOpening = prevOpening; + return (jint)rc; +} + +/* Proxy for the sqlite3_prepare[_v2/3]() family. */ +static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv * const env, jclass self, + jobject jDb, jbyteArray baSql, + jint nMax, jint prepFlags, + jobject jOutStmt, jobject outTail){ + sqlite3_stmt * pStmt = 0; + jobject jStmt = 0; + const char * zTail = 0; + jbyte * const pBuf = JBA_TOC(baSql); + int rc = SQLITE_ERROR; + assert(prepVersion==1 || prepVersion==2 || prepVersion==3); + if( !pBuf ){ + rc = baSql ? SQLITE_MISUSE : SQLITE_NOMEM; + goto end; + } + jStmt = new_sqlite3_stmt_wrapper(env, 0); + if( !jStmt ){ + rc = SQLITE_NOMEM; + goto end; + } + switch( prepVersion ){ + case 1: rc = sqlite3_prepare(PtrGet_sqlite3(jDb), (const char *)pBuf, + (int)nMax, &pStmt, &zTail); + break; + case 2: rc = sqlite3_prepare_v2(PtrGet_sqlite3(jDb), (const char *)pBuf, + (int)nMax, &pStmt, &zTail); + break; + case 3: rc = sqlite3_prepare_v3(PtrGet_sqlite3(jDb), (const char *)pBuf, + (int)nMax, (unsigned int)prepFlags, + &pStmt, &zTail); + break; + default: + assert(0 && "Invalid prepare() version"); + } +end: + JBA_RELEASE(baSql,pBuf); + if( 0==rc ){ + if( 0!=outTail ){ + /* Noting that pBuf is deallocated now but its address is all we need. */ + assert(zTail ? ((void*)zTail>=(void*)pBuf) : 1); + assert(zTail ? (((int)((void*)zTail - (void*)pBuf)) >= 0) : 1); + OutputPointer_set_Int32(env, outTail, (int)(zTail ? (zTail - (const char *)pBuf) : 0)); + } + if( pStmt ){ + NativePointerHolder_set(env, jStmt, pStmt, S3JniClassNames.sqlite3_stmt); + }else{ + /* Happens for comments and whitespace */ + UNREF_L(jStmt); + jStmt = 0; + } + }else{ + UNREF_L(jStmt); + jStmt = 0; + } +#if 0 + if( 0!=rc ){ + MARKER(("prepare rc = %d\n", rc)); + } +#endif + OutputPointer_set_sqlite3_stmt(env, jOutStmt, jStmt); + return (jint)rc; +} +JDECL(jint,1prepare)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql, + jint nMax, jobject jOutStmt, jobject outTail){ + return sqlite3_jni_prepare_v123(1, env, self, jDb, baSql, nMax, 0, + jOutStmt, outTail); +} +JDECL(jint,1prepare_1v2)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql, + jint nMax, jobject jOutStmt, jobject outTail){ + return sqlite3_jni_prepare_v123(2, env, self, jDb, baSql, nMax, 0, + jOutStmt, outTail); +} +JDECL(jint,1prepare_1v3)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql, + jint nMax, jint prepFlags, jobject jOutStmt, jobject outTail){ + return sqlite3_jni_prepare_v123(3, env, self, jDb, baSql, nMax, + prepFlags, jOutStmt, outTail); +} + + +static int s3jni_progress_handler_impl(void *pP){ + S3JniDb * const ps = (S3JniDb *)pP; + JNIEnv * const env = ps->env; + int rc = (int)(*env)->CallIntMethod(env, ps->progress.jObj, + ps->progress.midCallback); + IFTHREW{ + rc = s3jni_db_exception(env, ps, rc, + "sqlite3_progress_handler() callback threw"); + } + return rc; +} + +JDECL(void,1progress_1handler)(JENV_CSELF,jobject jDb, jint n, jobject jProgress){ + S3JniDb * ps = S3JniDb_for_db(env, jDb, 0, 0); + jclass klazz; + jmethodID xCallback; + if( n<1 || !jProgress ){ + if(ps){ + UNREF_G(ps->progress.jObj); + memset(&ps->progress, 0, sizeof(ps->progress)); + } + sqlite3_progress_handler(ps->pDb, 0, 0, 0); + return; + } + if(!ps){ + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + return; + } + klazz = (*env)->GetObjectClass(env, jProgress); + xCallback = (*env)->GetMethodID(env, klazz, "xCallback", "()I"); + IFTHREW { + EXCEPTION_CLEAR; + s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Cannot not find matching xCallback() on " + "ProgressHandler object."); + }else{ + UNREF_G(ps->progress.jObj); + ps->progress.midCallback = xCallback; + ps->progress.jObj = REF_G(jProgress); + sqlite3_progress_handler(ps->pDb, (int)n, s3jni_progress_handler_impl, ps); + } +} + + +JDECL(jint,1reset)(JENV_CSELF, jobject jpStmt){ + int rc = 0; + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); + if( pStmt ){ + rc = sqlite3_reset(pStmt); + } + return rc; +} + +#if S3JNI_ENABLE_AUTOEXT +JDECL(void,1reset_1auto_1extension)(JENV_CSELF){ + if(!S3JniGlobal.autoExt.isRunning){ + while( S3JniGlobal.autoExt.pHead ){ + S3JniAutoExtension_free(env, S3JniGlobal.autoExt.pHead); + } + } +} +#endif /* S3JNI_ENABLE_AUTOEXT */ + +/* sqlite3_result_text/blob() and friends. */ +static void result_blob_text(int asBlob, int as64, + int eTextRep/*only for (asBlob=0)*/, + JNIEnv * const env, sqlite3_context *pCx, + jbyteArray jBa, jlong nMax){ + if(jBa){ + jbyte * const pBuf = JBA_TOC(jBa); + jsize nBa = (*env)->GetArrayLength(env, jBa); + if( nMax>=0 && nBa>(jsize)nMax ){ + nBa = (jsize)nMax; + /** + From the sqlite docs: + + > If the 3rd parameter to any of the sqlite3_result_text* + interfaces other than sqlite3_result_text64() is negative, + then SQLite computes the string length itself by searching + the 2nd parameter for the first zero character. + + Note that the text64() interfaces take an unsigned value for + the length, which Java does not support. This binding takes + the approach of passing on negative values to the C API, + which will, in turn fail with SQLITE_TOOBIG at some later + point (recall that the sqlite3_result_xyz() family do not + have result values). + */ + } + if(as64){ /* 64-bit... */ + static const jsize nLimit64 = + SQLITE_MAX_ALLOCATION_SIZE/*only _kinda_ arbitrary!*/ + /* jsize is int32, not int64! */; + if(nBa > nLimit64){ + sqlite3_result_error_toobig(pCx); + }else if(asBlob){ + sqlite3_result_blob64(pCx, pBuf, (sqlite3_uint64)nBa, + SQLITE_TRANSIENT); + }else{ /* text64... */ + if(encodingTypeIsValid(eTextRep)){ + sqlite3_result_text64(pCx, (const char *)pBuf, + (sqlite3_uint64)nBa, + SQLITE_TRANSIENT, eTextRep); + }else{ + sqlite3_result_error_code(pCx, SQLITE_FORMAT); + } + } + }else{ /* 32-bit... */ + static const jsize nLimit = SQLITE_MAX_ALLOCATION_SIZE; + if(nBa > nLimit){ + sqlite3_result_error_toobig(pCx); + }else if(asBlob){ + sqlite3_result_blob(pCx, pBuf, (int)nBa, + SQLITE_TRANSIENT); + }else{ + switch(eTextRep){ + case SQLITE_UTF8: + sqlite3_result_text(pCx, (const char *)pBuf, (int)nBa, + SQLITE_TRANSIENT); + break; + case SQLITE_UTF16: + sqlite3_result_text16(pCx, (const char *)pBuf, (int)nBa, + SQLITE_TRANSIENT); + break; + case SQLITE_UTF16LE: + sqlite3_result_text16le(pCx, (const char *)pBuf, (int)nBa, + SQLITE_TRANSIENT); + break; + case SQLITE_UTF16BE: + sqlite3_result_text16be(pCx, (const char *)pBuf, (int)nBa, + SQLITE_TRANSIENT); + break; + } + } + JBA_RELEASE(jBa, pBuf); + } + }else{ + sqlite3_result_null(pCx); + } +} + +JDECL(void,1result_1blob)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jint nMax){ + return result_blob_text(1, 0, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +} + +JDECL(void,1result_1blob64)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jlong nMax){ + return result_blob_text(1, 1, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +} + +JDECL(void,1result_1double)(JENV_CSELF, jobject jpCx, jdouble v){ + sqlite3_result_double(PtrGet_sqlite3_context(jpCx), v); +} + +JDECL(void,1result_1error)(JENV_CSELF, jobject jpCx, jbyteArray baMsg, + int eTextRep){ + const char * zUnspecified = "Unspecified error."; + jsize const baLen = (*env)->GetArrayLength(env, baMsg); + jbyte * const pjBuf = baMsg ? JBA_TOC(baMsg) : NULL; + switch(pjBuf ? eTextRep : SQLITE_UTF8){ + case SQLITE_UTF8: { + const char *zMsg = pjBuf ? (const char *)pjBuf : zUnspecified; + sqlite3_result_error(PtrGet_sqlite3_context(jpCx), zMsg, (int)baLen); + break; + } + case SQLITE_UTF16: { + const void *zMsg = pjBuf + ? (const void *)pjBuf : (const void *)zUnspecified; + sqlite3_result_error16(PtrGet_sqlite3_context(jpCx), zMsg, (int)baLen); + break; + } + default: + sqlite3_result_error(PtrGet_sqlite3_context(jpCx), + "Invalid encoding argument passed " + "to sqlite3_result_error().", -1); + break; + } + JBA_RELEASE(baMsg,pjBuf); +} + +JDECL(void,1result_1error_1code)(JENV_CSELF, jobject jpCx, jint v){ + sqlite3_result_error_code(PtrGet_sqlite3_context(jpCx), v ? (int)v : SQLITE_ERROR); +} + +JDECL(void,1result_1error_1nomem)(JENV_CSELF, jobject jpCx){ + sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx)); +} + +JDECL(void,1result_1error_1toobig)(JENV_CSELF, jobject jpCx){ + sqlite3_result_error_toobig(PtrGet_sqlite3_context(jpCx)); +} + +JDECL(void,1result_1int)(JENV_CSELF, jobject jpCx, jint v){ + sqlite3_result_int(PtrGet_sqlite3_context(jpCx), (int)v); +} + +JDECL(void,1result_1int64)(JENV_CSELF, jobject jpCx, jlong v){ + sqlite3_result_int64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v); +} + +JDECL(void,1result_1java_1object)(JENV_CSELF, jobject jpCx, jobject v){ + if(v){ + ResultJavaVal * const rjv = ResultJavaVal_alloc(env, v); + if(rjv){ + sqlite3_result_pointer(PtrGet_sqlite3_context(jpCx), rjv, RESULT_JAVA_VAL_STRING, + ResultJavaVal_finalizer); + }else{ + sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx)); + } + }else{ + sqlite3_result_null(PtrGet_sqlite3_context(jpCx)); + } +} + +JDECL(void,1result_1null)(JENV_CSELF, jobject jpCx){ + sqlite3_result_null(PtrGet_sqlite3_context(jpCx)); +} + +JDECL(void,1result_1text)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jint nMax){ + return result_blob_text(0, 0, SQLITE_UTF8, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +} + +JDECL(void,1result_1text64)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jlong nMax, + jint eTextRep){ + return result_blob_text(0, 1, eTextRep, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +} + +JDECL(void,1result_1value)(JENV_CSELF, jobject jpCx, jobject jpSVal){ + sqlite3_result_value(PtrGet_sqlite3_context(jpCx), PtrGet_sqlite3_value(jpSVal)); +} + +JDECL(void,1result_1zeroblob)(JENV_CSELF, jobject jpCx, jint v){ + sqlite3_result_zeroblob(PtrGet_sqlite3_context(jpCx), (int)v); +} + +JDECL(jint,1result_1zeroblob64)(JENV_CSELF, jobject jpCx, jlong v){ + return (jint)sqlite3_result_zeroblob64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v); +} + +JDECL(jobject,1rollback_1hook)(JENV_CSELF,jobject jDb, jobject jHook){ + return s3jni_commit_rollback_hook(0, env, jDb, jHook); +} + +/* sqlite3_set_authorizer() callback proxy. */ +static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1, + const char*z2,const char*z3){ + S3JniDb * const ps = pState; + JNIEnv * const env = ps->env; + jstring const s0 = z0 ? (*env)->NewStringUTF(env, z0) : 0; + jstring const s1 = z1 ? (*env)->NewStringUTF(env, z1) : 0; + jstring const s2 = z2 ? (*env)->NewStringUTF(env, z2) : 0; + jstring const s3 = z3 ? (*env)->NewStringUTF(env, z3) : 0; + S3JniHook const * const pHook = &ps->authHook; + int rc; + + assert( pHook->jObj ); + rc = (*env)->CallIntMethod(env, pHook->jObj, pHook->midCallback, (jint)op, + s0, s1, s3, s3); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW("sqlite3_set_authorizer() callback"); + EXCEPTION_CLEAR; + } + UNREF_L(s0); + UNREF_L(s1); + UNREF_L(s2); + UNREF_L(s3); + return rc; +} + +JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniHook * const pHook = ps ? &ps->authHook : 0; + + if( !ps ) return SQLITE_MISUSE; + else if( !jHook ){ + S3JniHook_unref(env, pHook, 0); + return (jint)sqlite3_set_authorizer( ps->pDb, 0, 0 ); + }else{ + int rc = 0; + if( pHook->jObj ){ + if( (*env)->IsSameObject(env, pHook->jObj, jHook) ){ + /* Same object - this is a no-op. */ + return 0; + } + S3JniHook_unref(env, pHook, 0); + } + pHook->jObj = REF_G(jHook); + pHook->klazz = REF_G((*env)->GetObjectClass(env, jHook)); + pHook->midCallback = (*env)->GetMethodID(env, pHook->klazz, + "xAuth", + "(I" + "Ljava/lang/String;" + "Ljava/lang/String;" + "Ljava/lang/String;" + "Ljava/lang/String;" + ")I"); + IFTHREW { + S3JniHook_unref(env, pHook, 0); + return s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Error setting up Java parts of authorizer hook."); + } + rc = sqlite3_set_authorizer(ps->pDb, s3jni_xAuth, ps); + if( rc ) S3JniHook_unref(env, pHook, 0); + return rc; + } +} + + +JDECL(void,1set_1last_1insert_1rowid)(JENV_CSELF, jobject jpDb, jlong rowId){ + sqlite3_set_last_insert_rowid(PtrGet_sqlite3_context(jpDb), + (sqlite3_int64)rowId); +} + +FIXME_THREADING(nphCache) +JDECL(jint,1status)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh, + jboolean reset ){ + int iCur = 0, iHigh = 0; + int rc = sqlite3_status( op, &iCur, &iHigh, reset ); + if( 0==rc ){ + OutputPointer_set_Int32(env, jOutCurrent, iCur); + OutputPointer_set_Int32(env, jOutHigh, iHigh); + } + return (jint)rc; +} + +FIXME_THREADING(nphCache) +JDECL(jint,1status64)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh, + jboolean reset ){ + sqlite3_int64 iCur = 0, iHigh = 0; + int rc = sqlite3_status64( op, &iCur, &iHigh, reset ); + if( 0==rc ){ + OutputPointer_set_Int64(env, jOutCurrent, iCur); + OutputPointer_set_Int64(env, jOutHigh, iHigh); + } + return (jint)rc; +} + +static int s3jni_strlike_glob(int isLike, JNIEnv *const env, + jbyteArray baG, jbyteArray baT, jint escLike){ + int rc = 0; + jbyte * const pG = JBA_TOC(baG); + jbyte * const pT = pG ? JBA_TOC(baT) : 0; + OOM_CHECK(pT); + + /* Note that we're relying on the byte arrays having been + NUL-terminated on the Java side. */ + rc = isLike + ? sqlite3_strlike((const char *)pG, (const char *)pT, + (unsigned int)escLike) + : sqlite3_strglob((const char *)pG, (const char *)pT); + JBA_RELEASE(baG, pG); + JBA_RELEASE(baT, pT); + return rc; +} + +JDECL(jint,1strglob)(JENV_CSELF, jbyteArray baG, jbyteArray baT){ + return s3jni_strlike_glob(0, env, baG, baT, 0); +} + +JDECL(jint,1strlike)(JENV_CSELF, jbyteArray baG, jbyteArray baT, jint escChar){ + return s3jni_strlike_glob(1, env, baG, baT, escChar); +} + +JDECL(jint,1shutdown)(JENV_CSELF){ + S3JniGlobal_S3JniEnvCache_clear(); + /* Do not clear S3JniGlobal.jvm: it's legal to call + sqlite3_initialize() again to restart the lib. */ + return sqlite3_shutdown(); +} + +JDECL(jstring,1sql)(JENV_CSELF, jobject jpStmt){ + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); + jstring rv = 0; + if( pStmt ){ + const char * zSql = 0; + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + zSql = sqlite3_sql(pStmt); + rv = s3jni_utf8_to_jstring(jc, zSql, -1); + OOM_CHECK(rv); + } + return rv; +} + +JDECL(jint,1step)(JENV_CSELF,jobject jStmt){ + int rc = SQLITE_MISUSE; + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt); + if(pStmt){ + rc = sqlite3_step(pStmt); + } + return rc; +} + +static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ + S3JniDb * const ps = (S3JniDb *)pC; + JNIEnv * const env = ps->env; + jobject jX = NULL /* the tracer's X arg */; + jobject jP = NULL /* the tracer's P arg */; + jobject jPUnref = NULL /* potentially a local ref to jP */; + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + int rc; + int createStmt = 0; + switch(traceflag){ + case SQLITE_TRACE_STMT: + jX = s3jni_utf8_to_jstring(jc, (const char *)pX, -1); + if(!jX) return SQLITE_NOMEM; + /*MARKER(("TRACE_STMT@%p SQL=%p / %s\n", pP, jX, (const char *)pX));*/ + createStmt = 1; + break; + case SQLITE_TRACE_PROFILE: + jX = (*env)->NewObject(env, jc->g.cLong, jc->g.ctorLong1, + (jlong)*((sqlite3_int64*)pX)); + // hmm. ^^^ (*pX) really is zero. + // MARKER(("profile time = %llu\n", *((sqlite3_int64*)pX))); + if(!jX) return SQLITE_NOMEM; + createStmt = 1; + break; + case SQLITE_TRACE_ROW: + createStmt = 1; + break; + case SQLITE_TRACE_CLOSE: + jP = ps->jDb; + break; + default: + assert(!"cannot happen - unkown trace flag"); + return SQLITE_ERROR; + } + if( createStmt ){ + jP = jPUnref = new_sqlite3_stmt_wrapper(env, pP); + if(!jP){ + UNREF_L(jX); + return SQLITE_NOMEM; + } + } + assert(jP); + rc = (int)(*env)->CallIntMethod(env, ps->trace.jObj, + ps->trace.midCallback, + (jint)traceflag, jP, jX); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW("sqlite3_trace_v2() callback"); + EXCEPTION_CLEAR; + rc = SQLITE_ERROR; + } + UNREF_L(jPUnref); + UNREF_L(jX); + return rc; +} + +JDECL(jint,1trace_1v2)(JENV_CSELF,jobject jDb, jint traceMask, jobject jTracer){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + jclass klazz; + if( !traceMask || !jTracer ){ + if(ps){ + UNREF_G(ps->trace.jObj); + memset(&ps->trace, 0, sizeof(ps->trace)); + } + return (jint)sqlite3_trace_v2(ps->pDb, 0, 0, 0); + } + if(!ps) return SQLITE_NOMEM; + klazz = (*env)->GetObjectClass(env, jTracer); + ps->trace.midCallback = (*env)->GetMethodID(env, klazz, "xCallback", + "(ILjava/lang/Object;Ljava/lang/Object;)I"); + IFTHREW { + EXCEPTION_CLEAR; + return s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Cannot not find matching xCallback() on Tracer object."); + } + ps->trace.jObj = REF_G(jTracer); + return sqlite3_trace_v2(ps->pDb, (unsigned)traceMask, s3jni_trace_impl, ps); +} + +static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, + const char *zTable, sqlite3_int64 nRowid){ + S3JniDb * const ps = pState; + JNIEnv * const env = ps->env; + /* ACHTUNG: this will break if zDb or zTable contain chars which are + different in MUTF-8 than UTF-8. That seems like a low risk, + but it's possible. */ + jstring jDbName; + jstring jTable; + jDbName = (*env)->NewStringUTF(env, zDb); + jTable = jDbName ? (*env)->NewStringUTF(env, zTable) : 0; + IFTHREW { + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + }else{ + (*env)->CallVoidMethod(env, ps->updateHook.jObj, + ps->updateHook.midCallback, + (jint)opId, jDbName, jTable, (jlong)nRowid); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW("update hook"); + EXCEPTION_CLEAR; + s3jni_db_error(ps->pDb, SQLITE_ERROR, "update hook callback threw."); + } + } + UNREF_L(jDbName); + UNREF_L(jTable); +} + + +JDECL(jobject,1update_1hook)(JENV_CSELF, jobject jDb, jobject jHook){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + jclass klazz; + jobject pOld = 0; + jmethodID xCallback; + S3JniHook * const pHook = &ps->updateHook; + if(!ps){ + s3jni_db_error(ps->pDb, SQLITE_MISUSE, 0); + return 0; + } + pOld = pHook->jObj; + if(pOld && jHook && + (*env)->IsSameObject(env, pOld, jHook)){ + return pOld; + } + if( !jHook ){ + if(pOld){ + jobject tmp = REF_L(pOld); + UNREF_G(pOld); + pOld = tmp; + } + memset(pHook, 0, sizeof(S3JniHook)); + sqlite3_update_hook(ps->pDb, 0, 0); + return pOld; + } + klazz = (*env)->GetObjectClass(env, jHook); + xCallback = (*env)->GetMethodID(env, klazz, "xUpdateHook", + "(ILjava/lang/String;Ljava/lang/String;J)V"); + IFTHREW { + EXCEPTION_CLEAR; + s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Cannot not find matching callback on " + "update hook object."); + }else{ + pHook->midCallback = xCallback; + pHook->jObj = REF_G(jHook); + sqlite3_update_hook(ps->pDb, s3jni_update_hook_impl, ps); + if(pOld){ + jobject tmp = REF_L(pOld); + UNREF_G(pOld); + pOld = tmp; + } + } + return pOld; +} + + +JDECL(jbyteArray,1value_1blob)(JENV_CSELF, jobject jpSVal){ + sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); + int const nLen = sqlite3_value_bytes(sv); + const jbyte * pBytes = sqlite3_value_blob(sv); + jbyteArray const jba = pBytes + ? (*env)->NewByteArray(env, (jsize)nLen) + : NULL; + if(jba){ + (*env)->SetByteArrayRegion(env, jba, 0, nLen, pBytes); + } + return jba; +} + + +JDECL(jdouble,1value_1double)(JENV_CSELF, jobject jpSVal){ + return (jdouble) sqlite3_value_double(PtrGet_sqlite3_value(jpSVal)); +} + + +JDECL(jobject,1value_1dup)(JENV_CSELF, jobject jpSVal){ + sqlite3_value * const sv = sqlite3_value_dup(PtrGet_sqlite3_value(jpSVal)); + return sv ? new_sqlite3_value_wrapper(env, sv) : 0; +} + +JDECL(void,1value_1free)(JENV_CSELF, jobject jpSVal){ + sqlite3_value_free(PtrGet_sqlite3_value(jpSVal)); +} + +JDECL(jint,1value_1int)(JENV_CSELF, jobject jpSVal){ + return (jint) sqlite3_value_int(PtrGet_sqlite3_value(jpSVal)); +} + +JDECL(jlong,1value_1int64)(JENV_CSELF, jobject jpSVal){ + return (jlong) sqlite3_value_int64(PtrGet_sqlite3_value(jpSVal)); +} + +JDECL(jobject,1value_1java_1object)(JENV_CSELF, jobject jpSVal){ + ResultJavaVal * const rv = sqlite3_value_pointer(PtrGet_sqlite3_value(jpSVal), RESULT_JAVA_VAL_STRING); + return rv ? rv->jObj : NULL; +} + +JDECL(jstring,1value_1text)(JENV_CSELF, jobject jpSVal){ + sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); + int const n = sqlite3_value_bytes16(sv); + const void * const p = sqlite3_value_text16(sv); + return s3jni_text16_to_jstring(env, p, n); +} + +JDECL(jbyteArray,1value_1text_1utf8)(JENV_CSELF, jobject jpSVal){ + sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); + int const n = sqlite3_value_bytes(sv); + const unsigned char * const p = sqlite3_value_text(sv); + return s3jni_new_jbyteArray(env, p, n); +} + +static jbyteArray value_text16(int mode, JNIEnv * const env, jobject jpSVal){ + int const nLen = sqlite3_value_bytes16(PtrGet_sqlite3_value(jpSVal)); + jbyteArray jba; + const jbyte * pBytes; + switch(mode){ + case SQLITE_UTF16: + pBytes = sqlite3_value_text16(PtrGet_sqlite3_value(jpSVal)); + break; + case SQLITE_UTF16LE: + pBytes = sqlite3_value_text16le(PtrGet_sqlite3_value(jpSVal)); + break; + case SQLITE_UTF16BE: + pBytes = sqlite3_value_text16be(PtrGet_sqlite3_value(jpSVal)); + break; + default: + assert(!"not possible"); + return NULL; + } + jba = pBytes + ? (*env)->NewByteArray(env, (jsize)nLen) + : NULL; + if(jba){ + (*env)->SetByteArrayRegion(env, jba, 0, nLen, pBytes); + } + return jba; +} + +JDECL(jbyteArray,1value_1text16)(JENV_CSELF, jobject jpSVal){ + return value_text16(SQLITE_UTF16, env, jpSVal); +} + +JDECL(jbyteArray,1value_1text16le)(JENV_CSELF, jobject jpSVal){ + return value_text16(SQLITE_UTF16LE, env, jpSVal); +} + +JDECL(jbyteArray,1value_1text16be)(JENV_CSELF, jobject jpSVal){ + return value_text16(SQLITE_UTF16BE, env, jpSVal); +} + +JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ + MARKER(("\nVarious bits of internal info:\n")); + puts("FTS5 is " +#ifdef SQLITE_ENABLE_FTS5 + "available" +#else + "unavailable" +#endif + "." + ); + puts("sizeofs:"); +#define SO(T) printf("\tsizeof(" #T ") = %u\n", (unsigned)sizeof(T)) + SO(void*); + SO(S3JniEnvCache); + SO(S3JniHook); + SO(S3JniDb); + SO(S3JniClassNames); + printf("\t(^^^ %u NativePointerHolder subclasses)\n", + (unsigned)(sizeof(S3JniClassNames) / sizeof(const char *))); + SO(S3JniGlobal); + SO(S3JniAutoExtension); + SO(S3JniUdf); + printf("Cache info:\n"); + printf("\tNativePointerHolder cache: %u misses, %u hits\n", + S3JniGlobal.metrics.nphCacheMisses, + S3JniGlobal.metrics.nphCacheHits); + printf("\tJNIEnv cache %u misses, %u hits\n", + S3JniGlobal.metrics.envCacheMisses, + S3JniGlobal.metrics.envCacheHits); + puts("Java-side UDF calls:"); +#define UDF(T) printf("\t%-8s = %u\n", "x" #T, S3JniGlobal.metrics.udf.n##T) + UDF(Func); UDF(Step); UDF(Final); UDF(Value); UDF(Inverse); +#undef UDF + printf("xDestroy calls across all callback types: %u\n", + S3JniGlobal.metrics.nDestroy); +#undef SO +} + +//////////////////////////////////////////////////////////////////////// +// End of the sqlite3_... API bindings. Next up, FTS5... +//////////////////////////////////////////////////////////////////////// +#ifdef SQLITE_ENABLE_FTS5 + +/* Creates a verbose JNI Fts5 function name. */ +#define JFuncNameFtsXA(Suffix) \ + Java_org_sqlite_jni_Fts5ExtensionApi_ ## Suffix +#define JFuncNameFtsApi(Suffix) \ + Java_org_sqlite_jni_fts5_1api_ ## Suffix +#define JFuncNameFtsTok(Suffix) \ + Java_org_sqlite_jni_fts5_tokenizer_ ## Suffix + +#define JDECLFtsXA(ReturnType,Suffix) \ + JNIEXPORT ReturnType JNICALL \ + JFuncNameFtsXA(Suffix) +#define JDECLFtsApi(ReturnType,Suffix) \ + JNIEXPORT ReturnType JNICALL \ + JFuncNameFtsApi(Suffix) +#define JDECLFtsTok(ReturnType,Suffix) \ + JNIEXPORT ReturnType JNICALL \ + JFuncNameFtsTok(Suffix) + +#define PtrGet_fts5_api(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.fts5_api) +#define PtrGet_fts5_tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.fts5_tokenizer) +#define PtrGet_Fts5Context(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.Fts5Context) +#define PtrGet_Fts5Tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.Fts5Tokenizer) +#define Fts5ExtDecl Fts5ExtensionApi const * const fext = s3jni_ftsext() + +/** + State for binding Java-side FTS5 auxiliary functions. +*/ +typedef struct { + JNIEnv * env; /* env registered from */; + jobject jObj /* functor instance */; + jclass klazz /* jObj's class */; + jobject jUserData /* 2nd arg to JNI binding of + xCreateFunction(), ostensibly the 3rd arg + to the lib-level xCreateFunction(), except + that we necessarily use that slot for a + Fts5JniAux instance. */; + char * zFuncName /* Only for error reporting and debug logging */; + jmethodID jmid /* callback member's method ID */; +} Fts5JniAux; + +static void Fts5JniAux_free(Fts5JniAux * const s){ + JNIEnv * const env = s->env; + if(env){ + /*MARKER(("FTS5 aux function cleanup: %s\n", s->zFuncName));*/ + s3jni_call_xDestroy(env, s->jObj, s->klazz); + UNREF_G(s->jObj); + UNREF_G(s->klazz); + UNREF_G(s->jUserData); + } + sqlite3_free(s->zFuncName); + sqlite3_free(s); +} + +static void Fts5JniAux_xDestroy(void *p){ + if(p) Fts5JniAux_free(p); +} + +static Fts5JniAux * Fts5JniAux_alloc(JNIEnv * const env, jobject jObj){ + Fts5JniAux * s = sqlite3_malloc(sizeof(Fts5JniAux)); + if(s){ + const char * zSig = + "(Lorg/sqlite/jni/Fts5ExtensionApi;" + "Lorg/sqlite/jni/Fts5Context;" + "Lorg/sqlite/jni/sqlite3_context;" + "[Lorg/sqlite/jni/sqlite3_value;)V"; + memset(s, 0, sizeof(Fts5JniAux)); + s->env = env; + s->jObj = REF_G(jObj); + s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); + s->jmid = (*env)->GetMethodID(env, s->klazz, "xFunction", zSig); + IFTHREW{ + EXCEPTION_REPORT; + EXCEPTION_CLEAR; + Fts5JniAux_free(s); + s = 0; + } + } + return s; +} + +static inline Fts5ExtensionApi const * s3jni_ftsext(void){ + return &sFts5Api/*singleton from sqlite3.c*/; +} + +static inline jobject new_Fts5Context_wrapper(JNIEnv * const env, Fts5Context *sv){ + return new_NativePointerHolder_object(env, S3JniClassNames.Fts5Context, sv); +} +static inline jobject new_fts5_api_wrapper(JNIEnv * const env, fts5_api *sv){ + return new_NativePointerHolder_object(env, S3JniClassNames.fts5_api, sv); +} + +/** + Returns a per-JNIEnv global ref to the Fts5ExtensionApi singleton + instance, or NULL on OOM. +*/ +static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){ + S3JniEnvCache * const row = S3JniGlobal_env_cache(env); + if( !row->jFtsExt ){ + row->jFtsExt = new_NativePointerHolder_object(env, S3JniClassNames.Fts5ExtensionApi, + s3jni_ftsext()); + if(row->jFtsExt) row->jFtsExt = REF_G(row->jFtsExt); + } + return row->jFtsExt; +} + +/* +** Return a pointer to the fts5_api instance for database connection +** db. If an error occurs, return NULL and leave an error in the +** database handle (accessible using sqlite3_errcode()/errmsg()). +*/ +static fts5_api *s3jni_fts5_api_from_db(sqlite3 *db){ + fts5_api *pRet = 0; + sqlite3_stmt *pStmt = 0; + if( SQLITE_OK==sqlite3_prepare(db, "SELECT fts5(?1)", -1, &pStmt, 0) ){ + sqlite3_bind_pointer(pStmt, 1, (void*)&pRet, "fts5_api_ptr", NULL); + sqlite3_step(pStmt); + } + sqlite3_finalize(pStmt); + return pRet; +} + +JDECLFtsApi(jobject,getInstanceForDb)(JENV_CSELF,jobject jDb){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + jobject rv = 0; + if(!ps) return 0; + else if(ps->jFtsApi){ + rv = ps->jFtsApi; + }else{ + fts5_api * const pApi = s3jni_fts5_api_from_db(ps->pDb); + if( pApi ){ + rv = new_fts5_api_wrapper(env, pApi); + ps->jFtsApi = rv ? REF_G(rv) : 0; + } + } + return rv; +} + + +JDECLFtsXA(jobject,getInstance)(JENV_CSELF){ + return s3jni_getFts5ExensionApi(env); +} + +JDECLFtsXA(jint,xColumnCount)(JENV_OSELF,jobject jCtx){ + Fts5ExtDecl; + return (jint)fext->xColumnCount(PtrGet_Fts5Context(jCtx)); +} + +JDECLFtsXA(jint,xColumnSize)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOut32){ + Fts5ExtDecl; + int n1 = 0; + int const rc = fext->xColumnSize(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1); + if( 0==rc ) OutputPointer_set_Int32(env, jOut32, n1); + return rc; +} + +JDECLFtsXA(jint,xColumnText)(JENV_OSELF,jobject jCtx, jint iCol, + jobject jOut){ + Fts5ExtDecl; + const char *pz = 0; + int pn = 0; + int rc = fext->xColumnText(PtrGet_Fts5Context(jCtx), (int)iCol, + &pz, &pn); + if( 0==rc ){ + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + jstring jstr = pz ? s3jni_utf8_to_jstring(jc, pz, pn) : 0; + if( pz ){ + if( jstr ){ + OutputPointer_set_String(env, jOut, jstr); + UNREF_L(jstr)/*jOut has a reference*/; + }else{ + rc = SQLITE_NOMEM; + } + } + } + return (jint)rc; +} + +JDECLFtsXA(jint,xColumnTotalSize)(JENV_OSELF,jobject jCtx, jint iCol, jobject jOut64){ + Fts5ExtDecl; + sqlite3_int64 nOut = 0; + int const rc = fext->xColumnTotalSize(PtrGet_Fts5Context(jCtx), (int)iCol, &nOut); + if( 0==rc && jOut64 ) OutputPointer_set_Int64(env, jOut64, (jlong)nOut); + return (jint)rc; +} + +/** + Proxy for fts5_extension_function instances plugged in via + fts5_api::xCreateFunction(). +*/ +static void s3jni_fts5_extension_function(Fts5ExtensionApi const *pApi, + Fts5Context *pFts, + sqlite3_context *pCx, + int argc, + sqlite3_value **argv){ + Fts5JniAux * const pAux = pApi->xUserData(pFts); + JNIEnv *env; + jobject jpCx = 0; + jobjectArray jArgv = 0; + jobject jpFts = 0; + jobject jFXA; + int rc; + assert(pAux); + env = pAux->env; + jFXA = s3jni_getFts5ExensionApi(env); + if( !jFXA ) goto error_oom; + jpFts = new_Fts5Context_wrapper(env, pFts); + if(!jpFts) goto error_oom; + rc = udf_args(env, pCx, argc, argv, &jpCx, &jArgv); + if(rc) goto error_oom; + (*env)->CallVoidMethod(env, pAux->jObj, pAux->jmid, + jFXA, jpFts, jpCx, jArgv); + IFTHREW{ + EXCEPTION_CLEAR; + udf_report_exception(pCx, pAux->zFuncName, "xFunction"); + } + UNREF_L(jpFts); + UNREF_L(jpCx); + UNREF_L(jArgv); + return; +error_oom: + assert( !jArgv ); + assert( !jpCx ); + UNREF_L(jpFts); + sqlite3_result_error_nomem(pCx); + return; +} + +JDECLFtsApi(jint,xCreateFunction)(JENV_OSELF, jstring jName, + jobject jUserData, jobject jFunc){ + fts5_api * const pApi = PtrGet_fts5_api(jSelf); + int rc; + char const * zName; + Fts5JniAux * pAux; + assert(pApi); + zName = JSTR_TOC(jName); + if(!zName) return SQLITE_NOMEM; + pAux = Fts5JniAux_alloc(env, jFunc); + if( pAux ){ + rc = pApi->xCreateFunction(pApi, zName, pAux, + s3jni_fts5_extension_function, + Fts5JniAux_xDestroy); + }else{ + rc = SQLITE_NOMEM; + } + if( 0==rc ){ + pAux->jUserData = jUserData ? REF_G(jUserData) : 0; + pAux->zFuncName = sqlite3_mprintf("%s", zName) + /* OOM here is non-fatal. Ignore it. */; + } + JSTR_RELEASE(jName, zName); + return (jint)rc; +} + + +typedef struct s3jni_fts5AuxData s3jni_fts5AuxData; +struct s3jni_fts5AuxData { + JNIEnv *env; + jobject jObj; +}; + +static void s3jni_fts5AuxData_xDestroy(void *x){ + if(x){ + s3jni_fts5AuxData * const p = x; + if(p->jObj){ + JNIEnv *env = p->env; + s3jni_call_xDestroy(env, p->jObj, 0); + UNREF_G(p->jObj); + } + sqlite3_free(x); + } +} + +JDECLFtsXA(jobject,xGetAuxdata)(JENV_OSELF,jobject jCtx, jboolean bClear){ + Fts5ExtDecl; + jobject rv = 0; + s3jni_fts5AuxData * const pAux = fext->xGetAuxdata(PtrGet_Fts5Context(jCtx), bClear); + if(pAux){ + if(bClear){ + if( pAux->jObj ){ + rv = REF_L(pAux->jObj); + UNREF_G(pAux->jObj); + } + /* Note that we do not call xDestroy() in this case. */ + sqlite3_free(pAux); + }else{ + rv = pAux->jObj; + } + } + return rv; +} + +JDECLFtsXA(jint,xInst)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOutPhrase, + jobject jOutCol, jobject jOutOff){ + Fts5ExtDecl; + int n1 = 0, n2 = 2, n3 = 0; + int const rc = fext->xInst(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1, &n2, &n3); + if( 0==rc ){ + OutputPointer_set_Int32(env, jOutPhrase, n1); + OutputPointer_set_Int32(env, jOutCol, n2); + OutputPointer_set_Int32(env, jOutOff, n3); + } + return rc; +} + +JDECLFtsXA(jint,xInstCount)(JENV_OSELF,jobject jCtx, jobject jOut32){ + Fts5ExtDecl; + int nOut = 0; + int const rc = fext->xInstCount(PtrGet_Fts5Context(jCtx), &nOut); + if( 0==rc && jOut32 ) OutputPointer_set_Int32(env, jOut32, nOut); + return (jint)rc; +} + +JDECLFtsXA(jint,xPhraseCount)(JENV_OSELF,jobject jCtx){ + Fts5ExtDecl; + return (jint)fext->xPhraseCount(PtrGet_Fts5Context(jCtx)); +} + +/** + Initializes jc->jPhraseIter if it needed it. +*/ +static void s3jni_phraseIter_init(JNIEnv *const env, S3JniEnvCache * const jc, + jobject jIter){ + if(!jc->jPhraseIter.klazz){ + jclass klazz = (*env)->GetObjectClass(env, jIter); + jc->jPhraseIter.klazz = REF_G(klazz); + jc->jPhraseIter.fidA = (*env)->GetFieldID(env, klazz, "a", "J"); + EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field."); + jc->jPhraseIter.fidB = (*env)->GetFieldID(env, klazz, "a", "J"); + EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field."); + } +} + +/* Copy the 'a' and 'b' fields from pSrc to Fts5PhraseIter object jIter. */ +static void s3jni_phraseIter_NToJ(JNIEnv *const env, S3JniEnvCache const * const jc, + Fts5PhraseIter const * const pSrc, + jobject jIter){ + assert(jc->jPhraseIter.klazz); + (*env)->SetLongField(env, jIter, jc->jPhraseIter.fidA, (jlong)pSrc->a); + EXCEPTION_IS_FATAL("Cannot set Fts5PhraseIter.a field."); + (*env)->SetLongField(env, jIter, jc->jPhraseIter.fidB, (jlong)pSrc->b); + EXCEPTION_IS_FATAL("Cannot set Fts5PhraseIter.b field."); +} + +/* Copy the 'a' and 'b' fields from Fts5PhraseIter object jIter to pDest. */ +static void s3jni_phraseIter_JToN(JNIEnv *const env, S3JniEnvCache const * const jc, + jobject jIter, Fts5PhraseIter * const pDest){ + assert(jc->jPhraseIter.klazz); + pDest->a = + (const unsigned char *)(*env)->GetLongField(env, jIter, jc->jPhraseIter.fidA); + EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field."); + pDest->b = + (const unsigned char *)(*env)->GetLongField(env, jIter, jc->jPhraseIter.fidB); + EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field."); +} + +JDECLFtsXA(jint,xPhraseFirst)(JENV_OSELF,jobject jCtx, jint iPhrase, + jobject jIter, jobject jOutCol, + jobject jOutOff){ + Fts5ExtDecl; + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + Fts5PhraseIter iter; + int rc, iCol = 0, iOff = 0; + s3jni_phraseIter_init(env, jc, jIter); + rc = fext->xPhraseFirst(PtrGet_Fts5Context(jCtx), (int)iPhrase, + &iter, &iCol, &iOff); + if( 0==rc ){ + OutputPointer_set_Int32(env, jOutCol, iCol); + OutputPointer_set_Int32(env, jOutOff, iOff); + s3jni_phraseIter_NToJ(env, jc, &iter, jIter); + } + return rc; +} + +JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_OSELF,jobject jCtx, jint iPhrase, + jobject jIter, jobject jOutCol){ + Fts5ExtDecl; + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + Fts5PhraseIter iter; + int rc, iCol = 0; + s3jni_phraseIter_init(env, jc, jIter); + rc = fext->xPhraseFirstColumn(PtrGet_Fts5Context(jCtx), (int)iPhrase, + &iter, &iCol); + if( 0==rc ){ + OutputPointer_set_Int32(env, jOutCol, iCol); + s3jni_phraseIter_NToJ(env, jc, &iter, jIter); + } + return rc; +} + +JDECLFtsXA(void,xPhraseNext)(JENV_OSELF,jobject jCtx, jobject jIter, + jobject jOutCol, jobject jOutOff){ + Fts5ExtDecl; + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + Fts5PhraseIter iter; + int iCol = 0, iOff = 0; + if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/; + s3jni_phraseIter_JToN(env, jc, jIter, &iter); + fext->xPhraseNext(PtrGet_Fts5Context(jCtx), + &iter, &iCol, &iOff); + OutputPointer_set_Int32(env, jOutCol, iCol); + OutputPointer_set_Int32(env, jOutOff, iOff); + s3jni_phraseIter_NToJ(env, jc, &iter, jIter); +} + +JDECLFtsXA(void,xPhraseNextColumn)(JENV_OSELF,jobject jCtx, jobject jIter, + jobject jOutCol){ + Fts5ExtDecl; + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + Fts5PhraseIter iter; + int iCol = 0; + if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/; + s3jni_phraseIter_JToN(env, jc, jIter, &iter); + fext->xPhraseNextColumn(PtrGet_Fts5Context(jCtx), &iter, &iCol); + OutputPointer_set_Int32(env, jOutCol, iCol); + s3jni_phraseIter_NToJ(env, jc, &iter, jIter); +} + + +JDECLFtsXA(jint,xPhraseSize)(JENV_OSELF,jobject jCtx, jint iPhrase){ + Fts5ExtDecl; + return (jint)fext->xPhraseSize(PtrGet_Fts5Context(jCtx), (int)iPhrase); +} + +/** + State for use with xQueryPhrase() and xTokenize(). +*/ +struct s3jni_xQueryPhraseState { + JNIEnv *env; + Fts5ExtensionApi const * fext; + S3JniEnvCache const * jc; + jmethodID midCallback; + jobject jCallback; + jobject jFcx; + /* State for xTokenize() */ + struct { + const char * zPrev; + int nPrev; + jbyteArray jba; + } tok; +}; + +static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi, + Fts5Context * pFcx, void *pData){ + /* TODO: confirm that the Fts5Context passed to this function is + guaranteed to be the same one passed to xQueryPhrase(). If it's + not, we'll have to create a new wrapper object on every call. */ + struct s3jni_xQueryPhraseState const * s = pData; + JNIEnv * const env = s->env; + int rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback, + s->jc->jFtsExt, s->jFcx); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW("xQueryPhrase callback"); + EXCEPTION_CLEAR; + rc = SQLITE_ERROR; + } + return rc; +} + +JDECLFtsXA(jint,xQueryPhrase)(JENV_OSELF,jobject jFcx, jint iPhrase, + jobject jCallback){ + Fts5ExtDecl; + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + struct s3jni_xQueryPhraseState s; + jclass klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL; + if( !klazz ) return SQLITE_MISUSE; + s.env = env; + s.jc = jc; + s.jCallback = jCallback; + s.jFcx = jFcx; + s.fext = fext; + s.midCallback = (*env)->GetMethodID(env, klazz, "xCallback", + "(Lorg.sqlite.jni.Fts5ExtensionApi;" + "Lorg.sqlite.jni.Fts5Context;)I"); + EXCEPTION_IS_FATAL("Could not extract xQueryPhraseCallback.xCallback method."); + return (jint)fext->xQueryPhrase(PtrGet_Fts5Context(jFcx), iPhrase, &s, + s3jni_xQueryPhrase); +} + + +JDECLFtsXA(jint,xRowCount)(JENV_OSELF,jobject jCtx, jobject jOut64){ + Fts5ExtDecl; + sqlite3_int64 nOut = 0; + int const rc = fext->xRowCount(PtrGet_Fts5Context(jCtx), &nOut); + if( 0==rc && jOut64 ) OutputPointer_set_Int64(env, jOut64, (jlong)nOut); + return (jint)rc; +} + +JDECLFtsXA(jlong,xRowid)(JENV_OSELF,jobject jCtx){ + Fts5ExtDecl; + return (jlong)fext->xRowid(PtrGet_Fts5Context(jCtx)); +} + +JDECLFtsXA(int,xSetAuxdata)(JENV_OSELF,jobject jCtx, jobject jAux){ + Fts5ExtDecl; + int rc; + s3jni_fts5AuxData * pAux; + pAux = sqlite3_malloc(sizeof(*pAux)); + if(!pAux){ + if(jAux){ + // Emulate how xSetAuxdata() behaves when it cannot alloc + // its auxdata wrapper. + s3jni_call_xDestroy(env, jAux, 0); + } + return SQLITE_NOMEM; + } + pAux->env = env; + pAux->jObj = REF_G(jAux); + rc = fext->xSetAuxdata(PtrGet_Fts5Context(jCtx), pAux, + s3jni_fts5AuxData_xDestroy); + return rc; +} + +/** + xToken() impl for xTokenize(). +*/ +static int s3jni_xTokenize_xToken(void *p, int tFlags, const char* z, + int nZ, int iStart, int iEnd){ + int rc; + struct s3jni_xQueryPhraseState * const s = p; + JNIEnv * const env = s->env; + jbyteArray jba; + if( s->tok.zPrev == z && s->tok.nPrev == nZ ){ + jba = s->tok.jba; + }else{ + if(s->tok.jba){ + UNREF_L(s->tok.jba); + } + s->tok.zPrev = z; + s->tok.nPrev = nZ; + s->tok.jba = (*env)->NewByteArray(env, (jint)nZ); + if( !s->tok.jba ) return SQLITE_NOMEM; + jba = s->tok.jba; + (*env)->SetByteArrayRegion(env, jba, 0, (jint)nZ, (const jbyte*)z); + } + rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback, + (jint)tFlags, jba, (jint)iStart, + (jint)iEnd); + return rc; +} + +/** + Proxy for Fts5ExtensionApi.xTokenize() and fts5_tokenizer.xTokenize() +*/ +static jint s3jni_fts5_xTokenize(JENV_OSELF, const char *zClassName, + jint tokFlags, jobject jFcx, + jbyteArray jbaText, jobject jCallback){ + Fts5ExtDecl; + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + struct s3jni_xQueryPhraseState s; + int rc = 0; + jbyte * const pText = jCallback ? JBA_TOC(jbaText) : 0; + jsize nText = pText ? (*env)->GetArrayLength(env, jbaText) : 0; + jclass const klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL; + if( !klazz ) return SQLITE_MISUSE; + memset(&s, 0, sizeof(s)); + s.env = env; + s.jc = jc; + s.jCallback = jCallback; + s.jFcx = jFcx; + s.fext = fext; + s.midCallback = (*env)->GetMethodID(env, klazz, "xToken", "(I[BII)I"); + IFTHREW { + EXCEPTION_REPORT; + EXCEPTION_CLEAR; + JBA_RELEASE(jbaText, pText); + return SQLITE_ERROR; + } + s.tok.jba = REF_L(jbaText); + s.tok.zPrev = (const char *)pText; + s.tok.nPrev = (int)nText; + if( zClassName == S3JniClassNames.Fts5ExtensionApi ){ + rc = fext->xTokenize(PtrGet_Fts5Context(jFcx), + (const char *)pText, (int)nText, + &s, s3jni_xTokenize_xToken); + }else if( zClassName == S3JniClassNames.fts5_tokenizer ){ + fts5_tokenizer * const pTok = PtrGet_fts5_tokenizer(jSelf); + rc = pTok->xTokenize(PtrGet_Fts5Tokenizer(jFcx), &s, tokFlags, + (const char *)pText, (int)nText, + s3jni_xTokenize_xToken); + }else{ + (*env)->FatalError(env, "This cannot happen. Maintenance required."); + } + if(s.tok.jba){ + assert( s.tok.zPrev ); + UNREF_L(s.tok.jba); + } + JBA_RELEASE(jbaText, pText); + return (jint)rc; +} + +JDECLFtsXA(jint,xTokenize)(JENV_OSELF,jobject jFcx, jbyteArray jbaText, + jobject jCallback){ + return s3jni_fts5_xTokenize(env, jSelf, S3JniClassNames.Fts5ExtensionApi, + 0, jFcx, jbaText, jCallback); +} + +JDECLFtsTok(jint,xTokenize)(JENV_OSELF,jobject jFcx, jint tokFlags, + jbyteArray jbaText, jobject jCallback){ + return s3jni_fts5_xTokenize(env, jSelf, S3JniClassNames.Fts5Tokenizer, + tokFlags, jFcx, jbaText, jCallback); +} + + +JDECLFtsXA(jobject,xUserData)(JENV_OSELF,jobject jFcx){ + Fts5ExtDecl; + Fts5JniAux * const pAux = fext->xUserData(PtrGet_Fts5Context(jFcx)); + return pAux ? pAux->jUserData : 0; +} + +#endif /* SQLITE_ENABLE_FTS5 */ + +//////////////////////////////////////////////////////////////////////// +// End of the main API bindings. Start of SQLTester bits... +//////////////////////////////////////////////////////////////////////// + +#ifdef S3JNI_ENABLE_SQLTester +typedef struct SQLTesterJni SQLTesterJni; +struct SQLTesterJni { + sqlite3_int64 nDup; +}; +static SQLTesterJni SQLTester = { + 0 +}; + +static void SQLTester_dup_destructor(void*pToFree){ + u64 *p = (u64*)pToFree; + assert( p!=0 ); + p--; + assert( p[0]==0x2bbf4b7c ); + p[0] = 0; + p[1] = 0; + sqlite3_free(p); +} + +/* +** Implementation of +** +** dup(TEXT) +** +** This SQL function simply makes a copy of its text argument. But it +** returns the result using a custom destructor, in order to provide +** tests for the use of Mem.xDel() in the SQLite VDBE. +*/ +static void SQLTester_dup_func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + u64 *pOut; + char *z; + int n = sqlite3_value_bytes(argv[0]); + SQLTesterJni * const p = (SQLTesterJni *)sqlite3_user_data(context); + + ++p->nDup; + if( n>0 && (pOut = sqlite3_malloc( (n+16)&~7 ))!=0 ){ + pOut[0] = 0x2bbf4b7c; + z = (char*)&pOut[1]; + memcpy(z, sqlite3_value_text(argv[0]), n); + z[n] = 0; + sqlite3_result_text(context, z, n, SQLTester_dup_destructor); + } + return; +} + +/* +** Return the number of calls to the dup() SQL function since the +** SQLTester context was opened or since the last dup_count() call. +*/ +static void SQLTester_dup_count_func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + SQLTesterJni * const p = (SQLTesterJni *)sqlite3_user_data(context); + sqlite3_result_int64(context, p->nDup); + p->nDup = 0; +} + +/* +** Return non-zero if string z matches glob pattern zGlob and zero if the +** pattern does not match. +** +** To repeat: +** +** zero == no match +** non-zero == match +** +** Globbing rules: +** +** '*' Matches any sequence of zero or more characters. +** +** '?' Matches exactly one character. +** +** [...] Matches one character from the enclosed list of +** characters. +** +** [^...] Matches one character not in the enclosed list. +** +** '#' Matches any sequence of one or more digits with an +** optional + or - sign in front, or a hexadecimal +** literal of the form 0x... +*/ +static int SQLTester_strnotglob(const char *zGlob, const char *z){ + int c, c2; + int invert; + int seen; + + while( (c = (*(zGlob++)))!=0 ){ + if( c=='*' ){ + while( (c=(*(zGlob++))) == '*' || c=='?' ){ + if( c=='?' && (*(z++))==0 ) return 0; + } + if( c==0 ){ + return 1; + }else if( c=='[' ){ + while( *z && SQLTester_strnotglob(zGlob-1,z)==0 ){ + z++; + } + return (*z)!=0; + } + while( (c2 = (*(z++)))!=0 ){ + while( c2!=c ){ + c2 = *(z++); + if( c2==0 ) return 0; + } + if( SQLTester_strnotglob(zGlob,z) ) return 1; + } + return 0; + }else if( c=='?' ){ + if( (*(z++))==0 ) return 0; + }else if( c=='[' ){ + int prior_c = 0; + seen = 0; + invert = 0; + c = *(z++); + if( c==0 ) return 0; + c2 = *(zGlob++); + if( c2=='^' ){ + invert = 1; + c2 = *(zGlob++); + } + if( c2==']' ){ + if( c==']' ) seen = 1; + c2 = *(zGlob++); + } + while( c2 && c2!=']' ){ + if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){ + c2 = *(zGlob++); + if( c>=prior_c && c<=c2 ) seen = 1; + prior_c = 0; + }else{ + if( c==c2 ){ + seen = 1; + } + prior_c = c2; + } + c2 = *(zGlob++); + } + if( c2==0 || (seen ^ invert)==0 ) return 0; + }else if( c=='#' ){ + if( z[0]=='0' + && (z[1]=='x' || z[1]=='X') + && sqlite3Isxdigit(z[2]) + ){ + z += 3; + while( sqlite3Isxdigit(z[0]) ){ z++; } + }else{ + if( (z[0]=='-' || z[0]=='+') && sqlite3Isdigit(z[1]) ) z++; + if( !sqlite3Isdigit(z[0]) ) return 0; + z++; + while( sqlite3Isdigit(z[0]) ){ z++; } + } + }else{ + if( c!=(*(z++)) ) return 0; + } + } + return *z==0; +} + +JNIEXPORT jint JNICALL +Java_org_sqlite_jni_tester_SQLTester_strglob( + JENV_CSELF, jbyteArray baG, jbyteArray baT +){ + int rc = 0; + jbyte * const pG = JBA_TOC(baG); + jbyte * const pT = pG ? JBA_TOC(baT) : 0; + OOM_CHECK(pT); + + /* Note that we're relying on the byte arrays having been + NUL-terminated on the Java side. */ + rc = !SQLTester_strnotglob((const char *)pG, (const char *)pT); + JBA_RELEASE(baG, pG); + JBA_RELEASE(baT, pT); + return rc; +} + + +static int SQLTester_auto_extension(sqlite3 *pDb, const char **pzErr, + const struct sqlite3_api_routines *ignored){ + sqlite3_create_function(pDb, "dup", 1, SQLITE_UTF8, &SQLTester, + SQLTester_dup_func, 0, 0); + sqlite3_create_function(pDb, "dup_count", 0, SQLITE_UTF8, &SQLTester, + SQLTester_dup_count_func, 0, 0); + return 0; +} + +JNIEXPORT void JNICALL +Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions(JENV_CSELF){ + sqlite3_auto_extension( (void(*)(void))SQLTester_auto_extension ); +} + +#endif /* S3JNI_ENABLE_SQLTester */ +//////////////////////////////////////////////////////////////////////// +// End of SQLTester bindings. Start of lower-level bits. +//////////////////////////////////////////////////////////////////////// + + +/** + Uncaches the current JNIEnv from the S3JniGlobal state, clearing any + resources owned by that cache entry and making that slot available + for re-use. It is important that the Java-side decl of this + function be declared as synchronous. +*/ +JNIEXPORT jboolean JNICALL +Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JENV_CSELF){ + return S3JniGlobal_env_uncache(env) ? JNI_TRUE : JNI_FALSE; +} + +/** + Called during static init of the SQLite3Jni class to sync certain + compile-time constants to Java-space. + + This routine is part of the reason why we have to #include + sqlite3.c instead of sqlite3.h. +*/ +JNIEXPORT void JNICALL +Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ + enum JType { + JTYPE_INT, + JTYPE_BOOL + }; + typedef struct { + const char *zName; + enum JType jtype; + int value; + } ConfigFlagEntry; + const ConfigFlagEntry aLimits[] = { + {"SQLITE_ENABLE_FTS5", JTYPE_BOOL, +#ifdef SQLITE_ENABLE_FTS5 + 1 +#else + 0 +#endif + }, + {"SQLITE_MAX_ALLOCATION_SIZE", JTYPE_INT, SQLITE_MAX_ALLOCATION_SIZE}, + {"SQLITE_LIMIT_LENGTH", JTYPE_INT, SQLITE_LIMIT_LENGTH}, + {"SQLITE_MAX_LENGTH", JTYPE_INT, SQLITE_MAX_LENGTH}, + {"SQLITE_LIMIT_SQL_LENGTH", JTYPE_INT, SQLITE_LIMIT_SQL_LENGTH}, + {"SQLITE_MAX_SQL_LENGTH", JTYPE_INT, SQLITE_MAX_SQL_LENGTH}, + {"SQLITE_LIMIT_COLUMN", JTYPE_INT, SQLITE_LIMIT_COLUMN}, + {"SQLITE_MAX_COLUMN", JTYPE_INT, SQLITE_MAX_COLUMN}, + {"SQLITE_LIMIT_EXPR_DEPTH", JTYPE_INT, SQLITE_LIMIT_EXPR_DEPTH}, + {"SQLITE_MAX_EXPR_DEPTH", JTYPE_INT, SQLITE_MAX_EXPR_DEPTH}, + {"SQLITE_LIMIT_COMPOUND_SELECT", JTYPE_INT, SQLITE_LIMIT_COMPOUND_SELECT}, + {"SQLITE_MAX_COMPOUND_SELECT", JTYPE_INT, SQLITE_MAX_COMPOUND_SELECT}, + {"SQLITE_LIMIT_VDBE_OP", JTYPE_INT, SQLITE_LIMIT_VDBE_OP}, + {"SQLITE_MAX_VDBE_OP", JTYPE_INT, SQLITE_MAX_VDBE_OP}, + {"SQLITE_LIMIT_FUNCTION_ARG", JTYPE_INT, SQLITE_LIMIT_FUNCTION_ARG}, + {"SQLITE_MAX_FUNCTION_ARG", JTYPE_INT, SQLITE_MAX_FUNCTION_ARG}, + {"SQLITE_LIMIT_ATTACHED", JTYPE_INT, SQLITE_LIMIT_ATTACHED}, + {"SQLITE_MAX_ATTACHED", JTYPE_INT, SQLITE_MAX_ATTACHED}, + {"SQLITE_LIMIT_LIKE_PATTERN_LENGTH", JTYPE_INT, SQLITE_LIMIT_LIKE_PATTERN_LENGTH}, + {"SQLITE_MAX_LIKE_PATTERN_LENGTH", JTYPE_INT, SQLITE_MAX_LIKE_PATTERN_LENGTH}, + {"SQLITE_LIMIT_VARIABLE_NUMBER", JTYPE_INT, SQLITE_LIMIT_VARIABLE_NUMBER}, + {"SQLITE_MAX_VARIABLE_NUMBER", JTYPE_INT, SQLITE_MAX_VARIABLE_NUMBER}, + {"SQLITE_LIMIT_TRIGGER_DEPTH", JTYPE_INT, SQLITE_LIMIT_TRIGGER_DEPTH}, + {"SQLITE_MAX_TRIGGER_DEPTH", JTYPE_INT, SQLITE_MAX_TRIGGER_DEPTH}, + {"SQLITE_LIMIT_WORKER_THREADS", JTYPE_INT, SQLITE_LIMIT_WORKER_THREADS}, + {"SQLITE_MAX_WORKER_THREADS", JTYPE_INT, SQLITE_MAX_WORKER_THREADS}, + {0,0} + }; + jfieldID fieldId; + const ConfigFlagEntry * pConfFlag; + + memset(&S3JniGlobal, 0, sizeof(S3JniGlobal)); + if( (*env)->GetJavaVM(env, &S3JniGlobal.jvm) ){ + (*env)->FatalError(env, "GetJavaVM() failure shouldn't be possible."); + return; + } +#if 0 + /* Just for sanity checking... */ + (void)S3JniGlobal_env_cache(env); + if( !S3JniGlobal.envCache.aHead ){ + (*env)->FatalError(env, "Could not allocate JNIEnv-specific cache."); + return; + } + assert( 1 == S3JniGlobal.metrics.envCacheMisses ); + assert( env == S3JniGlobal.envCache.aHead->env ); + assert( 0 != S3JniGlobal.envCache.aHead->g.cObj ); +#endif + + for( pConfFlag = &aLimits[0]; pConfFlag->zName; ++pConfFlag ){ + char const * zSig = (JTYPE_BOOL == pConfFlag->jtype) ? "Z" : "I"; + fieldId = (*env)->GetStaticFieldID(env, jKlazz, pConfFlag->zName, zSig); + EXCEPTION_IS_FATAL("Missing an expected static member of the SQLite3Jni class."); + //MARKER(("Setting %s (field=%p) = %d\n", pConfFlag->zName, fieldId, pConfFlag->value)); + assert(fieldId); + switch(pConfFlag->jtype){ + case JTYPE_INT: + (*env)->SetStaticIntField(env, jKlazz, fieldId, (jint)pConfFlag->value); + break; + case JTYPE_BOOL: + (*env)->SetStaticBooleanField(env, jKlazz, fieldId, + pConfFlag->value ? JNI_TRUE : JNI_FALSE); + break; + } + EXCEPTION_IS_FATAL("Seting a static member of the SQLite3Jni class failed."); + } +} diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h new file mode 100644 index 0000000000..bcb55c4f1c --- /dev/null +++ b/ext/jni/src/c/sqlite3-jni.h @@ -0,0 +1,1989 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_sqlite_jni_SQLite3Jni */ + +#ifndef _Included_org_sqlite_jni_SQLite3Jni +#define _Included_org_sqlite_jni_SQLite3Jni +#ifdef __cplusplus +extern "C" { +#endif +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_EXISTS +#define org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_EXISTS 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READWRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READWRITE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READ +#define org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READ 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DENY +#define org_sqlite_jni_SQLite3Jni_SQLITE_DENY 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IGNORE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IGNORE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_INDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_INDEX 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TABLE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_INDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_INDEX 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TABLE 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TRIGGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TRIGGER 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_VIEW +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_VIEW 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TRIGGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TRIGGER 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VIEW +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VIEW 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DELETE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DELETE 9L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_INDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_INDEX 10L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TABLE 11L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_INDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_INDEX 12L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TABLE 13L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TRIGGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TRIGGER 14L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_VIEW +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_VIEW 15L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TRIGGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TRIGGER 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VIEW +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VIEW 17L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INSERT +#define org_sqlite_jni_SQLite3Jni_SQLITE_INSERT 18L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_PRAGMA +#define org_sqlite_jni_SQLite3Jni_SQLITE_PRAGMA 19L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READ +#define org_sqlite_jni_SQLite3Jni_SQLITE_READ 20L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SELECT +#define org_sqlite_jni_SQLite3Jni_SQLITE_SELECT 21L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRANSACTION +#define org_sqlite_jni_SQLite3Jni_SQLITE_TRANSACTION 22L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_UPDATE +#define org_sqlite_jni_SQLite3Jni_SQLITE_UPDATE 23L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ATTACH +#define org_sqlite_jni_SQLite3Jni_SQLITE_ATTACH 24L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DETACH +#define org_sqlite_jni_SQLite3Jni_SQLITE_DETACH 25L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ALTER_TABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_ALTER_TABLE 26L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_REINDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_REINDEX 27L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ANALYZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_ANALYZE 28L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VTABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VTABLE 29L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VTABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VTABLE 30L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FUNCTION +#define org_sqlite_jni_SQLite3Jni_SQLITE_FUNCTION 31L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SAVEPOINT +#define org_sqlite_jni_SQLite3Jni_SQLITE_SAVEPOINT 32L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_RECURSIVE +#define org_sqlite_jni_SQLite3Jni_SQLITE_RECURSIVE 33L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATIC 0LL +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRANSIENT +#define org_sqlite_jni_SQLite3Jni_SQLITE_TRANSIENT -1LL +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETSTART_INVERT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETSTART_INVERT 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_NOSAVEPOINT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_NOSAVEPOINT 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_INVERT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_INVERT 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_IGNORENOOP +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_IGNORENOOP 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_DATA +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_DATA 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_NOTFOUND +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_NOTFOUND 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONFLICT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONFLICT 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONSTRAINT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONSTRAINT 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_FOREIGN_KEY +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_FOREIGN_KEY 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_OMIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_OMIT 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_REPLACE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_REPLACE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_ABORT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_ABORT 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SINGLETHREAD +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SINGLETHREAD 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MULTITHREAD +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MULTITHREAD 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SERIALIZED +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SERIALIZED 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MALLOC +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MALLOC 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMALLOC +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMALLOC 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SCRATCH +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SCRATCH 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PAGECACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PAGECACHE 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_HEAP +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_HEAP 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMSTATUS +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMSTATUS 9L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MUTEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MUTEX 10L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMUTEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMUTEX 11L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOOKASIDE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOOKASIDE 13L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE 14L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE 15L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOG +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOG 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_URI +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_URI 17L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE2 +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE2 18L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE2 +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE2 19L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_COVERING_INDEX_SCAN +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_COVERING_INDEX_SCAN 20L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SQLLOG +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SQLLOG 21L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MMAP_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MMAP_SIZE 22L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_WIN32_HEAPSIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_WIN32_HEAPSIZE 23L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE_HDRSZ +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE_HDRSZ 24L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PMASZ +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PMASZ 25L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_STMTJRNL_SPILL +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_STMTJRNL_SPILL 26L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SMALL_MALLOC +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SMALL_MALLOC 27L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SORTERREF_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SORTERREF_SIZE 28L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMDB_MAXSIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMDB_MAXSIZE 29L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INTEGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_INTEGER 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FLOAT +#define org_sqlite_jni_SQLite3Jni_SQLITE_FLOAT 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TEXT +#define org_sqlite_jni_SQLite3Jni_SQLITE_TEXT 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_BLOB +#define org_sqlite_jni_SQLite3Jni_SQLITE_BLOB 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_NULL 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAINDBNAME +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAINDBNAME 1000L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LOOKASIDE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LOOKASIDE 1001L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FKEY +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FKEY 1002L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_TRIGGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_TRIGGER 1003L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_QPSG +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_QPSG 1007L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRIGGER_EQP +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRIGGER_EQP 1008L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_RESET_DATABASE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_RESET_DATABASE 1009L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DEFENSIVE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DEFENSIVE 1010L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_WRITABLE_SCHEMA +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DML +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DML 1013L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DDL +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DDL 1014L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_VIEW +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_VIEW 1015L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRUSTED_SCHEMA +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_STMT_SCANSTATUS +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_STMT_SCANSTATUS 1018L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_REVERSE_SCANORDER +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_REVERSE_SCANORDER 1019L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAX +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAX 1019L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_USED +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_USED 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_SCHEMA_USED +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_SCHEMA_USED 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_STMT_USED +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_STMT_USED 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_HIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_HIT 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_HIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_HIT 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_MISS +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_MISS 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_WRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_WRITE 9L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_DEFERRED_FKS +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_DEFERRED_FKS 10L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED_SHARED +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED_SHARED 11L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_SPILL +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_SPILL 12L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_MAX +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_MAX 12L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF8 +#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF8 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16LE +#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16LE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16BE +#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16BE 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16 +#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16_ALIGNED +#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16_ALIGNED 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCKSTATE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCKSTATE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_GET_LOCKPROXYFILE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_GET_LOCKPROXYFILE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SET_LOCKPROXYFILE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SET_LOCKPROXYFILE 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LAST_ERRNO +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LAST_ERRNO 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_HINT +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_HINT 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CHUNK_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CHUNK_SIZE 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_FILE_POINTER +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_FILE_POINTER 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC_OMITTED +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC_OMITTED 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_AV_RETRY +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_AV_RETRY 9L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PERSIST_WAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PERSIST_WAL 10L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_OVERWRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_OVERWRITE 11L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFSNAME +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFSNAME 12L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_POWERSAFE_OVERWRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_POWERSAFE_OVERWRITE 13L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PRAGMA +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PRAGMA 14L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BUSYHANDLER +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BUSYHANDLER 15L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TEMPFILENAME +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TEMPFILENAME 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_MMAP_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_MMAP_SIZE 18L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TRACE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TRACE 19L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_HAS_MOVED +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_HAS_MOVED 20L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC 21L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_PHASETWO +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_PHASETWO 22L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_SET_HANDLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_SET_HANDLE 23L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WAL_BLOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WAL_BLOCK 24L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ZIPVFS +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ZIPVFS 25L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RBU +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RBU 26L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFS_POINTER +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFS_POINTER 27L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_JOURNAL_POINTER +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_JOURNAL_POINTER 28L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_GET_HANDLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_GET_HANDLE 29L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PDB +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PDB 30L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCK_TIMEOUT +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCK_TIMEOUT 34L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_DATA_VERSION +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_DATA_VERSION 35L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_LIMIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_LIMIT 36L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_DONE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_DONE 37L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESERVE_BYTES +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESERVE_BYTES 38L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_START +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_START 39L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_EXTERNAL_READER +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_EXTERNAL_READER 40L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKSM_FILE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKSM_FILE 41L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESET_CACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESET_CACHE 42L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_NONE +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_NONE 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_SHARED +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_SHARED 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_RESERVED +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_RESERVED 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_PENDING +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_PENDING 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_EXCLUSIVE +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_EXCLUSIVE 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC512 +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC512 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC1K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC1K 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC2K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC2K 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC4K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC4K 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC8K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC8K 32L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC16K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC16K 64L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC32K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC32K 128L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC64K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC64K 256L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SAFE_APPEND +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SAFE_APPEND 512L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SEQUENTIAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SEQUENTIAL 1024L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 2048L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_POWERSAFE_OVERWRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_POWERSAFE_OVERWRITE 4096L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_IMMUTABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_IMMUTABLE 8192L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_BATCH_ATOMIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_BATCH_ATOMIC 16384L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READONLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READONLY 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READWRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READWRITE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_CREATE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_CREATE 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_URI +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_URI 64L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MEMORY +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MEMORY 128L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOMUTEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOMUTEX 32768L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_FULLMUTEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_FULLMUTEX 65536L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SHAREDCACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SHAREDCACHE 131072L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_PRIVATECACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_PRIVATECACHE 262144L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXRESCODE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXRESCODE 33554432L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOFOLLOW +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOFOLLOW 16777216L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_DB +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_DB 256L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_JOURNAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_JOURNAL 2048L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_DB +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_DB 512L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_JOURNAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_JOURNAL 4096L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TRANSIENT_DB +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TRANSIENT_DB 1024L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUBJOURNAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUBJOURNAL 8192L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUPER_JOURNAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUPER_JOURNAL 16384L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_WAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_WAL 524288L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_DELETEONCLOSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_DELETEONCLOSE 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXCLUSIVE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXCLUSIVE 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_PERSISTENT +#define org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_PERSISTENT 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NORMALIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NORMALIZE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NO_VTAB +#define org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NO_VTAB 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OK +#define org_sqlite_jni_SQLite3Jni_SQLITE_OK 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR +#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INTERNAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_INTERNAL 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_PERM +#define org_sqlite_jni_SQLite3Jni_SQLITE_PERM 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ABORT +#define org_sqlite_jni_SQLite3Jni_SQLITE_ABORT 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY +#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOMEM +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOMEM 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INTERRUPT +#define org_sqlite_jni_SQLite3Jni_SQLITE_INTERRUPT 9L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR 10L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT 11L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTFOUND +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTFOUND 12L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_FULL 13L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN +#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN 14L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_PROTOCOL +#define org_sqlite_jni_SQLite3Jni_SQLITE_PROTOCOL 15L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_EMPTY +#define org_sqlite_jni_SQLite3Jni_SQLITE_EMPTY 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SCHEMA +#define org_sqlite_jni_SQLite3Jni_SQLITE_SCHEMA 17L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TOOBIG +#define org_sqlite_jni_SQLite3Jni_SQLITE_TOOBIG 18L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT 19L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_MISMATCH +#define org_sqlite_jni_SQLite3Jni_SQLITE_MISMATCH 20L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_MISUSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_MISUSE 21L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOLFS +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOLFS 22L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_AUTH +#define org_sqlite_jni_SQLite3Jni_SQLITE_AUTH 23L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FORMAT +#define org_sqlite_jni_SQLite3Jni_SQLITE_FORMAT 24L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_RANGE +#define org_sqlite_jni_SQLite3Jni_SQLITE_RANGE 25L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTADB +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTADB 26L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE 27L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_WARNING +#define org_sqlite_jni_SQLite3Jni_SQLITE_WARNING 28L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ROW +#define org_sqlite_jni_SQLite3Jni_SQLITE_ROW 100L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DONE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DONE 101L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_MISSING_COLLSEQ +#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_MISSING_COLLSEQ 257L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_RETRY +#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_RETRY 513L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_SNAPSHOT +#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_SNAPSHOT 769L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_READ +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_READ 266L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHORT_READ +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHORT_READ 522L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_WRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_WRITE 778L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSYNC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSYNC 1034L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_FSYNC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_FSYNC 1290L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_TRUNCATE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_TRUNCATE 1546L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSTAT +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSTAT 1802L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_UNLOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_UNLOCK 2058L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_RDLOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_RDLOCK 2314L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE 2570L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BLOCKED +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BLOCKED 2826L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_NOMEM +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_NOMEM 3082L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ACCESS +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ACCESS 3338L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CHECKRESERVEDLOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CHECKRESERVEDLOCK 3594L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_LOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_LOCK 3850L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CLOSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CLOSE 4106L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_CLOSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_CLOSE 4362L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMOPEN +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMOPEN 4618L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMSIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMSIZE 4874L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMLOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMLOCK 5130L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMMAP +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMMAP 5386L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SEEK +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SEEK 5642L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE_NOENT +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE_NOENT 5898L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_MMAP +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_MMAP 6154L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_GETTEMPPATH +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_GETTEMPPATH 6410L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CONVPATH +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CONVPATH 6666L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_VNODE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_VNODE 6922L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_AUTH +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_AUTH 7178L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BEGIN_ATOMIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BEGIN_ATOMIC 7434L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_COMMIT_ATOMIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_COMMIT_ATOMIC 7690L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ROLLBACK_ATOMIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ROLLBACK_ATOMIC 7946L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DATA +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DATA 8202L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CORRUPTFS +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CORRUPTFS 8458L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_SHAREDCACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_SHAREDCACHE 262L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_VTAB +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_VTAB 518L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_RECOVERY +#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_RECOVERY 261L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_SNAPSHOT +#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_SNAPSHOT 517L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_TIMEOUT +#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_TIMEOUT 773L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_NOTEMPDIR +#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_NOTEMPDIR 270L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_ISDIR +#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_ISDIR 526L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_FULLPATH +#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_FULLPATH 782L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_CONVPATH +#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_CONVPATH 1038L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_SYMLINK +#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_SYMLINK 1550L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_VTAB +#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_VTAB 267L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_SEQUENCE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_SEQUENCE 523L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_INDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_INDEX 779L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_RECOVERY +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_RECOVERY 264L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTLOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTLOCK 520L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_ROLLBACK +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_ROLLBACK 776L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DBMOVED +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DBMOVED 1032L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTINIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTINIT 1288L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DIRECTORY +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DIRECTORY 1544L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ABORT_ROLLBACK +#define org_sqlite_jni_SQLite3Jni_SQLITE_ABORT_ROLLBACK 516L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_CHECK +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_CHECK 275L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_COMMITHOOK +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_COMMITHOOK 531L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FOREIGNKEY +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FOREIGNKEY 787L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FUNCTION +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FUNCTION 1043L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_NOTNULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_NOTNULL 1299L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PRIMARYKEY +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PRIMARYKEY 1555L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_TRIGGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_TRIGGER 1811L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_UNIQUE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_UNIQUE 2067L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_VTAB +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_VTAB 2323L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_ROWID +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_ROWID 2579L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PINNED +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PINNED 2835L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_DATATYPE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_DATATYPE 3091L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_WAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_WAL 283L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_ROLLBACK +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_ROLLBACK 539L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_WARNING_AUTOINDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_WARNING_AUTOINDEX 284L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_AUTH_USER +#define org_sqlite_jni_SQLite3Jni_SQLITE_AUTH_USER 279L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OK_LOAD_PERMANENTLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_OK_LOAD_PERMANENTLY 256L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SERIALIZE_NOCOPY +#define org_sqlite_jni_SQLite3Jni_SQLITE_SERIALIZE_NOCOPY 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_FREEONCLOSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_FREEONCLOSE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_READONLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_READONLY 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_RESIZEABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_RESIZEABLE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_CONFIG_STRMSIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_CONFIG_STRMSIZE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_OBJCONFIG_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_OBJCONFIG_SIZE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MEMORY_USED +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MEMORY_USED 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_USED +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_USED 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_OVERFLOW +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_OVERFLOW 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_SIZE 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PARSER_STACK +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PARSER_STACK 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_SIZE 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_COUNT +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_COUNT 9L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FULLSCAN_STEP +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FULLSCAN_STEP 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_SORT +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_SORT 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_AUTOINDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_AUTOINDEX 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_VM_STEP +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_VM_STEP 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_REPREPARE +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_REPREPARE 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_RUN +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_RUN 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_MISS +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_MISS 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_HIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_HIT 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_MEMUSED +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_MEMUSED 99L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_NORMAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_NORMAL 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_FULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_FULL 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_DATAONLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_DATAONLY 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_STMT +#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_STMT 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_PROFILE +#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_PROFILE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_ROW +#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_ROW 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_CLOSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_CLOSE 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TXN_NONE +#define org_sqlite_jni_SQLite3Jni_SQLITE_TXN_NONE 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TXN_READ +#define org_sqlite_jni_SQLite3Jni_SQLITE_TXN_READ 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TXN_WRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_TXN_WRITE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DETERMINISTIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_DETERMINISTIC 2048L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DIRECTONLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_DIRECTONLY 524288L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INNOCUOUS +#define org_sqlite_jni_SQLite3Jni_SQLITE_INNOCUOUS 2097152L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_SCAN_UNIQUE +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_SCAN_UNIQUE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_EQ +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_EQ 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GT +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GT 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LE +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LE 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LT +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LT 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GE +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GE 32L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_MATCH +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_MATCH 64L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIKE +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIKE 65L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GLOB +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GLOB 66L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_REGEXP +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_REGEXP 67L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_NE +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_NE 68L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOT +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOT 69L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOTNULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNULL 71L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_IS +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_IS 72L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIMIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIMIT 73L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_OFFSET +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_OFFSET 74L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_FUNCTION +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_FUNCTION 150L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_CONSTRAINT_SUPPORT +#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_CONSTRAINT_SUPPORT 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_INNOCUOUS +#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_INNOCUOUS 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_DIRECTONLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_DIRECTONLY 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_USES_ALL_SCHEMAS +#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_USES_ALL_SCHEMAS 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ROLLBACK +#define org_sqlite_jni_SQLite3Jni_SQLITE_ROLLBACK 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FAIL +#define org_sqlite_jni_SQLite3Jni_SQLITE_FAIL 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_REPLACE +#define org_sqlite_jni_SQLite3Jni_SQLITE_REPLACE 5L +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: init + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_init + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: uncacheJniEnv + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_auto_extension + * Signature: (Lorg/sqlite/jni/AutoExtension;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1auto_1extension + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_blob + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I[BI)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1blob + (JNIEnv *, jclass, jobject, jint, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_double + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;ID)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1double + (JNIEnv *, jclass, jobject, jint, jdouble); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_int + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;II)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1int + (JNIEnv *, jclass, jobject, jint, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_int64 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;IJ)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1int64 + (JNIEnv *, jclass, jobject, jint, jlong); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_null + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1null + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_parameter_count + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1parameter_1count + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_parameter_index + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;[B)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1parameter_1index + (JNIEnv *, jclass, jobject, jbyteArray); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_text + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I[BI)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1text + (JNIEnv *, jclass, jobject, jint, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_zeroblob + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;II)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1zeroblob + (JNIEnv *, jclass, jobject, jint, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_zeroblob64 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;IJ)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1zeroblob64 + (JNIEnv *, jclass, jobject, jint, jlong); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_busy_handler + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/BusyHandler;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1busy_1handler + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_busy_timeout + * Signature: (Lorg/sqlite/jni/sqlite3;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1busy_1timeout + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_cancel_auto_extension + * Signature: (Lorg/sqlite/jni/AutoExtension;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1cancel_1auto_1extension + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_changes + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1changes + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_changes64 + * Signature: (Lorg/sqlite/jni/sqlite3;)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1changes64 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_clear_bindings + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1clear_1bindings + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_close + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1close + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_close_v2 + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1close_1v2 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_blob + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1blob + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_bytes + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1bytes + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_bytes16 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1bytes16 + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_count + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1count + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_double + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)D + */ +JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1double + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_int + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1int + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_int64 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1int64 + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_name + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1name + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_database_name + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1database_1name + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_origin_name + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1origin_1name + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_table_name + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1table_1name + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_text16 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text16 + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_text + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_type + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1type + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_value + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Lorg/sqlite/jni/sqlite3_value; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1value + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_collation_needed + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/CollationNeeded;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1collation_1needed + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_context_db_handle + * Signature: (Lorg/sqlite/jni/sqlite3_context;)Lorg/sqlite/jni/sqlite3; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1context_1db_1handle + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_commit_hook + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/CommitHook;)Lorg/sqlite/jni/CommitHook; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1commit_1hook + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_compileoption_get + * Signature: (I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1compileoption_1get + (JNIEnv *, jclass, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_compileoption_used + * Signature: (Ljava/lang/String;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1compileoption_1used + (JNIEnv *, jclass, jstring); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_create_collation + * Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;ILorg/sqlite/jni/Collation;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1create_1collation + (JNIEnv *, jclass, jobject, jstring, jint, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_create_function + * Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;IILorg/sqlite/jni/SQLFunction;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1create_1function + (JNIEnv *, jclass, jobject, jstring, jint, jint, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_data_count + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1data_1count + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_db_filename + * Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1filename + (JNIEnv *, jclass, jobject, jstring); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_db_config + * Signature: (Lorg/sqlite/jni/sqlite3;IILorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2 + (JNIEnv *, jclass, jobject, jint, jint, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_db_config + * Signature: (Lorg/sqlite/jni/sqlite3;ILjava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2 + (JNIEnv *, jclass, jobject, jint, jstring); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_db_status + * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;Z)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1status + (JNIEnv *, jclass, jobject, jint, jobject, jobject, jboolean); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_errcode + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1errcode + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_expanded_sql + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1expanded_1sql + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_extended_errcode + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1extended_1errcode + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_extended_result_codes + * Signature: (Lorg/sqlite/jni/sqlite3;Z)Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1extended_1result_1codes + (JNIEnv *, jclass, jobject, jboolean); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_errmsg + * Signature: (Lorg/sqlite/jni/sqlite3;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1errmsg + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_errstr + * Signature: (I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1errstr + (JNIEnv *, jclass, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_error_offset + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1error_1offset + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_finalize + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1finalize + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_initialize + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1initialize + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_last_insert_rowid + * Signature: (Lorg/sqlite/jni/sqlite3;)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1last_1insert_1rowid + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_libversion + * Signature: ()Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1libversion + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_libversion_number + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1libversion_1number + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_open + * Signature: (Ljava/lang/String;Lorg/sqlite/jni/OutputPointer/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1open + (JNIEnv *, jclass, jstring, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_open_v2 + * Signature: (Ljava/lang/String;Lorg/sqlite/jni/OutputPointer/sqlite3;ILjava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1open_1v2 + (JNIEnv *, jclass, jstring, jobject, jint, jstring); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_prepare + * Signature: (Lorg/sqlite/jni/sqlite3;[BILorg/sqlite/jni/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare + (JNIEnv *, jclass, jobject, jbyteArray, jint, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_prepare_v2 + * Signature: (Lorg/sqlite/jni/sqlite3;[BILorg/sqlite/jni/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v2 + (JNIEnv *, jclass, jobject, jbyteArray, jint, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_prepare_v3 + * Signature: (Lorg/sqlite/jni/sqlite3;[BIILorg/sqlite/jni/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v3 + (JNIEnv *, jclass, jobject, jbyteArray, jint, jint, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_progress_handler + * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/ProgressHandler;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1progress_1handler + (JNIEnv *, jclass, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_reset + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1reset + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_reset_auto_extension + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1reset_1auto_1extension + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_double + * Signature: (Lorg/sqlite/jni/sqlite3_context;D)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1double + (JNIEnv *, jclass, jobject, jdouble); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_error + * Signature: (Lorg/sqlite/jni/sqlite3_context;[BI)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error + (JNIEnv *, jclass, jobject, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_error_toobig + * Signature: (Lorg/sqlite/jni/sqlite3_context;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error_1toobig + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_error_nomem + * Signature: (Lorg/sqlite/jni/sqlite3_context;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error_1nomem + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_error_code + * Signature: (Lorg/sqlite/jni/sqlite3_context;I)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error_1code + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_null + * Signature: (Lorg/sqlite/jni/sqlite3_context;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1null + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_int + * Signature: (Lorg/sqlite/jni/sqlite3_context;I)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1int + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_int64 + * Signature: (Lorg/sqlite/jni/sqlite3_context;J)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1int64 + (JNIEnv *, jclass, jobject, jlong); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_java_object + * Signature: (Lorg/sqlite/jni/sqlite3_context;Ljava/lang/Object;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1java_1object + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_value + * Signature: (Lorg/sqlite/jni/sqlite3_context;Lorg/sqlite/jni/sqlite3_value;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1value + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_zeroblob + * Signature: (Lorg/sqlite/jni/sqlite3_context;I)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1zeroblob + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_zeroblob64 + * Signature: (Lorg/sqlite/jni/sqlite3_context;J)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1zeroblob64 + (JNIEnv *, jclass, jobject, jlong); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_blob + * Signature: (Lorg/sqlite/jni/sqlite3_context;[BI)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1blob + (JNIEnv *, jclass, jobject, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_blob64 + * Signature: (Lorg/sqlite/jni/sqlite3_context;[BJ)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1blob64 + (JNIEnv *, jclass, jobject, jbyteArray, jlong); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_text + * Signature: (Lorg/sqlite/jni/sqlite3_context;[BI)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text + (JNIEnv *, jclass, jobject, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_text64 + * Signature: (Lorg/sqlite/jni/sqlite3_context;[BJI)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text64 + (JNIEnv *, jclass, jobject, jbyteArray, jlong, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_status + * Signature: (ILorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;Z)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1status + (JNIEnv *, jclass, jint, jobject, jobject, jboolean); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_status64 + * Signature: (ILorg/sqlite/jni/OutputPointer/Int64;Lorg/sqlite/jni/OutputPointer/Int64;Z)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1status64 + (JNIEnv *, jclass, jint, jobject, jobject, jboolean); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_rollback_hook + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/RollbackHook;)Lorg/sqlite/jni/RollbackHook; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1rollback_1hook + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_set_authorizer + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/Authorizer;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1set_1authorizer + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_set_last_insert_rowid + * Signature: (Lorg/sqlite/jni/sqlite3;J)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1set_1last_1insert_1rowid + (JNIEnv *, jclass, jobject, jlong); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_sleep + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1sleep + (JNIEnv *, jclass, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_sourceid + * Signature: ()Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1sourceid + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_sql + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1sql + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_step + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1step + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_strglob + * Signature: ([B[B)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1strglob + (JNIEnv *, jclass, jbyteArray, jbyteArray); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_strlike + * Signature: ([B[BI)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1strlike + (JNIEnv *, jclass, jbyteArray, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_threadsafe + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1threadsafe + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_total_changes + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1total_1changes + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_total_changes64 + * Signature: (Lorg/sqlite/jni/sqlite3;)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1total_1changes64 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_trace_v2 + * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/Tracer;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1trace_1v2 + (JNIEnv *, jclass, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_update_hook + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/UpdateHook;)Lorg/sqlite/jni/UpdateHook; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1update_1hook + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_blob + * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1blob + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_bytes + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1bytes + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_bytes16 + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1bytes16 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_double + * Signature: (Lorg/sqlite/jni/sqlite3_value;)D + */ +JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1double + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_dupe + * Signature: (Lorg/sqlite/jni/sqlite3_value;)Lorg/sqlite/jni/sqlite3_value; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1dupe + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_encoding + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1encoding + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_free + * Signature: (Lorg/sqlite/jni/sqlite3_value;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1free + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_int + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1int + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_int64 + * Signature: (Lorg/sqlite/jni/sqlite3_value;)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1int64 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_java_object + * Signature: (Lorg/sqlite/jni/sqlite3_value;)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1java_1object + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_text + * Signature: (Lorg/sqlite/jni/sqlite3_value;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_text_utf8 + * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text_1utf8 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_text16 + * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text16 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_text16le + * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text16le + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_text16be + * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text16be + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_type + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1type + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_numeric_type + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1numeric_1type + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_nochange + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1nochange + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_frombind + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1frombind + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_subtype + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1subtype + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_shutdown + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1shutdown + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_do_something_for_developer + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1do_1something_1for_1developer + (JNIEnv *, jclass); + +#ifdef __cplusplus +} +#endif +#endif +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_sqlite_jni_Fts5ExtensionApi */ + +#ifndef _Included_org_sqlite_jni_Fts5ExtensionApi +#define _Included_org_sqlite_jni_Fts5ExtensionApi +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: getInstance + * Signature: ()Lorg/sqlite/jni/Fts5ExtensionApi; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_getInstance + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xColumnCount + * Signature: (Lorg/sqlite/jni/Fts5Context;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnCount + (JNIEnv *, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xColumnSize + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnSize + (JNIEnv *, jobject, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xColumnText + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/String;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnText + (JNIEnv *, jobject, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xColumnTotalSize + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/Int64;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnTotalSize + (JNIEnv *, jobject, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xGetAuxdata + * Signature: (Lorg/sqlite/jni/Fts5Context;Z)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xGetAuxdata + (JNIEnv *, jobject, jobject, jboolean); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xInst + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xInst + (JNIEnv *, jobject, jobject, jint, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xInstCount + * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xInstCount + (JNIEnv *, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xPhraseCount + * Signature: (Lorg/sqlite/jni/Fts5Context;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseCount + (JNIEnv *, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xPhraseFirst + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseFirst + (JNIEnv *, jobject, jobject, jint, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xPhraseFirstColumn + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseFirstColumn + (JNIEnv *, jobject, jobject, jint, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xPhraseNext + * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseNext + (JNIEnv *, jobject, jobject, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xPhraseNextColumn + * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseNextColumn + (JNIEnv *, jobject, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xPhraseSize + * Signature: (Lorg/sqlite/jni/Fts5Context;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseSize + (JNIEnv *, jobject, jobject, jint); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xQueryPhrase + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/Fts5ExtensionApi/xQueryPhraseCallback;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xQueryPhrase + (JNIEnv *, jobject, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xRowCount + * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/OutputPointer/Int64;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xRowCount + (JNIEnv *, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xRowid + * Signature: (Lorg/sqlite/jni/Fts5Context;)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xRowid + (JNIEnv *, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xSetAuxdata + * Signature: (Lorg/sqlite/jni/Fts5Context;Ljava/lang/Object;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xSetAuxdata + (JNIEnv *, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xTokenize + * Signature: (Lorg/sqlite/jni/Fts5Context;[BLorg/sqlite/jni/Fts5/xTokenizeCallback;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xTokenize + (JNIEnv *, jobject, jobject, jbyteArray, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xUserData + * Signature: (Lorg/sqlite/jni/Fts5Context;)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xUserData + (JNIEnv *, jobject, jobject); + +#ifdef __cplusplus +} +#endif +#endif +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_sqlite_jni_fts5_api */ + +#ifndef _Included_org_sqlite_jni_fts5_api +#define _Included_org_sqlite_jni_fts5_api +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_sqlite_jni_fts5_api + * Method: getInstanceForDb + * Signature: (Lorg/sqlite/jni/sqlite3;)Lorg/sqlite/jni/fts5_api; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_1api_getInstanceForDb + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_fts5_api + * Method: xCreateFunction + * Signature: (Ljava/lang/String;Ljava/lang/Object;Lorg/sqlite/jni/fts5_extension_function;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_1api_xCreateFunction + (JNIEnv *, jobject, jstring, jobject, jobject); + +#ifdef __cplusplus +} +#endif +#endif +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_sqlite_jni_fts5_tokenizer */ + +#ifndef _Included_org_sqlite_jni_fts5_tokenizer +#define _Included_org_sqlite_jni_fts5_tokenizer +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_sqlite_jni_fts5_tokenizer + * Method: xTokenize + * Signature: (Lorg/sqlite/jni/Fts5Tokenizer;I[BLorg/sqlite/jni/Fts5/xTokenizeCallback;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_1tokenizer_xTokenize + (JNIEnv *, jobject, jobject, jint, jbyteArray, jobject); + +#ifdef __cplusplus +} +#endif +#endif +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_sqlite_jni_tester_SQLTester */ + +#ifndef _Included_org_sqlite_jni_tester_SQLTester +#define _Included_org_sqlite_jni_tester_SQLTester +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_sqlite_jni_tester_SQLTester + * Method: strglob + * Signature: ([B[B)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_tester_SQLTester_strglob + (JNIEnv *, jclass, jbyteArray, jbyteArray); + +/* + * Class: org_sqlite_jni_tester_SQLTester + * Method: installCustomExtensions + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions + (JNIEnv *, jclass); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/ext/jni/src/org/sqlite/jni/Authorizer.java b/ext/jni/src/org/sqlite/jni/Authorizer.java new file mode 100644 index 0000000000..114c27fc63 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Authorizer.java @@ -0,0 +1,32 @@ +/* +** 2023-08-05 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + A callback for use with sqlite3_set_authorizer(). +*/ +public interface Authorizer { + /** + Must functions as described for the sqlite3_set_authorizer() + callback, with one caveat: the string values passed here were + initially (at the C level) encoded in standard UTF-8. If they + contained any constructs which are not compatible with MUTF-8, + these strings will not have the expected values. For further + details, see the documentation for the SQLite3Jni class. + + Must not throw. + */ + int xAuth(int opId, @Nullable String s1, @Nullable String s2, + @Nullable String s3, @Nullable String s4); +} diff --git a/ext/jni/src/org/sqlite/jni/AutoExtension.java b/ext/jni/src/org/sqlite/jni/AutoExtension.java new file mode 100644 index 0000000000..a2ab6a0f75 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/AutoExtension.java @@ -0,0 +1,31 @@ +/* +** 2023-08-05 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + A callback for use with sqlite3_auto_extension(). +*/ +public interface AutoExtension { + /** + Must function as described for the sqlite3_auto_extension(), + with the caveat that the signature is more limited. + + As an exception (as it were) to the callbacks-must-not-throw + rule, AutoExtensions may do so and the exception's error message + will be set as the db's error string. + + Results are undefined if db is closed by an auto-extension. + */ + int xEntryPoint(sqlite3 db); +} diff --git a/ext/jni/src/org/sqlite/jni/BusyHandler.java b/ext/jni/src/org/sqlite/jni/BusyHandler.java new file mode 100644 index 0000000000..8ce729c904 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/BusyHandler.java @@ -0,0 +1,45 @@ +/* +** 2023-07-22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + Callback proxy for use with sqlite3_busy_handler(). +*/ +public abstract class BusyHandler { + /** + Must function as documented for the sqlite3_busy_handler() + callback argument, minus the (void*) argument the C-level + function requires. + + Any exceptions thrown by this callback are suppressed in order to + retain the C-style API semantics of the JNI bindings. + */ + public abstract int xCallback(int n); + + /** + Optionally override to perform any cleanup when this busy + handler is destroyed. It is destroyed when: + + - The associated db is passed to sqlite3_close() or + sqlite3_close_v2(). + + - sqlite3_busy_handler() is called to replace the handler, + whether it's passed a null handler or any other instance of + this class. + + - sqlite3_busy_timeout() is called, which implicitly installs + a busy handler. + */ + public void xDestroy(){} +} diff --git a/ext/jni/src/org/sqlite/jni/Collation.java b/ext/jni/src/org/sqlite/jni/Collation.java new file mode 100644 index 0000000000..a05b8ef9ef --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Collation.java @@ -0,0 +1,28 @@ +/* +** 2023-07-22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** +*/ +public abstract class Collation { + /** + Must compare the given byte arrays using memcmp() semantics. + */ + public abstract int xCompare(byte[] lhs, byte[] rhs); + /** + Called by SQLite when the collation is destroyed. If a Collation + requires custom cleanup, override this method. + */ + public void xDestroy() {} +} diff --git a/ext/jni/src/org/sqlite/jni/CollationNeeded.java b/ext/jni/src/org/sqlite/jni/CollationNeeded.java new file mode 100644 index 0000000000..85214a1d27 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/CollationNeeded.java @@ -0,0 +1,28 @@ +/* +** 2023-07-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 is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + Callback proxy for use with sqlite3_collation_needed(). +*/ +public interface CollationNeeded { + /** + Has the same semantics as the C-level sqlite3_create_collation() + callback. + + If it throws, the exception message is passed on to the db and + the exception is suppressed. + */ + int xCollationNeeded(sqlite3 db, int eTextRep, String collationName); +} diff --git a/ext/jni/src/org/sqlite/jni/CommitHook.java b/ext/jni/src/org/sqlite/jni/CommitHook.java new file mode 100644 index 0000000000..eaa75a0040 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/CommitHook.java @@ -0,0 +1,25 @@ +/* +** 2023-07-22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + Callback proxy for use with sqlite3_commit_hook(). +*/ +public interface CommitHook { + /** + Works as documented for the sqlite3_commit_hook() callback. + Must not throw. + */ + int xCommitHook(); +} diff --git a/ext/jni/src/org/sqlite/jni/Fts5.java b/ext/jni/src/org/sqlite/jni/Fts5.java new file mode 100644 index 0000000000..102cf575a8 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Fts5.java @@ -0,0 +1,38 @@ +/* +** 2023-08-05 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + INCOMPLETE AND COMPLETELY UNTESTED. + + A wrapper for communicating C-level (fts5_api*) instances with + Java. These wrappers do not own their associated pointer, they + simply provide a type-safe way to communicate it between Java and C + via JNI. +*/ +public final class Fts5 { + /* Not used */ + private Fts5(){} + + //! Callback type for use with xTokenize() variants + public static interface xTokenizeCallback { + int xToken(int tFlags, byte txt[], int iStart, int iEnd); + } + + public static final int FTS5_TOKENIZE_QUERY = 0x0001; + public static final int FTS5_TOKENIZE_PREFIX = 0x0002; + public static final int FTS5_TOKENIZE_DOCUMENT = 0x0004; + public static final int FTS5_TOKENIZE_AUX = 0x0008; + public static final int FTS5_TOKEN_COLOCATED = 0x0001; +} diff --git a/ext/jni/src/org/sqlite/jni/Fts5Context.java b/ext/jni/src/org/sqlite/jni/Fts5Context.java new file mode 100644 index 0000000000..e78f67d556 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Fts5Context.java @@ -0,0 +1,23 @@ +/* +** 2023-08-04 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + A wrapper for communicating C-level (Fts5Context*) instances with + Java. These wrappers do not own their associated pointer, they + simply provide a type-safe way to communicate it between Java and C + via JNI. +*/ +public final class Fts5Context extends NativePointerHolder { +} diff --git a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java new file mode 100644 index 0000000000..ac041e3001 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java @@ -0,0 +1,86 @@ +/* +** 2023-08-04 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; +import java.nio.charset.StandardCharsets; + +/** + ALMOST COMPLETELY UNTESTED. + + FAR FROM COMPLETE and the feasibility of binding this to Java + is still undetermined. This might be removed. +*/ +public final class Fts5ExtensionApi extends NativePointerHolder { + //! Only called from JNI + private Fts5ExtensionApi(){} + private int iVersion = 2; + + /* Callback type for used by xQueryPhrase(). */ + public static interface xQueryPhraseCallback { + int xCallback(Fts5ExtensionApi fapi, Fts5Context cx); + } + + /** + Returns the singleton instance of this class. + */ + public static synchronized native Fts5ExtensionApi getInstance(); + + public synchronized native int xColumnCount(@NotNull Fts5Context fcx); + public synchronized native int xColumnSize(@NotNull Fts5Context cx, int iCol, + @NotNull OutputPointer.Int32 pnToken); + public synchronized native int xColumnText(@NotNull Fts5Context cx, int iCol, + @NotNull OutputPointer.String txt); + public synchronized native int xColumnTotalSize(@NotNull Fts5Context fcx, int iCol, + @NotNull OutputPointer.Int64 pnToken); + public synchronized native Object xGetAuxdata(@NotNull Fts5Context cx, boolean clearIt); + public synchronized native int xInst(@NotNull Fts5Context cx, int iIdx, + @NotNull OutputPointer.Int32 piPhrase, + @NotNull OutputPointer.Int32 piCol, + @NotNull OutputPointer.Int32 piOff); + public synchronized native int xInstCount(@NotNull Fts5Context fcx, + @NotNull OutputPointer.Int32 pnInst); + public synchronized native int xPhraseCount(@NotNull Fts5Context fcx); + public synchronized native int xPhraseFirst(@NotNull Fts5Context cx, int iPhrase, + @NotNull Fts5PhraseIter iter, + @NotNull OutputPointer.Int32 iCol, + @NotNull OutputPointer.Int32 iOff); + public synchronized native int xPhraseFirstColumn(@NotNull Fts5Context cx, int iPhrase, + @NotNull Fts5PhraseIter iter, + @NotNull OutputPointer.Int32 iCol); + public synchronized native void xPhraseNext(@NotNull Fts5Context cx, + @NotNull Fts5PhraseIter iter, + @NotNull OutputPointer.Int32 iCol, + @NotNull OutputPointer.Int32 iOff); + public synchronized native void xPhraseNextColumn(@NotNull Fts5Context cx, + @NotNull Fts5PhraseIter iter, + @NotNull OutputPointer.Int32 iCol); + public synchronized native int xPhraseSize(@NotNull Fts5Context fcx, int iPhrase); + public synchronized native int xQueryPhrase(@NotNull Fts5Context cx, int iPhrase, + @NotNull xQueryPhraseCallback callback); + public synchronized native int xRowCount(@NotNull Fts5Context fcx, + @NotNull OutputPointer.Int64 nRow); + public synchronized native long xRowid(@NotNull Fts5Context cx); + /* Note that the JNI binding lacks the C version's xDelete() + callback argument. Instead, if pAux has an xDestroy() method, it + is called if the FTS5 API finalizes the aux state (including if + allocation of storage for the auxdata fails). Any reference to + pAux held by the JNI layer will be relinquished regardless of + whether pAux has an xDestroy() method. */ + public synchronized native int xSetAuxdata(@NotNull Fts5Context cx, @Nullable Object pAux); + public synchronized native int xTokenize(@NotNull Fts5Context cx, @NotNull byte pText[], + @NotNull Fts5.xTokenizeCallback callback); + + public synchronized native Object xUserData(Fts5Context cx); + //^^^ returns the pointer passed as the 3rd arg to the C-level + // fts5_api::xCreateFunction. +} diff --git a/ext/jni/src/org/sqlite/jni/Fts5Function.java b/ext/jni/src/org/sqlite/jni/Fts5Function.java new file mode 100644 index 0000000000..463ec034f5 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Fts5Function.java @@ -0,0 +1,27 @@ +/* +** 2023-08-04 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + Fts5Function is used in conjunction with the + sqlite3_create_fts_function() JNI-bound API to give that native code + access to the callback functions needed in order to implement + FTS5 auxiliary functions in Java. +*/ +public abstract class Fts5Function { + + public abstract void xFunction(Fts5ExtensionApi pApi, Fts5Context pFts, + sqlite3_context pCtx, sqlite3_value argv[]); + public void xDestroy() {} +} diff --git a/ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java b/ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java new file mode 100644 index 0000000000..eb4e05fdf8 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java @@ -0,0 +1,24 @@ +/* +** 2023-08-04 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + A wrapper for C-level Fts5PhraseIter. They are only modified and + inspected by native-level code. +*/ +public final class Fts5PhraseIter extends NativePointerHolder { + //! Updated and used only by native code. + private long a; + private long b; +} diff --git a/ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java b/ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java new file mode 100644 index 0000000000..0d266a13d8 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java @@ -0,0 +1,30 @@ +/* +** 2023-08-05x +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + INCOMPLETE AND COMPLETELY UNTESTED. + + A wrapper for communicating C-level (Fts5Tokenizer*) instances with + Java. These wrappers do not own their associated pointer, they + simply provide a type-safe way to communicate it between Java and C + via JNI. + + At the C level, the Fts5Tokenizer type is essentially a void + pointer used specifically for tokenizers. +*/ +public final class Fts5Tokenizer extends NativePointerHolder { + //! Only called from JNI. + private Fts5Tokenizer(){} +} diff --git a/ext/jni/src/org/sqlite/jni/NativePointerHolder.java b/ext/jni/src/org/sqlite/jni/NativePointerHolder.java new file mode 100644 index 0000000000..afe2618a00 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/NativePointerHolder.java @@ -0,0 +1,33 @@ +/* +** 2023-07-21 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + A helper for passing pointers between JNI C code and Java, in + particular for output pointers of high-level object types in the + sqlite3 C API, e.g. (sqlite3**) and (sqlite3_stmt**). This is + intended to be subclassed and the ContextType is intended to be the + class which is doing the subclassing. The intent of the ContextType + is strictly to provide some level of type safety by avoiding that + NativePointerHolder is not inadvertently passed to an incompatible + function signature. + + These objects do not _own_ the pointer they refer to. They are + intended simply to communicate that pointer between C and Java. +*/ +public class NativePointerHolder { + //! Only set from JNI, where access permissions don't matter. + private long nativePointer = 0; + public final long getNativePointer(){ return nativePointer; } +} diff --git a/ext/jni/src/org/sqlite/jni/OutputPointer.java b/ext/jni/src/org/sqlite/jni/OutputPointer.java new file mode 100644 index 0000000000..82a90c9185 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/OutputPointer.java @@ -0,0 +1,165 @@ +/* +** 2023-07-21 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + Helper classes for handling JNI output pointers. + + We do not use a generic OutputPointer because working with those + from the native JNI code is unduly quirky due to a lack of + autoboxing at that level. + + The usage is similar for all of thes types: + + ``` + OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + assert( null==out.get() ); + int rc = sqlite3_open(":memory:", out); + if( 0!=rc ) ... error; + assert( null!=out.get() ); + sqlite3 db = out.take(); + assert( null==out.get() ); + ``` + + With the minor exception that the primitive types permit direct + access to the object's value via the `value` property, whereas the + JNI-level opaque types do not permit client-level code to set that + property. +*/ +public final class OutputPointer { + + /** + Output pointer for use with routines, such as sqlite3_open(), + which return a database handle via an output pointer. These + pointers can only be set by the JNI layer, not by client-level + code. + */ + public static final class sqlite3 { + private org.sqlite.jni.sqlite3 value; + //! Initializes with a null value. + public sqlite3(){value = null;} + //! Sets the current value to null. + public void clear(){value = null;} + //! Returns the current value. + public final org.sqlite.jni.sqlite3 get(){return value;} + //! Equivalent to calling get() then clear(). + public final org.sqlite.jni.sqlite3 take(){ + final org.sqlite.jni.sqlite3 v = value; + value = null; + return v; + } + } + + /** + Output pointer for use with routines, such as sqlite3_prepare(), + which return a statement handle via an output pointer. These + pointers can only be set by the JNI layer, not by client-level + code. + */ + public static final class sqlite3_stmt { + private org.sqlite.jni.sqlite3_stmt value; + //! Initializes with a null value. + public sqlite3_stmt(){value = null;} + //! Sets the current value to null. + public void clear(){value = null;} + //! Returns the current value. + public final org.sqlite.jni.sqlite3_stmt get(){return value;} + //! Equivalent to calling get() then clear(). + public final org.sqlite.jni.sqlite3_stmt take(){ + final org.sqlite.jni.sqlite3_stmt v = value; + value = null; + return v; + } + } + + /** + Output pointer for use with native routines which return integers via + output pointers. + */ + public static final class Int32 { + /** + This is public for ease of use. Accessors are provided for + consistency with the higher-level types. + */ + public int value; + //! Initializes with the value 0. + public Int32(){this(0);} + //! Initializes with the value v. + public Int32(int v){value = v;} + //! Returns the current value. + public final int get(){return value;} + //! Sets the current value to v. + public final void set(int v){value = v;} + } + + /** + Output pointer for use with native routines which return 64-bit integers + via output pointers. + */ + public static final class Int64 { + /** + This is public for ease of use. Accessors are provided for + consistency with the higher-level types. + */ + public long value; + //! Initializes with the value 0. + public Int64(){this(0);} + //! Initializes with the value v. + public Int64(long v){value = v;} + //! Returns the current value. + public final long get(){return value;} + //! Sets the current value. + public final void set(long v){value = v;} + } + + /** + Output pointer for use with native routines which return strings via + output pointers. + */ + public static final class String { + /** + This is public for ease of use. Accessors are provided for + consistency with the higher-level types. + */ + public java.lang.String value; + //! Initializes with a null value. + public String(){this(null);} + //! Initializes with the value v. + public String(java.lang.String v){value = v;} + //! Returns the current value. + public final java.lang.String get(){return value;} + //! Sets the current value. + public final void set(java.lang.String v){value = v;} + } + + /** + Output pointer for use with native routines which return byte + arrays via output pointers. + */ + public static final class ByteArray { + /** + This is public for ease of use. Accessors are provided for + consistency with the higher-level types. + */ + public byte[] value; + //! Initializes with the value null. + public ByteArray(){this(null);} + //! Initializes with the value v. + public ByteArray(byte[] v){value = v;} + //! Returns the current value. + public final byte[] get(){return value;} + //! Sets the current value. + public final void set(byte[] v){value = v;} + } +} diff --git a/ext/jni/src/org/sqlite/jni/ProgressHandler.java b/ext/jni/src/org/sqlite/jni/ProgressHandler.java new file mode 100644 index 0000000000..c806eebca0 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/ProgressHandler.java @@ -0,0 +1,27 @@ +/* +** 2023-07-22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + Callback proxy for use with sqlite3_progress_handler(). +*/ +public interface ProgressHandler { + /** + Works as documented for the sqlite3_progress_handler() callback. + + If it throws, the exception message is passed on to the db and + the exception is suppressed. + */ + int xCallback(); +} diff --git a/ext/jni/src/org/sqlite/jni/ResultCode.java b/ext/jni/src/org/sqlite/jni/ResultCode.java new file mode 100644 index 0000000000..0989bc744d --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/ResultCode.java @@ -0,0 +1,155 @@ +/* +** 2023-07-21 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + This enum contains all of the core and "extended" result codes used + by the sqlite3 library. It is provided not for use with the C-style + API (with which it won't work) but for higher-level code which may + find it useful to map SQLite result codes to human-readable names. +*/ +public enum ResultCode { + SQLITE_OK(SQLite3Jni.SQLITE_OK), + SQLITE_ERROR(SQLite3Jni.SQLITE_ERROR), + SQLITE_INTERNAL(SQLite3Jni.SQLITE_INTERNAL), + SQLITE_PERM(SQLite3Jni.SQLITE_PERM), + SQLITE_ABORT(SQLite3Jni.SQLITE_ABORT), + SQLITE_BUSY(SQLite3Jni.SQLITE_BUSY), + SQLITE_LOCKED(SQLite3Jni.SQLITE_LOCKED), + SQLITE_NOMEM(SQLite3Jni.SQLITE_NOMEM), + SQLITE_READONLY(SQLite3Jni.SQLITE_READONLY), + SQLITE_INTERRUPT(SQLite3Jni.SQLITE_INTERRUPT), + SQLITE_IOERR(SQLite3Jni.SQLITE_IOERR), + SQLITE_CORRUPT(SQLite3Jni.SQLITE_CORRUPT), + SQLITE_NOTFOUND(SQLite3Jni.SQLITE_NOTFOUND), + SQLITE_FULL(SQLite3Jni.SQLITE_FULL), + SQLITE_CANTOPEN(SQLite3Jni.SQLITE_CANTOPEN), + SQLITE_PROTOCOL(SQLite3Jni.SQLITE_PROTOCOL), + SQLITE_EMPTY(SQLite3Jni.SQLITE_EMPTY), + SQLITE_SCHEMA(SQLite3Jni.SQLITE_SCHEMA), + SQLITE_TOOBIG(SQLite3Jni.SQLITE_TOOBIG), + SQLITE_CONSTRAINT(SQLite3Jni.SQLITE_CONSTRAINT), + SQLITE_MISMATCH(SQLite3Jni.SQLITE_MISMATCH), + SQLITE_MISUSE(SQLite3Jni.SQLITE_MISUSE), + SQLITE_NOLFS(SQLite3Jni.SQLITE_NOLFS), + SQLITE_AUTH(SQLite3Jni.SQLITE_AUTH), + SQLITE_FORMAT(SQLite3Jni.SQLITE_FORMAT), + SQLITE_RANGE(SQLite3Jni.SQLITE_RANGE), + SQLITE_NOTADB(SQLite3Jni.SQLITE_NOTADB), + SQLITE_NOTICE(SQLite3Jni.SQLITE_NOTICE), + SQLITE_WARNING(SQLite3Jni.SQLITE_WARNING), + SQLITE_ROW(SQLite3Jni.SQLITE_ROW), + SQLITE_DONE(SQLite3Jni.SQLITE_DONE), + SQLITE_ERROR_MISSING_COLLSEQ(SQLite3Jni.SQLITE_ERROR_MISSING_COLLSEQ), + SQLITE_ERROR_RETRY(SQLite3Jni.SQLITE_ERROR_RETRY), + SQLITE_ERROR_SNAPSHOT(SQLite3Jni.SQLITE_ERROR_SNAPSHOT), + SQLITE_IOERR_READ(SQLite3Jni.SQLITE_IOERR_READ), + SQLITE_IOERR_SHORT_READ(SQLite3Jni.SQLITE_IOERR_SHORT_READ), + SQLITE_IOERR_WRITE(SQLite3Jni.SQLITE_IOERR_WRITE), + SQLITE_IOERR_FSYNC(SQLite3Jni.SQLITE_IOERR_FSYNC), + SQLITE_IOERR_DIR_FSYNC(SQLite3Jni.SQLITE_IOERR_DIR_FSYNC), + SQLITE_IOERR_TRUNCATE(SQLite3Jni.SQLITE_IOERR_TRUNCATE), + SQLITE_IOERR_FSTAT(SQLite3Jni.SQLITE_IOERR_FSTAT), + SQLITE_IOERR_UNLOCK(SQLite3Jni.SQLITE_IOERR_UNLOCK), + SQLITE_IOERR_RDLOCK(SQLite3Jni.SQLITE_IOERR_RDLOCK), + SQLITE_IOERR_DELETE(SQLite3Jni.SQLITE_IOERR_DELETE), + SQLITE_IOERR_BLOCKED(SQLite3Jni.SQLITE_IOERR_BLOCKED), + SQLITE_IOERR_NOMEM(SQLite3Jni.SQLITE_IOERR_NOMEM), + SQLITE_IOERR_ACCESS(SQLite3Jni.SQLITE_IOERR_ACCESS), + SQLITE_IOERR_CHECKRESERVEDLOCK(SQLite3Jni.SQLITE_IOERR_CHECKRESERVEDLOCK), + SQLITE_IOERR_LOCK(SQLite3Jni.SQLITE_IOERR_LOCK), + SQLITE_IOERR_CLOSE(SQLite3Jni.SQLITE_IOERR_CLOSE), + SQLITE_IOERR_DIR_CLOSE(SQLite3Jni.SQLITE_IOERR_DIR_CLOSE), + SQLITE_IOERR_SHMOPEN(SQLite3Jni.SQLITE_IOERR_SHMOPEN), + SQLITE_IOERR_SHMSIZE(SQLite3Jni.SQLITE_IOERR_SHMSIZE), + SQLITE_IOERR_SHMLOCK(SQLite3Jni.SQLITE_IOERR_SHMLOCK), + SQLITE_IOERR_SHMMAP(SQLite3Jni.SQLITE_IOERR_SHMMAP), + SQLITE_IOERR_SEEK(SQLite3Jni.SQLITE_IOERR_SEEK), + SQLITE_IOERR_DELETE_NOENT(SQLite3Jni.SQLITE_IOERR_DELETE_NOENT), + SQLITE_IOERR_MMAP(SQLite3Jni.SQLITE_IOERR_MMAP), + SQLITE_IOERR_GETTEMPPATH(SQLite3Jni.SQLITE_IOERR_GETTEMPPATH), + SQLITE_IOERR_CONVPATH(SQLite3Jni.SQLITE_IOERR_CONVPATH), + SQLITE_IOERR_VNODE(SQLite3Jni.SQLITE_IOERR_VNODE), + SQLITE_IOERR_AUTH(SQLite3Jni.SQLITE_IOERR_AUTH), + SQLITE_IOERR_BEGIN_ATOMIC(SQLite3Jni.SQLITE_IOERR_BEGIN_ATOMIC), + SQLITE_IOERR_COMMIT_ATOMIC(SQLite3Jni.SQLITE_IOERR_COMMIT_ATOMIC), + SQLITE_IOERR_ROLLBACK_ATOMIC(SQLite3Jni.SQLITE_IOERR_ROLLBACK_ATOMIC), + SQLITE_IOERR_DATA(SQLite3Jni.SQLITE_IOERR_DATA), + SQLITE_IOERR_CORRUPTFS(SQLite3Jni.SQLITE_IOERR_CORRUPTFS), + SQLITE_LOCKED_SHAREDCACHE(SQLite3Jni.SQLITE_LOCKED_SHAREDCACHE), + SQLITE_LOCKED_VTAB(SQLite3Jni.SQLITE_LOCKED_VTAB), + SQLITE_BUSY_RECOVERY(SQLite3Jni.SQLITE_BUSY_RECOVERY), + SQLITE_BUSY_SNAPSHOT(SQLite3Jni.SQLITE_BUSY_SNAPSHOT), + SQLITE_BUSY_TIMEOUT(SQLite3Jni.SQLITE_BUSY_TIMEOUT), + SQLITE_CANTOPEN_NOTEMPDIR(SQLite3Jni.SQLITE_CANTOPEN_NOTEMPDIR), + SQLITE_CANTOPEN_ISDIR(SQLite3Jni.SQLITE_CANTOPEN_ISDIR), + SQLITE_CANTOPEN_FULLPATH(SQLite3Jni.SQLITE_CANTOPEN_FULLPATH), + SQLITE_CANTOPEN_CONVPATH(SQLite3Jni.SQLITE_CANTOPEN_CONVPATH), + SQLITE_CANTOPEN_SYMLINK(SQLite3Jni.SQLITE_CANTOPEN_SYMLINK), + SQLITE_CORRUPT_VTAB(SQLite3Jni.SQLITE_CORRUPT_VTAB), + SQLITE_CORRUPT_SEQUENCE(SQLite3Jni.SQLITE_CORRUPT_SEQUENCE), + SQLITE_CORRUPT_INDEX(SQLite3Jni.SQLITE_CORRUPT_INDEX), + SQLITE_READONLY_RECOVERY(SQLite3Jni.SQLITE_READONLY_RECOVERY), + SQLITE_READONLY_CANTLOCK(SQLite3Jni.SQLITE_READONLY_CANTLOCK), + SQLITE_READONLY_ROLLBACK(SQLite3Jni.SQLITE_READONLY_ROLLBACK), + SQLITE_READONLY_DBMOVED(SQLite3Jni.SQLITE_READONLY_DBMOVED), + SQLITE_READONLY_CANTINIT(SQLite3Jni.SQLITE_READONLY_CANTINIT), + SQLITE_READONLY_DIRECTORY(SQLite3Jni.SQLITE_READONLY_DIRECTORY), + SQLITE_ABORT_ROLLBACK(SQLite3Jni.SQLITE_ABORT_ROLLBACK), + SQLITE_CONSTRAINT_CHECK(SQLite3Jni.SQLITE_CONSTRAINT_CHECK), + SQLITE_CONSTRAINT_COMMITHOOK(SQLite3Jni.SQLITE_CONSTRAINT_COMMITHOOK), + SQLITE_CONSTRAINT_FOREIGNKEY(SQLite3Jni.SQLITE_CONSTRAINT_FOREIGNKEY), + SQLITE_CONSTRAINT_FUNCTION(SQLite3Jni.SQLITE_CONSTRAINT_FUNCTION), + SQLITE_CONSTRAINT_NOTNULL(SQLite3Jni.SQLITE_CONSTRAINT_NOTNULL), + SQLITE_CONSTRAINT_PRIMARYKEY(SQLite3Jni.SQLITE_CONSTRAINT_PRIMARYKEY), + SQLITE_CONSTRAINT_TRIGGER(SQLite3Jni.SQLITE_CONSTRAINT_TRIGGER), + SQLITE_CONSTRAINT_UNIQUE(SQLite3Jni.SQLITE_CONSTRAINT_UNIQUE), + SQLITE_CONSTRAINT_VTAB(SQLite3Jni.SQLITE_CONSTRAINT_VTAB), + SQLITE_CONSTRAINT_ROWID(SQLite3Jni.SQLITE_CONSTRAINT_ROWID), + SQLITE_CONSTRAINT_PINNED(SQLite3Jni.SQLITE_CONSTRAINT_PINNED), + SQLITE_CONSTRAINT_DATATYPE(SQLite3Jni.SQLITE_CONSTRAINT_DATATYPE), + SQLITE_NOTICE_RECOVER_WAL(SQLite3Jni.SQLITE_NOTICE_RECOVER_WAL), + SQLITE_NOTICE_RECOVER_ROLLBACK(SQLite3Jni.SQLITE_NOTICE_RECOVER_ROLLBACK), + SQLITE_WARNING_AUTOINDEX(SQLite3Jni.SQLITE_WARNING_AUTOINDEX), + SQLITE_AUTH_USER(SQLite3Jni.SQLITE_AUTH_USER), + SQLITE_OK_LOAD_PERMANENTLY(SQLite3Jni.SQLITE_OK_LOAD_PERMANENTLY); + + public final int value; + + ResultCode(int rc){ + value = rc; + ResultCodeMap.set(rc, this); + } + + /** + Returns the entry from this enum for the given result code, or + null if no match is found. + */ + public static ResultCode getEntryForInt(int rc){ + return ResultCodeMap.get(rc); + } + + /** + Internal level of indirection required because we cannot initialize + static enum members in an enum before the enum constructor is + invoked. + */ + private static final class ResultCodeMap { + private static final java.util.Map i2e + = new java.util.HashMap<>(); + private static void set(int rc, ResultCode e){ i2e.put(rc, e); } + private static ResultCode get(int rc){ return i2e.get(rc); } + } + +} diff --git a/ext/jni/src/org/sqlite/jni/RollbackHook.java b/ext/jni/src/org/sqlite/jni/RollbackHook.java new file mode 100644 index 0000000000..4ce3cb93e3 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/RollbackHook.java @@ -0,0 +1,25 @@ +/* +** 2023-07-22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + Callback proxy for use with sqlite3_rollback_hook(). +*/ +public interface RollbackHook { + /** + Works as documented for the sqlite3_rollback_hook() callback. + Must not throw. + */ + void xRollbackHook(); +} diff --git a/ext/jni/src/org/sqlite/jni/SQLFunction.java b/ext/jni/src/org/sqlite/jni/SQLFunction.java new file mode 100644 index 0000000000..21e5fe622a --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/SQLFunction.java @@ -0,0 +1,172 @@ +/* +** 2023-07-22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + SQLFunction is used in conjunction with the + sqlite3_create_function() JNI-bound API to give that native code + access to the callback functions needed in order to implement SQL + functions in Java. + + This class is not used by itself, but is a marker base class. The + three UDF types are modelled by the inner classes Scalar, + Aggregate, and Window. Most simply, clients may create + anonymous classes from those to implement UDFs. Clients are free to + create their own classes for use with UDFs, so long as they conform + to the public interfaces defined by those three classes. The JNI + layer only actively relies on the SQLFunction base class. +*/ +public abstract class SQLFunction { + + /** + PerContextState assists aggregate and window functions in + managinga their accumulator state across calls to the UDF's + callbacks. + + 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 Aggregate and Window 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 cx.getAggregateContext() 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. + + T must be of a type which can be legally stored as a value in + java.util.HashMap. + */ + public ValueHolder getAggregateState(sqlite3_context cx, T initialValue){ + ValueHolder rc = map.get(cx.getAggregateContext()); + if(null == rc){ + map.put(cx.getAggregateContext(), 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()); + return null==h ? null : h.value; + } + } + + //! Subclass for creating scalar functions. + public static abstract class Scalar extends SQLFunction { + + //! As for the xFunc() argument of the C API's sqlite3_create_function() + public abstract void xFunc(sqlite3_context cx, sqlite3_value[] args); + + /** + Optionally override to be notified when the UDF is finalized by + SQLite. + */ + public void xDestroy() {} + } + + /** + SQLFunction Subclass for creating aggregate functions. Its T is + the data type of its "accumulator" state, an instance of which is + intended to be be managed using the getAggregateState() and + takeAggregateState() methods. + */ + public static abstract class Aggregate extends SQLFunction { + + //! As for the xStep() argument of the C API's sqlite3_create_function() + public abstract void xStep(sqlite3_context cx, sqlite3_value[] args); + + //! As for the xFinal() argument of the C API's sqlite3_create_function() + public abstract void xFinal(sqlite3_context cx); + + /** + Optionally override to be notified when the UDF is finalized by + SQLite. + */ + public void xDestroy() {} + + //! Per-invocation state for the UDF. + private final PerContextState map = new PerContextState<>(); + + /** + To be called from the implementation's xStep() method, as well + as the xValue() and xInverse() methods of the Window + subclass, to fetch the current per-call UDF state. On the + first call to this method for any given sqlite3_context + argument, the context is set to the given initial value. On all other + calls, the 2nd argument is ignored. + + @see PerContextState#takeAggregateState() + */ + protected final ValueHolder getAggregateState(sqlite3_context cx, T initialValue){ + return map.getAggregateState(cx, initialValue); + } + + /** + To be called from the implementation's xFinal() method to fetch + the final state of the UDF and remove its mapping. + + @see PerContextState#takeAggregateState() + */ + protected final T takeAggregateState(sqlite3_context cx){ + return map.takeAggregateState(cx); + } + } + + /** + An SQLFunction subclass for creating window functions. Note that + Window inherits from Aggregate and each instance is + required to implement the inherited abstract methods from that + class. See Aggregate for information on managing the UDF's + invocation-specific state. + */ + public static abstract class Window extends Aggregate { + + //! As for the xInverse() argument of the C API's sqlite3_create_window_function() + public abstract void xInverse(sqlite3_context cx, sqlite3_value[] args); + + //! As for the xValue() argument of the C API's sqlite3_create_window_function() + public abstract void xValue(sqlite3_context cx); + } +} diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java new file mode 100644 index 0000000000..9b2a176504 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -0,0 +1,1645 @@ +/* +** 2023-07-21 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file declares JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; +import java.nio.charset.StandardCharsets; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; + +/** + 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. + + This annotation is solely for the reader's information. +*/ +@Documented +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.PARAMETER) +@interface Nullable{} + +/** + This annotation is for flagging parameters which may not legally be + null. Note that the C-style API does _not_ throw any + NullPointerExceptions on its own because it has a no-throw policy + in order to retain its C-style semantics. + + This annotation is solely for the reader's information. No policy + is in place to programmatically ensure that NotNull is conformed to + in client code. +*/ +@Documented +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.PARAMETER) +@interface NotNull{} + +/** + This class contains the entire sqlite3 JNI API binding. For + client-side use, a static import is recommended: + + ``` + import static org.sqlite.jni.SQLite3Jni.*; + ``` + + The C-side part can be found in sqlite3-jni.c. + + + Only functions which materially differ from their C counterparts + are documented here. The C documetation is otherwise applicable + here: + + https://sqlite.org/c3ref/intro.html + + A handful of Java-specific APIs have been added. + + + ****************************************************************** + *** Warning regarding Java's Modified UTF-8 vs standard UTF-8: *** + ****************************************************************** + + SQLite internally uses UTF-8 encoding, whereas Java natively uses + UTF-16. Java JNI has routines for converting to and from UTF-8, + _but_ JNI uses what its docs call modified UTF-8 (see links below) + Care must be taken when converting Java strings to or from standard + UTF-8 to ensure that the proper conversion is performed. In short, + Java's `String.getBytes(StandardCharsets.UTF_8)` performs the proper + conversion in Java, and there are no JNI C APIs for that conversion + (JNI's `NewStringUTF()` requires its input to be in MUTF-8). + + The known consequences and limitations this discrepancy places on + the SQLite3 JNI binding include: + + - Any functions which return client-side data from a database + take extra care to perform proper conversion, at the cost of + efficiency. + + - Functions which return database identifiers require those + identifiers to have identical representations in UTF-8 and + MUTF-8. They do not perform such conversions (A) because of the + much lower risk of an encoding discrepancy and (B) to avoid + significant extra code involved (see both the Java- and C-side + implementations of sqlite3_db_filename() for an example). Names + of databases, tables, columns, collations, and functions MUST NOT + contain characters which differ in MUTF-8 and UTF-8, or certain + APIs will mis-translate them on their way between languages + (possibly leading to a crash). + + - C functions which take C-style strings without a length argument + require special care when taking input from Java. In particular, + Java strings converted to byte arrays for encoding purposes are + not NUL-terminated, and conversion to a Java byte array must be + careful to add one. Functions which take a length do not require + this. Search the SQLite3Jni class for "\0" for many examples. + + - Similarly, C-side code which deals with strings which might not be + NUL-terminated (e.g. while tokenizing in FTS5-related code) cannot + use JNI's new-string functions to return them to Java because none + of those APIs take a string-length argument. Such cases must + return byte arrays instead of strings. + + Further reading: + + - https://stackoverflow.com/questions/57419723 + - https://stackoverflow.com/questions/7921016 + - https://itecnote.com/tecnote/java-getting-true-utf-8-characters-in-java-jni/ + - https://docs.oracle.com/javase/8/docs/api/java/lang/Character.html#unicode + - https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8 + +*/ +public final class SQLite3Jni { + static { + System.loadLibrary("sqlite3-jni"); + } + //! Not used + private SQLite3Jni(){} + //! Called from static init code. + private static native void init(); + + /** + Each thread which uses the SQLite3 JNI APIs should call + uncacheJniEnv() when it is done with the library - either right + before it terminates or when it is finished using the SQLite API. + This will clean up any cached per-JNIEnv info. Calling into the + library again after that "should" re-initialize the cache on + demand, but that's untested. + + This call forcibly wipes out all cached information for the + current JNIEnv, a side-effect of which is that behavior is + undefined if any database objects are (A) still active at the + time it is called _and_ (B) calls are subsequently made into the + library with such a database. Doing so will, at best, lead to a + crash. Azt worst, it will lead to the db possibly misbehaving + because some of its Java-bound state has been cleared. There is + no immediate harm in (A) so long as condition (B) is not met. + This process does _not_ actually close any databases or finalize + any prepared statements. For proper library behavior, and to + avoid C-side leaks, be sure to close them before calling this + function. + + Calling this from the main application thread is not strictly + required but is "polite." Additional threads must call this + before ending or they will leak cache entries in the C heap, + which in turn may keep numerous Java-side global references + active. + + This routine returns false without side effects if the current + JNIEnv is not cached, else returns true, but this information is + primarily for testing of the JNI bindings and is not information + which client-level code should use to make any informed + decisions. + */ + public static synchronized native boolean uncacheJniEnv(); + + ////////////////////////////////////////////////////////////////////// + // Maintenance reminder: please keep the sqlite3_.... functions + // alphabetized. The SQLITE_... values. on the other hand, are + // grouped by category. + + + /** + Functions almost as documented for the C API, with these + exceptions: + + - The callback interface is more limited because of + cross-language differences. Specifically, auto-extensions do + not have access to the sqlite3_api object which native + auto-extensions do. + + - If an auto-extension opens a db, thereby triggering recursion + in the auto-extension handler, it will fail with a message + explaining that recursion is not permitted. + + - All of the other auto extension routines will fail without side + effects if invoked from within the execution of an + auto-extension. i.e. auto extensions can neither be added, + removed, nor cleared while one registered with this function is + running. Auto-extensions registered directly with the library + via C code, as opposed to indirectly via Java, do not have that + limitation. + + See the AutoExtension class docs for more information. + + Achtung: it is as yet unknown whether auto extensions registered + from one JNIEnv (thread) can be safely called from another. + */ + public static synchronized native int sqlite3_auto_extension(@NotNull AutoExtension callback); + + public static int sqlite3_bind_blob( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data + ){ + return (null == data) + ? sqlite3_bind_null(stmt, ndx) + : sqlite3_bind_blob(stmt, ndx, data, data.length); + } + + private static synchronized native int sqlite3_bind_blob( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n + ); + + public static synchronized native int sqlite3_bind_double( + @NotNull sqlite3_stmt stmt, int ndx, double v + ); + + public static synchronized native int sqlite3_bind_int( + @NotNull sqlite3_stmt stmt, int ndx, int v + ); + + public static synchronized native int sqlite3_bind_int64( + @NotNull sqlite3_stmt stmt, int ndx, long v + ); + + public static synchronized native int sqlite3_bind_null( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native int sqlite3_bind_parameter_count( + @NotNull sqlite3_stmt stmt + ); + + + /** A level of indirection required to ensure that the input to the + C-level function of the same name is a NUL-terminated UTF-8 + string. */ + private static synchronized native int sqlite3_bind_parameter_index( + @NotNull sqlite3_stmt stmt, byte[] paramName + ); + + public static int sqlite3_bind_parameter_index( + @NotNull sqlite3_stmt stmt, @NotNull String paramName + ){ + final byte[] utf8 = (paramName+"\0").getBytes(StandardCharsets.UTF_8); + return sqlite3_bind_parameter_index(stmt, utf8); + } + + public static int sqlite3_bind_text( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable String data + ){ + if(null == data) return sqlite3_bind_null(stmt, ndx); + final byte[] utf8 = data.getBytes(StandardCharsets.UTF_8); + return sqlite3_bind_text(stmt, ndx, utf8, utf8.length); + } + + public static int sqlite3_bind_text( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data + ){ + return (null == data) + ? sqlite3_bind_null(stmt, ndx) + : sqlite3_bind_text(stmt, ndx, data, data.length); + } + + /** + Works like the C-level sqlite3_bind_text() but (A) assumes + SQLITE_TRANSIENT for the final parameter and (B) behaves like + sqlite3_bind_null() if the data argument is null. + */ + private static synchronized native int sqlite3_bind_text( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes + ); + + public static synchronized native int sqlite3_bind_zeroblob( + @NotNull sqlite3_stmt stmt, int ndx, int n + ); + + public static synchronized native int sqlite3_bind_zeroblob64( + @NotNull sqlite3_stmt stmt, int ndx, long n + ); + + /** + As for the C-level function of the same name, with a BusyHandler + instance in place of a callback function. Pass it a null handler + to clear the busy handler. Calling this multiple times with the + same object is a no-op on the second and subsequent calls. + */ + public static synchronized native int sqlite3_busy_handler( + @NotNull sqlite3 db, @Nullable BusyHandler handler + ); + + public static synchronized native int sqlite3_busy_timeout( + @NotNull sqlite3 db, int ms + ); + + /** + Works like the C API except that it returns false, without side + effects, if auto extensions are currently running. (The JNI-level + list of extensions cannot be manipulated while it is being traversed.) + */ + public static synchronized native boolean sqlite3_cancel_auto_extension( + @NotNull AutoExtension ax + ); + + public static synchronized native int sqlite3_changes( + @NotNull sqlite3 db + ); + + public static synchronized native long sqlite3_changes64( + @NotNull sqlite3 db + ); + + public static synchronized native int sqlite3_clear_bindings( + @NotNull sqlite3_stmt stmt + ); + + public static synchronized native int sqlite3_close( + @NotNull sqlite3 db + ); + + public static synchronized native int sqlite3_close_v2( + @NotNull sqlite3 db + ); + + public static synchronized native byte[] sqlite3_column_blob( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native int sqlite3_column_bytes( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native int sqlite3_column_bytes16( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native int sqlite3_column_count( + @NotNull sqlite3_stmt stmt + ); + + public static synchronized native double sqlite3_column_double( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native int sqlite3_column_int( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native long sqlite3_column_int64( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native String sqlite3_column_name( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native String sqlite3_column_database_name( + @NotNull sqlite3_stmt stmt, int ndx + ); + + /** + Column counterpart of sqlite3_value_java_object(). + */ + public static Object sqlite3_column_java_object( + @NotNull sqlite3_stmt stmt, int ndx + ){ + Object rv = null; + sqlite3_value v = sqlite3_column_value(stmt, ndx); + if(null!=v){ + v = sqlite3_value_dupe(v) /* we need a "protected" value */; + if(null!=v){ + rv = sqlite3_value_java_object(v); + sqlite3_value_free(v); + } + } + return rv; + } + + /** + Column counterpart of sqlite3_value_java_casted(). + */ + @SuppressWarnings("unchecked") + public static T sqlite3_column_java_casted( + @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; + } + + public static synchronized native String sqlite3_column_origin_name( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native String sqlite3_column_table_name( + @NotNull sqlite3_stmt stmt, int ndx + ); + + /** + To extract _standard_ UTF-8, use sqlite3_column_text(). + This API includes no functions for working with Java's Modified + UTF-8. + */ + public static synchronized native String sqlite3_column_text16( + @NotNull sqlite3_stmt stmt, int ndx + ); + + /** + Returns the given column's contents as UTF-8-encoded (not MUTF-8) text. + Use sqlite3_column_text16() to fetch the text + */ + public static synchronized native byte[] sqlite3_column_text( + @NotNull sqlite3_stmt stmt, int ndx + ); + + // The real utility of this function is questionable. + // /** + // Returns a Java value representation based on the value of + // sqlite_value_type(). For integer types it returns either Integer + // or Long, depending on whether the value will fit in an + // Integer. For floating-point values it always returns type Double. + + // If the column was bound using sqlite3_result_java_object() then + // that value, as an Object, is returned. + // */ + // public static Object sqlite3_column_to_java(@NotNull sqlite3_stmt stmt, + // int ndx){ + // sqlite3_value v = sqlite3_column_value(stmt, ndx); + // Object rv = null; + // if(null == v) return v; + // v = sqlite3_value_dup(v)/*need a protected value*/; + // if(null == v) return v /* OOM error in C */; + // if(112/* 'p' */ == sqlite3_value_subtype(v)){ + // rv = sqlite3_value_java_object(v); + // }else{ + // switch(sqlite3_value_type(v)){ + // case SQLITE_INTEGER: { + // final long i = sqlite3_value_int64(v); + // rv = (i<=0x7fffffff && i>=-0x7fffffff-1) + // ? new Integer((int)i) : new Long(i); + // break; + // } + // case SQLITE_FLOAT: rv = new Double(sqlite3_value_double(v)); break; + // case SQLITE_BLOB: rv = sqlite3_value_blob(v); break; + // case SQLITE_TEXT: rv = sqlite3_value_text(v); break; + // default: break; + // } + // } + // sqlite3_value_free(v); + // return rv; + // } + + public static synchronized native int sqlite3_column_type( + @NotNull sqlite3_stmt stmt, int ndx + ); + + public static synchronized native sqlite3_value sqlite3_column_value( + @NotNull sqlite3_stmt stmt, int ndx + ); + + /** + This functions like C's sqlite3_collation_needed16() because + Java's string type is compatible with that interface. + */ + public static synchronized native int sqlite3_collation_needed( + @NotNull sqlite3 db, @Nullable CollationNeeded callback + ); + + /** + Returns the db handle passed to sqlite3_open() or + sqlite3_open_v2(), as opposed to a new wrapper object. + */ + public static synchronized native sqlite3 sqlite3_context_db_handle( + @NotNull sqlite3_context cx + ); + + public static synchronized native CommitHook sqlite3_commit_hook( + @NotNull sqlite3 db, @Nullable CommitHook hook + ); + + public static native String sqlite3_compileoption_get( + int n + ); + + public static native boolean sqlite3_compileoption_used( + @NotNull String optName + ); + + public static synchronized native int sqlite3_create_collation( + @NotNull sqlite3 db, @NotNull String name, int eTextRep, + @NotNull Collation col + ); + + /** + The Java counterpart to the C-native sqlite3_create_function(), + sqlite3_create_function_v2(), and + sqlite3_create_window_function(). Which one it behaves like + depends on which methods the final argument implements. See + SQLFunction's inner classes (Scalar, Aggregate, and Window) + for details. + */ + public static synchronized native int sqlite3_create_function( + @NotNull sqlite3 db, @NotNull String functionName, + int nArg, int eTextRep, @NotNull SQLFunction func + ); + + public static synchronized native int sqlite3_data_count( + @NotNull sqlite3_stmt stmt + ); + + public static synchronized native String sqlite3_db_filename( + @NotNull sqlite3 db, @NotNull String dbName + ); + + /** + 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. + */ + public static synchronized native int sqlite3_db_config( + @NotNull sqlite3 db, int op, int onOff, @Nullable OutputPointer.Int32 out + ); + + /** + Overload for sqlite3_db_config() calls which take a (const char*) + variadic argument. As of SQLite3 v3.43 the only such option is + SQLITE_DBCONFIG_MAINDBNAME. Returns SQLITE_MISUSE if op is not + SQLITE_DBCONFIG_MAINDBNAME, but that set of options may be + extended in future versions. + */ + public static synchronized native int sqlite3_db_config( + @NotNull sqlite3 db, int op, @NotNull String val + ); + + public static synchronized native int sqlite3_db_status( + @NotNull sqlite3 db, int op, @NotNull OutputPointer.Int32 pCurrent, + @NotNull OutputPointer.Int32 pHighwater, boolean reset + ); + + public static synchronized native int sqlite3_errcode(@NotNull sqlite3 db); + + public static synchronized native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt); + + public static synchronized native int sqlite3_extended_errcode(@NotNull sqlite3 db); + + public static synchronized native boolean sqlite3_extended_result_codes( + @NotNull sqlite3 db, boolean onoff + ); + + public static synchronized native String sqlite3_errmsg(@NotNull sqlite3 db); + + public static synchronized native String sqlite3_errstr(int resultCode); + + /** + Note that the offset values assume UTF-8-encoded SQL. + */ + public static synchronized native int sqlite3_error_offset(@NotNull sqlite3 db); + + public static synchronized native int sqlite3_finalize(@NotNull sqlite3_stmt stmt); + + public static synchronized native int sqlite3_initialize(); + + public static synchronized native long sqlite3_last_insert_rowid(@NotNull sqlite3 db); + + public static synchronized native String sqlite3_libversion(); + + public static synchronized native int sqlite3_libversion_number(); + + /** + Works like its C counterpart and makes the native pointer of the + underling (sqlite3*) object available via + ppDb.getNativePointer(). That pointer is necessary for looking up + the JNI-side native, but clients need not pay it any + heed. Passing the object to sqlite3_close() or sqlite3_close_v2() + will clear that pointer mapping. + + Recall that even if opening fails, the output pointer might be + non-null. Any error message about the failure will be in that + object and it is up to the caller to sqlite3_close() that + db handle. + + Pedantic note: though any number of Java-level sqlite3 objects + may refer to/wrap a single C-level (sqlite3*), the JNI internals + take a reference to the object which is passed to sqlite3_open() + or sqlite3_open_v2() so that they have a predictible object to + pass to, e.g., the sqlite3_collation_needed() callback. + */ + public static synchronized native int sqlite3_open( + @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb + ); + + public static synchronized native int sqlite3_open_v2( + @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb, + int flags, @Nullable String zVfs + ); + + /** + The sqlite3_prepare() family of functions require slightly + different signatures than their native counterparts, but + overloading allows us to install several convenience forms. + + All of them which take their SQL in the form of a byte[] require + that it be in UTF-8 encoding unless explicitly noted otherwise. + + The forms which take a "tail" output pointer return (via that + output object) the index into their SQL byte array at which the + end of the first SQL statement processed by the call was + found. That's fundamentally how the C APIs work but making use of + that value requires more copying of the input SQL into + consecutively smaller arrays in order to consume all of + it. (There is an example of doing that in this project's Tester1 + class.) For that vast majority of uses, that capability is not + necessary, however, and overloads are provided which gloss over + that. + */ + private static synchronized native int sqlite3_prepare( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ); + + public static int sqlite3_prepare( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ){ + return sqlite3_prepare(db, sqlUtf8, sqlUtf8.length, outStmt, pTailOffset); + } + + public static int sqlite3_prepare( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ + return sqlite3_prepare(db, sqlUtf8, sqlUtf8.length, outStmt, null); + } + + public static int sqlite3_prepare( + @NotNull sqlite3 db, @NotNull String sql, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ + final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); + return sqlite3_prepare(db, utf8, utf8.length, outStmt, null); + } + + private static synchronized native int sqlite3_prepare_v2( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ); + + public static int sqlite3_prepare_v2( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ){ + return sqlite3_prepare_v2(db, sqlUtf8, sqlUtf8.length, outStmt, pTailOffset); + } + + public static int sqlite3_prepare_v2( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ + return sqlite3_prepare_v2(db, sqlUtf8, sqlUtf8.length, outStmt, null); + } + + public static int sqlite3_prepare_v2( + @NotNull sqlite3 db, @NotNull String sql, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ + final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); + return sqlite3_prepare_v2(db, utf8, utf8.length, outStmt, null); + } + + private static synchronized native int sqlite3_prepare_v3( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, + int prepFlags, @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ); + + public static int sqlite3_prepare_v3( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ){ + return sqlite3_prepare_v3(db, sqlUtf8, sqlUtf8.length, prepFlags, outStmt, pTailOffset); + } + + public static int sqlite3_prepare_v3( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ + return sqlite3_prepare_v3(db, sqlUtf8, sqlUtf8.length, prepFlags, outStmt, null); + } + + public static int sqlite3_prepare_v3( + @NotNull sqlite3 db, @NotNull String sql, int prepFlags, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ + final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); + return sqlite3_prepare_v3(db, utf8, utf8.length, prepFlags, outStmt, null); + } + + public static synchronized native void sqlite3_progress_handler( + @NotNull sqlite3 db, int n, @Nullable ProgressHandler h + ); + + //TODO??? void *sqlite3_preupdate_hook(...) and friends + + public static synchronized native int sqlite3_reset(@NotNull sqlite3_stmt stmt); + + /** + Works like the C API except that it has no side effects if auto + extensions are currently running. (The JNI-level list of + extensions cannot be manipulated while it is being traversed.) + */ + public static synchronized native void sqlite3_reset_auto_extension(); + + public static synchronized native void sqlite3_result_double( + @NotNull sqlite3_context cx, double v + ); + + /** + The main sqlite3_result_error() impl of which all others are + proxies. eTextRep must be one of SQLITE_UTF8 or SQLITE_UTF16 and + msg must be encoded correspondingly. Any other eTextRep value + results in the C-level sqlite3_result_error() being called with + a complaint about the invalid argument. + */ + private static synchronized native void sqlite3_result_error( + @NotNull sqlite3_context cx, @Nullable byte[] msg, + int eTextRep + ); + + public static void sqlite3_result_error( + @NotNull sqlite3_context cx, @NotNull byte[] utf8 + ){ + sqlite3_result_error(cx, utf8, SQLITE_UTF8); + } + + public static void sqlite3_result_error( + @NotNull sqlite3_context cx, @NotNull String msg + ){ + final byte[] utf8 = (msg+"\0").getBytes(StandardCharsets.UTF_8); + sqlite3_result_error(cx, utf8, SQLITE_UTF8); + } + + public static void sqlite3_result_error16( + @NotNull sqlite3_context cx, @Nullable byte[] utf16 + ){ + sqlite3_result_error(cx, utf16, SQLITE_UTF16); + } + + public static void sqlite3_result_error16( + @NotNull sqlite3_context cx, @NotNull String msg + ){ + final byte[] utf8 = (msg+"\0").getBytes(StandardCharsets.UTF_16); + sqlite3_result_error(cx, utf8, SQLITE_UTF16); + } + + public static void sqlite3_result_error( + @NotNull sqlite3_context cx, @NotNull Exception e + ){ + sqlite3_result_error(cx, e.getMessage()); + } + + public static void sqlite3_result_error16( + @NotNull sqlite3_context cx, @NotNull Exception e + ){ + sqlite3_result_error16(cx, e.getMessage()); + } + + public static synchronized native void sqlite3_result_error_toobig( + @NotNull sqlite3_context cx + ); + + public static synchronized native void sqlite3_result_error_nomem( + @NotNull sqlite3_context cx + ); + + public static synchronized native void sqlite3_result_error_code( + @NotNull sqlite3_context cx, int c + ); + + public static synchronized native void sqlite3_result_null( + @NotNull sqlite3_context cx + ); + + public static synchronized native void sqlite3_result_int( + @NotNull sqlite3_context cx, int v + ); + + public static synchronized native void sqlite3_result_int64( + @NotNull sqlite3_context cx, long v + ); + + /** + Binds the SQL result to the given object, or + sqlite3_result_null() if o is null. Use + sqlite3_value_java_object() or sqlite3_column_java_object() to + fetch it. + + This is implemented in terms of sqlite3_result_pointer(), but + that function is not exposed to JNI because its 3rd argument must + be a constant string (the library does not copy it), which we + cannot implement cross-language here unless, in the JNI layer, we + allocate such strings and store them somewhere for long-term use + (leaking them more likely than not). Even then, passing around a + pointer via Java like that has little practical use. + + Note that there is no sqlite3_bind_java_object() counterpart. + */ + public static synchronized native void sqlite3_result_java_object( + @NotNull sqlite3_context cx, @NotNull Object o + ); + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @NotNull Integer v + ){ + sqlite3_result_int(cx, v); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, int v + ){ + sqlite3_result_int(cx, v); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @NotNull Boolean v + ){ + sqlite3_result_int(cx, v ? 1 : 0); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, boolean v + ){ + sqlite3_result_int(cx, v ? 1 : 0); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @NotNull Long v + ){ + sqlite3_result_int64(cx, v); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, long v + ){ + sqlite3_result_int64(cx, v); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @NotNull Double v + ){ + sqlite3_result_double(cx, v); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, double v + ){ + sqlite3_result_double(cx, v); + } + + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @Nullable String v + ){ + sqlite3_result_text(cx, v); + } + + public static synchronized native void sqlite3_result_value( + @NotNull sqlite3_context cx, @NotNull sqlite3_value v + ); + + public static synchronized native void sqlite3_result_zeroblob( + @NotNull sqlite3_context cx, int n + ); + + public static synchronized native int sqlite3_result_zeroblob64( + @NotNull sqlite3_context cx, long n + ); + + private static synchronized native void sqlite3_result_blob( + @NotNull sqlite3_context cx, @Nullable byte[] blob, int maxLen + ); + + public static void sqlite3_result_blob( + @NotNull sqlite3_context cx, @Nullable byte[] blob + ){ + sqlite3_result_blob(cx, blob, (int)(null==blob ? 0 : blob.length)); + } + + /** + Binds the given text using C's sqlite3_result_blob64() unless: + + - blob is null ==> sqlite3_result_null() + + - blob is too large ==> sqlite3_result_error_toobig() + + If maxLen is larger than blob.length, it is truncated to that + value. If it is negative, results are undefined. + */ + private static synchronized native void sqlite3_result_blob64( + @NotNull sqlite3_context cx, @Nullable byte[] blob, long maxLen + ); + + public static void sqlite3_result_blob64( + @NotNull sqlite3_context cx, @Nullable byte[] blob + ){ + sqlite3_result_blob64(cx, blob, (long)(null==blob ? 0 : blob.length)); + } + + private static synchronized native void sqlite3_result_text( + @NotNull sqlite3_context cx, @Nullable byte[] text, int maxLen + ); + + public static void sqlite3_result_text( + @NotNull sqlite3_context cx, @Nullable byte[] text + ){ + sqlite3_result_text(cx, text, null==text ? 0 : text.length); + } + + public static void sqlite3_result_text( + @NotNull sqlite3_context cx, @Nullable String text + ){ + if(null == text) sqlite3_result_null(cx); + else{ + final byte[] utf8 = text.getBytes(StandardCharsets.UTF_8); + sqlite3_result_text(cx, utf8, utf8.length); + } + } + + /** + Binds the given text using C's sqlite3_result_text64() unless: + + - text is null ==> sqlite3_result_null() + + - text is too large ==> sqlite3_result_error_toobig() + + - The `encoding` argument has an invalid value ==> + sqlite3_result_error_code() with SQLITE_FORMAT + + If maxLength (in bytes, not characters) is larger than + text.length, it is silently truncated to text.length. If it is + negative, results are undefined. + */ + private static synchronized native void sqlite3_result_text64( + @NotNull sqlite3_context cx, @Nullable byte[] text, + long maxLength, int encoding + ); + + public static synchronized native int sqlite3_status( + int op, @NotNull OutputPointer.Int32 pCurrent, + @NotNull OutputPointer.Int32 pHighwater, boolean reset + ); + + public static synchronized native int sqlite3_status64( + int op, @NotNull OutputPointer.Int64 pCurrent, + @NotNull OutputPointer.Int64 pHighwater, boolean reset + ); + + /** + Sets the current UDF result to the given bytes, which are assumed + be encoded in UTF-16 using the platform's byte order. + */ + public static void sqlite3_result_text16( + @NotNull sqlite3_context cx, @Nullable byte[] text + ){ + sqlite3_result_text64(cx, text, text.length, SQLITE_UTF16); + } + + public static void sqlite3_result_text16( + @NotNull sqlite3_context cx, @Nullable String text + ){ + if(null == text) sqlite3_result_null(cx); + else{ + final byte[] b = text.getBytes(StandardCharsets.UTF_16); + sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16); + } + } + + /** + Sets the current UDF result to the given bytes, which are assumed + be encoded in UTF-16LE. + */ + public static void sqlite3_result_text16le( + @NotNull sqlite3_context cx, @Nullable String text + ){ + if(null == text) sqlite3_result_null(cx); + else{ + final byte[] b = text.getBytes(StandardCharsets.UTF_16LE); + sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16LE); + } + } + + /** + Sets the current UDF result to the given bytes, which are assumed + be encoded in UTF-16BE. + */ + public static void sqlite3_result_text16be( + @NotNull sqlite3_context cx, @Nullable byte[] text + ){ + sqlite3_result_text64(cx, text, text.length, SQLITE_UTF16BE); + } + + public static void sqlite3_result_text16be( + @NotNull sqlite3_context cx, @NotNull String text + ){ + final byte[] b = text.getBytes(StandardCharsets.UTF_16BE); + sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16BE); + } + + public static synchronized native RollbackHook sqlite3_rollback_hook( + @NotNull sqlite3 db, @Nullable RollbackHook hook + ); + + //! Sets or unsets (if auth is null) the current authorizer. + public static synchronized native int sqlite3_set_authorizer( + @NotNull sqlite3 db, @Nullable Authorizer auth + ); + + public static synchronized native void sqlite3_set_last_insert_rowid( + @NotNull sqlite3 db, long rowid + ); + + public static synchronized native int sqlite3_sleep(int ms); + + public static synchronized native String sqlite3_sourceid(); + + public static synchronized native String sqlite3_sql(@NotNull sqlite3_stmt stmt); + + public static synchronized native int sqlite3_step(@NotNull sqlite3_stmt stmt); + + /** + Internal impl of the public sqlite3_strglob() method. Neither argument + may be NULL and both _MUST_ be NUL-terminated. + */ + private static synchronized native int sqlite3_strglob( + @NotNull byte[] glob, @NotNull byte[] txt + ); + + public static int sqlite3_strglob( + @NotNull String glob, @NotNull String txt + ){ + return sqlite3_strglob( + (glob+"\0").getBytes(StandardCharsets.UTF_8), + (txt+"\0").getBytes(StandardCharsets.UTF_8) + ); + } + + /** + Internal impl of the public sqlite3_strlike() method. Neither + argument may be NULL and both _MUST_ be NUL-terminated. + */ + private static synchronized native int sqlite3_strlike( + @NotNull byte[] glob, @NotNull byte[] txt, int escChar + ); + + public static int sqlite3_strlike( + @NotNull String glob, @NotNull String txt, char escChar + ){ + return sqlite3_strlike( + (glob+"\0").getBytes(StandardCharsets.UTF_8), + (txt+"\0").getBytes(StandardCharsets.UTF_8), + (int)escChar + ); + } + + public static synchronized native int sqlite3_threadsafe(); + + public static synchronized native int sqlite3_total_changes(@NotNull sqlite3 db); + + public static synchronized native long sqlite3_total_changes64(@NotNull sqlite3 db); + + /** + Works like C's sqlite3_trace_v2() except that the 3rd argument to that + function is elided here because the roles of that functions' 3rd and 4th + arguments are encapsulated in the final argument to this function. + + Unlike the C API, which is documented as always returning 0, this + implementation returns SQLITE_NOMEM if allocation of per-db + mapping state fails and SQLITE_ERROR if the given callback object + cannot be processed propertly (i.e. an internal error). + */ + public static synchronized native int sqlite3_trace_v2( + @NotNull sqlite3 db, int traceMask, @Nullable Tracer tracer + ); + + public static synchronized native UpdateHook sqlite3_update_hook( + sqlite3 db, UpdateHook hook + ); + + public static synchronized native byte[] sqlite3_value_blob(@NotNull sqlite3_value v); + + public static synchronized native int sqlite3_value_bytes(@NotNull sqlite3_value v); + + public static synchronized native int sqlite3_value_bytes16(@NotNull sqlite3_value v); + + public static synchronized native double sqlite3_value_double(@NotNull sqlite3_value v); + + public static synchronized native sqlite3_value sqlite3_value_dupe( + @NotNull sqlite3_value v + ); + + public static synchronized native int sqlite3_value_encoding(@NotNull sqlite3_value v); + + public static synchronized native void sqlite3_value_free(@Nullable sqlite3_value v); + + public static synchronized native int sqlite3_value_int(@NotNull sqlite3_value v); + + public static synchronized native long sqlite3_value_int64(@NotNull sqlite3_value v); + + /** + If the given value was set using sqlite3_result_java_value() then + this function returns that object, else it returns null. + + It is up to the caller to inspect the object to determine its + type, and cast it if necessary. + */ + public static synchronized native Object sqlite3_value_java_object( + @NotNull sqlite3_value v + ); + + /** + 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. It returns null in all other cases. + */ + @SuppressWarnings("unchecked") + public static T sqlite3_value_java_casted(@NotNull sqlite3_value v, + @NotNull Class type){ + final Object o = sqlite3_value_java_object(v); + return type.isInstance(o) ? (T)o : null; + } + + /** + See sqlite3_column_text() for notes about encoding conversions. + See sqlite3_value_text_utf8() for how to extract text in standard + UTF-8. + */ + public static synchronized native String sqlite3_value_text(@NotNull sqlite3_value v); + + /** + The sqlite3_value counterpart of sqlite3_column_text_utf8(). + */ + public static synchronized native byte[] sqlite3_value_text_utf8(@NotNull sqlite3_value v); + + public static synchronized native byte[] sqlite3_value_text16(@NotNull sqlite3_value v); + + public static synchronized native byte[] sqlite3_value_text16le(@NotNull sqlite3_value v); + + public static synchronized native byte[] sqlite3_value_text16be(@NotNull sqlite3_value v); + + public static synchronized native int sqlite3_value_type(@NotNull sqlite3_value v); + + public static synchronized native int sqlite3_value_numeric_type(@NotNull sqlite3_value v); + + public static synchronized native int sqlite3_value_nochange(@NotNull sqlite3_value v); + + public static synchronized native int sqlite3_value_frombind(@NotNull sqlite3_value v); + + public static synchronized native int sqlite3_value_subtype(@NotNull sqlite3_value v); + + /** + Cleans up all per-JNIEnv and per-db state managed by the library + then calls the C-native sqlite3_shutdown(). + */ + public static synchronized native int sqlite3_shutdown(); + + /** + This is NOT part of the public API. It exists solely as a place + to hook in arbitrary C-side code during development and testing + of this library. + */ + public static synchronized native void sqlite3_do_something_for_developer(); + + ////////////////////////////////////////////////////////////////////// + // SQLITE_... constants follow... + + // version info + public static final int SQLITE_VERSION_NUMBER = sqlite3_libversion_number(); + public static final String SQLITE_VERSION = sqlite3_libversion(); + public static final String SQLITE_SOURCE_ID = sqlite3_sourceid(); + + //! Feature flags which are initialized at lib startup. Necessarily + // non-final so that lib init can fill out the proper values, + // but modifying them from client code has no effect. + public static boolean SQLITE_ENABLE_FTS5 = false; + + // access + public static final int SQLITE_ACCESS_EXISTS = 0; + public static final int SQLITE_ACCESS_READWRITE = 1; + public static final int SQLITE_ACCESS_READ = 2; + + // authorizer + public static final int SQLITE_DENY = 1; + public static final int SQLITE_IGNORE = 2; + public static final int SQLITE_CREATE_INDEX = 1; + public static final int SQLITE_CREATE_TABLE = 2; + public static final int SQLITE_CREATE_TEMP_INDEX = 3; + public static final int SQLITE_CREATE_TEMP_TABLE = 4; + public static final int SQLITE_CREATE_TEMP_TRIGGER = 5; + public static final int SQLITE_CREATE_TEMP_VIEW = 6; + public static final int SQLITE_CREATE_TRIGGER = 7; + public static final int SQLITE_CREATE_VIEW = 8; + public static final int SQLITE_DELETE = 9; + public static final int SQLITE_DROP_INDEX = 10; + public static final int SQLITE_DROP_TABLE = 11; + public static final int SQLITE_DROP_TEMP_INDEX = 12; + public static final int SQLITE_DROP_TEMP_TABLE = 13; + public static final int SQLITE_DROP_TEMP_TRIGGER = 14; + public static final int SQLITE_DROP_TEMP_VIEW = 15; + public static final int SQLITE_DROP_TRIGGER = 16; + public static final int SQLITE_DROP_VIEW = 17; + public static final int SQLITE_INSERT = 18; + public static final int SQLITE_PRAGMA = 19; + public static final int SQLITE_READ = 20; + public static final int SQLITE_SELECT = 21; + public static final int SQLITE_TRANSACTION = 22; + public static final int SQLITE_UPDATE = 23; + public static final int SQLITE_ATTACH = 24; + public static final int SQLITE_DETACH = 25; + public static final int SQLITE_ALTER_TABLE = 26; + public static final int SQLITE_REINDEX = 27; + public static final int SQLITE_ANALYZE = 28; + public static final int SQLITE_CREATE_VTABLE = 29; + public static final int SQLITE_DROP_VTABLE = 30; + public static final int SQLITE_FUNCTION = 31; + public static final int SQLITE_SAVEPOINT = 32; + public static final int SQLITE_RECURSIVE = 33; + + // blob finalizers: these should, because they are treated as + // special pointer values in C, ideally have the same sizeof() as + // the platform's (void*), but we can't know that size from here. + public static final long SQLITE_STATIC = 0; + public static final long SQLITE_TRANSIENT = -1; + + // changeset + public static final int SQLITE_CHANGESETSTART_INVERT = 2; + public static final int SQLITE_CHANGESETAPPLY_NOSAVEPOINT = 1; + public static final int SQLITE_CHANGESETAPPLY_INVERT = 2; + public static final int SQLITE_CHANGESETAPPLY_IGNORENOOP = 4; + public static final int SQLITE_CHANGESET_DATA = 1; + public static final int SQLITE_CHANGESET_NOTFOUND = 2; + public static final int SQLITE_CHANGESET_CONFLICT = 3; + public static final int SQLITE_CHANGESET_CONSTRAINT = 4; + public static final int SQLITE_CHANGESET_FOREIGN_KEY = 5; + public static final int SQLITE_CHANGESET_OMIT = 0; + public static final int SQLITE_CHANGESET_REPLACE = 1; + public static final int SQLITE_CHANGESET_ABORT = 2; + + // config + public static final int SQLITE_CONFIG_SINGLETHREAD = 1; + public static final int SQLITE_CONFIG_MULTITHREAD = 2; + public static final int SQLITE_CONFIG_SERIALIZED = 3; + public static final int SQLITE_CONFIG_MALLOC = 4; + public static final int SQLITE_CONFIG_GETMALLOC = 5; + public static final int SQLITE_CONFIG_SCRATCH = 6; + public static final int SQLITE_CONFIG_PAGECACHE = 7; + public static final int SQLITE_CONFIG_HEAP = 8; + public static final int SQLITE_CONFIG_MEMSTATUS = 9; + public static final int SQLITE_CONFIG_MUTEX = 10; + public static final int SQLITE_CONFIG_GETMUTEX = 11; + public static final int SQLITE_CONFIG_LOOKASIDE = 13; + public static final int SQLITE_CONFIG_PCACHE = 14; + public static final int SQLITE_CONFIG_GETPCACHE = 15; + public static final int SQLITE_CONFIG_LOG = 16; + public static final int SQLITE_CONFIG_URI = 17; + public static final int SQLITE_CONFIG_PCACHE2 = 18; + public static final int SQLITE_CONFIG_GETPCACHE2 = 19; + public static final int SQLITE_CONFIG_COVERING_INDEX_SCAN = 20; + public static final int SQLITE_CONFIG_SQLLOG = 21; + public static final int SQLITE_CONFIG_MMAP_SIZE = 22; + public static final int SQLITE_CONFIG_WIN32_HEAPSIZE = 23; + public static final int SQLITE_CONFIG_PCACHE_HDRSZ = 24; + public static final int SQLITE_CONFIG_PMASZ = 25; + public static final int SQLITE_CONFIG_STMTJRNL_SPILL = 26; + public static final int SQLITE_CONFIG_SMALL_MALLOC = 27; + public static final int SQLITE_CONFIG_SORTERREF_SIZE = 28; + public static final int SQLITE_CONFIG_MEMDB_MAXSIZE = 29; + + // data types + public static final int SQLITE_INTEGER = 1; + public static final int SQLITE_FLOAT = 2; + public static final int SQLITE_TEXT = 3; + public static final int SQLITE_BLOB = 4; + public static final int SQLITE_NULL = 5; + + // db config + public static final int SQLITE_DBCONFIG_MAINDBNAME = 1000; + public static final int SQLITE_DBCONFIG_LOOKASIDE = 1001; + public static final int SQLITE_DBCONFIG_ENABLE_FKEY = 1002; + public static final int SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003; + public static final int SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004; + public static final int SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005; + public static final int SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006; + public static final int SQLITE_DBCONFIG_ENABLE_QPSG = 1007; + public static final int SQLITE_DBCONFIG_TRIGGER_EQP = 1008; + public static final int SQLITE_DBCONFIG_RESET_DATABASE = 1009; + public static final int SQLITE_DBCONFIG_DEFENSIVE = 1010; + public static final int SQLITE_DBCONFIG_WRITABLE_SCHEMA = 1011; + public static final int SQLITE_DBCONFIG_LEGACY_ALTER_TABLE = 1012; + public static final int SQLITE_DBCONFIG_DQS_DML = 1013; + public static final int SQLITE_DBCONFIG_DQS_DDL = 1014; + public static final int SQLITE_DBCONFIG_ENABLE_VIEW = 1015; + public static final int SQLITE_DBCONFIG_LEGACY_FILE_FORMAT = 1016; + public static final int SQLITE_DBCONFIG_TRUSTED_SCHEMA = 1017; + public static final int SQLITE_DBCONFIG_STMT_SCANSTATUS = 1018; + public static final int SQLITE_DBCONFIG_REVERSE_SCANORDER = 1019; + public static final int SQLITE_DBCONFIG_MAX = 1019; + + // db status + public static final int SQLITE_DBSTATUS_LOOKASIDE_USED = 0; + public static final int SQLITE_DBSTATUS_CACHE_USED = 1; + public static final int SQLITE_DBSTATUS_SCHEMA_USED = 2; + public static final int SQLITE_DBSTATUS_STMT_USED = 3; + public static final int SQLITE_DBSTATUS_LOOKASIDE_HIT = 4; + public static final int SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE = 5; + public static final int SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL = 6; + public static final int SQLITE_DBSTATUS_CACHE_HIT = 7; + public static final int SQLITE_DBSTATUS_CACHE_MISS = 8; + public static final int SQLITE_DBSTATUS_CACHE_WRITE = 9; + public static final int SQLITE_DBSTATUS_DEFERRED_FKS = 10; + public static final int SQLITE_DBSTATUS_CACHE_USED_SHARED = 11; + public static final int SQLITE_DBSTATUS_CACHE_SPILL = 12; + public static final int SQLITE_DBSTATUS_MAX = 12; + + // encodings + public static final int SQLITE_UTF8 = 1; + public static final int SQLITE_UTF16LE = 2; + public static final int SQLITE_UTF16BE = 3; + public static final int SQLITE_UTF16 = 4; + public static final int SQLITE_UTF16_ALIGNED = 8; + + // fcntl + public static final int SQLITE_FCNTL_LOCKSTATE = 1; + public static final int SQLITE_FCNTL_GET_LOCKPROXYFILE = 2; + public static final int SQLITE_FCNTL_SET_LOCKPROXYFILE = 3; + public static final int SQLITE_FCNTL_LAST_ERRNO = 4; + public static final int SQLITE_FCNTL_SIZE_HINT = 5; + public static final int SQLITE_FCNTL_CHUNK_SIZE = 6; + public static final int SQLITE_FCNTL_FILE_POINTER = 7; + public static final int SQLITE_FCNTL_SYNC_OMITTED = 8; + public static final int SQLITE_FCNTL_WIN32_AV_RETRY = 9; + public static final int SQLITE_FCNTL_PERSIST_WAL = 10; + public static final int SQLITE_FCNTL_OVERWRITE = 11; + public static final int SQLITE_FCNTL_VFSNAME = 12; + public static final int SQLITE_FCNTL_POWERSAFE_OVERWRITE = 13; + public static final int SQLITE_FCNTL_PRAGMA = 14; + public static final int SQLITE_FCNTL_BUSYHANDLER = 15; + public static final int SQLITE_FCNTL_TEMPFILENAME = 16; + public static final int SQLITE_FCNTL_MMAP_SIZE = 18; + public static final int SQLITE_FCNTL_TRACE = 19; + public static final int SQLITE_FCNTL_HAS_MOVED = 20; + public static final int SQLITE_FCNTL_SYNC = 21; + public static final int SQLITE_FCNTL_COMMIT_PHASETWO = 22; + public static final int SQLITE_FCNTL_WIN32_SET_HANDLE = 23; + public static final int SQLITE_FCNTL_WAL_BLOCK = 24; + public static final int SQLITE_FCNTL_ZIPVFS = 25; + public static final int SQLITE_FCNTL_RBU = 26; + public static final int SQLITE_FCNTL_VFS_POINTER = 27; + public static final int SQLITE_FCNTL_JOURNAL_POINTER = 28; + public static final int SQLITE_FCNTL_WIN32_GET_HANDLE = 29; + public static final int SQLITE_FCNTL_PDB = 30; + public static final int SQLITE_FCNTL_BEGIN_ATOMIC_WRITE = 31; + public static final int SQLITE_FCNTL_COMMIT_ATOMIC_WRITE = 32; + public static final int SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE = 33; + public static final int SQLITE_FCNTL_LOCK_TIMEOUT = 34; + public static final int SQLITE_FCNTL_DATA_VERSION = 35; + public static final int SQLITE_FCNTL_SIZE_LIMIT = 36; + public static final int SQLITE_FCNTL_CKPT_DONE = 37; + public static final int SQLITE_FCNTL_RESERVE_BYTES = 38; + public static final int SQLITE_FCNTL_CKPT_START = 39; + public static final int SQLITE_FCNTL_EXTERNAL_READER = 40; + public static final int SQLITE_FCNTL_CKSM_FILE = 41; + public static final int SQLITE_FCNTL_RESET_CACHE = 42; + + // flock + public static final int SQLITE_LOCK_NONE = 0; + public static final int SQLITE_LOCK_SHARED = 1; + public static final int SQLITE_LOCK_RESERVED = 2; + public static final int SQLITE_LOCK_PENDING = 3; + public static final int SQLITE_LOCK_EXCLUSIVE = 4; + + // iocap + public static final int SQLITE_IOCAP_ATOMIC = 1; + public static final int SQLITE_IOCAP_ATOMIC512 = 2; + public static final int SQLITE_IOCAP_ATOMIC1K = 4; + public static final int SQLITE_IOCAP_ATOMIC2K = 8; + public static final int SQLITE_IOCAP_ATOMIC4K = 16; + public static final int SQLITE_IOCAP_ATOMIC8K = 32; + public static final int SQLITE_IOCAP_ATOMIC16K = 64; + public static final int SQLITE_IOCAP_ATOMIC32K = 128; + public static final int SQLITE_IOCAP_ATOMIC64K = 256; + public static final int SQLITE_IOCAP_SAFE_APPEND = 512; + public static final int SQLITE_IOCAP_SEQUENTIAL = 1024; + public static final int SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN = 2048; + public static final int SQLITE_IOCAP_POWERSAFE_OVERWRITE = 4096; + public static final int SQLITE_IOCAP_IMMUTABLE = 8192; + public static final int SQLITE_IOCAP_BATCH_ATOMIC = 16384; + + // limits. These get injected at init-time so that they stay in sync + // with the compile-time options. This unfortunately means they are + // not final, but keeping them in sync with their C values seems + // more important than protecting users from assigning to these + // (with unpredictable results). + public static int SQLITE_MAX_ALLOCATION_SIZE = -1; + public static int SQLITE_LIMIT_LENGTH = -1; + public static int SQLITE_MAX_LENGTH = -1; + public static int SQLITE_LIMIT_SQL_LENGTH = -1; + public static int SQLITE_MAX_SQL_LENGTH = -1; + public static int SQLITE_LIMIT_COLUMN = -1; + public static int SQLITE_MAX_COLUMN = -1; + public static int SQLITE_LIMIT_EXPR_DEPTH = -1; + public static int SQLITE_MAX_EXPR_DEPTH = -1; + public static int SQLITE_LIMIT_COMPOUND_SELECT = -1; + public static int SQLITE_MAX_COMPOUND_SELECT = -1; + public static int SQLITE_LIMIT_VDBE_OP = -1; + public static int SQLITE_MAX_VDBE_OP = -1; + public static int SQLITE_LIMIT_FUNCTION_ARG = -1; + public static int SQLITE_MAX_FUNCTION_ARG = -1; + public static int SQLITE_LIMIT_ATTACHED = -1; + public static int SQLITE_MAX_ATTACHED = -1; + public static int SQLITE_LIMIT_LIKE_PATTERN_LENGTH = -1; + public static int SQLITE_MAX_LIKE_PATTERN_LENGTH = -1; + public static int SQLITE_LIMIT_VARIABLE_NUMBER = -1; + public static int SQLITE_MAX_VARIABLE_NUMBER = -1; + public static int SQLITE_LIMIT_TRIGGER_DEPTH = -1; + public static int SQLITE_MAX_TRIGGER_DEPTH = -1; + public static int SQLITE_LIMIT_WORKER_THREADS = -1; + public static int SQLITE_MAX_WORKER_THREADS = -1; + + // open flags + public static final int SQLITE_OPEN_READONLY = 1; + public static final int SQLITE_OPEN_READWRITE = 2; + public static final int SQLITE_OPEN_CREATE = 4; + public static final int SQLITE_OPEN_URI = 64; + public static final int SQLITE_OPEN_MEMORY = 128; + public static final int SQLITE_OPEN_NOMUTEX = 32768; + public static final int SQLITE_OPEN_FULLMUTEX = 65536; + public static final int SQLITE_OPEN_SHAREDCACHE = 131072; + public static final int SQLITE_OPEN_PRIVATECACHE = 262144; + public static final int SQLITE_OPEN_EXRESCODE = 33554432; + public static final int SQLITE_OPEN_NOFOLLOW = 16777216; + public static final int SQLITE_OPEN_MAIN_DB = 256; + public static final int SQLITE_OPEN_MAIN_JOURNAL = 2048; + public static final int SQLITE_OPEN_TEMP_DB = 512; + public static final int SQLITE_OPEN_TEMP_JOURNAL = 4096; + public static final int SQLITE_OPEN_TRANSIENT_DB = 1024; + public static final int SQLITE_OPEN_SUBJOURNAL = 8192; + public static final int SQLITE_OPEN_SUPER_JOURNAL = 16384; + public static final int SQLITE_OPEN_WAL = 524288; + public static final int SQLITE_OPEN_DELETEONCLOSE = 8; + public static final int SQLITE_OPEN_EXCLUSIVE = 16; + + // prepare flags + public static final int SQLITE_PREPARE_PERSISTENT = 1; + public static final int SQLITE_PREPARE_NORMALIZE = 2; + public static final int SQLITE_PREPARE_NO_VTAB = 4; + + // result codes + public static final int SQLITE_OK = 0; + public static final int SQLITE_ERROR = 1; + public static final int SQLITE_INTERNAL = 2; + public static final int SQLITE_PERM = 3; + public static final int SQLITE_ABORT = 4; + public static final int SQLITE_BUSY = 5; + public static final int SQLITE_LOCKED = 6; + public static final int SQLITE_NOMEM = 7; + public static final int SQLITE_READONLY = 8; + public static final int SQLITE_INTERRUPT = 9; + public static final int SQLITE_IOERR = 10; + public static final int SQLITE_CORRUPT = 11; + public static final int SQLITE_NOTFOUND = 12; + public static final int SQLITE_FULL = 13; + public static final int SQLITE_CANTOPEN = 14; + public static final int SQLITE_PROTOCOL = 15; + public static final int SQLITE_EMPTY = 16; + public static final int SQLITE_SCHEMA = 17; + public static final int SQLITE_TOOBIG = 18; + public static final int SQLITE_CONSTRAINT = 19; + public static final int SQLITE_MISMATCH = 20; + public static final int SQLITE_MISUSE = 21; + public static final int SQLITE_NOLFS = 22; + public static final int SQLITE_AUTH = 23; + public static final int SQLITE_FORMAT = 24; + public static final int SQLITE_RANGE = 25; + public static final int SQLITE_NOTADB = 26; + public static final int SQLITE_NOTICE = 27; + public static final int SQLITE_WARNING = 28; + public static final int SQLITE_ROW = 100; + public static final int SQLITE_DONE = 101; + public static final int SQLITE_ERROR_MISSING_COLLSEQ = 257; + public static final int SQLITE_ERROR_RETRY = 513; + public static final int SQLITE_ERROR_SNAPSHOT = 769; + public static final int SQLITE_IOERR_READ = 266; + public static final int SQLITE_IOERR_SHORT_READ = 522; + public static final int SQLITE_IOERR_WRITE = 778; + public static final int SQLITE_IOERR_FSYNC = 1034; + public static final int SQLITE_IOERR_DIR_FSYNC = 1290; + public static final int SQLITE_IOERR_TRUNCATE = 1546; + public static final int SQLITE_IOERR_FSTAT = 1802; + public static final int SQLITE_IOERR_UNLOCK = 2058; + public static final int SQLITE_IOERR_RDLOCK = 2314; + public static final int SQLITE_IOERR_DELETE = 2570; + public static final int SQLITE_IOERR_BLOCKED = 2826; + public static final int SQLITE_IOERR_NOMEM = 3082; + public static final int SQLITE_IOERR_ACCESS = 3338; + public static final int SQLITE_IOERR_CHECKRESERVEDLOCK = 3594; + public static final int SQLITE_IOERR_LOCK = 3850; + public static final int SQLITE_IOERR_CLOSE = 4106; + public static final int SQLITE_IOERR_DIR_CLOSE = 4362; + public static final int SQLITE_IOERR_SHMOPEN = 4618; + public static final int SQLITE_IOERR_SHMSIZE = 4874; + public static final int SQLITE_IOERR_SHMLOCK = 5130; + public static final int SQLITE_IOERR_SHMMAP = 5386; + public static final int SQLITE_IOERR_SEEK = 5642; + public static final int SQLITE_IOERR_DELETE_NOENT = 5898; + public static final int SQLITE_IOERR_MMAP = 6154; + public static final int SQLITE_IOERR_GETTEMPPATH = 6410; + public static final int SQLITE_IOERR_CONVPATH = 6666; + public static final int SQLITE_IOERR_VNODE = 6922; + public static final int SQLITE_IOERR_AUTH = 7178; + public static final int SQLITE_IOERR_BEGIN_ATOMIC = 7434; + public static final int SQLITE_IOERR_COMMIT_ATOMIC = 7690; + public static final int SQLITE_IOERR_ROLLBACK_ATOMIC = 7946; + public static final int SQLITE_IOERR_DATA = 8202; + public static final int SQLITE_IOERR_CORRUPTFS = 8458; + public static final int SQLITE_LOCKED_SHAREDCACHE = 262; + public static final int SQLITE_LOCKED_VTAB = 518; + public static final int SQLITE_BUSY_RECOVERY = 261; + public static final int SQLITE_BUSY_SNAPSHOT = 517; + public static final int SQLITE_BUSY_TIMEOUT = 773; + public static final int SQLITE_CANTOPEN_NOTEMPDIR = 270; + public static final int SQLITE_CANTOPEN_ISDIR = 526; + public static final int SQLITE_CANTOPEN_FULLPATH = 782; + public static final int SQLITE_CANTOPEN_CONVPATH = 1038; + public static final int SQLITE_CANTOPEN_SYMLINK = 1550; + public static final int SQLITE_CORRUPT_VTAB = 267; + public static final int SQLITE_CORRUPT_SEQUENCE = 523; + public static final int SQLITE_CORRUPT_INDEX = 779; + public static final int SQLITE_READONLY_RECOVERY = 264; + public static final int SQLITE_READONLY_CANTLOCK = 520; + public static final int SQLITE_READONLY_ROLLBACK = 776; + public static final int SQLITE_READONLY_DBMOVED = 1032; + public static final int SQLITE_READONLY_CANTINIT = 1288; + public static final int SQLITE_READONLY_DIRECTORY = 1544; + public static final int SQLITE_ABORT_ROLLBACK = 516; + public static final int SQLITE_CONSTRAINT_CHECK = 275; + public static final int SQLITE_CONSTRAINT_COMMITHOOK = 531; + public static final int SQLITE_CONSTRAINT_FOREIGNKEY = 787; + public static final int SQLITE_CONSTRAINT_FUNCTION = 1043; + public static final int SQLITE_CONSTRAINT_NOTNULL = 1299; + public static final int SQLITE_CONSTRAINT_PRIMARYKEY = 1555; + public static final int SQLITE_CONSTRAINT_TRIGGER = 1811; + public static final int SQLITE_CONSTRAINT_UNIQUE = 2067; + public static final int SQLITE_CONSTRAINT_VTAB = 2323; + public static final int SQLITE_CONSTRAINT_ROWID = 2579; + public static final int SQLITE_CONSTRAINT_PINNED = 2835; + public static final int SQLITE_CONSTRAINT_DATATYPE = 3091; + public static final int SQLITE_NOTICE_RECOVER_WAL = 283; + public static final int SQLITE_NOTICE_RECOVER_ROLLBACK = 539; + public static final int SQLITE_WARNING_AUTOINDEX = 284; + public static final int SQLITE_AUTH_USER = 279; + public static final int SQLITE_OK_LOAD_PERMANENTLY = 256; + + // serialize + public static final int SQLITE_SERIALIZE_NOCOPY = 1; + public static final int SQLITE_DESERIALIZE_FREEONCLOSE = 1; + public static final int SQLITE_DESERIALIZE_READONLY = 4; + public static final int SQLITE_DESERIALIZE_RESIZEABLE = 2; + + // session + public static final int SQLITE_SESSION_CONFIG_STRMSIZE = 1; + public static final int SQLITE_SESSION_OBJCONFIG_SIZE = 1; + + // sqlite3 status + public static final int SQLITE_STATUS_MEMORY_USED = 0; + public static final int SQLITE_STATUS_PAGECACHE_USED = 1; + public static final int SQLITE_STATUS_PAGECACHE_OVERFLOW = 2; + public static final int SQLITE_STATUS_MALLOC_SIZE = 5; + public static final int SQLITE_STATUS_PARSER_STACK = 6; + public static final int SQLITE_STATUS_PAGECACHE_SIZE = 7; + public static final int SQLITE_STATUS_MALLOC_COUNT = 9; + + // stmt status + public static final int SQLITE_STMTSTATUS_FULLSCAN_STEP = 1; + public static final int SQLITE_STMTSTATUS_SORT = 2; + public static final int SQLITE_STMTSTATUS_AUTOINDEX = 3; + public static final int SQLITE_STMTSTATUS_VM_STEP = 4; + public static final int SQLITE_STMTSTATUS_REPREPARE = 5; + public static final int SQLITE_STMTSTATUS_RUN = 6; + public static final int SQLITE_STMTSTATUS_FILTER_MISS = 7; + public static final int SQLITE_STMTSTATUS_FILTER_HIT = 8; + public static final int SQLITE_STMTSTATUS_MEMUSED = 99; + + // sync flags + public static final int SQLITE_SYNC_NORMAL = 2; + public static final int SQLITE_SYNC_FULL = 3; + public static final int SQLITE_SYNC_DATAONLY = 16; + + // tracing flags + public static final int SQLITE_TRACE_STMT = 1; + public static final int SQLITE_TRACE_PROFILE = 2; + public static final int SQLITE_TRACE_ROW = 4; + public static final int SQLITE_TRACE_CLOSE = 8; + + // transaction state + public static final int SQLITE_TXN_NONE = 0; + public static final int SQLITE_TXN_READ = 1; + public static final int SQLITE_TXN_WRITE = 2; + + // udf flags + public static final int SQLITE_DETERMINISTIC = 2048; + public static final int SQLITE_DIRECTONLY = 524288; + public static final int SQLITE_INNOCUOUS = 2097152; + + // virtual tables + public static final int SQLITE_INDEX_SCAN_UNIQUE = 1; + public static final int SQLITE_INDEX_CONSTRAINT_EQ = 2; + public static final int SQLITE_INDEX_CONSTRAINT_GT = 4; + public static final int SQLITE_INDEX_CONSTRAINT_LE = 8; + public static final int SQLITE_INDEX_CONSTRAINT_LT = 16; + public static final int SQLITE_INDEX_CONSTRAINT_GE = 32; + public static final int SQLITE_INDEX_CONSTRAINT_MATCH = 64; + public static final int SQLITE_INDEX_CONSTRAINT_LIKE = 65; + public static final int SQLITE_INDEX_CONSTRAINT_GLOB = 66; + public static final int SQLITE_INDEX_CONSTRAINT_REGEXP = 67; + public static final int SQLITE_INDEX_CONSTRAINT_NE = 68; + public static final int SQLITE_INDEX_CONSTRAINT_ISNOT = 69; + public static final int SQLITE_INDEX_CONSTRAINT_ISNOTNULL = 70; + public static final int SQLITE_INDEX_CONSTRAINT_ISNULL = 71; + public static final int SQLITE_INDEX_CONSTRAINT_IS = 72; + public static final int SQLITE_INDEX_CONSTRAINT_LIMIT = 73; + public static final int SQLITE_INDEX_CONSTRAINT_OFFSET = 74; + public static final int SQLITE_INDEX_CONSTRAINT_FUNCTION = 150; + public static final int SQLITE_VTAB_CONSTRAINT_SUPPORT = 1; + public static final int SQLITE_VTAB_INNOCUOUS = 2; + public static final int SQLITE_VTAB_DIRECTONLY = 3; + public static final int SQLITE_VTAB_USES_ALL_SCHEMAS = 4; + public static final int SQLITE_ROLLBACK = 1; + public static final int SQLITE_FAIL = 3; + public static final int SQLITE_REPLACE = 5; + static { + // This MUST come after the SQLITE_MAX_... values or else + // attempting to modify them silently fails. + init(); + } +} diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java new file mode 100644 index 0000000000..dca49faf66 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -0,0 +1,1163 @@ +/* +** 2023-07-21 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains a set of tests for the sqlite3 JNI bindings. +*/ +package org.sqlite.jni; +import static org.sqlite.jni.SQLite3Jni.*; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +public class Tester1 { + private static final class Metrics { + int dbOpen; + } + + static final Metrics metrics = new Metrics(); + private static final OutputPointer.sqlite3_stmt outStmt + = new OutputPointer.sqlite3_stmt(); + + public static void out(Object val){ + System.out.print(val); + } + + public static void outln(Object val){ + System.out.println(val); + } + + @SuppressWarnings("unchecked") + public static void out(Object... vals){ + int n = 0; + for(Object v : vals) out((n++>0 ? " " : "")+v); + } + + @SuppressWarnings("unchecked") + public static void outln(Object... vals){ + out(vals); out("\n"); + } + + static int affirmCount = 0; + public static void affirm(Boolean v){ + ++affirmCount; + assert( v /* prefer assert over exception if it's enabled because + the JNI layer sometimes has to suppress exceptions. */); + if( !v ) throw new RuntimeException("Assertion failed."); + } + + private static void test1(){ + outln("libversion_number:", + sqlite3_libversion_number() + + "\n" + + sqlite3_libversion() + + "\n" + + SQLITE_SOURCE_ID); + affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER); + //outln("threadsafe = "+sqlite3_threadsafe()); + affirm(SQLITE_MAX_LENGTH > 0); + affirm(SQLITE_MAX_TRIGGER_DEPTH>0); + } + + public static sqlite3 createNewDb(){ + final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + int rc = sqlite3_open(":memory:", out); + ++metrics.dbOpen; + sqlite3 db = out.take(); + if( 0!=rc ){ + final String msg = db.getNativePointer()==0 + ? sqlite3_errstr(rc) + : sqlite3_errmsg(db); + throw new RuntimeException("Opening db failed: "+msg); + } + affirm( null == out.get() ); + affirm( 0 != db.getNativePointer() ); + rc = sqlite3_busy_timeout(db, 2000); + affirm( 0 == rc ); + return db; + } + + public static void execSql(sqlite3 db, String[] sql){ + execSql(db, String.join("", sql)); + } + + public static int execSql(sqlite3 db, boolean throwOnError, String sql){ + OutputPointer.Int32 oTail = new OutputPointer.Int32(); + final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8); + int pos = 0, n = 1; + byte[] sqlChunk = sqlUtf8; + int rc = 0; + sqlite3_stmt stmt = null; + while(pos < sqlChunk.length){ + if(pos > 0){ + sqlChunk = Arrays.copyOfRange(sqlChunk, pos, + sqlChunk.length); + } + if( 0==sqlChunk.length ) break; + rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); + if(throwOnError) affirm(0 == rc); + else if( 0!=rc ) break; + pos = oTail.value; + stmt = outStmt.take(); + if( null == stmt ){ + // empty statement was parsed. + continue; + } + affirm(0 != stmt.getNativePointer()); + while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){ + } + sqlite3_finalize(stmt); + affirm(0 == stmt.getNativePointer()); + if(0!=rc && SQLITE_ROW!=rc && SQLITE_DONE!=rc){ + break; + } + } + sqlite3_finalize(stmt); + if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0; + if( 0!=rc && throwOnError){ + throw new RuntimeException("db op failed with rc=" + +rc+": "+sqlite3_errmsg(db)); + } + return rc; + } + + public static void execSql(sqlite3 db, String sql){ + execSql(db, true, sql); + } + + public static sqlite3_stmt prepare(sqlite3 db, String sql){ + outStmt.clear(); + int rc = sqlite3_prepare(db, sql, outStmt); + affirm( 0 == rc ); + final sqlite3_stmt rv = outStmt.take(); + affirm( null == outStmt.get() ); + affirm( 0 != rv.getNativePointer() ); + return rv; + } + + private static void testCompileOption(){ + int i = 0; + String optName; + outln("compile options:"); + for( ; null != (optName = sqlite3_compileoption_get(i)); ++i){ + outln("\t"+optName+"\t (used="+ + sqlite3_compileoption_used(optName)+")"); + } + + } + + private static void testOpenDb1(){ + final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + int rc = sqlite3_open(":memory:", out); + ++metrics.dbOpen; + sqlite3 db = out.get(); + affirm(0 == rc); + affirm(0 < db.getNativePointer()); + sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, null) + /* This function has different mangled names in jdk8 vs jdk19, + and this call is here to ensure that the build fails + if it cannot find both names. */; + sqlite3_close_v2(db); + affirm(0 == db.getNativePointer()); + } + + private static void testOpenDb2(){ + final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + int rc = sqlite3_open_v2(":memory:", out, + SQLITE_OPEN_READWRITE + | SQLITE_OPEN_CREATE, null); + ++metrics.dbOpen; + affirm(0 == rc); + sqlite3 db = out.get(); + affirm(0 < db.getNativePointer()); + sqlite3_close_v2(db); + affirm(0 == db.getNativePointer()); + } + + private static void testPrepare123(){ + sqlite3 db = createNewDb(); + int rc; + rc = sqlite3_prepare(db, "CREATE TABLE t1(a);", outStmt); + affirm(0 == rc); + sqlite3_stmt stmt = outStmt.get(); + affirm(0 != stmt.getNativePointer()); + rc = sqlite3_step(stmt); + affirm(SQLITE_DONE == rc); + sqlite3_finalize(stmt); + affirm(0 == stmt.getNativePointer()); + + { /* Demonstrate how to use the "zTail" option of + sqlite3_prepare() family of functions. */ + OutputPointer.Int32 oTail = new OutputPointer.Int32(); + final byte[] sqlUtf8 = + "CREATE TABLE t2(a); INSERT INTO t2(a) VALUES(1),(2),(3)" + .getBytes(StandardCharsets.UTF_8); + int pos = 0, n = 1; + byte[] sqlChunk = sqlUtf8; + while(pos < sqlChunk.length){ + if(pos > 0){ + sqlChunk = Arrays.copyOfRange(sqlChunk, pos, sqlChunk.length); + } + //outln("SQL chunk #"+n+" length = "+sqlChunk.length+", pos = "+pos); + if( 0==sqlChunk.length ) break; + rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); + affirm(0 == rc); + stmt = outStmt.get(); + pos = oTail.value; + /*outln("SQL tail pos = "+pos+". Chunk = "+ + (new String(Arrays.copyOfRange(sqlChunk,0,pos), + StandardCharsets.UTF_8)));*/ + switch(n){ + case 1: affirm(19 == pos); break; + case 2: affirm(36 == pos); break; + default: affirm( false /* can't happen */ ); + + } + ++n; + affirm(0 != stmt.getNativePointer()); + rc = sqlite3_step(stmt); + affirm(SQLITE_DONE == rc); + sqlite3_finalize(stmt); + affirm(0 == stmt.getNativePointer()); + } + } + + + rc = sqlite3_prepare_v3(db, "INSERT INTO t2(a) VALUES(1),(2),(3)", + SQLITE_PREPARE_NORMALIZE, outStmt); + affirm(0 == rc); + stmt = outStmt.get(); + affirm(0 != stmt.getNativePointer()); + sqlite3_finalize(stmt); + affirm(0 == stmt.getNativePointer() ); + sqlite3_close_v2(db); + } + + private static void testBindFetchInt(){ + sqlite3 db = createNewDb(); + execSql(db, "CREATE TABLE t(a)"); + + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(:a);"); + affirm(1 == sqlite3_bind_parameter_count(stmt)); + final int paramNdx = sqlite3_bind_parameter_index(stmt, ":a"); + affirm(1 == paramNdx); + int total1 = 0; + long rowid = -1; + int changes = sqlite3_changes(db); + int changesT = sqlite3_total_changes(db); + long changes64 = sqlite3_changes64(db); + long changesT64 = sqlite3_total_changes64(db); + int rc; + for(int i = 99; i < 102; ++i ){ + total1 += i; + rc = sqlite3_bind_int(stmt, paramNdx, i); + affirm(0 == rc); + rc = sqlite3_step(stmt); + sqlite3_reset(stmt); + affirm(SQLITE_DONE == rc); + long x = sqlite3_last_insert_rowid(db); + affirm(x > rowid); + rowid = x; + } + sqlite3_finalize(stmt); + affirm(300 == total1); + affirm(sqlite3_changes(db) > changes); + affirm(sqlite3_total_changes(db) > changesT); + affirm(sqlite3_changes64(db) > changes64); + affirm(sqlite3_total_changes64(db) > changesT64); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); + int total2 = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + total2 += sqlite3_column_int(stmt, 0); + sqlite3_value sv = sqlite3_column_value(stmt, 0); + affirm( null != sv ); + affirm( 0 != sv.getNativePointer() ); + affirm( SQLITE_INTEGER == sqlite3_value_type(sv) ); + } + sqlite3_finalize(stmt); + affirm(total1 == total2); + sqlite3_close_v2(db); + affirm(0 == db.getNativePointer()); + } + + private static void testBindFetchInt64(){ + sqlite3 db = createNewDb(); + execSql(db, "CREATE TABLE t(a)"); + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); + long total1 = 0; + for(long i = 0xffffffff; i < 0xffffffff + 3; ++i ){ + total1 += i; + sqlite3_bind_int64(stmt, 1, i); + sqlite3_step(stmt); + sqlite3_reset(stmt); + } + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); + long total2 = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + total2 += sqlite3_column_int64(stmt, 0); + } + sqlite3_finalize(stmt); + affirm(total1 == total2); + sqlite3_close_v2(db); + } + + private static void testBindFetchDouble(){ + sqlite3 db = createNewDb(); + execSql(db, "CREATE TABLE t(a)"); + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); + double total1 = 0; + for(double i = 1.5; i < 5.0; i = i + 1.0 ){ + total1 += i; + sqlite3_bind_double(stmt, 1, i); + sqlite3_step(stmt); + sqlite3_reset(stmt); + } + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); + double total2 = 0; + int counter = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + ++counter; + total2 += sqlite3_column_double(stmt, 0); + } + affirm(4 == counter); + sqlite3_finalize(stmt); + affirm(total2<=total1+0.01 && total2>=total1-0.01); + sqlite3_close_v2(db); + } + + private static void testBindFetchText(){ + sqlite3 db = createNewDb(); + execSql(db, "CREATE TABLE t(a)"); + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); + String[] list1 = { "hell🤩", "w😃rld", "!" }; + int rc; + for( String e : list1 ){ + rc = sqlite3_bind_text(stmt, 1, e); + affirm(0 == rc); + rc = sqlite3_step(stmt); + affirm(SQLITE_DONE==rc); + sqlite3_reset(stmt); + } + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); + StringBuilder sbuf = new StringBuilder(); + int n = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + String txt = sqlite3_column_text16(stmt, 0); + //outln("txt = "+txt); + sbuf.append( txt ); + ++n; + } + sqlite3_finalize(stmt); + affirm(3 == n); + affirm("w😃rldhell🤩!".equals(sbuf.toString())); + sqlite3_close_v2(db); + } + + private static void testBindFetchBlob(){ + sqlite3 db = createNewDb(); + execSql(db, "CREATE TABLE t(a)"); + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); + byte[] list1 = { 0x32, 0x33, 0x34 }; + int rc = sqlite3_bind_blob(stmt, 1, list1); + affirm( 0==rc ); + rc = sqlite3_step(stmt); + affirm(SQLITE_DONE == rc); + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); + int n = 0; + int total = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + byte[] blob = sqlite3_column_blob(stmt, 0); + affirm(3 == blob.length); + int i = 0; + for(byte b : blob){ + affirm(b == list1[i++]); + total += b; + } + ++n; + } + sqlite3_finalize(stmt); + affirm(1 == n); + affirm(total == 0x32 + 0x33 + 0x34); + sqlite3_close_v2(db); + } + + private static void testSql(){ + sqlite3 db = createNewDb(); + sqlite3_stmt stmt = prepare(db, "SELECT 1"); + affirm( "SELECT 1".equals(sqlite3_sql(stmt)) ); + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT ?"); + sqlite3_bind_text(stmt, 1, "hell😃"); + affirm( "SELECT 'hell😃'".equals(sqlite3_expanded_sql(stmt)) ); + sqlite3_finalize(stmt); + } + + private static void testCollation(){ + final sqlite3 db = createNewDb(); + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + final ValueHolder xDestroyCalled = new ValueHolder<>(false); + final Collation myCollation = new Collation() { + private String myState = + "this is local state. There is much like it, but this is mine."; + @Override + // Reverse-sorts its inputs... + public int xCompare(byte[] lhs, byte[] rhs){ + int len = lhs.length > rhs.length ? rhs.length : lhs.length; + int c = 0, i = 0; + for(i = 0; i < len; ++i){ + c = lhs[i] - rhs[i]; + if(0 != c) break; + } + if(0==c){ + if(i < lhs.length) c = 1; + else if(i < rhs.length) c = -1; + } + return -c; + } + @Override + public void xDestroy() { + // Just demonstrates that xDestroy is called. + xDestroyCalled.value = true; + } + }; + final CollationNeeded collLoader = new CollationNeeded(){ + public int xCollationNeeded(sqlite3 dbArg, int eTextRep, String collationName){ + affirm(dbArg == db/* as opposed to a temporary object*/); + return sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation); + } + }; + int rc = sqlite3_collation_needed(db, collLoader); + affirm( 0 == rc ); + rc = sqlite3_collation_needed(db, collLoader); + affirm( 0 == rc /* Installing the same object again is a no-op */); + sqlite3_stmt stmt = prepare(db, "SELECT a FROM t ORDER BY a COLLATE reversi"); + int counter = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + final String val = sqlite3_column_text16(stmt, 0); + ++counter; + //outln("REVERSI'd row#"+counter+": "+val); + switch(counter){ + case 1: affirm("c".equals(val)); break; + case 2: affirm("b".equals(val)); break; + case 3: affirm("a".equals(val)); break; + } + } + affirm(3 == counter); + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT a FROM t ORDER BY a"); + counter = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + final String val = sqlite3_column_text16(stmt, 0); + ++counter; + //outln("Non-REVERSI'd row#"+counter+": "+val); + switch(counter){ + case 3: affirm("c".equals(val)); break; + case 2: affirm("b".equals(val)); break; + case 1: affirm("a".equals(val)); break; + } + } + affirm(3 == counter); + sqlite3_finalize(stmt); + affirm(!xDestroyCalled.value); + rc = sqlite3_collation_needed(db, null); + affirm( 0 == rc ); + sqlite3_close_v2(db); + affirm(xDestroyCalled.value); + } + + private static void testToUtf8(){ + /** + Java docs seem contradictory, claiming to use "modified UTF-8" + encoding while also claiming to export using RFC 2279: + + https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html + + Let's ensure that we can convert to standard UTF-8 in Java code + (noting that the JNI native API has no way to do this). + */ + final byte[] ba = "a \0 b".getBytes(StandardCharsets.UTF_8); + affirm( 5 == ba.length /* as opposed to 6 in modified utf-8 */); + } + + private static void testStatus(){ + final OutputPointer.Int64 cur64 = new OutputPointer.Int64(); + final OutputPointer.Int64 high64 = new OutputPointer.Int64(); + final OutputPointer.Int32 cur32 = new OutputPointer.Int32(); + final OutputPointer.Int32 high32 = new OutputPointer.Int32(); + final sqlite3 db = createNewDb(); + execSql(db, "create table t(a); insert into t values(1),(2),(3)"); + + int rc = sqlite3_status(SQLITE_STATUS_MEMORY_USED, cur32, high32, false); + affirm( 0 == rc ); + affirm( cur32.value > 0 ); + affirm( high32.value >= cur32.value ); + + rc = sqlite3_status64(SQLITE_STATUS_MEMORY_USED, cur64, high64, false); + affirm( 0 == rc ); + affirm( cur64.value > 0 ); + affirm( high64.value >= cur64.value ); + + cur32.value = 0; + high32.value = 1; + rc = sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, cur32, high32, false); + affirm( 0 == rc ); + affirm( cur32.value > 0 ); + affirm( high32.value == 0 /* always 0 for SCHEMA_USED */ ); + + sqlite3_close_v2(db); + } + + private static void testUdf1(){ + final sqlite3 db = createNewDb(); + // These ValueHolders are just to confirm that the func did what we want... + final ValueHolder xDestroyCalled = new ValueHolder<>(false); + final ValueHolder xFuncAccum = new ValueHolder<>(0); + + // Create an SQLFunction instance using one of its 3 subclasses: + // Scalar, Aggregate, or Window: + SQLFunction func = + // Each of the 3 subclasses requires a different set of + // functions, all of which must be implemented. Anonymous + // classes are a convenient way to implement these. + new SQLFunction.Scalar(){ + public void xFunc(sqlite3_context cx, sqlite3_value[] args){ + affirm(db == sqlite3_context_db_handle(cx)); + int result = 0; + for( sqlite3_value v : args ) result += sqlite3_value_int(v); + xFuncAccum.value += result;// just for post-run testing + sqlite3_result_int(cx, result); + } + /* OPTIONALLY override xDestroy... */ + public void xDestroy(){ + xDestroyCalled.value = true; + } + }; + + // Register and use the function... + int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func); + affirm(0 == rc); + affirm(0 == xFuncAccum.value); + final sqlite3_stmt stmt = prepare(db, "SELECT myfunc(1,2,3)"); + int n = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + affirm( 6 == sqlite3_column_int(stmt, 0) ); + ++n; + } + sqlite3_finalize(stmt); + affirm(1 == n); + affirm(6 == xFuncAccum.value); + affirm( !xDestroyCalled.value ); + sqlite3_close_v2(db); + affirm( xDestroyCalled.value ); + } + + private static void testUdfJavaObject(){ + final sqlite3 db = createNewDb(); + final ValueHolder testResult = new ValueHolder<>(db); + final SQLFunction func = new SQLFunction.Scalar(){ + public void xFunc(sqlite3_context cx, sqlite3_value args[]){ + sqlite3_result_java_object(cx, testResult.value); + } + }; + int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func); + affirm(0 == rc); + final sqlite3_stmt stmt = prepare(db, "select myfunc()"); + affirm( 0 != stmt.getNativePointer() ); + affirm( testResult.value == db ); + int n = 0; + if( SQLITE_ROW == sqlite3_step(stmt) ){ + final sqlite3_value v = sqlite3_column_value(stmt, 0); + affirm( testResult.value == sqlite3_value_java_object(v) ); + affirm( testResult.value == sqlite3_value_java_casted(v, sqlite3.class) ); + affirm( testResult.value == + sqlite3_value_java_casted(v, testResult.value.getClass()) ); + affirm( testResult.value == sqlite3_value_java_casted(v, Object.class) ); + affirm( null == sqlite3_value_java_casted(v, String.class) ); + ++n; + } + sqlite3_finalize(stmt); + affirm( 1 == n ); + sqlite3_close_v2(db); + } + + private static void testUdfAggregate(){ + final sqlite3 db = createNewDb(); + final ValueHolder xFinalNull = + // To confirm that xFinal() is called with no aggregate state + // when the corresponding result set is empty. + new ValueHolder<>(false); + SQLFunction func = new SQLFunction.Aggregate(){ + @Override + public void xStep(sqlite3_context cx, sqlite3_value[] args){ + this.getAggregateState(cx, 0).value += sqlite3_value_int(args[0]); + } + @Override + public void xFinal(sqlite3_context cx){ + final Integer v = this.takeAggregateState(cx); + if(null == v){ + xFinalNull.value = true; + sqlite3_result_null(cx); + }else{ + sqlite3_result_int(cx, v); + } + } + }; + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES(1),(2),(3)"); + int rc = sqlite3_create_function(db, "myfunc", 1, SQLITE_UTF8, func); + affirm(0 == rc); + sqlite3_stmt stmt = prepare(db, "select myfunc(a), myfunc(a+10) from t"); + int n = 0; + if( SQLITE_ROW == sqlite3_step(stmt) ){ + final int v = sqlite3_column_int(stmt, 0); + affirm( 6 == v ); + ++n; + } + affirm(!xFinalNull.value); + sqlite3_reset(stmt); + // Ensure that the accumulator is reset... + n = 0; + if( SQLITE_ROW == sqlite3_step(stmt) ){ + final int v = sqlite3_column_int(stmt, 0); + affirm( 6 == v ); + ++n; + } + sqlite3_finalize(stmt); + affirm( 1==n ); + + stmt = prepare(db, "select myfunc(a), myfunc(a+a) from t order by a"); + n = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + final int c0 = sqlite3_column_int(stmt, 0); + final int c1 = sqlite3_column_int(stmt, 1); + ++n; + affirm( 6 == c0 ); + affirm( 12 == c1 ); + } + affirm( 1 == n ); + affirm(!xFinalNull.value); + sqlite3_finalize(stmt); + + execSql(db, "SELECT myfunc(1) WHERE 0"); + affirm(xFinalNull.value); + sqlite3_close_v2(db); + } + + private static void testUdfWindow(){ + final sqlite3 db = createNewDb(); + /* Example window function, table, and results taken from: + https://sqlite.org/windowfunctions.html#udfwinfunc */ + final SQLFunction func = new SQLFunction.Window(){ + + private void xStepInverse(sqlite3_context cx, int v){ + this.getAggregateState(cx,0).value += v; + } + @Override public void xStep(sqlite3_context cx, sqlite3_value[] args){ + this.xStepInverse(cx, sqlite3_value_int(args[0])); + } + @Override public void xInverse(sqlite3_context cx, sqlite3_value[] args){ + this.xStepInverse(cx, -sqlite3_value_int(args[0])); + } + + private void xFinalValue(sqlite3_context cx, Integer v){ + if(null == v) sqlite3_result_null(cx); + else sqlite3_result_int(cx, v); + } + @Override public void xFinal(sqlite3_context cx){ + xFinalValue(cx, this.takeAggregateState(cx)); + } + @Override public void xValue(sqlite3_context cx){ + xFinalValue(cx, this.getAggregateState(cx,null).value); + } + }; + int rc = sqlite3_create_function(db, "winsumint", 1, SQLITE_UTF8, func); + affirm( 0 == rc ); + execSql(db, new String[] { + "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES", + "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)" + }); + final sqlite3_stmt stmt = prepare(db, + "SELECT x, winsumint(y) OVER ("+ + "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING"+ + ") AS sum_y "+ + "FROM twin ORDER BY x;"); + affirm( 0 == rc ); + int n = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + final String s = sqlite3_column_text16(stmt, 0); + final int i = sqlite3_column_int(stmt, 1); + switch(++n){ + case 1: affirm( "a".equals(s) && 9==i ); break; + case 2: affirm( "b".equals(s) && 12==i ); break; + case 3: affirm( "c".equals(s) && 16==i ); break; + case 4: affirm( "d".equals(s) && 12==i ); break; + case 5: affirm( "e".equals(s) && 9==i ); break; + default: affirm( false /* cannot happen */ ); + } + } + sqlite3_finalize(stmt); + affirm( 5 == n ); + sqlite3_close_v2(db); + } + + private static void listBoundMethods(){ + if(false){ + final java.lang.reflect.Field[] declaredFields = + SQLite3Jni.class.getDeclaredFields(); + outln("Bound constants:\n"); + for(java.lang.reflect.Field field : declaredFields) { + if(java.lang.reflect.Modifier.isStatic(field.getModifiers())) { + outln("\t"+field.getName()); + } + } + } + final java.lang.reflect.Method[] declaredMethods = + SQLite3Jni.class.getDeclaredMethods(); + final java.util.List funcList = new java.util.ArrayList<>(); + for(java.lang.reflect.Method m : declaredMethods){ + if((m.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0){ + final String name = m.getName(); + if(name.startsWith("sqlite3_")){ + funcList.add(name); + } + } + } + int count = 0; + java.util.Collections.sort(funcList); + for(String n : funcList){ + ++count; + outln("\t"+n+"()"); + } + outln(count+" functions named sqlite3_*."); + } + + private static void testTrace(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + /* Ensure that characters outside of the UTF BMP survive the trip + from Java to sqlite3 and back to Java. (At no small efficiency + penalty.) */ + final String nonBmpChar = "😃"; + sqlite3_trace_v2( + db, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE + | SQLITE_TRACE_ROW | SQLITE_TRACE_CLOSE, + new Tracer(){ + public int xCallback(int traceFlag, Object pNative, Object x){ + ++counter.value; + //outln("TRACE "+traceFlag+" pNative = "+pNative.getClass().getName()); + switch(traceFlag){ + case SQLITE_TRACE_STMT: + affirm(pNative instanceof sqlite3_stmt); + //outln("TRACE_STMT sql = "+x); + affirm(x instanceof String); + affirm( ((String)x).indexOf(nonBmpChar) > 0 ); + break; + case SQLITE_TRACE_PROFILE: + affirm(pNative instanceof sqlite3_stmt); + affirm(x instanceof Long); + //outln("TRACE_PROFILE time = "+x); + break; + case SQLITE_TRACE_ROW: + affirm(pNative instanceof sqlite3_stmt); + affirm(null == x); + //outln("TRACE_ROW = "+sqlite3_column_text16((sqlite3_stmt)pNative, 0)); + break; + case SQLITE_TRACE_CLOSE: + affirm(pNative instanceof sqlite3); + affirm(null == x); + break; + default: + affirm(false /*cannot happen*/); + break; + } + return 0; + } + }); + execSql(db, "SELECT coalesce(null,null,'"+nonBmpChar+"'); "+ + "SELECT 'w"+nonBmpChar+"orld'"); + affirm( 6 == counter.value ); + sqlite3_close_v2(db); + affirm( 7 == counter.value ); + } + + private static void testBusy(){ + final String dbName = "_busy-handler.db"; + final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3(); + + 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(); + rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo"); + affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) ); + + final ValueHolder xDestroyed = new ValueHolder<>(false); + final ValueHolder xBusyCalled = new ValueHolder<>(0); + BusyHandler handler = new BusyHandler(){ + @Override public int xCallback(int n){ + //outln("busy handler #"+n); + return n > 2 ? 0 : ++xBusyCalled.value; + } + @Override public void xDestroy(){ + xDestroyed.value = true; + } + }; + rc = sqlite3_busy_handler(db2, handler); + affirm(0 == rc); + + // Force a locked condition... + execSql(db1, "BEGIN EXCLUSIVE"); + affirm(!xDestroyed.value); + rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt); + affirm( SQLITE_BUSY == rc); + assert( null == outStmt.get() ); + affirm( 3 == xBusyCalled.value ); + sqlite3_close_v2(db1); + affirm(!xDestroyed.value); + sqlite3_close_v2(db2); + affirm(xDestroyed.value); + try{ + final java.io.File f = new java.io.File(dbName); + f.delete(); + }catch(Exception e){ + /* ignore */ + } + } + + private static void testProgress(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + sqlite3_progress_handler(db, 1, new ProgressHandler(){ + public int xCallback(){ + ++counter.value; + return 0; + } + }); + execSql(db, "SELECT 1; SELECT 2;"); + affirm( counter.value > 0 ); + int nOld = counter.value; + sqlite3_progress_handler(db, 0, null); + execSql(db, "SELECT 1; SELECT 2;"); + affirm( nOld == counter.value ); + sqlite3_close_v2(db); + } + + private static void testCommitHook(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder hookResult = new ValueHolder<>(0); + final CommitHook theHook = new CommitHook(){ + public int xCommitHook(){ + ++counter.value; + return hookResult.value; + } + }; + CommitHook oldHook = sqlite3_commit_hook(db, theHook); + affirm( null == oldHook ); + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 2 == counter.value ); + execSql(db, "BEGIN; SELECT 1; SELECT 2; COMMIT;"); + affirm( 2 == counter.value /* NOT invoked if no changes are made */ ); + execSql(db, "BEGIN; update t set a='d' where a='c'; COMMIT;"); + affirm( 3 == counter.value ); + oldHook = sqlite3_commit_hook(db, theHook); + affirm( theHook == oldHook ); + execSql(db, "BEGIN; update t set a='e' where a='d'; COMMIT;"); + affirm( 4 == counter.value ); + oldHook = sqlite3_commit_hook(db, null); + affirm( theHook == oldHook ); + execSql(db, "BEGIN; update t set a='f' where a='e'; COMMIT;"); + affirm( 4 == counter.value ); + oldHook = sqlite3_commit_hook(db, null); + affirm( null == oldHook ); + execSql(db, "BEGIN; update t set a='g' where a='f'; COMMIT;"); + affirm( 4 == counter.value ); + + final CommitHook newHook = new CommitHook(){ + public int xCommitHook(){return 0;} + }; + oldHook = sqlite3_commit_hook(db, newHook); + affirm( null == oldHook ); + execSql(db, "BEGIN; update t set a='h' where a='g'; COMMIT;"); + affirm( 4 == counter.value ); + oldHook = sqlite3_commit_hook(db, theHook); + affirm( newHook == oldHook ); + execSql(db, "BEGIN; update t set a='i' where a='h'; COMMIT;"); + affirm( 5 == counter.value ); + hookResult.value = SQLITE_ERROR; + int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;"); + affirm( SQLITE_CONSTRAINT == rc ); + affirm( 6 == counter.value ); + sqlite3_close_v2(db); + } + + private static void testUpdateHook(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder expectedOp = new ValueHolder<>(0); + final UpdateHook theHook = new UpdateHook(){ + @SuppressWarnings("unchecked") + public void xUpdateHook(int opId, String dbName, String tableName, long rowId){ + ++counter.value; + if( 0!=expectedOp.value ){ + affirm( expectedOp.value == opId ); + } + } + }; + UpdateHook oldHook = sqlite3_update_hook(db, theHook); + affirm( null == oldHook ); + expectedOp.value = SQLITE_INSERT; + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 3 == counter.value ); + expectedOp.value = SQLITE_UPDATE; + execSql(db, "update t set a='d' where a='c';"); + affirm( 4 == counter.value ); + oldHook = sqlite3_update_hook(db, theHook); + affirm( theHook == oldHook ); + expectedOp.value = SQLITE_DELETE; + execSql(db, "DELETE FROM t where a='d'"); + affirm( 5 == counter.value ); + oldHook = sqlite3_update_hook(db, null); + affirm( theHook == oldHook ); + execSql(db, "update t set a='e' where a='b';"); + affirm( 5 == counter.value ); + oldHook = sqlite3_update_hook(db, null); + affirm( null == oldHook ); + + final UpdateHook newHook = new UpdateHook(){ + public void xUpdateHook(int opId, String dbName, String tableName, long rowId){ + } + }; + oldHook = sqlite3_update_hook(db, newHook); + affirm( null == oldHook ); + execSql(db, "update t set a='h' where a='a'"); + affirm( 5 == counter.value ); + oldHook = sqlite3_update_hook(db, theHook); + affirm( newHook == oldHook ); + expectedOp.value = SQLITE_UPDATE; + execSql(db, "update t set a='i' where a='h'"); + affirm( 6 == counter.value ); + sqlite3_close_v2(db); + } + + private static void testRollbackHook(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + final RollbackHook theHook = new RollbackHook(){ + public void xRollbackHook(){ + ++counter.value; + } + }; + RollbackHook oldHook = sqlite3_rollback_hook(db, theHook); + affirm( null == oldHook ); + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 0 == counter.value ); + execSql(db, false, "BEGIN; SELECT 1; SELECT 2; ROLLBACK;"); + affirm( 1 == counter.value /* contra to commit hook, is invoked if no changes are made */ ); + + final RollbackHook newHook = new RollbackHook(){ + public void xRollbackHook(){return;} + }; + oldHook = sqlite3_rollback_hook(db, newHook); + affirm( theHook == oldHook ); + execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); + affirm( 1 == counter.value ); + oldHook = sqlite3_rollback_hook(db, theHook); + affirm( newHook == oldHook ); + execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); + affirm( 2 == counter.value ); + int rc = execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); + affirm( 0 == rc ); + affirm( 3 == counter.value ); + sqlite3_close_v2(db); + } + + /** + If FTS5 is available, runs FTS5 tests, else returns with no side + effects. If it is available but loading of the FTS5 bits fails, + it throws. + */ + @SuppressWarnings("unchecked") + private static void testFts5() throws Exception { + if( !SQLITE_ENABLE_FTS5 ){ + outln("SQLITE_ENABLE_FTS5 is not set. Skipping FTS5 tests."); + return; + } + Exception err = null; + try { + Class t = Class.forName("org.sqlite.jni.TesterFts5"); + java.lang.reflect.Constructor ctor = t.getConstructor(); + ctor.setAccessible(true); + ctor.newInstance() /* will run all tests */; + }catch(ClassNotFoundException e){ + outln("FTS5 classes not loaded."); + err = e; + }catch(NoSuchMethodException e){ + outln("FTS5 tester ctor not found."); + err = e; + }catch(Exception e){ + outln("Instantiation of FTS5 tester threw."); + err = e; + } + if( null != err ){ + outln("Exception: "+err); + err.printStackTrace(); + throw err; + } + } + + private static void testAuthorizer(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder authRc = new ValueHolder<>(0); + final Authorizer auth = new Authorizer(){ + public int xAuth(int op, String s0, String s1, String s2, String s3){ + ++counter.value; + //outln("xAuth(): "+s0+" "+s1+" "+s2+" "+s3); + return authRc.value; + } + }; + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + sqlite3_set_authorizer(db, auth); + execSql(db, "UPDATE t SET a=1"); + affirm( 1 == counter.value ); + authRc.value = SQLITE_DENY; + int rc = execSql(db, false, "UPDATE t SET a=2"); + affirm( SQLITE_AUTH==rc ); + // TODO: expand these tests considerably + sqlite3_close(db); + } + + private static void testAutoExtension(){ + final ValueHolder val = new ValueHolder<>(0); + final ValueHolder toss = new ValueHolder<>(null); + final AutoExtension ax = new AutoExtension(){ + public synchronized int xEntryPoint(sqlite3 db){ + ++val.value; + if( null!=toss.value ){ + throw new RuntimeException(toss.value); + } + return 0; + } + }; + int rc = sqlite3_auto_extension( ax ); + affirm( 0==rc ); + sqlite3_close(createNewDb()); + affirm( 1==val.value ); + sqlite3_close(createNewDb()); + affirm( 2==val.value ); + sqlite3_reset_auto_extension(); + sqlite3_close(createNewDb()); + affirm( 2==val.value ); + rc = sqlite3_auto_extension( ax ); + affirm( 0==rc ); + // Must not add a new entry + rc = sqlite3_auto_extension( ax ); + affirm( 0==rc ); + sqlite3_close( createNewDb() ); + affirm( 3==val.value ); + affirm( sqlite3_cancel_auto_extension(ax) ); + affirm( !sqlite3_cancel_auto_extension(ax) ); + sqlite3_close(createNewDb()); + affirm( 3==val.value ); + rc = sqlite3_auto_extension( ax ); + affirm( 0==rc ); + Exception err = null; + toss.value = "Throwing from AutoExtension."; + try{ + createNewDb(); + }catch(Exception e){ + err = e; + } + affirm( err!=null ); + affirm( err.getMessage().indexOf(toss.value)>0 ); + affirm( sqlite3_cancel_auto_extension(ax) ); + } + + private static void testSleep(){ + out("Sleeping briefly... "); + sqlite3_sleep(600); + outln("Woke up."); + } + + public static void main(String[] args) throws Exception { + final long timeStart = System.nanoTime(); + test1(); + if(false) testCompileOption(); + final java.util.List liArgs = + java.util.Arrays.asList(args); + testOpenDb1(); + testOpenDb2(); + testPrepare123(); + testBindFetchInt(); + testBindFetchInt64(); + testBindFetchDouble(); + testBindFetchText(); + testBindFetchBlob(); + testSql(); + testCollation(); + testToUtf8(); + testStatus(); + testUdf1(); + testUdfJavaObject(); + testUdfAggregate(); + testUdfWindow(); + testTrace(); + testBusy(); + testProgress(); + testCommitHook(); + testRollbackHook(); + testUpdateHook(); + testAuthorizer(); + testFts5(); + testAutoExtension(); + //testSleep(); + if(liArgs.indexOf("-v")>0){ + sqlite3_do_something_for_developer(); + //listBoundMethods(); + } + final long timeEnd = System.nanoTime(); + affirm( SQLite3Jni.uncacheJniEnv() ); + affirm( !SQLite3Jni.uncacheJniEnv() ); + outln("Tests done. Metrics:"); + outln("\tAssertions checked: "+affirmCount); + outln("\tDatabases opened: "+metrics.dbOpen); + + int nMethods = 0; + int nNatives = 0; + final java.lang.reflect.Method[] declaredMethods = + SQLite3Jni.class.getDeclaredMethods(); + for(java.lang.reflect.Method m : declaredMethods){ + int mod = m.getModifiers(); + if( 0!=(mod & java.lang.reflect.Modifier.STATIC) ){ + final String name = m.getName(); + if(name.startsWith("sqlite3_")){ + ++nMethods; + if( 0!=(mod & java.lang.reflect.Modifier.NATIVE) ){ + ++nNatives; + } + } + } + } + outln("\tSQLite3Jni sqlite3_*() methods: "+ + nNatives+" native methods and "+ + (nMethods - nNatives)+" Java impls"); + outln("\tTotal time = " + +((timeEnd - timeStart)/1000000.0)+"ms"); + } +} diff --git a/ext/jni/src/org/sqlite/jni/TesterFts5.java b/ext/jni/src/org/sqlite/jni/TesterFts5.java new file mode 100644 index 0000000000..6439768e29 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/TesterFts5.java @@ -0,0 +1,87 @@ +/* +** 2023-08-04 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains a set of tests for the sqlite3 JNI bindings. +*/ +package org.sqlite.jni; +import static org.sqlite.jni.SQLite3Jni.*; +import static org.sqlite.jni.Tester1.*; + +public class TesterFts5 { + + private static void test1(){ + Fts5ExtensionApi fea = Fts5ExtensionApi.getInstance(); + affirm( null != fea ); + affirm( fea.getNativePointer() != 0 ); + affirm( fea == Fts5ExtensionApi.getInstance() )/*singleton*/; + + sqlite3 db = createNewDb(); + fts5_api fApi = fts5_api.getInstanceForDb(db); + affirm( fApi != null ); + affirm( fApi == fts5_api.getInstanceForDb(db) /* singleton per db */ ); + + execSql(db, new String[] { + "CREATE VIRTUAL TABLE ft USING fts5(a, b);", + "INSERT INTO ft(rowid, a, b) VALUES(1, 'X Y', 'Y Z');", + "INSERT INTO ft(rowid, a, b) VALUES(2, 'A Z', 'Y Y');" + }); + + final String pUserData = "This is pUserData"; + ValueHolder xDestroyCalled = new ValueHolder<>(false); + ValueHolder xFuncCount = new ValueHolder<>(0); + final fts5_extension_function func = new fts5_extension_function(){ + public void xFunction(Fts5ExtensionApi ext, Fts5Context fCx, + sqlite3_context pCx, sqlite3_value argv[]){ + int nCols = ext.xColumnCount(fCx); + affirm( 2 == nCols ); + affirm( nCols == argv.length ); + affirm( ext.xUserData(fCx) == pUserData ); + if(true){ + OutputPointer.String op = new OutputPointer.String(); + for(int i = 0; i < nCols; ++i ){ + int rc = ext.xColumnText(fCx, i, op); + affirm( 0 == rc ); + final String val = op.value; + affirm( val.equals(sqlite3_value_text(argv[i])) ); + //outln("xFunction col "+i+": "+val); + } + } + ++xFuncCount.value; + } + public void xDestroy(){ + xDestroyCalled.value = true; + } + }; + + int rc = fApi.xCreateFunction("myaux", pUserData, func); + affirm( 0==rc ); + + affirm( 0==xFuncCount.value ); + execSql(db, "select myaux(ft,a,b) from ft;"); + affirm( 2==xFuncCount.value ); + affirm( !xDestroyCalled.value ); + sqlite3_close_v2(db); + affirm( xDestroyCalled.value ); + } + + public TesterFts5(){ + int oldAffirmCount = Tester1.affirmCount; + Tester1.affirmCount = 0; + final long timeStart = System.nanoTime(); + test1(); + final long timeEnd = System.nanoTime(); + outln("FTS5 Tests done. Metrics:"); + outln("\tAssertions checked: "+Tester1.affirmCount); + outln("\tTotal time = " + +((timeEnd - timeStart)/1000000.0)+"ms"); + Tester1.affirmCount = oldAffirmCount; + } +} diff --git a/ext/jni/src/org/sqlite/jni/Tracer.java b/ext/jni/src/org/sqlite/jni/Tracer.java new file mode 100644 index 0000000000..fa62edbfa7 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Tracer.java @@ -0,0 +1,62 @@ +/* +** 2023-07-22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + Callback proxy for use with sqlite3_trace_v2(). +*/ +public interface Tracer { + /** + Achtung: this interface is subject to change because the current + approach to mapping the passed-in natives back to Java is + uncomfortably quirky. + + 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. + + The 2nd argument to this function, if non-0, will be a native + pointer to either an sqlite3 or sqlite3_stmt object, depending on + the first argument (see below). Client code can pass it to the + sqlite3 resp. sqlite3_stmt constructor to create a wrapping + object, if necessary. This API does not do so by default because + tracing can be called frequently, creating such a wrapper for + each call is comparatively expensive, and the objects are + probably only seldom useful. + + The final argument to this function is the "X" argument + documented for sqlite3_trace() and sqlite3_trace_v2(). Its type + depends on value of the first argument: + + - SQLITE_TRACE_STMT: pNative is a sqlite3_stmt. pX is a string + containing the prepared SQL, with one caveat: JNI only provides + us with the ability to convert that string to MUTF-8, as + opposed to standard UTF-8, and is cannot be ruled out that that + difference may be significant for certain inputs. The + alternative would be that we first convert it to UTF-16 before + passing it on, but there's no readily-available way to do that + without calling back into the db to peform the conversion + (which would lead to further tracing). + + - SQLITE_TRACE_PROFILE: pNative is a sqlite3_stmt. pX is a Long + holding an approximate number of nanoseconds the statement took + to run. + + - SQLITE_TRACE_ROW: pNative is a sqlite3_stmt. pX is null. + + - SQLITE_TRACE_CLOSE: pNative is a sqlite3. pX is null. + */ + int xCallback(int traceFlag, Object pNative, Object pX); +} diff --git a/ext/jni/src/org/sqlite/jni/UpdateHook.java b/ext/jni/src/org/sqlite/jni/UpdateHook.java new file mode 100644 index 0000000000..171e2bdb41 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/UpdateHook.java @@ -0,0 +1,25 @@ +/* +** 2023-07-22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + Callback proxy for use with sqlite3_update_hook(). +*/ +public interface UpdateHook { + /** + Works as documented for the sqlite3_update_hook() callback. + Must not throw. + */ + void xUpdateHook(int opId, String dbName, String tableName, long rowId); +} diff --git a/ext/jni/src/org/sqlite/jni/ValueHolder.java b/ext/jni/src/org/sqlite/jni/ValueHolder.java new file mode 100644 index 0000000000..7f6a463ba5 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/ValueHolder.java @@ -0,0 +1,25 @@ +/* +** 2023-07-21 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + A helper class which simply holds a single value. Its current use + is for communicating values out of anonymous classes, as doing so + requires a "final" reference. +*/ +public class ValueHolder { + public T value; + public ValueHolder(){} + public ValueHolder(T v){value = v;} +} diff --git a/ext/jni/src/org/sqlite/jni/fts5_api.java b/ext/jni/src/org/sqlite/jni/fts5_api.java new file mode 100644 index 0000000000..43b3d62ded --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/fts5_api.java @@ -0,0 +1,69 @@ +/* +** 2023-08-05 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + INCOMPLETE AND COMPLETELY UNTESTED. + + A wrapper for communicating C-level (fts5_api*) instances with + Java. These wrappers do not own their associated pointer, they + simply provide a type-safe way to communicate it between Java and C + via JNI. +*/ +public final class fts5_api extends NativePointerHolder { + /* Only invoked from JNI */ + private fts5_api(){} + public final int iVersion = 2; + + /** + Returns the fts5_api instance associated with the given db, or + null if something goes horribly wrong. + */ + public static synchronized native fts5_api getInstanceForDb(@NotNull sqlite3 db); + + // int (*xCreateTokenizer)( + // fts5_api *pApi, + // const char *zName, + // void *pContext, + // fts5_tokenizer *pTokenizer, + // void (*xDestroy)(void*) + // ); + + // /* Find an existing tokenizer */ + // int (*xFindTokenizer)( + // fts5_api *pApi, + // const char *zName, + // void **ppContext, + // fts5_tokenizer *pTokenizer + // ); + + // /* Create a new auxiliary function */ + // int (*xCreateFunction)( + // fts5_api *pApi, + // const char *zName, + // void *pContext, + // fts5_extension_function xFunction, + // void (*xDestroy)(void*) + // ); + + public synchronized native int xCreateFunction(@NotNull String name, + @Nullable Object userData, + @NotNull fts5_extension_function xFunction); + + public int xCreateFunction(@NotNull String name, + @NotNull fts5_extension_function xFunction){ + return xCreateFunction(name, null, xFunction); + } + +} diff --git a/ext/jni/src/org/sqlite/jni/fts5_extension_function.java b/ext/jni/src/org/sqlite/jni/fts5_extension_function.java new file mode 100644 index 0000000000..0e273119f5 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/fts5_extension_function.java @@ -0,0 +1,37 @@ +/* +** 2023-08-05 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + JNI-level wrapper for C's fts5_extension_function type. + +*/ +public abstract class fts5_extension_function { + // typedef void (*fts5_extension_function)( + // const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ + // Fts5Context *pFts, /* First arg to pass to pApi functions */ + // sqlite3_context *pCtx, /* Context for returning result/error */ + // int nVal, /* Number of values in apVal[] array */ + // sqlite3_value **apVal /* Array of trailing arguments */ + // ); + + /** + The callback implementation, corresponding to the xFunction + argument of C's fts5_api::xCreateFunction(). + */ + public abstract void xFunction(Fts5ExtensionApi ext, Fts5Context fCx, + sqlite3_context pCx, sqlite3_value argv[]); + //! Optionally override + public void xDestroy(){} +} diff --git a/ext/jni/src/org/sqlite/jni/fts5_tokenizer.java b/ext/jni/src/org/sqlite/jni/fts5_tokenizer.java new file mode 100644 index 0000000000..097a0cc055 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/fts5_tokenizer.java @@ -0,0 +1,49 @@ +/* +** 2023-08-05 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + INCOMPLETE AND COMPLETELY UNTESTED. + + A wrapper for communicating C-level (fts5_tokenizer*) instances with + Java. These wrappers do not own their associated pointer, they + simply provide a type-safe way to communicate it between Java and C + via JNI. +*/ +public final class fts5_tokenizer extends NativePointerHolder { + /* Only invoked by JNI */ + private fts5_tokenizer(){} + + // int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut); + // void (*xDelete)(Fts5Tokenizer*); + + public native int xTokenize(@NotNull Fts5Tokenizer t, int tokFlags, + @NotNull byte pText[], + @NotNull Fts5.xTokenizeCallback callback); + + + // int (*xTokenize)(Fts5Tokenizer*, + // void *pCtx, + // int flags, /* Mask of FTS5_TOKENIZE_* flags */ + // const char *pText, int nText, + // int (*xToken)( + // void *pCtx, /* Copy of 2nd argument to xTokenize() */ + // int tflags, /* Mask of FTS5_TOKEN_* flags */ + // const char *pToken, /* Pointer to buffer containing token */ + // int nToken, /* Size of token in bytes */ + // int iStart, /* Byte offset of token within input text */ + // int iEnd /* Byte offset of end of token within input text */ + // ) + // ); +} diff --git a/ext/jni/src/org/sqlite/jni/sqlite3.java b/ext/jni/src/org/sqlite/jni/sqlite3.java new file mode 100644 index 0000000000..cfc6c08d47 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/sqlite3.java @@ -0,0 +1,37 @@ +/* +** 2023-07-21 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + A wrapper for communicating C-level (sqlite3*) instances with + Java. These wrappers do not own their associated pointer, they + simply provide a type-safe way to communicate it between Java + and C via JNI. +*/ +public final class sqlite3 extends NativePointerHolder { + // Only invoked from JNI + private sqlite3(){} + + public String toString(){ + long ptr = getNativePointer(); + if( 0==ptr ){ + return sqlite3.class.getSimpleName()+"@null"; + } + String fn = SQLite3Jni.sqlite3_db_filename(this, "main"); + return sqlite3.class.getSimpleName() + +"@"+String.format("0x%08x",ptr) + +"["+((null == fn) ? "" : fn)+"]" + ; + } +} diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_context.java b/ext/jni/src/org/sqlite/jni/sqlite3_context.java new file mode 100644 index 0000000000..a61ff21c7e --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/sqlite3_context.java @@ -0,0 +1,66 @@ +/* +** 2023-07-21 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + sqlite3_context instances are used in conjunction with user-defined + SQL functions (a.k.a. UDFs). +*/ +public final class sqlite3_context extends NativePointerHolder { + /** + For use only by the JNI layer. It's permitted to set this even + though it's private. + */ + private long aggregateContext = 0; + + /** + getAggregateContext() corresponds to C's + sqlite3_aggregate_context(), with a slightly different interface + to account for cross-language differences. It serves the same + purposes in a slightly different way: it provides a key which is + stable across invocations of "matching sets" of a UDF's callbacks, + such that all calls into those callbacks can determine which "set" + of those calls they belong to. + + If this object is being used in the context of an aggregate or + window UDF, this function returns a non-0 value which is distinct + for each set of UDF callbacks from a single invocation of the + UDF, otherwise it returns 0. The returned value is only only + valid within the context of execution of a single SQL statement, + and may be re-used by future invocations of the UDF in different + SQL statements. + + Consider this SQL, where MYFUNC is a user-defined aggregate function: + + SELECT MYFUNC(A), MYFUNC(B) FROM T; + + The xStep() and xFinal() methods of the callback need to be able + to differentiate between those two invocations in order to + perform their work properly. The value returned by + getAggregateContext() will be distinct for each of those + invocations of MYFUNC() and is intended to be used as a lookup + key for mapping callback invocations to whatever client-defined + state is needed by the UDF. + + There is one case where this will return 0 in the context of an + aggregate or window function: if the result set has no rows, + the UDF's xFinal() will be called without any other x...() members + having been called. In that one case, no aggregate context key will + have been generated. xFinal() implementations need to be prepared to + accept that condition as legal. + */ + public long getAggregateContext(){ + return aggregateContext; + } +} diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java b/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java new file mode 100644 index 0000000000..d672301378 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java @@ -0,0 +1,25 @@ +/* +** 2023-07-21 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +/** + A wrapper for communicating C-level (sqlite3_stmt*) instances with + Java. These wrappers do not own their associated pointer, they + simply provide a type-safe way to communicate it between Java and C + via JNI. +*/ +public final class sqlite3_stmt extends NativePointerHolder { + // Only invoked from JNI. + private sqlite3_stmt(){} +} diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_value.java b/ext/jni/src/org/sqlite/jni/sqlite3_value.java new file mode 100644 index 0000000000..2cfb32ff1a --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/sqlite3_value.java @@ -0,0 +1,19 @@ +/* +** 2023-07-21 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + +public final class sqlite3_value extends NativePointerHolder { + //! Invoked only from JNI. + private sqlite3_value(){} +} diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java new file mode 100644 index 0000000000..ffdb867d9b --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -0,0 +1,1421 @@ +/* +** 2023-08-08 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains the main application entry pointer for the +** SQLTester framework. +*/ +package org.sqlite.jni.tester; +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; +import java.nio.charset.StandardCharsets; +import java.util.regex.*; +import org.sqlite.jni.*; +import static org.sqlite.jni.SQLite3Jni.*; +import org.sqlite.jni.sqlite3; + + +/** + Modes for how to escape (or not) column values and names from + SQLTester.execSql() to the result buffer output. +*/ +enum ResultBufferMode { + //! Do not append to result buffer + NONE, + //! Append output escaped. + ESCAPED, + //! Append output as-is + ASIS +}; + +/** + Modes to specify how to emit multi-row output from + SQLTester.execSql() to the result buffer. +*/ +enum ResultRowMode { + //! Keep all result rows on one line, space-separated. + ONELINE, + //! Add a newline between each result row. + NEWLINE +}; + +/** + Base exception type for test-related failures. +*/ +class SQLTesterException extends RuntimeException { + private boolean bFatal = false; + + SQLTesterException(String msg){ + super(msg); + } + + protected SQLTesterException(String msg, boolean fatal){ + super(msg); + bFatal = fatal; + } + + /** + Indicates whether the framework should consider this exception + type as immediately fatal to the test run or not. + */ + final boolean isFatal(){ return bFatal; } +} + +class DbException extends SQLTesterException { + DbException(sqlite3 db, int rc, boolean closeDb){ + super("DB error #"+rc+": "+sqlite3_errmsg(db),true); + if( closeDb ) sqlite3_close_v2(db); + } + DbException(sqlite3 db, int rc){ + this(db, rc, false); + } +} + +/** + Generic test-failed exception. + */ +class TestScriptFailed extends SQLTesterException { + public TestScriptFailed(TestScript ts, String msg){ + super(ts.getOutputPrefix()+": "+msg, true); + } +} + +/** + Thrown when an unknown test command is encountered in a script. +*/ +class UnknownCommand extends SQLTesterException { + public UnknownCommand(TestScript ts, String cmd){ + super(ts.getOutputPrefix()+": unknown command: "+cmd, false); + } +} + +/** + Thrown when an "incompatible directive" is found in a script. This + can be the presence of a C-preprocessor construct, specific + metadata tags within a test script's header, or specific test + constructs which are incompatible with this particular + implementation. +*/ +class IncompatibleDirective extends SQLTesterException { + public IncompatibleDirective(TestScript ts, String line){ + super(ts.getOutputPrefix()+": incompatible directive: "+line, false); + } +} + +/** + Console output utility class. +*/ +class Outer { + private int verbosity = 0; + + static void out(Object val){ + System.out.print(val); + } + + Outer out(Object... vals){ + for(Object v : vals) out(v); + return this; + } + + Outer outln(Object... vals){ + out(vals).out("\n"); + return this; + } + + Outer verbose(Object... vals){ + if(verbosity>0){ + out("VERBOSE",(verbosity>1 ? "+: " : ": ")).outln(vals); + } + return this; + } + + void setVerbosity(int level){ + verbosity = level; + } + + int getVerbosity(){ + return verbosity; + } + + public boolean isVerbose(){return verbosity > 0;} + +} + +/** + This class provides an application which aims to implement the + rudimentary SQL-driven test tool described in the accompanying + test-script-interpreter.md. + + This is a work in progress. + + + An instance of this application provides a core set of services + which TestScript instances use for processing testing logic. + TestScripts, in turn, delegate the concrete test work to Command + objects, which the TestScript parses on their behalf. +*/ +public class SQLTester { + //! List of input script files. + private final java.util.List listInFiles = new ArrayList<>(); + //! Console output utility. + private final Outer outer = new Outer(); + //! Test input buffer. + private final StringBuilder inputBuffer = new StringBuilder(); + //! Test result buffer. + private final StringBuilder resultBuffer = new StringBuilder(); + //! Buffer for REQUIRED_PROPERTIES pragmas. + private final StringBuilder dbInitSql = new StringBuilder(); + //! Output representation of SQL NULL. + private String nullView = "nil"; + //! Total tests run. + private int nTotalTest = 0; + //! Total test script files run. + private int nTestFile = 0; + //! Number of scripts which were aborted. + private int nAbortedScript = 0; + //! Per-script test counter. + private int nTest = 0; + //! True to enable column name output from execSql() + private boolean emitColNames; + //! True to keep going regardless of how a test fails. + private boolean keepGoing = false; + //! The list of available db handles. + private final sqlite3[] aDb = new sqlite3[7]; + //! Index into aDb of the current db. + private int iCurrentDb = 0; + //! Name of the default db, re-created for each script. + private final String initialDbName = "test.db"; + + + public SQLTester(){ + reset(); + } + + void setVerbosity(int level){ + this.outer.setVerbosity( level ); + } + int getVerbosity(){ + return this.outer.getVerbosity(); + } + boolean isVerbose(){ + return this.outer.isVerbose(); + } + + void outputColumnNames(boolean b){ emitColNames = b; } + + void verbose(Object... vals){ + outer.verbose(vals); + } + + void outln(Object... vals){ + outer.outln(vals); + } + + void out(Object... vals){ + outer.out(vals); + } + + //! Adds the given test script to the to-test list. + public void addTestScript(String filename){ + listInFiles.add(filename); + //verbose("Added file ",filename); + } + + private void setupInitialDb() throws DbException { + if( null==aDb[0] ){ + Util.unlink(initialDbName); + openDb(0, initialDbName, true); + }else{ + outln("WARNING: setupInitialDb() unexpectedly ", + "triggered while it is opened."); + } + } + + static final String[] startEmoji = { + "🚴", "🏄", "🏇", "🤸", "⛹", "🏊", "⛷", "🧗", "🏋" + }; + static final int nStartEmoji = startEmoji.length; + static int iStartEmoji = 0; + + private static String nextStartEmoji(){ + return startEmoji[iStartEmoji++ % nStartEmoji]; + } + + public void runTests() throws Exception { + final long tStart = System.nanoTime(); + for(String f : listInFiles){ + reset(); + ++nTestFile; + final TestScript ts = new TestScript(f); + outln(nextStartEmoji(), " starting [",f,"]"); + boolean threw = false; + final long timeStart = System.nanoTime(); + try{ + ts.run(this); + }catch(SQLTesterException e){ + threw = true; + outln("🔥EXCEPTION: ",e.getClass().getSimpleName(),": ",e.getMessage()); + ++nAbortedScript; + if( keepGoing ) outln("Continuing anyway becaure of the keep-going option."); + else if( e.isFatal() ) throw e; + }finally{ + final long timeEnd = System.nanoTime(); + outln("🏁",(threw ? "❌" : "✅")," ",nTest," test(s) in ", + ((timeEnd-timeStart)/1000000.0),"ms."); + //ts.getFilename()); + } + } + final long tEnd = System.nanoTime(); + outln("Total run-time: ",((tEnd-tStart)/1000000.0),"ms"); + Util.unlink(initialDbName); + } + + private StringBuilder clearBuffer(StringBuilder b){ + b.setLength(0);; + return b; + } + + StringBuilder clearInputBuffer(){ + return clearBuffer(inputBuffer); + } + + StringBuilder clearResultBuffer(){ + return clearBuffer(resultBuffer); + } + + StringBuilder getInputBuffer(){ return inputBuffer; } + + void appendInput(String n, boolean addNL){ + inputBuffer.append(n); + if(addNL) inputBuffer.append('\n'); + } + + void appendResult(String n, boolean addNL){ + resultBuffer.append(n); + if(addNL) resultBuffer.append('\n'); + } + + void appendDbInitSql(String n) throws DbException { + dbInitSql.append(n).append('\n'); + if( null!=getCurrentDb() ){ + //outln("RUNNING DB INIT CODE: ",n); + execSql(null, true, ResultBufferMode.NONE, null, n); + } + } + String getDbInitSql(){ return dbInitSql.toString(); } + + String getInputText(){ return inputBuffer.toString(); } + + String getResultText(){ return resultBuffer.toString(); } + + private String takeBuffer(StringBuilder b){ + final String rc = b.toString(); + clearBuffer(b); + return rc; + } + + String takeInputBuffer(){ return takeBuffer(inputBuffer); } + + String takeResultBuffer(){ return takeBuffer(resultBuffer); } + + int getCurrentDbId(){ return iCurrentDb; } + + SQLTester affirmDbId(int n) throws IndexOutOfBoundsException { + if(n<0 || n>=aDb.length){ + throw new IndexOutOfBoundsException("illegal db number: "+n); + } + return this; + } + + sqlite3 setCurrentDb(int n) throws Exception{ + return affirmDbId(n).aDb[n]; + } + + sqlite3 getCurrentDb(){ return aDb[iCurrentDb]; } + + sqlite3 getDbById(int id) throws Exception{ + return affirmDbId(id).aDb[id]; + } + + void closeDb(int id) { + final sqlite3 db = affirmDbId(id).aDb[id]; + if( null != db ){ + sqlite3_close_v2(db); + aDb[id] = null; + } + } + + void closeDb() { closeDb(iCurrentDb); } + + void closeAllDbs(){ + for(int i = 0; i 0){ + //outln("RUNNING DB INIT CODE: ",dbInitSql.toString()); + rc = execSql(db, false, ResultBufferMode.NONE, + null, dbInitSql.toString()); + } + if( 0!=rc ){ + throw new DbException(db, rc, true); + } + return aDb[iCurrentDb] = db; + } + + sqlite3 openDb(int slot, String name, boolean createIfNeeded) throws DbException { + affirmDbId(slot); + iCurrentDb = slot; + return openDb(name, createIfNeeded); + } + + /** + Resets all tester context state except for that related to + tracking running totals. + */ + void reset(){ + clearInputBuffer(); + clearResultBuffer(); + clearBuffer(dbInitSql); + closeAllDbs(); + nTest = 0; + nullView = "nil"; + emitColNames = false; + iCurrentDb = 0; + dbInitSql.append("SELECT 1;"); + } + + void setNullValue(String v){nullView = v;} + + /** + If true, encountering an unknown command in a script causes the + remainder of the script to be skipped, rather than aborting the + whole script run. + */ + boolean skipUnknownCommands(){ + // Currently hard-coded. Potentially a flag someday. + return true; + } + + void incrementTestCounter(){ ++nTest; ++nTotalTest; } + + //! "Special" characters - we have to escape output if it contains any. + static final Pattern patternSpecial = Pattern.compile( + "[\\x00-\\x20\\x22\\x5c\\x7b\\x7d]" + ); + //! Either of '{' or '}'. + static final Pattern patternSquiggly = Pattern.compile("[{}]"); + + /** + Returns v or some escaped form of v, as defined in the tester's + spec doc. + */ + String escapeSqlValue(String v){ + if( "".equals(v) ) return "{}"; + Matcher m = patternSpecial.matcher(v); + if( !m.find() ){ + return v /* no escaping needed */; + } + m = patternSquiggly.matcher(v); + if( !m.find() ){ + return "{"+v+"}"; + } + final StringBuilder sb = new StringBuilder("\""); + final int n = v.length(); + for(int i = 0; i < n; ++i){ + final char ch = v.charAt(i); + switch(ch){ + case '\\': sb.append("\\\\"); break; + case '"': sb.append("\\\""); break; + default: + //verbose("CHAR ",(int)ch," ",ch," octal=",String.format("\\%03o", (int)ch)); + if( (int)ch < 32 ) sb.append(String.format("\\%03o", (int)ch)); + else sb.append(ch); + break; + } + } + sb.append("\""); + return sb.toString(); + } + + private void appendDbErr(sqlite3 db, StringBuilder sb, int rc){ + sb.append(org.sqlite.jni.ResultCode.getEntryForInt(rc)).append(' '); + final String msg = escapeSqlValue(sqlite3_errmsg(db)); + if( '{' == msg.charAt(0) ){ + sb.append(msg); + }else{ + sb.append('{').append(msg).append('}'); + } + } + + /** + Runs SQL on behalf of test commands and outputs the results following + the very specific rules of the test framework. + + If db is null, getCurrentDb() is assumed. If throwOnError is true then + any db-side error will result in an exception, else they result in + the db's result code. + + appendMode specifies how/whether to append results to the result + buffer. lineMode specifies whether to output all results in a + single line or one line per row. If appendMode is + ResultBufferMode.NONE then lineMode is ignored and may be null. + */ + public int execSql(sqlite3 db, boolean throwOnError, + ResultBufferMode appendMode, ResultRowMode lineMode, + String sql) throws SQLTesterException { + if( null==db && null==aDb[0] ){ + // Delay opening of the initial db to enable tests to change its + // name and inject on-connect code via, e.g., the MEMDB + // directive. this setup as the potential to misinteract with + // auto-extension timing and must be done carefully. + setupInitialDb(); + } + final OutputPointer.Int32 oTail = new OutputPointer.Int32(); + final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); + final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8); + if( null==db ) db = getCurrentDb(); + int pos = 0, n = 1; + byte[] sqlChunk = sqlUtf8; + int rc = 0; + sqlite3_stmt stmt = null; + int spacing = 0 /* emit a space for --result if>0 */ ; + final StringBuilder sb = (ResultBufferMode.NONE==appendMode) + ? null : resultBuffer; + //outln("sqlChunk len= = ",sqlChunk.length); + try{ + while(pos < sqlChunk.length){ + if(pos > 0){ + sqlChunk = Arrays.copyOfRange(sqlChunk, pos, + sqlChunk.length); + } + if( 0==sqlChunk.length ) break; + rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); + /*outln("PREPARE rc ",rc," oTail=",oTail.get(),": ", + new String(sqlChunk,StandardCharsets.UTF_8),"\n");*/ + if( 0!=rc ){ + if(throwOnError){ + throw new DbException(db, rc); + }else if( null!=sb ){ + appendDbErr(db, sb, rc); + } + break; + } + pos = oTail.value; + stmt = outStmt.take(); + if( null == stmt ){ + // empty statement was parsed. + continue; + } + if( null!=sb ){ + // Add the output to the result buffer... + final int nCol = sqlite3_column_count(stmt); + String colName = null, val = null; + while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){ + for(int i = 0; i < nCol; ++i){ + if( spacing++ > 0 ) sb.append(' '); + if( emitColNames ){ + colName = sqlite3_column_name(stmt, i); + switch(appendMode){ + case ASIS: + sb.append( colName ); + break; + case ESCAPED: + sb.append( escapeSqlValue(colName) ); + break; + default: + throw new SQLTesterException("Unhandled ResultBufferMode: "+appendMode); + } + sb.append(' '); + } + val = sqlite3_column_text16(stmt, i); + if( null==val ){ + sb.append( nullView ); + continue; + } + switch(appendMode){ + case ASIS: + sb.append( val ); + break; + case ESCAPED: + sb.append( escapeSqlValue(val) ); + break; + default: + throw new SQLTesterException("Unhandled ResultBufferMode: "+appendMode); + } + } + if( ResultRowMode.NEWLINE == lineMode ){ + spacing = 0; + sb.append('\n'); + } + } + }else{ + while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){} + } + sqlite3_finalize(stmt); + stmt = null; + if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0; + else if( rc!=0 ){ + if( null!=sb ){ + appendDbErr(db, sb, rc); + } + break; + } + } + }finally{ + sqlite3_finalize(stmt); + } + if( 0!=rc && throwOnError ){ + throw new DbException(db, rc); + } + return rc; + } + + public static void main(String[] argv) throws Exception{ + installCustomExtensions(); + boolean dumpInternals = false; + final SQLTester t = new SQLTester(); + for(String a : argv){ + if(a.startsWith("-")){ + final String flag = a.replaceFirst("-+",""); + if( flag.equals("verbose") ){ + // Use --verbose up to 3 times + t.setVerbosity(t.getVerbosity() + 1); + }else if( flag.equals("keep-going") ){ + t.keepGoing = true; + }else if( flag.equals("internals") ){ + dumpInternals = true; + }else{ + throw new IllegalArgumentException("Unhandled flag: "+flag); + } + continue; + } + t.addTestScript(a); + } + final AutoExtension ax = new AutoExtension() { + private final SQLTester tester = t; + public int xEntryPoint(sqlite3 db){ + final String init = tester.getDbInitSql(); + if( !init.isEmpty() ){ + tester.execSql(db, true, ResultBufferMode.NONE, null, init); + } + return 0; + } + }; + sqlite3_auto_extension(ax); + try { + t.runTests(); + }finally{ + sqlite3_cancel_auto_extension(ax); + t.outln("Processed ",t.nTotalTest," test(s) in ",t.nTestFile," file(s)."); + if( t.nAbortedScript > 0 ){ + t.outln("Aborted ",t.nAbortedScript," script(s)."); + } + if( dumpInternals ){ + sqlite3_do_something_for_developer(); + } + } + } + + /** + Internal impl of the public strglob() method. Neither argument + may be NULL and both _MUST_ be NUL-terminated. + */ + private static native int strglob(byte[] glob, byte[] txt); + + /** + Works essentially the same as sqlite3_strglob() except that the + glob character '#' matches a sequence of one or more digits. It + does not match when it appears at the start or middle of a series + of digits, e.g. "#23" or "1#3", but will match at the end, + e.g. "12#". + */ + static int strglob(String glob, String txt){ + return strglob( + (glob+"\0").getBytes(StandardCharsets.UTF_8), + (txt+"\0").getBytes(StandardCharsets.UTF_8) + ); + } + + /** + Sets up C-side components needed by the test framework. This must + not be called until main() is triggered so that it does not + interfere with library clients who don't use this class. + */ + static native void installCustomExtensions(); + static { + System.loadLibrary("sqlite3-jni") + /* Interestingly, when SQLTester is the main app, we have to + load that lib from here. The same load from SQLite3Jni does + not happen early enough. Without this, + installCustomExtensions() is an unresolved symbol. */; + } + +} + +/** + General utilities for the SQLTester bits. +*/ +final class Util { + + //! Throws a new T, appending all msg args into a string for the message. + static void toss(Class errorType, Object... msg) throws Exception { + StringBuilder sb = new StringBuilder(); + for(Object s : msg) sb.append(s); + final java.lang.reflect.Constructor ctor = + errorType.getConstructor(String.class); + throw ctor.newInstance(sb.toString()); + } + + static void toss(Object... msg) throws Exception{ + toss(RuntimeException.class, msg); + } + + //! Tries to delete the given file, silently ignoring failure. + static void unlink(String filename){ + try{ + final java.io.File f = new java.io.File(filename); + f.delete(); + }catch(Exception e){ + /* ignore */ + } + } + + /** + Appends all entries in argv[1..end] into a space-separated + string, argv[0] is not included because it's expected to be a + command name. + */ + static String argvToString(String[] argv){ + StringBuilder sb = new StringBuilder(); + for(int i = 1; i < argv.length; ++i ){ + if( i>1 ) sb.append(" "); + sb.append( argv[i] ); + } + return sb.toString(); + } + +} + +/** + Base class for test script commands. It provides a set of utility + APIs for concrete command implementations. + + Each subclass must have a public no-arg ctor and must implement + the process() method which is abstract in this class. + + Commands are intended to be stateless, except perhaps for counters + and similar internals. Specifically, no state which changes the + behavior between any two invocations of process() should be + retained. +*/ +abstract class Command { + protected Command(){} + + /** + Must process one command-unit of work and either return + (on success) or throw (on error). + + The first two arguments specify the context of the test. The TestScript + provides the content of the test and the SQLTester providers the sandbox + in which that script is being evaluated. + + argv is a list with the command name followed by any arguments to + that command. The argcCheck() method from this class provides + very basic argc validation. + */ + public abstract void process( + SQLTester st, TestScript ts, String[] argv + ) throws Exception; + + /** + If argv.length-1 (-1 because the command's name is in argv[0]) does not + fall in the inclusive range (min,max) then this function throws. Use + a max value of -1 to mean unlimited. + */ + protected final void argcCheck(TestScript ts, String[] argv, int min, int max) throws Exception{ + int argc = argv.length-1; + if(argc=0 && argc>max)){ + if( min==max ){ + ts.toss(argv[0]," requires exactly ",min," argument(s)"); + }else if(max>0){ + ts.toss(argv[0]," requires ",min,"-",max," arguments."); + }else{ + ts.toss(argv[0]," requires at least ",min," arguments."); + } + } + } + + /** + Equivalent to argcCheck(argv,argc,argc). + */ + protected final void argcCheck(TestScript ts, String[] argv, int argc) throws Exception{ + argcCheck(ts, argv, argc, argc); + } +} + +//! --close command +class CloseDbCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0,1); + Integer id; + if(argv.length>1){ + String arg = argv[1]; + if("all".equals(arg)){ + t.closeAllDbs(); + return; + } + else{ + id = Integer.parseInt(arg); + } + }else{ + id = t.getCurrentDbId(); + } + t.closeDb(id); + } +} + +//! --column-names command +class ColumnNamesCommand extends Command { + public void process( + SQLTester st, TestScript ts, String[] argv + ) throws Exception{ + argcCheck(ts,argv,1); + st.outputColumnNames( Integer.parseInt(argv[1])!=0 ); + } +} + +//! --db command +class DbCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1); + t.setCurrentDb( Integer.parseInt(argv[1]) ); + } +} + +//! --glob command +class GlobCommand extends Command { + private boolean negate = false; + public GlobCommand(){} + protected GlobCommand(boolean negate){ this.negate = negate; } + + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1,-1); + t.incrementTestCounter(); + final String sql = t.takeInputBuffer(); + int rc = t.execSql(null, true, ResultBufferMode.ESCAPED, + ResultRowMode.ONELINE, sql); + final String result = t.getResultText(); + final String sArgs = Util.argvToString(argv); + //t2.verbose2(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); + final String glob = Util.argvToString(argv); + rc = SQLTester.strglob(glob, result); + if( (negate && 0==rc) || (!negate && 0!=rc) ){ + ts.toss(argv[0], " mismatch: ", glob," vs input: ",result); + } + } +} + +//! --json command +class JsonCommand extends ResultCommand { + public JsonCommand(){ super(ResultBufferMode.ASIS); } +} + +//! --json-block command +class JsonBlockCommand extends TableResultCommand { + public JsonBlockCommand(){ super(true); } +} + +//! --new command +class NewDbCommand extends OpenDbCommand { + public NewDbCommand(){ super(true); } +} + +//! Placeholder dummy/no-op commands +class NoopCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + } +} + +//! --notglob command +class NotGlobCommand extends GlobCommand { + public NotGlobCommand(){ + super(true); + } +} + +//! --null command +class NullCommand extends Command { + public void process( + SQLTester st, TestScript ts, String[] argv + ) throws Exception{ + argcCheck(ts,argv,1); + st.setNullValue( argv[1] ); + } +} + +//! --open command +class OpenDbCommand extends Command { + private boolean createIfNeeded = false; + public OpenDbCommand(){} + protected OpenDbCommand(boolean c){createIfNeeded = c;} + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1); + t.openDb(argv[1], createIfNeeded); + } +} + +//! --print command +class PrintCommand extends Command { + public void process( + SQLTester st, TestScript ts, String[] argv + ) throws Exception{ + st.out(ts.getOutputPrefix(),": "); + if( 1==argv.length ){ + st.out( st.getInputText() ); + }else{ + st.outln( Util.argvToString(argv) ); + } + } +} + +//! --result command +class ResultCommand extends Command { + private final ResultBufferMode bufferMode; + protected ResultCommand(ResultBufferMode bm){ bufferMode = bm; } + public ResultCommand(){ this(ResultBufferMode.ESCAPED); } + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0,-1); + t.incrementTestCounter(); + final String sql = t.takeInputBuffer(); + //ts.verbose2(argv[0]," SQL =\n",sql); + int rc = t.execSql(null, false, bufferMode, ResultRowMode.ONELINE, sql); + final String result = t.getResultText().trim(); + final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; + if( !result.equals(sArgs) ){ + t.outln(argv[0]," FAILED comparison. Result buffer:\n", + result,"\nExpected result:\n",sArgs); + ts.toss(argv[0]+" comparison failed."); + } + } +} + +//! --run command +class RunCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0,1); + final sqlite3 db = (1==argv.length) + ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) ); + final String sql = t.takeInputBuffer(); + int rc = t.execSql(db, false, ResultBufferMode.NONE, + ResultRowMode.ONELINE, sql); + if( 0!=rc && t.isVerbose() ){ + String msg = sqlite3_errmsg(db); + ts.verbose1(argv[0]," non-fatal command error #",rc,": ", + msg,"\nfor SQL:\n",sql); + } + } +} + +//! --tableresult command +class TableResultCommand extends Command { + private final boolean jsonMode; + protected TableResultCommand(boolean jsonMode){ this.jsonMode = jsonMode; } + public TableResultCommand(){ this(false); } + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0); + t.incrementTestCounter(); + String body = ts.fetchCommandBody(t); + if( null==body ) ts.toss("Missing ",argv[0]," body."); + body = body.trim(); + if( !body.endsWith("\n--end") ){ + ts.toss(argv[0], " must be terminated with --end."); + }else{ + int n = body.length(); + body = body.substring(0, n-6); + } + final String[] globs = body.split("\\s*\\n\\s*"); + if( globs.length < 1 ){ + ts.toss(argv[0], " requires 1 or more ", + (jsonMode ? "json snippets" : "globs"),"."); + } + final String sql = t.takeInputBuffer(); + t.execSql(null, true, + jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED, + ResultRowMode.NEWLINE, sql); + final String rbuf = t.getResultText(); + final String[] res = rbuf.split("\n"); + if( res.length != globs.length ){ + ts.toss(argv[0], " failure: input has ", res.length, + " row(s) but expecting ",globs.length); + } + for(int i = 0; i < res.length; ++i){ + final String glob = globs[i].replaceAll("\\s+"," ").trim(); + //ts.verbose2(argv[0]," <<",glob,">> vs <<",res[i],">>"); + if( jsonMode ){ + if( !glob.equals(res[i]) ){ + ts.toss(argv[0], " json <<",glob, ">> does not match: <<", + res[i],">>"); + } + }else if( 0 != SQLTester.strglob(glob, res[i]) ){ + ts.toss(argv[0], " glob <<",glob,">> does not match: <<",res[i],">>"); + } + } + } +} + +//! --testcase command +class TestCaseCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1); + ts.setTestCaseName(argv[1]); + t.clearResultBuffer(); + t.clearInputBuffer(); + } +} + +//! --verbosity command +class VerbosityCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1); + ts.setVerbosity( Integer.parseInt(argv[1]) ); + } +} + +class CommandDispatcher { + + private static java.util.Map commandMap = + new java.util.HashMap<>(); + + /** + Returns a (cached) instance mapped to name, or null if no match + is found. + */ + static Command getCommandByName(String name){ + Command rv = commandMap.get(name); + if( null!=rv ) return rv; + switch(name){ + case "close": rv = new CloseDbCommand(); break; + case "column-names": rv = new ColumnNamesCommand(); break; + case "db": rv = new DbCommand(); break; + case "glob": rv = new GlobCommand(); break; + case "json": rv = new JsonCommand(); break; + case "json-block": rv = new JsonBlockCommand(); break; + case "new": rv = new NewDbCommand(); break; + case "notglob": rv = new NotGlobCommand(); break; + case "null": rv = new NullCommand(); break; + case "oom": rv = new NoopCommand(); break; + case "open": rv = new OpenDbCommand(); break; + case "print": rv = new PrintCommand(); break; + case "result": rv = new ResultCommand(); break; + case "run": rv = new RunCommand(); break; + case "tableresult": rv = new TableResultCommand(); break; + case "testcase": rv = new TestCaseCommand(); break; + case "verbosity": rv = new VerbosityCommand(); break; + default: rv = null; break; + } + if( null!=rv ) commandMap.put(name, rv); + return rv; + } + + /** + Treats argv[0] as a command name, looks it up with + getCommandByName(), and calls process() on that instance, passing + it arguments given to this function. + */ + static void dispatch(SQLTester tester, TestScript ts, String[] argv) throws Exception{ + final Command cmd = getCommandByName(argv[0]); + if(null == cmd){ + throw new UnknownCommand(ts, argv[0]); + } + cmd.process(tester, ts, argv); + } +} + + +/** + This class represents a single test script. It handles (or + delegates) its the reading-in and parsing, but the details of + evaluation are delegated elsewhere. +*/ +class TestScript { + //! input file + private String filename = null; + //! Name pulled from the SCRIPT_MODULE_NAME directive of the file + private String moduleName = null; + //! Current test case name. + private String testCaseName = null; + //! Content buffer state. + private final Cursor cur = new Cursor(); + //! Utility for console output. + private final Outer outer = new Outer(); + + //! File content and parse state. + private static final class Cursor { + private final StringBuilder sb = new StringBuilder(); + byte[] src = null; + //! Current position in this.src. + int pos = 0; + //! Current line number. Starts at 0 for internal reasons and will + // line up with 1-based reality once parsing starts. + int lineNo = 0 /* yes, zero */; + //! Putback value for this.pos. + int putbackPos = 0; + //! Putback line number + int putbackLineNo = 0; + //! Peeked-to pos, used by peekLine() and consumePeeked(). + int peekedPos = 0; + //! Peeked-to line number. + int peekedLineNo = 0; + + //! Restore parsing state to the start of the stream. + void rewind(){ + sb.setLength(0); + pos = lineNo = putbackPos = putbackLineNo = peekedPos = peekedLineNo = 0 + /* kinda missing memset() about now. */; + } + } + + private byte[] readFile(String filename) throws Exception { + return java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filename)); + } + + /** + Initializes the script with the content of the given file. + Throws if it cannot read the file. + */ + public TestScript(String filename) throws Exception{ + this.filename = filename; + setVerbosity(2); + cur.src = readFile(filename); + } + + public String getFilename(){ + return filename; + } + + public String getModuleName(){ + return moduleName; + } + + /** + Verbosity level 0 produces no debug/verbose output. Level 1 produces + some and level 2 produces more. + */ + public void setVerbosity(int level){ + outer.setVerbosity(level); + } + + public String getOutputPrefix(){ + String rc = "["+(moduleName==null ? filename : moduleName)+"]"; + if( null!=testCaseName ) rc += "["+testCaseName+"]"; + return rc + " line "+ cur.lineNo; + } + + static final String[] verboseLabel = {"🔈",/*"🔉",*/"🔊","📢"}; + //! Output vals only if level<=current verbosity level. + private TestScript verboseN(int level, Object... vals){ + final int verbosity = outer.getVerbosity(); + if(verbosity>=level){ + outer.out( verboseLabel[level-1], getOutputPrefix(), " ",level,": " + ).outln(vals); + } + return this; + } + + TestScript verbose1(Object... vals){return verboseN(1,vals);} + TestScript verbose2(Object... vals){return verboseN(2,vals);} + TestScript verbose3(Object... vals){return verboseN(3,vals);} + + private void reset(){ + testCaseName = null; + cur.rewind(); + } + + void setTestCaseName(String n){ testCaseName = n; } + + /** + Returns the next line from the buffer, minus the trailing EOL. + + Returns null when all input is consumed. Throws if it reads + illegally-encoded input, e.g. (non-)characters in the range + 128-256. + */ + String getLine(){ + if( cur.pos==cur.src.length ){ + return null /* EOF */; + } + cur.putbackPos = cur.pos; + cur.putbackLineNo = cur.lineNo; + cur.sb.setLength(0); + final boolean skipLeadingWs = false; + byte b = 0, prevB = 0; + int i = cur.pos; + if(skipLeadingWs) { + /* Skip any leading spaces, including newlines. This will eliminate + blank lines. */ + for(; i < cur.src.length; ++i, prevB=b){ + b = cur.src[i]; + switch((int)b){ + case 32/*space*/: case 9/*tab*/: case 13/*CR*/: continue; + case 10/*NL*/: ++cur.lineNo; continue; + default: break; + } + break; + } + if( i==cur.src.length ){ + return null /* EOF */; + } + } + boolean doBreak = false; + final byte[] aChar = {0,0,0,0} /* multi-byte char buffer */; + int nChar = 0 /* number of bytes in the char */; + for(; i < cur.src.length && !doBreak; ++i){ + b = cur.src[i]; + switch( (int)b ){ + case 13/*CR*/: continue; + case 10/*NL*/: + ++cur.lineNo; + if(cur.sb.length()>0) doBreak = true; + // Else it's an empty string + break; + default: + /* Multi-byte chars need to be gathered up and appended at + one time. Appending individual bytes to the StringBuffer + appends their integer value. */ + nChar = 1; + switch( b & 0xF0 ){ + case 0xC0: nChar = 2; break; + case 0xE0: nChar = 3; break; + case 0xF0: nChar = 4; break; + default: + if( b > 127 ) this.toss("Invalid character (#"+(int)b+")."); + break; + } + if( 1==nChar ){ + cur.sb.append((char)b); + }else{ + for(int x = 0; x < nChar; ++x) aChar[x] = cur.src[i+x]; + cur.sb.append(new String(Arrays.copyOf(aChar, nChar), + StandardCharsets.UTF_8)); + i += nChar-1; + } + break; + } + } + cur.pos = i; + final String rv = cur.sb.toString(); + if( i==cur.src.length && 0==rv.length() ){ + return null /* EOF */; + } + return rv; + }/*getLine()*/ + + /** + Fetches the next line then resets the cursor to its pre-call + state. consumePeeked() can be used to consume this peeked line + without having to re-parse it. + */ + String peekLine(){ + final int oldPos = cur.pos; + final int oldPB = cur.putbackPos; + final int oldPBL = cur.putbackLineNo; + final int oldLine = cur.lineNo; + final String rc = getLine(); + cur.peekedPos = cur.pos; + cur.peekedLineNo = cur.lineNo; + cur.pos = oldPos; + cur.lineNo = oldLine; + cur.putbackPos = oldPB; + cur.putbackLineNo = oldPBL; + return rc; + } + + /** + Only valid after calling peekLine() and before calling getLine(). + This places the cursor to the position it would have been at had + the peekLine() had been fetched with getLine(). + */ + void consumePeeked(){ + cur.pos = cur.peekedPos; + cur.lineNo = cur.peekedLineNo; + } + + /** + Restores the cursor to the position it had before the previous + call to getLine(). + */ + void putbackLine(){ + cur.pos = cur.putbackPos; + cur.lineNo = cur.putbackLineNo; + } + + private boolean checkRequiredProperties(SQLTester t, String[] props) throws SQLTesterException{ + int nOk = 0; + for(String rp : props){ + verbose1("REQUIRED_PROPERTIES: ",rp); + switch(rp){ + case "RECURSIVE_TRIGGERS": + t.appendDbInitSql("pragma recursive_triggers=on;"); + ++nOk; + break; + case "TEMPSTORE_FILE": + /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2, + which we just happen to know is the case */ + t.appendDbInitSql("pragma temp_store=1;"); + ++nOk; + break; + case "TEMPSTORE_MEM": + /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2, + which we just happen to know is the case */ + t.appendDbInitSql("pragma temp_store=0;"); + ++nOk; + break; + default: + break; + } + } + return props.length == nOk; + } + + private static final Pattern patternRequiredProperties = + Pattern.compile(" REQUIRED_PROPERTIES:[ \\t]*(\\S.*)\\s*$"); + private static final Pattern patternScriptModuleName = + Pattern.compile(" SCRIPT_MODULE_NAME:[ \\t]*(\\S+)\\s*$"); + private static final Pattern patternMixedModuleName = + Pattern.compile(" ((MIXED_)?MODULE_NAME):[ \\t]*(\\S+)\\s*$"); + private static final Pattern patternCommand = + Pattern.compile("^--(([a-z-]+)( .*)?)$"); + + /** + Looks for "directives." If a compatible one is found, it is + processed and this function returns. If an incompatible one is found, + a description of it is returned and processing of the test must + end immediately. + */ + private void checkForDirective( + SQLTester tester, String line + ) throws IncompatibleDirective { + if(line.startsWith("#")){ + throw new IncompatibleDirective(this, "C-preprocessor input: "+line); + }else if(line.startsWith("---")){ + new IncompatibleDirective(this, "triple-dash: "+line); + } + Matcher m = patternScriptModuleName.matcher(line); + if( m.find() ){ + moduleName = m.group(1); + return; + } + m = patternRequiredProperties.matcher(line); + if( m.find() ){ + final String rp = m.group(1); + //if( ! checkRequiredProperties( tester, rp.split("\\s+") ) ){ + throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp); + //} + } + m = patternMixedModuleName.matcher(line); + if( m.find() ){ + throw new IncompatibleDirective(this, m.group(1)+": "+m.group(3)); + } + if( line.indexOf("\n|")>=0 ){ + throw new IncompatibleDirective(this, "newline-pipe combination."); + } + return; + } + + boolean isCommandLine(String line, boolean checkForImpl){ + final Matcher m = patternCommand.matcher(line); + boolean rc = m.find(); + if( rc && checkForImpl ){ + rc = null!=CommandDispatcher.getCommandByName(m.group(2)); + } + return rc; + } + + /** + If line looks like a command, returns an argv for that command + invocation, else returns null. + */ + String[] getCommandArgv(String line){ + final Matcher m = patternCommand.matcher(line); + return m.find() ? m.group(1).trim().split("\\s+") : null; + } + + /** + Fetches lines until the next recognized command. Throws if + checkForDirective() does. Returns null if there is no input or + it's only whitespace. The returned string retains all whitespace. + + Note that "subcommands", --command-like constructs in the body + which do not match a known command name are considered to be + content, not commands. + */ + String fetchCommandBody(SQLTester tester){ + final StringBuilder sb = new StringBuilder(); + String line; + while( (null != (line = peekLine())) ){ + checkForDirective(tester, line); + if( !isCommandLine(line, true) ){ + sb.append(line).append("\n"); + consumePeeked(); + }else{ + break; + } + } + line = sb.toString(); + return line.trim().isEmpty() ? null : line; + } + + private void processCommand(SQLTester t, String[] argv) throws Exception{ + verbose1("running command: ",argv[0], " ", Util.argvToString(argv)); + if(outer.getVerbosity()>1){ + final String input = t.getInputText(); + if( !input.isEmpty() ) verbose3("Input buffer = ",input); + } + CommandDispatcher.dispatch(t, this, argv); + } + + void toss(Object... msg) throws TestScriptFailed { + StringBuilder sb = new StringBuilder(); + for(Object s : msg) sb.append(s); + throw new TestScriptFailed(this, sb.toString()); + } + + /** + Runs this test script in the context of the given tester object. + */ + public boolean run(SQLTester tester) throws Exception { + reset(); + setVerbosity(tester.getVerbosity()); + String line, directive; + String[] argv; + while( null != (line = getLine()) ){ + verbose3("input line: ",line); + checkForDirective(tester, line); + argv = getCommandArgv(line); + if( null!=argv ){ + processCommand(tester, argv); + continue; + } + tester.appendInput(line,true); + } + return true; + } +} diff --git a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md new file mode 100644 index 0000000000..a51d64d107 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md @@ -0,0 +1,269 @@ +# Specifications For A Rudimentary SQLite Test Script Interpreter + +## Overview + +The purpose of the Test Script Interpreter is to read and interpret +script files that contain SQL commands and desired results. The +interpreter will check results and report an discrepencies found. + +The test script files are ASCII text files. The filename always ends with +".test". Each script is evaluated independently; context does not carry +forward from one script to the next. So, for example, the --null command +run in one test script does not cause any changes in the behavior of +subsequent test scripts. All open database connections are closed +at the end of each test script. All database files created by a test +script are deleted when the script finishes. + +## Parsing Rules: + + 1. The test script is read line by line, where a line is a sequence of + characters that runs up to the next '\\n' (0x0a) character or until + the end of the file. There is never a need to read ahead past the + end of the current line. + + 2. If any line contains the string " MODULE_NAME:" (with a space before + the initial "M") or "MIXED_MODULE_NAME:" then that test script is + incompatible with this spec. Processing of the test script should + end immediately. There is no need to read any more of the file. + In verbose mode, the interpreter might choose to emit an informational + messages saying that the test script was abandoned due to an + incompatible module type. + + 3. If any line contains the string "SCRIPT_MODULE_NAME:" then the input + script is known to be of the correct type for this specification and + processing may continue. The "MODULE_NAME" checking in steps 2 and 3 + may optionally be discontinued after sighting a "SCRIPT_MODULE_NAME". + + 4. If any line contains "REQUIRED_PROPERTIES:" and that substring is followed + by any non-whitespace text, then the script is not compatible with this + spec. Processing should stop immediately. In verbose mode, the + interpreter might choose to emit an information message saying that the + test script was abandoned due to unsupported requirement properties. + + 5. If any line begins with the "\|" (0x7c) character, that indicates that + the input script is not compatible with this specification. Processing + of the script should stop immediately. In verbose mode, the interpreter + might choose to emit an informational message indicating that the + test script was abandoned because it contained "a dbtotxt format database + specification". + + 6. Any line that begins with "#" is a C-preprocessor line. The interpreter + described by this spec does not know how to deal with C-preprocessor lines. + Hence, processing should be abandoned. In verbose mode, the interpreter + might emit an informational message similar to + "script NAME abandoned due to C-preprocessor line: ..." + + 7. If a line begins with exactly two minus signs followed by a + lowercase letter, that is a command. Process commands as described + below. + + 8. All other lines should be accumulated into the "input buffer". + The various commands will have access to this input buffer. + Some commands will reset the buffer. + +## Initialization + +The initial state of the interpreter at the start of processing each script +is as if the following command sequence had been run: + +> ~~~ +--close all +--db 0 +--new test.db +--null nil +~~~ + +In words, all database connections are closed except for connection 0 (the +default) which is open on an empty database named "test.db". The string +"nil" is displayed for NULL column values. + +The only context carried forward after the evaluation of one test script +into the evaluation of the next test script is the count of the number of +tests run and the number of failures seen. + +## Commands: + +Each command looks like an SQL comment. The command begins at the left +margin (no leading space) and starts with exactly 2 minus signs ("-"). +The command name consists of lowercase letters and maybe a "-" or two. +Some commands have arguments. +The arguments are separated from the command name by one or more spaces. + +Commands have access to the input buffer and might reset the input buffer. +The command can also optionally read (and consume) additional text from +script that comes after the command. + +Unknown or unrecognized commands indicate that the script contains features +that are not (yet) supported by this specification. Processing of the +script should terminate immediately. When this happens and when the +interpreter is in a "verbose" mode, the interpreter might choose to emit +an informational message along the lines of "test script NAME abandoned +due to unsupported command: --whatever". + +The initial implemention will only recognize a few commands. Other +commands may be added later. The following is the initial set of +commands: + +### The --testcase command + +Every test case starts with a --testcase command. The --testcase +command resets both the "input buffer" and the "result buffer". The +argument to the --testcase command is the name of the test case. That +test case name is used for logging and debugging and when printing +errors. The input buffer is set to the body of the test case. + +### The --result command + +The --result command tries to execute the text in the input buffer as SQL. +For each row of result coming out of this SQL, the text of that result is +appended to the "result buffer". If a result row contains multiple columns, +the columns are processed from left to right. For each column, text is +appended to the result buffer according to the following rules: + + * If the result buffer already contains some text, append a space. + (In this way, all column values and all row values are separated from + each other by a single space.) + + * If sqlite3_column_text() returns NULL, then append "nil" - or + some other text that is specified by the --null command - and skip + all subsequent rules. + + * If sqlite3_column_text() is an empty string, append `{}` to the + result buffer and skip all subsequent rules. + + * If sqlite3_column_text() does not contain any special + characters, append it to the result buffer without any + formatting and skip all subsequent rules. Special characters are: + 0x00 to 0x20 (inclusive), double-quote (0x22), backslash (0x5c), + curly braces (0x7b and 0x7d). + + * If sqlite3_column_text() does not contains curly braces, then put + the text inside of `{...}` and append it and skip all subsequent rules. + + * Append the text within double-quotes (`"..."`) and within the text + escape '"' and '\\' by prepending a single '\\' and escape any + control characters (characters less than 0x20) using octal notation: + '\\NNN'. + +If an error is encountered while running the SQL, then append the +symbolic C-preprocessor name for the error +code (ex: "SQLITE_CONSTRAINT") as if it were a column value. Then append +the error message text as if it where a column value. Then stop processing. + +After the SQL text has been run, compare the content of the result buffer +against the argument to the --result command and report a testing error if +there are any differences. + +The --result command resets the input buffer, but it does not reset +the result buffer. This distinction does not matter for the --result +command itself, but it is important for related commands like --glob +and --notglob. Sometimes test cases will contains a bunch of SQL +followed by multiple --glob and/or --notglob statements. All of the +globs should be evaluted agains the result buffer correct, but the SQL +should only be run once. This is accomplished by resetting the input +buffer but not the result buffer. + +### The --glob command + +The --glob command works just like --result except that the argument to +--glob is interpreted as a TEST-GLOB pattern and the results are compared +using that glob pattern rather than using strcmp(). Other than that, +the two operate the same. + +The TEST-GLOB pattern is slightly different for a standard GLOB: + + * The '*' character matches zero or more characters. + + * The '?' character matches any single character + + * The '[...]' character sequence machines a single character + in between the brackets. + + * The '#' character matches one or more digits (This is the main + difference between standard unix-glob and TEST-GLOB. unix-glob + does not have this feature. It was added to because it comes + up a lot during SQLite testing.) + +### The --notglob command + +The --notglob command works just like --glob except that it reports an +error if the GLOB does match, rather than if the GLOB does not matches. + +### The --oom command + +This command is to be used for out-of-memory testing. It means that +OOM errors should be simulated to ensure that SQLite is able to deal with +them. This command can be silently ignored for now. We might add support +for this later. + +### The --tableresult command + +The --tableresult command works like --glob except that the GLOB pattern +to be matched is taken from subsequent lines of the input script up to +the next --end. Every span of one or more whitespace characters in this +pattern text is collapsed into a single space (0x20). +Leading and trailing whitespace are removed from the pattern. +The --end that ends the GLOB pattern is not part of the GLOB pattern, but +the --end is consumed from the script input. + +### The --new and --open commands + +The --new and --open commands cause a database file to be opened. +The name of the file is the argument to the command. The --new command +opens an initially empty database (it deletes the file before opening it) +whereas the --open command opens an existing database if it already +exists. + +### The --db command + +The script interpreter can have up to 7 different SQLite database +connections open at a time. The --db command is used to switch between +them. The argument to --db is an integer between 0 and 6 that selects +which database connection to use moving forward. + +### The --close command + +The --close command causes an existing database connection to close. +This command is a no-op if the database connection is not currently +open. There can be up to 7 different database connections, numbered 0 +through 6. The number of the database connection to close is an +argument to the --close command, which will fail if an out-of-range +value is provided. Or if the argument to --close is "all" then all +open database connections are closed. If passed no argument, the +currently-active database is assumed. + +### The --null command + +The NULL command changes the text that is used to represent SQL NULL +values in the result buffer. + +### The --run command + +The --run command executes text in the input buffer as if it where SQL. +However, nothing is added to the result buffer. Any output from the SQL +is silently ignored. Errors in the SQL are silently ignored. + +The --run command normally executes the SQL in the current database +connection. However, if --run has an argument that is an integer between +0 and 6 then the SQL is run in the alternative database connection specified +by that argument. + +### The --json and --json-block commands + +The --json and --json-block commands work like --result and --tableresult, +respectively. The difference is that column values are appended to the +result buffer literally, without ever enclosing the values in `{...}` or +`"..."` and without escaping any characters in the column value and comparison +is always an exact strcmp() not a GLOB. + +### The --print command + +The --print command emits both its arguments and its body (if any) to +stdout, indenting each line of output. + +### The --column-names command + +The --column-names command requires 0 or 1 as an argument, to disable +resp. enable it, and modifies SQL execution to include column names +in output. When this option is on, each column value emitted gets +prefixed by its column name, with a single space between them. diff --git a/ext/jni/src/tests/000-000-sanity.test b/ext/jni/src/tests/000-000-sanity.test new file mode 100644 index 0000000000..2bfacb1cec --- /dev/null +++ b/ext/jni/src/tests/000-000-sanity.test @@ -0,0 +1,52 @@ +/* +** This is a comment. There are many like it but this one is mine. +** +** SCRIPT_MODULE_NAME: sanity-check +** xMIXED_MODULE_NAME: mixed-module +** xMODULE_NAME: module-name +** xREQUIRED_PROPERTIES: small fast reliable +** xREQUIRED_PROPERTIES: RECURSIVE_TRIGGERS +** xREQUIRED_PROPERTIES: TEMPSTORE_MEM TEMPSTORE_FILE +** +*/ +--print starting up 😃 +--close all +--oom +--db 0 +--new my.db +--null zilch +--testcase 1.0 +SELECT 1, null; +--result 1 zilch +--glob *zil* +--notglob *ZIL* +SELECT 1, 2; +intentional error; +--run +--testcase json-1 +SELECT json_array(1,2,3) +--json [1,2,3] +--testcase tableresult-1 + select 1, 'a'; + select 2, 'b'; +--tableresult + # [a-z] + 2 b +--end +--testcase json-block-1 + select json_array(1,2,3); + select json_object('a',1,'b',2); +--json-block + [1,2,3] + {"a":1,"b":2} +--end +--testcase col-names-on +--column-names 1 + select 1 as 'a', 2 as 'b'; +--result a 1 b 2 +--testcase col-names-off +--column-names 0 + select 1 as 'a', 2 as 'b'; +--result 1 2 +--close +--print reached the end 😃 diff --git a/ext/jni/src/tests/000-001-ignored.test b/ext/jni/src/tests/000-001-ignored.test new file mode 100644 index 0000000000..5af852e197 --- /dev/null +++ b/ext/jni/src/tests/000-001-ignored.test @@ -0,0 +1,9 @@ +/* +** This script must be marked as ignored because it contains +** content which triggers that condition. +** +** SCRIPT_MODULE_NAME: ignored +** +*/ + +| diff --git a/ext/misc/decimal.c b/ext/misc/decimal.c index 06286f23fd..9365ae68b9 100644 --- a/ext/misc/decimal.c +++ b/ext/misc/decimal.c @@ -642,7 +642,7 @@ static Decimal *decimalFromDouble(double r){ /* ** SQL Function: decimal(X) -** OR: decimal_sci(X) +** OR: decimal_exp(X) ** ** Convert input X into decimal and then back into text. ** @@ -650,7 +650,7 @@ static Decimal *decimalFromDouble(double r){ ** point value is done. Or if X is an 8-byte blob, it is interpreted ** as a float and similarly expanded. ** -** The decimal_sci(X) function returns the result in scientific notation. +** The decimal_exp(X) function returns the result in exponential notation. ** decimal(X) returns a complete decimal, without the e+NNN at the end. */ static void decimalFunc( @@ -853,7 +853,7 @@ int sqlite3_decimal_init( void (*xFunc)(sqlite3_context*,int,sqlite3_value**); } aFunc[] = { { "decimal", 1, 0, decimalFunc }, - { "decimal_sci", 1, 1, decimalFunc }, + { "decimal_exp", 1, 1, decimalFunc }, { "decimal_cmp", 2, 0, decimalCmpFunc }, { "decimal_add", 2, 0, decimalAddFunc }, { "decimal_sub", 2, 0, decimalSubFunc }, diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c index c89fdbf136..4e85cc8aec 100644 --- a/ext/rtree/rtree.c +++ b/ext/rtree/rtree.c @@ -1919,7 +1919,7 @@ static int rtreeFilter( #else p->u.rValue = (double)iVal; if( iVal>=((sqlite3_int64)1)<<48 - || -iVal>=((sqlite3_int64)1)<<48 + || iVal<=-(((sqlite3_int64)1)<<48) ){ if( p->op==RTREE_LT ) p->op = RTREE_LE; if( p->op==RTREE_GT ) p->op = RTREE_GE; diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile index ed5550e7b8..080c427045 100644 --- a/ext/wasm/GNUmakefile +++ b/ext/wasm/GNUmakefile @@ -1046,8 +1046,7 @@ endif # Push files to public wasm-testing.sqlite.org server wasm-testing.include = *.js *.mjs *.html \ ./tests \ - batch-runner.list \ - $(dir.dout) $(dir.sql) $(dir.common) $(dir.fiddle) $(dir.jacc) + $(dir.dout) $(dir.common) $(dir.fiddle) $(dir.jacc) wasm-testing.exclude = sql/speedtest1.sql wasm-testing.dir = /jail/sites/wasm-testing wasm-testing.dest ?= wasm-testing:$(wasm-testing.dir) diff --git a/main.mk b/main.mk index 948047290d..89e20dde4a 100644 --- a/main.mk +++ b/main.mk @@ -942,6 +942,9 @@ testrunner: testfixture$(EXE) # devtest: testfixture$(EXE) fuzztest testrunner +mdevtest: + tclsh $(TOP)/test/testrunner.tcl mdevtest + # A very quick test using only testfixture and omitting all the slower # tests. Designed to run in under 3 minutes on a workstation. # diff --git a/manifest b/manifest index ef64aba6d6..529f68dbb3 100644 --- a/manifest +++ b/manifest @@ -1,21 +1,21 @@ -C Fix\sproblems\srelated\sto\sstructured-exception-handling\son\sthis\sbranch. -D 2023-08-21T18:25:41.874 +C Merge\slatest\schanges\sfrom\sthe\swal2\sbranch\sinto\sthis\sone. +D 2023-08-21T18:31:53.800 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 -F Makefile.in 1ac6badc8e625d87c76f64fad033ece76721c6d9341c685baeb17aea5f620acd +F Makefile.in 0a5501ba18ddbe0ef10c02bfc12931b55148a216f2b14af79b2b76d715c4c65b F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6 -F Makefile.msc 0b57ab2867b1fdc90c4e35a6777a3b24b81006cc7ba947c8db13929b4edd3800 -F README.md c1c4218efcc4071a6e26db2b517fdbc1035696a29b370edd655faddbef02b224 +F Makefile.msc 01eac287493d2ea7b0ced738dcf19a2234c60195d39e1c86f2b0fa20c9d7d14e +F README.md 093d7054271141a0a8518558e3d49087cb71f84d33b50ee10053946ed85dcac8 F VERSION c6366dc72582d3144ce87b013cc35fe48d62f6d07d5be0c9716ea33c862144aa F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 F art/sqlite370.jpg d512473dae7e378a67e28ff96a34da7cb331def2 F autoconf/INSTALL 83e4a25da9fd053c7b3665eaaaf7919707915903 -F autoconf/Makefile.am a8d1d24affe52ebf8d7ddcf91aa973fa0316618ab95bb68c87cabf8faf527dc8 +F autoconf/Makefile.am adedc1324b6a87fdd1265ddd336d2fb7d4f36a0e77b86ea553ae7cc4ea239347 F autoconf/Makefile.fallback 22fe523eb36dfce31e0f6349f782eb084e86a5620b2b0b4f84a2d6133f53f5ac -F autoconf/Makefile.msc 00f11ce1f7904416fe841c33e7d789defe8c39e1df6b97e93ed2af3b1bbaf9d7 +F autoconf/Makefile.msc 012cdd820963653a7db147a185ebe9085756b6ab15ac964e8cc1dae4c29485cd F autoconf/README.first 6c4f34fe115ff55d4e8dbfa3cecf04a0188292f7 F autoconf/README.txt 42cfd21d0b19dc7d5d85fb5c405c5f3c6a4c923021c39128f6ba685355d8fd56 F autoconf/configure.ac ec7fa914c5e74ff212fe879f9bb6918e1234497e05facfb641f30c4d5893b277 @@ -38,6 +38,7 @@ F configure.ac 4654d32ac0a0d0b48f1e1e79bdc3d777b723cf2f63c33eb1d7c4ed8b435938e8 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd F doc/begin_concurrent.md 4bee2c3990d1eb800f1ce3726a911292a8e4b889300b2ffd4b08d357370db299 +F doc/compile-for-windows.md e9d49959f44114a35dbec66f6aa5c3688ea9cb1b3f969a8537cd80b86d8969f7 F doc/json-enhancements.md e356fc834781f1f1aa22ee300027a270b2c960122468499bf347bb123ce1ea4f F doc/lemon.html d2862dbef72496e87f7996f37e814b146848190a742c12161d13fd15346051b0 F doc/pager-invariants.txt 27fed9a70ddad2088750c4a2b493b63853da2710 @@ -87,14 +88,14 @@ F ext/fts3/unicode/UnicodeData.txt cd07314edb62d49fde34debdaf92fa2aa69011e7 F ext/fts3/unicode/mkunicode.tcl d5aebf022fa4577ee8cdf27468f0d847879993959101f6dbd6348ef0cfc324a7 F ext/fts3/unicode/parseunicode.tcl a981bd6466d12dd17967515801c3ff23f74a281be1a03cf1e6f52a6959fc77eb F ext/fts5/extract_api_docs.tcl a36e54ec777172ddd3f9a88daf593b00848368e0 -F ext/fts5/fts5.h 9bebc9fb8b75b0777e741c758540e07c3c80ce75204198979028fe6cc4c59486 +F ext/fts5/fts5.h 05501612cc655504c5dce8ba765ab621d50fc478490089beaa0d75e00b23e520 F ext/fts5/fts5Int.h 78a63cc0795186cde5384816a9403a68c65774b35d952e05b81a1b4b158e07c8 F ext/fts5/fts5_aux.c 572d5ec92ba7301df2fea3258576332f2f4d2dfd66d8263afd157d9deceac480 F ext/fts5/fts5_buffer.c 3001fbabb585d6de52947b44b455235072b741038391f830d6b729225eeaf6a5 F ext/fts5/fts5_config.c 054359543566cbff1ba65a188330660a5457299513ac71c53b3a07d934c7b081 -F ext/fts5/fts5_expr.c 2473c13542f463cae4b938c498d6193c90d38ea1a2a4f9849c0479736e50d24d +F ext/fts5/fts5_expr.c bd3b81ce669c4104e34ffe66570af1999a317b142c15fccb112de9fb0caa57a6 F ext/fts5/fts5_hash.c 65e7707bc8774706574346d18c20218facf87de3599b995963c3e6d6809f203d -F ext/fts5/fts5_index.c 93b4cd116b76b6adf224cd3d213f1e06cfe10ae0eb21d6372b1c53b8f0c382a3 +F ext/fts5/fts5_index.c 7990b39f68010d6ee0d89a92784900a5fe582b90ca02fedc8a5d4b6b589498b8 F ext/fts5/fts5_main.c 4df36d3e7e641dd8af1244fdd9b1639d80844d4559531580586edb802691c353 F ext/fts5/fts5_storage.c 3c9b41fce41b6410f2e8f82eb035c6a29b2560483f773e6dc98cf3cb2e4ddbb5 F ext/fts5/fts5_tcl.c b1445cbe69908c411df8084a10b2485500ac70a9c747cdc8cda175a3da59d8ae @@ -153,7 +154,7 @@ F ext/fts5/test/fts5determin.test 1b77879b2ae818b5b71c859e534ee334dac088b7cf3ff3 F ext/fts5/test/fts5dlidx.test b90852c55881b29dbac6380b274de27beae623ac4b6d567c6c8fb9cdc315a86e F ext/fts5/test/fts5doclist.test faa9e9cc3c0645fa6203667cb5f007c359447c6ee66753f71a58175c2497cacd F ext/fts5/test/fts5ea.test b01e3a18cdfabbff8104a96a5242a06a68a998a0 -F ext/fts5/test/fts5eb.test a973baadac524dbbb4ad9b0e99030e12cabde2c6b28e0ac437298007b642cd12 +F ext/fts5/test/fts5eb.test 5f0a86e9fe4715912e6bfa556368aae96d13c61a481373f24daa40429d5d5ca1 F ext/fts5/test/fts5fault1.test d28a65caee75db6897c3cf1358c5230d3bb2a3bf7fb31062c19c7e5382b3d2bd F ext/fts5/test/fts5fault2.test 69c8fdbef830cd0d450908d4504d5bb86609e255af99c421c20a0756251fe344 F ext/fts5/test/fts5fault3.test da2f9e3e56ff5740d68ebdd6877c97089e7ed28ddff28a0da87a6afea27e5522 @@ -204,13 +205,13 @@ F ext/fts5/test/fts5secure2.test 2e961d7eef939f294c56b5d895cac7f1c3a60b934ee2cfd F ext/fts5/test/fts5secure3.test 12bc9ffa5dbd5a0951c6fe73bbf53b1c6507217589d7c8f5d4637a4fbb534401 F ext/fts5/test/fts5secure4.test 0d10a80590c07891478700af7793b232962042677432b9846cf7fc8337b67c97 F ext/fts5/test/fts5secure5.test c07a68ced5951567ac116c22f2d2aafae497e47fe9fcb6a335c22f9c7a4f2c3a -F ext/fts5/test/fts5secure6.test 7a959d834be6725c641b3c3b38ef86570ea671216ad803e054e4fdff33a72ce2 +F ext/fts5/test/fts5secure6.test a0a28cfb9bf9721408b65b5d7c7ce369af3d688e273da24d101c25d60cdce05c F ext/fts5/test/fts5securefault.test dbca2b6a1c16700017f5051138991b705410889933f2a37c57ae8a23b296b10b F ext/fts5/test/fts5simple.test a298670508c1458b88ce6030440f26a30673931884eb5f4094ac1773b3ba217b F ext/fts5/test/fts5simple2.test 258a1b0c590409bfa5271e872c79572b319d2a56554d0585f68f146a0da603f0 F ext/fts5/test/fts5simple3.test d5c74a9d3ca71bd5dd5cacb7c55b86ea12cdddfc8b1910e3de2995206898380f F ext/fts5/test/fts5synonym.test 1651815b8008de170e8e600dcacc17521d765482ea8f074ae82cfa870d8bb7fb -F ext/fts5/test/fts5synonym2.test b54cce5c34ec08ed616f646635538ae82e34a0e28f947ec60b6fadbc4b3fb17a +F ext/fts5/test/fts5synonym2.test 8f891fc49cc1e8daed727051e77e1f42849c784a6a54bef82564761b2cb3e016 F ext/fts5/test/fts5tok1.test 1f7817499f5971450d8c4a652114b3d833393c8134e32422d0af27884ffe9cef F ext/fts5/test/fts5tok2.test dcacb32d4a2a3f0dd3215d4a3987f78ae4be21a2 F ext/fts5/test/fts5tokenizer.test ac3c9112b263a639fb0508ae73a3ee886bf4866d2153771a8e8a20c721305a43 @@ -234,6 +235,46 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 +F ext/jni/GNUmakefile 3deba6bc0bf37c1ee5f15d1ff3c3512ae2f3cf44a2b8ae7b4af92690514b0cb4 +F ext/jni/README.md 5c60e4580aa5c94ff74d7bef1fb6231e578f7764e831a07b5981b6ab62b35560 +F ext/jni/jar-dist.make 93da95f8fe01ef22fccacc27f2e805938058e91e8c72c0532558d3a812a42e74 +F ext/jni/src/c/sqlite3-jni.c bea6b8691a5fa3a8626a771757bb261208d3c5fc6598266d3b0ee23d88e35632 +F ext/jni/src/c/sqlite3-jni.h 28565de9efc971195c684095ba0d184b90401290698c987f7ea3f54e47ff4f2f +F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 +F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 +F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c +F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 +F ext/jni/src/org/sqlite/jni/CollationNeeded.java ad67843b6dd1c06b6b0a1dc72887b7c48e2a98042fcf6cacf14d42444037eab8 +F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a +F ext/jni/src/org/sqlite/jni/Fts5.java 13844685231e8b4840a706db3bed84d5dfcf15be0ae7e809eac40420dba24901 +F ext/jni/src/org/sqlite/jni/Fts5Context.java 0a5a02047a6a1dd3e4a38b0e542a8dd2de365033ba30e6ae019a676305959890 +F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java 01f890105c6b7edbbad1c0f5635f783cea62c4b2ae694a71e76514a936ee03ec +F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7babcd11a0c308a832b7940574259bcc +F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 +F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 +F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee +F ext/jni/src/org/sqlite/jni/OutputPointer.java d81f8bd43d2296ae373692370cfad16ddde76f5c14cd2760f7b4e1113ef56d4c +F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc +F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7ce7797f2c6c7fca2004ff12ce20f86 +F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 +F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 4b6fd22e04e63eb65d8e4e38fda39ecf15ce244d034607517627ce2e766e7e65 +F ext/jni/src/org/sqlite/jni/Tester1.java 4253dc7bcff64500a9388f1a17d3d39dbe4eb9d7db9fc035ce6e2380d45ad5fc +F ext/jni/src/org/sqlite/jni/TesterFts5.java 59e22dd24af033ea8827d36225a2f3297908fb6af8818ead8850c6c6847557b1 +F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d +F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d +F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee +F ext/jni/src/org/sqlite/jni/fts5_api.java 5198be71c162e3e0cb1f4962a7cdf0d7596e8af53f70c4af6db24aab8d53d9ba +F ext/jni/src/org/sqlite/jni/fts5_extension_function.java ac825035d7d83fc7fd960347abfa6803e1614334a21533302041823ad5fc894c +F ext/jni/src/org/sqlite/jni/fts5_tokenizer.java e530b36e6437fcc500e95d5d75fbffe272bdea20d2fac6be2e1336c578fba98b +F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c388ec3bc1de547f098a0217158fc +F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 +F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc +F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1f1286428fab38dfefe328e72b5735f533b19af8dd17712dd3df7e044d21c8b8 +F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e +F ext/jni/src/tests/000-000-sanity.test cfe6dc1b950751d6096e3f5695becaadcdaa048bfe9567209d6eb676e693366d +F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 F ext/lsm1/lsm-test/README 87ea529d2abe615e856d4714bfe8bb185e6c2771b8612aa6298588b7b43e6f86 @@ -297,7 +338,7 @@ F ext/misc/completion.c 6dafd7f4348eecc7be9e920d4b419d1fb2af75d938cd9c59a20cfe8b F ext/misc/compress.c 3354c77a7c8e86e07d849916000cdac451ed96500bfb5bd83b20eb61eee012c9 F ext/misc/csv.c ca8d6dafc5469639de81937cb66ae2e6b358542aba94c4f791910d355a8e7f73 F ext/misc/dbdump.c b8592f6f2da292c62991a13864a60d6c573c47a9cc58362131b9e6a64f823e01 -F ext/misc/decimal.c a61343b36672760e1d6d5b20a42cb52264db55bcd11d0a44e2e06e8ce23227e3 +F ext/misc/decimal.c 172cf81a8634e6a0f0bedaf71a8372fee63348cf5a3c4e1b78bb233c35889fdc F ext/misc/eval.c 04bc9aada78c888394204b4ed996ab834b99726fb59603b0ee3ed6e049755dc1 F ext/misc/explain.c 0086fab288d4352ea638cf40ac382aad3b0dc5e845a1ea829a694c015fd970fe F ext/misc/fileio.c 4e7f7cd30de8df4820c552f14af3c9ca451c5ffe1f2e7bef34d598a12ebfb720 @@ -415,7 +456,7 @@ F ext/repair/test/checkindex01.test b530f141413b587c9eb78ff734de6bb79bc3515c3350 F ext/repair/test/test.tcl 686d76d888dffd021f64260abf29a55c57b2cedfa7fc69150b42b1d6119aac3c F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 F ext/rtree/geopoly.c 971e0b5bd9adaf0811feb8c0842a310811159da10319eb0e74fdb42bf26b99ca -F ext/rtree/rtree.c fb36e05027505f2c0dab24564e1d58ca4b789a6dfa48cf51aeee570018cf4814 +F ext/rtree/rtree.c 6954f4a3ca51c2e3db35c52e0513f3520999eb7a967f3d53b71db7ebddd8b3a5 F ext/rtree/rtree.h 4a690463901cb5e6127cf05eb8e642f127012fd5003830dbc974eca5802d9412 F ext/rtree/rtree1.test 877d40b8b61b1f88cec9d4dc0ff8334f5b05299fac12a35141532e2881860e9d F ext/rtree/rtree2.test 9d9deddbb16fd0c30c36e6b4fdc3ee3132d765567f0f9432ee71e1303d32603d @@ -496,7 +537,7 @@ F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb F ext/wasm/EXPORTED_FUNCTIONS.fiddle 7fb73f7150ab79d83bb45a67d257553c905c78cd3d693101699243f36c5ae6c3 -F ext/wasm/GNUmakefile 8159bc5f9433fe21022c1a8e8c30cb1a523530ba9ef53bdf5d1e0a2186554806 +F ext/wasm/GNUmakefile d02f3c8798b754f68b1f6b422ccff894a10bf352fc9c4eb8945baeace1acac28 F ext/wasm/README-dist.txt 6382cb9548076fca472fb3330bbdba3a55c1ea0b180ff9253f084f07ff383576 F ext/wasm/README.md a8a2962c3aebdf8d2104a9102e336c5554e78fc6072746e5daf9c61514e7d193 F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api c5eaceabb9e759aaae7d3101a4a3e542f96ab2c99d89a80ce20ec18c23115f33 @@ -571,7 +612,7 @@ F ext/wasm/wasmfs.make 8a4955882aaa0783b3f60a9484a1f0f3d8b6f775c0fcd17c082f31966 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0 -F main.mk 17ab4dd35f863772967540d8ebcb659691638e18c746b68ce8a8b3f545cb8ec3 +F main.mk 5fe45da4f1e5f705eb9c2dfc896bc8b0e212e4edee70fb989ae243118dd314a9 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 F mptest/crash01.test 61e61469e257df0850df4293d7d4d6c2af301421 @@ -580,7 +621,7 @@ F mptest/mptest.c aa41ace6dbc5050d76b02548d3521e6bbccae4f0 F mptest/multiwrite01.test dab5c5f8f9534971efce679152c5146da265222d F spec.template 86a4a43b99ebb3e75e6b9a735d5fd293a24e90ca F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b -F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786 +F sqlite3.1 acdff36db796e2d00225b911d3047d580cd136547298435426ce9d40347973cc F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a F sqlite_cfg.h.in baf2e409c63d4e7a765e17769b6ff17c5a82bbd9cbf1e284fd2e4cefaff3fcf2 F src/alter.c 3ff8c2fca0c0636d43459154bb40d79c882df1b34df77f89c4ec47ab2e2389f5 @@ -604,7 +645,7 @@ F src/delete.c cb766727c78e715f9fb7ec8a7d03658ed2a3016343ca687acfcec9083cdca500 F src/expr.c 1affe0cc049683ef0ef3545d9b6901508556b0ef7e2892a344c3df6d7288d79d F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fkey.c a7fcbf7e66d14dbb73cf49f31489ebf66d0e6006c62b95246924a3bae9f37b36 -F src/func.c cb04a0496022ed1b32f2701695632cae4a44c806684178a298f2efaf27160519 +F src/func.c 2c5b3d0c5f9998fa60ef2eda64fcb51d59a950dd0e695cdb5324943c4947c3d9 F src/global.c 29f56a330ed9d1b5cd9b79ac0ca36f97ac3afc730ff8bfa987b0db9e559d684d F src/hash.c 9ee4269fb1d6632a6fecfb9479c93a1f29271bddbbaf215dd60420bcb80c7220 F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 @@ -613,7 +654,7 @@ F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276 F src/json.c ae840f87b418f039f5d336b488933d09396bd31e6b31e855b93055ccaee4e255 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa -F src/loadext.c 176d6b2cb18a6ad73b133db17f6fc351c4d9a2d510deebdb76c22bde9cfd1465 +F src/loadext.c 98cfba10989b3da6f1807ad42444017742db7f100a54f1032af7a8b1295912c0 F src/main.c e56843b488ead74369eeffc64cd5b9d3a7ffbf8621bb0cd695272d9b9ce53afa F src/malloc.c 47b82c5daad557d9b963e3873e99c22570fb470719082c6658bf64e3012f7d23 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 @@ -636,10 +677,10 @@ F src/os_common.h 6c0eb8dd40ef3e12fe585a13e709710267a258e2c8dd1c40b1948a1d14582e F src/os_kv.c 4d39e1f1c180b11162c6dc4aa8ad34053873a639bac6baae23272fc03349986a F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d872107 F src/os_unix.c c8003dfe6502315cf930160b6cf1b93f82211bed5ab9226509faa6324030c8ea -F src/os_win.c 7038223a1cda0a47e2ab4db47f63bf1833fe53ba0542f0f283a062ea13894103 +F src/os_win.c 4a50a154aeebc66a1f8fb79c1ff6dd5fe3d005556533361e0d460d41cb6a45a8 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a F src/pager.c fda6c42c41a7eb58a62fd786cdef7a0e836234553ab8e59e03ea10b4dc3e26a1 -F src/pager.h 55870bc2e0cabd29bcbaf650d342d3b970e316f440886f388052e34623ad1e29 +F src/pager.h 314eb19f250d6ce31bdc32255a47141125f9b7c13cc202da2995eed5ef3c9825 F src/parse.y 92a9cc670816e1274a107d02ed8efec6028c23713c767f035479fde411c86f27 F src/pcache.c 4cd4a0043167da9ba7e19b4d179a0e6354e7fe32c16f781ecf9bf0a5ff63b40b F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5 @@ -652,17 +693,17 @@ F src/random.c 9bd018738ec450bf35d28050b4b33fa9a6eebf3aaefb1a1cff42dc14a7725673 F src/resolve.c 37953a5f36c60bea413c3c04efcd433b6177009f508ef2ace0494728912fe2e9 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c 996dda45d2a1a0228005849702348c7fd598437afa169c2c110f2c2ee582b382 -F src/shell.c.in 694aaf751f00610381533d4a31c83d142cfc83ef91ef65e2aa6912ace7c39b40 +F src/shell.c.in 2f9be25294b68b07e7e81f0adcec4475aba6011b64f160e414efe226910c4d7b F src/sqlite.h.in bc319c39cb72ef72c4f1d1d3b7662bce1fb8ba961913e0c145472f2d9fc11a3b F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 -F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4 +F src/sqlite3ext.h 2f30b2671f4c03cd27a43f039e11251391066c97d11385f5f963bb40b03038ac F src/sqliteInt.h d639aff687cfaa64f273a0f62eebb81bad875fdb9e8ff02d1a0235a8c1957e96 F src/sqliteLimit.h 33b1c9baba578d34efe7dfdb43193b366111cdf41476b1e82699e14c11ee1fb6 F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 F src/tclsqlite.c ecbc3c99c0d0c3ed122a913f143026c26d38d57f33e06bb71185dd5c1efe37cd F src/test1.c 91addc5c30c1389d257380b6e42ae0681d32d24553b9938d798a47ff96fa75ca -F src/test2.c 827446e259a3b7ab949da1542953edda7b5117982576d3e6f1c24a0dd20a5cef +F src/test2.c 54520d0565ef2b9bf0f8f1dcac43dc4d06baf4ffe13d10905f8d8c3ad3e4b9ab F src/test3.c e5178558c41ff53236ae0271e9acb3d6885a94981d2eb939536ee6474598840e F src/test4.c 4533b76419e7feb41b40582554663ed3cd77aaa54e135cf76b3205098cd6e664 F src/test5.c 328aae2c010c57a9829d255dc099d6899311672d @@ -716,15 +757,15 @@ F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c F src/tokenize.c 23d9f4539880b40226254ad9072f4ecf12eb1902e62aea47aac29928afafcfd5 F src/treeview.c 1d52fbc4e97161e65858d36e3424ea6e3fc045dd8a679c82b4b9593dc30de3bd F src/trigger.c ad6ab9452715fa9a8075442e15196022275b414b9141b566af8cdb7a1605f2b0 -F src/update.c eafa1d6e32de4749986cbebc32ffb094c4401318bb713e5d2a18fe3e5a93ae8d -F src/upsert.c 5303dc6c518fa7d4b280ec65170f465c7a70b7ac2b22491598f6d0b4875b3145 +F src/update.c 0f8df185d63e1d3a777ae889880be1f9227bb37bb40d3e461555a6925ac298f8 +F src/upsert.c fa125a8d3410ce9a97b02cb50f7ae68a2476c405c76aa692d3acf6b8586e9242 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 -F src/util.c a40062117e705eb3339201842717a022092816b92479eead6397cde28af32ff9 +F src/util.c 278b81c3b33db1b5a5f3859adf8905c165b910080043061d44d3c5a25b4b406d F src/vacuum.c b1dd6d73869229b6e08bac910ac011dc9da42e3120ec2b7241accc5a752bd419 F src/vdbe.c 3ef180bab789aa99aec753fd26ecb8f3d56c9f6870997cafd8c5fc2b7c989a43 F src/vdbe.h 41485521f68e9437fdb7ec4a90f9d86ab294e9bb8281e33b235915e29122cfc0 F src/vdbeInt.h 949669dfd8a41550d27dcb905b494f2ccde9a2e6c1b0b04daa1227e2e74c2b2c -F src/vdbeapi.c f37822f215740ede2a8fcae99bc13f2cc3a72dd0e1d22b81b9298c5ca67dbc38 +F src/vdbeapi.c 37341acd781fda162e8cf4d9fc2eaea2febad3b365877a9d7233b8c6d0960d85 F src/vdbeaux.c 2c87c99975ac23e777e9c270d979eca38f2a021b1f14f061c83faab5b24d5576 F src/vdbeblob.c 2516697b3ee8154eb8915f29466fb5d4f1ae39ee8b755ea909cefaf57ec5e2ce F src/vdbemem.c 317b9f48708139db6239ade40c7980b4bc8233168383690d588dad6d8437f722 @@ -733,8 +774,8 @@ F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf8 F src/vdbevtab.c 57fa8f56478e5b5cb558cb425e7878515e0a105c54f96f1d1bbf4b9433529254 F src/vtab.c 1ecf8c3745d29275688d583e12822fa984d421e0286b5ef50c137bc3bf6d7a64 F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 -F src/wal.c 56eedfe60c278d0ee53f8a91b513ab40020773a1e83e29ec7ccc6a000ad5bd28 -F src/wal.h dcb0533caecf286be1c87b683e1282f3ca29fa5c00eb798e9226ce377a687cf4 +F src/wal.c ba4ea857cac27bb2fa9eb4bfecec4b552baca0fdbe527aac324e106de5201cc6 +F src/wal.h 8d02ab8c2a93a941f5898eb3345bf711c1d3f8f86f4be8d5428fb6c074962d8a F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2 F src/where.c b8917792f1e0dbfa28fb29e6cd3d560060d69667be0ba4c491cbc772363264f5 F src/whereInt.h c7d19902863beadec1d04e66aca39c0bcd60b74f05f0eaa7422c7005dfc5d51a @@ -966,7 +1007,7 @@ F test/dbpage.test fce29035c7566fd7835ec0f19422cb4b9c6944ce0e1b936ff8452443f92e8 F test/dbpagefault.test d9111a62f3601d3efc6841ace3940181937342d245f92a1cca6cba8206d4f58a F test/dbstatus.test 4a4221a883025ffd39696b3d1b3910b928fb097d77e671351acb35f3aed42759 F test/dbstatus2.test f5fe0afed3fa45e57cfa70d1147606c20d2ba23feac78e9a172f2fe8ab5b78ef -F test/decimal.test 18e7b4cb12e8d5c60d768b686ba52af3e1ca3ced4f870231f0476666fd9fab7e +F test/decimal.test ef731887b43ee32ef86e1c8fddb61a40789f988332c029c601dcf2c319277e9e F test/default.test 9687cfb16717e4b8238c191697c98be88c0b16e568dd5368cd9284154097ef50 F test/delete.test 2686e1c98d552ef37d79ad55b17b93fe96fad9737786917ce3839767f734c48f F test/delete2.test 3a03f2cca1f9a67ec469915cb8babd6485db43fa @@ -1151,7 +1192,7 @@ F test/fuzz3.test 9c813e6613b837cb7a277b0383cd66bfa07042b4cf0317157c35852f30043c F test/fuzz4.test c229bcdb45518a89e1d208a21343e061503460ac69fae1539320a89f572eb634 F test/fuzz_common.tcl b7197de6ed1ee8250a4f82d67876f4561b42ee8cbbfc6160dcb66331bad3f830 F test/fuzz_malloc.test f348276e732e814802e39f042b1f6da6362a610af73a528d8f76898fde6b22f2 -F test/fuzzcheck.c 29a2f0237553375498f891c9487a2ef3267b47deecc5d5b4335fa37f904cb8d3 +F test/fuzzcheck.c 69b8549e112fb815931a8c14c7955a0c407ae91a79356eecb82458384f2cb989 F test/fuzzdata1.db 3e86d9cf5aea68ddb8e27c02d7dfdaa226347426c7eb814918e4d95475bf8517 F test/fuzzdata2.db 128b3feeb78918d075c9b14b48610145a0dd4c8d6f1ca7c2870c7e425f5bf31f F test/fuzzdata3.db c6586d3e3cef0fbc18108f9bb649aa77bfc38aba @@ -1253,7 +1294,7 @@ F test/json/README.md 63e3e589e1df8fd3cc1588ba1faaff659214003f8b77a15af5c6452b35 F test/json/json-generator.tcl dc0dd0f393800c98658fc4c47eaa6af29d4e17527380cd28656fb261bddc8a3f F test/json/json-q1.txt 65f9d1cdcc4cffa9823fb73ed936aae5658700cd001fde448f68bfb91c807307 F test/json/json-speed-check.sh 8b7babf530faa58bd59d6d362cec8e9036a68c5457ff46f3b1f1511d21af6737 x -F test/json101.test 243b0a2650218ac5eafde6ce2a92a0e9d02bf24f62aec68693b69d9a693f120a +F test/json101.test dc9d5a2a5b1fd1b54dbd71c538b17933cc98d84b4c1f821ead754933663dca55 F test/json102.test 24f6f204f9cde45b971016691d0b92a9b4c58040d699e36d6b12cb165f9083ff F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a8880112dbe F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1 @@ -1265,7 +1306,7 @@ F test/kvtest.c 6e0228409ea7ca0497dad503fbd109badb5e59545d131014b6aaac68b56f484a F test/lastinsert.test 42e948fd6442f07d60acbd15d33fb86473e0ef63 F test/laststmtchanges.test ae613f53819206b3222771828d024154d51db200 F test/lemon-test01.y 58b764610fd934e189ffbb0bbfa33d171b9cb06019b55bdc04d090d6767e11d7 -F test/like.test 5fe0bc37f307aef0a453ce2de4632bdfc0759448f0421c39f6d53caefe905fac +F test/like.test 242ee7f5d08a031144c0daf63bbd7e7710c847ccf387a83347e0b61b3aa69526 F test/like2.test d3be15fefee3e02fc88942a9b98f26c5339bbdef7783c90023c092c4955fe3d3 F test/like3.test a76e5938fadbe6d32807284c796bafd869974a961057bc5fc5a28e06de98745c F test/limit.test 350f5d03c29e7dff9a2cde016f84f8d368d40bcd02fa2b2a52fa10c4bf3cbfaf @@ -1390,7 +1431,7 @@ F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442 F test/pendingrace.test cbdf0f74bc939fb43cebad64dda7a0b5a3941a10b7e9cc2b596ff3e423a18156 F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff -F test/permutations.test afbe9e54e88b93bb9b9b55d078d6c826de33a48b784258ba74d9549d2eb000a1 +F test/permutations.test f46a1b5a8f1c81b7e3054f59a8354d8d7af3ca1827a347d4a9865440fc8c47b9 F test/pg_common.tcl 3b27542224db1e713ae387459b5d117c836a5f6e328846922993b6d2b7640d9f F test/pragma.test 57a36226218c03cfb381019fe43234b2cefbd8a1f12825514f906a17ccf7991e F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f @@ -1419,7 +1460,7 @@ F test/recover.test fd5199f928757cb308661b5fdca1abc19398a798ff7f24b57c3071e9f8e0 F test/regexp1.test 8f2a8bc1569666e29a4cee6c1a666cd224eb6d50e2470d1dc1df995170f3e0f1 F test/regexp2.test 55ed41da802b0e284ac7e2fe944be3948f93ff25abbca0361a609acfed1368b5 F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2118d -F test/releasetest_data.tcl c8cf85aeb313a771e18dae01396f5ca70e85d4574df681990629b1103f185afe +F test/releasetest_data.tcl 80ef3941bf7ad136f4dc3d8960786f10a4f488797238171bdd757cc6eb4c3efa F test/resetdb.test 54c06f18bc832ac6d6319e5ab23d5c8dd49fdbeec7c696d791682a8006bd5fc3 F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb F test/returning1.test db532cde29d6aebbc48c6ddc3149b30476f8e69ca7a2c4b53986c7635e6fd8ec @@ -1523,7 +1564,7 @@ F test/snapshot_up.test a0a29c4cf33475fcef07c3f8e64af795e24ab91b4cc68295863402a3 F test/soak.test 18944cf21b94a7fe0df02016a6ee1e9632bc4e8d095a0cb49d95e15d5cca2d5c F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087 F test/sort.test f86751134159abb5e5fd4381a0d7038c91013638cd1e3fa1d7850901f6df6196 -F test/sort2.test cc23b7c19d684657559e8a55b02f7fcee03851d0 +F test/sort2.test 2f8c66402a03adebe77ce7aafca129fbf32df27d6c9b8f7a9f1b958e39f56da8 F test/sort3.test 1480ed7c4c157682542224e05e3b75faf4a149e5 F test/sort4.test cca6f4b0b5255882645bbbe346a6a9f4a5c7b6a18513a6a7bf4ac1c4761ddc19 F test/sort5.test 6b43ae0e2169b5ceed441844492e55ba7f1ae0790528395ddf7888ab3094525d @@ -1582,8 +1623,8 @@ F test/temptable2.test 76821347810ecc88203e6ef0dd6897b6036ac788e9dd3e6b04fd4d163 F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637 F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc F test/tester.tcl e6b9adcf4e56aa5c658501c10650646fee467b85d51a4ac80263d2cdae3993ba -F test/testrunner.tcl 59490f189cac99b16b0376d0cc0a7ecfb753a84b89c9f4c361af337d88db53ac -F test/testrunner_data.tcl 8169c68654ac8906833b8a6aadca973358a441ebf88270dd05c153e5f96f76b8 +F test/testrunner.tcl 56a744d4e6e516b2091c2ca6b7b27b9600e9ded136a2c860c350515511ebe20a +F test/testrunner_data.tcl 8afa4eeec7f7421db3d3af64cdd5544015e887c4289cf871a730cca5a7a8d934 F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899 F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -1800,7 +1841,7 @@ F test/upfrom2.test 66f3ebf721b3cebd922faee5c386bf244f816d416b57c000753ff51af623 F test/upfrom3.test 6130f24ebf97f5ea865e5d2a14a2d543fe5428a62e87cc60f62d875e45c1f5f0 F test/upfrom4.test 78f742a6577c91a7a55c64edb8811004e7c6aa99b8d57b2320f70a918c357807 F test/upfromfault.test 3a10075a0043f0c4fad6614b2c371f88a8ba5a4acab68b907438413865d6a8d6 -F test/upsert1.test b0ae2f58680c5205b4bc1cdeed3c3d444057c506f6c44494fa3eac60731d68a2 +F test/upsert1.test a512e2f884d3a36159fce2e45108c236f78ae38e35bda55f4050db580ceb25d3 F test/upsert2.test 720e94d09f7362a282bc69b3c6b83d51daeaaf0440eb4920a08b86518b8c7496 F test/upsert3.test 88d7d590a1948a9cb6eac1b54b0642f67a9f35a1fc0f19b200e97d5d39e3179c F test/upsert4.test 25d2a1da92f149331ae0c51ca6e3eee78189577585eab92de149900d62994fa5 @@ -2052,7 +2093,7 @@ F tool/sqldiff.c 2a693b4e7c1818c23f871f82f0c3fe67d80b67e3f087893089d33da29c1e387 F tool/sqlite3_analyzer.c.in f88615bf33098945e0a42f17733f472083d150b58bdaaa5555a7129d0a51621c F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaaef898 F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848 -F tool/src-verify.c f0cef434a8d8629c4928a02a644fb85e33c8b875a7f2352ba68cc50965a7d213 +F tool/src-verify.c 41c586dee84d0b190ad13e0282ed83d4a65ec9fefde9adf4943efdf6558eea7f F tool/srcck1.c 371de5363b70154012955544f86fdee8f6e5326f F tool/stack_usage.tcl f8e71b92cdb099a147dad572375595eae55eca43 F tool/stripccomments.c 20b8aabc4694d0d4af5566e42da1f1a03aff057689370326e9269a9ddcffdc37 @@ -2088,8 +2129,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 331f2f3e5db9b6139be984f1b959cd0d51563adaa68452aa2f42741c69bc6319 -R 8cf5124efb077972dc4592e59c017049 +P d3d77e35ea39df9e22032a7e1af5b7f38d53a4d43bc46afe36e342cdcfd17528 135bf72c6b5f436b11463dd40b3a73bcc610bf9c2dcbe6646dbedd0d9325f6a9 +R b7d659f9bcf8ba358058d0baa617e913 U dan -Z 86179e20e1202dcde51614a70e1512c5 +Z 380a55b5d6a3d9fc5af4f4bcbddcaf42 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index acba96d09a..f60660f49d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d3d77e35ea39df9e22032a7e1af5b7f38d53a4d43bc46afe36e342cdcfd17528 \ No newline at end of file +4d8df0c426b8ce3db6cfb71e23f752026ef886b9bb833dc4be9717db9955b1db \ No newline at end of file diff --git a/sqlite3.1 b/sqlite3.1 index 80353b0eec..08b1ff262b 100644 --- a/sqlite3.1 +++ b/sqlite3.1 @@ -2,7 +2,7 @@ .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) -.TH SQLITE3 1 "Fri Oct 31 10:41:31 EDT 2014" +.TH SQLITE3 1 "Fri Aug 11 23:50:12 CET 2023" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: @@ -49,9 +49,9 @@ a table named "memos" and insert a couple of records into that table: $ .B sqlite3 mydata.db .br -SQLite version 3.8.8 +SQLite version 3.43.0 2023-08-11 17:45:23 .br -Enter ".help" for instructions +Enter ".help" for usage hints. .br sqlite> .B create table memos(text, priority INTEGER); @@ -108,141 +108,13 @@ sqlite> .B .help .nf .tr %. -%backup ?DB? FILE Backup DB (default "main") to FILE -%bail on|off Stop after hitting an error. Default OFF -%clone NEWDB Clone data into NEWDB from the existing database -%databases List names and files of attached databases -%dump ?TABLE? ... Dump the database in an SQL text format - If TABLE specified, only dump tables matching - LIKE pattern TABLE. -%echo on|off Turn command echo on or off -%eqp on|off Enable or disable automatic EXPLAIN QUERY PLAN -%exit Exit this program -%explain ?on|off? Turn output mode suitable for EXPLAIN on or off. - With no args, it turns EXPLAIN on. -%fullschema Show schema and the content of sqlite_stat tables -%headers on|off Turn display of headers on or off -%help Show this message -%import FILE TABLE Import data from FILE into TABLE -%indices ?TABLE? Show names of all indices - If TABLE specified, only show indices for tables - matching LIKE pattern TABLE. -%load FILE ?ENTRY? Load an extension library -%log FILE|off Turn logging on or off. FILE can be stderr/stdout -%mode MODE ?TABLE? Set output mode where MODE is one of: - csv Comma-separated values - column Left-aligned columns. (See .width) - html HTML code - insert SQL insert statements for TABLE - line One value per line - list Values delimited by .separator string - tabs Tab-separated values - tcl TCL list elements -%nullvalue STRING Use STRING in place of NULL values -%once FILENAME Output for the next SQL command only to FILENAME -%open ?FILENAME? Close existing database and reopen FILENAME -%output ?FILENAME? Send output to FILENAME or stdout -%print STRING... Print literal STRING -%prompt MAIN CONTINUE Replace the standard prompts -%quit Exit this program -%read FILENAME Execute SQL in FILENAME -%restore ?DB? FILE Restore content of DB (default "main") from FILE -%save FILE Write in-memory database into FILE -%schema ?TABLE? Show the CREATE statements - If TABLE specified, only show tables matching - LIKE pattern TABLE. -%separator STRING ?NL? Change separator used by output mode and .import - NL is the end-of-line mark for CSV -%shell CMD ARGS... Run CMD ARGS... in a system shell -%show Show the current values for various settings -%stats on|off Turn stats on or off -%system CMD ARGS... Run CMD ARGS... in a system shell -%tables ?TABLE? List names of tables - If TABLE specified, only list tables matching - LIKE pattern TABLE. -%timeout MS Try opening locked tables for MS milliseconds -%timer on|off Turn SQL timer on or off -%trace FILE|off Output each SQL statement as it is run -%vfsname ?AUX? Print the name of the VFS stack -%width NUM1 NUM2 ... Set column widths for "column" mode - Negative values right-justify -sqlite> +... .sp .fi -.SH OPTIONS -.B sqlite3 -has the following options: -.TP -.B \-bail -Stop after hitting an error. -.TP -.B \-batch -Force batch I/O. -.TP -.B \-column -Query results will be displayed in a table like form, using -whitespace characters to separate the columns and align the -output. -.TP -.BI \-cmd\ command -run -.I command -before reading stdin -.TP -.B \-csv -Set output mode to CSV (comma separated values). -.TP -.B \-echo -Print commands before execution. -.TP -.BI \-init\ file -Read and execute commands from -.I file -, which can contain a mix of SQL statements and meta-commands. -.TP -.B \-[no]header -Turn headers on or off. -.TP -.B \-help -Show help on options and exit. -.TP -.B \-html -Query results will be output as simple HTML tables. -.TP -.B \-interactive -Force interactive I/O. -.TP -.B \-line -Query results will be displayed with one value per line, rows -separated by a blank line. Designed to be easily parsed by -scripts or other programs -.TP -.B \-list -Query results will be displayed with the separator (|, by default) -character between each field value. The default. -.TP -.BI \-mmap\ N -Set default mmap size to -.I N -\. -.TP -.BI \-nullvalue\ string -Set string used to represent NULL values. Default is '' -(empty string). -.TP -.BI \-separator\ separator -Set output field separator. Default is '|'. -.TP -.B \-stats -Print memory stats before each finalize. -.TP -.B \-version -Show SQLite version. -.TP -.BI \-vfs\ name -Use -.I name -as the default VFS. + +The available commands differ by version and build options, so they +are not listed here. Please refer to your local copy for all available +options. .SH INIT FILE @@ -265,22 +137,25 @@ continue prompt = " ...> " .sp .fi -o If the file +o If the file +.B ${XDG_CONFIG_HOME}/sqlite3/sqliterc +or .B ~/.sqliterc -exists, it is processed first. -can be found in the user's home directory, it is -read and processed. It should generally only contain meta-commands. +exists, the first of those to be found is processed during startup. +It should generally only contain meta-commands. o If the -init option is present, the specified file is processed. o All other command line options are processed. .SH SEE ALSO -http://www.sqlite.org/cli.html +https://sqlite.org/cli.html +.br +https://sqlite.org/fiddle (a WebAssembly build of the CLI app) .br The sqlite3-doc package. .SH AUTHOR This manual page was originally written by Andreas Rottmann , for the Debian GNU/Linux system (but may be used -by others). It was subsequently revised by Bill Bumgarner and -further updated by Laszlo Boszormenyi . +by others). It was subsequently revised by Bill Bumgarner , +Laszlo Boszormenyi , and the sqlite3 developers. diff --git a/src/func.c b/src/func.c index b8b36fdedb..6d8f147d7a 100644 --- a/src/func.c +++ b/src/func.c @@ -2156,8 +2156,10 @@ void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3 *db){ ** sensitive. */ void sqlite3RegisterLikeFunctions(sqlite3 *db, int caseSensitive){ + FuncDef *pDef; struct compareInfo *pInfo; int flags; + int nArg; if( caseSensitive ){ pInfo = (struct compareInfo*)&likeInfoAlt; flags = SQLITE_FUNC_LIKE | SQLITE_FUNC_CASE; @@ -2165,10 +2167,13 @@ void sqlite3RegisterLikeFunctions(sqlite3 *db, int caseSensitive){ pInfo = (struct compareInfo*)&likeInfoNorm; flags = SQLITE_FUNC_LIKE; } - sqlite3CreateFunc(db, "like", 2, SQLITE_UTF8, pInfo, likeFunc, 0, 0, 0, 0, 0); - sqlite3CreateFunc(db, "like", 3, SQLITE_UTF8, pInfo, likeFunc, 0, 0, 0, 0, 0); - sqlite3FindFunction(db, "like", 2, SQLITE_UTF8, 0)->funcFlags |= flags; - sqlite3FindFunction(db, "like", 3, SQLITE_UTF8, 0)->funcFlags |= flags; + for(nArg=2; nArg<=3; nArg++){ + sqlite3CreateFunc(db, "like", nArg, SQLITE_UTF8, pInfo, likeFunc, + 0, 0, 0, 0, 0); + pDef = sqlite3FindFunction(db, "like", nArg, SQLITE_UTF8, 0); + pDef->funcFlags |= flags; + pDef->funcFlags &= ~SQLITE_FUNC_UNSAFE; + } } /* diff --git a/src/loadext.c b/src/loadext.c index 4fc1352e03..e792fa5a98 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -512,7 +512,9 @@ static const sqlite3_api_routines sqlite3Apis = { /* Version 3.40.0 and later */ sqlite3_value_encoding, /* Version 3.41.0 and later */ - sqlite3_is_interrupted + sqlite3_is_interrupted, + /* Version 3.43.0 and later */ + sqlite3_stmt_explain }; /* True if x is the directory separator character diff --git a/src/os_win.c b/src/os_win.c index 73a2f946d8..dc16c08b51 100644 --- a/src/os_win.c +++ b/src/os_win.c @@ -4747,6 +4747,7 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789"; size_t i, j; + DWORD pid; int nPre = sqlite3Strlen30(SQLITE_TEMP_FILE_PREFIX); int nMax, nBuf, nDir, nLen; char *zBuf; @@ -4959,7 +4960,10 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ j = sqlite3Strlen30(zBuf); sqlite3_randomness(15, &zBuf[j]); + pid = osGetCurrentProcessId(); for(i=0; i<15; i++, j++){ + zBuf[j] += pid & 0xff; + pid >>= 8; zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ]; } zBuf[j] = 0; diff --git a/src/pager.h b/src/pager.h index 6634a2814b..0073eed2d3 100644 --- a/src/pager.h +++ b/src/pager.h @@ -274,7 +274,7 @@ int sqlite3PagerWalInfo(Pager*, u32 *pnPrior, u32 *pnFrame); # define enable_simulated_io_errors() #endif -#ifdef SQLITE_USE_SEH +#if defined(SQLITE_USE_SEH) && !defined(SQLITE_OMIT_WAL) int sqlite3PagerWalSystemErrno(Pager*); #endif diff --git a/src/shell.c.in b/src/shell.c.in index 1e65d0e9fd..07d92d0142 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -4799,7 +4799,6 @@ static const char *(azHelp[]) = { " --async Write to FILE without journal and fsync()", #endif ".bail on|off Stop after hitting an error. Default OFF", - ".binary on|off Turn binary output on or off. Default OFF", #ifndef SQLITE_SHELL_FIDDLE ".cd DIRECTORY Change the working directory to DIRECTORY", #endif @@ -4809,6 +4808,9 @@ static const char *(azHelp[]) = { ".clone NEWDB Clone data into NEWDB from the existing database", #endif ".connection [close] [#] Open or close an auxiliary database connection", +#if defined(_WIN32) || defined(WIN32) + ".crnl on|off Translate \\n to \\r\\n. Default ON", +#endif ".databases List names and files of attached databases", ".dbconfig ?op? ?val? List or change sqlite3_db_config() options", #if SQLITE_SHELL_HAVE_RECOVER @@ -8164,6 +8166,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else + /* Undocumented. Legacy only. See "crnl" below */ if( c=='b' && n>=3 && cli_strncmp(azArg[0], "binary", n)==0 ){ if( nArg==2 ){ if( booleanValue(azArg[1]) ){ @@ -8172,6 +8175,8 @@ static int do_meta_command(char *zLine, ShellState *p){ setTextMode(p->out, 1); } }else{ + raw_printf(stderr, "The \".binary\" command is deprecated." + " Use \".crnl\" instead.\n"); raw_printf(stderr, "Usage: .binary on|off\n"); rc = 1; } @@ -8299,6 +8304,22 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else + if( c=='c' && n==4 && cli_strncmp(azArg[0], "crnl", n)==0 ){ + if( nArg==2 ){ + if( booleanValue(azArg[1]) ){ + setTextMode(p->out, 1); + }else{ + setBinaryMode(p->out, 1); + } + }else{ +#if !defined(_WIN32) && !defined(WIN32) + raw_printf(stderr, "The \".crnl\" is a no-op on non-Windows machines.\n"); +#endif + raw_printf(stderr, "Usage: .crnl on|off\n"); + rc = 1; + } + }else + if( c=='d' && n>1 && cli_strncmp(azArg[0], "databases", n)==0 ){ char **azName = 0; int nName = 0; @@ -11283,6 +11304,7 @@ static int do_meta_command(char *zLine, ShellState *p){ #endif /* SQLITE_USER_AUTHENTICATION */ if( c=='v' && cli_strncmp(azArg[0], "version", n)==0 ){ + char *zPtrSz = sizeof(void*)==8 ? "64-bit" : "32-bit"; utf8_printf(p->out, "SQLite %s %s\n" /*extra-version-info*/, sqlite3_libversion(), sqlite3_sourceid()); #if SQLITE_HAVE_ZLIB @@ -11293,11 +11315,11 @@ static int do_meta_command(char *zLine, ShellState *p){ #if defined(__clang__) && defined(__clang_major__) utf8_printf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "." CTIMEOPT_VAL(__clang_minor__) "." - CTIMEOPT_VAL(__clang_patchlevel__) "\n"); + CTIMEOPT_VAL(__clang_patchlevel__) " (%s)\n", zPtrSz); #elif defined(_MSC_VER) - utf8_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) "\n"); + utf8_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) " (%s)\n", zPtrSz); #elif defined(__GNUC__) && defined(__VERSION__) - utf8_printf(p->out, "gcc-" __VERSION__ "\n"); + utf8_printf(p->out, "gcc-" __VERSION__ " (%s)\n", zPtrSz); #endif }else @@ -12471,7 +12493,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ }else if( cli_strcmp(z,"-bail")==0 ){ /* No-op. The bail_on_error flag should already be set. */ }else if( cli_strcmp(z,"-version")==0 ){ - printf("%s %s\n", sqlite3_libversion(), sqlite3_sourceid()); + printf("%s %s (%d-bit)\n", sqlite3_libversion(), sqlite3_sourceid(), + 8*(int)sizeof(char*)); return 0; }else if( cli_strcmp(z,"-interactive")==0 ){ stdin_is_interactive = 1; diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h index 19e030028a..7116380992 100644 --- a/src/sqlite3ext.h +++ b/src/sqlite3ext.h @@ -361,6 +361,8 @@ struct sqlite3_api_routines { int (*value_encoding)(sqlite3_value*); /* Version 3.41.0 and later */ int (*is_interrupted)(sqlite3*); + /* Version 3.43.0 and later */ + int (*stmt_explain)(sqlite3_stmt*,int); }; /* @@ -689,6 +691,8 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_value_encoding sqlite3_api->value_encoding /* Version 3.41.0 and later */ #define sqlite3_is_interrupted sqlite3_api->is_interrupted +/* Version 3.43.0 and later */ +#define sqlite3_stmt_explain sqlite3_api->stmt_explain #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) diff --git a/src/test2.c b/src/test2.c index d5db3867b8..c75fa2ebab 100644 --- a/src/test2.c +++ b/src/test2.c @@ -608,7 +608,7 @@ static int faultSimCallback(int x){ zInt[i] = (x%10) + '0'; } if( isNeg ) zInt[i--] = '-'; - memcpy(faultSimScript+faultSimScriptSize, zInt+i+1, sizeof(zInt)-i); + memcpy(faultSimScript+faultSimScriptSize, zInt+i+1, sizeof(zInt)-i-1); } rc = Tcl_Eval(faultSimInterp, faultSimScript); if( rc ){ diff --git a/src/update.c b/src/update.c index 038c6bce6d..838dc9ed67 100644 --- a/src/update.c +++ b/src/update.c @@ -1270,9 +1270,9 @@ static void updateVirtualTable( sqlite3ExprDup(db, pChanges->a[aXRef[i]].pExpr, 0) ); }else{ - Expr *pRow = exprRowColumn(pParse, i); - if( pRow ) pRow->op2 = OPFLAG_NOCHNG; - pList = sqlite3ExprListAppend(pParse, pList, pRow); + Expr *pRowExpr = exprRowColumn(pParse, i); + if( pRowExpr ) pRowExpr->op2 = OPFLAG_NOCHNG; + pList = sqlite3ExprListAppend(pParse, pList, pRowExpr); } } diff --git a/src/upsert.c b/src/upsert.c index 85994020cf..be0d0550df 100644 --- a/src/upsert.c +++ b/src/upsert.c @@ -178,7 +178,7 @@ int sqlite3UpsertAnalyzeTarget( pExpr = &sCol[0]; } for(jj=0; jja[jj].pExpr,pExpr,iCursor)<2 ){ + if( sqlite3ExprCompare(0,pTarget->a[jj].pExpr,pExpr,iCursor)<2 ){ break; /* Column ii of the index matches column jj of target */ } } diff --git a/src/util.c b/src/util.c index e417034754..97deb64cfd 100644 --- a/src/util.c +++ b/src/util.c @@ -610,7 +610,11 @@ do_atof_calc: } assert( r>=0.0 ); if( r>+1.7976931348623157081452742373e+308L ){ +#ifdef INFINITY *pResult = +INFINITY; +#else + *pResult = 1.0e308*10.0; +#endif }else{ *pResult = (double)r; } @@ -1016,7 +1020,7 @@ void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){ ** The error terms on constants like 1.0e+100 computed using the ** decimal extension, for example as follows: ** - ** SELECT decimal_sci(decimal_sub('1.0e+100',decimal(1.0e+100))); + ** SELECT decimal_exp(decimal_sub('1.0e+100',decimal(1.0e+100))); */ double rr[2]; rr[0] = r; diff --git a/src/vdbeapi.c b/src/vdbeapi.c index cc58e7a877..79b5de9f0e 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -1864,7 +1864,7 @@ int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode){ Vdbe *v = (Vdbe*)pStmt; int rc; sqlite3_mutex_enter(v->db->mutex); - if( v->explain==eMode ){ + if( ((int)v->explain)==eMode ){ rc = SQLITE_OK; }else if( eMode<0 || eMode>2 ){ rc = SQLITE_ERROR; diff --git a/src/wal.c b/src/wal.c index 62cd16d350..85c37a9361 100644 --- a/src/wal.c +++ b/src/wal.c @@ -795,6 +795,8 @@ struct Wal { #ifdef SQLITE_USE_SEH u32 lockMask; /* Mask of locks held */ void *pFree; /* Pointer to sqlite3_free() if exception thrown */ + u32 *pWiValue; /* Value to write into apWiData[iWiPg] */ + int iWiPg; /* Write pWiValue into apWiData[iWiPg] */ int iSysErrno; /* System error code following exception */ #endif #ifdef SQLITE_DEBUG @@ -969,11 +971,24 @@ static void sehInjectFault(Wal *pWal){ #define SEH_FREE_ON_ERROR(X,Y) \ assert( (X==0 || Y==0) && pWal->pFree==X ); pWal->pFree = Y +/* +** There are two ways to use this macro. To arrange for pWal->apWiData[iPg] +** to be set to pValue if an exception is thrown: +** +** SEH_SET_ON_ERROR(iPg, pValue); +** +** and to cancel the same: +** +** SEH_SET_ON_ERROR(0, 0); +*/ +#define SEH_SET_ON_ERROR(X,Y) pWal->iWiPg = X; pWal->pWiValue = Y + #else # define SEH_TRY VVA_ONLY(pWal->nSehTry++); # define SEH_EXCEPT(X) VVA_ONLY(pWal->nSehTry--); assert( pWal->nSehTry==0 ); # define SEH_INJECT_FAULT assert( pWal->nSehTry>0 ); # define SEH_FREE_ON_ERROR(X,Y) +# define SEH_SET_ON_ERROR(X,Y) #endif /* ifdef SQLITE_USE_SEH */ /* @@ -1782,6 +1797,7 @@ static int walIndexRecoverOne(Wal *pWal, int iWal, u32 *pnCkpt, int *pbZero){ rc = walIndexPage(pWal, iPg, (volatile u32**)&aShare); assert( aShare!=0 || rc!=SQLITE_OK ); if( aShare==0 ) break; + SEH_SET_ON_ERROR(iPg, aShare); pWal->apWiData[iPg] = aPrivate; if( iWal ){ @@ -1820,6 +1836,7 @@ static int walIndexRecoverOne(Wal *pWal, int iWal, u32 *pnCkpt, int *pbZero){ } } pWal->apWiData[iPg] = aShare; + SEH_SET_ON_ERROR(0, 0); nHdr = (iPg==0 ? WALINDEX_HDR_SIZE : 0); nHdr32 = nHdr / sizeof(u32); #ifndef SQLITE_SAFER_WALINDEX_RECOVERY @@ -3001,7 +3018,9 @@ static void walLimitSize(Wal *pWal, i64 nMax){ ** ** 2) Frees the pointer at Wal.pFree, if any, using sqlite3_free(). ** -** 3) Returns SQLITE_IOERR. +** 3) Set pWal->apWiData[pWal->iWiPg] to pWal->pWiValue if not NULL +** +** 4) Returns SQLITE_IOERR. */ static int walHandleException(Wal *pWal){ if( pWal->exclusiveMode==0 ){ @@ -3020,6 +3039,10 @@ static int walHandleException(Wal *pWal){ } sqlite3_free(pWal->pFree); pWal->pFree = 0; + if( pWal->pWiValue ){ + pWal->apWiData[pWal->iWiPg] = pWal->pWiValue; + pWal->pWiValue = 0; + } return SQLITE_IOERR_IN_PAGE; } @@ -3834,7 +3857,8 @@ static int walSnapshotRecover( rc = walHashGet(pWal, walFramePage(i), &sLoc); if( rc!=SQLITE_OK ) break; - pgno = sLoc.aPgno[i-sLoc.iZero]; + assert( i - sLoc.iZero - 1 >=0 ); + pgno = sLoc.aPgno[i-sLoc.iZero-1]; iDbOff = (i64)(pgno-1) * szPage; if( iDbOff+szPage<=szDb ){ diff --git a/src/wal.h b/src/wal.h index 9abf0a6cfd..cfe3dc2f69 100644 --- a/src/wal.h +++ b/src/wal.h @@ -46,6 +46,7 @@ # define sqlite3WalFindFrame(x,y,z) 0 # define sqlite3WalFile(x) 0 # define sqlite3WalJournalMode(x) 0 +# undef SQLITE_USE_SEH #else #define WAL_SAVEPOINT_NDATA 4 diff --git a/test/decimal.test b/test/decimal.test index 6ce5d642dd..cf4e06ad92 100644 --- a/test/decimal.test +++ b/test/decimal.test @@ -49,7 +49,7 @@ do_execsql_test 1080 { SELECT decimal('+123e+4'); } {1230000} do_execsql_test 1081 { - SELECT decimal_sci('+123e+4'); + SELECT decimal_exp('+123e+4'); } {+1.23e+06} diff --git a/test/fuzzcheck.c b/test/fuzzcheck.c index 76ce5af0ec..23200a5f03 100644 --- a/test/fuzzcheck.c +++ b/test/fuzzcheck.c @@ -1205,10 +1205,12 @@ int runCombinedDbSqlInput( iSql = decodeDatabase((unsigned char*)aData, (int)nByte, &aDb, &nDb); if( iSql<0 ) return 0; if( nDb>=75 ){ - dbFlags = (aDb[72]<<24) + (aDb[73]<<16) + (aDb[74]<<8) + aDb[75]; + dbFlags = ((unsigned int)aDb[72]<<24) + ((unsigned int)aDb[73]<<16) + + ((unsigned int)aDb[74]<<8) + (unsigned int)aDb[75]; } if( nDb>=79 ){ - dbOpt = (aDb[76]<<24) + (aDb[77]<<16) + (aDb[78]<<8) + aDb[79]; + dbOpt = ((unsigned int)aDb[76]<<24) + ((unsigned int)aDb[77]<<16) + + ((unsigned int)aDb[78]<<8) + (unsigned int)aDb[79]; } nSql = (int)(nByte - iSql); if( bScript ){ @@ -2031,7 +2033,9 @@ int main(int argc, char **argv){ if( strcmp(z,"version")==0 ){ int ii; const char *zz; - printf("SQLite %s %s\n", sqlite3_libversion(), sqlite3_sourceid()); + printf("SQLite %s %s (%d-bit)\n", + sqlite3_libversion(), sqlite3_sourceid(), + 8*(int)sizeof(char*)); for(ii=0; (zz = sqlite3_compileoption_get(ii))!=0; ii++){ printf("%s\n", zz); } @@ -2536,9 +2540,10 @@ int main(int argc, char **argv){ printf("fuzzcheck: %u query invariants checked\n", g.nInvariant); } printf("fuzzcheck: 0 errors out of %d tests in %d.%03d seconds\n" - "SQLite %s %s\n", + "SQLite %s %s (%d-bit)\n", nTest, (int)(iElapse/1000), (int)(iElapse%1000), - sqlite3_libversion(), sqlite3_sourceid()); + sqlite3_libversion(), sqlite3_sourceid(), + 8*(int)sizeof(char*)); } free(azSrcDb); free(pHeap); diff --git a/test/json101.test b/test/json101.test index 8bb133ec73..4da1d132cc 100644 --- a/test/json101.test +++ b/test/json101.test @@ -122,14 +122,14 @@ do_execsql_test json101-4.8 { # json_extract(JSON,'$') will return objects and arrays without change. # -do_execsql_test json-4.10 { +do_execsql_test json101-4.10 { SELECT count(*) FROM j1 WHERE json_type(x) IN ('object','array'); SELECT x FROM j1 WHERE json_extract(x,'$')<>x AND json_type(x) IN ('object','array'); } {4} -do_execsql_test json-5.1 { +do_execsql_test json101-5.1 { CREATE TABLE j2(id INTEGER PRIMARY KEY, json, src); INSERT INTO j2(id,json,src) VALUES(1,'{ @@ -257,7 +257,7 @@ do_execsql_test json-5.1 { SELECT count(*) FROM j2; } {3} -do_execsql_test json-5.2 { +do_execsql_test json101-5.2 { SELECT id, json_valid(json), json_type(json), '|' FROM j2 ORDER BY id; } {1 1 object | 2 1 object | 3 1 array |} @@ -268,13 +268,13 @@ ifcapable !vtab { # fullkey is always the same as path+key (with appropriate formatting) # -do_execsql_test json-5.3 { +do_execsql_test json101-5.3 { SELECT j2.rowid, jx.rowid, fullkey, path, key FROM j2, json_tree(j2.json) AS jx WHERE fullkey!=(path || CASE WHEN typeof(key)=='integer' THEN '['||key||']' ELSE '.'||key END); } {} -do_execsql_test json-5.4 { +do_execsql_test json101-5.4 { SELECT j2.rowid, jx.rowid, fullkey, path, key FROM j2, json_each(j2.json) AS jx WHERE fullkey!=(path || CASE WHEN typeof(key)=='integer' THEN '['||key||']' @@ -285,58 +285,58 @@ do_execsql_test json-5.4 { # Verify that the json_each.json and json_tree.json output is always the # same as input. # -do_execsql_test json-5.5 { +do_execsql_test json101-5.5 { SELECT j2.rowid, jx.rowid, fullkey, path, key FROM j2, json_each(j2.json) AS jx WHERE jx.json<>j2.json; } {} -do_execsql_test json-5.6 { +do_execsql_test json101-5.6 { SELECT j2.rowid, jx.rowid, fullkey, path, key FROM j2, json_tree(j2.json) AS jx WHERE jx.json<>j2.json; } {} -do_execsql_test json-5.7 { +do_execsql_test json101-5.7 { SELECT j2.rowid, jx.rowid, fullkey, path, key FROM j2, json_each(j2.json) AS jx WHERE jx.value<>jx.atom AND type NOT IN ('array','object'); } {} -do_execsql_test json-5.8 { +do_execsql_test json101-5.8 { SELECT j2.rowid, jx.rowid, fullkey, path, key FROM j2, json_tree(j2.json) AS jx WHERE jx.value<>jx.atom AND type NOT IN ('array','object'); } {} -do_execsql_test json-6.1 { +do_execsql_test json101-6.1 { SELECT json_valid('{"a":55,"b":72,}'); } {0} -do_execsql_test json-6.2 { +do_execsql_test json101-6.2 { SELECT json_error_position('{"a":55,"b":72,}'); } {0} -do_execsql_test json-6.3 { +do_execsql_test json101-6.3 { SELECT json_valid(json('{"a":55,"b":72,}')); } {1} -do_execsql_test json-6.4 { +do_execsql_test json101-6.4 { SELECT json_valid('{"a":55,"b":72 , }'); } {0} -do_execsql_test json-6.5 { +do_execsql_test json101-6.5 { SELECT json_error_position('{"a":55,"b":72 , }'); } {0} -do_execsql_test json-6.6 { +do_execsql_test json101-6.6 { SELECT json_error_position('{"a":55,"b":72,,}'); } {16} -do_execsql_test json-6.7 { +do_execsql_test json101-6.7 { SELECT json_valid('{"a":55,"b":72}'); } {1} -do_execsql_test json-6.8 { +do_execsql_test json101-6.8 { SELECT json_error_position('["a",55,"b",72,]'); } {0} -do_execsql_test json-6.9 { +do_execsql_test json101-6.9 { SELECT json_error_position('["a",55,"b",72 , ]'); } {0} -do_execsql_test json-6.10 { +do_execsql_test json101-6.10 { SELECT json_error_position('["a",55,"b",72,,]'); } {16} -do_execsql_test json-6.11 { +do_execsql_test json101-6.11 { SELECT json_valid('["a",55,"b",72]'); } {1} @@ -352,7 +352,7 @@ foreach {tn isvalid ws} { 7.6 1 char(0x20,0x09,0x0a,0x0d,0x20) 7.7 0 char(0x20,0x09,0x0a,0x0c,0x0d,0x20) } { - do_execsql_test json-$tn.1 \ + do_execsql_test json101-$tn.1 \ "SELECT json_valid(printf('%s{%s\"x\"%s:%s9%s}%s', $::ws,$::ws,$::ws,$::ws,$::ws,$::ws));" \ $isvalid @@ -361,23 +361,23 @@ foreach {tn isvalid ws} { # Ticket https://www.sqlite.org/src/info/ad2559db380abf8e # Control characters must be escaped in JSON strings. # -do_execsql_test json-8.1 { +do_execsql_test json101-8.1 { DROP TABLE IF EXISTS t8; CREATE TABLE t8(a,b); INSERT INTO t8(a) VALUES('abc' || char(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35) || 'xyz'); UPDATE t8 SET b=json_array(a); SELECT b FROM t8; } {{["abc\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#xyz"]}} -do_execsql_test json-8.2 { +do_execsql_test json101-8.2 { SELECT a=json_extract(b,'$[0]') FROM t8; } {1} # 2017-04-12. Regression reported on the mailing list by Rolf Ade # -do_execsql_test json-8.3 { +do_execsql_test json101-8.3 { SELECT json_valid(char(0x22,0xe4,0x22)); } {1} -do_execsql_test json-8.4 { +do_execsql_test json101-8.4 { SELECT unicode(json_extract(char(0x22,228,0x22),'$')); } {228} @@ -385,331 +385,331 @@ do_execsql_test json-8.4 { # String values are quoted and interior quotes are escaped. NULL values # are rendered as the unquoted string "null". # -do_execsql_test json-9.1 { +do_execsql_test json101-9.1 { SELECT json_quote('abc"xyz'); } {{"abc\"xyz"}} -do_execsql_test json-9.2 { +do_execsql_test json101-9.2 { SELECT json_quote(3.14159); } {3.14159} -do_execsql_test json-9.3 { +do_execsql_test json101-9.3 { SELECT json_quote(12345); } {12345} -do_execsql_test json-9.4 { +do_execsql_test json101-9.4 { SELECT json_quote(null); } {"null"} -do_catchsql_test json-9.5 { +do_catchsql_test json101-9.5 { SELECT json_quote(x'30313233'); } {1 {JSON cannot hold BLOB values}} -do_catchsql_test json-9.6 { +do_catchsql_test json101-9.6 { SELECT json_quote(123,456) } {1 {wrong number of arguments to function json_quote()}} -do_catchsql_test json-9.7 { +do_catchsql_test json101-9.7 { SELECT json_quote() } {1 {wrong number of arguments to function json_quote()}} # Make sure only valid backslash-escapes are accepted. # -do_execsql_test json-10.1 { +do_execsql_test json101-10.1 { SELECT json_valid('" \ "'); } {0} -do_execsql_test json-10.2 { +do_execsql_test json101-10.2 { SELECT json_valid('" \! "'); } {0} -do_execsql_test json-10.3 { +do_execsql_test json101-10.3 { SELECT json_valid('" \" "'); } {1} -do_execsql_test json-10.4 { +do_execsql_test json101-10.4 { SELECT json_valid('" \# "'); } {0} -do_execsql_test json-10.5 { +do_execsql_test json101-10.5 { SELECT json_valid('" \$ "'); } {0} -do_execsql_test json-10.6 { +do_execsql_test json101-10.6 { SELECT json_valid('" \% "'); } {0} -do_execsql_test json-10.7 { +do_execsql_test json101-10.7 { SELECT json_valid('" \& "'); } {0} -do_execsql_test json-10.8 { +do_execsql_test json101-10.8 { SELECT json_valid('" \'' "'); } {0} -do_execsql_test json-10.9 { +do_execsql_test json101-10.9 { SELECT json_valid('" \( "'); } {0} -do_execsql_test json-10.10 { +do_execsql_test json101-10.10 { SELECT json_valid('" \) "'); } {0} -do_execsql_test json-10.11 { +do_execsql_test json101-10.11 { SELECT json_valid('" \* "'); } {0} -do_execsql_test json-10.12 { +do_execsql_test json101-10.12 { SELECT json_valid('" \+ "'); } {0} -do_execsql_test json-10.13 { +do_execsql_test json101-10.13 { SELECT json_valid('" \, "'); } {0} -do_execsql_test json-10.14 { +do_execsql_test json101-10.14 { SELECT json_valid('" \- "'); } {0} -do_execsql_test json-10.15 { +do_execsql_test json101-10.15 { SELECT json_valid('" \. "'); } {0} -do_execsql_test json-10.16 { +do_execsql_test json101-10.16 { SELECT json_valid('" \/ "'); } {1} -do_execsql_test json-10.17 { +do_execsql_test json101-10.17 { SELECT json_valid('" \0 "'); } {0} -do_execsql_test json-10.18 { +do_execsql_test json101-10.18 { SELECT json_valid('" \1 "'); } {0} -do_execsql_test json-10.19 { +do_execsql_test json101-10.19 { SELECT json_valid('" \2 "'); } {0} -do_execsql_test json-10.20 { +do_execsql_test json101-10.20 { SELECT json_valid('" \3 "'); } {0} -do_execsql_test json-10.21 { +do_execsql_test json101-10.21 { SELECT json_valid('" \4 "'); } {0} -do_execsql_test json-10.22 { +do_execsql_test json101-10.22 { SELECT json_valid('" \5 "'); } {0} -do_execsql_test json-10.23 { +do_execsql_test json101-10.23 { SELECT json_valid('" \6 "'); } {0} -do_execsql_test json-10.24 { +do_execsql_test json101-10.24 { SELECT json_valid('" \7 "'); } {0} -do_execsql_test json-10.25 { +do_execsql_test json101-10.25 { SELECT json_valid('" \8 "'); } {0} -do_execsql_test json-10.26 { +do_execsql_test json101-10.26 { SELECT json_valid('" \9 "'); } {0} -do_execsql_test json-10.27 { +do_execsql_test json101-10.27 { SELECT json_valid('" \: "'); } {0} -do_execsql_test json-10.28 { +do_execsql_test json101-10.28 { SELECT json_valid('" \; "'); } {0} -do_execsql_test json-10.29 { +do_execsql_test json101-10.29 { SELECT json_valid('" \< "'); } {0} -do_execsql_test json-10.30 { +do_execsql_test json101-10.30 { SELECT json_valid('" \= "'); } {0} -do_execsql_test json-10.31 { +do_execsql_test json101-10.31 { SELECT json_valid('" \> "'); } {0} -do_execsql_test json-10.32 { +do_execsql_test json101-10.32 { SELECT json_valid('" \? "'); } {0} -do_execsql_test json-10.33 { +do_execsql_test json101-10.33 { SELECT json_valid('" \@ "'); } {0} -do_execsql_test json-10.34 { +do_execsql_test json101-10.34 { SELECT json_valid('" \A "'); } {0} -do_execsql_test json-10.35 { +do_execsql_test json101-10.35 { SELECT json_valid('" \B "'); } {0} -do_execsql_test json-10.36 { +do_execsql_test json101-10.36 { SELECT json_valid('" \C "'); } {0} -do_execsql_test json-10.37 { +do_execsql_test json101-10.37 { SELECT json_valid('" \D "'); } {0} -do_execsql_test json-10.38 { +do_execsql_test json101-10.38 { SELECT json_valid('" \E "'); } {0} -do_execsql_test json-10.39 { +do_execsql_test json101-10.39 { SELECT json_valid('" \F "'); } {0} -do_execsql_test json-10.40 { +do_execsql_test json101-10.40 { SELECT json_valid('" \G "'); } {0} -do_execsql_test json-10.41 { +do_execsql_test json101-10.41 { SELECT json_valid('" \H "'); } {0} -do_execsql_test json-10.42 { +do_execsql_test json101-10.42 { SELECT json_valid('" \I "'); } {0} -do_execsql_test json-10.43 { +do_execsql_test json101-10.43 { SELECT json_valid('" \J "'); } {0} -do_execsql_test json-10.44 { +do_execsql_test json101-10.44 { SELECT json_valid('" \K "'); } {0} -do_execsql_test json-10.45 { +do_execsql_test json101-10.45 { SELECT json_valid('" \L "'); } {0} -do_execsql_test json-10.46 { +do_execsql_test json101-10.46 { SELECT json_valid('" \M "'); } {0} -do_execsql_test json-10.47 { +do_execsql_test json101-10.47 { SELECT json_valid('" \N "'); } {0} -do_execsql_test json-10.48 { +do_execsql_test json101-10.48 { SELECT json_valid('" \O "'); } {0} -do_execsql_test json-10.49 { +do_execsql_test json101-10.49 { SELECT json_valid('" \P "'); } {0} -do_execsql_test json-10.50 { +do_execsql_test json101-10.50 { SELECT json_valid('" \Q "'); } {0} -do_execsql_test json-10.51 { +do_execsql_test json101-10.51 { SELECT json_valid('" \R "'); } {0} -do_execsql_test json-10.52 { +do_execsql_test json101-10.52 { SELECT json_valid('" \S "'); } {0} -do_execsql_test json-10.53 { +do_execsql_test json101-10.53 { SELECT json_valid('" \T "'); } {0} -do_execsql_test json-10.54 { +do_execsql_test json101-10.54 { SELECT json_valid('" \U "'); } {0} -do_execsql_test json-10.55 { +do_execsql_test json101-10.55 { SELECT json_valid('" \V "'); } {0} -do_execsql_test json-10.56 { +do_execsql_test json101-10.56 { SELECT json_valid('" \W "'); } {0} -do_execsql_test json-10.57 { +do_execsql_test json101-10.57 { SELECT json_valid('" \X "'); } {0} -do_execsql_test json-10.58 { +do_execsql_test json101-10.58 { SELECT json_valid('" \Y "'); } {0} -do_execsql_test json-10.59 { +do_execsql_test json101-10.59 { SELECT json_valid('" \Z "'); } {0} -do_execsql_test json-10.60 { +do_execsql_test json101-10.60 { SELECT json_valid('" \[ "'); } {0} -do_execsql_test json-10.61 { +do_execsql_test json101-10.61 { SELECT json_valid('" \\ "'); } {1} -do_execsql_test json-10.62 { +do_execsql_test json101-10.62 { SELECT json_valid('" \] "'); } {0} -do_execsql_test json-10.63 { +do_execsql_test json101-10.63 { SELECT json_valid('" \^ "'); } {0} -do_execsql_test json-10.64 { +do_execsql_test json101-10.64 { SELECT json_valid('" \_ "'); } {0} -do_execsql_test json-10.65 { +do_execsql_test json101-10.65 { SELECT json_valid('" \` "'); } {0} -do_execsql_test json-10.66 { +do_execsql_test json101-10.66 { SELECT json_valid('" \a "'); } {0} -do_execsql_test json-10.67 { +do_execsql_test json101-10.67 { SELECT json_valid('" \b "'); } {1} -do_execsql_test json-10.68 { +do_execsql_test json101-10.68 { SELECT json_valid('" \c "'); } {0} -do_execsql_test json-10.69 { +do_execsql_test json101-10.69 { SELECT json_valid('" \d "'); } {0} -do_execsql_test json-10.70 { +do_execsql_test json101-10.70 { SELECT json_valid('" \e "'); } {0} -do_execsql_test json-10.71 { +do_execsql_test json101-10.71 { SELECT json_valid('" \f "'); } {1} -do_execsql_test json-10.72 { +do_execsql_test json101-10.72 { SELECT json_valid('" \g "'); } {0} -do_execsql_test json-10.73 { +do_execsql_test json101-10.73 { SELECT json_valid('" \h "'); } {0} -do_execsql_test json-10.74 { +do_execsql_test json101-10.74 { SELECT json_valid('" \i "'); } {0} -do_execsql_test json-10.75 { +do_execsql_test json101-10.75 { SELECT json_valid('" \j "'); } {0} -do_execsql_test json-10.76 { +do_execsql_test json101-10.76 { SELECT json_valid('" \k "'); } {0} -do_execsql_test json-10.77 { +do_execsql_test json101-10.77 { SELECT json_valid('" \l "'); } {0} -do_execsql_test json-10.78 { +do_execsql_test json101-10.78 { SELECT json_valid('" \m "'); } {0} -do_execsql_test json-10.79 { +do_execsql_test json101-10.79 { SELECT json_valid('" \n "'); } {1} -do_execsql_test json-10.80 { +do_execsql_test json101-10.80 { SELECT json_valid('" \o "'); } {0} -do_execsql_test json-10.81 { +do_execsql_test json101-10.81 { SELECT json_valid('" \p "'); } {0} -do_execsql_test json-10.82 { +do_execsql_test json101-10.82 { SELECT json_valid('" \q "'); } {0} -do_execsql_test json-10.83 { +do_execsql_test json101-10.83 { SELECT json_valid('" \r "'); } {1} -do_execsql_test json-10.84 { +do_execsql_test json101-10.84 { SELECT json_valid('" \s "'); } {0} -do_execsql_test json-10.85 { +do_execsql_test json101-10.85 { SELECT json_valid('" \t "'); } {1} -do_execsql_test json-10.86.0 { +do_execsql_test json101-10.86.0 { SELECT json_valid('" \u "'); } {0} -do_execsql_test json-10.86.1 { +do_execsql_test json101-10.86.1 { SELECT json_valid('" \ua "'); } {0} -do_execsql_test json-10.86.2 { +do_execsql_test json101-10.86.2 { SELECT json_valid('" \uab "'); } {0} -do_execsql_test json-10.86.3 { +do_execsql_test json101-10.86.3 { SELECT json_valid('" \uabc "'); } {0} -do_execsql_test json-10.86.4 { +do_execsql_test json101-10.86.4 { SELECT json_valid('" \uabcd "'); } {1} -do_execsql_test json-10.86.5 { +do_execsql_test json101-10.86.5 { SELECT json_valid('" \uFEDC "'); } {1} -do_execsql_test json-10.86.6 { +do_execsql_test json101-10.86.6 { SELECT json_valid('" \u1234 "'); } {1} -do_execsql_test json-10.87 { +do_execsql_test json101-10.87 { SELECT json_valid('" \v "'); } {0} -do_execsql_test json-10.88 { +do_execsql_test json101-10.88 { SELECT json_valid('" \w "'); } {0} -do_execsql_test json-10.89 { +do_execsql_test json101-10.89 { SELECT json_valid('" \x "'); } {0} -do_execsql_test json-10.90 { +do_execsql_test json101-10.90 { SELECT json_valid('" \y "'); } {0} -do_execsql_test json-10.91 { +do_execsql_test json101-10.91 { SELECT json_valid('" \z "'); } {0} -do_execsql_test json-10.92 { +do_execsql_test json101-10.92 { SELECT json_valid('" \{ "'); } {0} -do_execsql_test json-10.93 { +do_execsql_test json101-10.93 { SELECT json_valid('" \| "'); } {0} -do_execsql_test json-10.94 { +do_execsql_test json101-10.94 { SELECT json_valid('" \} "'); } {0} -do_execsql_test json-10.95 { +do_execsql_test json101-10.95 { SELECT json_valid('" \~ "'); } {0} @@ -719,20 +719,20 @@ do_execsql_test json-10.95 { # # The following tests confirm that deeply nested JSON is considered invalid. # -do_execsql_test json-11.0 { +do_execsql_test json101-11.0 { /* Shallow enough to be parsed */ SELECT json_valid(printf('%.1000c0%.1000c','[',']')); } {1} -do_execsql_test json-11.1 { +do_execsql_test json101-11.1 { /* Too deep by one */ SELECT json_valid(printf('%.1001c0%.1001c','[',']')); } {0} -do_execsql_test json-11.2 { +do_execsql_test json101-11.2 { /* Shallow enough to be parsed { */ SELECT json_valid(replace(printf('%.1000c0%.1000c','[','}'),'[','{"a":')); /* } */ } {1} -do_execsql_test json-11.3 { +do_execsql_test json101-11.3 { /* Too deep by one { */ SELECT json_valid(replace(printf('%.1001c0%.1001c','[','}'),'[','{"a":')); /* } */ @@ -742,7 +742,7 @@ do_execsql_test json-11.3 { # a json structure even though the element name constains a "." # character, by quoting the element name in the path. # -do_execsql_test json-12.100 { +do_execsql_test json101-12.100 { CREATE TABLE t12(x); INSERT INTO t12(x) VALUES( '{"settings": @@ -766,11 +766,11 @@ do_execsql_test json-12.100 { } }'); } {} -do_execsql_test json-12.110 { +do_execsql_test json101-12.110 { SELECT json_remove(x, '$.settings.layer2."dis.legomenon".forceDisplay') FROM t12; } {{{"settings":{"layer2":{"hapax.legomenon":{"forceDisplay":true,"transliterate":true,"add.footnote":true,"summary.report":true},"dis.legomenon":{"transliterate":false,"add.footnote":false,"summary.report":true},"tris.legomenon":{"forceDisplay":true,"transliterate":false,"add.footnote":false,"summary.report":false}}}}}} -do_execsql_test json-12.120 { +do_execsql_test json101-12.120 { SELECT json_extract(x, '$.settings.layer2."tris.legomenon"."summary.report"') FROM t12; } {0} @@ -779,7 +779,7 @@ do_execsql_test json-12.120 { # ticket https://www.sqlite.org/src/tktview/80177f0c226ff54f6ddd41 # Make sure the query planner knows about the arguments to table-valued functions. # -do_execsql_test json-13.100 { +do_execsql_test json101-13.100 { DROP TABLE IF EXISTS t1; DROP TABLE IF EXISTS t2; CREATE TABLE t1(id, json); @@ -794,7 +794,7 @@ do_execsql_test json-13.100 { WHERE EXISTS(SELECT 1 FROM json_each(t1.json,'$.items') AS Z WHERE Z.value==t2.id); } {1 {{"items":[3,5]}} 3 {{"value":3}} 1 {{"items":[3,5]}} 5 {{"value":5}}} -do_execsql_test json-13.110 { +do_execsql_test json101-13.110 { SELECT * FROM t2 CROSS JOIN t1 WHERE EXISTS(SELECT 1 FROM json_each(t1.json,'$.items') AS Z WHERE Z.value==t2.id); @@ -804,28 +804,28 @@ do_execsql_test json-13.110 { # Incorrect fullkey output from json_each() # when the input JSON is not an array or object. # -do_execsql_test json-14.100 { +do_execsql_test json101-14.100 { SELECT fullkey FROM json_each('123'); } {$} -do_execsql_test json-14.110 { +do_execsql_test json101-14.110 { SELECT fullkey FROM json_each('123.56'); } {$} -do_execsql_test json-14.120 { +do_execsql_test json101-14.120 { SELECT fullkey FROM json_each('"hello"'); } {$} -do_execsql_test json-14.130 { +do_execsql_test json101-14.130 { SELECT fullkey FROM json_each('null'); } {$} -do_execsql_test json-14.140 { +do_execsql_test json101-14.140 { SELECT fullkey FROM json_tree('123'); } {$} -do_execsql_test json-14.150 { +do_execsql_test json101-14.150 { SELECT fullkey FROM json_tree('123.56'); } {$} -do_execsql_test json-14.160 { +do_execsql_test json101-14.160 { SELECT fullkey FROM json_tree('"hello"'); } {$} -do_execsql_test json-14.170 { +do_execsql_test json101-14.170 { SELECT fullkey FROM json_tree('null'); } {$} @@ -835,16 +835,16 @@ do_execsql_test json-14.170 { # # Bug reported via private email. See TH3 for more information. # -do_execsql_test json-15.100 { +do_execsql_test json101-15.100 { SELECT * FROM JSON_EACH('{"a":1, "b":2}'); } {a 1 integer 1 2 {} {$.a} {$} b 2 integer 2 4 {} {$.b} {$}} -do_execsql_test json-15.110 { +do_execsql_test json101-15.110 { SELECT xyz.* FROM JSON_EACH('{"a":1, "b":2}') AS xyz; } {a 1 integer 1 2 {} {$.a} {$} b 2 integer 2 4 {} {$.b} {$}} -do_execsql_test json-15.120 { +do_execsql_test json101-15.120 { SELECT * FROM (JSON_EACH('{"a":1, "b":2}')); } {a 1 integer 1 2 {} {$.a} {$} b 2 integer 2 4 {} {$.b} {$}} -do_execsql_test json-15.130 { +do_execsql_test json101-15.130 { SELECT xyz.* FROM (JSON_EACH('{"a":1, "b":2}')) AS xyz; } {a 1 integer 1 2 {} {$.a} {$} b 2 integer 2 4 {} {$.b} {$}} @@ -852,18 +852,18 @@ do_execsql_test json-15.130 { # Mailing list bug report on the handling of surrogate pairs # in JSON. # -do_execsql_test json-16.10 { +do_execsql_test json101-16.10 { SELECT length(json_extract('"abc\uD834\uDD1Exyz"','$')); } {7} -do_execsql_test json-16.20 { +do_execsql_test json101-16.20 { SELECT length(json_extract('"\uD834\uDD1E"','$')); } {1} -do_execsql_test json-16.30 { +do_execsql_test json101-16.30 { SELECT unicode(json_extract('"\uD834\uDD1E"','$')); } {119070} # 2022-01-30 dbsqlfuzz 4678cf825d27f87c9b8343720121e12cf944b71a -do_execsql_test json-17.1 { +do_execsql_test json101-17.1 { DROP TABLE IF EXISTS t1; DROP TABLE IF EXISTS t2; CREATE TABLE t1(a,b,c); @@ -872,19 +872,19 @@ do_execsql_test json-17.1 { } {} # 2022-04-04 forum post https://sqlite.org/forum/forumpost/c082aeab43 -do_execsql_test json-18.1 { +do_execsql_test json101-18.1 { SELECT json_valid('{"":5}'); } {1} -do_execsql_test json-18.2 { +do_execsql_test json101-18.2 { SELECT json_extract('{"":5}', '$.""'); } {5} -do_execsql_test json-18.3 { +do_execsql_test json101-18.3 { SELECT json_extract('[3,{"a":4,"":[5,{"hi":6},7]},8]', '$[1].""[1].hi'); } {6} -do_execsql_test json-18.4 { +do_execsql_test json101-18.4 { SELECT json_extract('[3,{"a":4,"":[5,{"hi":6},7]},8]', '$[1].""[1]."hi"'); } {6} -do_catchsql_test json-18.5 { +do_catchsql_test json101-18.5 { SELECT json_extract('{"":8}', '$.'); } {1 {JSON path error near ''}} @@ -893,28 +893,28 @@ do_catchsql_test json-18.5 { # a problem with transaction control. But the json() function makes # the problem more easily accessible, so it is tested here. # -do_execsql_test json-19.1 { +do_execsql_test json101-19.1 { DROP TABLE IF EXISTS t1; CREATE TABLE t1(x); } {} -do_catchsql_test json-19.2 { +do_catchsql_test json101-19.2 { BEGIN; INSERT INTO t1 VALUES(0), (json('not-valid-json')); } {1 {malformed JSON}} -do_execsql_test json-19.3 { +do_execsql_test json101-19.3 { COMMIT; SELECT * FROM t1; } {} # 2023-03-17 positive and negative infinities # -do_execsql_test json-20.1 { +do_execsql_test json101-20.1 { SELECT json_object('a',2e370,'b',-3e380); } {{{"a":9.0e+999,"b":-9.0e+999}}} -do_execsql_test json-20.2 { +do_execsql_test json101-20.2 { SELECT json_object('a',2e370,'b',-3e380)->>'a'; } Inf -do_execsql_test json-20.3 { +do_execsql_test json101-20.3 { SELECT json_object('a',2e370,'b',-3e380)->>'b'; } {-Inf} @@ -924,91 +924,91 @@ do_execsql_test json-20.3 { # db null NULL if {[db exists {SELECT * FROM pragma_compile_options WHERE compile_options LIKE '%legacy_json_valid%'}]} { - do_execsql_test json-21.1-legacy { + do_execsql_test json101-21.1-legacy { SELECT json_valid(NULL); } 0 } else { - do_execsql_test json-21.1-correct { + do_execsql_test json101-21.1-correct { SELECT json_valid(NULL); } NULL } -do_execsql_test json-21.2 { +do_execsql_test json101-21.2 { SELECT json_error_position(NULL); } NULL -do_execsql_test json-21.3 { +do_execsql_test json101-21.3 { SELECT json(NULL); } NULL -do_execsql_test json-21.4 { +do_execsql_test json101-21.4 { SELECT json_array(NULL); } {[null]} -do_execsql_test json-21.5 { +do_execsql_test json101-21.5 { SELECT json_extract(NULL); } NULL -do_execsql_test json-21.6 { +do_execsql_test json101-21.6 { SELECT json_insert(NULL,'$',123); } NULL -do_execsql_test json-21.7 { +do_execsql_test json101-21.7 { SELECT NULL->0; } NULL -do_execsql_test json-21.8 { +do_execsql_test json101-21.8 { SELECT NULL->>0; } NULL -do_execsql_test json-21.9 { +do_execsql_test json101-21.9 { SELECT '{a:5}'->NULL; } NULL -do_execsql_test json-21.10 { +do_execsql_test json101-21.10 { SELECT '{a:5}'->>NULL; } NULL -do_catchsql_test json-21.11 { +do_catchsql_test json101-21.11 { SELECT json_object(NULL,5); } {1 {json_object() labels must be TEXT}} -do_execsql_test json-21.12 { +do_execsql_test json101-21.12 { SELECT json_patch(NULL,'{a:5}'); } NULL -do_execsql_test json-21.13 { +do_execsql_test json101-21.13 { SELECT json_patch('{a:5}',NULL); } NULL -do_execsql_test json-21.14 { +do_execsql_test json101-21.14 { SELECT json_patch(NULL,NULL); } NULL -do_execsql_test json-21.15 { +do_execsql_test json101-21.15 { SELECT json_remove(NULL,'$'); } NULL -do_execsql_test json-21.16 { +do_execsql_test json101-21.16 { SELECT json_remove('{a:5,b:7}',NULL); } NULL -do_execsql_test json-21.17 { +do_execsql_test json101-21.17 { SELECT json_replace(NULL,'$.a',123); } NULL -do_execsql_test json-21.18 { +do_execsql_test json101-21.18 { SELECT json_replace('{a:5,b:7}',NULL,NULL); } {{{"a":5,"b":7}}} -do_execsql_test json-21.19 { +do_execsql_test json101-21.19 { SELECT json_set(NULL,'$.a',123); } NULL -do_execsql_test json-21.20 { +do_execsql_test json101-21.20 { SELECT json_set('{a:5,b:7}',NULL,NULL); } {{{"a":5,"b":7}}} -do_execsql_test json-21.21 { +do_execsql_test json101-21.21 { SELECT json_type(NULL); } NULL -do_execsql_test json-21.22 { +do_execsql_test json101-21.22 { SELECT json_type('{a:5,b:7}',NULL); } NULL -do_execsql_test json-21.23 { +do_execsql_test json101-21.23 { SELECT json_quote(NULL); } null -do_execsql_test json-21.24 { +do_execsql_test json101-21.24 { SELECT count(*) FROM json_each(NULL); } 0 -do_execsql_test json-21.25 { +do_execsql_test json101-21.25 { SELECT count(*) FROM json_tree(NULL); } 0 -do_execsql_test json-21.26 { +do_execsql_test json101-21.26 { WITH c(x) AS (VALUES(1),(2.0),(NULL),('three')) SELECT json_group_array(x) FROM c; } {[1,2.0,null,"three"]} -do_execsql_test json-21.27 { +do_execsql_test json101-21.27 { WITH c(x,y) AS (VALUES('a',1),('b',2.0),('c',NULL),(NULL,'three'),('e','four')) SELECT json_group_object(x,y) FROM c; } {{{"a":1,"b":2.0,"c":null,:"three","e":"four"}}} diff --git a/test/like.test b/test/like.test index e8662dc6c3..d314e96a19 100644 --- a/test/like.test +++ b/test/like.test @@ -1140,4 +1140,24 @@ do_execsql_test 17.1 { } {1} +# 2023-08-15 https://sqlite.org/forum/forumpost/925dc9f67804c540 +# +reset_db +sqlite3_db_config db DEFENSIVE 1 +db eval {PRAGMA trusted_schema=OFF} +do_execsql_test 18.0 { + CREATE TABLE t1(x INT, y TEXT); + INSERT INTO t1 VALUES(1,'abc'),(2,'ABC'),(3,'Abc'); + CREATE VIEW t2 AS SELECT * FROM t1 WHERE y LIKE 'a%'; + SELECT * FROM t2; +} {1 abc 2 ABC 3 Abc} +do_execsql_test 18.1 { + PRAGMA case_sensitive_like=OFF; + SELECT * FROM t2; +} {1 abc 2 ABC 3 Abc} +do_execsql_test 18.2 { + PRAGMA case_sensitive_like=ON; + SELECT * FROM t2; +} {1 abc} + finish_test diff --git a/test/permutations.test b/test/permutations.test index de2dd1872b..71bf9b9baa 100644 --- a/test/permutations.test +++ b/test/permutations.test @@ -10,9 +10,11 @@ #*********************************************************************** # -set testdir [file dirname $argv0] -source $testdir/tester.tcl -db close +if {[info vars ::trd::tcltest]==""} { + set testdir [file dirname $argv0] + source $testdir/tester.tcl + db close +} #------------------------------------------------------------------------- # test_suite NAME OPTIONS @@ -832,117 +834,111 @@ test_suite "inmemory_journal" -description { recoverpgsz.test }] -ifcapable mem3 { - test_suite "memsys3" -description { - Run tests using the allocator in mem3.c. - } -files [test_set $::allquicktests -exclude { - autovacuum.test delete3.test manydb.test - bigrow.test incrblob2.test memdb.test - bitvec.test index2.test memsubsys1.test - capi3c.test ioerr.test memsubsys2.test - capi3.test join3.test pagesize.test - collate5.test limit.test backup_ioerr.test - backup_malloc.test - }] -initialize { - catch {db close} - sqlite3_reset_auto_extension - sqlite3_shutdown - sqlite3_config_heap 25000000 0 - sqlite3_config_lookaside 0 0 - ifcapable mem5 { - # If both memsys3 and memsys5 are enabled in the build, the call to - # [sqlite3_config_heap] will initialize the system to use memsys5. - # The following overrides this preference and installs the memsys3 - # allocator. - sqlite3_install_memsys3 - } - install_malloc_faultsim 1 - sqlite3_initialize - autoinstall_test_functions - } -shutdown { - catch {db close} - sqlite3_shutdown - sqlite3_config_heap 0 0 - sqlite3_config_lookaside 100 500 - install_malloc_faultsim 1 - sqlite3_initialize - autoinstall_test_functions +test_suite "memsys3" -description { + Run tests using the allocator in mem3.c. +} -files [test_set $::allquicktests -exclude { + autovacuum.test delete3.test manydb.test + bigrow.test incrblob2.test memdb.test + bitvec.test index2.test memsubsys1.test + capi3c.test ioerr.test memsubsys2.test + capi3.test join3.test pagesize.test + collate5.test limit.test backup_ioerr.test + backup_malloc.test +}] -initialize { + catch {db close} + sqlite3_reset_auto_extension + sqlite3_shutdown + sqlite3_config_heap 25000000 0 + sqlite3_config_lookaside 0 0 + ifcapable mem5 { + # If both memsys3 and memsys5 are enabled in the build, the call to + # [sqlite3_config_heap] will initialize the system to use memsys5. + # The following overrides this preference and installs the memsys3 + # allocator. + sqlite3_install_memsys3 } + install_malloc_faultsim 1 + sqlite3_initialize + autoinstall_test_functions +} -shutdown { + catch {db close} + sqlite3_shutdown + sqlite3_config_heap 0 0 + sqlite3_config_lookaside 100 500 + install_malloc_faultsim 1 + sqlite3_initialize + autoinstall_test_functions } -ifcapable mem5 { - test_suite "memsys5" -description { - Run tests using the allocator in mem5.c. - } -files [test_set $::allquicktests -exclude { - autovacuum.test delete3.test manydb.test - bigrow.test incrblob2.test memdb.test - bitvec.test index2.test memsubsys1.test - capi3c.test ioerr.test memsubsys2.test - capi3.test join3.test pagesize.test - collate5.test limit.test zeroblob.test - }] -initialize { - catch {db close} - sqlite3_shutdown - sqlite3_config_heap 25000000 64 - sqlite3_config_lookaside 0 0 - install_malloc_faultsim 1 - sqlite3_initialize - autoinstall_test_functions - } -shutdown { - catch {db close} - sqlite3_shutdown - sqlite3_config_heap 0 0 - sqlite3_config_lookaside 100 500 - install_malloc_faultsim 1 - sqlite3_initialize - autoinstall_test_functions - } - - test_suite "memsys5-2" -description { - Run tests using the allocator in mem5.c in a different configuration. - } -files { - select1.test - } -initialize { - catch {db close} - sqlite3_shutdown - sqlite3_config_memstatus 0 - sqlite3_config_heap 40000000 16 - sqlite3_config_lookaside 0 0 - install_malloc_faultsim 1 - sqlite3_initialize - autoinstall_test_functions - } -shutdown { - catch {db close} - sqlite3_shutdown - sqlite3_config_heap 0 0 - sqlite3_config_lookaside 100 500 - install_malloc_faultsim 1 - sqlite3_initialize - autoinstall_test_functions - } +test_suite "memsys5" -description { + Run tests using the allocator in mem5.c. +} -files [test_set $::allquicktests -exclude { + autovacuum.test delete3.test manydb.test + bigrow.test incrblob2.test memdb.test + bitvec.test index2.test memsubsys1.test + capi3c.test ioerr.test memsubsys2.test + capi3.test join3.test pagesize.test + collate5.test limit.test zeroblob.test +}] -initialize { + catch {db close} + sqlite3_shutdown + sqlite3_config_heap 25000000 64 + sqlite3_config_lookaside 0 0 + install_malloc_faultsim 1 + sqlite3_initialize + autoinstall_test_functions +} -shutdown { + catch {db close} + sqlite3_shutdown + sqlite3_config_heap 0 0 + sqlite3_config_lookaside 100 500 + install_malloc_faultsim 1 + sqlite3_initialize + autoinstall_test_functions } -ifcapable threadsafe { - test_suite "no_mutex_try" -description { - The sqlite3_mutex_try() interface always fails - } -files [ - test_set $::allquicktests -exclude mutex1.test mutex2.test - ] -initialize { - catch {db close} - sqlite3_shutdown - install_mutex_counters 1 - set ::disable_mutex_try 1 - sqlite3_initialize - autoinstall_test_functions - } -shutdown { - catch {db close} - catch {db2 close} - catch {db3 close} - sqlite3_shutdown - install_mutex_counters 0 - sqlite3_initialize - autoinstall_test_functions - } +test_suite "memsys5-2" -description { + Run tests using the allocator in mem5.c in a different configuration. +} -files { + select1.test +} -initialize { + catch {db close} + sqlite3_shutdown + sqlite3_config_memstatus 0 + sqlite3_config_heap 40000000 16 + sqlite3_config_lookaside 0 0 + install_malloc_faultsim 1 + sqlite3_initialize + autoinstall_test_functions +} -shutdown { + catch {db close} + sqlite3_shutdown + sqlite3_config_heap 0 0 + sqlite3_config_lookaside 100 500 + install_malloc_faultsim 1 + sqlite3_initialize + autoinstall_test_functions +} + +test_suite "no_mutex_try" -description { + The sqlite3_mutex_try() interface always fails +} -files [ + test_set $::allquicktests -exclude mutex1.test mutex2.test +] -initialize { + catch {db close} + sqlite3_shutdown + install_mutex_counters 1 + set ::disable_mutex_try 1 + sqlite3_initialize + autoinstall_test_functions +} -shutdown { + catch {db close} + catch {db2 close} + catch {db3 close} + sqlite3_shutdown + install_mutex_counters 0 + sqlite3_initialize + autoinstall_test_functions } # run_tests "crash_safe_append" -description { diff --git a/test/releasetest_data.tcl b/test/releasetest_data.tcl index 0744812990..4ed57a9c8e 100644 --- a/test/releasetest_data.tcl +++ b/test/releasetest_data.tcl @@ -53,6 +53,12 @@ array set ::Configs [strip_comments { --enable-session -DSQLITE_ENABLE_RBU } + "All-Debug" { + --enable-debug --enable-all + } + "All-O0" { + -O0 --enable-all + } "Sanitize" { CC=clang -fsanitize=address,undefined -DSQLITE_ENABLE_STAT4 @@ -672,6 +678,12 @@ proc main_trscript {args} { lappend opts -DSQLITE_ENABLE_PREUPDATE_HOOK lappend opts -DSQLITE_ENABLE_SESSION } + --enable-all { + } + --enable-debug { + # lappend makeOpts OPTIMIZATIONS=0 + lappend opts -DSQLITE_DEBUG + } default { error "Cannot translate $param for MSVC" } @@ -717,7 +729,12 @@ proc main_trscript {args} { puts " \$SRCDIR/configure --with-tcl=\$TCL $configOpts" puts {fi} puts {} - puts {OPTS=" -DSQLITE_NO_SYNC=1"} + if {[info exists ::env(OPTS)]} { + puts "# From environment variable:" + puts "OPTS=$::env(OPTS)" + puts "" + } + puts {OPTS="$OPTS -DSQLITE_NO_SYNC=1"} foreach o $opts { puts "OPTS=\"\$OPTS $o\"" } diff --git a/test/sort2.test b/test/sort2.test index f686654d53..9fe4b4ccf3 100644 --- a/test/sort2.test +++ b/test/sort2.test @@ -69,7 +69,7 @@ foreach {tn script} { # Because it uses so much data, this test can take 12-13 seconds even on # a modern workstation. So it is omitted from "veryquick" and other # permutations.test tests. - if {[isquick]==0} { + if {[isquick]==0 && [clang_sanitize_address]==0} { do_execsql_test $tn.3 { PRAGMA cache_size = 5; WITH r(x,y) AS ( diff --git a/test/testrunner.tcl b/test/testrunner.tcl index 4e21ce08ab..3b94182a9e 100644 --- a/test/testrunner.tcl +++ b/test/testrunner.tcl @@ -1,6 +1,6 @@ set dir [pwd] -set testdir [file dirname $argv0] +set testdir [file normalize [file dirname $argv0]] set saved $argv set argv [list] source [file join $testdir testrunner_data.tcl] @@ -8,6 +8,42 @@ source [file join $testdir permutations.test] set argv $saved cd $dir +# This script requires an interpreter that supports [package require sqlite3] +# to run. If this is not such an intepreter, see if there is a [testfixture] +# in the current directory. If so, run the command using it. If not, +# recommend that the user build one. +# +proc find_interpreter {} { + set interpreter [file tail [info nameofexec]] + set rc [catch { package require sqlite3 }] + if {$rc} { + if { [string match -nocase testfixture* $interpreter]==0 + && [file executable ./testfixture] + } { + puts "Failed to find tcl package sqlite3. Restarting with ./testfixture.." + set status [catch { + exec ./testfixture [info script] {*}$::argv >@ stdout + } msg] + exit $status + } + } + if {$rc} { + puts stderr "Failed to find tcl package sqlite3" + puts stderr "Run \"make testfixture\" and then try again..." + exit 1 + } +} +find_interpreter + +# Usually this script is run by [testfixture]. But it can also be run +# by a regular [tclsh]. For these cases, emulate the [clock_milliseconds] +# command. +if {[info commands clock_milliseconds]==""} { + proc clock_milliseconds {} { + clock milliseconds + } +} + #------------------------------------------------------------------------- # Usage: # @@ -74,16 +110,20 @@ proc guess_number_of_cores {} { if {[catch {number_of_cores} ret]} { set ret 4 - if {$::tcl_platform(os)=="Darwin"} { - set cmd "sysctl -n hw.logicalcpu" + if {$::tcl_platform(platform)=="windows"} { + catch { set ret $::env(NUMBER_OF_PROCESSORS) } } else { - set cmd "nproc" - } - catch { - set fd [open "|$cmd" r] - set ret [gets $fd] - close $fd - set ret [expr $ret] + if {$::tcl_platform(os)=="Darwin"} { + set cmd "sysctl -n hw.logicalcpu" + } else { + set cmd "nproc" + } + catch { + set fd [open "|$cmd" r] + set ret [gets $fd] + close $fd + set ret [expr $ret] + } } } return $ret @@ -152,10 +192,8 @@ set TRG(schema) { state TEXT CHECK( state IN ('', 'ready', 'running', 'done', 'failed') ), time INTEGER, -- Time in ms output TEXT, -- full output of test script - priority AS ((config='make') + ((config='build')*2) + (slow*4)), - jobtype AS ( - CASE WHEN config IN ('build', 'make') THEN config ELSE 'script' END - ), + priority INTEGER, + jobtype TEXT CHECK( jobtype IN ('script', 'build', 'make') ), PRIMARY KEY(build, config, filename) ); @@ -181,10 +219,11 @@ if {[llength $argv]==2 set script [file normalize [lindex $argv 1]] set ::argv [list] + set testdir [file dirname $argv0] + source $::testdir/tester.tcl + if {$permutation=="full"} { - set testdir [file dirname $argv0] - source $::testdir/tester.tcl unset -nocomplain ::G(isquick) reset_db @@ -241,6 +280,22 @@ if {([llength $argv]==2 || [llength $argv]==1) } #-------------------------------------------------------------------------- +#-------------------------------------------------------------------------- +# Check if this is the "script" command: +# +if {[string compare -nocase script [lindex $argv 0]]==0} { + if {[llength $argv]!=2 && !([llength $argv]==3&&[lindex $argv 1]=="-msvc")} { + usage + } + + set bMsvc [expr ([llength $argv]==3)] + set config [lindex $argv [expr [llength $argv]-1]] + + puts [trd_buildscript $config [file dirname $testdir] $bMsvc] + exit +} + + #-------------------------------------------------------------------------- # Check if this is the "status" command: # @@ -289,9 +344,13 @@ if {[llength $argv]==1 set cmdline [mydb one { SELECT value FROM config WHERE name='cmdline' }] set nJob [mydb one { SELECT value FROM config WHERE name='njob' }] - set tm [expr [clock_milliseconds] - [mydb one { - SELECT value FROM config WHERE name='start' - }]] + + set now [clock_milliseconds] + set tm [mydb one { + SELECT + COALESCE((SELECT value FROM config WHERE name='end'), $now) - + (SELECT value FROM config WHERE name='start') + }] set total 0 foreach s {"" ready running done failed} { set S($s) 0 } @@ -315,7 +374,6 @@ if {[llength $argv]==1 set srcdir [file dirname [file dirname $TRG(info_script)]] if {$S(running)>0} { puts "Running: " - set now [clock_milliseconds] mydb eval { SELECT build, config, filename, time FROM script WHERE state='running' ORDER BY time @@ -411,8 +469,6 @@ proc dirs_allocDir {} { return $iRet } -set testdir [file dirname $argv0] - # Check that directory $dir exists. If it does not, create it. If # it does, delete its contents. # @@ -449,7 +505,16 @@ proc testset_patternlist {patternlist} { set first [lindex $patternlist 0] - if {$first=="release"} { + if {$first=="mdevtest"} { + set patternlist [lrange $patternlist 1 end] + + foreach b {All-Debug All-O0} { + lappend testset [list $b build testfixture] + lappend testset [list $b make fuzztest] + testset_append testset $b veryquick $patternlist + } + + } elseif {$first=="release"} { set platform $::TRG(platform) set patternlist [lrange $patternlist 1 end] @@ -634,9 +699,20 @@ proc make_new_testset {} { set state "" } + set priority [expr {$slow*2}] + if {$c=="make"} { incr priority 3 } + if {$c=="build"} { incr priority 1 } + + if {$c=="make" || $c=="build"} { + set jobtype $c + } else { + set jobtype "script" + } + trdb eval { - INSERT INTO script(build, config, filename, slow, state) - VALUES ($b, $c, $s, $slow, $state) + INSERT INTO script + (build, config, filename, slow, state, priority, jobtype) + VALUES ($b, $c, $s, $slow, $state, $priority, $jobtype) } } } @@ -721,12 +797,7 @@ proc launch_another_job {iJob} { if {$b=="Zipvfs"} { set script [zipvfs_testrunner_script] } else { - set cmd [info nameofexec] - lappend cmd [file join $testdir releasetest_data.tcl] - lappend cmd trscript - if {$TRG(platform)=="win"} { lappend cmd -msvc } - lappend cmd $b $srcdir - set script [exec {*}$cmd] + set script [trd_buildscript $b $srcdir [expr {$TRG(platform)=="win"}]] } set fd [open [file join $builddir $TRG(make)] w] @@ -865,6 +936,8 @@ proc run_testset {} { one_line_report r_write_db { + set tm [clock_milliseconds] + trdb eval { REPLACE INTO config VALUES('end', $tm ); } set nErr [trdb one {SELECT count(*) FROM script WHERE state='failed'}] if {$nErr>0} { puts "$nErr failures:" diff --git a/test/testrunner_data.tcl b/test/testrunner_data.tcl index f74ee146ca..c75b7c9d66 100644 --- a/test/testrunner_data.tcl +++ b/test/testrunner_data.tcl @@ -5,6 +5,7 @@ namespace eval trd { variable tcltest variable extra variable all_configs + variable build # Tcl tests to run for various builds. @@ -35,6 +36,7 @@ namespace eval trd { set tcltest(win.Have-Not) veryquick set tcltest(win.Windows-Memdebug) veryquick set tcltest(win.Windows-Win32Heap) veryquick + set tcltest(win.Windows-Sanitize) veryquick set tcltest(win.Default) full # Extra [make xyz] tests that should be run for various builds. @@ -63,6 +65,7 @@ namespace eval trd { set extra(win.Stdcall) {fuzztest sourcetest} set extra(win.Windows-Memdebug) {fuzztest sourcetest} set extra(win.Windows-Win32Heap) {fuzztest sourcetest} + set extra(win.Windows-Sanitize) fuzztest set extra(win.Have-Not) {fuzztest sourcetest} # The following mirrors the set of test suites invoked by "all.test". @@ -75,6 +78,245 @@ namespace eval trd { inmemory_journal pcache0 pcache10 pcache50 pcache90 pcache100 prepare mmap } + + #----------------------------------------------------------------------- + # Start of build() definitions. + # + set build(Default) { + -O2 + --disable-amalgamation --disable-shared + --enable-session + -DSQLITE_ENABLE_RBU + } + + # These two are used by [testrunner.tcl mdevtest]. + # + set build(All-Debug) { + --enable-debug --enable-all + } + set build(All-O0) { + -O0 --enable-all + } + + set build(Sanitize) { + CC=clang -fsanitize=address,undefined + -DSQLITE_ENABLE_STAT4 + -DCONFIG_SLOWDOWN_FACTOR=5.0 + --enable-debug + --enable-all + } + set build(Stdcall) { + -DUSE_STDCALL=1 + -O2 + } + + # The "Have-Not" configuration sets all possible -UHAVE_feature options + # in order to verify that the code works even on platforms that lack + # these support services. + set build(Have-Not) { + -DHAVE_FDATASYNC=0 + -DHAVE_GMTIME_R=0 + -DHAVE_ISNAN=0 + -DHAVE_LOCALTIME_R=0 + -DHAVE_LOCALTIME_S=0 + -DHAVE_MALLOC_USABLE_SIZE=0 + -DHAVE_STRCHRNUL=0 + -DHAVE_USLEEP=0 + -DHAVE_UTIME=0 + } + set build(Unlock-Notify) { + -O2 + -DSQLITE_ENABLE_UNLOCK_NOTIFY + -DSQLITE_THREADSAFE + -DSQLITE_TCL_DEFAULT_FULLMUTEX=1 + } + set build(User-Auth) { + -O2 + -DSQLITE_USER_AUTHENTICATION=1 + } + set build(Secure-Delete) { + -O2 + -DSQLITE_SECURE_DELETE=1 + -DSQLITE_SOUNDEX=1 + } + set build(Update-Delete-Limit) { + -O2 + -DSQLITE_DEFAULT_FILE_FORMAT=4 + -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1 + -DSQLITE_ENABLE_STMT_SCANSTATUS + -DSQLITE_LIKE_DOESNT_MATCH_BLOBS + -DSQLITE_ENABLE_CURSOR_HINTS + } + set build(Check-Symbols) { + -DSQLITE_MEMDEBUG=1 + -DSQLITE_ENABLE_FTS3_PARENTHESIS=1 + -DSQLITE_ENABLE_FTS3=1 + -DSQLITE_ENABLE_RTREE=1 + -DSQLITE_ENABLE_MEMSYS5=1 + -DSQLITE_ENABLE_MEMSYS3=1 + -DSQLITE_ENABLE_COLUMN_METADATA=1 + -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1 + -DSQLITE_SECURE_DELETE=1 + -DSQLITE_SOUNDEX=1 + -DSQLITE_ENABLE_ATOMIC_WRITE=1 + -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 + -DSQLITE_ENABLE_OVERSIZE_CELL_CHECK=1 + -DSQLITE_ENABLE_STAT4 + -DSQLITE_ENABLE_STMT_SCANSTATUS + --enable-fts5 --enable-session + } + set build(Debug-One) { + --disable-shared + -O2 -funsigned-char + -DSQLITE_DEBUG=1 + -DSQLITE_MEMDEBUG=1 + -DSQLITE_MUTEX_NOOP=1 + -DSQLITE_TCL_DEFAULT_FULLMUTEX=1 + -DSQLITE_ENABLE_FTS3=1 + -DSQLITE_ENABLE_RTREE=1 + -DSQLITE_ENABLE_MEMSYS5=1 + -DSQLITE_ENABLE_COLUMN_METADATA=1 + -DSQLITE_ENABLE_STAT4 + -DSQLITE_ENABLE_HIDDEN_COLUMNS + -DSQLITE_MAX_ATTACHED=125 + -DSQLITE_MUTATION_TEST + --enable-fts5 + } + set build(Debug-Two) { + -DSQLITE_DEFAULT_MEMSTATUS=0 + -DSQLITE_MAX_EXPR_DEPTH=0 + --enable-debug + } + set build(Fast-One) { + -O6 + -DSQLITE_ENABLE_FTS4=1 + -DSQLITE_ENABLE_RTREE=1 + -DSQLITE_ENABLE_STAT4 + -DSQLITE_ENABLE_RBU + -DSQLITE_MAX_ATTACHED=125 + -DSQLITE_MAX_MMAP_SIZE=12884901888 + -DSQLITE_ENABLE_SORTER_MMAP=1 + -DLONGDOUBLE_TYPE=double + --enable-session + } + set build(Device-One) { + -O2 + -DSQLITE_DEBUG=1 + -DSQLITE_DEFAULT_AUTOVACUUM=1 + -DSQLITE_DEFAULT_CACHE_SIZE=64 + -DSQLITE_DEFAULT_PAGE_SIZE=1024 + -DSQLITE_DEFAULT_TEMP_CACHE_SIZE=32 + -DSQLITE_DISABLE_LFS=1 + -DSQLITE_ENABLE_ATOMIC_WRITE=1 + -DSQLITE_ENABLE_IOTRACE=1 + -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 + -DSQLITE_MAX_PAGE_SIZE=4096 + -DSQLITE_OMIT_LOAD_EXTENSION=1 + -DSQLITE_OMIT_PROGRESS_CALLBACK=1 + -DSQLITE_OMIT_VIRTUALTABLE=1 + -DSQLITE_ENABLE_HIDDEN_COLUMNS + -DSQLITE_TEMP_STORE=3 + } + set build(Device-Two) { + -DSQLITE_4_BYTE_ALIGNED_MALLOC=1 + -DSQLITE_DEFAULT_AUTOVACUUM=1 + -DSQLITE_DEFAULT_CACHE_SIZE=1000 + -DSQLITE_DEFAULT_LOCKING_MODE=0 + -DSQLITE_DEFAULT_PAGE_SIZE=1024 + -DSQLITE_DEFAULT_TEMP_CACHE_SIZE=1000 + -DSQLITE_DISABLE_LFS=1 + -DSQLITE_ENABLE_FTS3=1 + -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 + -DSQLITE_ENABLE_RTREE=1 + -DSQLITE_MAX_COMPOUND_SELECT=50 + -DSQLITE_MAX_PAGE_SIZE=32768 + -DSQLITE_OMIT_TRACE=1 + -DSQLITE_TEMP_STORE=3 + -DSQLITE_THREADSAFE=2 + --enable-fts5 --enable-session + } + set build(Locking-Style) { + -O2 + -DSQLITE_ENABLE_LOCKING_STYLE=1 + } + set build(Apple) { + -Os + -DHAVE_GMTIME_R=1 + -DHAVE_ISNAN=1 + -DHAVE_LOCALTIME_R=1 + -DHAVE_PREAD=1 + -DHAVE_PWRITE=1 + -DHAVE_UTIME=1 + -DSQLITE_DEFAULT_CACHE_SIZE=1000 + -DSQLITE_DEFAULT_CKPTFULLFSYNC=1 + -DSQLITE_DEFAULT_MEMSTATUS=1 + -DSQLITE_DEFAULT_PAGE_SIZE=1024 + -DSQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS=1 + -DSQLITE_ENABLE_API_ARMOR=1 + -DSQLITE_ENABLE_AUTO_PROFILE=1 + -DSQLITE_ENABLE_FLOCKTIMEOUT=1 + -DSQLITE_ENABLE_FTS3=1 + -DSQLITE_ENABLE_FTS3_PARENTHESIS=1 + -DSQLITE_ENABLE_FTS3_TOKENIZER=1 + -DSQLITE_ENABLE_PERSIST_WAL=1 + -DSQLITE_ENABLE_PURGEABLE_PCACHE=1 + -DSQLITE_ENABLE_RTREE=1 + -DSQLITE_ENABLE_SNAPSHOT=1 + -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1 + -DSQLITE_MAX_LENGTH=2147483645 + -DSQLITE_MAX_VARIABLE_NUMBER=500000 + -DSQLITE_NO_SYNC=1 + -DSQLITE_OMIT_AUTORESET=1 + -DSQLITE_OMIT_LOAD_EXTENSION=1 + -DSQLITE_PREFER_PROXY_LOCKING=1 + -DSQLITE_SERIES_CONSTRAINT_VERIFY=1 + -DSQLITE_THREADSAFE=2 + -DSQLITE_USE_URI=1 + -DSQLITE_WRITE_WALFRAME_PREBUFFERED=1 + -DUSE_GUARDED_FD=1 + -DUSE_PREAD=1 + --enable-fts5 + } + set build(Extra-Robustness) { + -DSQLITE_ENABLE_OVERSIZE_CELL_CHECK=1 + -DSQLITE_MAX_ATTACHED=62 + } + set build(Devkit) { + -DSQLITE_DEFAULT_FILE_FORMAT=4 + -DSQLITE_MAX_ATTACHED=30 + -DSQLITE_ENABLE_COLUMN_METADATA + -DSQLITE_ENABLE_FTS4 + -DSQLITE_ENABLE_FTS5 + -DSQLITE_ENABLE_FTS4_PARENTHESIS + -DSQLITE_DISABLE_FTS4_DEFERRED + -DSQLITE_ENABLE_RTREE + --enable-fts5 + } + set build(No-lookaside) { + -DSQLITE_TEST_REALLOC_STRESS=1 + -DSQLITE_OMIT_LOOKASIDE=1 + } + set build(Valgrind) { + -DSQLITE_ENABLE_STAT4 + -DSQLITE_ENABLE_FTS4 + -DSQLITE_ENABLE_RTREE + -DSQLITE_ENABLE_HIDDEN_COLUMNS + -DLONGDOUBLE_TYPE=double + -DCONFIG_SLOWDOWN_FACTOR=8.0 + } + + set build(Windows-Memdebug) { + MEMDEBUG=1 + DEBUG=3 + } + set build(Windows-Win32Heap) { + WIN32HEAP=1 + DEBUG=4 + } + set build(Windows-Sanitize) { + ASAN=1 + } + } @@ -84,6 +326,7 @@ proc trd_import {} { variable ::trd::tcltest variable ::trd::extra variable ::trd::all_configs + variable ::trd::build } } @@ -106,13 +349,13 @@ proc trd_builds {platform} { set ret } -proc trd_configs {platform build} { +proc trd_configs {platform bld} { trd_import set clist [list] - if {[info exists tcltest($platform.$build)]} { - set clist $tcltest($platform.$build) + if {[info exists tcltest($platform.$bld)]} { + set clist $tcltest($platform.$bld) if {$clist=="all"} { set clist $all_configs } elseif {$clist=="all_plus_autovacuum_crash"} { @@ -123,12 +366,12 @@ proc trd_configs {platform build} { set clist } -proc trd_extras {platform build} { +proc trd_extras {platform bld} { trd_import set elist [list] - if {[info exists extra($platform.$build)]} { - set elist $extra($platform.$build) + if {[info exists extra($platform.$bld)]} { + set elist $extra($platform.$bld) } set elist @@ -139,5 +382,180 @@ proc trd_all_configs {} { set all_configs } +proc trimscript {text} { + set text [string map {"\n " "\n"} [string trim $text]] +} + +proc make_sh_script {srcdir opts cflags makeOpts configOpts} { + + set tcldir [::tcl::pkgconfig get libdir,install] + set myopts "" + if {[info exists ::env(OPTS)]} { + append myopts "# From environment variable:\n" + append myopts "OPTS=$::env(OPTS)\n" + } + foreach o [lsort $opts] { + append myopts "OPTS=\"\$OPTS $o\"\n" + } + + return [trimscript [subst -nocommands { + set -e + if [ "\$#" -ne 1 ] ; then + echo "Usage: \$0 " + exit -1 + fi + + SRCDIR="$srcdir" + TCLDIR="$tcldir" + + if [ ! -f Makefile ] ; then + \$SRCDIR/configure --with-tcl=\$TCL $configOpts + fi + + $myopts + CFLAGS="$cflags" + + make \$1 "CFLAGS=\$CFLAGS" "OPTS=\$OPTS" $makeOpts + }]] +} + +# Generate the text of a *.bat script. +# +proc make_bat_file {srcdir opts cflags makeOpts} { + set srcdir [file nativename [file normalize $srcdir]] + + return [trimscript [subst -nocommands { + set TARGET=%1 + set TMP=%CD% + nmake /f $srcdir\\Makefile.msc TOP="$srcdir" %TARGET% "CCOPTS=$cflags" "OPTS=$opts" $makeOpts + }]] +} + + +# Generate the text of a shell script. +# +proc make_script {cfg srcdir bMsvc} { + set opts [list] ;# OPTS value + set cflags [expr {$bMsvc ? "-Zi" : "-g"}] ;# CFLAGS value + set makeOpts [list] ;# Extra args for [make] + set configOpts [list] ;# Extra args for [configure] + + # Define either SQLITE_OS_WIN or SQLITE_OS_UNIX, as appropriate. + if {$::tcl_platform(platform)=="windows"} { + lappend opts -DSQLITE_OS_WIN=1 + } else { + lappend opts -DSQLITE_OS_UNIX=1 + } + + # Unless the configuration specifies -DHAVE_USLEEP=0, set -DHAVE_USLEEP=1. + # + if {[lsearch $cfg "-DHAVE_USLEEP=0"]<0} { + lappend cfg -DHAVE_USLEEP=1 + } + + # Loop through the parameters of the nominated configuration, updating + # $opts, $cflags, $makeOpts and $configOpts along the way. Rules are as + # follows: + # + # 1. If the parameter begins with "-D", add it to $opts. + # + # 2. If the parameter begins with "--" add it to $configOpts. Unless + # this command is preparing a script for MSVC - then add an + # equivalent to $makeOpts or $opts. + # + # 3. If the parameter begins with "-" add it to $cflags. If in MSVC + # mode and the parameter is an -O option, instead add + # an OPTIMIZATIONS= switch to $makeOpts. + # + # 4. If none of the above apply, add the parameter to $makeOpts + # + foreach param $cfg { + + if {[string range $param 0 1]=="-D"} { + lappend opts $param + continue + } + + if {[string range $param 0 1]=="--"} { + if {$bMsvc==0} { + lappend configOpts $param + } else { + + switch -- $param { + --disable-amalgamation { + lappend makeOpts USE_AMALGAMATION=0 + } + --disable-shared { + lappend makeOpts USE_CRT_DLL=0 DYNAMIC_SHELL=0 + } + --enable-fts5 { + lappend opts -DSQLITE_ENABLE_FTS5 + } + --enable-shared { + lappend makeOpts USE_CRT_DLL=1 DYNAMIC_SHELL=1 + } + --enable-session { + lappend opts -DSQLITE_ENABLE_PREUPDATE_HOOK + lappend opts -DSQLITE_ENABLE_SESSION + } + --enable-all { + } + --enable-debug { + # lappend makeOpts OPTIMIZATIONS=0 + lappend opts -DSQLITE_DEBUG + } + default { + error "Cannot translate $param for MSVC" + } + + } + } + + continue + } + + if {[string range $param 0 0]=="-"} { + if {$bMsvc && [regexp -- {^-O(\d+)$} $param -> level]} { + lappend makeOpts OPTIMIZATIONS=$level + } else { + lappend cflags $param + } + continue + } + + lappend makeOpts $param + } + + if {$bMsvc==0} { + set zRet [make_sh_script $srcdir $opts $cflags $makeOpts $configOpts] + } else { + set zRet [make_bat_file $srcdir $opts $cflags $makeOpts] + } +} + +# Usage: +# +# trd_buildscript CONFIG SRCDIR MSVC +# +# This command returns the full text of a script (either a shell script or +# an ms-dos bat file) that may be used to build SQLite source code according +# to a nominated configuration. +# +# Parameter CONFIG must be a configuration defined above in the ::trd::build +# array. SRCDIR is the root directory of an SQLite source tree (the parent +# directory of that containing this script). MSVC is a boolean - true to +# use the MSVC compiler, false otherwise. +# +proc trd_buildscript {config srcdir bMsvc} { + trd_import + + # Ensure that the named configuration exists. + if {![info exists build($config)]} { + error "No such build config: $config" + } + + # Generate and return the script. + return [make_script $build($config) $srcdir $bMsvc] +} diff --git a/test/upsert1.test b/test/upsert1.test index a321d6171d..7818311330 100644 --- a/test/upsert1.test +++ b/test/upsert1.test @@ -255,4 +255,17 @@ do_execsql_test upsert1-1100 { SELECT * FROM t1; } {1 22} +# 2023-08-17 dbsqlfuzz 9983e2c77634a8ccf33b5c91fa9982599de5f9e9 +# Bound parameters in the ON CONFLICT clause of an UPSERT. +# +reset_db +do_execsql_test upsert1-1200 { + CREATE TABLE t1(a INT, b INT); + CREATE UNIQUE INDEX t1x ON t1(b+3); +} +sqlite3_db_config db ENABLE_QPSG 1 +do_catchsql_test upsert1-1210 { + INSERT INTO t1(a,b) VALUES(1,2) ON CONFLICT(b+?1) DO NOTHING; +} {1 {ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint}} + finish_test diff --git a/tool/src-verify.c b/tool/src-verify.c index b73a89272a..7629046564 100644 --- a/tool/src-verify.c +++ b/tool/src-verify.c @@ -47,8 +47,12 @@ # include #else # include -# define R_OK 04 -# define access(f,m) _access((f),(m)) +# ifndef R_OK +# define R_OK 04 +# endif +# ifndef access +# define access(f,m) _access((f),(m)) +# endif #endif typedef unsigned long long int u64;