diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile new file mode 100644 index 0000000000..36ef42d31a --- /dev/null +++ b/ext/jni/GNUmakefile @@ -0,0 +1,346 @@ +# Quick-and-dirty makefile to bootstrap the sqlite3-jni project. This +# build assumes a Linux-like system. +default: all + +JAVA_HOME ?= $(HOME)/jdk/current +# e.g. /usr/lib/jvm/default-javajava-19-openjdk-amd64 +JDK_HOME ?= $(JAVA_HOME) +# ^^^ JDK_HOME is not as widely used as JAVA_HOME +bin.javac := $(JDK_HOME)/bin/javac +bin.java := $(JDK_HOME)/bin/java +bin.jar := $(JDK_HOME)/bin/jar +ifeq (,$(wildcard $(JDK_HOME))) +$(error set JDK_HOME to the top-most dir of your JDK installation.) +endif +MAKEFILE := $(lastword $(MAKEFILE_LIST)) +$(MAKEFILE): + +package.jar := sqlite3-jni.jar + +dir.top := ../.. +dir.tool := ../../tool +dir.jni := $(patsubst %/,%,$(dir $(MAKEFILE))) + +dir.src := $(dir.jni)/src +dir.src.c := $(dir.src)/c +dir.bld := $(dir.jni)/bld +dir.bld.c := $(dir.bld) +dir.src.jni := $(dir.src)/org/sqlite/jni +dir.src.jni.tester := $(dir.src.jni)/tester +$(dir.bld.c): + mkdir -p $@ + +classpath := $(dir.src) +CLEAN_FILES := $(package.jar) +DISTCLEAN_FILES := $(dir.jni)/*~ $(dir.src.c)/*~ $(dir.src.jni)/*~ + +sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h +.NOTPARALLEL: $(sqlite3-jni.h) +SQLite3Jni.java := src/org/sqlite/jni/SQLite3Jni.java +SQLTester.java := src/org/sqlite/jni/tester/SQLTester.java +SQLite3Jni.class := $(SQLite3Jni.java:.java=.class) +SQLTester.class := $(SQLTester.java:.java=.class) + +######################################################################## +# The future of FTS5 customization in this API is as yet unclear. +# It would be a real doozy to bind to JNI. +enable.fts5 ?= 1 +# If enable.tester is 0, the org/sqlite/jni/tester/* bits are elided. +enable.tester ?= 1 + +# bin.version-info = binary to output various sqlite3 version info +# building the distribution zip file. +bin.version-info := $(dir.top)/version-info +.NOTPARALLEL: $(bin.version-info) +$(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile + $(MAKE) -C $(dir.top) version-info + +# Be explicit about which Java files to compile so that we can work on +# in-progress files without requiring them to be in a compilable statae. +JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/%,\ + BusyHandler.java \ + Collation.java \ + CollationNeeded.java \ + CommitHook.java \ + NativePointerHolder.java \ + OutputPointer.java \ + ProgressHandler.java \ + ResultCode.java \ + RollbackHook.java \ + SQLFunction.java \ + sqlite3_context.java \ + sqlite3.java \ + SQLite3Jni.java \ + sqlite3_stmt.java \ + sqlite3_value.java \ + Tester1.java \ + Tracer.java \ + UpdateHook.java \ + ValueHolder.java \ +) +ifeq (1,$(enable.fts5)) + JAVA_FILES.main += $(patsubst %,$(dir.src.jni)/%,\ + fts5_api.java \ + fts5_extension_function.java \ + fts5_tokenizer.java \ + Fts5.java \ + Fts5Context.java \ + Fts5ExtensionApi.java \ + Fts5Function.java \ + Fts5PhraseIter.java \ + Fts5Tokenizer.java \ + TesterFts5.java \ + ) +endif +JAVA_FILES.tester := $(dir.src.jni.tester)/SQLTester.java + +CLASS_FILES.main := $(JAVA_FILES.main:.java=.class) +CLASS_FILES.tester := $(JAVA_FILES.tester:.java=.class) + +JAVA_FILES += $(JAVA_FILES.main) +ifeq (1,$(enable.tester)) + JAVA_FILES += $(JAVA_FILES.tester) +endif + +CLASS_FILES := +define DOTCLASS_DEPS +$(1).class: $(1).java $(MAKEFILE) +all: $(1).class +CLASS_FILES += $(1).class +endef +$(foreach B,$(basename $(JAVA_FILES)),$(eval $(call DOTCLASS_DEPS,$(B)))) +$(CLASS_FILES.tester): $(CLASS_FILES.main) +javac.flags ?= -Xlint:unchecked -Xlint:deprecation +java.flags ?= +jnicheck ?= 1 +ifeq (1,$(jnicheck)) + java.flags += -Xcheck:jni +endif +$(SQLite3Jni.class): $(JAVA_FILES) + $(bin.javac) $(javac.flags) -h $(dir.bld.c) -cp $(classpath) $(JAVA_FILES) +all: $(SQLite3Jni.class) +#.PHONY: classfiles + +######################################################################## +# Set up sqlite3.c and sqlite3.h... +# +# To build with SEE (https://sqlite.org/see), either put sqlite3-see.c +# in the top of this build tree or pass +# sqlite3.c=PATH_TO_sqlite3-see.c to the build. Note that only +# encryption modules with no 3rd-party dependencies will currently +# work here: AES256-OFB, AES128-OFB, and AES128-CCM. Not +# coincidentally, those 3 modules are included in the sqlite3-see.c +# bundle. +# +# A custom sqlite3.c must not have any spaces in its name. +# $(sqlite3.canonical.c) must point to the sqlite3.c in +# the sqlite3 canonical source tree, as that source file +# is required for certain utility and test code. +sqlite3.canonical.c := $(firstword $(wildcard $(dir.src.c)/sqlite3.c) $(dir.top)/sqlite3.c) +sqlite3.canonical.h := $(firstword $(wildcard $(dir.src.c)/sqlite3.h) $(dir.top)/sqlite3.h) +sqlite3.c := $(sqlite3.canonical.c) +sqlite3.h := $(sqlite3.canonical.h) +#ifeq (,$(shell grep sqlite3_activate_see $(sqlite3.c) 2>/dev/null)) +# SQLITE_C_IS_SEE := 0 +#else +# SQLITE_C_IS_SEE := 1 +# $(info This is an SEE build.) +#endif + +.NOTPARALLEL: $(sqlite3.h) +$(sqlite3.h): + $(MAKE) -C $(dir.top) sqlite3.c +$(sqlite3.c): $(sqlite3.h) + +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..80486d4a07 --- /dev/null +++ b/ext/jni/README.md @@ -0,0 +1,233 @@ +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 +======================================================================== + +- Lots of APIs left to bind. Most "day-to-day" functionality is already + in place and is believed to work well. + + +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..9f9d13002a --- /dev/null +++ b/ext/jni/jar-dist.make @@ -0,0 +1,59 @@ +#!/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 \ + -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(). + +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..bf2d5527b0 --- /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_00024Int32_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..ffe0b83846 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -0,0 +1,1164 @@ +/* +** 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 ); + outln(cur32.value," ",high32.value); + 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/manifest b/manifest index 267fd91960..f112606117 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Update\swasm's\spush-testing\srule\sto\sexclude\sfiles\swhich\swere\srecently\sremoved\sfrom\sthe\sbuild. -D 2023-08-12T21:08:41.355 +C Merge\sthe\sJava\sNative\sInterface\s(JNI)\sbinding\sinto\strunk. +D 2023-08-12T21:39:18.053 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,6 +231,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 435485ff2005c4bcdea808f5efe6d4ee66a00430c2499dcc4927b20378486bea +F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb +F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 +F ext/jni/src/c/sqlite3-jni.c bea6b8691a5fa3a8626a771757bb261208d3c5fc6598266d3b0ee23d88e35632 +F ext/jni/src/c/sqlite3-jni.h c5f941b057a24ee62942e6e1bf5a7fd527e5004d20d9638e84a9382813c3cf2a +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 07c14a90427529ceba54b5e8344ca03602f5789dc53c4163ce22f92d8c577a11 +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 @@ -2051,8 +2091,9 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 01f49448cd0cfe3af499aedfe887b7b0be4f1ab09cd5a16119ddafb32b107708 -R 2ecaec09aa6a0d3ef6dc669f83d54223 +P 0a6930a7ff8f8c6ca244d1d654532f3d2a02d77ef67c6cae0c53092743d59ea6 1ba7754045a009d9c94b23ac76b9bb8d9c9cb24d42dcdf1203ee75ac85765d3e +R 71919983f1228c04d42d3555e47fba1b +T +closed 1ba7754045a009d9c94b23ac76b9bb8d9c9cb24d42dcdf1203ee75ac85765d3e Closed\sby\sintegrate-merge. U stephan -Z 90975d5312aef561538895b18087fc5f +Z 06dd3519c67c3e383d12eeb6b6fbb0d1 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 3606b738a2..1c747eb83a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -0a6930a7ff8f8c6ca244d1d654532f3d2a02d77ef67c6cae0c53092743d59ea6 \ No newline at end of file +48b13edcec6935bf125b265b41a3e6f7b2407afff89d5b4daa2939e3c5679ca0 \ No newline at end of file