summaryrefslogtreecommitdiffstats
path: root/ext/jni
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 14:07:11 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 14:07:11 +0000
commit63847496f14c813a5d80efd5b7de0f1294ffe1e3 (patch)
tree01c7571c7c762ceee70638549a99834fdd7c411b /ext/jni
parentInitial commit. (diff)
downloadsqlite3-63847496f14c813a5d80efd5b7de0f1294ffe1e3.tar.xz
sqlite3-63847496f14c813a5d80efd5b7de0f1294ffe1e3.zip
Adding upstream version 3.45.1.upstream/3.45.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ext/jni')
-rw-r--r--ext/jni/GNUmakefile505
-rw-r--r--ext/jni/README.md316
-rw-r--r--ext/jni/jar-dist.make60
-rw-r--r--ext/jni/src/c/sqlite3-jni.c6343
-rw-r--r--ext/jni/src/c/sqlite3-jni.h2461
-rw-r--r--ext/jni/src/org/sqlite/jni/annotation/Experimental.java30
-rw-r--r--ext/jni/src/org/sqlite/jni/annotation/NotNull.java71
-rw-r--r--ext/jni/src/org/sqlite/jni/annotation/Nullable.java33
-rw-r--r--ext/jni/src/org/sqlite/jni/annotation/package-info.java17
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java34
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java138
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java29
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java40
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java26
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/CApi.java2897
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java45
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/CollationCallback.java35
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java29
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java26
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java25
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/ConfigSqlLogCallback.java25
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java46
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/OutputPointer.java253
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java81
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java27
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java27
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/ResultCode.java155
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java26
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/SQLFunction.java36
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/SQLTester.java1433
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java33
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java35
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/Tester1.java2194
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java50
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java26
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/ValueHolder.java27
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/WindowFunction.java39
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java37
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/package-info.java89
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/sqlite3.java43
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java31
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java30
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java79
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java30
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java19
-rw-r--r--ext/jni/src/org/sqlite/jni/fts5/Fts5.java32
-rw-r--r--ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java24
-rw-r--r--ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java97
-rw-r--r--ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java25
-rw-r--r--ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java31
-rw-r--r--ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java842
-rw-r--r--ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java22
-rw-r--r--ext/jni/src/org/sqlite/jni/fts5/fts5_api.java76
-rw-r--r--ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java47
-rw-r--r--ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java49
-rw-r--r--ext/jni/src/org/sqlite/jni/test-script-interpreter.md269
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java144
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java37
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java318
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java1991
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java85
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java1213
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java25
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java42
-rw-r--r--ext/jni/src/tests/000-000-sanity.test53
-rw-r--r--ext/jni/src/tests/000-001-ignored.test9
-rw-r--r--ext/jni/src/tests/900-001-fts.test12
67 files changed, 23474 insertions, 0 deletions
diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile
new file mode 100644
index 0000000..26a38da
--- /dev/null
+++ b/ext/jni/GNUmakefile
@@ -0,0 +1,505 @@
+# Quick-and-dirty makefile to bootstrap the sqlite3-jni project. This
+# build assumes a Linux-like system.
+default: all
+
+JAVA_HOME ?= $(HOME)/jdk/current
+# e.g. /usr/lib/jvm/default-javajava-19-openjdk-amd64
+JDK_HOME ?= $(JAVA_HOME)
+# ^^^ JDK_HOME is not as widely used as JAVA_HOME
+bin.jar := $(JDK_HOME)/bin/jar
+bin.java := $(JDK_HOME)/bin/java
+bin.javac := $(JDK_HOME)/bin/javac
+bin.javadoc := $(JDK_HOME)/bin/javadoc
+ifeq (,$(wildcard $(JDK_HOME)))
+$(error set JDK_HOME to the top-most dir of your JDK installation.)
+endif
+MAKEFILE := $(lastword $(MAKEFILE_LIST))
+$(MAKEFILE):
+
+package.jar := sqlite3-jni.jar
+
+dir.top := ../..
+dir.tool := ../../tool
+dir.jni := $(patsubst %/,%,$(dir $(MAKEFILE)))
+dir.src := $(dir.jni)/src
+dir.src.c := $(dir.src)/c
+dir.bld := $(dir.jni)/bld
+dir.bld.c := $(dir.bld)
+dir.src.jni := $(dir.src)/org/sqlite/jni
+dir.src.capi := $(dir.src.jni)/capi
+dir.src.fts5 := $(dir.src.jni)/fts5
+dir.tests := $(dir.src)/tests
+mkdir ?= mkdir -p
+$(dir.bld.c):
+ $(mkdir) $@
+
+javac.flags ?= -Xlint:unchecked -Xlint:deprecation
+java.flags ?=
+javac.flags += -encoding utf8
+# -------------^^^^^^^^^^^^^^ required for Windows builds
+jnicheck ?= 1
+ifeq (1,$(jnicheck))
+ java.flags += -Xcheck:jni
+endif
+
+classpath := $(dir.src)
+CLEAN_FILES := $(package.jar)
+DISTCLEAN_FILES := $(dir.jni)/*~ $(dir.src.c)/*~ $(dir.src.jni)/*~
+
+sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h
+.NOTPARALLEL: $(sqlite3-jni.h)
+CApi.java := $(dir.src.capi)/CApi.java
+SQLTester.java := $(dir.src.capi)/SQLTester.java
+CApi.class := $(CApi.java:.java=.class)
+SQLTester.class := $(SQLTester.java:.java=.class)
+
+########################################################################
+# The future of FTS5 customization in this API is as yet unclear.
+# The pieces are all in place, and are all thin proxies so not much
+# complexity, but some semantic changes were required in porting
+# which are largely untested.
+#
+# Reminder: this flag influences the contents of $(sqlite3-jni.h),
+# which is checked in. Please do not check in changes to that file in
+# which the fts5 APIs have been stripped unless that feature is
+# intended to be stripped for good.
+enable.fts5 ?= 1
+
+ifeq (,$(wildcard $(dir.tests)/*))
+ enable.tester := 0
+else
+ enable.tester := 1
+endif
+
+# bin.version-info = binary to output various sqlite3 version info
+# building the distribution zip file.
+bin.version-info := $(dir.top)/version-info
+.NOTPARALLEL: $(bin.version-info)
+$(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile
+ $(MAKE) -C $(dir.top) version-info
+
+# Be explicit about which Java files to compile so that we can work on
+# in-progress files without requiring them to be in a compilable statae.
+JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/annotation/%,\
+ Experimental.java \
+ NotNull.java \
+ Nullable.java \
+) $(patsubst %,$(dir.src.capi)/%,\
+ AbstractCollationCallback.java \
+ AggregateFunction.java \
+ AuthorizerCallback.java \
+ AutoExtensionCallback.java \
+ BusyHandlerCallback.java \
+ CollationCallback.java \
+ CollationNeededCallback.java \
+ CommitHookCallback.java \
+ ConfigLogCallback.java \
+ ConfigSqlLogCallback.java \
+ NativePointerHolder.java \
+ OutputPointer.java \
+ PrepareMultiCallback.java \
+ PreupdateHookCallback.java \
+ ProgressHandlerCallback.java \
+ ResultCode.java \
+ RollbackHookCallback.java \
+ ScalarFunction.java \
+ SQLFunction.java \
+ CallbackProxy.java \
+ CApi.java \
+ TableColumnMetadata.java \
+ TraceV2Callback.java \
+ UpdateHookCallback.java \
+ ValueHolder.java \
+ WindowFunction.java \
+ XDestroyCallback.java \
+ sqlite3.java \
+ sqlite3_blob.java \
+ sqlite3_context.java \
+ sqlite3_stmt.java \
+ sqlite3_value.java \
+) $(patsubst %,$(dir.src.jni)/wrapper1/%,\
+ AggregateFunction.java \
+ ScalarFunction.java \
+ SqlFunction.java \
+ Sqlite.java \
+ SqliteException.java \
+ ValueHolder.java \
+ WindowFunction.java \
+)
+
+JAVA_FILES.unittest := $(patsubst %,$(dir.src.jni)/%,\
+ capi/Tester1.java \
+ wrapper1/Tester2.java \
+)
+ifeq (1,$(enable.fts5))
+ JAVA_FILES.unittest += $(patsubst %,$(dir.src.fts5)/%,\
+ TesterFts5.java \
+ )
+ JAVA_FILES.main += $(patsubst %,$(dir.src.fts5)/%,\
+ fts5_api.java \
+ fts5_extension_function.java \
+ fts5_tokenizer.java \
+ Fts5.java \
+ Fts5Context.java \
+ Fts5ExtensionApi.java \
+ Fts5PhraseIter.java \
+ Fts5Tokenizer.java \
+ XTokenizeCallback.java \
+ )
+endif
+JAVA_FILES.tester := $(SQLTester.java)
+JAVA_FILES.package.info := \
+ $(dir.src.jni)/package-info.java \
+ $(dir.src.jni)/annotation/package-info.java
+
+CLASS_FILES.main := $(JAVA_FILES.main:.java=.class)
+CLASS_FILES.unittest := $(JAVA_FILES.unittest:.java=.class)
+CLASS_FILES.tester := $(JAVA_FILES.tester:.java=.class)
+
+JAVA_FILES += $(JAVA_FILES.main) $(JAVA_FILES.unittest)
+ifeq (1,$(enable.tester))
+ JAVA_FILES += $(JAVA_FILES.tester)
+endif
+
+CLASS_FILES :=
+define CLASSFILE_DEPS
+all: $(1).class
+$(1).class: $(1).java
+CLASS_FILES += $(1).class
+endef
+$(foreach B,$(basename \
+ $(JAVA_FILES.main) $(JAVA_FILES.unittest) $(JAVA_FILES.tester)),\
+ $(eval $(call CLASSFILE_DEPS,$(B))))
+$(CLASS_FILES): $(MAKEFILE)
+ $(bin.javac) $(javac.flags) -h $(dir.bld.c) -cp $(classpath) $(JAVA_FILES)
+
+#.PHONY: classfiles
+
+########################################################################
+# Set up sqlite3.c and sqlite3.h...
+#
+# To build with SEE (https://sqlite.org/see), either put sqlite3-see.c
+# in the top of this build tree or pass
+# sqlite3.c=PATH_TO_sqlite3-see.c to the build. Note that only
+# encryption modules with no 3rd-party dependencies will currently
+# work here: AES256-OFB, AES128-OFB, and AES128-CCM. Not
+# coincidentally, those 3 modules are included in the sqlite3-see.c
+# bundle.
+#
+# A custom sqlite3.c must not have any spaces in its name.
+# $(sqlite3.canonical.c) must point to the sqlite3.c in
+# the sqlite3 canonical source tree, as that source file
+# is required for certain utility and test code.
+sqlite3.canonical.c := $(firstword $(wildcard $(dir.src.c)/sqlite3.c) $(dir.top)/sqlite3.c)
+sqlite3.canonical.h := $(firstword $(wildcard $(dir.src.c)/sqlite3.h) $(dir.top)/sqlite3.h)
+sqlite3.c := $(sqlite3.canonical.c)
+sqlite3.h := $(sqlite3.canonical.h)
+#ifeq (,$(shell grep sqlite3_activate_see $(sqlite3.c) 2>/dev/null))
+# SQLITE_C_IS_SEE := 0
+#else
+# SQLITE_C_IS_SEE := 1
+# $(info This is an SEE build.)
+#endif
+
+.NOTPARALLEL: $(sqlite3.h)
+$(sqlite3.h):
+ $(MAKE) -C $(dir.top) sqlite3.c
+$(sqlite3.c): $(sqlite3.h)
+
+opt.threadsafe ?= 1
+opt.fatal-oom ?= 1
+opt.debug ?= 1
+opt.metrics ?= 1
+SQLITE_OPT = \
+ -DSQLITE_THREADSAFE=$(opt.threadsafe) \
+ -DSQLITE_TEMP_STORE=2 \
+ -DSQLITE_USE_URI=1 \
+ -DSQLITE_OMIT_LOAD_EXTENSION \
+ -DSQLITE_OMIT_DEPRECATED \
+ -DSQLITE_OMIT_SHARED_CACHE \
+ -DSQLITE_C=$(sqlite3.c) \
+ -DSQLITE_JNI_FATAL_OOM=$(opt.fatal-oom) \
+ -DSQLITE_JNI_ENABLE_METRICS=$(opt.metrics)
+
+opt.extras ?= 1
+ifeq (1,$(opt.extras))
+SQLITE_OPT += -DSQLITE_ENABLE_RTREE \
+ -DSQLITE_ENABLE_EXPLAIN_COMMENTS \
+ -DSQLITE_ENABLE_STMTVTAB \
+ -DSQLITE_ENABLE_DBPAGE_VTAB \
+ -DSQLITE_ENABLE_DBSTAT_VTAB \
+ -DSQLITE_ENABLE_BYTECODE_VTAB \
+ -DSQLITE_ENABLE_OFFSET_SQL_FUNC \
+ -DSQLITE_ENABLE_PREUPDATE_HOOK \
+ -DSQLITE_ENABLE_NORMALIZE \
+ -DSQLITE_ENABLE_SQLLOG \
+ -DSQLITE_ENABLE_COLUMN_METADATA
+endif
+
+ifeq (1,$(opt.debug))
+ SQLITE_OPT += -DSQLITE_DEBUG -g -DDEBUG -UNDEBUG
+else
+ SQLITE_OPT += -Os
+endif
+
+ifeq (1,$(enable.fts5))
+ SQLITE_OPT += -DSQLITE_ENABLE_FTS5
+endif
+
+sqlite3-jni.c := $(dir.src.c)/sqlite3-jni.c
+sqlite3-jni.o := $(dir.bld.c)/sqlite3-jni.o
+sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h
+package.dll := $(dir.bld.c)/libsqlite3-jni.so
+# All javac-generated .h files must be listed in $(sqlite3-jni.h.in):
+sqlite3-jni.h.in :=
+# $(java.with.jni) lists all Java files which contain JNI decls:
+java.with.jni :=
+define ADD_JNI_H
+sqlite3-jni.h.in += $$(dir.bld.c)/org_sqlite_jni$(3)_$(2).h
+java.with.jni += $(1)/$(2).java
+$$(dir.bld.c)/org_sqlite_jni$(3)_$(2).h: $(1)/$(2).java
+endef
+# Invoke ADD_JNI_H once for each Java file which includes JNI
+# declarations:
+$(eval $(call ADD_JNI_H,$(dir.src.capi),CApi,_capi))
+$(eval $(call ADD_JNI_H,$(dir.src.capi),SQLTester,_capi))
+ifeq (1,$(enable.fts5))
+ $(eval $(call ADD_JNI_H,$(dir.src.fts5),Fts5ExtensionApi,_fts5))
+ $(eval $(call ADD_JNI_H,$(dir.src.fts5),fts5_api,_fts5))
+ $(eval $(call ADD_JNI_H,$(dir.src.fts5),fts5_tokenizer,_fts5))
+endif
+$(sqlite3-jni.h.in): $(dir.bld.c)
+
+#package.dll.cfiles :=
+package.dll.cflags = \
+ -std=c99 \
+ -fPIC \
+ -I. \
+ -I$(dir $(sqlite3.h)) \
+ -I$(dir.src.c) \
+ -I$(JDK_HOME)/include \
+ $(patsubst %,-I%,$(patsubst %.h,,$(wildcard $(JDK_HOME)/include/*))) \
+ -Wall
+# The gross $(patsubst...) above is to include the platform-specific
+# subdir which lives under $(JDK_HOME)/include and is a required
+# include path for client-level code.
+#
+# Using (-Wall -Wextra) triggers an untennable number of
+# gcc warnings from sqlite3.c for mundane things like
+# unused parameters.
+########################################################################
+ifeq (1,$(enable.tester))
+ package.dll.cflags += -DSQLITE_JNI_ENABLE_SQLTester
+endif
+
+$(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE)
+ @cat $(sqlite3-jni.h.in) > $@.tmp
+ @if cmp $@ $@.tmp >/dev/null; then \
+ rm -f $@.tmp; \
+ echo "$@ not modified"; \
+ else \
+ mv $@.tmp $@; \
+ echo "Updated $@"; \
+ fi
+ @if [ x1 != x$(enable.fts5) ]; then \
+ echo "*** REMINDER:"; \
+ echo "*** enable.fts5=0, so please do not check in changes to $@."; \
+ fi
+
+$(package.dll): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h)
+$(package.dll): $(sqlite3-jni.c) $(MAKEFILE)
+ $(CC) $(package.dll.cflags) $(SQLITE_OPT) \
+ $(sqlite3-jni.c) -shared -o $@
+all: $(package.dll)
+
+.PHONY: test test-one
+Tester1.flags ?=
+Tester2.flags ?=
+test.flags.jvm = -ea -Djava.library.path=$(dir.bld.c) \
+ $(java.flags) -cp $(classpath)
+test.deps := $(CLASS_FILES) $(package.dll)
+test-one: $(test.deps)
+ $(bin.java) $(test.flags.jvm) org.sqlite.jni.capi.Tester1 $(Tester1.flags)
+ $(bin.java) $(test.flags.jvm) org.sqlite.jni.wrapper1.Tester2 $(Tester2.flags)
+test-sqllog: $(test.deps)
+ @echo "Testing with -sqllog..."
+ $(bin.java) $(test.flags.jvm) org.sqlite.jni.capi.Tester1 $(Tester1.flags) -sqllog
+test-mt: $(test.deps)
+ @echo "Testing in multi-threaded mode:";
+ $(bin.java) $(test.flags.jvm) org.sqlite.jni.capi.Tester1 \
+ -t 7 -r 50 -shuffle $(Tester1.flags)
+ $(bin.java) $(test.flags.jvm) org.sqlite.jni.wrapper1.Tester2 \
+ -t 7 -r 50 -shuffle $(Tester2.flags)
+
+test: test-one test-mt
+tests: test test-sqllog
+
+tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test))
+tester.flags ?= # --verbose
+.PHONY: tester tester-local tester-ext
+ifeq (1,$(enable.tester))
+tester-local: $(CLASS_FILES.tester) $(package.dll)
+ $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
+ $(java.flags) -cp $(classpath) \
+ org.sqlite.jni.capi.SQLTester $(tester.flags) $(tester.scripts)
+tester: tester-local
+else
+tester:
+ @echo "SQLTester support is disabled."
+endif
+
+tester.extdir.default := $(dir.tests)/ext
+tester.extdir ?= $(tester.extdir.default)
+tester.extern-scripts := $(wildcard $(tester.extdir)/*.test)
+ifneq (,$(tester.extern-scripts))
+tester-ext:
+ $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
+ $(java.flags) -cp $(classpath) \
+ org.sqlite.jni.capi.SQLTester $(tester.flags) $(tester.extern-scripts)
+else
+tester-ext:
+ @echo "******************************************************"; \
+ echo "*** Include the out-of-tree test suite in the 'tester'"; \
+ echo "*** target by either symlinking its directory to"; \
+ echo "*** $(tester.extdir.default) or passing it to make"; \
+ echo "*** as tester.extdir=/path/to/that/dir."; \
+ echo "******************************************************";
+endif
+
+tester-ext: tester-local
+tester: tester-ext
+tests: tester
+########################################################################
+# Build each SQLITE_THREADMODE variant and run all tests against them.
+multitest: clean
+define MULTIOPT
+multitest: multitest-$(1)
+multitest-$(1):
+ $$(MAKE) opt.debug=$$(opt.debug) $(patsubst %,opt.%,$(2)) \
+ tests clean enable.fts5=1
+endef
+
+$(eval $(call MULTIOPT,01,threadsafe=0 oom=1))
+$(eval $(call MULTIOPT,00,threadsafe=0 oom=0))
+$(eval $(call MULTIOPT,11,threadsafe=1 oom=1))
+$(eval $(call MULTIOPT,10,threadsafe=1 oom=0))
+$(eval $(call MULTIOPT,21,threadsafe=2 oom=1))
+$(eval $(call MULTIOPT,20,threadsafe=2 oom=0))
+
+
+########################################################################
+# jar bundle...
+package.jar.in := $(abspath $(dir.src)/jar.in)
+CLEAN_FILES += $(package.jar.in)
+JAVA_FILES.jar := $(JAVA_FILES.main) $(JAVA_FILES.unittest) $(JAVA_FILES.package.info)
+CLASS_FILES.jar := $(filter-out %/package-info.class,$(JAVA_FILES.jar:.java=.class))
+$(package.jar.in): $(package.dll) $(MAKEFILE)
+ ls -1 \
+ $(dir.src.jni)/*/*.java $(dir.src.jni)/*/*.class \
+ | sed -e 's,^$(dir.src)/,,' | sort > $@
+
+$(package.jar): $(CLASS_FILES.jar) $(MAKEFILE) $(package.jar.in)
+ @rm -f $(dir.src)/c/*~ $(dir.src.jni)/*~
+ cd $(dir.src); $(bin.jar) -cfe ../$@ org.sqlite.jni.capi.Tester1 @$(package.jar.in)
+ @ls -la $@
+ @echo "To use this jar you will need the -Djava.library.path=DIR/CONTAINING/libsqlite3-jni.so flag."
+ @echo "e.g. java -Djava.library.path=bld -jar $@"
+
+jar: $(package.jar)
+run-jar: $(package.jar) $(package.dll)
+ $(bin.java) -Djava.library.path=$(dir.bld) -jar $(package.jar) $(run-jar.flags)
+
+########################################################################
+# javadoc...
+dir.doc := $(dir.jni)/javadoc
+doc.index := $(dir.doc)/index.html
+javadoc.exclude := -exclude org.sqlite.jni.fts5
+# ^^^^ 2023-09-13: elide the fts5 parts from the public docs for
+# the time being, as it's not clear where the Java bindings for
+# those bits are going.
+# javadoc.exclude += -exclude org.sqlite.jni.capi
+# ^^^^ exclude the capi API only for certain builds (TBD)
+$(doc.index): $(JAVA_FILES.main) $(MAKEFILE)
+ @if [ -d $(dir.doc) ]; then rm -fr $(dir.doc)/*; fi
+ $(bin.javadoc) -cp $(classpath) -d $(dir.doc) -quiet \
+ -subpackages org.sqlite.jni $(javadoc.exclude)
+ @echo "javadoc output is in $@"
+
+.PHONY: doc javadoc docserve
+.FORCE: doc
+doc: $(doc.index)
+javadoc: $(doc.index)
+# Force rebild of docs
+redoc:
+ @rm -f $(doc.index)
+ @$(MAKE) doc
+docserve: $(doc.index)
+ cd $(dir.doc) && althttpd -max-age 1 -page index.html
+########################################################################
+# Clean up...
+CLEAN_FILES += $(dir.bld.c)/* \
+ $(dir.src.jni)/*.class \
+ $(dir.src.jni)/*/*.class \
+ $(package.dll) \
+ hs_err_pid*.log
+
+.PHONY: clean distclean
+clean:
+ -rm -f $(CLEAN_FILES)
+distclean: clean
+ -rm -f $(DISTCLEAN_FILES)
+ -rm -fr $(dir.bld.c) $(dir.doc)
+
+########################################################################
+# disttribution bundle rules...
+
+ifeq (,$(filter snapshot,$(MAKECMDGOALS)))
+dist-name-prefix := sqlite-jni
+else
+dist-name-prefix := sqlite-jni-snapshot-$(shell /usr/bin/date +%Y%m%d)
+endif
+dist-name := $(dist-name-prefix)-TEMP
+
+
+dist-dir.top := $(dist-name)
+dist-dir.src := $(dist-dir.top)/src
+dist.top.extras := \
+ README.md
+
+.PHONY: dist snapshot
+
+dist: \
+ $(bin.version-info) $(sqlite3.canonical.c) \
+ $(package.jar) $(MAKEFILE)
+ @echo "Making end-user deliverables..."
+ @echo "****************************************************************************"; \
+ echo "*** WARNING: be sure to build this with JDK8 (javac 1.8) for compatibility."; \
+ echo "*** reasons!"; $$($(bin.javac) -version); \
+ echo "****************************************************************************"
+ @rm -fr $(dist-dir.top)
+ @mkdir -p $(dist-dir.src)
+ @cp -p $(dist.top.extras) $(dist-dir.top)/.
+ @cp -p jar-dist.make $(dist-dir.top)/Makefile
+ @cp -p $(dir.src.c)/*.[ch] $(dist-dir.src)/.
+ @cp -p $(sqlite3.canonical.c) $(sqlite3.canonical.h) $(dist-dir.src)/.
+ @set -e; \
+ vnum=$$($(bin.version-info) --download-version); \
+ vjar=$$($(bin.version-info) --version); \
+ vdir=$(dist-name-prefix)-$$vnum; \
+ arczip=$$vdir.zip; \
+ cp -p $(package.jar) $(dist-dir.top)/sqlite3-jni-$${vjar}.jar; \
+ echo "Making $$arczip ..."; \
+ rm -fr $$arczip $$vdir; \
+ mv $(dist-dir.top) $$vdir; \
+ zip -qr $$arczip $$vdir; \
+ rm -fr $$vdir; \
+ ls -la $$arczip; \
+ set +e; \
+ unzip -lv $$arczip || echo "Missing unzip app? Not fatal."
+
+snapshot: dist
+
+.PHONY: dist-clean
+clean: dist-clean
+dist-clean:
+ rm -fr $(dist-name) $(wildcard sqlite-jni-*.zip)
diff --git a/ext/jni/README.md b/ext/jni/README.md
new file mode 100644
index 0000000..fc7b5f7
--- /dev/null
+++ b/ext/jni/README.md
@@ -0,0 +1,316 @@
+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:
+
+ <https://sqlite.org/src/doc/trunk/ext/jni/README.md>
+
+Technical support is available in the forum:
+
+ <https://sqlite.org/forum>
+
+
+> **FOREWARNING:** this subproject is very much in development and
+ subject to any number of changes. Please do not rely on any
+ information about its API until this disclaimer is removed. The JNI
+ bindings released with version 3.43 are a "tech preview." Once
+ finalized, strong backward compatibility guarantees will apply.
+
+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.
+
+- Virtual tables are unlikely to be supported due to the amount of
+ glue code needed to fit them into Java.
+
+- Support for mixed-mode operation, where client code accesses SQLite
+ both via the Java-side API and the C API via their own native
+ code. Such cases would be a minefield of potential mis-interactions
+ between this project's JNI bindings and mixed-mode client code.
+
+
+Hello World
+-----------------------------------------------------------------------
+
+```java
+import org.sqlite.jni.*;
+import static org.sqlite.jni.CApi.*;
+
+...
+
+final sqlite3 db = sqlite3_open(":memory:");
+try {
+ final int rc = sqlite3_errcode(db);
+ if( 0 != rc ){
+ if( null != db ){
+ System.out.print("Error opening db: "+sqlite3_errmsg(db));
+ }else{
+ System.out.print("Error opening db: rc="+rc);
+ }
+ ... handle error ...
+ }
+ // ... else use the db ...
+}finally{
+ // ALWAYS close databases using sqlite3_close() or sqlite3_close_v2()
+ // when done with them. All of their active statement handles must
+ // first have been passed to sqlite3_finalize().
+ sqlite3_close_v2(db);
+}
+```
+
+
+Building
+========================================================================
+
+The canonical builds assumes a Linux-like environment and requires:
+
+- GNU Make
+- A JDK supporting Java 8 or higher
+- A modern C compiler. gcc and clang should both work.
+
+Put simply:
+
+```console
+$ export JAVA_HOME=/path/to/jdk/root
+$ make
+$ make test
+$ make clean
+```
+
+The jar distribution can be created with `make jar`, but note that it
+does not contain the binary DLL file. A different DLL is needed for
+each target platform.
+
+
+<a id='1to1ish'></a>
+One-to-One(-ish) Mapping to C
+========================================================================
+
+This JNI binding aims to provide as close to a 1-to-1 experience with
+the C API as cross-language semantics allow. Interface changes are
+necessarily made where cross-language semantics do not allow a 1-to-1,
+and judiciously made where a 1-to-1 mapping would be unduly cumbersome
+to use in Java. In all cases, this binding makes every effort to
+provide semantics compatible with the C API documentation even if the
+interface to those semantics is slightly different. Any cases which
+deviate from those semantics (either removing or adding semantics) are
+clearly documented.
+
+Where it makes sense to do so for usability, Java-side overloads are
+provided which accept or return data in alternative forms or provide
+sensible default argument values. In all such cases they are thin
+proxies around the corresponding C APIs and do not introduce new
+semantics.
+
+In a few cases, Java-specific capabilities have been added in
+new APIs, all of which have "_java" somewhere in their names.
+Examples include:
+
+- `sqlite3_result_java_object()`
+- `sqlite3_column_java_object()`
+- `sqlite3_value_java_object()`
+
+which, as one might surmise, collectively enable the passing of
+arbitrary Java objects from user-defined SQL functions through to the
+caller.
+
+
+Golden Rule: Garbage Collection Cannot Free SQLite Resources
+------------------------------------------------------------------------
+
+It is important that all databases and prepared statement handles get
+cleaned up by client code. A database cannot be closed if it has open
+statement handles. `sqlite3_close()` fails if the db cannot be closed
+whereas `sqlite3_close_v2()` recognizes that case and marks the db as
+a "zombie," pending finalization when the library detects that all
+pending statements have been closed. Be aware that Java garbage
+collection _cannot_ close a database or finalize a prepared statement.
+Those things require explicit API calls.
+
+Classes for which it is sensible support Java's `AutoCloseable`
+interface so can be used with try-with-resources constructs.
+
+
+Golden Rule #2: _Never_ Throw from Callbacks (Unless...)
+------------------------------------------------------------------------
+
+All routines in this API, barring explicitly documented exceptions,
+retain C-like semantics. For example, they are not permitted to throw
+or propagate exceptions and must return error information (if any) via
+result codes or `null`. The only cases where the C-style APIs may
+throw is through client-side misuse, e.g. passing in a null where it
+may cause a `NullPointerException`. The APIs clearly mark function
+parameters which should not be null, but does not generally actively
+defend itself against such misuse. Some C-style APIs explicitly accept
+`null` as a no-op for usability's sake, and some of the JNI APIs
+deliberately return an error code, instead of segfaulting, when passed
+a `null`.
+
+Client-defined callbacks _must never throw exceptions_ unless _very
+explitly documented_ as being throw-safe. Exceptions are generally
+reserved for higher-level bindings which are constructed to
+specifically deal with them and ensure that they do not leak C-level
+resources. In some cases, callback handlers are permitted to throw, in
+which cases they get translated to C-level result codes and/or
+messages. If a callback which is not permitted to throw throws, its
+exception may trigger debug output but will otherwise be suppressed.
+
+The reason some callbacks are permitted to throw and others not is
+because all such callbacks act as proxies for C function callback
+interfaces and some of those interfaces have no error-reporting
+mechanism. Those which are capable of propagating errors back through
+the library convert exceptions from callbacks into corresponding
+C-level error information. Those which cannot propagate errors
+necessarily suppress any exceptions in order to maintain the C-style
+semantics of the APIs.
+
+
+Unwieldy Constructs are Re-mapped
+------------------------------------------------------------------------
+
+Some constructs, when modelled 1-to-1 from C to Java, are unduly
+clumsy to work with in Java because they try to shoehorn C's way of
+doing certain things into Java's wildly different ways. The following
+subsections cover those, starting with a verbose explanation and
+demonstration of where such changes are "really necessary"...
+
+### Custom Collations
+
+A prime example of where interface changes for Java are necessary for
+usability is [registration of a custom
+collation](https://sqlite.org/c3ref/create_collation.html):
+
+```c
+// C:
+int sqlite3_create_collation(sqlite3 * db, const char * name, int eTextRep,
+ void *pUserData,
+ int (*xCompare)(void*,int,void const *,int,void const *));
+
+int sqlite3_create_collation_v2(sqlite3 * db, const char * name, int eTextRep,
+ void *pUserData,
+ int (*xCompare)(void*,int,void const *,int,void const *),
+ void (*xDestroy)(void*));
+```
+
+The `pUserData` object is optional client-defined state for the
+`xCompare()` and/or `xDestroy()` callback functions, both of which are
+passed that object as their first argument. That data is passed around
+"externally" in C because that's how C models the world. If we were to
+bind that part as-is to Java, the result would be awkward to use (^Yes,
+we tried this.):
+
+```java
+// Java:
+int sqlite3_create_collation(sqlite3 db, String name, int eTextRep,
+ Object pUserData, xCompareType xCompare);
+
+int sqlite3_create_collation_v2(sqlite3 db, String name, int eTextRep,
+ Object pUserData,
+ xCompareType xCompare, xDestroyType xDestroy);
+```
+
+The awkwardness comes from (A) having two distinctly different objects
+for callbacks and (B) having their internal state provided separately,
+which is ill-fitting in Java. For the sake of usability, C APIs which
+follow that pattern use a slightly different Java interface:
+
+```java
+int sqlite3_create_collation(sqlite3 db, String name, int eTextRep,
+ SomeCallbackType collation);
+```
+
+Where the `Collation` class has an abstract `call()` method and
+no-op `xDestroy()` method which can be overridden if needed, leading to
+a much more Java-esque usage:
+
+```java
+int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new SomeCallbackType(){
+
+ // Required comparison function:
+ @Override public int call(byte[] lhs, byte[] rhs){ ... }
+
+ // Optional finalizer function:
+ @Override public void xDestroy(){ ... }
+
+ // Optional local state:
+ private String localState1 =
+ "This is local state. There are many like it, but this one is mine.";
+ private MyStateType localState2 = new MyStateType();
+ ...
+});
+```
+
+Noting that:
+
+- It is possible to bind in call-scope-local state via closures, if
+ desired, as opposed to packing it into the Collation object.
+
+- No capabilities of the C API are lost or unduly obscured via the
+ above API reshaping, so power users need not make any compromises.
+
+- In the specific example above, `sqlite3_create_collation_v2()`
+ becomes superfluous because the provided interface effectively
+ provides both the v1 and v2 interfaces, the difference being that
+ overriding the `xDestroy()` method effectively gives it v2
+ semantics.
+
+
+### User-defined SQL Functions (a.k.a. UDFs)
+
+The [`sqlite3_create_function()`](https://sqlite.org/c3ref/create_function.html)
+family of APIs make heavy use of function pointers to provide
+client-defined callbacks, necessitating interface changes in the JNI
+binding. The Java API has only one core function-registration function:
+
+```java
+int sqlite3_create_function(sqlite3 db, String funcName, int nArgs,
+ int encoding, SQLFunction func);
+```
+
+> Design question: does the encoding argument serve any purpose in
+ Java? That's as-yet undetermined. If not, it will be removed.
+
+`SQLFunction` is not used directly, but is instead instantiated via
+one of its three subclasses:
+
+- `ScalarFunction` implements simple scalar functions using but a
+ single callback.
+- `AggregateFunction` implements aggregate functions using two
+ callbacks.
+- `WindowFunction` implements window functions using four
+ callbacks.
+
+Search [`Tester1.java`](/file/ext/jni/src/org/sqlite/jni/capi/Tester1.java) for
+`SQLFunction` for how it's used.
+
+Reminder: see the disclaimer at the top of this document regarding the
+in-flux nature of this API.
+
+### And so on...
+
+Various APIs which accept callbacks, e.g. `sqlite3_trace_v2()` and
+`sqlite3_update_hook()`, use interfaces similar to those shown above.
+Despite the changes in signature, the JNI layer makes every effort to
+provide the same semantics as the C API documentation suggests.
diff --git a/ext/jni/jar-dist.make b/ext/jni/jar-dist.make
new file mode 100644
index 0000000..7596c99
--- /dev/null
+++ b/ext/jni/jar-dist.make
@@ -0,0 +1,60 @@
+#!/this/is/make
+#^^^^ help emacs out
+#
+# This is a POSIX-make-compatible makefile for building the sqlite3
+# JNI library from "dist" zip file. It must be edited to set the
+# proper top-level JDK directory and, depending on the platform, add a
+# platform-specific -I directory. It should build as-is with any
+# 2020s-era version of gcc or clang. It requires JDK version 8 or
+# higher and that JAVA_HOME points to the top-most installation
+# directory of that JDK. On Ubuntu-style systems the JDK is typically
+# installed under /usr/lib/jvm/java-VERSION-PLATFORM.
+
+default: all
+
+JAVA_HOME = /usr/lib/jvm/java-1.8.0-openjdk-amd64
+CFLAGS = \
+ -fPIC \
+ -Isrc \
+ -I$(JAVA_HOME)/include \
+ -I$(JAVA_HOME)/include/linux \
+ -I$(JAVA_HOME)/include/apple \
+ -I$(JAVA_HOME)/include/bsd \
+ -Wall
+
+SQLITE_OPT = \
+ -DSQLITE_ENABLE_RTREE \
+ -DSQLITE_ENABLE_EXPLAIN_COMMENTS \
+ -DSQLITE_ENABLE_STMTVTAB \
+ -DSQLITE_ENABLE_DBPAGE_VTAB \
+ -DSQLITE_ENABLE_DBSTAT_VTAB \
+ -DSQLITE_ENABLE_BYTECODE_VTAB \
+ -DSQLITE_ENABLE_OFFSET_SQL_FUNC \
+ -DSQLITE_OMIT_LOAD_EXTENSION \
+ -DSQLITE_OMIT_DEPRECATED \
+ -DSQLITE_OMIT_SHARED_CACHE \
+ -DSQLITE_THREADSAFE=1 \
+ -DSQLITE_TEMP_STORE=2 \
+ -DSQLITE_USE_URI=1 \
+ -DSQLITE_ENABLE_FTS5 \
+ -DSQLITE_DEBUG
+
+sqlite3-jni.dll = libsqlite3-jni.so
+$(sqlite3-jni.dll):
+ @echo "************************************************************************"; \
+ echo "*** If this fails to build, be sure to edit this makefile ***"; \
+ echo "*** to configure it for your system. ***"; \
+ echo "************************************************************************"
+ $(CC) $(CFLAGS) $(SQLITE_OPT) \
+ src/sqlite3-jni.c -shared -o $@
+ @echo "Now try running it with: make test"
+
+test.flags = -Djava.library.path=. sqlite3-jni-*.jar
+test: $(sqlite3-jni.dll)
+ java -jar $(test.flags)
+ java -jar $(test.flags) -t 7 -r 10 -shuffle
+
+clean:
+ -rm -f $(sqlite3-jni.dll)
+
+all: $(sqlite3-jni.dll)
diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c
new file mode 100644
index 0000000..5deff19
--- /dev/null
+++ b/ext/jni/src/c/sqlite3-jni.c
@@ -0,0 +1,6343 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file implements the JNI bindings declared in
+** org.sqlite.jni.capi.CApi (from which sqlite3-jni.h is generated).
+*/
+
+/*
+** If you found this comment by searching the code for
+** CallStaticObjectMethod because it appears in console output then
+** you're probably the victim of an OpenJDK bug:
+**
+** https://bugs.openjdk.org/browse/JDK-8130659
+**
+** It's known to happen with OpenJDK v8 but not with v19. It was
+** triggered by this code long before it made any use of
+** CallStaticObjectMethod().
+*/
+
+/*
+** Define any SQLITE_... config defaults we want if they aren't
+** overridden by the builder. Please keep these alphabetized.
+*/
+
+/**********************************************************************/
+/* SQLITE_D... */
+#ifndef SQLITE_DEFAULT_CACHE_SIZE
+# define SQLITE_DEFAULT_CACHE_SIZE -16384
+#endif
+#if !defined(SQLITE_DEFAULT_PAGE_SIZE)
+# define SQLITE_DEFAULT_PAGE_SIZE 8192
+#endif
+#ifndef SQLITE_DQS
+# define SQLITE_DQS 0
+#endif
+
+/**********************************************************************/
+/* SQLITE_ENABLE_... */
+/*
+** Unconditionally enable API_ARMOR in the JNI build. It ensures that
+** public APIs behave predictable in the face of passing illegal NULLs
+** or ranges which might otherwise invoke undefined behavior.
+*/
+#undef SQLITE_ENABLE_API_ARMOR
+#define SQLITE_ENABLE_API_ARMOR 1
+
+#ifndef SQLITE_ENABLE_BYTECODE_VTAB
+# define SQLITE_ENABLE_BYTECODE_VTAB 1
+#endif
+#ifndef SQLITE_ENABLE_DBPAGE_VTAB
+# define SQLITE_ENABLE_DBPAGE_VTAB 1
+#endif
+#ifndef SQLITE_ENABLE_DBSTAT_VTAB
+# define SQLITE_ENABLE_DBSTAT_VTAB 1
+#endif
+#ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS
+# define SQLITE_ENABLE_EXPLAIN_COMMENTS 1
+#endif
+#ifndef SQLITE_ENABLE_MATH_FUNCTIONS
+# define SQLITE_ENABLE_MATH_FUNCTIONS 1
+#endif
+#ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC
+# define SQLITE_ENABLE_OFFSET_SQL_FUNC 1
+#endif
+#ifndef SQLITE_ENABLE_RTREE
+# define SQLITE_ENABLE_RTREE 1
+#endif
+//#ifndef SQLITE_ENABLE_SESSION
+//# define SQLITE_ENABLE_SESSION 1
+//#endif
+#ifndef SQLITE_ENABLE_STMTVTAB
+# define SQLITE_ENABLE_STMTVTAB 1
+#endif
+//#ifndef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
+//# define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
+//#endif
+
+/**********************************************************************/
+/* SQLITE_J... */
+#ifdef SQLITE_JNI_FATAL_OOM
+#if !SQLITE_JNI_FATAL_OOM
+#undef SQLITE_JNI_FATAL_OOM
+#endif
+#endif
+
+/**********************************************************************/
+/* SQLITE_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_S... */
+#ifndef SQLITE_STRICT_SUBTYPE
+# define SQLITE_STRICT_SUBTYPE 1
+#endif
+
+/**********************************************************************/
+/* SQLITE_T... */
+#ifndef SQLITE_TEMP_STORE
+# define SQLITE_TEMP_STORE 2
+#endif
+#ifndef SQLITE_THREADSAFE
+# define SQLITE_THREADSAFE 1
+#endif
+
+/**********************************************************************/
+/* SQLITE_USE_... */
+#ifndef SQLITE_USE_URI
+# define SQLITE_USE_URI 1
+#endif
+
+
+/*
+** Which sqlite3.c we're using needs to be configurable to enable
+** building against a custom copy, e.g. the SEE variant. We have to
+** include sqlite3.c, as opposed to sqlite3.h, in order to get access
+** to some interal details like SQLITE_MAX_... and friends. This
+** increases the rebuild time considerably but we need this in order
+** to access some internal functionality and keep the to-Java-exported
+** values of SQLITE_MAX_... and SQLITE_LIMIT_... in sync with the C
+** build.
+*/
+#ifndef SQLITE_C
+# define SQLITE_C sqlite3.c
+#endif
+#define INC__STRINGIFY_(f) #f
+#define INC__STRINGIFY(f) INC__STRINGIFY_(f)
+#include INC__STRINGIFY(SQLITE_C)
+#undef INC__STRINGIFY_
+#undef INC__STRINGIFY
+#undef SQLITE_C
+
+/*
+** End of the sqlite3 lib setup. What follows is JNI-specific.
+*/
+
+#include "sqlite3-jni.h"
+#include <assert.h>
+#include <stdio.h> /* only for testing/debugging */
+#include <stdint.h> /* intptr_t for 32-bit builds */
+
+/* Only for debugging */
+#define MARKER(pfexp) \
+ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \
+ printf pfexp; \
+ } while(0)
+
+/*
+** Creates a verbose JNI function name. Suffix must be
+** the JNI-mangled form of the function's name, minus the
+** prefix seen in this macro.
+*/
+#define JniFuncName(Suffix) \
+ Java_org_sqlite_jni_capi_CApi_sqlite3_ ## Suffix
+
+/* Prologue for JNI function declarations and definitions. */
+#define JniDecl(ReturnType,Suffix) \
+ JNIEXPORT ReturnType JNICALL JniFuncName(Suffix)
+
+/*
+** S3JniApi's intent is that CFunc be the C API func(s) the
+** being-declared JNI function is wrapping, making it easier to find
+** that function's JNI-side entry point. The other args are for JniDecl.
+** See the many examples in this file.
+*/
+#define S3JniApi(CFunc,ReturnType,Suffix) JniDecl(ReturnType,Suffix)
+
+/*
+** S3JniCast_L2P and P2L cast jlong (64-bit) to/from pointers. This is
+** required for casting warning-free on 32-bit builds, where we
+** otherwise get complaints that we're casting between different-sized
+** int types.
+**
+** This use of intptr_t is the _only_ reason we require <stdint.h>
+** which, in turn, requires building with -std=c99 (or later).
+**
+** See also: the notes for LongPtrGet_T.
+*/
+#define S3JniCast_L2P(JLongAsPtr) (void*)((intptr_t)(JLongAsPtr))
+#define S3JniCast_P2L(PTR) (jlong)((intptr_t)(PTR))
+
+/*
+** Shortcuts for the first 2 parameters to all JNI bindings.
+**
+** The type of the jSelf arg differs, but no docs seem to mention
+** this: for static methods it's of type jclass and for non-static
+** it's jobject. jobject actually works for all funcs, in the sense
+** that it compiles and runs so long as we don't use jSelf (which is
+** only rarely needed in this code), but to be pedantically correct we
+** need the proper type in the signature.
+**
+** https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers
+*/
+#define JniArgsEnvObj JNIEnv * env, jobject jSelf
+#define JniArgsEnvClass JNIEnv * env, jclass jKlazz
+/*
+** Helpers to account for -Xcheck:jni warnings about not having
+** checked for exceptions.
+*/
+#define S3JniIfThrew if( (*env)->ExceptionCheck(env) )
+#define S3JniExceptionClear (*env)->ExceptionClear(env)
+#define S3JniExceptionReport (*env)->ExceptionDescribe(env)
+#define S3JniExceptionIgnore S3JniIfThrew S3JniExceptionClear
+#define S3JniExceptionWarnIgnore \
+ S3JniIfThrew {S3JniExceptionReport; S3JniExceptionClear;}(void)0
+#define S3JniExceptionWarnCallbackThrew(STR) \
+ MARKER(("WARNING: " STR " MUST NOT THROW.\n")); \
+ (*env)->ExceptionDescribe(env)
+
+/** To be used for cases where we're _really_ not expecting an
+ exception, e.g. looking up well-defined Java class members. */
+#define S3JniExceptionIsFatal(MSG) S3JniIfThrew {\
+ S3JniExceptionReport; S3JniExceptionClear; \
+ (*env)->FatalError(env, MSG); \
+ }
+
+/*
+** Declares local var env = s3jni_env(). All JNI calls involve a
+** JNIEnv somewhere, always named env, and many of our macros assume
+** env is in scope. Where it's not, but should be, use this to make it
+** so.
+*/
+#define S3JniDeclLocal_env JNIEnv * const env = s3jni_env()
+
+/* Fail fatally with an OOM message. */
+static inline void s3jni_oom(JNIEnv * const env){
+ (*env)->FatalError(env, "SQLite3 JNI is out of memory.") /* does not return */;
+}
+
+/*
+** sqlite3_malloc() proxy which fails fatally on OOM. This should
+** only be used for routines which manage global state and have no
+** recovery strategy for OOM. For sqlite3 API which can reasonably
+** return SQLITE_NOMEM, s3jni_malloc() should be used instead.
+*/
+static void * s3jni_malloc_or_die(JNIEnv * const env, size_t n){
+ void * const rv = sqlite3_malloc(n);
+ if( n && !rv ) s3jni_oom(env);
+ return rv;
+}
+
+/*
+** Works like sqlite3_malloc() unless built with SQLITE_JNI_FATAL_OOM,
+** in which case it calls s3jni_oom() on OOM.
+*/
+#ifdef SQLITE_JNI_FATAL_OOM
+#define s3jni_malloc(SIZE) s3jni_malloc_or_die(env, SIZE)
+#else
+#define s3jni_malloc(SIZE) sqlite3_malloc(((void)env,(SIZE)))
+/* the ((void)env) trickery here is to avoid ^^^^^^ an otherwise
+ unused arg in at least one place. */
+#endif
+
+/*
+** Works like sqlite3_realloc() unless built with SQLITE_JNI_FATAL_OOM,
+** in which case it calls s3jni_oom() on OOM.
+*/
+#ifdef SQLITE_JNI_FATAL_OOM
+static void * s3jni_realloc_or_die(JNIEnv * const env, void * p, size_t n){
+ void * const rv = sqlite3_realloc(p, (int)n);
+ if( n && !rv ) s3jni_oom(env);
+ return rv;
+}
+#define s3jni_realloc(MEM,SIZE) s3jni_realloc_or_die(env, (MEM), (SIZE))
+#else
+#define s3jni_realloc(MEM,SIZE) sqlite3_realloc((MEM), ((void)env, (SIZE)))
+#endif
+
+/* Fail fatally if !EXPR. */
+#define s3jni_oom_fatal(EXPR) if( !(EXPR) ) s3jni_oom(env)
+/* Maybe fail fatally if !EXPR. */
+#ifdef SQLITE_JNI_FATAL_OOM
+#define s3jni_oom_check s3jni_oom_fatal
+#else
+#define s3jni_oom_check(EXPR)
+#endif
+//#define S3JniDb_oom(pDb,EXPR) ((EXPR) ? sqlite3OomFault(pDb) : 0)
+
+#define s3jni_db_oom(pDb) (void)((pDb) ? ((pDb)->mallocFailed=1) : 0)
+
+/* Helpers for Java value reference management. */
+static jobject s3jni_ref_global(JNIEnv * const env, jobject const v){
+ jobject const rv = v ? (*env)->NewGlobalRef(env, v) : NULL;
+ s3jni_oom_fatal( v ? !!rv : 1 );
+ return rv;
+}
+static jobject s3jni_ref_local(JNIEnv * const env, jobject const v){
+ jobject const rv = v ? (*env)->NewLocalRef(env, v) : NULL;
+ s3jni_oom_fatal( v ? !!rv : 1 );
+ return rv;
+}
+static inline void s3jni_unref_global(JNIEnv * const env, jobject const v){
+ if( v ) (*env)->DeleteGlobalRef(env, v);
+}
+static inline void s3jni_unref_local(JNIEnv * const env, jobject const v){
+ if( v ) (*env)->DeleteLocalRef(env, v);
+}
+#define S3JniRefGlobal(VAR) s3jni_ref_global(env, (VAR))
+#define S3JniRefLocal(VAR) s3jni_ref_local(env, (VAR))
+#define S3JniUnrefGlobal(VAR) s3jni_unref_global(env, (VAR))
+#define S3JniUnrefLocal(VAR) s3jni_unref_local(env, (VAR))
+
+/*
+** Lookup key type for use with s3jni_nphop() and a cache of a
+** frequently-needed Java-side class reference and one or two Java
+** class member IDs.
+*/
+typedef struct S3JniNphOp S3JniNphOp;
+struct S3JniNphOp {
+ const int index /* index into S3JniGlobal.nph[] */;
+ const char * const zName /* Full Java name of the class */;
+ const char * const zMember /* Name of member property */;
+ const char * const zTypeSig /* JNI type signature of zMember */;
+ /*
+ ** klazz is a global ref to the class represented by pRef.
+ **
+ ** According to:
+ **
+ ** https://developer.ibm.com/articles/j-jni/
+ **
+ ** > ... the IDs returned for a given class don't change for the
+ ** lifetime of the JVM process. But the call to get the field or
+ ** method can require significant work in the JVM, because fields
+ ** and methods might have been inherited from superclasses, making
+ ** the JVM walk up the class hierarchy to find them. Because the
+ ** IDs are the same for a given class, you should look them up
+ ** once and then reuse them. Similarly, looking up class objects
+ ** can be expensive, so they should be cached as well.
+ */
+ jclass klazz;
+ volatile jfieldID fidValue /* NativePointerHolder.nativePointer or
+ ** OutputPointer.T.value */;
+ volatile jmethodID midCtor /* klazz's no-arg constructor. Used by
+ ** NativePointerHolder_new(). */;
+};
+
+/*
+** Cache keys for each concrete NativePointerHolder subclasses and
+** OutputPointer.T types. The members are to be used with s3jni_nphop()
+** and friends, and each one's member->index corresponds to its index
+** in the S3JniGlobal.nph[] array.
+*/
+static const struct {
+ const S3JniNphOp sqlite3;
+ const S3JniNphOp sqlite3_backup;
+ const S3JniNphOp sqlite3_blob;
+ const S3JniNphOp sqlite3_context;
+ const S3JniNphOp sqlite3_stmt;
+ const S3JniNphOp sqlite3_value;
+ const S3JniNphOp OutputPointer_Bool;
+ const S3JniNphOp OutputPointer_Int32;
+ const S3JniNphOp OutputPointer_Int64;
+ const S3JniNphOp OutputPointer_sqlite3;
+ const S3JniNphOp OutputPointer_sqlite3_blob;
+ const S3JniNphOp OutputPointer_sqlite3_stmt;
+ const S3JniNphOp OutputPointer_sqlite3_value;
+ const S3JniNphOp OutputPointer_String;
+#ifdef SQLITE_ENABLE_FTS5
+ const S3JniNphOp OutputPointer_ByteArray;
+ const S3JniNphOp Fts5Context;
+ const S3JniNphOp Fts5ExtensionApi;
+ const S3JniNphOp fts5_api;
+ const S3JniNphOp fts5_tokenizer;
+ const S3JniNphOp Fts5Tokenizer;
+#endif
+} S3JniNphOps = {
+#define MkRef(INDEX, KLAZZ, MEMBER, SIG) \
+ { INDEX, "org/sqlite/jni/" KLAZZ, MEMBER, SIG }
+/* NativePointerHolder ref */
+#define RefN(INDEX, KLAZZ) MkRef(INDEX, KLAZZ, "nativePointer", "J")
+/* OutputPointer.T ref */
+#define RefO(INDEX, KLAZZ, SIG) MkRef(INDEX, KLAZZ, "value", SIG)
+ RefN(0, "capi/sqlite3"),
+ RefN(1, "capi/sqlite3_backup"),
+ RefN(2, "capi/sqlite3_blob"),
+ RefN(3, "capi/sqlite3_context"),
+ RefN(4, "capi/sqlite3_stmt"),
+ RefN(5, "capi/sqlite3_value"),
+ RefO(6, "capi/OutputPointer$Bool", "Z"),
+ RefO(7, "capi/OutputPointer$Int32", "I"),
+ RefO(8, "capi/OutputPointer$Int64", "J"),
+ RefO(9, "capi/OutputPointer$sqlite3",
+ "Lorg/sqlite/jni/capi/sqlite3;"),
+ RefO(10, "capi/OutputPointer$sqlite3_blob",
+ "Lorg/sqlite/jni/capi/sqlite3_blob;"),
+ RefO(11, "capi/OutputPointer$sqlite3_stmt",
+ "Lorg/sqlite/jni/capi/sqlite3_stmt;"),
+ RefO(12, "capi/OutputPointer$sqlite3_value",
+ "Lorg/sqlite/jni/capi/sqlite3_value;"),
+ RefO(13, "capi/OutputPointer$String", "Ljava/lang/String;"),
+#ifdef SQLITE_ENABLE_FTS5
+ RefO(14, "capi/OutputPointer$ByteArray", "[B"),
+ RefN(15, "fts5/Fts5Context"),
+ RefN(16, "fts5/Fts5ExtensionApi"),
+ RefN(17, "fts5/fts5_api"),
+ RefN(18, "fts5/fts5_tokenizer"),
+ RefN(19, "fts5/Fts5Tokenizer")
+#endif
+#undef MkRef
+#undef RefN
+#undef RefO
+};
+
+#define S3JniNph(T) &S3JniNphOps.T
+
+enum {
+ /*
+ ** Size of the NativePointerHolder cache. Need enough space for
+ ** (only) the library's NativePointerHolder and OutputPointer types,
+ ** a fixed count known at build-time. This value needs to be
+ ** exactly the number of S3JniNphOp entries in the S3JniNphOps
+ ** object.
+ */
+ S3Jni_NphCache_size = sizeof(S3JniNphOps) / sizeof(S3JniNphOp)
+};
+
+/*
+** State for binding C callbacks to Java methods.
+*/
+typedef struct S3JniHook S3JniHook;
+struct S3JniHook{
+ jobject jObj /* global ref to Java instance */;
+ jmethodID midCallback /* callback method. Signature depends on
+ ** jObj's type */;
+ /* We lookup the jObj.xDestroy() method as-needed for contexts which
+ ** support custom finalizers. Fundamentally we can support them for
+ ** any Java type, but we only want to expose support for them where
+ ** the C API does. */
+ jobject jExtra /* Global ref to a per-hook-type value */;
+ int doXDestroy /* If true then S3JniHook_unref() will call
+ jObj->xDestroy() if it's available. */;
+ S3JniHook * pNext /* Next entry in S3Global.hooks.aFree */;
+};
+/* For clean bitwise-copy init of local instances. */
+static const S3JniHook S3JniHook_empty = {0,0,0,0,0};
+
+/*
+** Per-(sqlite3*) state for various JNI bindings. This state is
+** allocated as needed, cleaned up in sqlite3_close(_v2)(), and
+** recycled when possible.
+**
+** Trivia: vars and parameters of this type are often named "ps"
+** because this class used to have a name for which that abbreviation
+** made sense.
+*/
+typedef struct S3JniDb S3JniDb;
+struct S3JniDb {
+ sqlite3 *pDb /* The associated db handle */;
+ jobject jDb /* A global ref of the output object which gets
+ returned from sqlite3_open(_v2)(). We need this in
+ order to have an object to pass to routines like
+ sqlite3_collation_needed()'s callback, or else we
+ have to dynamically create one for that purpose,
+ which would be fine except that it would be a
+ different instance (and maybe even a different
+ class) than the one the user may expect to
+ receive. */;
+ char * zMainDbName /* Holds the string allocated on behalf of
+ SQLITE_DBCONFIG_MAINDBNAME. */;
+ struct {
+ S3JniHook busyHandler;
+ S3JniHook collationNeeded;
+ S3JniHook commit;
+ S3JniHook progress;
+ S3JniHook rollback;
+ S3JniHook trace;
+ S3JniHook update;
+ S3JniHook auth;
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ S3JniHook preUpdate;
+#endif
+ } hooks;
+#ifdef SQLITE_ENABLE_FTS5
+ /* FTS5-specific state */
+ struct {
+ jobject jApi /* global ref to s3jni_fts5_api_from_db() */;
+ } fts;
+#endif
+ S3JniDb * pNext /* Next entry in SJG.perDb.aFree */;
+};
+
+static const char * const S3JniDb_clientdata_key = "S3JniDb";
+#define S3JniDb_from_clientdata(pDb) \
+ (pDb ? sqlite3_get_clientdata(pDb, S3JniDb_clientdata_key) : 0)
+
+/*
+** Cache for per-JNIEnv (i.e. per-thread) data.
+**
+** Trivia: vars and parameters of this type are often named "jc"
+** because this class used to have a name for which that abbreviation
+** made sense.
+*/
+typedef struct S3JniEnv S3JniEnv;
+struct S3JniEnv {
+ JNIEnv *env /* JNIEnv in which this cache entry was created */;
+ /*
+ ** pdbOpening is used to coordinate the Java/DB connection of a
+ ** being-open()'d db in the face of auto-extensions.
+ ** Auto-extensions run before we can bind the C db to its Java
+ ** representation, but auto-extensions require that binding to pass
+ ** on to their Java-side callbacks. We handle this as follows:
+ **
+ ** - In the JNI side of sqlite3_open(), allocate the Java side of
+ ** that connection and set pdbOpening to point to that
+ ** object.
+ **
+ ** - Call sqlite3_open(), which triggers the auto-extension
+ ** handler. That handler uses pdbOpening to connect the native
+ ** db handle which it receives with pdbOpening.
+ **
+ ** - When sqlite3_open() returns, check whether pdbOpening->pDb is
+ ** NULL. If it isn't, auto-extension handling set it up. If it
+ ** is, complete the Java/C binding unless sqlite3_open() returns
+ ** a NULL db, in which case free pdbOpening.
+ */
+ S3JniDb * pdbOpening;
+ S3JniEnv * pNext /* Next entry in SJG.envCache.aHead or
+ SJG.envCache.aFree */;
+};
+
+/*
+** State for proxying sqlite3_auto_extension() in Java. This was
+** initially a separate class from S3JniHook and now the older name is
+** retained for readability in the APIs which use this, as well as for
+** its better code-searchability.
+*/
+typedef S3JniHook S3JniAutoExtension;
+
+/*
+** Type IDs for SQL function categories.
+*/
+enum UDFType {
+ UDF_UNKNOWN_TYPE = 0/*for error propagation*/,
+ UDF_SCALAR,
+ UDF_AGGREGATE,
+ UDF_WINDOW
+};
+
+/*
+** State for binding Java-side UDFs.
+*/
+typedef struct S3JniUdf S3JniUdf;
+struct S3JniUdf {
+ jobject jObj /* SQLFunction instance */;
+ char * zFuncName /* Only for error reporting and debug logging */;
+ enum UDFType type /* UDF type */;
+ /** Method IDs for the various UDF methods. */
+ jmethodID jmidxFunc /* xFunc method (scalar) */;
+ jmethodID jmidxStep /* xStep method (aggregate/window) */;
+ jmethodID jmidxFinal /* xFinal method (aggregate/window) */;
+ jmethodID jmidxValue /* xValue method (window) */;
+ jmethodID jmidxInverse /* xInverse method (window) */;
+ S3JniUdf * pNext /* Next entry in SJG.udf.aFree. */;
+};
+
+#if defined(SQLITE_JNI_ENABLE_METRICS) && 0==SQLITE_JNI_ENABLE_METRICS
+# undef SQLITE_JNI_ENABLE_METRICS
+#endif
+
+/*
+** If true, modifying S3JniGlobal.metrics is protected by a mutex,
+** else it isn't.
+*/
+#ifdef SQLITE_DEBUG
+# define S3JNI_METRICS_MUTEX SQLITE_THREADSAFE
+#else
+# define S3JNI_METRICS_MUTEX 0
+#endif
+#ifndef SQLITE_JNI_ENABLE_METRICS
+# undef S3JNI_METRICS_MUTEX
+# define S3JNI_METRICS_MUTEX 0
+#endif
+
+/*
+** Global state, e.g. caches and metrics.
+*/
+typedef struct S3JniGlobalType S3JniGlobalType;
+struct S3JniGlobalType {
+ /*
+ ** According to: https://developer.ibm.com/articles/j-jni/
+ **
+ ** > A thread can get a JNIEnv by calling GetEnv() using the JNI
+ ** invocation interface through a JavaVM object. The JavaVM object
+ ** itself can be obtained by calling the JNI GetJavaVM() method
+ ** using a JNIEnv object and can be cached and shared across
+ ** threads. Caching a copy of the JavaVM object enables any thread
+ ** with access to the cached object to get access to its own
+ ** JNIEnv when necessary.
+ */
+ JavaVM * jvm;
+ /*
+ ** Global mutex. It must not be used for anything which might call
+ ** back into the JNI layer.
+ */
+ sqlite3_mutex * mutex;
+ /*
+ ** Cache of references to Java classes and method IDs for
+ ** NativePointerHolder subclasses and OutputPointer.T types.
+ */
+ struct {
+ S3JniNphOp list[S3Jni_NphCache_size];
+ sqlite3_mutex * mutex; /* mutex for this->list */
+ volatile void const * locker; /* sanity-checking-only context object
+ for this->mutex */
+ } nph;
+ /*
+ ** Cache of per-thread state.
+ */
+ struct {
+ S3JniEnv * aHead /* Linked list of in-use instances */;
+ S3JniEnv * aFree /* Linked list of free instances */;
+ sqlite3_mutex * mutex /* mutex for aHead and aFree. */;
+ volatile void const * locker /* env mutex is held on this
+ object's behalf. Used only for
+ sanity checking. */;
+ } envCache;
+ /*
+ ** Per-db state. This can move into the core library once we can tie
+ ** client-defined state to db handles there.
+ */
+ struct {
+ S3JniDb * aFree /* Linked list of free instances */;
+ sqlite3_mutex * mutex /* mutex for aHead and aFree */;
+ volatile void const * locker
+ /* perDb mutex is held on this object's behalf. Used only for
+ sanity checking. Note that the mutex is at the class level, not
+ instance level. */;
+ } perDb;
+ struct {
+ S3JniUdf * aFree /* Head of the free-item list. Guarded by global
+ mutex. */;
+ } udf;
+ /*
+ ** Refs to global classes and methods. Obtained during static init
+ ** and never released.
+ */
+ struct {
+ jclass cLong /* global ref to java.lang.Long */;
+ jclass cString /* global ref to java.lang.String */;
+ jobject oCharsetUtf8 /* global ref to StandardCharset.UTF_8 */;
+ jmethodID ctorLong1 /* the Long(long) constructor */;
+ jmethodID ctorStringBA /* the String(byte[],Charset) constructor */;
+ jmethodID stringGetBytes /* the String.getBytes(Charset) method */;
+
+ /*
+ ByteBuffer may or may not be supported via JNI on any given
+ platform:
+
+ https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#nio_support
+
+ We only store a ref to byteBuffer.klazz if JNI support for
+ ByteBuffer is available (which we determine during static init).
+ */
+ struct {
+ jclass klazz /* global ref to java.nio.ByteBuffer */;
+ jmethodID midAlloc /* ByteBuffer.allocateDirect() */;
+ jmethodID midLimit /* ByteBuffer.limit() */;
+ } byteBuffer;
+ } g;
+ /*
+ ** The list of Java-side auto-extensions
+ ** (org.sqlite.jni.capi.AutoExtensionCallback objects).
+ */
+ struct {
+ S3JniAutoExtension *aExt /* The auto-extension list. It is
+ maintained such that all active
+ entries are in the first contiguous
+ nExt array elements. */;
+ int nAlloc /* number of entries allocated for aExt,
+ as distinct from the number of active
+ entries. */;
+ int nExt /* number of active entries in aExt, all in the
+ first nExt'th array elements. */;
+ sqlite3_mutex * mutex /* mutex for manipulation/traversal of aExt */;
+ volatile const void * locker /* object on whose behalf the mutex
+ is held. Only for sanity checking
+ in debug builds. */;
+ } autoExt;
+#ifdef SQLITE_ENABLE_FTS5
+ struct {
+ volatile jobject jExt /* Global ref to Java singleton for the
+ Fts5ExtensionApi instance. */;
+ struct {
+ jfieldID fidA /* Fts5Phrase::a member */;
+ jfieldID fidB /* Fts5Phrase::b member */;
+ } jPhraseIter;
+ } fts5;
+#endif
+ struct {
+#ifdef SQLITE_ENABLE_SQLLOG
+ S3JniHook sqllog /* sqlite3_config(SQLITE_CONFIG_SQLLOG) callback */;
+#endif
+ S3JniHook configlog /* sqlite3_config(SQLITE_CONFIG_LOG) callback */;
+ S3JniHook * aFree /* free-item list, for recycling. */;
+ sqlite3_mutex * mutex /* mutex for aFree */;
+ volatile const void * locker /* object on whose behalf the mutex
+ is held. Only for sanity checking
+ in debug builds. */;
+ } hook;
+#ifdef SQLITE_JNI_ENABLE_METRICS
+ /* Internal metrics. */
+ struct {
+ volatile unsigned nEnvHit;
+ volatile unsigned nEnvMiss;
+ volatile unsigned nEnvAlloc;
+ volatile unsigned nMutexEnv /* number of times envCache.mutex was entered for
+ a S3JniEnv operation. */;
+ volatile unsigned nMutexNph /* number of times SJG.mutex was entered */;
+ volatile unsigned nMutexHook /* number of times SJG.mutex hooks.was entered */;
+ volatile unsigned nMutexPerDb /* number of times perDb.mutex was entered */;
+ volatile unsigned nMutexAutoExt /* number of times autoExt.mutex was entered */;
+ volatile unsigned nMutexGlobal /* number of times global mutex was entered. */;
+ volatile unsigned nMutexUdf /* number of times global mutex was entered
+ for UDFs. */;
+ volatile unsigned nDestroy /* xDestroy() calls across all types */;
+ volatile unsigned nPdbAlloc /* Number of S3JniDb alloced. */;
+ volatile unsigned nPdbRecycled /* Number of S3JniDb reused. */;
+ volatile unsigned nUdfAlloc /* Number of S3JniUdf alloced. */;
+ volatile unsigned nUdfRecycled /* Number of S3JniUdf reused. */;
+ volatile unsigned nHookAlloc /* Number of S3JniHook alloced. */;
+ volatile unsigned nHookRecycled /* Number of S3JniHook reused. */;
+ struct {
+ /* Number of calls for each type of UDF callback. */
+ volatile unsigned nFunc;
+ volatile unsigned nStep;
+ volatile unsigned nFinal;
+ volatile unsigned nValue;
+ volatile unsigned nInverse;
+ } udf;
+ unsigned nMetrics /* Total number of mutex-locked
+ metrics increments. */;
+#if S3JNI_METRICS_MUTEX
+ sqlite3_mutex * mutex;
+#endif
+ } metrics;
+#endif /* SQLITE_JNI_ENABLE_METRICS */
+};
+static S3JniGlobalType S3JniGlobal = {};
+#define SJG S3JniGlobal
+
+/* Increments *p, possibly protected by a mutex. */
+#ifndef SQLITE_JNI_ENABLE_METRICS
+#define s3jni_incr(PTR)
+#elif S3JNI_METRICS_MUTEX
+static void s3jni_incr( volatile unsigned int * const p ){
+ sqlite3_mutex_enter(SJG.metrics.mutex);
+ ++SJG.metrics.nMetrics;
+ ++(*p);
+ sqlite3_mutex_leave(SJG.metrics.mutex);
+}
+#else
+#define s3jni_incr(PTR) ++(*(PTR))
+#endif
+
+/* Helpers for working with specific mutexes. */
+#if SQLITE_THREADSAFE
+#define s3jni_mutex_enter2(M, Metric) \
+ sqlite3_mutex_enter( M ); \
+ s3jni_incr( &SJG.metrics.Metric )
+#define s3jni_mutex_leave2(M) \
+ sqlite3_mutex_leave( M )
+
+#define s3jni_mutex_enter(M, L, Metric) \
+ assert( (void*)env != (void*)L && "Invalid use of " #L); \
+ s3jni_mutex_enter2( M, Metric ); \
+ L = env
+#define s3jni_mutex_leave(M, L) \
+ assert( (void*)env == (void*)L && "Invalid use of " #L); \
+ L = 0; \
+ s3jni_mutex_leave2( M )
+
+#define S3JniEnv_mutex_assertLocked \
+ assert( 0 != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
+#define S3JniEnv_mutex_assertLocker \
+ assert( (env) == SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
+#define S3JniEnv_mutex_assertNotLocker \
+ assert( (env) != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
+
+#define S3JniEnv_mutex_enter \
+ s3jni_mutex_enter( SJG.envCache.mutex, SJG.envCache.locker, nMutexEnv )
+#define S3JniEnv_mutex_leave \
+ s3jni_mutex_leave( SJG.envCache.mutex, SJG.envCache.locker )
+
+#define S3JniAutoExt_mutex_enter \
+ s3jni_mutex_enter( SJG.autoExt.mutex, SJG.autoExt.locker, nMutexAutoExt )
+#define S3JniAutoExt_mutex_leave \
+ s3jni_mutex_leave( SJG.autoExt.mutex, SJG.autoExt.locker )
+#define S3JniAutoExt_mutex_assertLocker \
+ assert( env == SJG.autoExt.locker && "Misuse of S3JniGlobal.autoExt.mutex" )
+
+#define S3JniGlobal_mutex_enter \
+ s3jni_mutex_enter2( SJG.mutex, nMutexGlobal )
+#define S3JniGlobal_mutex_leave \
+ s3jni_mutex_leave2( SJG.mutex )
+
+#define S3JniHook_mutex_enter \
+ s3jni_mutex_enter( SJG.hook.mutex, SJG.hook.locker, nMutexHook )
+#define S3JniHook_mutex_leave \
+ s3jni_mutex_leave( SJG.hook.mutex, SJG.hook.locker )
+
+#define S3JniNph_mutex_enter \
+ s3jni_mutex_enter( SJG.nph.mutex, SJG.nph.locker, nMutexNph )
+#define S3JniNph_mutex_leave \
+ s3jni_mutex_leave( SJG.nph.mutex, SJG.nph.locker )
+
+#define S3JniDb_mutex_assertLocker \
+ assert( (env) == SJG.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" )
+#define S3JniDb_mutex_enter \
+ s3jni_mutex_enter( SJG.perDb.mutex, SJG.perDb.locker, nMutexPerDb )
+#define S3JniDb_mutex_leave \
+ s3jni_mutex_leave( SJG.perDb.mutex, SJG.perDb.locker )
+
+#else /* SQLITE_THREADSAFE==0 */
+#define S3JniAutoExt_mutex_assertLocker
+#define S3JniAutoExt_mutex_enter
+#define S3JniAutoExt_mutex_leave
+#define S3JniDb_mutex_assertLocker
+#define S3JniDb_mutex_enter
+#define S3JniDb_mutex_leave
+#define S3JniEnv_mutex_assertLocked
+#define S3JniEnv_mutex_assertLocker
+#define S3JniEnv_mutex_assertNotLocker
+#define S3JniEnv_mutex_enter
+#define S3JniEnv_mutex_leave
+#define S3JniGlobal_mutex_enter
+#define S3JniGlobal_mutex_leave
+#define S3JniHook_mutex_enter
+#define S3JniHook_mutex_leave
+#define S3JniNph_mutex_enter
+#define S3JniNph_mutex_leave
+#endif
+
+/* Helpers for jstring and jbyteArray. */
+static const char * s3jni__jstring_to_mutf8(JNIEnv * const env, jstring v ){
+ const char *z = v ? (*env)->GetStringUTFChars(env, v, NULL) : 0;
+ s3jni_oom_check( v ? !!z : !z );
+ return z;
+}
+
+#define s3jni_jstring_to_mutf8(ARG) s3jni__jstring_to_mutf8(env, (ARG))
+#define s3jni_mutf8_release(ARG,VAR) if( VAR ) (*env)->ReleaseStringUTFChars(env, ARG, VAR)
+
+/*
+** If jBA is not NULL then its GetByteArrayElements() value is
+** returned. If jBA is not NULL and nBA is not NULL then *nBA is set
+** to the GetArrayLength() of jBA. If GetByteArrayElements() requires
+** an allocation and that allocation fails then this function either
+** fails fatally or returns 0, depending on build-time options.
+ */
+static jbyte * s3jni__jbyteArray_bytes2(JNIEnv * const env, jbyteArray jBA, jsize * nBA ){
+ jbyte * const rv = jBA ? (*env)->GetByteArrayElements(env, jBA, NULL) : 0;
+ s3jni_oom_check( jBA ? !!rv : 1 );
+ if( jBA && nBA ) *nBA = (*env)->GetArrayLength(env, jBA);
+ return rv;
+}
+
+#define s3jni_jbyteArray_bytes2(jByteArray,ptrToSz) \
+ s3jni__jbyteArray_bytes2(env, (jByteArray), (ptrToSz))
+#define s3jni_jbyteArray_bytes(jByteArray) s3jni__jbyteArray_bytes2(env, (jByteArray), 0)
+#define s3jni_jbyteArray_release(jByteArray,jBytes) \
+ if( jBytes ) (*env)->ReleaseByteArrayElements(env, jByteArray, jBytes, JNI_ABORT)
+#define s3jni_jbyteArray_commit(jByteArray,jBytes) \
+ if( jBytes ) (*env)->ReleaseByteArrayElements(env, jByteArray, jBytes, JNI_COMMIT)
+
+/*
+** If jbb is-a java.nio.Buffer object and the JNI environment supports
+** it, *pBuf is set to the buffer's memory and *pN is set to its
+** limit() (as opposed to its capacity()). If jbb is NULL, not a
+** Buffer, or the JNI environment does not support that operation,
+** *pBuf is set to 0 and *pN is set to 0.
+**
+** Note that the length of the buffer can be larger than SQLITE_LIMIT
+** but this function does not know what byte range of the buffer is
+** required so cannot check for that violation. The caller is required
+** to ensure that any to-be-bind()ed range fits within SQLITE_LIMIT.
+**
+** Sidebar: it is unfortunate that we cannot get ByteBuffer.limit()
+** via a JNI method like we can for ByteBuffer.capacity(). We instead
+** have to call back into Java to get the limit(). Depending on how
+** the ByteBuffer is used, the limit and capacity might be the same,
+** but when reusing a buffer, the limit may well change whereas the
+** capacity is fixed. The problem with, e.g., read()ing blob data to a
+** ByteBuffer's memory based on its capacity is that Java-level code
+** is restricted to accessing the range specified in
+** ByteBuffer.limit(). If we were to honor only the capacity, we
+** could end up writing to, or reading from, parts of a ByteBuffer
+** which client code itself cannot access without explicitly modifying
+** the limit. The penalty we pay for this correctness is that we must
+** call into Java to get the limit() of every ByteBuffer we work with.
+**
+** An alternative to having to call into ByteBuffer.limit() from here
+** would be to add private native impls of all ByteBuffer-using
+** methods, each of which adds a jint parameter which _must_ be set to
+** theBuffer.limit() by public Java APIs which use those private impls
+** to do the real work.
+*/
+static void s3jni__get_nio_buffer(JNIEnv * const env, jobject jbb, void **pBuf, jint * pN ){
+ *pBuf = 0;
+ *pN = 0;
+ if( jbb ){
+ *pBuf = (*env)->GetDirectBufferAddress(env, jbb);
+ if( *pBuf ){
+ /*
+ ** Maintenance reminder: do not use
+ ** (*env)->GetDirectBufferCapacity(env,jbb), even though it
+ ** would be much faster, for reasons explained in this
+ ** function's comments.
+ */
+ *pN = (*env)->CallIntMethod(env, jbb, SJG.g.byteBuffer.midLimit);
+ S3JniExceptionIsFatal("Error calling ByteBuffer.limit() method.");
+ }
+ }
+}
+#define s3jni_get_nio_buffer(JOBJ,vpOut,jpOut) \
+ s3jni__get_nio_buffer(env,(JOBJ),(vpOut),(jpOut))
+
+/*
+** Returns the current JNIEnv object. Fails fatally if it cannot find
+** the object.
+*/
+static JNIEnv * s3jni_env(void){
+ JNIEnv * env = 0;
+ if( (*SJG.jvm)->GetEnv(SJG.jvm, (void **)&env,
+ JNI_VERSION_1_8) ){
+ fprintf(stderr, "Fatal error: cannot get current JNIEnv.\n");
+ abort();
+ }
+ return env;
+}
+
+/*
+** Fetches the S3JniGlobal.envCache row for the given env, allocing a
+** row if needed. When a row is allocated, its state is initialized
+** insofar as possible. Calls (*env)->FatalError() if allocation of an
+** entry fails. That's hypothetically possible but "shouldn't happen."
+*/
+static S3JniEnv * S3JniEnv__get(JNIEnv * const env){
+ struct S3JniEnv * row;
+ S3JniEnv_mutex_enter;
+ row = SJG.envCache.aHead;
+ for( ; row; row = row->pNext ){
+ if( row->env == env ){
+ s3jni_incr( &SJG.metrics.nEnvHit );
+ S3JniEnv_mutex_leave;
+ return row;
+ }
+ }
+ s3jni_incr( &SJG.metrics.nEnvMiss );
+ row = SJG.envCache.aFree;
+ if( row ){
+ SJG.envCache.aFree = row->pNext;
+ }else{
+ row = s3jni_malloc_or_die(env, sizeof(*row));
+ s3jni_incr( &SJG.metrics.nEnvAlloc );
+ }
+ memset(row, 0, sizeof(*row));
+ row->pNext = SJG.envCache.aHead;
+ SJG.envCache.aHead = row;
+ row->env = env;
+
+ S3JniEnv_mutex_leave;
+ return row;
+}
+
+#define S3JniEnv_get() S3JniEnv__get(env)
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own Java/JNI bindings.
+**
+** For purposes of certain hand-crafted JNI function bindings, we
+** need a way of reporting errors which is consistent with the rest of
+** the C API, as opposed to throwing Java exceptions. To that end, this
+** internal-use-only function is a thin proxy around
+** sqlite3ErrorWithMessage(). The intent is that it only be used from
+** JNI bindings such as sqlite3_prepare_v2/v3(), and definitely not
+** from client code.
+**
+** Returns err_code.
+*/
+static int s3jni_db_error(sqlite3* const db, int err_code,
+ const char * const zMsg){
+ if( db!=0 ){
+ if( 0==zMsg ){
+ sqlite3Error(db, err_code);
+ }else{
+ const int nMsg = sqlite3Strlen30(zMsg);
+ sqlite3_mutex_enter(sqlite3_db_mutex(db));
+ sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg);
+ sqlite3_mutex_leave(sqlite3_db_mutex(db));
+ }
+ }
+ return err_code;
+}
+
+/*
+** Creates a new jByteArray of length nP, copies p's contents into it,
+** and returns that byte array (NULL on OOM unless fail-fast alloc
+** errors are enabled). p may be NULL, in which case the array is
+** created but no bytes are filled.
+*/
+static jbyteArray s3jni__new_jbyteArray(JNIEnv * const env,
+ const void * const p, int nP){
+ jbyteArray jba = (*env)->NewByteArray(env, (jint)nP);
+
+ s3jni_oom_check( jba );
+ if( jba && p ){
+ (*env)->SetByteArrayRegion(env, jba, 0, (jint)nP, (const jbyte*)p);
+ }
+ return jba;
+}
+
+#define s3jni_new_jbyteArray(P,n) s3jni__new_jbyteArray(env, P, n)
+
+
+/*
+** Uses the java.lang.String(byte[],Charset) constructor to create a
+** new String from UTF-8 string z. n is the number of bytes to
+** copy. If n<0 then sqlite3Strlen30() is used to calculate it.
+**
+** Returns NULL if z is NULL or on OOM, else returns a new jstring
+** owned by the caller.
+**
+** Sidebar: this is a painfully inefficient way to convert from
+** standard UTF-8 to a Java string, but JNI offers only algorithms for
+** working with MUTF-8, not UTF-8.
+*/
+static jstring s3jni__utf8_to_jstring(JNIEnv * const env,
+ const char * const z, int n){
+ jstring rv = NULL;
+ if( 0==n || (n<0 && z && !z[0]) ){
+ /* Fast-track the empty-string case via the MUTF-8 API. We could
+ hypothetically do this for any strings where n<4 and z is
+ NUL-terminated and none of z[0..3] are NUL bytes. */
+ rv = (*env)->NewStringUTF(env, "");
+ s3jni_oom_check( rv );
+ }else if( z ){
+ jbyteArray jba;
+ if( n<0 ) n = sqlite3Strlen30(z);
+ jba = s3jni_new_jbyteArray((unsigned const char *)z, n);
+ if( jba ){
+ rv = (*env)->NewObject(env, SJG.g.cString, SJG.g.ctorStringBA,
+ jba, SJG.g.oCharsetUtf8);
+ S3JniIfThrew{
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ }
+ S3JniUnrefLocal(jba);
+ }
+ s3jni_oom_check( rv );
+ }
+ return rv;
+}
+#define s3jni_utf8_to_jstring(CStr,n) s3jni__utf8_to_jstring(env, CStr, n)
+
+/*
+** Converts the given java.lang.String object into a NUL-terminated
+** UTF-8 C-string by calling jstr.getBytes(StandardCharset.UTF_8).
+** Returns NULL if jstr is NULL or on allocation error. If jstr is not
+** NULL and nLen is not NULL then nLen is set to the length of the
+** returned string, not including the terminating NUL. If jstr is not
+** NULL and it returns NULL, this indicates an allocation error. In
+** that case, if nLen is not NULL then it is either set to 0 (if
+** fetching of jstr's bytes fails to allocate) or set to what would
+** have been the length of the string had C-string allocation
+** succeeded.
+**
+** The returned memory is allocated from sqlite3_malloc() and
+** ownership is transferred to the caller.
+*/
+static char * s3jni__jstring_to_utf8(JNIEnv * const env,
+ jstring jstr, int *nLen){
+ jbyteArray jba;
+ jsize nBA;
+ char *rv;
+
+ if( !jstr ) return 0;
+ jba = (*env)->CallObjectMethod(env, jstr, SJG.g.stringGetBytes,
+ SJG.g.oCharsetUtf8);
+
+ if( (*env)->ExceptionCheck(env) || !jba
+ /* order of these checks is significant for -Xlint:jni */ ) {
+ S3JniExceptionReport;
+ s3jni_oom_check( jba );
+ if( nLen ) *nLen = 0;
+ return 0;
+ }
+ nBA = (*env)->GetArrayLength(env, jba);
+ if( nLen ) *nLen = (int)nBA;
+ rv = s3jni_malloc( nBA + 1 );
+ if( rv ){
+ (*env)->GetByteArrayRegion(env, jba, 0, nBA, (jbyte*)rv);
+ rv[nBA] = 0;
+ }
+ S3JniUnrefLocal(jba);
+ return rv;
+}
+#define s3jni_jstring_to_utf8(JStr,n) s3jni__jstring_to_utf8(env, JStr, n)
+
+/*
+** Expects to be passed a pointer from sqlite3_column_text16() or
+** sqlite3_value_text16() and a byte-length value from
+** sqlite3_column_bytes16() or sqlite3_value_bytes16(). It creates a
+** Java String of exactly half that character length, returning NULL
+** if !p or (*env)->NewString() fails.
+*/
+static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, int nP){
+ jstring const rv = p
+ ? (*env)->NewString(env, (const jchar *)p, (jsize)(nP/2))
+ : NULL;
+ s3jni_oom_check( p ? !!rv : 1 );
+ return rv;
+}
+
+/*
+** Creates a new ByteBuffer instance with a capacity of n. assert()s
+** that SJG.g.byteBuffer.klazz is not 0 and n>0.
+*/
+static jobject s3jni__new_ByteBuffer(JNIEnv * const env, int n){
+ jobject rv = 0;
+ assert( SJG.g.byteBuffer.klazz );
+ assert( SJG.g.byteBuffer.midAlloc );
+ assert( n > 0 );
+ rv = (*env)->CallStaticObjectMethod(env, SJG.g.byteBuffer.klazz,
+ SJG.g.byteBuffer.midAlloc, (jint)n);
+ S3JniIfThrew {
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ }
+ s3jni_oom_check( rv );
+ return rv;
+}
+
+/*
+** If n>0 and sqlite3_jni_supports_nio() is true then this creates a
+** new ByteBuffer object and copies n bytes from p to it. Returns NULL
+** if n is 0, sqlite3_jni_supports_nio() is false, or on allocation
+** error (unless fatal alloc failures are enabled).
+*/
+static jobject s3jni__blob_to_ByteBuffer(JNIEnv * const env,
+ const void * p, int n){
+ jobject rv = NULL;
+ assert( n >= 0 );
+ if( 0==n || !SJG.g.byteBuffer.klazz ){
+ return NULL;
+ }
+ rv = s3jni__new_ByteBuffer(env, n);
+ if( rv ){
+ void * tgt = (*env)->GetDirectBufferAddress(env, rv);
+ memcpy(tgt, p, (size_t)n);
+ }
+ return rv;
+}
+
+
+/*
+** Requires jx to be a Throwable. Calls its toString() method and
+** returns its value converted to a UTF-8 string. The caller owns the
+** returned string and must eventually sqlite3_free() it. Returns 0
+** if there is a problem fetching the info or on OOM.
+**
+** Design note: we use toString() instead of getMessage() because the
+** former includes the exception type's name:
+**
+** Exception e = new RuntimeException("Hi");
+** System.out.println(e.toString()); // java.lang.RuntimeException: Hi
+** System.out.println(e.getMessage()); // Hi
+*/
+static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx){
+ jmethodID mid;
+ jstring msg;
+ char * zMsg;
+ jclass const klazz = (*env)->GetObjectClass(env, jx);
+ mid = (*env)->GetMethodID(env, klazz, "toString", "()Ljava/lang/String;");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew{
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ return 0;
+ }
+ msg = (*env)->CallObjectMethod(env, jx, mid);
+ S3JniIfThrew{
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ return 0;
+ }
+ zMsg = s3jni_jstring_to_utf8( msg, 0);
+ S3JniUnrefLocal(msg);
+ return zMsg;
+}
+
+/*
+** Extracts env's current exception, sets ps->pDb's error message to
+** its message string, and clears the exception. If errCode is non-0,
+** it is used as-is, else SQLITE_ERROR is assumed. If there's a
+** problem extracting the exception's message, it's treated as
+** non-fatal and zDfltMsg is used in its place.
+**
+** Locks the global S3JniDb mutex.
+**
+** This must only be called if a JNI exception is pending.
+**
+** Returns errCode unless it is 0, in which case SQLITE_ERROR is
+** returned.
+*/
+static int s3jni__db_exception(JNIEnv * const env, sqlite3 * const pDb,
+ int errCode, const char *zDfltMsg){
+ jthrowable const ex = (*env)->ExceptionOccurred(env);
+
+ if( 0==errCode ) errCode = SQLITE_ERROR;
+ if( ex ){
+ char * zMsg;
+ S3JniExceptionClear;
+ zMsg = s3jni_exception_error_msg(env, ex);
+ s3jni_db_error(pDb, errCode, zMsg ? zMsg : zDfltMsg);
+ sqlite3_free(zMsg);
+ S3JniUnrefLocal(ex);
+ }else if( zDfltMsg ){
+ s3jni_db_error(pDb, errCode, zDfltMsg);
+ }
+ return errCode;
+}
+#define s3jni_db_exception(pDb,ERRCODE,DFLTMSG) \
+ s3jni__db_exception(env, (pDb), (ERRCODE), (DFLTMSG) )
+
+/*
+** Extracts the (void xDestroy()) method from jObj and applies it to
+** jObj. If jObj is NULL, this is a no-op. The lack of an xDestroy()
+** method is silently ignored. Any exceptions thrown by xDestroy()
+** trigger a warning to stdout or stderr and then the exception is
+** suppressed.
+*/
+static void s3jni__call_xDestroy(JNIEnv * const env, jobject jObj){
+ if( jObj ){
+ jclass const klazz = (*env)->GetObjectClass(env, jObj);
+ jmethodID method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V");
+
+ S3JniUnrefLocal(klazz);
+ if( method ){
+ s3jni_incr( &SJG.metrics.nDestroy );
+ (*env)->CallVoidMethod(env, jObj, method);
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("xDestroy() callback");
+ S3JniExceptionClear;
+ }
+ }else{
+ /* Non-fatal. */
+ S3JniExceptionClear;
+ }
+ }
+}
+#define s3jni_call_xDestroy(JOBJ) s3jni__call_xDestroy(env, (JOBJ))
+
+/*
+** Internal helper for many hook callback impls. Locks the S3JniDb
+** mutex, makes a copy of src into dest, with a some differences: (1)
+** if src->jObj or src->jExtra are not NULL then dest will be a new
+** LOCAL ref to it instead of a copy of the prior GLOBAL ref. (2)
+** dest->doXDestroy is always false.
+**
+** If dest->jObj is not NULL when this returns then the caller is
+** obligated to eventually free the new ref by passing *dest to
+** S3JniHook_localundup(). The dest pointer must NOT be passed to
+** S3JniHook_unref(), as that routine assumes that dest->jObj/jExtra
+** are GLOBAL refs (it's illegal to try to unref the wrong ref type).
+**
+** Background: when running a hook we need a call-local copy lest
+** another thread modify the hook while we're running it. That copy
+** has to have its own Java reference, but it need only be call-local.
+*/
+static void S3JniHook__localdup( JNIEnv * const env, S3JniHook const * const src,
+ S3JniHook * const dest ){
+ S3JniHook_mutex_enter;
+ *dest = *src;
+ if(src->jObj) dest->jObj = S3JniRefLocal(src->jObj);
+ if(src->jExtra) dest->jExtra = S3JniRefLocal(src->jExtra);
+ dest->doXDestroy = 0;
+ S3JniHook_mutex_leave;
+}
+#define S3JniHook_localdup(src,dest) S3JniHook__localdup(env,src,dest)
+
+static void S3JniHook__localundup( JNIEnv * const env, S3JniHook * const h ){
+ S3JniUnrefLocal(h->jObj);
+ S3JniUnrefLocal(h->jExtra);
+ *h = S3JniHook_empty;
+}
+#define S3JniHook_localundup(HOOK) S3JniHook__localundup(env, &(HOOK))
+
+/*
+** Removes any Java references from s and clears its state. If
+** doXDestroy is true and s->jObj is not NULL, s->jObj
+** is passed to s3jni_call_xDestroy() before any references are
+** cleared. It is legal to call this when the object has no Java
+** references. s must not be NULL.
+*/
+static void S3JniHook__unref(JNIEnv * const env, S3JniHook * const s){
+ if( s->jObj ){
+ if( s->doXDestroy ){
+ s3jni_call_xDestroy(s->jObj);
+ }
+ S3JniUnrefGlobal(s->jObj);
+ S3JniUnrefGlobal(s->jExtra);
+ }else{
+ assert( !s->jExtra );
+ }
+ *s = S3JniHook_empty;
+}
+#define S3JniHook_unref(hook) S3JniHook__unref(env, (hook))
+
+/*
+** Allocates one blank S3JniHook object from the recycling bin, if
+** available, else from the heap. Returns NULL or dies on OOM,
+** depending on build options. Locks on SJG.hooks.mutex.
+*/
+static S3JniHook *S3JniHook__alloc(JNIEnv * const env){
+ S3JniHook * p = 0;
+ S3JniHook_mutex_enter;
+ if( SJG.hook.aFree ){
+ p = SJG.hook.aFree;
+ SJG.hook.aFree = p->pNext;
+ p->pNext = 0;
+ s3jni_incr(&SJG.metrics.nHookRecycled);
+ }
+ S3JniHook_mutex_leave;
+ if( 0==p ){
+ p = s3jni_malloc(sizeof(S3JniHook));
+ if( p ){
+ s3jni_incr(&SJG.metrics.nHookAlloc);
+ }
+ }
+ if( p ){
+ *p = S3JniHook_empty;
+ }
+ return p;
+}
+#define S3JniHook_alloc() S3JniHook__alloc(env)
+
+/*
+** The rightful fate of all results from S3JniHook_alloc(). Locks on
+** SJG.hook.mutex.
+*/
+static void S3JniHook__free(JNIEnv * const env, S3JniHook * const p){
+ if(p){
+ assert( !p->pNext );
+ S3JniHook_unref(p);
+ S3JniHook_mutex_enter;
+ p->pNext = SJG.hook.aFree;
+ SJG.hook.aFree = p;
+ S3JniHook_mutex_leave;
+ }
+}
+#define S3JniHook_free(hook) S3JniHook__free(env, hook)
+
+#if 0
+/* S3JniHook__free() without the lock: caller must hold the global mutex */
+static void S3JniHook__free_unlocked(JNIEnv * const env, S3JniHook * const p){
+ if(p){
+ assert( !p->pNext );
+ assert( p->pNext != SJG.hook.aFree );
+ S3JniHook_unref(p);
+ p->pNext = SJG.hook.aFree;
+ SJG.hook.aFree = p;
+ }
+}
+#define S3JniHook_free_unlocked(hook) S3JniHook__free_unlocked(env, hook)
+#endif
+
+/*
+** Clears all of s's state. Requires that that the caller has locked
+** S3JniGlobal.perDb.mutex. Make sure to do anything needed with
+** s->pNext and s->pPrev before calling this, as this clears them.
+*/
+static void S3JniDb_clear(JNIEnv * const env, S3JniDb * const s){
+ S3JniDb_mutex_assertLocker;
+ sqlite3_free( s->zMainDbName );
+#define UNHOOK(MEMBER) \
+ S3JniHook_unref(&s->hooks.MEMBER)
+ UNHOOK(auth);
+ UNHOOK(busyHandler);
+ UNHOOK(collationNeeded);
+ UNHOOK(commit);
+ UNHOOK(progress);
+ UNHOOK(rollback);
+ UNHOOK(trace);
+ UNHOOK(update);
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ UNHOOK(preUpdate);
+#endif
+#undef UNHOOK
+ S3JniUnrefGlobal(s->jDb);
+ memset(s, 0, sizeof(S3JniDb));
+}
+
+/*
+** Clears s's state and moves it to the free-list. Requires that
+** S3JniGlobal.perDb.mutex is locked.
+*/
+static void S3JniDb__set_aside_unlocked(JNIEnv * const env, S3JniDb * const s){
+ assert( s );
+ S3JniDb_mutex_assertLocker;
+ if( s ){
+ S3JniDb_clear(env, s);
+ s->pNext = SJG.perDb.aFree;
+ SJG.perDb.aFree = s;
+ }
+}
+#define S3JniDb_set_aside_unlocked(JniDb) S3JniDb__set_aside_unlocked(env, JniDb)
+
+static void S3JniDb__set_aside(JNIEnv * const env, S3JniDb * const s){
+ S3JniDb_mutex_enter;
+ S3JniDb_set_aside_unlocked(s);
+ S3JniDb_mutex_leave;
+}
+#define S3JniDb_set_aside(JNIDB) S3JniDb__set_aside(env, JNIDB)
+
+/*
+** Uncache any state for the given JNIEnv, clearing all Java
+** references the cache owns. Returns true if env was cached and false
+** if it was not found in the cache. Ownership of the S3JniEnv object
+** associated with the given argument is transferred to this function,
+** which makes it free for re-use.
+**
+** Requires that the env mutex be locked.
+*/
+static int S3JniEnv_uncache(JNIEnv * const env){
+ struct S3JniEnv * row;
+ struct S3JniEnv * pPrev = 0;
+
+ S3JniEnv_mutex_assertLocked;
+ row = SJG.envCache.aHead;
+ for( ; row; pPrev = row, row = row->pNext ){
+ if( row->env == env ){
+ break;
+ }
+ }
+ if( !row ){
+ return 0;
+ }
+ if( pPrev) pPrev->pNext = row->pNext;
+ else{
+ assert( SJG.envCache.aHead == row );
+ SJG.envCache.aHead = row->pNext;
+ }
+ memset(row, 0, sizeof(S3JniEnv));
+ row->pNext = SJG.envCache.aFree;
+ SJG.envCache.aFree = row;
+ return 1;
+}
+
+/*
+** Fetches the given nph-ref from cache the cache and returns the
+** object with its klazz member set. This is an O(1) operation except
+** on the first call for a given pRef, during which pRef->klazz and
+** pRef->pRef are initialized thread-safely. In the latter case it's
+** still effectively O(1), but with a much longer 1.
+**
+** It is up to the caller to populate the other members of the
+** returned object if needed, taking care to lock the modification
+** with S3JniNph_mutex_enter/leave.
+**
+** This simple cache catches >99% of searches in the current
+** (2023-07-31) tests.
+*/
+static S3JniNphOp * s3jni__nphop(JNIEnv * const env, S3JniNphOp const* pRef){
+ S3JniNphOp * const pNC = &SJG.nph.list[pRef->index];
+
+ assert( (void*)pRef>=(void*)&S3JniNphOps && (void*)pRef<(void*)(&S3JniNphOps + 1)
+ && "pRef is out of range" );
+ assert( pRef->index>=0
+ && (pRef->index < (sizeof(S3JniNphOps) / sizeof(S3JniNphOp)))
+ && "pRef->index is out of range" );
+ if( !pNC->klazz ){
+ S3JniNph_mutex_enter;
+ if( !pNC->klazz ){
+ jclass const klazz = (*env)->FindClass(env, pRef->zName);
+ //printf("FindClass %s\n", pRef->zName);
+ S3JniExceptionIsFatal("FindClass() unexpectedly threw");
+ pNC->klazz = S3JniRefGlobal(klazz);
+ }
+ S3JniNph_mutex_leave;
+ }
+ assert( pNC->klazz );
+ return pNC;
+}
+
+#define s3jni_nphop(PRef) s3jni__nphop(env, PRef)
+
+/*
+** Common code for accessor functions for NativePointerHolder and
+** OutputPointer types. pRef must be a pointer from S3JniNphOps. jOut
+** must be an instance of that class (Java's type safety takes care of
+** that requirement). If necessary, this fetches the jfieldID for
+** jOut's pRef->zMember, which must be of the type represented by the
+** JNI type signature pRef->zTypeSig, and stores it in
+** S3JniGlobal.nph.list[pRef->index]. Fails fatally if the pRef->zMember
+** property is not found, as that presents a serious internal misuse.
+**
+** Property lookups are cached on a per-pRef basis.
+*/
+static jfieldID s3jni_nphop_field(JNIEnv * const env, S3JniNphOp const* pRef){
+ S3JniNphOp * const pNC = s3jni_nphop(pRef);
+
+ if( !pNC->fidValue ){
+ S3JniNph_mutex_enter;
+ if( !pNC->fidValue ){
+ pNC->fidValue = (*env)->GetFieldID(env, pNC->klazz,
+ pRef->zMember, pRef->zTypeSig);
+ S3JniExceptionIsFatal("Code maintenance required: missing "
+ "required S3JniNphOp::fidValue.");
+ }
+ S3JniNph_mutex_leave;
+ }
+ assert( pNC->fidValue );
+ return pNC->fidValue;
+}
+
+/*
+** Sets a native ptr value in NativePointerHolder object jNph,
+** which must be of the native type described by pRef. jNph
+** may not be NULL.
+*/
+static void NativePointerHolder__set(JNIEnv * const env, S3JniNphOp const* pRef,
+ jobject jNph, const void * p){
+ assert( jNph );
+ (*env)->SetLongField(env, jNph, s3jni_nphop_field(env, pRef),
+ S3JniCast_P2L(p));
+ S3JniExceptionIsFatal("Could not set NativePointerHolder.nativePointer.");
+}
+
+#define NativePointerHolder_set(PREF,JNPH,P) \
+ NativePointerHolder__set(env, PREF, JNPH, P)
+
+/*
+** Fetches a native ptr value from NativePointerHolder object jNph,
+** which must be of the native type described by pRef. This is a
+** no-op if jNph is NULL.
+*/
+static void * NativePointerHolder__get(JNIEnv * env, jobject jNph,
+ S3JniNphOp const* pRef){
+ void * rv = 0;
+ if( jNph ){
+ rv = S3JniCast_L2P(
+ (*env)->GetLongField(env, jNph, s3jni_nphop_field(env, pRef))
+ );
+ S3JniExceptionIsFatal("Cannot fetch NativePointerHolder.nativePointer.");
+ }
+ return rv;
+}
+
+#define NativePointerHolder_get(JOBJ,NPHREF) \
+ NativePointerHolder__get(env, (JOBJ), (NPHREF))
+
+/*
+** Helpers for extracting pointers from jobjects, noting that we rely
+** on the corresponding Java interfaces having already done the
+** type-checking. OBJ must be a jobject referring to a
+** NativePointerHolder<T>, where T matches PtrGet_T. Don't use these
+** in contexts where that's not the case. Note that these aren't
+** type-safe in the strictest sense:
+**
+** sqlite3 * s = PtrGet_sqlite3_stmt(...)
+**
+** will work, despite the incorrect macro name, so long as the
+** argument is a Java sqlite3 object, as this operation only has void
+** pointers to work with.
+*/
+#define PtrGet_T(T,JOBJ) (T*)NativePointerHolder_get((JOBJ), S3JniNph(T))
+#define PtrGet_sqlite3(JOBJ) PtrGet_T(sqlite3, (JOBJ))
+#define PtrGet_sqlite3_backup(JOBJ) PtrGet_T(sqlite3_backup, (JOBJ))
+#define PtrGet_sqlite3_blob(JOBJ) PtrGet_T(sqlite3_blob, (JOBJ))
+#define PtrGet_sqlite3_context(JOBJ) PtrGet_T(sqlite3_context, (JOBJ))
+#define PtrGet_sqlite3_stmt(JOBJ) PtrGet_T(sqlite3_stmt, (JOBJ))
+#define PtrGet_sqlite3_value(JOBJ) PtrGet_T(sqlite3_value, (JOBJ))
+/*
+** LongPtrGet_T(X,Y) expects X to be an unqualified sqlite3 struct
+** type name and Y to be a native pointer to such an object in the
+** form of a jlong value. The jlong is simply cast to (X*). This
+** approach is, as of 2023-09-27, supplanting the former approach. We
+** now do the native pointer extraction in the Java side, rather than
+** the C side, because it's reportedly significantly faster. The
+** intptr_t part here is necessary for compatibility with (at least)
+** ARM32.
+**
+** 2023-11-09: testing has not revealed any measurable performance
+** difference between the approach of passing type T to C compared to
+** passing pointer-to-T to C, and adding support for the latter
+** everywhere requires sigificantly more code. As of this writing, the
+** older/simpler approach is being applied except for (A) where the
+** newer approach has already been applied and (B) hot-spot APIs where
+** a difference of microseconds (i.e. below our testing measurement
+** threshold) might add up.
+*/
+#define LongPtrGet_T(T,JLongAsPtr) (T*)((intptr_t)((JLongAsPtr)))
+#define LongPtrGet_sqlite3(JLongAsPtr) LongPtrGet_T(sqlite3,(JLongAsPtr))
+#define LongPtrGet_sqlite3_backup(JLongAsPtr) LongPtrGet_T(sqlite3_backup,(JLongAsPtr))
+#define LongPtrGet_sqlite3_blob(JLongAsPtr) LongPtrGet_T(sqlite3_blob,(JLongAsPtr))
+#define LongPtrGet_sqlite3_stmt(JLongAsPtr) LongPtrGet_T(sqlite3_stmt,(JLongAsPtr))
+#define LongPtrGet_sqlite3_value(JLongAsPtr) LongPtrGet_T(sqlite3_value,(JLongAsPtr))
+/*
+** Extracts the new S3JniDb instance from the free-list, or allocates
+** one if needed, associates it with pDb, and returns. Returns NULL
+** on OOM. The returned object MUST, on success of the calling
+** operation, subsequently be associated with jDb via
+** NativePointerHolder_set() or freed using S3JniDb_set_aside().
+*/
+static S3JniDb * S3JniDb_alloc(JNIEnv * const env, jobject jDb){
+ S3JniDb * rv = 0;
+ S3JniDb_mutex_enter;
+ if( SJG.perDb.aFree ){
+ rv = SJG.perDb.aFree;
+ SJG.perDb.aFree = rv->pNext;
+ rv->pNext = 0;
+ s3jni_incr( &SJG.metrics.nPdbRecycled );
+ }
+ S3JniDb_mutex_leave;
+ if( 0==rv ){
+ rv = s3jni_malloc(sizeof(S3JniDb));
+ if( rv ){
+ s3jni_incr( &SJG.metrics.nPdbAlloc );
+ }
+ }
+ if( rv ){
+ memset(rv, 0, sizeof(S3JniDb));
+ rv->jDb = S3JniRefGlobal(jDb);
+ }
+ return rv;
+}
+
+/*
+** Returns the S3JniDb object for the given org.sqlite.jni.capi.sqlite3
+** object, or NULL if jDb is NULL, no pointer can be extracted
+** from it, or no matching entry can be found.
+*/
+static S3JniDb * S3JniDb__from_java(JNIEnv * const env, jobject jDb){
+ sqlite3 * const pDb = jDb ? PtrGet_sqlite3(jDb) : 0;
+ return pDb ? S3JniDb_from_clientdata(pDb) : 0;
+}
+#define S3JniDb_from_java(jObject) S3JniDb__from_java(env,(jObject))
+
+/*
+** S3JniDb finalizer for use with sqlite3_set_clientdata().
+*/
+static void S3JniDb_xDestroy(void *p){
+ S3JniDeclLocal_env;
+ S3JniDb * const ps = p;
+ assert( !ps->pNext && "Else ps is already in the free-list.");
+ S3JniDb_set_aside(ps);
+}
+
+/*
+** Evaluates to the S3JniDb object for the given sqlite3 object, or
+** NULL if pDb is NULL or was not initialized via the JNI interfaces.
+*/
+#define S3JniDb_from_c(sqlite3Ptr) \
+ ((sqlite3Ptr) ? S3JniDb_from_clientdata(sqlite3Ptr) : 0)
+#define S3JniDb_from_jlong(sqlite3PtrAsLong) \
+ S3JniDb_from_c(LongPtrGet_T(sqlite3,sqlite3PtrAsLong))
+
+/*
+** Unref any Java-side state in (S3JniAutoExtension*) AX and zero out
+** AX.
+*/
+#define S3JniAutoExtension_clear(AX) S3JniHook_unref(AX);
+
+/*
+** Initializes a pre-allocated S3JniAutoExtension object. Returns
+** non-0 if there is an error collecting the required state from
+** jAutoExt (which must be an AutoExtensionCallback object). On error,
+** it passes ax to S3JniAutoExtension_clear().
+*/
+static int S3JniAutoExtension_init(JNIEnv *const env,
+ S3JniAutoExtension * const ax,
+ jobject const jAutoExt){
+ jclass const klazz = (*env)->GetObjectClass(env, jAutoExt);
+
+ S3JniAutoExt_mutex_assertLocker;
+ *ax = S3JniHook_empty;
+ ax->midCallback = (*env)->GetMethodID(env, klazz, "call",
+ "(Lorg/sqlite/jni/capi/sqlite3;)I");
+ S3JniUnrefLocal(klazz);
+ S3JniExceptionWarnIgnore;
+ if( !ax->midCallback ){
+ S3JniAutoExtension_clear(ax);
+ return SQLITE_ERROR;
+ }
+ ax->jObj = S3JniRefGlobal(jAutoExt);
+ return 0;
+}
+
+/*
+** Sets the value property of the OutputPointer.Bool jOut object to
+** v.
+*/
+static void OutputPointer_set_Bool(JNIEnv * const env, jobject const jOut,
+ int v){
+ (*env)->SetBooleanField(env, jOut, s3jni_nphop_field(
+ env, S3JniNph(OutputPointer_Bool)
+ ), v ? JNI_TRUE : JNI_FALSE );
+ S3JniExceptionIsFatal("Cannot set OutputPointer.Bool.value");
+}
+
+/*
+** Sets the value property of the OutputPointer.Int32 jOut object to
+** v.
+*/
+static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut,
+ int v){
+ (*env)->SetIntField(env, jOut, s3jni_nphop_field(
+ env, S3JniNph(OutputPointer_Int32)
+ ), (jint)v);
+ S3JniExceptionIsFatal("Cannot set OutputPointer.Int32.value");
+}
+
+/*
+** Sets the value property of the OutputPointer.Int64 jOut object to
+** v.
+*/
+static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut,
+ jlong v){
+ (*env)->SetLongField(env, jOut, s3jni_nphop_field(
+ env, S3JniNph(OutputPointer_Int64)
+ ), v);
+ S3JniExceptionIsFatal("Cannot set OutputPointer.Int64.value");
+}
+
+/*
+** Internal helper for OutputPointer_set_TYPE() where TYPE is an
+** Object type.
+*/
+static void OutputPointer_set_obj(JNIEnv * const env,
+ S3JniNphOp const * const pRef,
+ jobject const jOut,
+ jobject v){
+ (*env)->SetObjectField(env, jOut, s3jni_nphop_field(env, pRef), v);
+ S3JniExceptionIsFatal("Cannot set OutputPointer.T.value");
+}
+
+#ifdef SQLITE_ENABLE_FTS5
+#if 0
+/*
+** Sets the value property of the OutputPointer.ByteArray jOut object
+** to v.
+*/
+static void OutputPointer_set_ByteArray(JNIEnv * const env, jobject const jOut,
+ jbyteArray const v){
+ OutputPointer_set_obj(env, S3JniNph(OutputPointer_ByteArray), jOut, v);
+}
+#endif
+#endif /* SQLITE_ENABLE_FTS5 */
+
+/*
+** Sets the value property of the OutputPointer.String jOut object to
+** v.
+*/
+static void OutputPointer_set_String(JNIEnv * const env, jobject const jOut,
+ jstring const v){
+ OutputPointer_set_obj(env, S3JniNph(OutputPointer_String), jOut, v);
+}
+
+/*
+** Returns true if eTextRep is a valid sqlite3 encoding constant, else
+** returns false.
+*/
+static int encodingTypeIsValid(int eTextRep){
+ switch( eTextRep ){
+ case SQLITE_UTF8: case SQLITE_UTF16:
+ case SQLITE_UTF16LE: case SQLITE_UTF16BE:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/* For use with sqlite3_result_pointer(), sqlite3_value_pointer(),
+ sqlite3_bind_java_object(), and sqlite3_column_java_object(). */
+static const char * const s3jni__value_jref_key = "org.sqlite.jni.capi.ResultJavaVal";
+
+/*
+** If v is not NULL, it must be a jobject global reference. Its
+** reference is relinquished.
+*/
+static void S3Jni_jobject_finalizer(void *v){
+ if( v ){
+ S3JniDeclLocal_env;
+ S3JniUnrefGlobal((jobject)v);
+ }
+}
+
+/*
+** Returns a new Java instance of the class referred to by pRef, which
+** MUST be interface-compatible with NativePointerHolder and MUST have
+** a no-arg constructor. The NativePointerHolder_set() method is
+** passed the new Java object (which must not be NULL) and pNative
+** (which may be NULL). Hypothetically returns NULL if Java fails to
+** allocate, but the JNI docs are not entirely clear on that detail.
+**
+** Always use a static pointer from the S3JniNphOps struct for the
+** 2nd argument.
+*/
+static jobject NativePointerHolder_new(JNIEnv * const env,
+ S3JniNphOp const * pRef,
+ const void * pNative){
+ jobject rv = 0;
+ S3JniNphOp * const pNC = s3jni_nphop(pRef);
+ if( !pNC->midCtor ){
+ S3JniNph_mutex_enter;
+ if( !pNC->midCtor ){
+ pNC->midCtor = (*env)->GetMethodID(env, pNC->klazz, "<init>", "()V");
+ S3JniExceptionIsFatal("Cannot find constructor for class.");
+ }
+ S3JniNph_mutex_leave;
+ }
+ rv = (*env)->NewObject(env, pNC->klazz, pNC->midCtor);
+ S3JniExceptionIsFatal("No-arg constructor threw.");
+ s3jni_oom_check(rv);
+ if( rv ) NativePointerHolder_set(pRef, rv, pNative);
+ return rv;
+}
+
+static inline jobject new_java_sqlite3(JNIEnv * const env, sqlite3 *sv){
+ return NativePointerHolder_new(env, S3JniNph(sqlite3), sv);
+}
+static inline jobject new_java_sqlite3_backup(JNIEnv * const env, sqlite3_backup *sv){
+ return NativePointerHolder_new(env, S3JniNph(sqlite3_backup), sv);
+}
+static inline jobject new_java_sqlite3_blob(JNIEnv * const env, sqlite3_blob *sv){
+ return NativePointerHolder_new(env, S3JniNph(sqlite3_blob), sv);
+}
+static inline jobject new_java_sqlite3_context(JNIEnv * const env, sqlite3_context *sv){
+ return NativePointerHolder_new(env, S3JniNph(sqlite3_context), sv);
+}
+static inline jobject new_java_sqlite3_stmt(JNIEnv * const env, sqlite3_stmt *sv){
+ return NativePointerHolder_new(env, S3JniNph(sqlite3_stmt), sv);
+}
+static inline jobject new_java_sqlite3_value(JNIEnv * const env, sqlite3_value *sv){
+ return NativePointerHolder_new(env, S3JniNph(sqlite3_value), sv);
+}
+
+/* Helper typedefs for UDF callback types. */
+typedef void (*udf_xFunc_f)(sqlite3_context*,int,sqlite3_value**);
+typedef void (*udf_xStep_f)(sqlite3_context*,int,sqlite3_value**);
+typedef void (*udf_xFinal_f)(sqlite3_context*);
+/*typedef void (*udf_xValue_f)(sqlite3_context*);*/
+/*typedef void (*udf_xInverse_f)(sqlite3_context*,int,sqlite3_value**);*/
+
+/*
+** Allocate a new S3JniUdf (User-defined Function) and associate it
+** with the SQLFunction-type jObj. Returns NULL on OOM. If the
+** returned object's type==UDF_UNKNOWN_TYPE then the type of UDF was
+** not unambiguously detected based on which callback members it has,
+** which falls into the category of user error.
+**
+** The caller must arrange for the returned object to eventually be
+** passed to S3JniUdf_free().
+*/
+static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){
+ S3JniUdf * s = 0;
+
+ S3JniGlobal_mutex_enter;
+ s3jni_incr(&SJG.metrics.nMutexUdf);
+ if( SJG.udf.aFree ){
+ s = SJG.udf.aFree;
+ SJG.udf.aFree = s->pNext;
+ s->pNext = 0;
+ s3jni_incr(&SJG.metrics.nUdfRecycled);
+ }
+ S3JniGlobal_mutex_leave;
+ if( !s ){
+ s = s3jni_malloc( sizeof(*s));
+ s3jni_incr(&SJG.metrics.nUdfAlloc);
+ }
+ if( s ){
+ const char * zFSI = /* signature for xFunc, xStep, xInverse */
+ "(Lorg/sqlite/jni/capi/sqlite3_context;[Lorg/sqlite/jni/capi/sqlite3_value;)V";
+ const char * zFV = /* signature for xFinal, xValue */
+ "(Lorg/sqlite/jni/capi/sqlite3_context;)V";
+ jclass const klazz = (*env)->GetObjectClass(env, jObj);
+
+ memset(s, 0, sizeof(*s));
+ s->jObj = S3JniRefGlobal(jObj);
+
+#define FGET(FuncName,FuncSig,Field) \
+ s->Field = (*env)->GetMethodID(env, klazz, FuncName, FuncSig); \
+ if( !s->Field ) (*env)->ExceptionClear(env)
+
+ FGET("xFunc", zFSI, jmidxFunc);
+ FGET("xStep", zFSI, jmidxStep);
+ FGET("xFinal", zFV, jmidxFinal);
+ FGET("xValue", zFV, jmidxValue);
+ FGET("xInverse", zFSI, jmidxInverse);
+#undef FGET
+
+ S3JniUnrefLocal(klazz);
+ if( s->jmidxFunc ) s->type = UDF_SCALAR;
+ else if( s->jmidxStep && s->jmidxFinal ){
+ s->type = (s->jmidxValue && s->jmidxInverse)
+ ? UDF_WINDOW : UDF_AGGREGATE;
+ }else{
+ s->type = UDF_UNKNOWN_TYPE;
+ }
+ }
+ return s;
+}
+
+/*
+** Frees up all resources owned by s, clears its state, then either
+** caches it for reuse (if cacheIt is true) or frees it. The former
+** requires locking the global mutex, so it must not be held when this
+** is called.
+*/
+static void S3JniUdf_free(JNIEnv * const env, S3JniUdf * const s,
+ int cacheIt){
+ assert( !s->pNext );
+ if( s->jObj ){
+ s3jni_call_xDestroy(s->jObj);
+ S3JniUnrefGlobal(s->jObj);
+ sqlite3_free(s->zFuncName);
+ assert( !s->pNext );
+ memset(s, 0, sizeof(*s));
+ }
+ if( cacheIt ){
+ S3JniGlobal_mutex_enter;
+ s->pNext = S3JniGlobal.udf.aFree;
+ S3JniGlobal.udf.aFree = s;
+ S3JniGlobal_mutex_leave;
+ }else{
+ sqlite3_free( s );
+ }
+}
+
+/* Finalizer for sqlite3_create_function() and friends. */
+static void S3JniUdf_finalizer(void * s){
+ S3JniUdf_free(s3jni_env(), (S3JniUdf*)s, 1);
+}
+
+/*
+** Helper for processing args to UDF handlers with signature
+** (sqlite3_context*,int,sqlite3_value**).
+*/
+typedef struct {
+ jobject jcx /* sqlite3_context */;
+ jobjectArray jargv /* sqlite3_value[] */;
+} udf_jargs;
+
+/*
+** Converts the given (cx, argc, argv) into arguments for the given
+** UDF, writing the result (Java wrappers for cx and argv) in the
+** final 2 arguments. Returns 0 on success, SQLITE_NOMEM on allocation
+** error. On error *jCx and *jArgv will be set to 0. The output
+** objects are of type org.sqlite.jni.capi.sqlite3_context and
+** array-of-org.sqlite.jni.capi.sqlite3_value, respectively.
+*/
+static int udf_args(JNIEnv *env,
+ sqlite3_context * const cx,
+ int argc, sqlite3_value**argv,
+ jobject * jCx, jobjectArray *jArgv){
+ jobjectArray ja = 0;
+ jobject jcx = new_java_sqlite3_context(env, cx);
+ jint i;
+ *jCx = 0;
+ *jArgv = 0;
+ if( !jcx ) goto error_oom;
+ ja = (*env)->NewObjectArray(
+ env, argc, s3jni_nphop(S3JniNph(sqlite3_value))->klazz,
+ NULL);
+ s3jni_oom_check( ja );
+ if( !ja ) goto error_oom;
+ for(i = 0; i < argc; ++i){
+ jobject jsv = new_java_sqlite3_value(env, argv[i]);
+ if( !jsv ) goto error_oom;
+ (*env)->SetObjectArrayElement(env, ja, i, jsv);
+ S3JniUnrefLocal(jsv)/*ja has a ref*/;
+ }
+ *jCx = jcx;
+ *jArgv = ja;
+ return 0;
+error_oom:
+ S3JniUnrefLocal(jcx);
+ S3JniUnrefLocal(ja);
+ return SQLITE_NOMEM;
+}
+
+/*
+** Requires that jCx and jArgv are sqlite3_context
+** resp. array-of-sqlite3_value values initialized by udf_args(). The
+** latter will be 0-and-NULL for UDF types with no arguments. This
+** function zeroes out the nativePointer member of jCx and each entry
+** in jArgv. This is a safety-net precaution to avoid undefined
+** behavior if a Java-side UDF holds a reference to its context or one
+** of its arguments. This MUST be called from any function which
+** successfully calls udf_args(), after calling the corresponding UDF
+** and checking its exception status, or which Java-wraps a
+** sqlite3_context for use with a UDF(ish) call. It MUST NOT be called
+** in any other case.
+*/
+static void udf_unargs(JNIEnv *env, jobject jCx, int argc, jobjectArray jArgv){
+ int i = 0;
+ assert(jCx);
+ NativePointerHolder_set(S3JniNph(sqlite3_context), jCx, 0);
+ for( ; i < argc; ++i ){
+ jobject jsv = (*env)->GetObjectArrayElement(env, jArgv, i);
+ /*
+ ** There is a potential Java-triggerable case of Undefined
+ ** Behavior here, but it would require intentional misuse of the
+ ** API:
+ **
+ ** If a Java UDF grabs an sqlite3_value from its argv and then
+ ** assigns that element to null, it becomes unreachable to us so
+ ** we cannot clear out its pointer. That Java-side object's
+ ** getNativePointer() will then refer to a stale value, so passing
+ ** it into (e.g.) sqlite3_value_SOMETHING() would invoke UB.
+ **
+ ** High-level wrappers can avoid that possibility if they do not
+ ** expose sqlite3_value directly to clients (as is the case in
+ ** org.sqlite.jni.wrapper1.SqlFunction).
+ **
+ ** One potential (but expensive) workaround for this would be to
+ ** privately store a duplicate argv array in each sqlite3_context
+ ** wrapper object, and clear the native pointers from that copy.
+ */
+ assert(jsv && "Someone illegally modified a UDF argument array.");
+ if( jsv ){
+ NativePointerHolder_set(S3JniNph(sqlite3_value), jsv, 0);
+ }
+ }
+}
+
+
+/*
+** Must be called immediately after a Java-side UDF callback throws.
+** If translateToErr is true then it sets the exception's message in
+** the result error using sqlite3_result_error(). If translateToErr is
+** false then it emits a warning that the function threw but should
+** not do so. In either case, it clears the exception state.
+**
+** Returns SQLITE_NOMEM if an allocation fails, else SQLITE_ERROR. In
+** the former case it calls sqlite3_result_error_nomem().
+*/
+static int udf_report_exception(JNIEnv * const env, int translateToErr,
+ sqlite3_context * cx,
+ const char *zFuncName, const char *zFuncType ){
+ jthrowable const ex = (*env)->ExceptionOccurred(env);
+ int rc = SQLITE_ERROR;
+
+ assert(ex && "This must only be called when a Java exception is pending.");
+ if( translateToErr ){
+ char * zMsg;
+ char * z;
+
+ S3JniExceptionClear;
+ zMsg = s3jni_exception_error_msg(env, ex);
+ z = sqlite3_mprintf("Client-defined SQL function %s.%s() threw: %s",
+ zFuncName ? zFuncName : "<unnamed>", zFuncType,
+ zMsg ? zMsg : "Unknown exception" );
+ sqlite3_free(zMsg);
+ if( z ){
+ sqlite3_result_error(cx, z, -1);
+ sqlite3_free(z);
+ }else{
+ sqlite3_result_error_nomem(cx);
+ rc = SQLITE_NOMEM;
+ }
+ }else{
+ S3JniExceptionWarnCallbackThrew("client-defined SQL function");
+ S3JniExceptionClear;
+ }
+ S3JniUnrefLocal(ex);
+ return rc;
+}
+
+/*
+** Sets up the state for calling a Java-side xFunc/xStep/xInverse()
+** UDF, calls it, and returns 0 on success.
+*/
+static int udf_xFSI(sqlite3_context* const pCx, int argc,
+ sqlite3_value** const argv, S3JniUdf * const s,
+ jmethodID xMethodID, const char * const zFuncType){
+ S3JniDeclLocal_env;
+ udf_jargs args = {0,0};
+ int rc = udf_args(env, pCx, argc, argv, &args.jcx, &args.jargv);
+
+ if( 0 == rc ){
+ (*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv);
+ S3JniIfThrew{
+ rc = udf_report_exception(env, 'F'==zFuncType[1]/*xFunc*/, pCx,
+ s->zFuncName, zFuncType);
+ }
+ udf_unargs(env, args.jcx, argc, args.jargv);
+ }
+ S3JniUnrefLocal(args.jcx);
+ S3JniUnrefLocal(args.jargv);
+ return rc;
+}
+
+/*
+** Sets up the state for calling a Java-side xFinal/xValue() UDF,
+** calls it, and returns 0 on success.
+*/
+static int udf_xFV(sqlite3_context* cx, S3JniUdf * s,
+ jmethodID xMethodID,
+ const char *zFuncType){
+ S3JniDeclLocal_env;
+ jobject jcx = new_java_sqlite3_context(env, cx);
+ int rc = 0;
+ int const isFinal = 'F'==zFuncType[1]/*xFinal*/;
+
+ if( jcx ){
+ (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx);
+ S3JniIfThrew{
+ rc = udf_report_exception(env, isFinal, cx, s->zFuncName,
+ zFuncType);
+ }
+ udf_unargs(env, jcx, 0, 0);
+ S3JniUnrefLocal(jcx);
+ }else{
+ if( isFinal ) sqlite3_result_error_nomem(cx);
+ rc = SQLITE_NOMEM;
+ }
+ return rc;
+}
+
+/* Proxy for C-to-Java xFunc. */
+static void udf_xFunc(sqlite3_context* cx, int argc,
+ sqlite3_value** argv){
+ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
+ s3jni_incr( &SJG.metrics.udf.nFunc );
+ udf_xFSI(cx, argc, argv, s, s->jmidxFunc, "xFunc");
+}
+/* Proxy for C-to-Java xStep. */
+static void udf_xStep(sqlite3_context* cx, int argc,
+ sqlite3_value** argv){
+ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
+ s3jni_incr( &SJG.metrics.udf.nStep );
+ udf_xFSI(cx, argc, argv, s, s->jmidxStep, "xStep");
+}
+/* Proxy for C-to-Java xFinal. */
+static void udf_xFinal(sqlite3_context* cx){
+ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
+ s3jni_incr( &SJG.metrics.udf.nFinal );
+ udf_xFV(cx, s, s->jmidxFinal, "xFinal");
+}
+/* Proxy for C-to-Java xValue. */
+static void udf_xValue(sqlite3_context* cx){
+ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
+ s3jni_incr( &SJG.metrics.udf.nValue );
+ udf_xFV(cx, s, s->jmidxValue, "xValue");
+}
+/* Proxy for C-to-Java xInverse. */
+static void udf_xInverse(sqlite3_context* cx, int argc,
+ sqlite3_value** argv){
+ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
+ s3jni_incr( &SJG.metrics.udf.nInverse );
+ udf_xFSI(cx, argc, argv, s, s->jmidxInverse, "xInverse");
+}
+
+
+////////////////////////////////////////////////////////////////////////
+// What follows is the JNI/C bindings. They are in alphabetical order
+// except for this macro-generated subset which are kept together
+// (alphabetized) here at the front...
+////////////////////////////////////////////////////////////////////////
+
+/** Create a trivial JNI wrapper for (int CName(void)). */
+#define WRAP_INT_VOID(JniNameSuffix,CName) \
+ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass){ \
+ return (jint)CName(); \
+ }
+/** Create a trivial JNI wrapper for (int CName(int)). */
+#define WRAP_INT_INT(JniNameSuffix,CName) \
+ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jint arg){ \
+ return (jint)CName((int)arg); \
+ }
+/*
+** Create a trivial JNI wrapper for (const mutf8_string *
+** CName(void)). This is only valid for functions which are known to
+** return ASCII or text which is equivalent in UTF-8 and MUTF-8.
+*/
+#define WRAP_MUTF8_VOID(JniNameSuffix,CName) \
+ JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass){ \
+ jstring const rv = (*env)->NewStringUTF( env, CName() ); \
+ s3jni_oom_check(rv); \
+ return rv; \
+ }
+/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*)). */
+#define WRAP_INT_STMT(JniNameSuffix,CName) \
+ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt){ \
+ return (jint)CName(LongPtrGet_sqlite3_stmt(jpStmt)); \
+ }
+/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*,int)). */
+#define WRAP_INT_STMT_INT(JniNameSuffix,CName) \
+ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt, jint n){ \
+ return (jint)CName(LongPtrGet_sqlite3_stmt(jpStmt), (int)n); \
+ }
+/** Create a trivial JNI wrapper for (boolean CName(sqlite3_stmt*)). */
+#define WRAP_BOOL_STMT(JniNameSuffix,CName) \
+ JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jobject jStmt){ \
+ return CName(PtrGet_sqlite3_stmt(jStmt)) ? JNI_TRUE : JNI_FALSE; \
+ }
+/** Create a trivial JNI wrapper for (jstring CName(sqlite3_stmt*,int)). */
+#define WRAP_STR_STMT_INT(JniNameSuffix,CName) \
+ JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt, jint ndx){ \
+ return s3jni_utf8_to_jstring( \
+ CName(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx), \
+ -1); \
+ }
+/** Create a trivial JNI wrapper for (boolean CName(sqlite3*)). */
+#define WRAP_BOOL_DB(JniNameSuffix,CName) \
+ JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \
+ return CName(LongPtrGet_sqlite3(jpDb)) ? JNI_TRUE : JNI_FALSE; \
+ }
+/** Create a trivial JNI wrapper for (int CName(sqlite3*)). */
+#define WRAP_INT_DB(JniNameSuffix,CName) \
+ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \
+ return (jint)CName(LongPtrGet_sqlite3(jpDb)); \
+ }
+/** Create a trivial JNI wrapper for (int64 CName(sqlite3*)). */
+#define WRAP_INT64_DB(JniNameSuffix,CName) \
+ JniDecl(jlong,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \
+ return (jlong)CName(LongPtrGet_sqlite3(jpDb)); \
+ }
+/** Create a trivial JNI wrapper for (jstring CName(sqlite3*,int)). */
+#define WRAP_STR_DB_INT(JniNameSuffix,CName) \
+ JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass, jlong jpDb, jint ndx){ \
+ return s3jni_utf8_to_jstring( \
+ CName(LongPtrGet_sqlite3(jpDb), (int)ndx), \
+ -1); \
+ }
+/** Create a trivial JNI wrapper for (int CName(sqlite3_value*)). */
+#define WRAP_INT_SVALUE(JniNameSuffix,CName,DfltOnNull) \
+ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpSValue){ \
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSValue); \
+ return (jint)(sv ? CName(sv): DfltOnNull); \
+ }
+/** Create a trivial JNI wrapper for (boolean CName(sqlite3_value*)). */
+#define WRAP_BOOL_SVALUE(JniNameSuffix,CName,DfltOnNull) \
+ JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jlong jpSValue){ \
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSValue); \
+ return (jint)(sv ? CName(sv) : DfltOnNull) \
+ ? JNI_TRUE : JNI_FALSE; \
+ }
+
+WRAP_INT_DB(1changes, sqlite3_changes)
+WRAP_INT64_DB(1changes64, sqlite3_changes64)
+WRAP_INT_STMT(1clear_1bindings, sqlite3_clear_bindings)
+WRAP_INT_STMT_INT(1column_1bytes, sqlite3_column_bytes)
+WRAP_INT_STMT_INT(1column_1bytes16, sqlite3_column_bytes16)
+WRAP_INT_STMT(1column_1count, sqlite3_column_count)
+WRAP_STR_STMT_INT(1column_1decltype, sqlite3_column_decltype)
+WRAP_STR_STMT_INT(1column_1name, sqlite3_column_name)
+#ifdef SQLITE_ENABLE_COLUMN_METADATA
+WRAP_STR_STMT_INT(1column_1database_1name, sqlite3_column_database_name)
+WRAP_STR_STMT_INT(1column_1origin_1name, sqlite3_column_origin_name)
+WRAP_STR_STMT_INT(1column_1table_1name, sqlite3_column_table_name)
+#endif
+WRAP_INT_STMT_INT(1column_1type, sqlite3_column_type)
+WRAP_INT_STMT(1data_1count, sqlite3_data_count)
+WRAP_STR_DB_INT(1db_1name, sqlite3_db_name)
+WRAP_INT_DB(1error_1offset, sqlite3_error_offset)
+WRAP_INT_DB(1extended_1errcode, sqlite3_extended_errcode)
+WRAP_BOOL_DB(1get_1autocommit, sqlite3_get_autocommit)
+WRAP_MUTF8_VOID(1libversion, sqlite3_libversion)
+WRAP_INT_VOID(1libversion_1number, sqlite3_libversion_number)
+WRAP_INT_VOID(1keyword_1count, sqlite3_keyword_count)
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+WRAP_INT_DB(1preupdate_1blobwrite, sqlite3_preupdate_blobwrite)
+WRAP_INT_DB(1preupdate_1count, sqlite3_preupdate_count)
+WRAP_INT_DB(1preupdate_1depth, sqlite3_preupdate_depth)
+#endif
+WRAP_INT_INT(1release_1memory, sqlite3_release_memory)
+WRAP_INT_INT(1sleep, sqlite3_sleep)
+WRAP_MUTF8_VOID(1sourceid, sqlite3_sourceid)
+WRAP_BOOL_STMT(1stmt_1busy, sqlite3_stmt_busy)
+WRAP_INT_STMT_INT(1stmt_1explain, sqlite3_stmt_explain)
+WRAP_INT_STMT(1stmt_1isexplain, sqlite3_stmt_isexplain)
+WRAP_BOOL_STMT(1stmt_1readonly, sqlite3_stmt_readonly)
+WRAP_INT_DB(1system_1errno, sqlite3_system_errno)
+WRAP_INT_VOID(1threadsafe, sqlite3_threadsafe)
+WRAP_INT_DB(1total_1changes, sqlite3_total_changes)
+WRAP_INT64_DB(1total_1changes64, sqlite3_total_changes64)
+WRAP_INT_SVALUE(1value_1encoding, sqlite3_value_encoding,SQLITE_UTF8)
+WRAP_BOOL_SVALUE(1value_1frombind, sqlite3_value_frombind,0)
+WRAP_INT_SVALUE(1value_1nochange, sqlite3_value_nochange,0)
+WRAP_INT_SVALUE(1value_1numeric_1type, sqlite3_value_numeric_type,SQLITE_NULL)
+WRAP_INT_SVALUE(1value_1subtype, sqlite3_value_subtype,0)
+WRAP_INT_SVALUE(1value_1type, sqlite3_value_type,SQLITE_NULL)
+
+#undef WRAP_BOOL_DB
+#undef WRAP_BOOL_STMT
+#undef WRAP_BOOL_SVALUE
+#undef WRAP_INT64_DB
+#undef WRAP_INT_DB
+#undef WRAP_INT_INT
+#undef WRAP_INT_STMT
+#undef WRAP_INT_STMT_INT
+#undef WRAP_INT_SVALUE
+#undef WRAP_INT_VOID
+#undef WRAP_MUTF8_VOID
+#undef WRAP_STR_STMT_INT
+#undef WRAP_STR_DB_INT
+
+S3JniApi(sqlite3_aggregate_context(),jlong,1aggregate_1context)(
+ JniArgsEnvClass, jobject jCx, jboolean initialize
+){
+ sqlite3_context * const pCx = PtrGet_sqlite3_context(jCx);
+ void * const p = pCx
+ ? sqlite3_aggregate_context(pCx, (int)(initialize
+ ? (int)sizeof(void*)
+ : 0))
+ : 0;
+ return S3JniCast_P2L(p);
+}
+
+/*
+** Central auto-extension runner for auto-extensions created in Java.
+*/
+static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr,
+ const struct sqlite3_api_routines *ignored){
+ int rc = 0;
+ unsigned i, go = 1;
+ JNIEnv * env = 0;
+ S3JniDb * ps;
+ S3JniEnv * jc;
+
+ if( 0==SJG.autoExt.nExt ) return 0;
+ env = s3jni_env();
+ jc = S3JniEnv_get();
+ S3JniDb_mutex_enter;
+ ps = jc->pdbOpening ? jc->pdbOpening : S3JniDb_from_c(pDb);
+ if( !ps ){
+ *pzErr = sqlite3_mprintf("Unexpected arrival of null S3JniDb in "
+ "auto-extension runner.");
+ S3JniDb_mutex_leave;
+ return SQLITE_ERROR;
+ }
+ assert( ps->jDb );
+ if( !ps->pDb ){
+ assert( jc->pdbOpening == ps );
+ rc = sqlite3_set_clientdata(pDb, S3JniDb_clientdata_key,
+ ps, 0/* we'll re-set this after open()
+ completes. */);
+ if( rc ){
+ S3JniDb_mutex_leave;
+ return rc;
+ }
+ }
+ else{
+ assert( ps == jc->pdbOpening );
+ jc->pdbOpening = 0;
+ }
+ S3JniDb_mutex_leave;
+ NativePointerHolder_set(S3JniNph(sqlite3), ps->jDb, pDb)
+ /* As of here, the Java/C connection is complete except for the
+ (temporary) lack of finalizer for the ps object. */;
+ ps->pDb = pDb;
+ for( i = 0; go && 0==rc; ++i ){
+ S3JniAutoExtension ax = S3JniHook_empty
+ /* We need a copy of the auto-extension object, with our own
+ ** local reference to it, to avoid a race condition with another
+ ** thread manipulating the list during the call and invaliding
+ ** what ax references. */;
+ S3JniAutoExt_mutex_enter;
+ if( i >= SJG.autoExt.nExt ){
+ go = 0;
+ }else{
+ S3JniHook_localdup(&SJG.autoExt.aExt[i], &ax);
+ }
+ S3JniAutoExt_mutex_leave;
+ if( ax.jObj ){
+ rc = (*env)->CallIntMethod(env, ax.jObj, ax.midCallback, ps->jDb);
+ S3JniHook_localundup(ax);
+ S3JniIfThrew {
+ jthrowable const ex = (*env)->ExceptionOccurred(env);
+ char * zMsg;
+ S3JniExceptionClear;
+ zMsg = s3jni_exception_error_msg(env, ex);
+ S3JniUnrefLocal(ex);
+ *pzErr = sqlite3_mprintf("auto-extension threw: %s", zMsg);
+ sqlite3_free(zMsg);
+ rc = SQLITE_ERROR;
+ }
+ }
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_auto_extension(),jint,1auto_1extension)(
+ JniArgsEnvClass, jobject jAutoExt
+){
+ int i;
+ S3JniAutoExtension * ax = 0;
+ int rc = 0;
+
+ if( !jAutoExt ) return SQLITE_MISUSE;
+ S3JniAutoExt_mutex_enter;
+ for( i = 0; i < SJG.autoExt.nExt; ++i ){
+ /* Look for a match. */
+ ax = &SJG.autoExt.aExt[i];
+ if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
+ /* same object, so this is a no-op. */
+ S3JniAutoExt_mutex_leave;
+ return 0;
+ }
+ }
+ if( i == SJG.autoExt.nExt ){
+ assert( SJG.autoExt.nExt <= SJG.autoExt.nAlloc );
+ if( SJG.autoExt.nExt == SJG.autoExt.nAlloc ){
+ /* Allocate another slot. */
+ unsigned n = 1 + SJG.autoExt.nAlloc;
+ S3JniAutoExtension * const aNew =
+ s3jni_realloc( SJG.autoExt.aExt, n * sizeof(*ax) );
+ if( !aNew ){
+ rc = SQLITE_NOMEM;
+ }else{
+ SJG.autoExt.aExt = aNew;
+ ++SJG.autoExt.nAlloc;
+ }
+ }
+ if( 0==rc ){
+ ax = &SJG.autoExt.aExt[SJG.autoExt.nExt];
+ rc = S3JniAutoExtension_init(env, ax, jAutoExt);
+ assert( rc ? (0==ax->jObj && 0==ax->midCallback)
+ : (0!=ax->jObj && 0!=ax->midCallback) );
+ }
+ }
+ if( 0==rc ){
+ static int once = 0;
+ if( 0==once && ++once ){
+ rc = sqlite3_auto_extension(
+ (void(*)(void))s3jni_run_java_auto_extensions
+ /* Reminder: the JNI binding of sqlite3_reset_auto_extension()
+ ** does not call the core-lib impl. It only clears Java-side
+ ** auto-extensions. */
+ );
+ if( rc ){
+ assert( ax );
+ S3JniAutoExtension_clear(ax);
+ }
+ }
+ if( 0==rc ){
+ ++SJG.autoExt.nExt;
+ }
+ }
+ S3JniAutoExt_mutex_leave;
+ return rc;
+}
+
+S3JniApi(sqlite3_backup_finish(),jint,1backup_1finish)(
+ JniArgsEnvClass, jlong jpBack
+){
+ int rc = 0;
+ if( jpBack!=0 ){
+ rc = sqlite3_backup_finish( LongPtrGet_sqlite3_backup(jpBack) );
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_backup_init(),jobject,1backup_1init)(
+ JniArgsEnvClass, jlong jpDbDest, jstring jTDest,
+ jlong jpDbSrc, jstring jTSrc
+){
+ sqlite3 * const pDest = LongPtrGet_sqlite3(jpDbDest);
+ sqlite3 * const pSrc = LongPtrGet_sqlite3(jpDbSrc);
+ char * const zDest = s3jni_jstring_to_utf8(jTDest, 0);
+ char * const zSrc = s3jni_jstring_to_utf8(jTSrc, 0);
+ jobject rv = 0;
+
+ if( pDest && pSrc && zDest && zSrc ){
+ sqlite3_backup * const pB =
+ sqlite3_backup_init(pDest, zDest, pSrc, zSrc);
+ if( pB ){
+ rv = new_java_sqlite3_backup(env, pB);
+ if( !rv ){
+ sqlite3_backup_finish( pB );
+ }
+ }
+ }
+ sqlite3_free(zDest);
+ sqlite3_free(zSrc);
+ return rv;
+}
+
+S3JniApi(sqlite3_backup_pagecount(),jint,1backup_1pagecount)(
+ JniArgsEnvClass, jlong jpBack
+){
+ return sqlite3_backup_pagecount(LongPtrGet_sqlite3_backup(jpBack));
+}
+
+S3JniApi(sqlite3_backup_remaining(),jint,1backup_1remaining)(
+ JniArgsEnvClass, jlong jpBack
+){
+ return sqlite3_backup_remaining(LongPtrGet_sqlite3_backup(jpBack));
+}
+
+S3JniApi(sqlite3_backup_step(),jint,1backup_1step)(
+ JniArgsEnvClass, jlong jpBack, jint nPage
+){
+ return sqlite3_backup_step(LongPtrGet_sqlite3_backup(jpBack), (int)nPage);
+}
+
+S3JniApi(sqlite3_bind_blob(),jint,1bind_1blob)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jbyteArray baData, jint nMax
+){
+ jsize nBA = 0;
+ jbyte * const pBuf = baData ? s3jni_jbyteArray_bytes2(baData, &nBA) : 0;
+ int rc;
+ if( pBuf ){
+ if( nMax>nBA ){
+ nMax = nBA;
+ }
+ rc = sqlite3_bind_blob(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx,
+ pBuf, (int)nMax, SQLITE_TRANSIENT);
+ s3jni_jbyteArray_release(baData, pBuf);
+ }else{
+ rc = baData
+ ? SQLITE_NOMEM
+ : sqlite3_bind_null( LongPtrGet_sqlite3_stmt(jpStmt), ndx );
+ }
+ return (jint)rc;
+}
+
+/**
+ Helper for use with s3jni_setup_nio_args().
+*/
+struct S3JniNioArgs {
+ jobject jBuf; /* input - ByteBuffer */
+ jint iOffset; /* input - byte offset */
+ jint iHowMany; /* input - byte count to bind/read/write */
+ jint nBuf; /* output - jBuf's buffer size */
+ void * p; /* output - jBuf's buffer memory */
+ void * pStart; /* output - offset of p to bind/read/write */
+ int nOut; /* output - number of bytes from pStart to bind/read/write */
+};
+typedef struct S3JniNioArgs S3JniNioArgs;
+static const S3JniNioArgs S3JniNioArgs_empty = {
+ 0,0,0,0,0,0,0
+};
+
+/*
+** Internal helper for sqlite3_bind_nio_buffer(),
+** sqlite3_result_nio_buffer(), and similar methods which take a
+** ByteBuffer object as either input or output. Populates pArgs and
+** returns 0 on success, non-0 if the operation should fail. The
+** caller is required to check for SJG.g.byteBuffer.klazz!=0 before calling
+** this and reporting it in a way appropriate for that routine. This
+** function may assert() that SJG.g.byteBuffer.klazz is not 0.
+**
+** The (jBuffer, iOffset, iHowMany) arguments are the (ByteBuffer, offset,
+** length) arguments to the bind/result method.
+**
+** If iHowMany is negative then it's treated as "until the end" and
+** the calculated slice is trimmed to fit if needed. If iHowMany is
+** positive and extends past the end of jBuffer then SQLITE_ERROR is
+** returned.
+**
+** Returns 0 if everything looks to be in order, else some SQLITE_...
+** result code
+*/
+static int s3jni_setup_nio_args(
+ JNIEnv *env, S3JniNioArgs * pArgs,
+ jobject jBuffer, jint iOffset, jint iHowMany
+){
+ jlong iEnd = 0;
+ const int bAllowTruncate = iHowMany<0;
+ *pArgs = S3JniNioArgs_empty;
+ pArgs->jBuf = jBuffer;
+ pArgs->iOffset = iOffset;
+ pArgs->iHowMany = iHowMany;
+ assert( SJG.g.byteBuffer.klazz );
+ if( pArgs->iOffset<0 ){
+ return SQLITE_ERROR
+ /* SQLITE_MISUSE or SQLITE_RANGE would fit better but we use
+ SQLITE_ERROR for consistency with the code documented for a
+ negative target blob offset in sqlite3_blob_read/write(). */;
+ }
+ s3jni_get_nio_buffer(pArgs->jBuf, &pArgs->p, &pArgs->nBuf);
+ if( !pArgs->p ){
+ return SQLITE_MISUSE;
+ }else if( pArgs->iOffset>=pArgs->nBuf ){
+ pArgs->pStart = 0;
+ pArgs->nOut = 0;
+ return 0;
+ }
+ assert( pArgs->nBuf > 0 );
+ assert( pArgs->iOffset < pArgs->nBuf );
+ iEnd = pArgs->iHowMany<0
+ ? pArgs->nBuf - pArgs->iOffset
+ : pArgs->iOffset + pArgs->iHowMany;
+ if( iEnd>(jlong)pArgs->nBuf ){
+ if( bAllowTruncate ){
+ iEnd = pArgs->nBuf - pArgs->iOffset;
+ }else{
+ return SQLITE_ERROR
+ /* again: for consistency with blob_read/write(), though
+ SQLITE_MISUSE or SQLITE_RANGE would be a better fit. */;
+ }
+ }
+ if( iEnd - pArgs->iOffset > (jlong)SQLITE_MAX_LENGTH ){
+ return SQLITE_TOOBIG;
+ }
+ assert( pArgs->iOffset >= 0 );
+ assert( iEnd > pArgs->iOffset );
+ pArgs->pStart = pArgs->p + pArgs->iOffset;
+ pArgs->nOut = (int)(iEnd - pArgs->iOffset);
+ assert( pArgs->nOut > 0 );
+ assert( (pArgs->pStart + pArgs->nOut) <= (pArgs->p + pArgs->nBuf) );
+ return 0;
+}
+
+S3JniApi(sqlite3_bind_nio_buffer(),jint,1bind_1nio_1buffer)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx, jobject jBuffer,
+ jint iOffset, jint iN
+){
+ sqlite3_stmt * pStmt = PtrGet_sqlite3_stmt(jpStmt);
+ S3JniNioArgs args;
+ int rc;
+ if( !pStmt || !SJG.g.byteBuffer.klazz ) return SQLITE_MISUSE;
+ rc = s3jni_setup_nio_args(env, &args, jBuffer, iOffset, iN);
+ if(rc){
+ return rc;
+ }else if( !args.pStart || !args.nOut ){
+ return sqlite3_bind_null(pStmt, ndx);
+ }
+ return sqlite3_bind_blob( pStmt, (int)ndx, args.pStart,
+ args.nOut, SQLITE_TRANSIENT );
+}
+
+S3JniApi(sqlite3_bind_double(),jint,1bind_1double)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jdouble val
+){
+ return (jint)sqlite3_bind_double(LongPtrGet_sqlite3_stmt(jpStmt),
+ (int)ndx, (double)val);
+}
+
+S3JniApi(sqlite3_bind_int(),jint,1bind_1int)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jint val
+){
+ return (jint)sqlite3_bind_int(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)val);
+}
+
+S3JniApi(sqlite3_bind_int64(),jint,1bind_1int64)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jlong val
+){
+ return (jint)sqlite3_bind_int64(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val);
+}
+
+/*
+** Bind a new global ref to Object `val` using sqlite3_bind_pointer().
+*/
+S3JniApi(sqlite3_bind_java_object(),jint,1bind_1java_1object)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jobject val
+){
+ sqlite3_stmt * const pStmt = LongPtrGet_sqlite3_stmt(jpStmt);
+ int rc = SQLITE_MISUSE;
+
+ if(pStmt){
+ jobject const rv = S3JniRefGlobal(val);
+ if( rv ){
+ rc = sqlite3_bind_pointer(pStmt, ndx, rv, s3jni__value_jref_key,
+ S3Jni_jobject_finalizer);
+ }else if(val){
+ rc = SQLITE_NOMEM;
+ }else{
+ rc = sqlite3_bind_null(pStmt, ndx);
+ }
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_bind_null(),jint,1bind_1null)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx
+){
+ return (jint)sqlite3_bind_null(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx);
+}
+
+S3JniApi(sqlite3_bind_parameter_count(),jint,1bind_1parameter_1count)(
+ JniArgsEnvClass, jlong jpStmt
+){
+ return (jint)sqlite3_bind_parameter_count(LongPtrGet_sqlite3_stmt(jpStmt));
+}
+
+S3JniApi(sqlite3_bind_parameter_index(),jint,1bind_1parameter_1index)(
+ JniArgsEnvClass, jlong jpStmt, jbyteArray jName
+){
+ int rc = 0;
+ jbyte * const pBuf = s3jni_jbyteArray_bytes(jName);
+ if( pBuf ){
+ rc = sqlite3_bind_parameter_index(LongPtrGet_sqlite3_stmt(jpStmt),
+ (const char *)pBuf);
+ s3jni_jbyteArray_release(jName, pBuf);
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_bind_parameter_name(),jstring,1bind_1parameter_1name)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx
+){
+ const char *z =
+ sqlite3_bind_parameter_name(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx);
+ return z ? s3jni_utf8_to_jstring(z, -1) : 0;
+}
+
+/*
+** Impl of sqlite3_bind_text/text16().
+*/
+static int s3jni__bind_text(int is16, JNIEnv *env, jlong jpStmt, jint ndx,
+ jbyteArray baData, jint nMax){
+ jsize nBA = 0;
+ jbyte * const pBuf =
+ baData ? s3jni_jbyteArray_bytes2(baData, &nBA) : 0;
+ int rc;
+ if( pBuf ){
+ if( nMax>nBA ){
+ nMax = nBA;
+ }
+ /* Note that we rely on the Java layer having assured that baData
+ is NUL-terminated if nMax is negative. In order to avoid UB for
+ such cases, we do not expose the byte-limit arguments in the
+ public API. */
+ rc = is16
+ ? sqlite3_bind_text16(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx,
+ pBuf, (int)nMax, SQLITE_TRANSIENT)
+ : sqlite3_bind_text(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx,
+ (const char *)pBuf,
+ (int)nMax, SQLITE_TRANSIENT);
+ }else{
+ rc = baData
+ ? sqlite3_bind_null(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx)
+ : SQLITE_NOMEM;
+ }
+ s3jni_jbyteArray_release(baData, pBuf);
+ return (jint)rc;
+
+}
+
+S3JniApi(sqlite3_bind_text(),jint,1bind_1text)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jbyteArray baData, jint nMax
+){
+ return s3jni__bind_text(0, env, jpStmt, ndx, baData, nMax);
+}
+
+S3JniApi(sqlite3_bind_text16(),jint,1bind_1text16)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jbyteArray baData, jint nMax
+){
+ return s3jni__bind_text(1, env, jpStmt, ndx, baData, nMax);
+}
+
+S3JniApi(sqlite3_bind_value(),jint,1bind_1value)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jlong jpValue
+){
+ int rc = 0;
+ sqlite3_stmt * pStmt = LongPtrGet_sqlite3_stmt(jpStmt);
+ if( pStmt ){
+ sqlite3_value *v = LongPtrGet_sqlite3_value(jpValue);
+ if( v ){
+ rc = sqlite3_bind_value(pStmt, (int)ndx, v);
+ }else{
+ rc = sqlite3_bind_null(pStmt, (int)ndx);
+ }
+ }else{
+ rc = SQLITE_MISUSE;
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_bind_zeroblob(),jint,1bind_1zeroblob)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jint n
+){
+ return (jint)sqlite3_bind_zeroblob(LongPtrGet_sqlite3_stmt(jpStmt),
+ (int)ndx, (int)n);
+}
+
+S3JniApi(sqlite3_bind_zeroblob64(),jint,1bind_1zeroblob64)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jlong n
+){
+ return (jint)sqlite3_bind_zeroblob64(LongPtrGet_sqlite3_stmt(jpStmt),
+ (int)ndx, (sqlite3_uint64)n);
+}
+
+S3JniApi(sqlite3_blob_bytes(),jint,1blob_1bytes)(
+ JniArgsEnvClass, jlong jpBlob
+){
+ return sqlite3_blob_bytes(LongPtrGet_sqlite3_blob(jpBlob));
+}
+
+S3JniApi(sqlite3_blob_close(),jint,1blob_1close)(
+ JniArgsEnvClass, jlong jpBlob
+){
+ sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob);
+ return b ? (jint)sqlite3_blob_close(b) : SQLITE_MISUSE;
+}
+
+S3JniApi(sqlite3_blob_open(),jint,1blob_1open)(
+ JniArgsEnvClass, jlong jpDb, jstring jDbName, jstring jTbl, jstring jCol,
+ jlong jRowId, jint flags, jobject jOut
+){
+ sqlite3 * const db = LongPtrGet_sqlite3(jpDb);
+ sqlite3_blob * pBlob = 0;
+ char * zDbName = 0, * zTableName = 0, * zColumnName = 0;
+ int rc;
+
+ if( !db || !jDbName || !jTbl || !jCol ) return SQLITE_MISUSE;
+ zDbName = s3jni_jstring_to_utf8(jDbName,0);
+ zTableName = zDbName ? s3jni_jstring_to_utf8(jTbl,0) : 0;
+ zColumnName = zTableName ? s3jni_jstring_to_utf8(jCol,0) : 0;
+ rc = zColumnName
+ ? sqlite3_blob_open(db, zDbName, zTableName, zColumnName,
+ (sqlite3_int64)jRowId, (int)flags, &pBlob)
+ : SQLITE_NOMEM;
+ if( 0==rc ){
+ jobject rv = new_java_sqlite3_blob(env, pBlob);
+ if( !rv ){
+ sqlite3_blob_close(pBlob);
+ rc = SQLITE_NOMEM;
+ }
+ OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_blob), jOut, rv);
+ }
+ sqlite3_free(zDbName);
+ sqlite3_free(zTableName);
+ sqlite3_free(zColumnName);
+ return rc;
+}
+
+S3JniApi(sqlite3_blob_read(),jint,1blob_1read)(
+ JniArgsEnvClass, jlong jpBlob, jbyteArray jTgt, jint iOffset
+){
+ jbyte * const pBa = s3jni_jbyteArray_bytes(jTgt);
+ int rc = jTgt ? (pBa ? SQLITE_MISUSE : SQLITE_NOMEM) : SQLITE_MISUSE;
+ if( pBa ){
+ jsize const nTgt = (*env)->GetArrayLength(env, jTgt);
+ rc = sqlite3_blob_read(LongPtrGet_sqlite3_blob(jpBlob), pBa,
+ (int)nTgt, (int)iOffset);
+ if( 0==rc ){
+ s3jni_jbyteArray_commit(jTgt, pBa);
+ }else{
+ s3jni_jbyteArray_release(jTgt, pBa);
+ }
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_blob_read_nio_buffer(),jint,1blob_1read_1nio_1buffer)(
+ JniArgsEnvClass, jlong jpBlob, jint iSrcOff, jobject jBB, jint iTgtOff, jint iHowMany
+){
+ sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob);
+ S3JniNioArgs args;
+ int rc;
+ if( !b || !SJG.g.byteBuffer.klazz || iHowMany<0 ){
+ return SQLITE_MISUSE;
+ }else if( iTgtOff<0 || iSrcOff<0 ){
+ return SQLITE_ERROR
+ /* for consistency with underlying sqlite3_blob_read() */;
+ }else if( 0==iHowMany ){
+ return 0;
+ }
+ rc = s3jni_setup_nio_args(env, &args, jBB, iTgtOff, iHowMany);
+ if(rc){
+ return rc;
+ }else if( !args.pStart || !args.nOut ){
+ return 0;
+ }
+ assert( args.iHowMany>0 );
+ return sqlite3_blob_read( b, args.pStart, (int)args.nOut, (int)iSrcOff );
+}
+
+S3JniApi(sqlite3_blob_reopen(),jint,1blob_1reopen)(
+ JniArgsEnvClass, jlong jpBlob, jlong iNewRowId
+){
+ return (jint)sqlite3_blob_reopen(LongPtrGet_sqlite3_blob(jpBlob),
+ (sqlite3_int64)iNewRowId);
+}
+
+S3JniApi(sqlite3_blob_write(),jint,1blob_1write)(
+ JniArgsEnvClass, jlong jpBlob, jbyteArray jBa, jint iOffset
+){
+ sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob);
+ jbyte * const pBuf = b ? s3jni_jbyteArray_bytes(jBa) : 0;
+ const jsize nBA = pBuf ? (*env)->GetArrayLength(env, jBa) : 0;
+ int rc = SQLITE_MISUSE;
+ if(b && pBuf){
+ rc = sqlite3_blob_write( b, pBuf, (int)nBA, (int)iOffset );
+ }
+ s3jni_jbyteArray_release(jBa, pBuf);
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_blob_write_nio_buffer(),jint,1blob_1write_1nio_1buffer)(
+ JniArgsEnvClass, jlong jpBlob, jint iTgtOff, jobject jBB, jint iSrcOff, jint iHowMany
+){
+ sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob);
+ S3JniNioArgs args;
+ int rc;
+ if( !b || !SJG.g.byteBuffer.klazz ){
+ return SQLITE_MISUSE;
+ }else if( iTgtOff<0 || iSrcOff<0 ){
+ return SQLITE_ERROR
+ /* for consistency with underlying sqlite3_blob_write() */;
+ }else if( 0==iHowMany ){
+ return 0;
+ }
+ rc = s3jni_setup_nio_args(env, &args, jBB, iSrcOff, iHowMany);
+ if(rc){
+ return rc;
+ }else if( !args.pStart || !args.nOut ){
+ return 0;
+ }
+ return sqlite3_blob_write( b, args.pStart, (int)args.nOut, (int)iTgtOff );
+}
+
+/* Central C-to-Java busy handler proxy. */
+static int s3jni_busy_handler(void* pState, int n){
+ S3JniDb * const ps = (S3JniDb *)pState;
+ int rc = 0;
+ S3JniDeclLocal_env;
+ S3JniHook hook;
+
+ S3JniHook_localdup(&ps->hooks.busyHandler, &hook);
+ if( hook.jObj ){
+ rc = (*env)->CallIntMethod(env, hook.jObj,
+ hook.midCallback, (jint)n);
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("sqlite3_busy_handler() callback");
+ rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR,
+ "sqlite3_busy_handler() callback threw.");
+ }
+ S3JniHook_localundup(hook);
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_busy_handler(),jint,1busy_1handler)(
+ JniArgsEnvClass, jlong jpDb, jobject jBusy
+){
+ S3JniDb * const ps = S3JniDb_from_jlong(jpDb);
+ S3JniHook * const pHook = ps ? &ps->hooks.busyHandler : 0;
+ S3JniHook hook = S3JniHook_empty;
+ int rc = 0;
+
+ if( !ps ) return (jint)SQLITE_MISUSE;
+ S3JniDb_mutex_enter;
+ if( jBusy ){
+ if( pHook->jObj && (*env)->IsSameObject(env, pHook->jObj, jBusy) ){
+ /* Same object - this is a no-op. */
+ }else{
+ jclass const klazz = (*env)->GetObjectClass(env, jBusy);
+ hook.jObj = S3JniRefGlobal(jBusy);
+ hook.midCallback = (*env)->GetMethodID(env, klazz, "call", "(I)I");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ rc = SQLITE_ERROR;
+ }
+ }
+ }
+ if( 0==rc ){
+ if( jBusy ){
+ if( hook.jObj ){ /* Replace handler */
+ rc = sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps);
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ *pHook = hook /* transfer Java ref ownership */;
+ hook = S3JniHook_empty;
+ }
+ }/* else no-op */
+ }else{ /* Clear handler */
+ rc = sqlite3_busy_handler(ps->pDb, 0, 0);
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ }
+ }
+ }
+ S3JniHook_unref(&hook);
+ S3JniDb_mutex_leave;
+ return rc;
+}
+
+S3JniApi(sqlite3_busy_timeout(),jint,1busy_1timeout)(
+ JniArgsEnvClass, jlong jpDb, jint ms
+){
+ S3JniDb * const ps = S3JniDb_from_jlong(jpDb);
+ int rc = SQLITE_MISUSE;
+ if( ps ){
+ S3JniDb_mutex_enter;
+ S3JniHook_unref(&ps->hooks.busyHandler);
+ rc = sqlite3_busy_timeout(ps->pDb, (int)ms);
+ S3JniDb_mutex_leave;
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_cancel_auto_extension(),jboolean,1cancel_1auto_1extension)(
+ JniArgsEnvClass, jobject jAutoExt
+){
+ S3JniAutoExtension * ax;
+ jboolean rc = JNI_FALSE;
+ int i;
+
+ if( !jAutoExt ){
+ return rc;
+ }
+ S3JniAutoExt_mutex_enter;
+ /* This algo corresponds to the one in the core. */
+ for( i = SJG.autoExt.nExt-1; i >= 0; --i ){
+ ax = &SJG.autoExt.aExt[i];
+ if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
+ S3JniAutoExtension_clear(ax);
+ /* Move final entry into this slot. */
+ --SJG.autoExt.nExt;
+ *ax = SJG.autoExt.aExt[SJG.autoExt.nExt];
+ SJG.autoExt.aExt[SJG.autoExt.nExt] = S3JniHook_empty;
+ assert( !SJG.autoExt.aExt[SJG.autoExt.nExt].jObj );
+ rc = JNI_TRUE;
+ break;
+ }
+ }
+ S3JniAutoExt_mutex_leave;
+ return rc;
+}
+
+/* Wrapper for sqlite3_close(_v2)(). */
+static jint s3jni_close_db(JNIEnv * const env, jlong jpDb, int version){
+ int rc = 0;
+ S3JniDb * const ps = S3JniDb_from_jlong(jpDb);
+
+ assert(version == 1 || version == 2);
+ if( ps ){
+ rc = 1==version
+ ? (jint)sqlite3_close(ps->pDb)
+ : (jint)sqlite3_close_v2(ps->pDb);
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_close(),jint,1close)(JniArgsEnvClass, jlong pDb){
+ return s3jni_close_db(env, pDb, 1);
+}
+
+S3JniApi(sqlite3_close_v2(),jint,1close_1v2)(JniArgsEnvClass, jlong pDb){
+ return s3jni_close_db(env, pDb, 2);
+}
+
+/*
+** Assumes z is an array of unsigned short and returns the index in
+** that array of the first element with the value 0.
+*/
+static unsigned int s3jni_utf16_strlen(void const * z){
+ unsigned int i = 0;
+ const unsigned short * p = z;
+ while( p[i] ) ++i;
+ return i;
+}
+
+/* Descriptive alias for use with sqlite3_collation_needed(). */
+typedef S3JniHook S3JniCollationNeeded;
+
+/* Central C-to-Java sqlite3_collation_needed16() hook impl. */
+static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb,
+ int eTextRep, const void * z16Name){
+ S3JniCollationNeeded * const pHook = pState;
+ S3JniDeclLocal_env;
+ S3JniHook hook;
+
+ S3JniHook_localdup(pHook, &hook);
+ if( hook.jObj ){
+ unsigned int const nName = s3jni_utf16_strlen(z16Name);
+ jstring jName = (*env)->NewString(env, (jchar const *)z16Name, nName);
+
+ s3jni_oom_check( jName );
+ assert( hook.jExtra );
+ S3JniIfThrew{
+ S3JniExceptionClear;
+ }else if( hook.jExtra ){
+ (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback,
+ hook.jExtra, (jint)eTextRep, jName);
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("sqlite3_collation_needed() callback");
+ }
+ }
+ S3JniUnrefLocal(jName);
+ S3JniHook_localundup(hook);
+ }
+}
+
+S3JniApi(sqlite3_collation_needed(),jint,1collation_1needed)(
+ JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+ S3JniDb * ps;
+ S3JniCollationNeeded * pHook;
+ int rc = 0;
+
+ S3JniDb_mutex_enter;
+ ps = S3JniDb_from_jlong(jpDb);
+ if( !ps ){
+ S3JniDb_mutex_leave;
+ return SQLITE_MISUSE;
+ }
+ pHook = &ps->hooks.collationNeeded;
+ if( pHook->jObj && jHook &&
+ (*env)->IsSameObject(env, pHook->jObj, jHook) ){
+ /* no-op */
+ }else if( !jHook ){
+ rc = sqlite3_collation_needed(ps->pDb, 0, 0);
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ }
+ }else{
+ jclass const klazz = (*env)->GetObjectClass(env, jHook);
+ jmethodID const xCallback = (*env)->GetMethodID(
+ env, klazz, "call", "(Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)V"
+ );
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ rc = s3jni_db_exception(ps->pDb, SQLITE_MISUSE,
+ "Cannot not find matching call() in "
+ "CollationNeededCallback object.");
+ }else{
+ rc = sqlite3_collation_needed16(ps->pDb, pHook,
+ s3jni_collation_needed_impl16);
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ pHook->midCallback = xCallback;
+ pHook->jObj = S3JniRefGlobal(jHook);
+ pHook->jExtra = S3JniRefGlobal(ps->jDb);
+ }
+ }
+ }
+ S3JniDb_mutex_leave;
+ return rc;
+}
+
+S3JniApi(sqlite3_column_blob(),jbyteArray,1column_1blob)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+ void const * const p = sqlite3_column_blob(pStmt, (int)ndx);
+ int const n = p ? sqlite3_column_bytes(pStmt, (int)ndx) : 0;
+
+ return p ? s3jni_new_jbyteArray(p, n) : 0;
+}
+
+S3JniApi(sqlite3_column_double(),jdouble,1column_1double)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ return (jdouble)sqlite3_column_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
+}
+
+S3JniApi(sqlite3_column_int(),jint,1column_1int)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ return (jint)sqlite3_column_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
+}
+
+S3JniApi(sqlite3_column_int64(),jlong,1column_1int64)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
+}
+
+S3JniApi(sqlite3_column_java_object(),jobject,1column_1java_1object)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx
+){
+ sqlite3_stmt * const stmt = LongPtrGet_sqlite3_stmt(jpStmt);
+ jobject rv = 0;
+ if( stmt ){
+ sqlite3 * const db = sqlite3_db_handle(stmt);
+ sqlite3_value * sv;
+ sqlite3_mutex_enter(sqlite3_db_mutex(db));
+ sv = sqlite3_column_value(stmt, (int)ndx);
+ if( sv ){
+ rv = S3JniRefLocal(
+ sqlite3_value_pointer(sv, s3jni__value_jref_key)
+ );
+ }
+ sqlite3_mutex_leave(sqlite3_db_mutex(db));
+ }
+ return rv;
+}
+
+S3JniApi(sqlite3_column_nio_buffer(),jobject,1column_1nio_1buffer)(
+ JniArgsEnvClass, jobject jStmt, jint ndx
+){
+ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jStmt);
+ jobject rv = 0;
+ if( stmt ){
+ const void * const p = sqlite3_column_blob(stmt, (int)ndx);
+ if( p ){
+ const int n = sqlite3_column_bytes(stmt, (int)ndx);
+ rv = s3jni__blob_to_ByteBuffer(env, p, n);
+ }
+ }
+ return rv;
+}
+
+S3JniApi(sqlite3_column_text(),jbyteArray,1column_1text)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt);
+ const unsigned char * const p = stmt ? sqlite3_column_text(stmt, (int)ndx) : 0;
+ const int n = p ? sqlite3_column_bytes(stmt, (int)ndx) : 0;
+ return p ? s3jni_new_jbyteArray(p, n) : NULL;
+}
+
+#if 0
+// this impl might prove useful.
+S3JniApi(sqlite3_column_text(),jstring,1column_1text)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt);
+ const unsigned char * const p = stmt ? sqlite3_column_text(stmt, (int)ndx) : 0;
+ const int n = p ? sqlite3_column_bytes(stmt, (int)ndx) : 0;
+ return p ? s3jni_utf8_to_jstring( (const char *)p, n) : 0;
+}
+#endif
+
+S3JniApi(sqlite3_column_text16(),jstring,1column_1text16)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt);
+ const void * const p = stmt ? sqlite3_column_text16(stmt, (int)ndx) : 0;
+ const int n = p ? sqlite3_column_bytes16(stmt, (int)ndx) : 0;
+ return s3jni_text16_to_jstring(env, p, n);
+}
+
+S3JniApi(sqlite3_column_value(),jobject,1column_1value)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ sqlite3_value * const sv =
+ sqlite3_column_value(PtrGet_sqlite3_stmt(jpStmt), (int)ndx)
+ /* reminder: returns an SQL NULL if jpStmt==NULL */;
+ return new_java_sqlite3_value(env, sv);
+}
+
+/*
+** Impl for commit hooks (if isCommit is true) or rollback hooks.
+*/
+static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){
+ S3JniDeclLocal_env;
+ int rc = 0;
+ S3JniHook hook;
+
+ S3JniHook_localdup(isCommit
+ ? &ps->hooks.commit : &ps->hooks.rollback,
+ &hook);
+ if( hook.jObj ){
+ rc = isCommit
+ ? (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback)
+ : (int)((*env)->CallVoidMethod(env, hook.jObj, hook.midCallback), 0);
+ S3JniIfThrew{
+ rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR,
+ isCommit
+ ? "Commit hook callback threw"
+ : "Rollback hook callback threw");
+ }
+ S3JniHook_localundup(hook);
+ }
+ return rc;
+}
+
+/* C-to-Java commit hook wrapper. */
+static int s3jni_commit_hook_impl(void *pP){
+ return s3jni_commit_rollback_hook_impl(1, pP);
+}
+
+/* C-to-Java rollback hook wrapper. */
+static void s3jni_rollback_hook_impl(void *pP){
+ (void)s3jni_commit_rollback_hook_impl(0, pP);
+}
+
+/*
+** Proxy for sqlite3_commit_hook() (if isCommit is true) or
+** sqlite3_rollback_hook().
+*/
+static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,
+ jlong jpDb, jobject jHook){
+ S3JniDb * ps;
+ jobject pOld = 0; /* previous hoook */
+ S3JniHook * pHook; /* ps->hooks.commit|rollback */
+
+ S3JniDb_mutex_enter;
+ ps = S3JniDb_from_jlong(jpDb);
+ if( !ps ){
+ s3jni_db_error(ps->pDb, SQLITE_MISUSE, 0);
+ S3JniDb_mutex_leave;
+ return 0;
+ }
+ pHook = isCommit ? &ps->hooks.commit : &ps->hooks.rollback;
+ pOld = pHook->jObj;
+ if( pOld && jHook &&
+ (*env)->IsSameObject(env, pOld, jHook) ){
+ /* No-op. */
+ }else if( !jHook ){
+ if( pOld ){
+ jobject tmp = S3JniRefLocal(pOld);
+ S3JniUnrefGlobal(pOld);
+ pOld = tmp;
+ }
+ *pHook = S3JniHook_empty;
+ if( isCommit ) sqlite3_commit_hook(ps->pDb, 0, 0);
+ else sqlite3_rollback_hook(ps->pDb, 0, 0);
+ }else{
+ jclass const klazz = (*env)->GetObjectClass(env, jHook);
+ jmethodID const xCallback = (*env)->GetMethodID(env, klazz, "call",
+ isCommit ? "()I" : "()V");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ "Cannot not find matching call() method in"
+ "hook object.");
+ }else{
+ pHook->midCallback = xCallback;
+ pHook->jObj = S3JniRefGlobal(jHook);
+ if( isCommit ) sqlite3_commit_hook(ps->pDb, s3jni_commit_hook_impl, ps);
+ else sqlite3_rollback_hook(ps->pDb, s3jni_rollback_hook_impl, ps);
+ if( pOld ){
+ jobject tmp = S3JniRefLocal(pOld);
+ S3JniUnrefGlobal(pOld);
+ pOld = tmp;
+ }
+ }
+ }
+ S3JniDb_mutex_leave;
+ return pOld;
+}
+
+S3JniApi(sqlite3_commit_hook(),jobject,1commit_1hook)(
+ JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+ return s3jni_commit_rollback_hook(1, env, jpDb, jHook);
+}
+
+S3JniApi(sqlite3_compileoption_get(),jstring,1compileoption_1get)(
+ JniArgsEnvClass, jint n
+){
+ const char * z = sqlite3_compileoption_get(n);
+ jstring const rv = z ? (*env)->NewStringUTF( env, z ) : 0;
+ /* We know these to be ASCII, so MUTF-8 is fine. */;
+ s3jni_oom_check(z ? !!rv : 1);
+ return rv;
+}
+
+S3JniApi(sqlite3_compileoption_used(),jboolean,1compileoption_1used)(
+ JniArgsEnvClass, jstring name
+){
+ const char *zUtf8 = s3jni_jstring_to_mutf8(name)
+ /* We know these to be ASCII, so MUTF-8 is fine (and
+ hypothetically faster to convert). */;
+ const jboolean rc =
+ 0==sqlite3_compileoption_used(zUtf8) ? JNI_FALSE : JNI_TRUE;
+ s3jni_mutf8_release(name, zUtf8);
+ return rc;
+}
+
+S3JniApi(sqlite3_complete(),jint,1complete)(
+ JniArgsEnvClass, jbyteArray jSql
+){
+ jbyte * const pBuf = s3jni_jbyteArray_bytes(jSql);
+ const jsize nBA = pBuf ? (*env)->GetArrayLength(env, jSql) : 0;
+ int rc;
+
+ assert( (nBA>0 ? 0==pBuf[nBA-1] : (pBuf ? 0==*pBuf : 1))
+ && "Byte array is not NUL-terminated." );
+ rc = (pBuf && 0==pBuf[(nBA ? nBA-1 : 0)])
+ ? sqlite3_complete( (const char *)pBuf )
+ : (jSql ? SQLITE_NOMEM : SQLITE_MISUSE);
+ s3jni_jbyteArray_release(jSql, pBuf);
+ return rc;
+}
+
+S3JniApi(sqlite3_config() /*for a small subset of options.*/
+ sqlite3_config__enable()/* internal name to avoid name-mangling issues*/,
+ jint,1config_1_1enable)(JniArgsEnvClass, jint n){
+ switch( n ){
+ case SQLITE_CONFIG_SINGLETHREAD:
+ case SQLITE_CONFIG_MULTITHREAD:
+ case SQLITE_CONFIG_SERIALIZED:
+ return sqlite3_config( n );
+ default:
+ return SQLITE_MISUSE;
+ }
+}
+/* C-to-Java SQLITE_CONFIG_LOG wrapper. */
+static void s3jni_config_log(void *ignored, int errCode, const char *z){
+ S3JniDeclLocal_env;
+ S3JniHook hook = S3JniHook_empty;
+
+ S3JniHook_localdup(&SJG.hook.configlog, &hook);
+ if( hook.jObj ){
+ jstring const jArg1 = z ? s3jni_utf8_to_jstring(z, -1) : 0;
+ if( z ? !!jArg1 : 1 ){
+ (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback, errCode, jArg1);
+ }
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("SQLITE_CONFIG_LOG callback");
+ S3JniExceptionClear;
+ }
+ S3JniHook_localundup(hook);
+ S3JniUnrefLocal(jArg1);
+ }
+}
+
+S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_LOG */
+ sqlite3_config__config_log() /* internal name */,
+ jint, 1config_1_1CONFIG_1LOG
+)(JniArgsEnvClass, jobject jLog){
+ S3JniHook * const pHook = &SJG.hook.configlog;
+ int rc = 0;
+
+ S3JniGlobal_mutex_enter;
+ if( !jLog ){
+ rc = sqlite3_config( SQLITE_CONFIG_LOG, NULL, NULL );
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ }
+ }else if( pHook->jObj && (*env)->IsSameObject(env, jLog, pHook->jObj) ){
+ /* No-op */
+ }else {
+ jclass const klazz = (*env)->GetObjectClass(env, jLog);
+ jmethodID const midCallback = (*env)->GetMethodID(env, klazz, "call",
+ "(ILjava/lang/String;)V");
+ S3JniUnrefLocal(klazz);
+ if( midCallback ){
+ rc = sqlite3_config( SQLITE_CONFIG_LOG, s3jni_config_log, NULL );
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ pHook->midCallback = midCallback;
+ pHook->jObj = S3JniRefGlobal(jLog);
+ }
+ }else{
+ S3JniExceptionWarnIgnore;
+ rc = SQLITE_ERROR;
+ }
+ }
+ S3JniGlobal_mutex_leave;
+ return rc;
+}
+
+#ifdef SQLITE_ENABLE_SQLLOG
+/* C-to-Java SQLITE_CONFIG_SQLLOG wrapper. */
+static void s3jni_config_sqllog(void *ignored, sqlite3 *pDb, const char *z, int op){
+ jobject jArg0 = 0;
+ jstring jArg1 = 0;
+ S3JniDeclLocal_env;
+ S3JniDb * const ps = S3JniDb_from_c(pDb);
+ S3JniHook hook = S3JniHook_empty;
+
+ if( ps ){
+ S3JniHook_localdup(&SJG.hook.sqllog, &hook);
+ }
+ if( !hook.jObj ) return;
+ jArg0 = S3JniRefLocal(ps->jDb);
+ switch( op ){
+ case 0: /* db opened */
+ case 1: /* SQL executed */
+ jArg1 = s3jni_utf8_to_jstring( z, -1);
+ break;
+ case 2: /* db closed */
+ break;
+ default:
+ (*env)->FatalError(env, "Unhandled 4th arg to SQLITE_CONFIG_SQLLOG.");
+ break;
+ }
+ (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback, jArg0, jArg1, op);
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("SQLITE_CONFIG_SQLLOG callback");
+ S3JniExceptionClear;
+ }
+ S3JniHook_localundup(hook);
+ S3JniUnrefLocal(jArg0);
+ S3JniUnrefLocal(jArg1);
+}
+//! Requirement of SQLITE_CONFIG_SQLLOG.
+void sqlite3_init_sqllog(void){
+ sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, 0 );
+}
+#endif
+
+S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_SQLLOG */
+ sqlite3_config__SQLLOG() /*internal name*/,
+ jint, 1config_1_1SQLLOG
+)(JniArgsEnvClass, jobject jLog){
+#ifndef SQLITE_ENABLE_SQLLOG
+ return SQLITE_MISUSE;
+#else
+ S3JniHook * const pHook = &SJG.hook.sqllog;
+ int rc = 0;
+
+ S3JniGlobal_mutex_enter;
+ if( !jLog ){
+ rc = sqlite3_config( SQLITE_CONFIG_SQLLOG, NULL );
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ }
+ }else if( pHook->jObj && (*env)->IsSameObject(env, jLog, pHook->jObj) ){
+ /* No-op */
+ }else {
+ jclass const klazz = (*env)->GetObjectClass(env, jLog);
+ jmethodID const midCallback = (*env)->GetMethodID(env, klazz, "call",
+ "(Lorg/sqlite/jni/capi/sqlite3;"
+ "Ljava/lang/String;"
+ "I)V");
+ S3JniUnrefLocal(klazz);
+ if( midCallback ){
+ rc = sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, NULL );
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ pHook->midCallback = midCallback;
+ pHook->jObj = S3JniRefGlobal(jLog);
+ }
+ }else{
+ S3JniExceptionWarnIgnore;
+ rc = SQLITE_ERROR;
+ }
+ }
+ S3JniGlobal_mutex_leave;
+ return rc;
+#endif
+}
+
+S3JniApi(sqlite3_context_db_handle(),jobject,1context_1db_1handle)(
+ JniArgsEnvClass, jobject jpCx
+){
+ sqlite3_context * const pCx = PtrGet_sqlite3_context(jpCx);
+ sqlite3 * const pDb = pCx ? sqlite3_context_db_handle(pCx) : 0;
+ S3JniDb * const ps = pDb ? S3JniDb_from_c(pDb) : 0;
+ return ps ? ps->jDb : 0;
+}
+
+/*
+** State for CollationCallbacks. This used to be its own separate
+** type, but has since been consolidated with S3JniHook. It retains
+** its own typedef for code legibility and searchability reasons.
+*/
+typedef S3JniHook S3JniCollationCallback;
+
+/*
+** Proxy for Java-side CollationCallback.xCompare() callbacks.
+*/
+static int CollationCallback_xCompare(void *pArg, int nLhs, const void *lhs,
+ int nRhs, const void *rhs){
+ S3JniCollationCallback * const pCC = pArg;
+ S3JniDeclLocal_env;
+ jint rc = 0;
+ if( pCC->jObj ){
+ jbyteArray jbaLhs = s3jni_new_jbyteArray(lhs, (jint)nLhs);
+ jbyteArray jbaRhs = jbaLhs
+ ? s3jni_new_jbyteArray(rhs, (jint)nRhs) : 0;
+ if( !jbaRhs ){
+ S3JniUnrefLocal(jbaLhs);
+ /* We have no recovery strategy here. */
+ s3jni_oom_check( jbaRhs );
+ return 0;
+ }
+ rc = (*env)->CallIntMethod(env, pCC->jObj, pCC->midCallback,
+ jbaLhs, jbaRhs);
+ S3JniExceptionIgnore;
+ S3JniUnrefLocal(jbaLhs);
+ S3JniUnrefLocal(jbaRhs);
+ }
+ return (int)rc;
+}
+
+/* CollationCallback finalizer for use by the sqlite3 internals. */
+static void CollationCallback_xDestroy(void *pArg){
+ S3JniCollationCallback * const pCC = pArg;
+ S3JniDeclLocal_env;
+ S3JniHook_free(pCC);
+}
+
+S3JniApi(sqlite3_create_collation() sqlite3_create_collation_v2(),
+ jint,1create_1collation
+)(JniArgsEnvClass, jobject jDb, jstring name, jint eTextRep,
+ jobject oCollation){
+ int rc;
+ S3JniDb * ps;
+
+ if( !jDb || !name || !encodingTypeIsValid(eTextRep) ){
+ return (jint)SQLITE_MISUSE;
+ }
+ S3JniDb_mutex_enter;
+ ps = S3JniDb_from_java(jDb);
+ jclass const klazz = (*env)->GetObjectClass(env, oCollation);
+ jmethodID const midCallback =
+ (*env)->GetMethodID(env, klazz, "call", "([B[B)I");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew{
+ rc = s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ "Could not get call() method from "
+ "CollationCallback object.");
+ }else{
+ char * const zName = s3jni_jstring_to_utf8(name, 0);
+ S3JniCollationCallback * const pCC =
+ zName ? S3JniHook_alloc() : 0;
+ if( pCC ){
+ rc = sqlite3_create_collation_v2(ps->pDb, zName, (int)eTextRep,
+ pCC, CollationCallback_xCompare,
+ CollationCallback_xDestroy);
+ if( 0==rc ){
+ pCC->midCallback = midCallback;
+ pCC->jObj = S3JniRefGlobal(oCollation);
+ pCC->doXDestroy = 1;
+ }else{
+ CollationCallback_xDestroy(pCC);
+ }
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ sqlite3_free(zName);
+ }
+ S3JniDb_mutex_leave;
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_create_function() sqlite3_create_function_v2()
+ sqlite3_create_window_function(),
+ jint,1create_1function
+)(JniArgsEnvClass, jobject jDb, jstring jFuncName, jint nArg,
+ jint eTextRep, jobject jFunctor){
+ S3JniUdf * s = 0;
+ int rc;
+ sqlite3 * const pDb = PtrGet_sqlite3(jDb);
+ char * zFuncName = 0;
+
+ if( !pDb || !jFuncName ){
+ return SQLITE_MISUSE;
+ }else if( !encodingTypeIsValid(eTextRep) ){
+ return s3jni_db_error(pDb, SQLITE_FORMAT,
+ "Invalid function encoding option.");
+ }
+ s = S3JniUdf_alloc(env, jFunctor);
+ if( !s ) return SQLITE_NOMEM;
+
+ if( UDF_UNKNOWN_TYPE==s->type ){
+ rc = s3jni_db_error(pDb, SQLITE_MISUSE,
+ "Cannot unambiguously determine function type.");
+ S3JniUdf_free(env, s, 1);
+ goto error_cleanup;
+ }
+ zFuncName = s3jni_jstring_to_utf8(jFuncName,0);
+ if( !zFuncName ){
+ rc = SQLITE_NOMEM;
+ S3JniUdf_free(env, s, 1);
+ goto error_cleanup;
+ }
+ s->zFuncName = zFuncName /* pass on ownership */;
+ if( UDF_WINDOW == s->type ){
+ rc = sqlite3_create_window_function(pDb, zFuncName, nArg, eTextRep, s,
+ udf_xStep, udf_xFinal, udf_xValue,
+ udf_xInverse, S3JniUdf_finalizer);
+ }else{
+ udf_xFunc_f xFunc = 0;
+ udf_xStep_f xStep = 0;
+ udf_xFinal_f xFinal = 0;
+ if( UDF_SCALAR == s->type ){
+ xFunc = udf_xFunc;
+ }else{
+ assert( UDF_AGGREGATE == s->type );
+ xStep = udf_xStep;
+ xFinal = udf_xFinal;
+ }
+ rc = sqlite3_create_function_v2(pDb, zFuncName, nArg, eTextRep, s,
+ xFunc, xStep, xFinal, S3JniUdf_finalizer);
+ }
+error_cleanup:
+ /* Reminder: on sqlite3_create_function() error, s will be
+ ** destroyed via create_function(). */
+ return (jint)rc;
+}
+
+
+S3JniApi(sqlite3_db_config() /*for MAINDBNAME*/,
+ jint,1db_1config__Lorg_sqlite_jni_capi_sqlite3_2ILjava_lang_String_2
+)(JniArgsEnvClass, jobject jDb, jint op, jstring jStr){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ int rc;
+ char *zStr;
+
+ switch( (ps && jStr) ? op : 0 ){
+ case SQLITE_DBCONFIG_MAINDBNAME:
+ S3JniDb_mutex_enter
+ /* Protect against a race in modifying/freeing
+ ps->zMainDbName. */;
+ zStr = s3jni_jstring_to_utf8( jStr, 0);
+ if( zStr ){
+ rc = sqlite3_db_config(ps->pDb, (int)op, zStr);
+ if( rc ){
+ sqlite3_free( zStr );
+ }else{
+ sqlite3_free( ps->zMainDbName );
+ ps->zMainDbName = zStr;
+ }
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ S3JniDb_mutex_leave;
+ break;
+ case 0:
+ default:
+ rc = SQLITE_MISUSE;
+ }
+ return rc;
+}
+
+S3JniApi(
+ sqlite3_db_config(),
+ /* WARNING: openjdk v19 creates a different mangled name for this
+ ** function than openjdk v8 does. We account for that by exporting
+ ** both versions of the name. */
+ jint,1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_Int32_2
+)(
+ JniArgsEnvClass, jobject jDb, jint op, jint onOff, jobject jOut
+){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ int rc;
+ switch( ps ? op : 0 ){
+ case SQLITE_DBCONFIG_ENABLE_FKEY:
+ case SQLITE_DBCONFIG_ENABLE_TRIGGER:
+ case SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER:
+ case SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION:
+ case SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE:
+ case SQLITE_DBCONFIG_ENABLE_QPSG:
+ case SQLITE_DBCONFIG_TRIGGER_EQP:
+ case SQLITE_DBCONFIG_RESET_DATABASE:
+ case SQLITE_DBCONFIG_DEFENSIVE:
+ case SQLITE_DBCONFIG_WRITABLE_SCHEMA:
+ case SQLITE_DBCONFIG_LEGACY_ALTER_TABLE:
+ case SQLITE_DBCONFIG_DQS_DML:
+ case SQLITE_DBCONFIG_DQS_DDL:
+ case SQLITE_DBCONFIG_ENABLE_VIEW:
+ case SQLITE_DBCONFIG_LEGACY_FILE_FORMAT:
+ case SQLITE_DBCONFIG_TRUSTED_SCHEMA:
+ case SQLITE_DBCONFIG_STMT_SCANSTATUS:
+ case SQLITE_DBCONFIG_REVERSE_SCANORDER: {
+ int pOut = 0;
+ rc = sqlite3_db_config( ps->pDb, (int)op, onOff, &pOut );
+ if( 0==rc && jOut ){
+ OutputPointer_set_Int32(env, jOut, pOut);
+ }
+ break;
+ }
+ default:
+ rc = SQLITE_MISUSE;
+ }
+ return (jint)rc;
+}
+
+/*
+** This is a workaround for openjdk v19 (and possibly others) encoding
+** this function's name differently than JDK v8 does. If we do not
+** install both names for this function then Java will not be able to
+** find the function in both environments.
+*/
+JniDecl(jint,1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_00024Int32_2)(
+ JniArgsEnvClass, jobject jDb, jint op, jint onOff, jobject jOut
+){
+ return JniFuncName(1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_Int32_2)(
+ env, jKlazz, jDb, op, onOff, jOut
+ );
+}
+
+S3JniApi(sqlite3_db_filename(),jstring,1db_1filename)(
+ JniArgsEnvClass, jobject jDb, jstring jDbName
+){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ char *zDbName;
+ jstring jRv = 0;
+ int nStr = 0;
+
+ if( !ps || !jDbName ){
+ return 0;
+ }
+ zDbName = s3jni_jstring_to_utf8( jDbName, &nStr);
+ if( zDbName ){
+ char const * zRv = sqlite3_db_filename(ps->pDb, zDbName);
+ sqlite3_free(zDbName);
+ if( zRv ){
+ jRv = s3jni_utf8_to_jstring( zRv, -1);
+ }
+ }
+ return jRv;
+}
+
+S3JniApi(sqlite3_db_handle(),jobject,1db_1handle)(
+ JniArgsEnvClass, jobject jpStmt
+){
+ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+ sqlite3 * const pDb = pStmt ? sqlite3_db_handle(pStmt) : 0;
+ S3JniDb * const ps = pDb ? S3JniDb_from_c(pDb) : 0;
+ return ps ? ps->jDb : 0;
+}
+
+S3JniApi(sqlite3_db_readonly(),jint,1db_1readonly)(
+ JniArgsEnvClass, jobject jDb, jstring jDbName
+){
+ int rc = 0;
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ char *zDbName = jDbName ? s3jni_jstring_to_utf8( jDbName, 0 ) : 0;
+ rc = sqlite3_db_readonly(ps ? ps->pDb : 0, zDbName);
+ sqlite3_free(zDbName);
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_db_release_memory(),jint,1db_1release_1memory)(
+ JniArgsEnvClass, jobject jDb
+){
+ sqlite3 * const pDb = PtrGet_sqlite3(jDb);
+ return pDb ? sqlite3_db_release_memory(pDb) : SQLITE_MISUSE;
+}
+
+S3JniApi(sqlite3_db_status(),jint,1db_1status)(
+ JniArgsEnvClass, jobject jDb, jint op, jobject jOutCurrent,
+ jobject jOutHigh, jboolean reset
+){
+ int iCur = 0, iHigh = 0;
+ sqlite3 * const pDb = PtrGet_sqlite3(jDb);
+ int rc = sqlite3_db_status( pDb, op, &iCur, &iHigh, reset );
+ if( 0==rc ){
+ OutputPointer_set_Int32(env, jOutCurrent, iCur);
+ OutputPointer_set_Int32(env, jOutHigh, iHigh);
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_errcode(),jint,1errcode)(
+ JniArgsEnvClass, jobject jpDb
+){
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ return pDb ? sqlite3_errcode(pDb) : SQLITE_MISUSE;
+}
+
+S3JniApi(sqlite3_errmsg(),jstring,1errmsg)(
+ JniArgsEnvClass, jobject jpDb
+){
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ return pDb ? s3jni_utf8_to_jstring( sqlite3_errmsg(pDb), -1) : 0
+ /* We don't use errmsg16() directly only because it would cause an
+ additional level of internal encoding in sqlite3. The end
+ effect should be identical to using errmsg16(), however. */;
+}
+
+S3JniApi(sqlite3_errstr(),jstring,1errstr)(
+ JniArgsEnvClass, jint rcCode
+){
+ jstring rv;
+ const char * z = sqlite3_errstr((int)rcCode);
+ if( !z ){
+ /* This hypothetically cannot happen, but we'll behave like the
+ low-level library would in such a case... */
+ z = "unknown error";
+ }
+ rv = (*env)->NewStringUTF(env, z)
+ /* We know these values to be plain ASCII, so pose no MUTF-8
+ ** incompatibility */;
+ s3jni_oom_check( rv );
+ return rv;
+}
+
+#ifndef SQLITE_ENABLE_NORMALIZE
+/* Dummy stub for sqlite3_normalized_sql(). Never called. */
+static const char * sqlite3_normalized_sql(sqlite3_stmt *s){
+ S3JniDeclLocal_env;
+ (*env)->FatalError(env, "dummy sqlite3_normalized_sql() was "
+ "impossibly called.") /* does not return */;
+ return 0;
+}
+#endif
+
+/*
+** Impl for sqlite3_expanded_sql() (if isExpanded is true) and
+** sqlite3_normalized_sql().
+*/
+static jstring s3jni_xn_sql(int isExpanded, JNIEnv *env, jobject jpStmt){
+ jstring rv = 0;
+ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+
+ if( pStmt ){
+ char * zSql = isExpanded
+ ? sqlite3_expanded_sql(pStmt)
+ : (char*)sqlite3_normalized_sql(pStmt);
+ s3jni_oom_fatal(zSql);
+ if( zSql ){
+ rv = s3jni_utf8_to_jstring(zSql, -1);
+ if( isExpanded ) sqlite3_free(zSql);
+ }
+ }
+ return rv;
+}
+
+S3JniApi(sqlite3_expanded_sql(),jstring,1expanded_1sql)(
+ JniArgsEnvClass, jobject jpStmt
+){
+ return s3jni_xn_sql(1, env, jpStmt);
+}
+
+S3JniApi(sqlite3_normalized_sql(),jstring,1normalized_1sql)(
+ JniArgsEnvClass, jobject jpStmt
+){
+#ifdef SQLITE_ENABLE_NORMALIZE
+ return s3jni_xn_sql(0, env, jpStmt);
+#else
+ return 0;
+#endif
+}
+
+S3JniApi(sqlite3_extended_result_codes(),jint,1extended_1result_1codes)(
+ JniArgsEnvClass, jobject jpDb, jboolean onoff
+){
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ int const rc = pDb
+ ? sqlite3_extended_result_codes(pDb, onoff ? 1 : 0)
+ : SQLITE_MISUSE;
+ return rc;
+}
+
+S3JniApi(sqlite3_finalize(),jint,1finalize)(
+ JniArgsEnvClass, jlong jpStmt
+){
+ return jpStmt
+ ? sqlite3_finalize(LongPtrGet_sqlite3_stmt(jpStmt))
+ : 0;
+}
+
+S3JniApi(sqlite3_get_auxdata(),jobject,1get_1auxdata)(
+ JniArgsEnvClass, jobject jCx, jint n
+){
+ return sqlite3_get_auxdata(PtrGet_sqlite3_context(jCx), (int)n);
+}
+
+S3JniApi(sqlite3_initialize(),jint,1initialize)(
+ JniArgsEnvClass
+){
+ return sqlite3_initialize();
+}
+
+S3JniApi(sqlite3_interrupt(),void,1interrupt)(
+ JniArgsEnvClass, jobject jpDb
+){
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ if( pDb ){
+ sqlite3_interrupt(pDb);
+ }
+}
+
+S3JniApi(sqlite3_is_interrupted(),jboolean,1is_1interrupted)(
+ JniArgsEnvClass, jobject jpDb
+){
+ int rc = 0;
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ if( pDb ){
+ rc = sqlite3_is_interrupted(pDb);
+ }
+ return rc ? JNI_TRUE : JNI_FALSE;
+}
+
+/*
+** Uncaches the current JNIEnv from the S3JniGlobal state, clearing
+** any resources owned by that cache entry and making that slot
+** available for re-use.
+*/
+S3JniApi(sqlite3_java_uncache_thread(), jboolean, 1java_1uncache_1thread)(
+ JniArgsEnvClass
+){
+ int rc;
+ S3JniEnv_mutex_enter;
+ rc = S3JniEnv_uncache(env);
+ S3JniEnv_mutex_leave;
+ return rc ? JNI_TRUE : JNI_FALSE;
+}
+
+S3JniApi(sqlite3_jni_db_error(), jint, 1jni_1db_1error)(
+ JniArgsEnvClass, jobject jDb, jint jRc, jstring jStr
+){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ int rc = SQLITE_MISUSE;
+ if( ps ){
+ char *zStr;
+ zStr = jStr
+ ? s3jni_jstring_to_utf8( jStr, 0)
+ : NULL;
+ rc = s3jni_db_error( ps->pDb, (int)jRc, zStr );
+ sqlite3_free(zStr);
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_jni_supports_nio(), jboolean,1jni_1supports_1nio)(
+ JniArgsEnvClass
+){
+ return SJG.g.byteBuffer.klazz ? JNI_TRUE : JNI_FALSE;
+}
+
+
+S3JniApi(sqlite3_keyword_check(),jboolean,1keyword_1check)(
+ JniArgsEnvClass, jstring jWord
+){
+ int nWord = 0;
+ char * zWord = s3jni_jstring_to_utf8(jWord, &nWord);
+ int rc = 0;
+
+ s3jni_oom_check(jWord ? !!zWord : 1);
+ if( zWord && nWord ){
+ rc = sqlite3_keyword_check(zWord, nWord);
+ }
+ sqlite3_free(zWord);
+ return rc ? JNI_TRUE : JNI_FALSE;
+}
+
+S3JniApi(sqlite3_keyword_name(),jstring,1keyword_1name)(
+ JniArgsEnvClass, jint ndx
+){
+ const char * zWord = 0;
+ int n = 0;
+ jstring rv = 0;
+
+ if( 0==sqlite3_keyword_name(ndx, &zWord, &n) ){
+ rv = s3jni_utf8_to_jstring(zWord, n);
+ }
+ return rv;
+}
+
+
+S3JniApi(sqlite3_last_insert_rowid(),jlong,1last_1insert_1rowid)(
+ JniArgsEnvClass, jobject jpDb
+){
+ return (jlong)sqlite3_last_insert_rowid(PtrGet_sqlite3(jpDb));
+}
+
+S3JniApi(sqlite3_limit(),jint,1limit)(
+ JniArgsEnvClass, jobject jpDb, jint id, jint newVal
+){
+ jint rc = 0;
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ if( pDb ){
+ rc = sqlite3_limit( pDb, (int)id, (int)newVal );
+ }
+ return rc;
+}
+
+/* Pre-open() code common to sqlite3_open[_v2](). */
+static int s3jni_open_pre(JNIEnv * const env, S3JniEnv **jc,
+ jstring jDbName, char **zDbName,
+ S3JniDb ** ps){
+ int rc = 0;
+ jobject jDb = 0;
+
+ *jc = S3JniEnv_get();
+ if( !*jc ){
+ rc = SQLITE_NOMEM;
+ goto end;
+ }
+ *zDbName = jDbName ? s3jni_jstring_to_utf8( jDbName, 0) : 0;
+ if( jDbName && !*zDbName ){
+ rc = SQLITE_NOMEM;
+ goto end;
+ }
+ jDb = new_java_sqlite3(env, 0);
+ if( !jDb ){
+ sqlite3_free(*zDbName);
+ *zDbName = 0;
+ rc = SQLITE_NOMEM;
+ goto end;
+ }
+ *ps = S3JniDb_alloc(env, jDb);
+ if( *ps ){
+ (*jc)->pdbOpening = *ps;
+ }else{
+ S3JniUnrefLocal(jDb);
+ rc = SQLITE_NOMEM;
+ }
+end:
+ return rc;
+}
+
+/*
+** Post-open() code common to both the sqlite3_open() and
+** sqlite3_open_v2() bindings. ps->jDb must be the
+** org.sqlite.jni.capi.sqlite3 object which will hold the db's native
+** pointer. theRc must be the result code of the open() op. If
+** *ppDb is NULL then ps is set aside and its state cleared,
+** else ps is associated with *ppDb. If *ppDb is not NULL then
+** ps->jDb is stored in jOut (an OutputPointer.sqlite3 instance).
+**
+** Must be called if s3jni_open_pre() succeeds and must not be called
+** if it doesn't.
+**
+** Returns theRc.
+*/
+static int s3jni_open_post(JNIEnv * const env, S3JniEnv * const jc,
+ S3JniDb * ps, sqlite3 **ppDb,
+ jobject jOut, int theRc){
+ int rc = 0;
+ jc->pdbOpening = 0;
+ if( *ppDb ){
+ assert(ps->jDb);
+ if( 0==ps->pDb ){
+ ps->pDb = *ppDb;
+ NativePointerHolder_set(S3JniNph(sqlite3), ps->jDb, *ppDb);
+ }else{
+ assert( ps->pDb==*ppDb
+ && "Set up via s3jni_run_java_auto_extensions()" );
+ }
+ rc = sqlite3_set_clientdata(ps->pDb, S3JniDb_clientdata_key,
+ ps, S3JniDb_xDestroy)
+ /* As of here, the Java/C connection is complete */;
+ }else{
+ S3JniDb_set_aside(ps);
+ ps = 0;
+ }
+ OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3),
+ jOut, ps ? ps->jDb : 0);
+ return theRc ? theRc : rc;
+}
+
+S3JniApi(sqlite3_open(),jint,1open)(
+ JniArgsEnvClass, jstring strName, jobject jOut
+){
+ sqlite3 * pOut = 0;
+ char *zName = 0;
+ S3JniDb * ps = 0;
+ S3JniEnv * jc = 0;
+ int rc;
+
+ if( 0==jOut ) return SQLITE_MISUSE;
+ rc = s3jni_open_pre(env, &jc, strName, &zName, &ps);
+ if( 0==rc ){
+ rc = s3jni_open_post(env, jc, ps, &pOut, jOut,
+ sqlite3_open(zName, &pOut));
+ assert(rc==0 ? pOut!=0 : 1);
+ sqlite3_free(zName);
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_open_v2(),jint,1open_1v2)(
+ JniArgsEnvClass, jstring strName,
+ jobject jOut, jint flags, jstring strVfs
+){
+ sqlite3 * pOut = 0;
+ char *zName = 0;
+ S3JniDb * ps = 0;
+ S3JniEnv * jc = 0;
+ char *zVfs = 0;
+ int rc;
+
+ if( 0==jOut ) return SQLITE_MISUSE;
+ rc = s3jni_open_pre(env, &jc, strName, &zName, &ps);
+ if( 0==rc ){
+ if( strVfs ){
+ zVfs = s3jni_jstring_to_utf8( strVfs, 0);
+ if( !zVfs ){
+ rc = SQLITE_NOMEM;
+ }
+ }
+ if( 0==rc ){
+ rc = sqlite3_open_v2(zName, &pOut, (int)flags, zVfs);
+ }
+ rc = s3jni_open_post(env, jc, ps, &pOut, jOut, rc);
+ }
+ assert(rc==0 ? pOut!=0 : 1);
+ sqlite3_free(zName);
+ sqlite3_free(zVfs);
+ return (jint)rc;
+}
+
+/* Proxy for the sqlite3_prepare[_v2/3]() family. */
+static jint sqlite3_jni_prepare_v123( int prepVersion, JNIEnv * const env,
+ jclass self,
+ jlong jpDb, jbyteArray baSql,
+ jint nMax, jint prepFlags,
+ jobject jOutStmt, jobject outTail){
+ sqlite3_stmt * pStmt = 0;
+ jobject jStmt = 0;
+ const char * zTail = 0;
+ sqlite3 * const pDb = LongPtrGet_sqlite3(jpDb);
+ jbyte * const pBuf = pDb ? s3jni_jbyteArray_bytes(baSql) : 0;
+ int rc = SQLITE_ERROR;
+
+ assert(prepVersion==1 || prepVersion==2 || prepVersion==3);
+ if( !pDb || !jOutStmt ){
+ rc = SQLITE_MISUSE;
+ goto end;
+ }else if( !pBuf ){
+ rc = baSql ? SQLITE_NOMEM : SQLITE_MISUSE;
+ goto end;
+ }
+ jStmt = new_java_sqlite3_stmt(env, 0);
+ if( !jStmt ){
+ rc = SQLITE_NOMEM;
+ goto end;
+ }
+ switch( prepVersion ){
+ case 1: rc = sqlite3_prepare(pDb, (const char *)pBuf,
+ (int)nMax, &pStmt, &zTail);
+ break;
+ case 2: rc = sqlite3_prepare_v2(pDb, (const char *)pBuf,
+ (int)nMax, &pStmt, &zTail);
+ break;
+ case 3: rc = sqlite3_prepare_v3(pDb, (const char *)pBuf,
+ (int)nMax, (unsigned int)prepFlags,
+ &pStmt, &zTail);
+ break;
+ default:
+ assert(!"Invalid prepare() version");
+ }
+end:
+ s3jni_jbyteArray_release(baSql,pBuf);
+ if( 0==rc ){
+ if( 0!=outTail ){
+ /* Noting that pBuf is deallocated now but its address is all we need for
+ ** what follows... */
+ assert(zTail ? ((void*)zTail>=(void*)pBuf) : 1);
+ assert(zTail ? (((int)((void*)zTail - (void*)pBuf)) >= 0) : 1);
+ OutputPointer_set_Int32(
+ env, outTail, (int)(zTail ? (zTail - (const char *)pBuf) : 0)
+ );
+ }
+ if( pStmt ){
+ NativePointerHolder_set(S3JniNph(sqlite3_stmt), jStmt, pStmt);
+ }else{
+ /* Happens for comments and whitespace. */
+ S3JniUnrefLocal(jStmt);
+ jStmt = 0;
+ }
+ }else{
+ S3JniUnrefLocal(jStmt);
+ jStmt = 0;
+ }
+ if( jOutStmt ){
+ OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_stmt),
+ jOutStmt, jStmt);
+ }
+ return (jint)rc;
+}
+S3JniApi(sqlite3_prepare(),jint,1prepare)(
+ JNIEnv * const env, jclass self, jlong jpDb, jbyteArray baSql,
+ jint nMax, jobject jOutStmt, jobject outTail
+){
+ return sqlite3_jni_prepare_v123(1, env, self, jpDb, baSql, nMax, 0,
+ jOutStmt, outTail);
+}
+S3JniApi(sqlite3_prepare_v2(),jint,1prepare_1v2)(
+ JNIEnv * const env, jclass self, jlong jpDb, jbyteArray baSql,
+ jint nMax, jobject jOutStmt, jobject outTail
+){
+ return sqlite3_jni_prepare_v123(2, env, self, jpDb, baSql, nMax, 0,
+ jOutStmt, outTail);
+}
+S3JniApi(sqlite3_prepare_v3(),jint,1prepare_1v3)(
+ JNIEnv * const env, jclass self, jlong jpDb, jbyteArray baSql,
+ jint nMax, jint prepFlags, jobject jOutStmt, jobject outTail
+){
+ return sqlite3_jni_prepare_v123(3, env, self, jpDb, baSql, nMax,
+ prepFlags, jOutStmt, outTail);
+}
+
+/*
+** Impl for C-to-Java of the callbacks for both sqlite3_update_hook()
+** and sqlite3_preupdate_hook(). The differences are that for
+** update_hook():
+**
+** - pDb is NULL
+** - iKey1 is the row ID
+** - iKey2 is unused
+*/
+static void s3jni_updatepre_hook_impl(void * pState, sqlite3 *pDb, int opId,
+ const char *zDb, const char *zTable,
+ sqlite3_int64 iKey1, sqlite3_int64 iKey2){
+ S3JniDb * const ps = pState;
+ S3JniDeclLocal_env;
+ jstring jDbName;
+ jstring jTable;
+ const int isPre = 0!=pDb;
+ S3JniHook hook;
+
+ S3JniHook_localdup(isPre ?
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ &ps->hooks.preUpdate
+#else
+ &S3JniHook_empty
+#endif
+ : &ps->hooks.update, &hook);
+ if( !hook.jObj ){
+ return;
+ }
+ jDbName = s3jni_utf8_to_jstring( zDb, -1);
+ jTable = jDbName ? s3jni_utf8_to_jstring( zTable, -1) : 0;
+ S3JniIfThrew {
+ S3JniExceptionClear;
+ s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
+ }else{
+ assert( hook.jObj );
+ assert( hook.midCallback );
+ assert( ps->jDb );
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ if( isPre ) (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback,
+ ps->jDb, (jint)opId, jDbName, jTable,
+ (jlong)iKey1, (jlong)iKey2);
+ else
+#endif
+ (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback,
+ (jint)opId, jDbName, jTable, (jlong)iKey1);
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("sqlite3_(pre)update_hook() callback");
+ s3jni_db_exception(ps->pDb, 0,
+ "sqlite3_(pre)update_hook() callback threw");
+ }
+ }
+ S3JniUnrefLocal(jDbName);
+ S3JniUnrefLocal(jTable);
+ S3JniHook_localundup(hook);
+}
+
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+static void s3jni_preupdate_hook_impl(void * pState, sqlite3 *pDb, int opId,
+ const char *zDb, const char *zTable,
+ sqlite3_int64 iKey1, sqlite3_int64 iKey2){
+ return s3jni_updatepre_hook_impl(pState, pDb, opId, zDb, zTable,
+ iKey1, iKey2);
+}
+#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
+
+static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb,
+ const char *zTable, sqlite3_int64 nRowid){
+ return s3jni_updatepre_hook_impl(pState, NULL, opId, zDb, zTable, nRowid, 0);
+}
+
+#if !defined(SQLITE_ENABLE_PREUPDATE_HOOK)
+/* We need no-op impls for preupdate_{count,depth,blobwrite}() */
+S3JniApi(sqlite3_preupdate_blobwrite(),jint,1preupdate_1blobwrite)(
+ JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; }
+S3JniApi(sqlite3_preupdate_count(),jint,1preupdate_1count)(
+ JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; }
+S3JniApi(sqlite3_preupdate_depth(),jint,1preupdate_1depth)(
+ JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; }
+#endif /* !SQLITE_ENABLE_PREUPDATE_HOOK */
+
+/*
+** JNI wrapper for both sqlite3_update_hook() and
+** sqlite3_preupdate_hook() (if isPre is true).
+*/
+static jobject s3jni_updatepre_hook(JNIEnv * env, int isPre, jlong jpDb, jobject jHook){
+ S3JniDb * const ps = S3JniDb_from_jlong(jpDb);
+ jclass klazz;
+ jobject pOld = 0;
+ jmethodID xCallback;
+ S3JniHook * pHook;
+
+ if( !ps ) return 0;
+ S3JniDb_mutex_enter;
+ pHook = isPre ?
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ &ps->hooks.preUpdate
+#else
+ 0
+#endif
+ : &ps->hooks.update;
+ if( !pHook ){
+ goto end;
+ }
+ pOld = pHook->jObj;
+ if( pOld && jHook && (*env)->IsSameObject(env, pOld, jHook) ){
+ goto end;
+ }
+ if( !jHook ){
+ if( pOld ){
+ jobject tmp = S3JniRefLocal(pOld);
+ S3JniUnrefGlobal(pOld);
+ pOld = tmp;
+ }
+ *pHook = S3JniHook_empty;
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ if( isPre ) sqlite3_preupdate_hook(ps->pDb, 0, 0);
+ else
+#endif
+ sqlite3_update_hook(ps->pDb, 0, 0);
+ goto end;
+ }
+ klazz = (*env)->GetObjectClass(env, jHook);
+ xCallback = isPre
+ ? (*env)->GetMethodID(env, klazz, "call",
+ "(Lorg/sqlite/jni/capi/sqlite3;"
+ "I"
+ "Ljava/lang/String;"
+ "Ljava/lang/String;"
+ "JJ)V")
+ : (*env)->GetMethodID(env, klazz, "call",
+ "(ILjava/lang/String;Ljava/lang/String;J)V");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ S3JniExceptionClear;
+ s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ "Cannot not find matching callback on "
+ "(pre)update hook object.");
+ }else{
+ pHook->midCallback = xCallback;
+ pHook->jObj = S3JniRefGlobal(jHook);
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ if( isPre ) sqlite3_preupdate_hook(ps->pDb, s3jni_preupdate_hook_impl, ps);
+ else
+#endif
+ sqlite3_update_hook(ps->pDb, s3jni_update_hook_impl, ps);
+ if( pOld ){
+ jobject tmp = S3JniRefLocal(pOld);
+ S3JniUnrefGlobal(pOld);
+ pOld = tmp;
+ }
+ }
+end:
+ S3JniDb_mutex_leave;
+ return pOld;
+}
+
+
+S3JniApi(sqlite3_preupdate_hook(),jobject,1preupdate_1hook)(
+ JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ return s3jni_updatepre_hook(env, 1, jpDb, jHook);
+#else
+ return NULL;
+#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
+}
+
+/* Impl for sqlite3_preupdate_{new,old}(). */
+static int s3jni_preupdate_newold(JNIEnv * const env, int isNew, jlong jpDb,
+ jint iCol, jobject jOut){
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ sqlite3 * const pDb = LongPtrGet_sqlite3(jpDb);
+ int rc = SQLITE_MISUSE;
+ if( pDb ){
+ sqlite3_value * pOut = 0;
+ int (*fOrig)(sqlite3*,int,sqlite3_value**) =
+ isNew ? sqlite3_preupdate_new : sqlite3_preupdate_old;
+ rc = fOrig(pDb, (int)iCol, &pOut);
+ if( 0==rc ){
+ jobject pWrap = new_java_sqlite3_value(env, pOut);
+ if( !pWrap ){
+ rc = SQLITE_NOMEM;
+ }
+ OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_value),
+ jOut, pWrap);
+ S3JniUnrefLocal(pWrap);
+ }
+ }
+ return rc;
+#else
+ return SQLITE_MISUSE;
+#endif
+}
+
+S3JniApi(sqlite3_preupdate_new(),jint,1preupdate_1new)(
+ JniArgsEnvClass, jlong jpDb, jint iCol, jobject jOut
+){
+ return s3jni_preupdate_newold(env, 1, jpDb, iCol, jOut);
+}
+
+S3JniApi(sqlite3_preupdate_old(),jint,1preupdate_1old)(
+ JniArgsEnvClass, jlong jpDb, jint iCol, jobject jOut
+){
+ return s3jni_preupdate_newold(env, 0, jpDb, iCol, jOut);
+}
+
+
+/* Central C-to-Java sqlite3_progress_handler() proxy. */
+static int s3jni_progress_handler_impl(void *pP){
+ S3JniDb * const ps = (S3JniDb *)pP;
+ int rc = 0;
+ S3JniDeclLocal_env;
+ S3JniHook hook;
+
+ S3JniHook_localdup(&ps->hooks.progress, &hook);
+ if( hook.jObj ){
+ rc = (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback);
+ S3JniIfThrew{
+ rc = s3jni_db_exception(ps->pDb, rc,
+ "sqlite3_progress_handler() callback threw");
+ }
+ S3JniHook_localundup(hook);
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_progress_handler(),void,1progress_1handler)(
+ JniArgsEnvClass,jobject jDb, jint n, jobject jProgress
+){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ S3JniHook * const pHook = ps ? &ps->hooks.progress : 0;
+
+ if( !ps ) return;
+ S3JniDb_mutex_enter;
+ if( n<1 || !jProgress ){
+ S3JniHook_unref(pHook);
+ sqlite3_progress_handler(ps->pDb, 0, 0, 0);
+ }else{
+ jclass const klazz = (*env)->GetObjectClass(env, jProgress);
+ jmethodID const xCallback = (*env)->GetMethodID(env, klazz, "call", "()I");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ S3JniExceptionClear;
+ s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ "Cannot not find matching xCallback() on "
+ "ProgressHandler object.");
+ }else{
+ S3JniUnrefGlobal(pHook->jObj);
+ pHook->midCallback = xCallback;
+ pHook->jObj = S3JniRefGlobal(jProgress);
+ sqlite3_progress_handler(ps->pDb, (int)n, s3jni_progress_handler_impl, ps);
+ }
+ }
+ S3JniDb_mutex_leave;
+}
+
+S3JniApi(sqlite3_randomness(),void,1randomness)(
+ JniArgsEnvClass, jbyteArray jTgt
+){
+ jbyte * const jba = s3jni_jbyteArray_bytes(jTgt);
+ if( jba ){
+ jsize const nTgt = (*env)->GetArrayLength(env, jTgt);
+ sqlite3_randomness( (int)nTgt, jba );
+ s3jni_jbyteArray_commit(jTgt, jba);
+ }
+}
+
+
+S3JniApi(sqlite3_reset(),jint,1reset)(
+ JniArgsEnvClass, jobject jpStmt
+){
+ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+ return pStmt ? sqlite3_reset(pStmt) : SQLITE_MISUSE;
+}
+
+/* Clears all entries from S3JniGlobal.autoExt. */
+static void s3jni_reset_auto_extension(JNIEnv *env){
+ int i;
+ S3JniAutoExt_mutex_enter;
+ for( i = 0; i < SJG.autoExt.nExt; ++i ){
+ S3JniAutoExtension_clear( &SJG.autoExt.aExt[i] );
+ }
+ SJG.autoExt.nExt = 0;
+ S3JniAutoExt_mutex_leave;
+}
+
+S3JniApi(sqlite3_reset_auto_extension(),void,1reset_1auto_1extension)(
+ JniArgsEnvClass
+){
+ s3jni_reset_auto_extension(env);
+}
+
+/* Impl for sqlite3_result_text/blob() and friends. */
+static void result_blob_text(int as64 /* true for text64/blob64() mode */,
+ int eTextRep /* 0 for blobs, else SQLITE_UTF... */,
+ JNIEnv * const env, sqlite3_context *pCx,
+ jbyteArray jBa, jlong nMax){
+ int const asBlob = 0==eTextRep;
+ if( !pCx ){
+ /* We should arguably emit a warning here. But where to log it? */
+ return;
+ }else if( jBa ){
+ jbyte * const pBuf = s3jni_jbyteArray_bytes(jBa);
+ jsize nBA = (*env)->GetArrayLength(env, jBa);
+ if( nMax>=0 && nBA>(jsize)nMax ){
+ nBA = (jsize)nMax;
+ /**
+ From the sqlite docs:
+
+ > If the 3rd parameter to any of the sqlite3_result_text*
+ interfaces other than sqlite3_result_text64() is negative,
+ then SQLite computes the string length itself by searching
+ the 2nd parameter for the first zero character.
+
+ Note that the text64() interfaces take an unsigned value for
+ the length, which Java does not support. This binding takes
+ the approach of passing on negative values to the C API,
+ which will in turn fail with SQLITE_TOOBIG at some later
+ point (recall that the sqlite3_result_xyz() family do not
+ have result values).
+ */
+ }
+ if( as64 ){ /* 64-bit... */
+ static const jsize nLimit64 =
+ SQLITE_MAX_ALLOCATION_SIZE/*only _kinda_ arbitrary*/;
+ if( nBA > nLimit64 ){
+ sqlite3_result_error_toobig(pCx);
+ }else if( asBlob ){
+ sqlite3_result_blob64(pCx, pBuf, (sqlite3_uint64)nBA,
+ SQLITE_TRANSIENT);
+ }else{ /* text64... */
+ if( encodingTypeIsValid(eTextRep) ){
+ sqlite3_result_text64(pCx, (const char *)pBuf,
+ (sqlite3_uint64)nBA,
+ SQLITE_TRANSIENT, eTextRep);
+ }else{
+ sqlite3_result_error_code(pCx, SQLITE_FORMAT);
+ }
+ }
+ }else{ /* 32-bit... */
+ static const jsize nLimit = SQLITE_MAX_ALLOCATION_SIZE;
+ if( nBA > nLimit ){
+ sqlite3_result_error_toobig(pCx);
+ }else if( asBlob ){
+ sqlite3_result_blob(pCx, pBuf, (int)nBA,
+ SQLITE_TRANSIENT);
+ }else{
+ switch( eTextRep ){
+ case SQLITE_UTF8:
+ sqlite3_result_text(pCx, (const char *)pBuf, (int)nBA,
+ SQLITE_TRANSIENT);
+ break;
+ case SQLITE_UTF16:
+ sqlite3_result_text16(pCx, (const char *)pBuf, (int)nBA,
+ SQLITE_TRANSIENT);
+ break;
+ case SQLITE_UTF16LE:
+ sqlite3_result_text16le(pCx, (const char *)pBuf, (int)nBA,
+ SQLITE_TRANSIENT);
+ break;
+ case SQLITE_UTF16BE:
+ sqlite3_result_text16be(pCx, (const char *)pBuf, (int)nBA,
+ SQLITE_TRANSIENT);
+ break;
+ }
+ }
+ s3jni_jbyteArray_release(jBa, pBuf);
+ }
+ }else{
+ sqlite3_result_null(pCx);
+ }
+}
+
+S3JniApi(sqlite3_result_blob(),void,1result_1blob)(
+ JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jint nMax
+){
+ return result_blob_text(0, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax);
+}
+
+S3JniApi(sqlite3_result_blob64(),void,1result_1blob64)(
+ JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jlong nMax
+){
+ return result_blob_text(1, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax);
+}
+
+S3JniApi(sqlite3_result_double(),void,1result_1double)(
+ JniArgsEnvClass, jobject jpCx, jdouble v
+){
+ sqlite3_result_double(PtrGet_sqlite3_context(jpCx), v);
+}
+
+S3JniApi(sqlite3_result_error(),void,1result_1error)(
+ JniArgsEnvClass, jobject jpCx, jbyteArray baMsg, jint eTextRep
+){
+ const char * zUnspecified = "Unspecified error.";
+ jsize const baLen = (*env)->GetArrayLength(env, baMsg);
+ jbyte * const pjBuf = baMsg ? s3jni_jbyteArray_bytes(baMsg) : NULL;
+ switch( pjBuf ? eTextRep : SQLITE_UTF8 ){
+ case SQLITE_UTF8: {
+ const char *zMsg = pjBuf ? (const char *)pjBuf : zUnspecified;
+ int const n = pjBuf ? (int)baLen : (int)sqlite3Strlen30(zMsg);
+ sqlite3_result_error(PtrGet_sqlite3_context(jpCx), zMsg, n);
+ break;
+ }
+ case SQLITE_UTF16: {
+ const void *zMsg = pjBuf;
+ sqlite3_result_error16(PtrGet_sqlite3_context(jpCx), zMsg, (int)baLen);
+ break;
+ }
+ default:
+ sqlite3_result_error(PtrGet_sqlite3_context(jpCx),
+ "Invalid encoding argument passed "
+ "to sqlite3_result_error().", -1);
+ break;
+ }
+ s3jni_jbyteArray_release(baMsg,pjBuf);
+}
+
+S3JniApi(sqlite3_result_error_code(),void,1result_1error_1code)(
+ JniArgsEnvClass, jobject jpCx, jint v
+){
+ sqlite3_result_error_code(PtrGet_sqlite3_context(jpCx), (int)v);
+}
+
+S3JniApi(sqlite3_result_error_nomem(),void,1result_1error_1nomem)(
+ JniArgsEnvClass, jobject jpCx
+){
+ sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx));
+}
+
+S3JniApi(sqlite3_result_error_toobig(),void,1result_1error_1toobig)(
+ JniArgsEnvClass, jobject jpCx
+){
+ sqlite3_result_error_toobig(PtrGet_sqlite3_context(jpCx));
+}
+
+S3JniApi(sqlite3_result_int(),void,1result_1int)(
+ JniArgsEnvClass, jobject jpCx, jint v
+){
+ sqlite3_result_int(PtrGet_sqlite3_context(jpCx), (int)v);
+}
+
+S3JniApi(sqlite3_result_int64(),void,1result_1int64)(
+ JniArgsEnvClass, jobject jpCx, jlong v
+){
+ sqlite3_result_int64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v);
+}
+
+S3JniApi(sqlite3_result_java_object(),void,1result_1java_1object)(
+ JniArgsEnvClass, jobject jpCx, jobject v
+){
+ sqlite3_context * pCx = PtrGet_sqlite3_context(jpCx);
+ if( !pCx ) return;
+ else if( v ){
+ jobject const rjv = S3JniRefGlobal(v);
+ if( rjv ){
+ sqlite3_result_pointer(pCx, rjv,
+ s3jni__value_jref_key, S3Jni_jobject_finalizer);
+ }else{
+ sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx));
+ }
+ }else{
+ sqlite3_result_null(PtrGet_sqlite3_context(jpCx));
+ }
+}
+
+S3JniApi(sqlite3_result_nio_buffer(),void,1result_1nio_1buffer)(
+ JniArgsEnvClass, jobject jpCtx, jobject jBuffer,
+ jint iOffset, jint iN
+){
+ sqlite3_context * pCx = PtrGet_sqlite3_context(jpCtx);
+ int rc;
+ S3JniNioArgs args;
+ if( !pCx ){
+ return;
+ }else if( !SJG.g.byteBuffer.klazz ){
+ sqlite3_result_error(
+ pCx, "This JVM does not support JNI access to ByteBuffers.", -1
+ );
+ return;
+ }
+ rc = s3jni_setup_nio_args(env, &args, jBuffer, iOffset, iN);
+ if(rc){
+ if( iOffset<0 ){
+ sqlite3_result_error(pCx, "Start index may not be negative.", -1);
+ }else if( SQLITE_TOOBIG==rc ){
+ sqlite3_result_error_toobig(pCx);
+ }else{
+ sqlite3_result_error(
+ pCx, "Invalid arguments to sqlite3_result_nio_buffer().", -1
+ );
+ }
+ }else if( !args.pStart || !args.nOut ){
+ sqlite3_result_null(pCx);
+ }else{
+ sqlite3_result_blob(pCx, args.pStart, args.nOut, SQLITE_TRANSIENT);
+ }
+}
+
+
+S3JniApi(sqlite3_result_null(),void,1result_1null)(
+ JniArgsEnvClass, jobject jpCx
+){
+ sqlite3_result_null(PtrGet_sqlite3_context(jpCx));
+}
+
+S3JniApi(sqlite3_result_subtype(),void,1result_1subtype)(
+ JniArgsEnvClass, jobject jpCx, jint v
+){
+ sqlite3_result_subtype(PtrGet_sqlite3_context(jpCx), (unsigned int)v);
+}
+
+
+S3JniApi(sqlite3_result_text(),void,1result_1text)(
+ JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jint nMax
+){
+ return result_blob_text(0, SQLITE_UTF8, env,
+ PtrGet_sqlite3_context(jpCx), jBa, nMax);
+}
+
+S3JniApi(sqlite3_result_text64(),void,1result_1text64)(
+ JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jlong nMax,
+ jint eTextRep
+){
+ return result_blob_text(1, eTextRep, env,
+ PtrGet_sqlite3_context(jpCx), jBa, nMax);
+}
+
+S3JniApi(sqlite3_result_value(),void,1result_1value)(
+ JniArgsEnvClass, jobject jpCx, jobject jpSVal
+){
+ sqlite3_result_value(PtrGet_sqlite3_context(jpCx),
+ PtrGet_sqlite3_value(jpSVal));
+}
+
+S3JniApi(sqlite3_result_zeroblob(),void,1result_1zeroblob)(
+ JniArgsEnvClass, jobject jpCx, jint v
+){
+ sqlite3_result_zeroblob(PtrGet_sqlite3_context(jpCx), (int)v);
+}
+
+S3JniApi(sqlite3_result_zeroblob64(),jint,1result_1zeroblob64)(
+ JniArgsEnvClass, jobject jpCx, jlong v
+){
+ return (jint)sqlite3_result_zeroblob64(PtrGet_sqlite3_context(jpCx),
+ (sqlite3_int64)v);
+}
+
+S3JniApi(sqlite3_rollback_hook(),jobject,1rollback_1hook)(
+ JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+ return s3jni_commit_rollback_hook(0, env, jpDb, jHook);
+}
+
+/* Callback for sqlite3_set_authorizer(). */
+int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1,
+ const char*z2,const char*z3){
+ S3JniDb * const ps = pState;
+ S3JniDeclLocal_env;
+ S3JniHook hook;
+ int rc = 0;
+
+ S3JniHook_localdup(&ps->hooks.auth, &hook );
+ if( hook.jObj ){
+ jstring const s0 = z0 ? s3jni_utf8_to_jstring( z0, -1) : 0;
+ jstring const s1 = z1 ? s3jni_utf8_to_jstring( z1, -1) : 0;
+ jstring const s2 = z2 ? s3jni_utf8_to_jstring( z2, -1) : 0;
+ jstring const s3 = z3 ? s3jni_utf8_to_jstring( z3, -1) : 0;
+
+ rc = (*env)->CallIntMethod(env, hook.jObj, hook.midCallback, (jint)op,
+ s0, s1, s3, s3);
+ S3JniIfThrew{
+ rc = s3jni_db_exception(ps->pDb, rc, "sqlite3_set_authorizer() callback");
+ }
+ S3JniUnrefLocal(s0);
+ S3JniUnrefLocal(s1);
+ S3JniUnrefLocal(s2);
+ S3JniUnrefLocal(s3);
+ S3JniHook_localundup(hook);
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_set_authorizer(),jint,1set_1authorizer)(
+ JniArgsEnvClass,jobject jDb, jobject jHook
+){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ S3JniHook * const pHook = ps ? &ps->hooks.auth : 0;
+ int rc = 0;
+
+ if( !ps ) return SQLITE_MISUSE;
+ S3JniDb_mutex_enter;
+ if( !jHook ){
+ S3JniHook_unref(pHook);
+ rc = sqlite3_set_authorizer( ps->pDb, 0, 0 );
+ }else{
+ jclass klazz;
+ if( pHook->jObj ){
+ if( (*env)->IsSameObject(env, pHook->jObj, jHook) ){
+ /* Same object - this is a no-op. */
+ S3JniDb_mutex_leave;
+ return 0;
+ }
+ S3JniHook_unref(pHook);
+ }
+ pHook->jObj = S3JniRefGlobal(jHook);
+ klazz = (*env)->GetObjectClass(env, jHook);
+ pHook->midCallback = (*env)->GetMethodID(env, klazz,
+ "call",
+ "(I"
+ "Ljava/lang/String;"
+ "Ljava/lang/String;"
+ "Ljava/lang/String;"
+ "Ljava/lang/String;"
+ ")I");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ rc = s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ "Error setting up Java parts of authorizer hook.");
+ }else{
+ rc = sqlite3_set_authorizer(ps->pDb, s3jni_xAuth, ps);
+ }
+ if( rc ) S3JniHook_unref(pHook);
+ }
+ S3JniDb_mutex_leave;
+ return rc;
+}
+
+S3JniApi(sqlite3_set_auxdata(),void,1set_1auxdata)(
+ JniArgsEnvClass, jobject jCx, jint n, jobject jAux
+){
+ sqlite3_set_auxdata(PtrGet_sqlite3_context(jCx), (int)n,
+ S3JniRefGlobal(jAux), S3Jni_jobject_finalizer);
+}
+
+S3JniApi(sqlite3_set_last_insert_rowid(),void,1set_1last_1insert_1rowid)(
+ JniArgsEnvClass, jobject jpDb, jlong rowId
+){
+ sqlite3_set_last_insert_rowid(PtrGet_sqlite3(jpDb),
+ (sqlite3_int64)rowId);
+}
+
+S3JniApi(sqlite3_shutdown(),jint,1shutdown)(
+ JniArgsEnvClass
+){
+ s3jni_reset_auto_extension(env);
+#ifdef SQLITE_ENABLE_SQLLOG
+ S3JniHook_unref(&SJG.hook.sqllog);
+#endif
+ S3JniHook_unref(&SJG.hook.configlog);
+ /* Free up S3JniDb recycling bin. */
+ S3JniDb_mutex_enter; {
+ while( S3JniGlobal.perDb.aFree ){
+ S3JniDb * const d = S3JniGlobal.perDb.aFree;
+ S3JniGlobal.perDb.aFree = d->pNext;
+ S3JniDb_clear(env, d);
+ sqlite3_free(d);
+ }
+ } S3JniDb_mutex_leave;
+ S3JniGlobal_mutex_enter; {
+ /* Free up S3JniUdf recycling bin. */
+ while( S3JniGlobal.udf.aFree ){
+ S3JniUdf * const u = S3JniGlobal.udf.aFree;
+ S3JniGlobal.udf.aFree = u->pNext;
+ u->pNext = 0;
+ S3JniUdf_free(env, u, 0);
+ }
+ } S3JniGlobal_mutex_leave;
+ S3JniHook_mutex_enter; {
+ /* Free up S3JniHook recycling bin. */
+ while( S3JniGlobal.hook.aFree ){
+ S3JniHook * const u = S3JniGlobal.hook.aFree;
+ S3JniGlobal.hook.aFree = u->pNext;
+ u->pNext = 0;
+ assert( !u->doXDestroy );
+ assert( !u->jObj );
+ assert( !u->jExtra );
+ sqlite3_free( u );
+ }
+ } S3JniHook_mutex_leave;
+ /* Free up env cache. */
+ S3JniEnv_mutex_enter; {
+ while( SJG.envCache.aHead ){
+ S3JniEnv_uncache( SJG.envCache.aHead->env );
+ }
+ } S3JniEnv_mutex_leave;
+ /* Do not clear S3JniGlobal.jvm or S3JniGlobal.g: it's legal to
+ ** restart the lib. */
+ return sqlite3_shutdown();
+}
+
+S3JniApi(sqlite3_status(),jint,1status)(
+ JniArgsEnvClass, jint op, jobject jOutCurrent, jobject jOutHigh,
+ jboolean reset
+){
+ int iCur = 0, iHigh = 0;
+ int rc = sqlite3_status( op, &iCur, &iHigh, reset );
+ if( 0==rc ){
+ OutputPointer_set_Int32(env, jOutCurrent, iCur);
+ OutputPointer_set_Int32(env, jOutHigh, iHigh);
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_status64(),jint,1status64)(
+ JniArgsEnvClass, jint op, jobject jOutCurrent, jobject jOutHigh,
+ jboolean reset
+){
+ sqlite3_int64 iCur = 0, iHigh = 0;
+ int rc = sqlite3_status64( op, &iCur, &iHigh, reset );
+ if( 0==rc ){
+ OutputPointer_set_Int64(env, jOutCurrent, iCur);
+ OutputPointer_set_Int64(env, jOutHigh, iHigh);
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_stmt_status(),jint,1stmt_1status)(
+ JniArgsEnvClass, jobject jStmt, jint op, jboolean reset
+){
+ return sqlite3_stmt_status(PtrGet_sqlite3_stmt(jStmt),
+ (int)op, reset ? 1 : 0);
+}
+
+
+static int s3jni_strlike_glob(int isLike, JNIEnv *const env,
+ jbyteArray baG, jbyteArray baT, jint escLike){
+ int rc = 0;
+ jbyte * const pG = s3jni_jbyteArray_bytes(baG);
+ jbyte * const pT = s3jni_jbyteArray_bytes(baT);
+
+ /* Note that we're relying on the byte arrays having been
+ NUL-terminated on the Java side. */
+ rc = isLike
+ ? sqlite3_strlike((const char *)pG, (const char *)pT,
+ (unsigned int)escLike)
+ : sqlite3_strglob((const char *)pG, (const char *)pT);
+ s3jni_jbyteArray_release(baG, pG);
+ s3jni_jbyteArray_release(baT, pT);
+ return rc;
+}
+
+S3JniApi(sqlite3_strglob(),jint,1strglob)(
+ JniArgsEnvClass, jbyteArray baG, jbyteArray baT
+){
+ return s3jni_strlike_glob(0, env, baG, baT, 0);
+}
+
+S3JniApi(sqlite3_strlike(),jint,1strlike)(
+ JniArgsEnvClass, jbyteArray baG, jbyteArray baT, jint escChar
+){
+ return s3jni_strlike_glob(1, env, baG, baT, escChar);
+}
+
+S3JniApi(sqlite3_sql(),jstring,1sql)(
+ JniArgsEnvClass, jobject jpStmt
+){
+ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+ jstring rv = 0;
+ if( pStmt ){
+ const char * zSql = 0;
+ zSql = sqlite3_sql(pStmt);
+ rv = s3jni_utf8_to_jstring( zSql, -1);
+ }
+ return rv;
+}
+
+S3JniApi(sqlite3_step(),jint,1step)(
+ JniArgsEnvClass, jlong jpStmt
+){
+ sqlite3_stmt * const pStmt = LongPtrGet_sqlite3_stmt(jpStmt);
+ return pStmt ? (jint)sqlite3_step(pStmt) : (jint)SQLITE_MISUSE;
+}
+
+S3JniApi(sqlite3_table_column_metadata(),jint,1table_1column_1metadata)(
+ JniArgsEnvClass, jobject jDb, jstring jDbName, jstring jTableName,
+ jstring jColumnName, jobject jDataType, jobject jCollSeq, jobject jNotNull,
+ jobject jPrimaryKey, jobject jAutoinc
+){
+ sqlite3 * const db = PtrGet_sqlite3(jDb);
+ char * zDbName = 0, * zTableName = 0, * zColumnName = 0;
+ const char * pzCollSeq = 0;
+ const char * pzDataType = 0;
+ int pNotNull = 0, pPrimaryKey = 0, pAutoinc = 0;
+ int rc;
+
+ if( !db || !jDbName || !jTableName ) return SQLITE_MISUSE;
+ zDbName = s3jni_jstring_to_utf8(jDbName,0);
+ zTableName = zDbName ? s3jni_jstring_to_utf8(jTableName,0) : 0;
+ zColumnName = (zTableName && jColumnName)
+ ? s3jni_jstring_to_utf8(jColumnName,0) : 0;
+ rc = zTableName
+ ? sqlite3_table_column_metadata(db, zDbName, zTableName,
+ zColumnName, &pzDataType, &pzCollSeq,
+ &pNotNull, &pPrimaryKey, &pAutoinc)
+ : SQLITE_NOMEM;
+ if( 0==rc ){
+ jstring jseq = jCollSeq
+ ? (pzCollSeq ? s3jni_utf8_to_jstring(pzCollSeq, -1) : 0)
+ : 0;
+ jstring jdtype = jDataType
+ ? (pzDataType ? s3jni_utf8_to_jstring(pzDataType, -1) : 0)
+ : 0;
+ if( (jCollSeq && pzCollSeq && !jseq)
+ || (jDataType && pzDataType && !jdtype) ){
+ rc = SQLITE_NOMEM;
+ }else{
+ if( jNotNull ) OutputPointer_set_Bool(env, jNotNull, pNotNull);
+ if( jPrimaryKey ) OutputPointer_set_Bool(env, jPrimaryKey, pPrimaryKey);
+ if( jAutoinc ) OutputPointer_set_Bool(env, jAutoinc, pAutoinc);
+ if( jCollSeq ) OutputPointer_set_String(env, jCollSeq, jseq);
+ if( jDataType ) OutputPointer_set_String(env, jDataType, jdtype);
+ }
+ S3JniUnrefLocal(jseq);
+ S3JniUnrefLocal(jdtype);
+ }
+ sqlite3_free(zDbName);
+ sqlite3_free(zTableName);
+ sqlite3_free(zColumnName);
+ return rc;
+}
+
+static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){
+ S3JniDb * const ps = (S3JniDb *)pC;
+ S3JniDeclLocal_env;
+ jobject jX = NULL /* the tracer's X arg */;
+ jobject jP = NULL /* the tracer's P arg */;
+ jobject jPUnref = NULL /* potentially a local ref to jP */;
+ int rc = 0;
+ S3JniHook hook;
+
+ S3JniHook_localdup(&ps->hooks.trace, &hook );
+ if( !hook.jObj ){
+ return 0;
+ }
+ switch( traceflag ){
+ case SQLITE_TRACE_STMT:
+ jX = s3jni_utf8_to_jstring( (const char *)pX, -1);
+ if( !jX ) rc = SQLITE_NOMEM;
+ break;
+ case SQLITE_TRACE_PROFILE:
+ jX = (*env)->NewObject(env, SJG.g.cLong, SJG.g.ctorLong1,
+ (jlong)*((sqlite3_int64*)pX));
+ // hmm. ^^^ (*pX) really is zero.
+ // MARKER(("profile time = %llu\n", *((sqlite3_int64*)pX)));
+ s3jni_oom_check( jX );
+ if( !jX ) rc = SQLITE_NOMEM;
+ break;
+ case SQLITE_TRACE_ROW:
+ break;
+ case SQLITE_TRACE_CLOSE:
+ jP = jPUnref = S3JniRefLocal(ps->jDb);
+ break;
+ default:
+ assert(!"cannot happen - unknown trace flag");
+ rc = SQLITE_ERROR;
+ }
+ if( 0==rc ){
+ if( !jP ){
+ /* Create a new temporary sqlite3_stmt wrapper */
+ jP = jPUnref = new_java_sqlite3_stmt(env, pP);
+ if( !jP ){
+ rc = SQLITE_NOMEM;
+ }
+ }
+ if( 0==rc ){
+ assert(jP);
+ rc = (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback,
+ (jint)traceflag, jP, jX);
+ S3JniIfThrew{
+ rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR,
+ "sqlite3_trace_v2() callback threw.");
+ }
+ }
+ }
+ S3JniUnrefLocal(jPUnref);
+ S3JniUnrefLocal(jX);
+ S3JniHook_localundup(hook);
+ return rc;
+}
+
+S3JniApi(sqlite3_trace_v2(),jint,1trace_1v2)(
+ JniArgsEnvClass,jobject jDb, jint traceMask, jobject jTracer
+){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ int rc;
+
+ if( !ps ) return SQLITE_MISUSE;
+ if( !traceMask || !jTracer ){
+ S3JniDb_mutex_enter;
+ rc = (jint)sqlite3_trace_v2(ps->pDb, 0, 0, 0);
+ S3JniHook_unref(&ps->hooks.trace);
+ S3JniDb_mutex_leave;
+ }else{
+ jclass const klazz = (*env)->GetObjectClass(env, jTracer);
+ S3JniHook hook = S3JniHook_empty;
+ hook.midCallback = (*env)->GetMethodID(
+ env, klazz, "call", "(ILjava/lang/Object;Ljava/lang/Object;)I"
+ );
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ S3JniExceptionClear;
+ rc = s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ "Cannot not find matching call() on "
+ "TracerCallback object.");
+ }else{
+ hook.jObj = S3JniRefGlobal(jTracer);
+ S3JniDb_mutex_enter;
+ rc = sqlite3_trace_v2(ps->pDb, (unsigned)traceMask, s3jni_trace_impl, ps);
+ if( 0==rc ){
+ S3JniHook_unref(&ps->hooks.trace);
+ ps->hooks.trace = hook /* transfer ownership of reference */;
+ }else{
+ S3JniHook_unref(&hook);
+ }
+ S3JniDb_mutex_leave;
+ }
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_txn_state(),jint,1txn_1state)(
+ JniArgsEnvClass,jobject jDb, jstring jSchema
+){
+ sqlite3 * const pDb = PtrGet_sqlite3(jDb);
+ int rc = SQLITE_MISUSE;
+ if( pDb ){
+ char * zSchema = jSchema
+ ? s3jni_jstring_to_utf8(jSchema, 0)
+ : 0;
+ if( !jSchema || (zSchema && jSchema) ){
+ rc = sqlite3_txn_state(pDb, zSchema);
+ sqlite3_free(zSchema);
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_update_hook(),jobject,1update_1hook)(
+ JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+ return s3jni_updatepre_hook(env, 0, jpDb, jHook);
+}
+
+
+S3JniApi(sqlite3_value_blob(),jbyteArray,1value_1blob)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
+ const jbyte * pBytes = sv ? sqlite3_value_blob(sv) : 0;
+ int const nLen = pBytes ? sqlite3_value_bytes(sv) : 0;
+
+ s3jni_oom_check( nLen ? !!pBytes : 1 );
+ return pBytes
+ ? s3jni_new_jbyteArray(pBytes, nLen)
+ : NULL;
+}
+
+S3JniApi(sqlite3_value_bytes(),jint,1value_1bytes)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
+ return sv ? sqlite3_value_bytes(sv) : 0;
+}
+
+S3JniApi(sqlite3_value_bytes16(),jint,1value_1bytes16)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
+ return sv ? sqlite3_value_bytes16(sv) : 0;
+}
+
+
+S3JniApi(sqlite3_value_double(),jdouble,1value_1double)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
+ return (jdouble) (sv ? sqlite3_value_double(sv) : 0.0);
+}
+
+
+S3JniApi(sqlite3_value_dup(),jobject,1value_1dup)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
+ sqlite3_value * const sd = sv ? sqlite3_value_dup(sv) : 0;
+ jobject rv = sd ? new_java_sqlite3_value(env, sd) : 0;
+ if( sd && !rv ) {
+ /* OOM */
+ sqlite3_value_free(sd);
+ }
+ return rv;
+}
+
+S3JniApi(sqlite3_value_free(),void,1value_1free)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
+ if( sv ){
+ sqlite3_value_free(sv);
+ }
+}
+
+S3JniApi(sqlite3_value_int(),jint,1value_1int)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
+ return (jint) (sv ? sqlite3_value_int(sv) : 0);
+}
+
+S3JniApi(sqlite3_value_int64(),jlong,1value_1int64)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
+ return (jlong) (sv ? sqlite3_value_int64(sv) : 0LL);
+}
+
+S3JniApi(sqlite3_value_java_object(),jobject,1value_1java_1object)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
+ return sv
+ ? sqlite3_value_pointer(sv, s3jni__value_jref_key)
+ : 0;
+}
+
+S3JniApi(sqlite3_value_nio_buffer(),jobject,1value_1nio_1buffer)(
+ JniArgsEnvClass, jobject jVal
+){
+ sqlite3_value * const sv = PtrGet_sqlite3_value(jVal);
+ jobject rv = 0;
+ if( sv ){
+ const void * const p = sqlite3_value_blob(sv);
+ if( p ){
+ const int n = sqlite3_value_bytes(sv);
+ rv = s3jni__blob_to_ByteBuffer(env, p, n);
+ }
+ }
+ return rv;
+}
+
+S3JniApi(sqlite3_value_text(),jbyteArray,1value_1text)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
+ const unsigned char * const p = sv ? sqlite3_value_text(sv) : 0;
+ int const n = p ? sqlite3_value_bytes(sv) : 0;
+ return p ? s3jni_new_jbyteArray(p, n) : 0;
+}
+
+#if 0
+// this impl might prove useful.
+S3JniApi(sqlite3_value_text(),jstring,1value_1text)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
+ const unsigned char * const p = sv ? sqlite3_value_text(sv) : 0;
+ int const n = p ? sqlite3_value_bytes(sv) : 0;
+ return p ? s3jni_utf8_to_jstring( (const char *)p, n) : 0;
+}
+#endif
+
+S3JniApi(sqlite3_value_text16(),jstring,1value_1text16)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal);
+ const int n = sv ? sqlite3_value_bytes16(sv) : 0;
+ const void * const p = sv ? sqlite3_value_text16(sv) : 0;
+ return p ? s3jni_text16_to_jstring(env, p, n) : 0;
+}
+
+JniDecl(void,1jni_1internal_1details)(JniArgsEnvClass){
+ MARKER(("\nVarious bits of internal info:\n"));
+ puts("FTS5 is "
+#ifdef SQLITE_ENABLE_FTS5
+ "available"
+#else
+ "unavailable"
+#endif
+ "."
+ );
+ puts("sizeofs:");
+#define SO(T) printf("\tsizeof(" #T ") = %u\n", (unsigned)sizeof(T))
+ SO(void*);
+ SO(jmethodID);
+ SO(jfieldID);
+ SO(S3JniEnv);
+ SO(S3JniHook);
+ SO(S3JniDb);
+ SO(S3JniNphOps);
+ printf("\t(^^^ %u NativePointerHolder/OutputPointer.T types)\n",
+ (unsigned)S3Jni_NphCache_size);
+ SO(S3JniGlobal);
+ SO(S3JniGlobal.nph);
+ SO(S3JniGlobal.metrics);
+ SO(S3JniAutoExtension);
+ SO(S3JniUdf);
+#undef SO
+#ifdef SQLITE_JNI_ENABLE_METRICS
+ printf("Cache info:\n");
+ printf("\tJNIEnv cache: %u allocs, %u misses, %u hits\n",
+ SJG.metrics.nEnvAlloc, SJG.metrics.nEnvMiss,
+ SJG.metrics.nEnvHit);
+ printf("Mutex entry:"
+ "\n\tglobal = %u"
+ "\n\tenv = %u"
+ "\n\tnph = %u for S3JniNphOp init"
+ "\n\thook = %u"
+ "\n\tperDb = %u"
+ "\n\tautoExt list = %u"
+ "\n\tS3JniUdf = %u (free-list)"
+ "\n\tmetrics = %u\n",
+ SJG.metrics.nMutexGlobal, SJG.metrics.nMutexEnv,
+ SJG.metrics.nMutexNph, SJG.metrics.nMutexHook,
+ SJG.metrics.nMutexPerDb, SJG.metrics.nMutexAutoExt,
+ SJG.metrics.nMutexUdf, SJG.metrics.nMetrics);
+ puts("Allocs:");
+ printf("\tS3JniDb: %u alloced (*%u = %u bytes), %u recycled\n",
+ SJG.metrics.nPdbAlloc, (unsigned) sizeof(S3JniDb),
+ (unsigned)(SJG.metrics.nPdbAlloc * sizeof(S3JniDb)),
+ SJG.metrics.nPdbRecycled);
+ printf("\tS3JniUdf: %u alloced (*%u = %u bytes), %u recycled\n",
+ SJG.metrics.nUdfAlloc, (unsigned) sizeof(S3JniUdf),
+ (unsigned)(SJG.metrics.nUdfAlloc * sizeof(S3JniUdf)),
+ SJG.metrics.nUdfRecycled);
+ printf("\tS3JniHook: %u alloced (*%u = %u bytes), %u recycled\n",
+ SJG.metrics.nHookAlloc, (unsigned) sizeof(S3JniHook),
+ (unsigned)(SJG.metrics.nHookAlloc * sizeof(S3JniHook)),
+ SJG.metrics.nHookRecycled);
+ printf("\tS3JniEnv: %u alloced (*%u = %u bytes)\n",
+ SJG.metrics.nEnvAlloc, (unsigned) sizeof(S3JniEnv),
+ (unsigned)(SJG.metrics.nEnvAlloc * sizeof(S3JniEnv)));
+ puts("Java-side UDF calls:");
+#define UDF(T) printf("\t%-8s = %u\n", "x" #T, SJG.metrics.udf.n##T)
+ UDF(Func); UDF(Step); UDF(Final); UDF(Value); UDF(Inverse);
+#undef UDF
+ printf("xDestroy calls across all callback types: %u\n",
+ SJG.metrics.nDestroy);
+#else
+ puts("Built without SQLITE_JNI_ENABLE_METRICS.");
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////
+// End of the sqlite3_... API bindings. Next up, FTS5...
+////////////////////////////////////////////////////////////////////////
+#ifdef SQLITE_ENABLE_FTS5
+
+/* Creates a verbose JNI Fts5 function name. */
+#define JniFuncNameFtsXA(Suffix) \
+ Java_org_sqlite_jni_fts5_Fts5ExtensionApi_ ## Suffix
+#define JniFuncNameFtsApi(Suffix) \
+ Java_org_sqlite_jni_fts5_fts5_1api_ ## Suffix
+#define JniFuncNameFtsTok(Suffix) \
+ Java_org_sqlite_jni_fts5_fts5_tokenizer_ ## Suffix
+
+#define JniDeclFtsXA(ReturnType,Suffix) \
+ JNIEXPORT ReturnType JNICALL \
+ JniFuncNameFtsXA(Suffix)
+#define JniDeclFtsApi(ReturnType,Suffix) \
+ JNIEXPORT ReturnType JNICALL \
+ JniFuncNameFtsApi(Suffix)
+#define JniDeclFtsTok(ReturnType,Suffix) \
+ JNIEXPORT ReturnType JNICALL \
+ JniFuncNameFtsTok(Suffix)
+
+#define PtrGet_fts5_api(OBJ) NativePointerHolder_get(OBJ,S3JniNph(fts5_api))
+#define PtrGet_fts5_tokenizer(OBJ) NativePointerHolder_get(OBJ,S3JniNph(fts5_tokenizer))
+#define PtrGet_Fts5Context(OBJ) NativePointerHolder_get(OBJ,S3JniNph(Fts5Context))
+#define PtrGet_Fts5Tokenizer(OBJ) NativePointerHolder_get(OBJ,S3JniNph(Fts5Tokenizer))
+#define s3jni_ftsext() &sFts5Api/*singleton from sqlite3.c*/
+#define Fts5ExtDecl Fts5ExtensionApi const * const ext = s3jni_ftsext()
+
+/**
+ State for binding Java-side FTS5 auxiliary functions.
+*/
+typedef struct {
+ jobject jObj /* functor instance */;
+ jobject jUserData /* 2nd arg to JNI binding of
+ xCreateFunction(), ostensibly the 3rd arg
+ to the lib-level xCreateFunction(), except
+ that we necessarily use that slot for a
+ Fts5JniAux instance. */;
+ char * zFuncName /* Only for error reporting and debug logging */;
+ jmethodID jmid /* callback member's method ID */;
+} Fts5JniAux;
+
+static void Fts5JniAux_free(Fts5JniAux * const s){
+ S3JniDeclLocal_env;
+ if( env ){
+ /*MARKER(("FTS5 aux function cleanup: %s\n", s->zFuncName));*/
+ s3jni_call_xDestroy(s->jObj);
+ S3JniUnrefGlobal(s->jObj);
+ S3JniUnrefGlobal(s->jUserData);
+ }
+ sqlite3_free(s->zFuncName);
+ sqlite3_free(s);
+}
+
+static void Fts5JniAux_xDestroy(void *p){
+ if( p ) Fts5JniAux_free(p);
+}
+
+static Fts5JniAux * Fts5JniAux_alloc(JNIEnv * const env, jobject jObj){
+ Fts5JniAux * s = s3jni_malloc( sizeof(Fts5JniAux));
+
+ if( s ){
+ jclass klazz;
+ memset(s, 0, sizeof(Fts5JniAux));
+ s->jObj = S3JniRefGlobal(jObj);
+ klazz = (*env)->GetObjectClass(env, jObj);
+ s->jmid = (*env)->GetMethodID(env, klazz, "call",
+ "(Lorg/sqlite/jni/fts5/Fts5ExtensionApi;"
+ "Lorg/sqlite/jni/fts5/Fts5Context;"
+ "Lorg/sqlite/jni/capi/sqlite3_context;"
+ "[Lorg/sqlite/jni/capi/sqlite3_value;)V");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew{
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ Fts5JniAux_free(s);
+ s = 0;
+ }
+ }
+ return s;
+}
+
+static inline jobject new_java_Fts5Context(JNIEnv * const env, Fts5Context *sv){
+ return NativePointerHolder_new(env, S3JniNph(Fts5Context), sv);
+}
+static inline jobject new_java_fts5_api(JNIEnv * const env, fts5_api *sv){
+ return NativePointerHolder_new(env, S3JniNph(fts5_api), sv);
+}
+
+/*
+** Returns a per-JNIEnv global ref to the Fts5ExtensionApi singleton
+** instance, or NULL on OOM.
+*/
+static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){
+ if( !SJG.fts5.jExt ){
+ S3JniGlobal_mutex_enter;
+ if( !SJG.fts5.jExt ){
+ jobject const pNPH = NativePointerHolder_new(
+ env, S3JniNph(Fts5ExtensionApi), s3jni_ftsext()
+ );
+ if( pNPH ){
+ SJG.fts5.jExt = S3JniRefGlobal(pNPH);
+ S3JniUnrefLocal(pNPH);
+ }
+ }
+ S3JniGlobal_mutex_leave;
+ }
+ return SJG.fts5.jExt;
+}
+
+/*
+** Returns a pointer to the fts5_api instance for database connection
+** db. If an error occurs, returns NULL and leaves an error in the
+** database handle (accessible using sqlite3_errcode()/errmsg()).
+*/
+static fts5_api *s3jni_fts5_api_from_db(sqlite3 *db){
+ fts5_api *pRet = 0;
+ sqlite3_stmt *pStmt = 0;
+ if( SQLITE_OK==sqlite3_prepare(db, "SELECT fts5(?1)", -1, &pStmt, 0) ){
+ sqlite3_bind_pointer(pStmt, 1, (void*)&pRet, "fts5_api_ptr", NULL);
+ sqlite3_step(pStmt);
+ }
+ sqlite3_finalize(pStmt);
+ return pRet;
+}
+
+JniDeclFtsApi(jobject,getInstanceForDb)(JniArgsEnvClass,jobject jDb){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+#if 0
+ jobject rv = 0;
+ if( !ps ) return 0;
+ else if( ps->fts.jApi ){
+ rv = ps->fts.jApi;
+ }else{
+ fts5_api * const pApi = s3jni_fts5_api_from_db(ps->pDb);
+ if( pApi ){
+ rv = new_java_fts5_api(env, pApi);
+ ps->fts.jApi = rv ? S3JniRefGlobal(rv) : 0;
+ }
+ }
+ return rv;
+#else
+ if( ps && !ps->fts.jApi ){
+ S3JniDb_mutex_enter;
+ if( !ps->fts.jApi ){
+ fts5_api * const pApi = s3jni_fts5_api_from_db(ps->pDb);
+ if( pApi ){
+ jobject const rv = new_java_fts5_api(env, pApi);
+ ps->fts.jApi = rv ? S3JniRefGlobal(rv) : 0;
+ }
+ }
+ S3JniDb_mutex_leave;
+ }
+ return ps ? ps->fts.jApi : 0;
+#endif
+}
+
+
+JniDeclFtsXA(jobject,getInstance)(JniArgsEnvClass){
+ return s3jni_getFts5ExensionApi(env);
+}
+
+JniDeclFtsXA(jint,xColumnCount)(JniArgsEnvObj,jobject jCtx){
+ Fts5ExtDecl;
+ return (jint)ext->xColumnCount(PtrGet_Fts5Context(jCtx));
+}
+
+JniDeclFtsXA(jint,xColumnSize)(JniArgsEnvObj,jobject jCtx, jint iIdx, jobject jOut32){
+ Fts5ExtDecl;
+ int n1 = 0;
+ int const rc = ext->xColumnSize(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1);
+ if( 0==rc ) OutputPointer_set_Int32(env, jOut32, n1);
+ return rc;
+}
+
+JniDeclFtsXA(jint,xColumnText)(JniArgsEnvObj,jobject jCtx, jint iCol,
+ jobject jOut){
+ Fts5ExtDecl;
+ const char *pz = 0;
+ int pn = 0;
+ int rc = ext->xColumnText(PtrGet_Fts5Context(jCtx), (int)iCol,
+ &pz, &pn);
+ if( 0==rc ){
+ jstring jstr = pz ? s3jni_utf8_to_jstring( pz, pn) : 0;
+ if( pz ){
+ if( jstr ){
+ OutputPointer_set_String(env, jOut, jstr);
+ S3JniUnrefLocal(jstr)/*jOut has a reference*/;
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ }
+ }
+ return (jint)rc;
+}
+
+JniDeclFtsXA(jint,xColumnTotalSize)(JniArgsEnvObj,jobject jCtx, jint iCol, jobject jOut64){
+ Fts5ExtDecl;
+ sqlite3_int64 nOut = 0;
+ int const rc = ext->xColumnTotalSize(PtrGet_Fts5Context(jCtx), (int)iCol, &nOut);
+ if( 0==rc && jOut64 ) OutputPointer_set_Int64(env, jOut64, (jlong)nOut);
+ return (jint)rc;
+}
+
+/*
+** Proxy for fts5_extension_function instances plugged in via
+** fts5_api::xCreateFunction().
+*/
+static void s3jni_fts5_extension_function(Fts5ExtensionApi const *pApi,
+ Fts5Context *pFts,
+ sqlite3_context *pCx,
+ int argc,
+ sqlite3_value **argv){
+ Fts5JniAux * const pAux = pApi->xUserData(pFts);
+ jobject jpCx = 0;
+ jobjectArray jArgv = 0;
+ jobject jpFts = 0;
+ jobject jFXA;
+ int rc;
+ S3JniDeclLocal_env;
+
+ assert(pAux);
+ jFXA = s3jni_getFts5ExensionApi(env);
+ if( !jFXA ) goto error_oom;
+ jpFts = new_java_Fts5Context(env, pFts);
+ if( !jpFts ) goto error_oom;
+ rc = udf_args(env, pCx, argc, argv, &jpCx, &jArgv);
+ if( rc ) goto error_oom;
+ (*env)->CallVoidMethod(env, pAux->jObj, pAux->jmid,
+ jFXA, jpFts, jpCx, jArgv);
+ S3JniIfThrew{
+ udf_report_exception(env, 1, pCx, pAux->zFuncName, "call");
+ }
+ udf_unargs(env, jpCx, argc, jArgv);
+ S3JniUnrefLocal(jpFts);
+ S3JniUnrefLocal(jpCx);
+ S3JniUnrefLocal(jArgv);
+ return;
+error_oom:
+ s3jni_db_oom( sqlite3_context_db_handle(pCx) );
+ assert( !jArgv );
+ assert( !jpCx );
+ S3JniUnrefLocal(jpFts);
+ sqlite3_result_error_nomem(pCx);
+ return;
+}
+
+JniDeclFtsApi(jint,xCreateFunction)(JniArgsEnvObj, jstring jName,
+ jobject jUserData, jobject jFunc){
+ fts5_api * const pApi = PtrGet_fts5_api(jSelf);
+ int rc;
+ char * zName;
+ Fts5JniAux * pAux;
+
+ assert(pApi);
+ zName = s3jni_jstring_to_utf8( jName, 0);
+ if(!zName) return SQLITE_NOMEM;
+ pAux = Fts5JniAux_alloc(env, jFunc);
+ if( pAux ){
+ rc = pApi->xCreateFunction(pApi, zName, pAux,
+ s3jni_fts5_extension_function,
+ Fts5JniAux_xDestroy);
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ if( 0==rc ){
+ pAux->jUserData = jUserData ? S3JniRefGlobal(jUserData) : 0;
+ pAux->zFuncName = zName;
+ }else{
+ sqlite3_free(zName);
+ }
+ return (jint)rc;
+}
+
+
+typedef struct S3JniFts5AuxData S3JniFts5AuxData;
+/*
+** TODO: this middle-man struct is no longer necessary. Conider
+** removing it and passing around jObj itself instead.
+*/
+struct S3JniFts5AuxData {
+ jobject jObj;
+};
+
+static void S3JniFts5AuxData_xDestroy(void *x){
+ if( x ){
+ S3JniFts5AuxData * const p = x;
+ if( p->jObj ){
+ S3JniDeclLocal_env;
+ s3jni_call_xDestroy(p->jObj);
+ S3JniUnrefGlobal(p->jObj);
+ }
+ sqlite3_free(x);
+ }
+}
+
+JniDeclFtsXA(jobject,xGetAuxdata)(JniArgsEnvObj,jobject jCtx, jboolean bClear){
+ Fts5ExtDecl;
+ jobject rv = 0;
+ S3JniFts5AuxData * const pAux = ext->xGetAuxdata(PtrGet_Fts5Context(jCtx), bClear);
+ if( pAux ){
+ if( bClear ){
+ if( pAux->jObj ){
+ rv = S3JniRefLocal(pAux->jObj);
+ S3JniUnrefGlobal(pAux->jObj);
+ }
+ /* Note that we do not call xDestroy() in this case. */
+ sqlite3_free(pAux);
+ }else{
+ rv = pAux->jObj;
+ }
+ }
+ return rv;
+}
+
+JniDeclFtsXA(jint,xInst)(JniArgsEnvObj,jobject jCtx, jint iIdx, jobject jOutPhrase,
+ jobject jOutCol, jobject jOutOff){
+ Fts5ExtDecl;
+ int n1 = 0, n2 = 2, n3 = 0;
+ int const rc = ext->xInst(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1, &n2, &n3);
+ if( 0==rc ){
+ OutputPointer_set_Int32(env, jOutPhrase, n1);
+ OutputPointer_set_Int32(env, jOutCol, n2);
+ OutputPointer_set_Int32(env, jOutOff, n3);
+ }
+ return rc;
+}
+
+JniDeclFtsXA(jint,xInstCount)(JniArgsEnvObj,jobject jCtx, jobject jOut32){
+ Fts5ExtDecl;
+ int nOut = 0;
+ int const rc = ext->xInstCount(PtrGet_Fts5Context(jCtx), &nOut);
+ if( 0==rc && jOut32 ) OutputPointer_set_Int32(env, jOut32, nOut);
+ return (jint)rc;
+}
+
+JniDeclFtsXA(jint,xPhraseCount)(JniArgsEnvObj,jobject jCtx){
+ Fts5ExtDecl;
+ return (jint)ext->xPhraseCount(PtrGet_Fts5Context(jCtx));
+}
+
+/* Copy the 'a' and 'b' fields from pSrc to Fts5PhraseIter object jIter. */
+static void s3jni_phraseIter_NToJ(JNIEnv *const env,
+ Fts5PhraseIter const * const pSrc,
+ jobject jIter){
+ S3JniGlobalType * const g = &S3JniGlobal;
+ assert(g->fts5.jPhraseIter.fidA);
+ (*env)->SetLongField(env, jIter, g->fts5.jPhraseIter.fidA,
+ S3JniCast_P2L(pSrc->a));
+ S3JniExceptionIsFatal("Cannot set Fts5PhraseIter.a field.");
+ (*env)->SetLongField(env, jIter, g->fts5.jPhraseIter.fidB,
+ S3JniCast_P2L(pSrc->b));
+ S3JniExceptionIsFatal("Cannot set Fts5PhraseIter.b field.");
+}
+
+/* Copy the 'a' and 'b' fields from Fts5PhraseIter object jIter to pDest. */
+static void s3jni_phraseIter_JToN(JNIEnv *const env, jobject jIter,
+ Fts5PhraseIter * const pDest){
+ S3JniGlobalType * const g = &S3JniGlobal;
+ assert(g->fts5.jPhraseIter.fidA);
+ pDest->a = S3JniCast_L2P(
+ (*env)->GetLongField(env, jIter, g->fts5.jPhraseIter.fidA)
+ );
+ S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.a field.");
+ pDest->b = S3JniCast_L2P(
+ (*env)->GetLongField(env, jIter, g->fts5.jPhraseIter.fidB)
+ );
+ S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.b field.");
+}
+
+JniDeclFtsXA(jint,xPhraseFirst)(JniArgsEnvObj,jobject jCtx, jint iPhrase,
+ jobject jIter, jobject jOutCol,
+ jobject jOutOff){
+ Fts5ExtDecl;
+ Fts5PhraseIter iter;
+ int rc, iCol = 0, iOff = 0;
+ rc = ext->xPhraseFirst(PtrGet_Fts5Context(jCtx), (int)iPhrase,
+ &iter, &iCol, &iOff);
+ if( 0==rc ){
+ OutputPointer_set_Int32(env, jOutCol, iCol);
+ OutputPointer_set_Int32(env, jOutOff, iOff);
+ s3jni_phraseIter_NToJ(env, &iter, jIter);
+ }
+ return rc;
+}
+
+JniDeclFtsXA(jint,xPhraseFirstColumn)(JniArgsEnvObj,jobject jCtx, jint iPhrase,
+ jobject jIter, jobject jOutCol){
+ Fts5ExtDecl;
+ Fts5PhraseIter iter;
+ int rc, iCol = 0;
+ rc = ext->xPhraseFirstColumn(PtrGet_Fts5Context(jCtx), (int)iPhrase,
+ &iter, &iCol);
+ if( 0==rc ){
+ OutputPointer_set_Int32(env, jOutCol, iCol);
+ s3jni_phraseIter_NToJ(env, &iter, jIter);
+ }
+ return rc;
+}
+
+JniDeclFtsXA(void,xPhraseNext)(JniArgsEnvObj,jobject jCtx, jobject jIter,
+ jobject jOutCol, jobject jOutOff){
+ Fts5ExtDecl;
+ Fts5PhraseIter iter;
+ int iCol = 0, iOff = 0;
+ s3jni_phraseIter_JToN(env, jIter, &iter);
+ ext->xPhraseNext(PtrGet_Fts5Context(jCtx), &iter, &iCol, &iOff);
+ OutputPointer_set_Int32(env, jOutCol, iCol);
+ OutputPointer_set_Int32(env, jOutOff, iOff);
+ s3jni_phraseIter_NToJ(env, &iter, jIter);
+}
+
+JniDeclFtsXA(void,xPhraseNextColumn)(JniArgsEnvObj,jobject jCtx, jobject jIter,
+ jobject jOutCol){
+ Fts5ExtDecl;
+ Fts5PhraseIter iter;
+ int iCol = 0;
+ s3jni_phraseIter_JToN(env, jIter, &iter);
+ ext->xPhraseNextColumn(PtrGet_Fts5Context(jCtx), &iter, &iCol);
+ OutputPointer_set_Int32(env, jOutCol, iCol);
+ s3jni_phraseIter_NToJ(env, &iter, jIter);
+}
+
+
+JniDeclFtsXA(jint,xPhraseSize)(JniArgsEnvObj,jobject jCtx, jint iPhrase){
+ Fts5ExtDecl;
+ return (jint)ext->xPhraseSize(PtrGet_Fts5Context(jCtx), (int)iPhrase);
+}
+
+/* State for use with xQueryPhrase() and xTokenize(). */
+struct s3jni_xQueryPhraseState {
+ Fts5ExtensionApi const * ext;
+ jmethodID midCallback; /* jCallback->call() method */
+ jobject jCallback; /* Fts5ExtensionApi.XQueryPhraseCallback instance */
+ jobject jFcx; /* (Fts5Context*) for xQueryPhrase()
+ callback. This is NOT the instance that is
+ passed to xQueryPhrase(), it's the one
+ created by xQueryPhrase() for use by its
+ callback. */
+ /* State for xTokenize() */
+ struct {
+ const char * zPrev;
+ int nPrev;
+ jbyteArray jba;
+ } tok;
+};
+
+static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi,
+ Fts5Context * pFcx, void *pData){
+ struct s3jni_xQueryPhraseState * const s = pData;
+ S3JniDeclLocal_env;
+
+ if( !s->jFcx ){
+ s->jFcx = new_java_Fts5Context(env, pFcx);
+ if( !s->jFcx ) return SQLITE_NOMEM;
+ }
+ int rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback,
+ SJG.fts5.jExt, s->jFcx);
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("xQueryPhrase() callback");
+ S3JniExceptionClear;
+ rc = SQLITE_ERROR;
+ }
+ return rc;
+}
+
+JniDeclFtsXA(jint,xQueryPhrase)(JniArgsEnvObj,jobject jFcx, jint iPhrase,
+ jobject jCallback){
+ Fts5ExtDecl;
+ int rc;
+ struct s3jni_xQueryPhraseState s;
+ jclass klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL;
+
+ if( !klazz ) return SQLITE_MISUSE;
+ s.jCallback = jCallback;
+ s.jFcx = 0;
+ s.ext = ext;
+ s.midCallback = (*env)->GetMethodID(env, klazz, "call",
+ "(Lorg/sqlite/jni/fts5/Fts5ExtensionApi;"
+ "Lorg/sqlite/jni/fts5/Fts5Context;)I");
+ S3JniUnrefLocal(klazz);
+ S3JniExceptionIsFatal("Could not extract xQueryPhraseCallback.call() method.");
+ rc = ext->xQueryPhrase(PtrGet_Fts5Context(jFcx), iPhrase, &s,
+ s3jni_xQueryPhrase);
+ S3JniUnrefLocal(s.jFcx);
+ return (jint)rc;
+}
+
+
+JniDeclFtsXA(jint,xRowCount)(JniArgsEnvObj,jobject jCtx, jobject jOut64){
+ Fts5ExtDecl;
+ sqlite3_int64 nOut = 0;
+ int const rc = ext->xRowCount(PtrGet_Fts5Context(jCtx), &nOut);
+ if( 0==rc && jOut64 ) OutputPointer_set_Int64(env, jOut64, (jlong)nOut);
+ return (jint)rc;
+}
+
+JniDeclFtsXA(jlong,xRowid)(JniArgsEnvObj,jobject jCtx){
+ Fts5ExtDecl;
+ return (jlong)ext->xRowid(PtrGet_Fts5Context(jCtx));
+}
+
+JniDeclFtsXA(jint,xSetAuxdata)(JniArgsEnvObj,jobject jCtx, jobject jAux){
+ Fts5ExtDecl;
+ int rc;
+ S3JniFts5AuxData * pAux;
+
+ pAux = s3jni_malloc( sizeof(*pAux));
+ if( !pAux ){
+ if( jAux ){
+ /* Emulate how xSetAuxdata() behaves when it cannot alloc
+ ** its auxdata wrapper. */
+ s3jni_call_xDestroy(jAux);
+ }
+ return SQLITE_NOMEM;
+ }
+ pAux->jObj = S3JniRefGlobal(jAux);
+ rc = ext->xSetAuxdata(PtrGet_Fts5Context(jCtx), pAux,
+ S3JniFts5AuxData_xDestroy);
+ return rc;
+}
+
+/* xToken() impl for xTokenize(). */
+static int s3jni_xTokenize_xToken(void *p, int tFlags, const char* z,
+ int nZ, int iStart, int iEnd){
+ int rc;
+ S3JniDeclLocal_env;
+ struct s3jni_xQueryPhraseState * const s = p;
+ jbyteArray jba;
+
+ S3JniUnrefLocal(s->tok.jba);
+ s->tok.zPrev = z;
+ s->tok.nPrev = nZ;
+ s->tok.jba = s3jni_new_jbyteArray(z, nZ);
+ if( !s->tok.jba ) return SQLITE_NOMEM;
+ jba = s->tok.jba;
+ rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback,
+ (jint)tFlags, jba, (jint)iStart,
+ (jint)iEnd);
+ S3JniIfThrew {
+ S3JniExceptionWarnCallbackThrew("xTokenize() callback");
+ rc = SQLITE_ERROR;
+ }
+ return rc;
+}
+
+/*
+** Proxy for Fts5ExtensionApi.xTokenize() and
+** fts5_tokenizer.xTokenize()
+*/
+static jint s3jni_fts5_xTokenize(JniArgsEnvObj, S3JniNphOp const *pRef,
+ jint tokFlags, jobject jFcx,
+ jbyteArray jbaText, jobject jCallback){
+ Fts5ExtDecl;
+ struct s3jni_xQueryPhraseState s;
+ int rc = 0;
+ jbyte * const pText = jCallback ? s3jni_jbyteArray_bytes(jbaText) : 0;
+ jsize nText = pText ? (*env)->GetArrayLength(env, jbaText) : 0;
+ jclass const klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL;
+
+ if( !klazz ) return SQLITE_MISUSE;
+ memset(&s, 0, sizeof(s));
+ s.jCallback = jCallback;
+ s.jFcx = jFcx;
+ s.ext = ext;
+ s.midCallback = (*env)->GetMethodID(env, klazz, "call", "(I[BII)I");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ s3jni_jbyteArray_release(jbaText, pText);
+ return SQLITE_ERROR;
+ }
+ s.tok.jba = S3JniRefLocal(jbaText);
+ s.tok.zPrev = (const char *)pText;
+ s.tok.nPrev = (int)nText;
+ if( pRef == S3JniNph(Fts5ExtensionApi) ){
+ rc = ext->xTokenize(PtrGet_Fts5Context(jFcx),
+ (const char *)pText, (int)nText,
+ &s, s3jni_xTokenize_xToken);
+ }else if( pRef == S3JniNph(fts5_tokenizer) ){
+ fts5_tokenizer * const pTok = PtrGet_fts5_tokenizer(jSelf);
+ rc = pTok->xTokenize(PtrGet_Fts5Tokenizer(jFcx), &s, tokFlags,
+ (const char *)pText, (int)nText,
+ s3jni_xTokenize_xToken);
+ }else{
+ (*env)->FatalError(env, "This cannot happen. Maintenance required.");
+ }
+ if( s.tok.jba ){
+ assert( s.tok.zPrev );
+ S3JniUnrefLocal(s.tok.jba);
+ }
+ s3jni_jbyteArray_release(jbaText, pText);
+ return (jint)rc;
+}
+
+JniDeclFtsXA(jint,xTokenize)(JniArgsEnvObj,jobject jFcx, jbyteArray jbaText,
+ jobject jCallback){
+ return s3jni_fts5_xTokenize(env, jSelf, S3JniNph(Fts5ExtensionApi),
+ 0, jFcx, jbaText, jCallback);
+}
+
+JniDeclFtsTok(jint,xTokenize)(JniArgsEnvObj,jobject jFcx, jint tokFlags,
+ jbyteArray jbaText, jobject jCallback){
+ return s3jni_fts5_xTokenize(env, jSelf, S3JniNph(Fts5Tokenizer),
+ tokFlags, jFcx, jbaText, jCallback);
+}
+
+
+JniDeclFtsXA(jobject,xUserData)(JniArgsEnvObj,jobject jFcx){
+ Fts5ExtDecl;
+ Fts5JniAux * const pAux = ext->xUserData(PtrGet_Fts5Context(jFcx));
+ return pAux ? pAux->jUserData : 0;
+}
+
+#endif /* SQLITE_ENABLE_FTS5 */
+
+////////////////////////////////////////////////////////////////////////
+// End of the main API bindings. Start of SQLTester bits...
+////////////////////////////////////////////////////////////////////////
+
+#ifdef SQLITE_JNI_ENABLE_SQLTester
+typedef struct SQLTesterJni SQLTesterJni;
+struct SQLTesterJni {
+ sqlite3_int64 nDup;
+};
+static SQLTesterJni SQLTester = {
+ 0
+};
+
+static void SQLTester_dup_destructor(void*pToFree){
+ u64 *p = (u64*)pToFree;
+ assert( p!=0 );
+ p--;
+ assert( p[0]==0x2bbf4b7c );
+ p[0] = 0;
+ p[1] = 0;
+ sqlite3_free(p);
+}
+
+/*
+** Implementation of
+**
+** dup(TEXT)
+**
+** This SQL function simply makes a copy of its text argument. But it
+** returns the result using a custom destructor, in order to provide
+** tests for the use of Mem.xDel() in the SQLite VDBE.
+*/
+static void SQLTester_dup_func(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ u64 *pOut;
+ char *z;
+ int n = sqlite3_value_bytes(argv[0]);
+ SQLTesterJni * const p = (SQLTesterJni *)sqlite3_user_data(context);
+ S3JniDeclLocal_env;
+
+ ++p->nDup;
+ if( n>0 && (pOut = s3jni_malloc( (n+16)&~7 ))!=0 ){
+ pOut[0] = 0x2bbf4b7c;
+ z = (char*)&pOut[1];
+ memcpy(z, sqlite3_value_text(argv[0]), n);
+ z[n] = 0;
+ sqlite3_result_text(context, z, n, SQLTester_dup_destructor);
+ }
+ return;
+}
+
+/*
+** Return the number of calls to the dup() SQL function since the
+** SQLTester context was opened or since the last dup_count() call.
+*/
+static void SQLTester_dup_count_func(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ SQLTesterJni * const p = (SQLTesterJni *)sqlite3_user_data(context);
+ sqlite3_result_int64(context, p->nDup);
+ p->nDup = 0;
+}
+
+/*
+** Return non-zero if string z matches glob pattern zGlob and zero if the
+** pattern does not match.
+**
+** To repeat:
+**
+** zero == no match
+** non-zero == match
+**
+** Globbing rules:
+**
+** '*' Matches any sequence of zero or more characters.
+**
+** '?' Matches exactly one character.
+**
+** [...] Matches one character from the enclosed list of
+** characters.
+**
+** [^...] Matches one character not in the enclosed list.
+**
+** '#' Matches any sequence of one or more digits with an
+** optional + or - sign in front, or a hexadecimal
+** literal of the form 0x...
+*/
+static int SQLTester_strnotglob(const char *zGlob, const char *z){
+ int c, c2;
+ int invert;
+ int seen;
+
+ while( (c = (*(zGlob++)))!=0 ){
+ if( c=='*' ){
+ while( (c=(*(zGlob++))) == '*' || c=='?' ){
+ if( c=='?' && (*(z++))==0 ) return 0;
+ }
+ if( c==0 ){
+ return 1;
+ }else if( c=='[' ){
+ while( *z && SQLTester_strnotglob(zGlob-1,z)==0 ){
+ z++;
+ }
+ return (*z)!=0;
+ }
+ while( (c2 = (*(z++)))!=0 ){
+ while( c2!=c ){
+ c2 = *(z++);
+ if( c2==0 ) return 0;
+ }
+ if( SQLTester_strnotglob(zGlob,z) ) return 1;
+ }
+ return 0;
+ }else if( c=='?' ){
+ if( (*(z++))==0 ) return 0;
+ }else if( c=='[' ){
+ int prior_c = 0;
+ seen = 0;
+ invert = 0;
+ c = *(z++);
+ if( c==0 ) return 0;
+ c2 = *(zGlob++);
+ if( c2=='^' ){
+ invert = 1;
+ c2 = *(zGlob++);
+ }
+ if( c2==']' ){
+ if( c==']' ) seen = 1;
+ c2 = *(zGlob++);
+ }
+ while( c2 && c2!=']' ){
+ if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
+ c2 = *(zGlob++);
+ if( c>=prior_c && c<=c2 ) seen = 1;
+ prior_c = 0;
+ }else{
+ if( c==c2 ){
+ seen = 1;
+ }
+ prior_c = c2;
+ }
+ c2 = *(zGlob++);
+ }
+ if( c2==0 || (seen ^ invert)==0 ) return 0;
+ }else if( c=='#' ){
+ if( z[0]=='0'
+ && (z[1]=='x' || z[1]=='X')
+ && sqlite3Isxdigit(z[2])
+ ){
+ z += 3;
+ while( sqlite3Isxdigit(z[0]) ){ z++; }
+ }else{
+ if( (z[0]=='-' || z[0]=='+') && sqlite3Isdigit(z[1]) ) z++;
+ if( !sqlite3Isdigit(z[0]) ) return 0;
+ z++;
+ while( sqlite3Isdigit(z[0]) ){ z++; }
+ }
+ }else{
+ if( c!=(*(z++)) ) return 0;
+ }
+ }
+ return *z==0;
+}
+
+JNIEXPORT jint JNICALL
+Java_org_sqlite_jni_capi_SQLTester_strglob(
+ JniArgsEnvClass, jbyteArray baG, jbyteArray baT
+){
+ int rc = 0;
+ jbyte * const pG = s3jni_jbyteArray_bytes(baG);
+ jbyte * const pT = pG ? s3jni_jbyteArray_bytes(baT) : 0;
+
+ s3jni_oom_fatal(pT);
+ /* Note that we're relying on the byte arrays having been
+ NUL-terminated on the Java side. */
+ rc = !SQLTester_strnotglob((const char *)pG, (const char *)pT);
+ s3jni_jbyteArray_release(baG, pG);
+ s3jni_jbyteArray_release(baT, pT);
+ return rc;
+}
+
+
+static int SQLTester_auto_extension(sqlite3 *pDb, const char **pzErr,
+ const struct sqlite3_api_routines *ignored){
+ sqlite3_create_function(pDb, "dup", 1, SQLITE_UTF8, &SQLTester,
+ SQLTester_dup_func, 0, 0);
+ sqlite3_create_function(pDb, "dup_count", 0, SQLITE_UTF8, &SQLTester,
+ SQLTester_dup_count_func, 0, 0);
+ return 0;
+}
+
+JNIEXPORT void JNICALL
+Java_org_sqlite_jni_capi_SQLTester_installCustomExtensions(JniArgsEnvClass){
+ sqlite3_auto_extension( (void(*)(void))SQLTester_auto_extension );
+}
+
+#endif /* SQLITE_JNI_ENABLE_SQLTester */
+////////////////////////////////////////////////////////////////////////
+// End of SQLTester bindings. Start of lower-level bits.
+////////////////////////////////////////////////////////////////////////
+
+/*
+** Called during static init of the CApi class to set up global
+** state.
+*/
+JNIEXPORT void JNICALL
+Java_org_sqlite_jni_capi_CApi_init(JniArgsEnvClass){
+ jclass klazz;
+
+ memset(&S3JniGlobal, 0, sizeof(S3JniGlobal));
+ if( (*env)->GetJavaVM(env, &SJG.jvm) ){
+ (*env)->FatalError(env, "GetJavaVM() failure shouldn't be possible.");
+ return;
+ }
+
+ /* Grab references to various global classes and objects... */
+ SJG.g.cLong = S3JniRefGlobal((*env)->FindClass(env,"java/lang/Long"));
+ S3JniExceptionIsFatal("Error getting reference to Long class.");
+ SJG.g.ctorLong1 = (*env)->GetMethodID(env, SJG.g.cLong,
+ "<init>", "(J)V");
+ S3JniExceptionIsFatal("Error getting reference to Long constructor.");
+
+ SJG.g.cString = S3JniRefGlobal((*env)->FindClass(env,"java/lang/String"));
+ S3JniExceptionIsFatal("Error getting reference to String class.");
+ SJG.g.ctorStringBA =
+ (*env)->GetMethodID(env, SJG.g.cString,
+ "<init>", "([BLjava/nio/charset/Charset;)V");
+ S3JniExceptionIsFatal("Error getting reference to String(byte[],Charset) ctor.");
+ SJG.g.stringGetBytes =
+ (*env)->GetMethodID(env, SJG.g.cString,
+ "getBytes", "(Ljava/nio/charset/Charset;)[B");
+ S3JniExceptionIsFatal("Error getting reference to String.getBytes(Charset).");
+
+ { /* java.nio.charset.StandardCharsets.UTF_8 */
+ jfieldID fUtf8;
+ klazz = (*env)->FindClass(env,"java/nio/charset/StandardCharsets");
+ S3JniExceptionIsFatal("Error getting reference to StandardCharsets class.");
+ fUtf8 = (*env)->GetStaticFieldID(env, klazz, "UTF_8",
+ "Ljava/nio/charset/Charset;");
+ S3JniExceptionIsFatal("Error getting StandardCharsets.UTF_8 field.");
+ SJG.g.oCharsetUtf8 =
+ S3JniRefGlobal((*env)->GetStaticObjectField(env, klazz, fUtf8));
+ S3JniExceptionIsFatal("Error getting reference to StandardCharsets.UTF_8.");
+ S3JniUnrefLocal(klazz);
+ }
+
+#ifdef SQLITE_ENABLE_FTS5
+ klazz = (*env)->FindClass(env, "org/sqlite/jni/fts5/Fts5PhraseIter");
+ S3JniExceptionIsFatal("Error getting reference to org.sqlite.jni.fts5.Fts5PhraseIter.");
+ SJG.fts5.jPhraseIter.fidA = (*env)->GetFieldID(env, klazz, "a", "J");
+ S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.a field.");
+ SJG.fts5.jPhraseIter.fidB = (*env)->GetFieldID(env, klazz, "b", "J");
+ S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.b field.");
+ S3JniUnrefLocal(klazz);
+#endif
+
+ SJG.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.mutex );
+ SJG.hook.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.hook.mutex );
+ SJG.nph.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.nph.mutex );
+ SJG.envCache.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.envCache.mutex );
+ SJG.perDb.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.perDb.mutex );
+ SJG.autoExt.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.autoExt.mutex );
+
+#if S3JNI_METRICS_MUTEX
+ SJG.metrics.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.metrics.mutex );
+#endif
+
+ {
+ /* Test whether this JVM supports direct memory access via
+ ByteBuffer. */
+ unsigned char buf[16] = {0};
+ jobject bb = (*env)->NewDirectByteBuffer(env, buf, 16);
+ if( bb ){
+ SJG.g.byteBuffer.klazz = S3JniRefGlobal((*env)->GetObjectClass(env, bb));
+ SJG.g.byteBuffer.midAlloc = (*env)->GetStaticMethodID(
+ env, SJG.g.byteBuffer.klazz, "allocateDirect", "(I)Ljava/nio/ByteBuffer;"
+ );
+ S3JniExceptionIsFatal("Error getting ByteBuffer.allocateDirect() method.");
+ SJG.g.byteBuffer.midLimit = (*env)->GetMethodID(
+ env, SJG.g.byteBuffer.klazz, "limit", "()I"
+ );
+ S3JniExceptionIsFatal("Error getting ByteBuffer.limit() method.");
+ S3JniUnrefLocal(bb);
+ }else{
+ SJG.g.byteBuffer.klazz = 0;
+ SJG.g.byteBuffer.midAlloc = 0;
+ }
+ }
+
+ sqlite3_shutdown()
+ /* So that it becomes legal for Java-level code to call
+ ** sqlite3_config(). */;
+}
diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h
new file mode 100644
index 0000000..082a202
--- /dev/null
+++ b/ext/jni/src/c/sqlite3-jni.h
@@ -0,0 +1,2461 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class org_sqlite_jni_capi_CApi */
+
+#ifndef _Included_org_sqlite_jni_capi_CApi
+#define _Included_org_sqlite_jni_capi_CApi
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef org_sqlite_jni_capi_CApi_SQLITE_ACCESS_EXISTS
+#define org_sqlite_jni_capi_CApi_SQLITE_ACCESS_EXISTS 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READWRITE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READ 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DENY
+#define org_sqlite_jni_capi_CApi_SQLITE_DENY 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IGNORE
+#define org_sqlite_jni_capi_CApi_SQLITE_IGNORE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_INDEX 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TABLE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_INDEX 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TABLE 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TRIGGER 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_VIEW 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TRIGGER 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_VIEW 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DELETE
+#define org_sqlite_jni_capi_CApi_SQLITE_DELETE 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_INDEX 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TABLE 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_INDEX 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TABLE 13L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TRIGGER 14L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_VIEW 15L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TRIGGER 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_VIEW 17L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INSERT
+#define org_sqlite_jni_capi_CApi_SQLITE_INSERT 18L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PRAGMA
+#define org_sqlite_jni_capi_CApi_SQLITE_PRAGMA 19L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_READ 20L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SELECT
+#define org_sqlite_jni_capi_CApi_SQLITE_SELECT 21L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRANSACTION
+#define org_sqlite_jni_capi_CApi_SQLITE_TRANSACTION 22L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UPDATE
+#define org_sqlite_jni_capi_CApi_SQLITE_UPDATE 23L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ATTACH
+#define org_sqlite_jni_capi_CApi_SQLITE_ATTACH 24L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DETACH
+#define org_sqlite_jni_capi_CApi_SQLITE_DETACH 25L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ALTER_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_ALTER_TABLE 26L
+#undef org_sqlite_jni_capi_CApi_SQLITE_REINDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_REINDEX 27L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ANALYZE
+#define org_sqlite_jni_capi_CApi_SQLITE_ANALYZE 28L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_VTABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_VTABLE 29L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_VTABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_VTABLE 30L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FUNCTION
+#define org_sqlite_jni_capi_CApi_SQLITE_FUNCTION 31L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SAVEPOINT
+#define org_sqlite_jni_capi_CApi_SQLITE_SAVEPOINT 32L
+#undef org_sqlite_jni_capi_CApi_SQLITE_RECURSIVE
+#define org_sqlite_jni_capi_CApi_SQLITE_RECURSIVE 33L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATIC
+#define org_sqlite_jni_capi_CApi_SQLITE_STATIC 0LL
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRANSIENT
+#define org_sqlite_jni_capi_CApi_SQLITE_TRANSIENT -1LL
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETSTART_INVERT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETSTART_INVERT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_NOSAVEPOINT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_NOSAVEPOINT 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_INVERT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_INVERT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_IGNORENOOP
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_IGNORENOOP 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_DATA
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_DATA 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_NOTFOUND
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_NOTFOUND 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONFLICT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONFLICT 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONSTRAINT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONSTRAINT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_FOREIGN_KEY
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_FOREIGN_KEY 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_OMIT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_OMIT 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_REPLACE
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_REPLACE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_ABORT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_ABORT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SINGLETHREAD
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SINGLETHREAD 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MULTITHREAD
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MULTITHREAD 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SERIALIZED
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SERIALIZED 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MALLOC
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MALLOC 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMALLOC
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMALLOC 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SCRATCH
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SCRATCH 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PAGECACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PAGECACHE 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_HEAP
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_HEAP 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMSTATUS
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMSTATUS 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MUTEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MUTEX 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMUTEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMUTEX 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOOKASIDE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOOKASIDE 13L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE 14L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE 15L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOG
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOG 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_URI
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_URI 17L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE2
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE2 18L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE2
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE2 19L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_COVERING_INDEX_SCAN
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_COVERING_INDEX_SCAN 20L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SQLLOG
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SQLLOG 21L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MMAP_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MMAP_SIZE 22L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_WIN32_HEAPSIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_WIN32_HEAPSIZE 23L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE_HDRSZ
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE_HDRSZ 24L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PMASZ
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PMASZ 25L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_STMTJRNL_SPILL
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_STMTJRNL_SPILL 26L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SMALL_MALLOC
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SMALL_MALLOC 27L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SORTERREF_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SORTERREF_SIZE 28L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMDB_MAXSIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMDB_MAXSIZE 29L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INTEGER
+#define org_sqlite_jni_capi_CApi_SQLITE_INTEGER 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FLOAT
+#define org_sqlite_jni_capi_CApi_SQLITE_FLOAT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TEXT
+#define org_sqlite_jni_capi_CApi_SQLITE_TEXT 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BLOB
+#define org_sqlite_jni_capi_CApi_SQLITE_BLOB 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NULL
+#define org_sqlite_jni_capi_CApi_SQLITE_NULL 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAINDBNAME
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAINDBNAME 1000L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LOOKASIDE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LOOKASIDE 1001L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FKEY
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FKEY 1002L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_TRIGGER 1003L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_QPSG
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_QPSG 1007L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRIGGER_EQP
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRIGGER_EQP 1008L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_RESET_DATABASE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_RESET_DATABASE 1009L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DEFENSIVE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DEFENSIVE 1010L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_WRITABLE_SCHEMA
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DML
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DML 1013L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DDL
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DDL 1014L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_VIEW 1015L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRUSTED_SCHEMA
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_STMT_SCANSTATUS
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_STMT_SCANSTATUS 1018L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_REVERSE_SCANORDER
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_REVERSE_SCANORDER 1019L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAX
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAX 1019L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_USED 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_SCHEMA_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_SCHEMA_USED 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_STMT_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_STMT_USED 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_HIT
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_HIT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_HIT
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_HIT 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_MISS
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_MISS 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_WRITE 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_DEFERRED_FKS
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_DEFERRED_FKS 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED_SHARED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED_SHARED 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_SPILL
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_SPILL 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_MAX
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_MAX 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF8
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF8 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16LE
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF16LE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16BE
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF16BE 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF16 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16_ALIGNED
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF16_ALIGNED 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCKSTATE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCKSTATE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_GET_LOCKPROXYFILE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_GET_LOCKPROXYFILE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SET_LOCKPROXYFILE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SET_LOCKPROXYFILE 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LAST_ERRNO
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LAST_ERRNO 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_HINT
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_HINT 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CHUNK_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CHUNK_SIZE 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_FILE_POINTER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_FILE_POINTER 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC_OMITTED
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC_OMITTED 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_AV_RETRY
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_AV_RETRY 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PERSIST_WAL
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PERSIST_WAL 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_OVERWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_OVERWRITE 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFSNAME
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFSNAME 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_POWERSAFE_OVERWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_POWERSAFE_OVERWRITE 13L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PRAGMA
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PRAGMA 14L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BUSYHANDLER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BUSYHANDLER 15L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TEMPFILENAME
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TEMPFILENAME 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_MMAP_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_MMAP_SIZE 18L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TRACE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TRACE 19L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_HAS_MOVED
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_HAS_MOVED 20L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC 21L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_PHASETWO
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_PHASETWO 22L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_SET_HANDLE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_SET_HANDLE 23L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WAL_BLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WAL_BLOCK 24L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ZIPVFS
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ZIPVFS 25L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RBU
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RBU 26L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFS_POINTER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFS_POINTER 27L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_JOURNAL_POINTER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_JOURNAL_POINTER 28L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_GET_HANDLE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_GET_HANDLE 29L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PDB
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PDB 30L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCK_TIMEOUT
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCK_TIMEOUT 34L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_DATA_VERSION
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_DATA_VERSION 35L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_LIMIT
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_LIMIT 36L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_DONE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_DONE 37L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESERVE_BYTES
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESERVE_BYTES 38L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_START
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_START 39L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_EXTERNAL_READER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_EXTERNAL_READER 40L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKSM_FILE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKSM_FILE 41L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESET_CACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESET_CACHE 42L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_NONE
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_NONE 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_SHARED
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_SHARED 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_RESERVED
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_RESERVED 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_PENDING
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_PENDING 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_EXCLUSIVE
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_EXCLUSIVE 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC512
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC512 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC1K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC1K 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC2K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC2K 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC4K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC4K 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC8K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC8K 32L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC16K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC16K 64L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC32K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC32K 128L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC64K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC64K 256L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SAFE_APPEND
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SAFE_APPEND 512L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SEQUENTIAL
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SEQUENTIAL 1024L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 2048L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_POWERSAFE_OVERWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_POWERSAFE_OVERWRITE 4096L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_IMMUTABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_IMMUTABLE 8192L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_BATCH_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_BATCH_ATOMIC 16384L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LENGTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LENGTH 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_SQL_LENGTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_SQL_LENGTH 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COLUMN
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COLUMN 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_EXPR_DEPTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_EXPR_DEPTH 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COMPOUND_SELECT
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COMPOUND_SELECT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VDBE_OP
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VDBE_OP 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_FUNCTION_ARG
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_FUNCTION_ARG 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_ATTACHED
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_ATTACHED 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LIKE_PATTERN_LENGTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LIKE_PATTERN_LENGTH 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VARIABLE_NUMBER
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VARIABLE_NUMBER 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_TRIGGER_DEPTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_TRIGGER_DEPTH 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_WORKER_THREADS
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_WORKER_THREADS 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_READONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_READONLY 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_READWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_READWRITE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_CREATE
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_CREATE 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_URI
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_URI 64L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_MEMORY
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_MEMORY 128L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOMUTEX
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOMUTEX 32768L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_FULLMUTEX
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_FULLMUTEX 65536L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_SHAREDCACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_SHAREDCACHE 131072L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_PRIVATECACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_PRIVATECACHE 262144L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOFOLLOW
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOFOLLOW 16777216L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_EXRESCODE
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_EXRESCODE 33554432L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_PERSISTENT
+#define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_PERSISTENT 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NO_VTAB
+#define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NO_VTAB 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OK
+#define org_sqlite_jni_capi_CApi_SQLITE_OK 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR
+#define org_sqlite_jni_capi_CApi_SQLITE_ERROR 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INTERNAL
+#define org_sqlite_jni_capi_CApi_SQLITE_INTERNAL 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PERM
+#define org_sqlite_jni_capi_CApi_SQLITE_PERM 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ABORT
+#define org_sqlite_jni_capi_CApi_SQLITE_ABORT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY
+#define org_sqlite_jni_capi_CApi_SQLITE_BUSY 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCKED
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCKED 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOMEM
+#define org_sqlite_jni_capi_CApi_SQLITE_NOMEM 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INTERRUPT
+#define org_sqlite_jni_capi_CApi_SQLITE_INTERRUPT 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT
+#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTFOUND
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTFOUND 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FULL
+#define org_sqlite_jni_capi_CApi_SQLITE_FULL 13L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN 14L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PROTOCOL
+#define org_sqlite_jni_capi_CApi_SQLITE_PROTOCOL 15L
+#undef org_sqlite_jni_capi_CApi_SQLITE_EMPTY
+#define org_sqlite_jni_capi_CApi_SQLITE_EMPTY 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SCHEMA
+#define org_sqlite_jni_capi_CApi_SQLITE_SCHEMA 17L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TOOBIG
+#define org_sqlite_jni_capi_CApi_SQLITE_TOOBIG 18L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT 19L
+#undef org_sqlite_jni_capi_CApi_SQLITE_MISMATCH
+#define org_sqlite_jni_capi_CApi_SQLITE_MISMATCH 20L
+#undef org_sqlite_jni_capi_CApi_SQLITE_MISUSE
+#define org_sqlite_jni_capi_CApi_SQLITE_MISUSE 21L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOLFS
+#define org_sqlite_jni_capi_CApi_SQLITE_NOLFS 22L
+#undef org_sqlite_jni_capi_CApi_SQLITE_AUTH
+#define org_sqlite_jni_capi_CApi_SQLITE_AUTH 23L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FORMAT
+#define org_sqlite_jni_capi_CApi_SQLITE_FORMAT 24L
+#undef org_sqlite_jni_capi_CApi_SQLITE_RANGE
+#define org_sqlite_jni_capi_CApi_SQLITE_RANGE 25L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTADB
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTADB 26L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTICE
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTICE 27L
+#undef org_sqlite_jni_capi_CApi_SQLITE_WARNING
+#define org_sqlite_jni_capi_CApi_SQLITE_WARNING 28L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ROW
+#define org_sqlite_jni_capi_CApi_SQLITE_ROW 100L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DONE
+#define org_sqlite_jni_capi_CApi_SQLITE_DONE 101L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR_MISSING_COLLSEQ
+#define org_sqlite_jni_capi_CApi_SQLITE_ERROR_MISSING_COLLSEQ 257L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR_RETRY
+#define org_sqlite_jni_capi_CApi_SQLITE_ERROR_RETRY 513L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR_SNAPSHOT
+#define org_sqlite_jni_capi_CApi_SQLITE_ERROR_SNAPSHOT 769L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_READ 266L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHORT_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHORT_READ 522L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_WRITE 778L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSYNC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSYNC 1034L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_FSYNC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_FSYNC 1290L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_TRUNCATE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_TRUNCATE 1546L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSTAT
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSTAT 1802L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_UNLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_UNLOCK 2058L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_RDLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_RDLOCK 2314L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE 2570L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_BLOCKED
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_BLOCKED 2826L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_NOMEM
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_NOMEM 3082L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_ACCESS
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_ACCESS 3338L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CHECKRESERVEDLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CHECKRESERVEDLOCK 3594L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_LOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_LOCK 3850L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CLOSE 4106L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_CLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_CLOSE 4362L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMOPEN
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMOPEN 4618L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMSIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMSIZE 4874L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMLOCK 5130L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMMAP
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMMAP 5386L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SEEK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SEEK 5642L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE_NOENT
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE_NOENT 5898L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_MMAP
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_MMAP 6154L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_GETTEMPPATH
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_GETTEMPPATH 6410L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CONVPATH
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CONVPATH 6666L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_VNODE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_VNODE 6922L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_AUTH
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_AUTH 7178L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_BEGIN_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_BEGIN_ATOMIC 7434L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_COMMIT_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_COMMIT_ATOMIC 7690L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_ROLLBACK_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_ROLLBACK_ATOMIC 7946L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DATA
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DATA 8202L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CORRUPTFS
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CORRUPTFS 8458L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCKED_SHAREDCACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCKED_SHAREDCACHE 262L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCKED_VTAB
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCKED_VTAB 518L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY_RECOVERY
+#define org_sqlite_jni_capi_CApi_SQLITE_BUSY_RECOVERY 261L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY_SNAPSHOT
+#define org_sqlite_jni_capi_CApi_SQLITE_BUSY_SNAPSHOT 517L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY_TIMEOUT
+#define org_sqlite_jni_capi_CApi_SQLITE_BUSY_TIMEOUT 773L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_NOTEMPDIR
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_NOTEMPDIR 270L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_ISDIR
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_ISDIR 526L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_FULLPATH
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_FULLPATH 782L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_CONVPATH
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_CONVPATH 1038L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_SYMLINK
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_SYMLINK 1550L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_VTAB
+#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_VTAB 267L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_SEQUENCE
+#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_SEQUENCE 523L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_INDEX 779L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_RECOVERY
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_RECOVERY 264L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTLOCK 520L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_ROLLBACK
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_ROLLBACK 776L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_DBMOVED
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_DBMOVED 1032L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTINIT
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTINIT 1288L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_DIRECTORY
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_DIRECTORY 1544L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ABORT_ROLLBACK
+#define org_sqlite_jni_capi_CApi_SQLITE_ABORT_ROLLBACK 516L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_CHECK
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_CHECK 275L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_COMMITHOOK
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_COMMITHOOK 531L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FOREIGNKEY
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FOREIGNKEY 787L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FUNCTION
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FUNCTION 1043L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_NOTNULL
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_NOTNULL 1299L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PRIMARYKEY
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PRIMARYKEY 1555L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_TRIGGER 1811L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_UNIQUE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_UNIQUE 2067L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_VTAB
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_VTAB 2323L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_ROWID
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_ROWID 2579L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PINNED
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PINNED 2835L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_DATATYPE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_DATATYPE 3091L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_WAL
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_WAL 283L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_ROLLBACK
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_ROLLBACK 539L
+#undef org_sqlite_jni_capi_CApi_SQLITE_WARNING_AUTOINDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_WARNING_AUTOINDEX 284L
+#undef org_sqlite_jni_capi_CApi_SQLITE_AUTH_USER
+#define org_sqlite_jni_capi_CApi_SQLITE_AUTH_USER 279L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OK_LOAD_PERMANENTLY
+#define org_sqlite_jni_capi_CApi_SQLITE_OK_LOAD_PERMANENTLY 256L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SERIALIZE_NOCOPY
+#define org_sqlite_jni_capi_CApi_SQLITE_SERIALIZE_NOCOPY 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_FREEONCLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_FREEONCLOSE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_READONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_READONLY 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_RESIZEABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_RESIZEABLE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SESSION_CONFIG_STRMSIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_SESSION_CONFIG_STRMSIZE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SESSION_OBJCONFIG_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_SESSION_OBJCONFIG_SIZE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_MEMORY_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_MEMORY_USED 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_USED 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_OVERFLOW
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_OVERFLOW 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_SIZE 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PARSER_STACK
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PARSER_STACK 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_SIZE 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_COUNT
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_COUNT 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FULLSCAN_STEP
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FULLSCAN_STEP 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_SORT
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_SORT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_AUTOINDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_AUTOINDEX 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_VM_STEP
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_VM_STEP 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_REPREPARE
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_REPREPARE 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_RUN
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_RUN 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_MISS
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_MISS 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_HIT
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_HIT 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_MEMUSED
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_MEMUSED 99L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SYNC_NORMAL
+#define org_sqlite_jni_capi_CApi_SQLITE_SYNC_NORMAL 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SYNC_FULL
+#define org_sqlite_jni_capi_CApi_SQLITE_SYNC_FULL 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SYNC_DATAONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_SYNC_DATAONLY 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_STMT
+#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_STMT 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_PROFILE
+#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_PROFILE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_ROW
+#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_ROW 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_CLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_CLOSE 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TXN_NONE
+#define org_sqlite_jni_capi_CApi_SQLITE_TXN_NONE 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TXN_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_TXN_READ 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TXN_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_TXN_WRITE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DETERMINISTIC
+#define org_sqlite_jni_capi_CApi_SQLITE_DETERMINISTIC 2048L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DIRECTONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_DIRECTONLY 524288L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SUBTYPE
+#define org_sqlite_jni_capi_CApi_SQLITE_SUBTYPE 1048576L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INNOCUOUS
+#define org_sqlite_jni_capi_CApi_SQLITE_INNOCUOUS 2097152L
+#undef org_sqlite_jni_capi_CApi_SQLITE_RESULT_SUBTYPE
+#define org_sqlite_jni_capi_CApi_SQLITE_RESULT_SUBTYPE 16777216L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_SCAN_UNIQUE
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_SCAN_UNIQUE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_EQ
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_EQ 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GT
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LE
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LE 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LT
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LT 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GE
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GE 32L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_MATCH
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_MATCH 64L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIKE
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIKE 65L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GLOB
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GLOB 66L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_REGEXP
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_REGEXP 67L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_NE
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_NE 68L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOT
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOT 69L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOTNULL
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNULL
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNULL 71L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_IS
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_IS 72L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIMIT
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIMIT 73L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_OFFSET
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_OFFSET 74L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_FUNCTION
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_FUNCTION 150L
+#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_CONSTRAINT_SUPPORT
+#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_CONSTRAINT_SUPPORT 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_INNOCUOUS
+#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_INNOCUOUS 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_DIRECTONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_DIRECTONLY 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_USES_ALL_SCHEMAS
+#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_USES_ALL_SCHEMAS 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ROLLBACK
+#define org_sqlite_jni_capi_CApi_SQLITE_ROLLBACK 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FAIL
+#define org_sqlite_jni_capi_CApi_SQLITE_FAIL 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_REPLACE
+#define org_sqlite_jni_capi_CApi_SQLITE_REPLACE 5L
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: init
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_init
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_java_uncache_thread
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1java_1uncache_1thread
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_jni_supports_nio
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1supports_1nio
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_jni_db_error
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1db_1error
+ (JNIEnv *, jclass, jobject, jint, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_aggregate_context
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Z)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1aggregate_1context
+ (JNIEnv *, jclass, jobject, jboolean);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_auto_extension
+ * Signature: (Lorg/sqlite/jni/capi/AutoExtensionCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1auto_1extension
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_backup_finish
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1finish
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_backup_init
+ * Signature: (JLjava/lang/String;JLjava/lang/String;)Lorg/sqlite/jni/capi/sqlite3_backup;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1init
+ (JNIEnv *, jclass, jlong, jstring, jlong, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_backup_pagecount
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1pagecount
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_backup_remaining
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1remaining
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_backup_step
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1step
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_blob
+ * Signature: (JI[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1blob
+ (JNIEnv *, jclass, jlong, jint, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_double
+ * Signature: (JID)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1double
+ (JNIEnv *, jclass, jlong, jint, jdouble);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_int
+ * Signature: (JII)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1int
+ (JNIEnv *, jclass, jlong, jint, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_int64
+ * Signature: (JIJ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1int64
+ (JNIEnv *, jclass, jlong, jint, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_java_object
+ * Signature: (JILjava/lang/Object;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1java_1object
+ (JNIEnv *, jclass, jlong, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_nio_buffer
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;ILjava/nio/ByteBuffer;II)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1nio_1buffer
+ (JNIEnv *, jclass, jobject, jint, jobject, jint, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_null
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1null
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_parameter_count
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1parameter_1count
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_parameter_index
+ * Signature: (J[B)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1parameter_1index
+ (JNIEnv *, jclass, jlong, jbyteArray);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_parameter_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1parameter_1name
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_text
+ * Signature: (JI[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1text
+ (JNIEnv *, jclass, jlong, jint, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_text16
+ * Signature: (JI[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1text16
+ (JNIEnv *, jclass, jlong, jint, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_value
+ * Signature: (JIJ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1value
+ (JNIEnv *, jclass, jlong, jint, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_zeroblob
+ * Signature: (JII)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1zeroblob
+ (JNIEnv *, jclass, jlong, jint, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_zeroblob64
+ * Signature: (JIJ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1zeroblob64
+ (JNIEnv *, jclass, jlong, jint, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_blob_bytes
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1bytes
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_blob_close
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1close
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_blob_open
+ * Signature: (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;JILorg/sqlite/jni/capi/OutputPointer/sqlite3_blob;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1open
+ (JNIEnv *, jclass, jlong, jstring, jstring, jstring, jlong, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_blob_read
+ * Signature: (J[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1read
+ (JNIEnv *, jclass, jlong, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_blob_read_nio_buffer
+ * Signature: (JILjava/nio/ByteBuffer;II)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1read_1nio_1buffer
+ (JNIEnv *, jclass, jlong, jint, jobject, jint, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_blob_reopen
+ * Signature: (JJ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1reopen
+ (JNIEnv *, jclass, jlong, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_blob_write
+ * Signature: (J[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1write
+ (JNIEnv *, jclass, jlong, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_blob_write_nio_buffer
+ * Signature: (JILjava/nio/ByteBuffer;II)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1write_1nio_1buffer
+ (JNIEnv *, jclass, jlong, jint, jobject, jint, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_busy_handler
+ * Signature: (JLorg/sqlite/jni/capi/BusyHandlerCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1busy_1handler
+ (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_busy_timeout
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1busy_1timeout
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_cancel_auto_extension
+ * Signature: (Lorg/sqlite/jni/capi/AutoExtensionCallback;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1cancel_1auto_1extension
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_changes
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1changes
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_changes64
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1changes64
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_clear_bindings
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1clear_1bindings
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_close
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1close
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_close_v2
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1close_1v2
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_blob
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1blob
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_bytes
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1bytes
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_bytes16
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1bytes16
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_count
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1count
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_database_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1database_1name
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_decltype
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1decltype
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_double
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)D
+ */
+JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1double
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_int
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1int
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_int64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1int64
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_java_object
+ * Signature: (JI)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1java_1object
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1name
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_nio_buffer
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)Ljava/nio/ByteBuffer;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1nio_1buffer
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_origin_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1origin_1name
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_table_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1table_1name
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_text
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1text
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_text16
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1text16
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_type
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1type
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_value
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)Lorg/sqlite/jni/capi/sqlite3_value;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1value
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_collation_needed
+ * Signature: (JLorg/sqlite/jni/capi/CollationNeededCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1collation_1needed
+ (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_commit_hook
+ * Signature: (JLorg/sqlite/jni/capi/CommitHookCallback;)Lorg/sqlite/jni/capi/CommitHookCallback;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1commit_1hook
+ (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_compileoption_get
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1compileoption_1get
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_compileoption_used
+ * Signature: (Ljava/lang/String;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1compileoption_1used
+ (JNIEnv *, jclass, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_complete
+ * Signature: ([B)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1complete
+ (JNIEnv *, jclass, jbyteArray);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_config__enable
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config_1_1enable
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_config__CONFIG_LOG
+ * Signature: (Lorg/sqlite/jni/capi/ConfigLogCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config_1_1CONFIG_1LOG
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_config__SQLLOG
+ * Signature: (Lorg/sqlite/jni/capi/ConfigSqlLogCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config_1_1SQLLOG
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_context_db_handle
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)Lorg/sqlite/jni/capi/sqlite3;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1context_1db_1handle
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_create_collation
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;ILorg/sqlite/jni/capi/CollationCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1create_1collation
+ (JNIEnv *, jclass, jobject, jstring, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_create_function
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;IILorg/sqlite/jni/capi/SQLFunction;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1create_1function
+ (JNIEnv *, jclass, jobject, jstring, jint, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_data_count
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1data_1count
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_config
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;IILorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_Int32_2
+ (JNIEnv *, jclass, jobject, jint, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_config
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1config__Lorg_sqlite_jni_capi_sqlite3_2ILjava_lang_String_2
+ (JNIEnv *, jclass, jobject, jint, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1name
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_filename
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1filename
+ (JNIEnv *, jclass, jobject, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_handle
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Lorg/sqlite/jni/capi/sqlite3;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1handle
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_readonly
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1readonly
+ (JNIEnv *, jclass, jobject, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_release_memory
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1release_1memory
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_status
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;Z)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1status
+ (JNIEnv *, jclass, jobject, jint, jobject, jobject, jboolean);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_errcode
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1errcode
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_errmsg
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1errmsg
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_error_offset
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1error_1offset
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_errstr
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1errstr
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_expanded_sql
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1expanded_1sql
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_extended_errcode
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1errcode
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_extended_result_codes
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Z)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1result_1codes
+ (JNIEnv *, jclass, jobject, jboolean);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_get_autocommit
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1get_1autocommit
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_get_auxdata
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1get_1auxdata
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_finalize
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1finalize
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_initialize
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1initialize
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_interrupt
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1interrupt
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_is_interrupted
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1is_1interrupted
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_keyword_check
+ * Signature: (Ljava/lang/String;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1keyword_1check
+ (JNIEnv *, jclass, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_keyword_count
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1keyword_1count
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_keyword_name
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1keyword_1name
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_last_insert_rowid
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1last_1insert_1rowid
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_libversion
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1libversion
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_libversion_number
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1libversion_1number
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_limit
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;II)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1limit
+ (JNIEnv *, jclass, jobject, jint, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_normalized_sql
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1normalized_1sql
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_open
+ * Signature: (Ljava/lang/String;Lorg/sqlite/jni/capi/OutputPointer/sqlite3;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1open
+ (JNIEnv *, jclass, jstring, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_open_v2
+ * Signature: (Ljava/lang/String;Lorg/sqlite/jni/capi/OutputPointer/sqlite3;ILjava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1open_1v2
+ (JNIEnv *, jclass, jstring, jobject, jint, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_prepare
+ * Signature: (J[BILorg/sqlite/jni/capi/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1prepare
+ (JNIEnv *, jclass, jlong, jbyteArray, jint, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_prepare_v2
+ * Signature: (J[BILorg/sqlite/jni/capi/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1prepare_1v2
+ (JNIEnv *, jclass, jlong, jbyteArray, jint, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_prepare_v3
+ * Signature: (J[BIILorg/sqlite/jni/capi/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1prepare_1v3
+ (JNIEnv *, jclass, jlong, jbyteArray, jint, jint, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_preupdate_blobwrite
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1blobwrite
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_preupdate_count
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1count
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_preupdate_depth
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1depth
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_preupdate_hook
+ * Signature: (JLorg/sqlite/jni/capi/PreupdateHookCallback;)Lorg/sqlite/jni/capi/PreupdateHookCallback;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1hook
+ (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_preupdate_new
+ * Signature: (JILorg/sqlite/jni/capi/OutputPointer/sqlite3_value;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1new
+ (JNIEnv *, jclass, jlong, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_preupdate_old
+ * Signature: (JILorg/sqlite/jni/capi/OutputPointer/sqlite3_value;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1old
+ (JNIEnv *, jclass, jlong, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_progress_handler
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILorg/sqlite/jni/capi/ProgressHandlerCallback;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1progress_1handler
+ (JNIEnv *, jclass, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_randomness
+ * Signature: ([B)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1randomness
+ (JNIEnv *, jclass, jbyteArray);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_release_memory
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1release_1memory
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_reset
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1reset
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_reset_auto_extension
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1reset_1auto_1extension
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_double
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;D)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1double
+ (JNIEnv *, jclass, jobject, jdouble);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_error
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BI)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error
+ (JNIEnv *, jclass, jobject, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_error_toobig
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error_1toobig
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_error_nomem
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error_1nomem
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_error_code
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error_1code
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_int
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1int
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_int64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;J)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1int64
+ (JNIEnv *, jclass, jobject, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_java_object
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Ljava/lang/Object;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1java_1object
+ (JNIEnv *, jclass, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_nio_buffer
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Ljava/nio/ByteBuffer;II)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1nio_1buffer
+ (JNIEnv *, jclass, jobject, jobject, jint, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_null
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1null
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_subtype
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1subtype
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_value
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Lorg/sqlite/jni/capi/sqlite3_value;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1value
+ (JNIEnv *, jclass, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_zeroblob
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1zeroblob
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_zeroblob64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1zeroblob64
+ (JNIEnv *, jclass, jobject, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_blob
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BI)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1blob
+ (JNIEnv *, jclass, jobject, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_blob64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BJ)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1blob64
+ (JNIEnv *, jclass, jobject, jbyteArray, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_text
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BI)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1text
+ (JNIEnv *, jclass, jobject, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_text64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BJI)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1text64
+ (JNIEnv *, jclass, jobject, jbyteArray, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_rollback_hook
+ * Signature: (JLorg/sqlite/jni/capi/RollbackHookCallback;)Lorg/sqlite/jni/capi/RollbackHookCallback;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1rollback_1hook
+ (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_set_authorizer
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Lorg/sqlite/jni/capi/AuthorizerCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1set_1authorizer
+ (JNIEnv *, jclass, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_set_auxdata
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;ILjava/lang/Object;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1set_1auxdata
+ (JNIEnv *, jclass, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_set_last_insert_rowid
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;J)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1set_1last_1insert_1rowid
+ (JNIEnv *, jclass, jobject, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_shutdown
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1shutdown
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_sleep
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1sleep
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_sourceid
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1sourceid
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_sql
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1sql
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_status
+ * Signature: (ILorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;Z)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1status
+ (JNIEnv *, jclass, jint, jobject, jobject, jboolean);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_status64
+ * Signature: (ILorg/sqlite/jni/capi/OutputPointer/Int64;Lorg/sqlite/jni/capi/OutputPointer/Int64;Z)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1status64
+ (JNIEnv *, jclass, jint, jobject, jobject, jboolean);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_step
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1step
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_stmt_busy
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1busy
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_stmt_explain
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1explain
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_stmt_isexplain
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1isexplain
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_stmt_readonly
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1readonly
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_stmt_status
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;IZ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1status
+ (JNIEnv *, jclass, jobject, jint, jboolean);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_strglob
+ * Signature: ([B[B)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1strglob
+ (JNIEnv *, jclass, jbyteArray, jbyteArray);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_strlike
+ * Signature: ([B[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1strlike
+ (JNIEnv *, jclass, jbyteArray, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_system_errno
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1system_1errno
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_table_column_metadata
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/sqlite/jni/capi/OutputPointer/String;Lorg/sqlite/jni/capi/OutputPointer/String;Lorg/sqlite/jni/capi/OutputPointer/Bool;Lorg/sqlite/jni/capi/OutputPointer/Bool;Lorg/sqlite/jni/capi/OutputPointer/Bool;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1table_1column_1metadata
+ (JNIEnv *, jclass, jobject, jstring, jstring, jstring, jobject, jobject, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_threadsafe
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1threadsafe
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_total_changes
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1total_1changes
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_total_changes64
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1total_1changes64
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_trace_v2
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILorg/sqlite/jni/capi/TraceV2Callback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1trace_1v2
+ (JNIEnv *, jclass, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_txn_state
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1txn_1state
+ (JNIEnv *, jclass, jobject, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_update_hook
+ * Signature: (JLorg/sqlite/jni/capi/UpdateHookCallback;)Lorg/sqlite/jni/capi/UpdateHookCallback;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1update_1hook
+ (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_blob
+ * Signature: (J)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1blob
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_bytes
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1bytes
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_bytes16
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1bytes16
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_double
+ * Signature: (J)D
+ */
+JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1double
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_dup
+ * Signature: (J)Lorg/sqlite/jni/capi/sqlite3_value;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1dup
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_encoding
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1encoding
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_free
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1free
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_frombind
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1frombind
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_int
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1int
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_int64
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1int64
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_java_object
+ * Signature: (J)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1java_1object
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_nio_buffer
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_value;)Ljava/nio/ByteBuffer;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1nio_1buffer
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_nochange
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1nochange
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_numeric_type
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1numeric_1type
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_subtype
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1subtype
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_text
+ * Signature: (J)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1text
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_text16
+ * Signature: (J)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1text16
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_type
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1type
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_jni_internal_details
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1internal_1details
+ (JNIEnv *, jclass);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class org_sqlite_jni_capi_SQLTester */
+
+#ifndef _Included_org_sqlite_jni_capi_SQLTester
+#define _Included_org_sqlite_jni_capi_SQLTester
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: org_sqlite_jni_capi_SQLTester
+ * Method: strglob
+ * Signature: ([B[B)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_SQLTester_strglob
+ (JNIEnv *, jclass, jbyteArray, jbyteArray);
+
+/*
+ * Class: org_sqlite_jni_capi_SQLTester
+ * Method: installCustomExtensions
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_SQLTester_installCustomExtensions
+ (JNIEnv *, jclass);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class org_sqlite_jni_fts5_Fts5ExtensionApi */
+
+#ifndef _Included_org_sqlite_jni_fts5_Fts5ExtensionApi
+#define _Included_org_sqlite_jni_fts5_Fts5ExtensionApi
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: getInstance
+ * Signature: ()Lorg/sqlite/jni/fts5/Fts5ExtensionApi;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_getInstance
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xColumnCount
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnCount
+ (JNIEnv *, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xColumnSize
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnSize
+ (JNIEnv *, jobject, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xColumnText
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnText
+ (JNIEnv *, jobject, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xColumnTotalSize
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/Int64;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnTotalSize
+ (JNIEnv *, jobject, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xGetAuxdata
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Z)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xGetAuxdata
+ (JNIEnv *, jobject, jobject, jboolean);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xInst
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xInst
+ (JNIEnv *, jobject, jobject, jint, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xInstCount
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xInstCount
+ (JNIEnv *, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xPhraseCount
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseCount
+ (JNIEnv *, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xPhraseFirst
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseFirst
+ (JNIEnv *, jobject, jobject, jint, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xPhraseFirstColumn
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseFirstColumn
+ (JNIEnv *, jobject, jobject, jint, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xPhraseNext
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseNext
+ (JNIEnv *, jobject, jobject, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xPhraseNextColumn
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseNextColumn
+ (JNIEnv *, jobject, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xPhraseSize
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;I)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseSize
+ (JNIEnv *, jobject, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xQueryPhrase
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/fts5/Fts5ExtensionApi/XQueryPhraseCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xQueryPhrase
+ (JNIEnv *, jobject, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xRowCount
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/capi/OutputPointer/Int64;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xRowCount
+ (JNIEnv *, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xRowid
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xRowid
+ (JNIEnv *, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xSetAuxdata
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Ljava/lang/Object;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xSetAuxdata
+ (JNIEnv *, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xTokenize
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;[BLorg/sqlite/jni/fts5/XTokenizeCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xTokenize
+ (JNIEnv *, jobject, jobject, jbyteArray, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xUserData
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xUserData
+ (JNIEnv *, jobject, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class org_sqlite_jni_fts5_fts5_api */
+
+#ifndef _Included_org_sqlite_jni_fts5_fts5_api
+#define _Included_org_sqlite_jni_fts5_fts5_api
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef org_sqlite_jni_fts5_fts5_api_iVersion
+#define org_sqlite_jni_fts5_fts5_api_iVersion 2L
+/*
+ * Class: org_sqlite_jni_fts5_fts5_api
+ * Method: getInstanceForDb
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)Lorg/sqlite/jni/fts5/fts5_api;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_fts5_1api_getInstanceForDb
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_fts5_api
+ * Method: xCreateFunction
+ * Signature: (Ljava/lang/String;Ljava/lang/Object;Lorg/sqlite/jni/fts5/fts5_extension_function;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_fts5_1api_xCreateFunction
+ (JNIEnv *, jobject, jstring, jobject, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class org_sqlite_jni_fts5_fts5_tokenizer */
+
+#ifndef _Included_org_sqlite_jni_fts5_fts5_tokenizer
+#define _Included_org_sqlite_jni_fts5_fts5_tokenizer
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: org_sqlite_jni_fts5_fts5_tokenizer
+ * Method: xTokenize
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Tokenizer;I[BLorg/sqlite/jni/fts5/XTokenizeCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_fts5_1tokenizer_xTokenize
+ (JNIEnv *, jobject, jobject, jint, jbyteArray, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/ext/jni/src/org/sqlite/jni/annotation/Experimental.java b/ext/jni/src/org/sqlite/jni/annotation/Experimental.java
new file mode 100644
index 0000000..190435c
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/annotation/Experimental.java
@@ -0,0 +1,30 @@
+/*
+** 2023-09-27
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file houses the Experimental annotation for the sqlite3 C API.
+*/
+package org.sqlite.jni.annotation;
+import java.lang.annotation.*;
+
+/**
+ This annotation is for flagging methods, constructors, and types
+ which are expressly experimental and subject to any amount of
+ change or outright removal. Client code should not rely on such
+ features.
+*/
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target({
+ ElementType.METHOD,
+ ElementType.CONSTRUCTOR,
+ ElementType.TYPE
+})
+public @interface Experimental{}
diff --git a/ext/jni/src/org/sqlite/jni/annotation/NotNull.java b/ext/jni/src/org/sqlite/jni/annotation/NotNull.java
new file mode 100644
index 0000000..0c31782
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/annotation/NotNull.java
@@ -0,0 +1,71 @@
+/*
+** 2023-09-27
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file houses the NotNull annotation for the sqlite3 C API.
+*/
+package org.sqlite.jni.annotation;
+import java.lang.annotation.*;
+
+/**
+ This annotation is for flagging parameters which may not legally be
+ null or point to closed/finalized C-side resources.
+
+ <p>In the case of Java types which map directly to C struct types
+ (e.g. {@link org.sqlite.jni.capi.sqlite3}, {@link
+ org.sqlite.jni.capi.sqlite3_stmt}, and {@link
+ org.sqlite.jni.capi.sqlite3_context}), a closed/finalized resource
+ is also considered to be null for purposes this annotation because
+ the C-side effect of passing such a handle is the same as if null
+ is passed.</p>
+
+ <p>When used in the context of Java interfaces which are called
+ from the C APIs, this annotation communicates that the C API will
+ never pass a null value to the callback for that parameter.</p>
+
+ <p>Passing a null, for this annotation's definition of null, for
+ any parameter marked with this annoation specifically invokes
+ undefined behavior (see below).</p>
+
+ <p>Passing 0 (i.e. C NULL) or a negative value for any long-type
+ parameter marked with this annoation specifically invokes undefined
+ behavior (see below). Such values are treated as C pointers in the
+ JNI layer.</p>
+
+ <p><b>Undefined behaviour:</b> the JNI build uses the {@code
+ SQLITE_ENABLE_API_ARMOR} build flag, meaning that the C code
+ invoked with invalid NULL pointers and the like will not invoke
+ undefined behavior in the conventional C sense, but may, for
+ example, return result codes which are not documented for the
+ affected APIs or may otherwise behave unpredictably. In no known
+ cases will such arguments result in C-level code dereferencing a
+ NULL pointer or accessing out-of-bounds (or otherwise invalid)
+ memory. In other words, they may cause unexpected behavior but
+ should never cause an outright crash or security issue.</p>
+
+ <p>Note that the C-style API does not throw any exceptions on its
+ own because it has a no-throw policy in order to retain its C-style
+ semantics, but it may trigger NullPointerExceptions (or similar) if
+ passed a null for a parameter flagged with this annotation.</p>
+
+ <p>This annotation is informational only. No policy is in place to
+ programmatically ensure that NotNull is conformed to in client
+ code.</p>
+
+ <p>This annotation is solely for the use by the classes in the
+ org.sqlite.jni package and subpackages, but is made public so that
+ javadoc will link to it from the annotated functions. It is not
+ part of the public API and client-level code must not rely on
+ it.</p>
+*/
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.PARAMETER)
+public @interface NotNull{}
diff --git a/ext/jni/src/org/sqlite/jni/annotation/Nullable.java b/ext/jni/src/org/sqlite/jni/annotation/Nullable.java
new file mode 100644
index 0000000..e3fa30e
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/annotation/Nullable.java
@@ -0,0 +1,33 @@
+/*
+** 2023-09-27
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file houses the Nullable annotation for the sqlite3 C API.
+*/
+package org.sqlite.jni.annotation;
+import java.lang.annotation.*;
+
+/**
+ This annotation is for flagging parameters which may legally be
+ null, noting that they may behave differently if passed null but
+ are prepared to expect null as a value. When used in the context of
+ callback methods which are called into from the C APIs, this
+ annotation communicates that the C API may pass a null value to the
+ callback.
+
+ <p>This annotation is solely for the use by the classes in this
+ package but is made public so that javadoc will link to it from the
+ annotated functions. It is not part of the public API and
+ client-level code must not rely on it.
+*/
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.PARAMETER)
+public @interface Nullable{}
diff --git a/ext/jni/src/org/sqlite/jni/annotation/package-info.java b/ext/jni/src/org/sqlite/jni/annotation/package-info.java
new file mode 100644
index 0000000..20ac7a3
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/annotation/package-info.java
@@ -0,0 +1,17 @@
+/*
+** 2023-09-27
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+*/
+/**
+ This package houses annotations specific to the JNI bindings of the
+ SQLite3 C API.
+*/
+package org.sqlite.jni.annotation;
diff --git a/ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java b/ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java
new file mode 100644
index 0000000..9255366
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java
@@ -0,0 +1,34 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import org.sqlite.jni.annotation.NotNull;
+
+/**
+ An implementation of {@link CollationCallback} which provides a
+ no-op xDestroy() method.
+*/
+public abstract class AbstractCollationCallback
+ implements CollationCallback, XDestroyCallback {
+ /**
+ Must compare the given byte arrays and return the result using
+ {@code memcmp()} semantics.
+ */
+ public abstract int call(@NotNull byte[] lhs, @NotNull byte[] rhs);
+
+ /**
+ Optionally override to be notified when the UDF is finalized by
+ SQLite. This implementation does nothing.
+ */
+ public void xDestroy(){}
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java b/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java
new file mode 100644
index 0000000..1fa6c6b
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java
@@ -0,0 +1,138 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+
+/**
+ A SQLFunction implementation for aggregate functions. Its T is the
+ data type of its "accumulator" state, an instance of which is
+ intended to be be managed using the getAggregateState() and
+ takeAggregateState() methods.
+*/
+public abstract class AggregateFunction<T> implements SQLFunction {
+
+ /**
+ As for the xStep() argument of the C API's
+ sqlite3_create_function(). If this function throws, the
+ exception is not propagated and a warning might be emitted to a
+ debugging channel.
+ */
+ public abstract void xStep(sqlite3_context cx, sqlite3_value[] args);
+
+ /**
+ As for the xFinal() argument of the C API's sqlite3_create_function().
+ If this function throws, it is translated into an sqlite3_result_error().
+ */
+ public abstract void xFinal(sqlite3_context cx);
+
+ /**
+ Optionally override to be notified when the UDF is finalized by
+ SQLite.
+ */
+ public void xDestroy() {}
+
+ /**
+ PerContextState assists aggregate and window functions in
+ managing their accumulator state across calls to the UDF's
+ callbacks.
+
+ <p>T must be of a type which can be legally stored as a value in
+ java.util.HashMap<KeyType,T>.
+
+ <p>If a given aggregate or window function is called multiple times
+ in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)...,
+ then the clients need some way of knowing which call is which so
+ that they can map their state between their various UDF callbacks
+ and reset it via xFinal(). This class takes care of such
+ mappings.
+
+ <p>This class works by mapping
+ sqlite3_context.getAggregateContext() to a single piece of
+ state, of a client-defined type (the T part of this class), which
+ persists across a "matching set" of the UDF's callbacks.
+
+ <p>This class is a helper providing commonly-needed functionality
+ - it is not required for use with aggregate or window functions.
+ Client UDFs are free to perform such mappings using custom
+ approaches. The provided {@link AggregateFunction} and {@link
+ WindowFunction} classes use this.
+ */
+ public static final class PerContextState<T> {
+ private final java.util.Map<Long,ValueHolder<T>> map
+ = new java.util.HashMap<>();
+
+ /**
+ Should be called from a UDF's xStep(), xValue(), and xInverse()
+ methods, passing it that method's first argument and an initial
+ value for the persistent state. If there is currently no
+ mapping for the given context within the map, one is created
+ using the given initial value, else the existing one is used
+ and the 2nd argument is ignored. It returns a ValueHolder<T>
+ which can be used to modify that state directly without
+ requiring that the client update the underlying map's entry.
+
+ <p>The caller is obligated to eventually call
+ takeAggregateState() to clear the mapping.
+ */
+ public ValueHolder<T> getAggregateState(sqlite3_context cx, T initialValue){
+ final Long key = cx.getAggregateContext(true);
+ ValueHolder<T> rc = null==key ? null : map.get(key);
+ if( null==rc ){
+ map.put(key, rc = new ValueHolder<>(initialValue));
+ }
+ return rc;
+ }
+
+ /**
+ Should be called from a UDF's xFinal() method and passed that
+ method's first argument. This function removes the value
+ associated with cx.getAggregateContext() from the map and
+ returns it, returning null if no other UDF method has been
+ called to set up such a mapping. The latter condition will be
+ the case if a UDF is used in a statement which has no result
+ rows.
+ */
+ public T takeAggregateState(sqlite3_context cx){
+ final ValueHolder<T> h = map.remove(cx.getAggregateContext(false));
+ return null==h ? null : h.value;
+ }
+ }
+
+ /** Per-invocation state for the UDF. */
+ private final PerContextState<T> map = new PerContextState<>();
+
+ /**
+ To be called from the implementation's xStep() method, as well
+ as the xValue() and xInverse() methods of the {@link WindowFunction}
+ subclass, to fetch the current per-call UDF state. On the
+ first call to this method for any given sqlite3_context
+ argument, the context is set to the given initial value. On all other
+ calls, the 2nd argument is ignored.
+
+ @see SQLFunction.PerContextState#getAggregateState
+ */
+ protected final ValueHolder<T> getAggregateState(sqlite3_context cx, T initialValue){
+ return map.getAggregateState(cx, initialValue);
+ }
+
+ /**
+ To be called from the implementation's xFinal() method to fetch
+ the final state of the UDF and remove its mapping.
+
+ see SQLFunction.PerContextState#takeAggregateState
+ */
+ protected final T takeAggregateState(sqlite3_context cx){
+ return map.takeAggregateState(cx);
+ }
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java b/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java
new file mode 100644
index 0000000..298e3a5
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java
@@ -0,0 +1,29 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import org.sqlite.jni.annotation.*;
+
+/**
+ Callback for use with {@link CApi#sqlite3_set_authorizer}.
+*/
+public interface AuthorizerCallback extends CallbackProxy {
+ /**
+ Must function as described for the C-level
+ sqlite3_set_authorizer() callback. If it throws, the error is
+ converted to a db-level error and the exception is suppressed.
+ */
+ int call(int opId, @Nullable String s1, @Nullable String s2,
+ @Nullable String s3, @Nullable String s4);
+
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java b/ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java
new file mode 100644
index 0000000..7a54132
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java
@@ -0,0 +1,40 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with the {@link CApi#sqlite3_auto_extension}
+ family of APIs.
+*/
+public interface AutoExtensionCallback extends CallbackProxy {
+ /**
+ Must function as described for a C-level
+ sqlite3_auto_extension() callback.
+
+ <p>This callback may throw and the exception's error message will
+ be set as the db's error string.
+
+ <p>Tips for implementations:
+
+ <p>- Opening a database from an auto-extension handler will lead to
+ an endless recursion of the auto-handler triggering itself
+ indirectly for each newly-opened database.
+
+ <p>- If this routine is stateful, it may be useful to make the
+ overridden method synchronized.
+
+ <p>- Results are undefined if the given db is closed by an auto-extension.
+ */
+ int call(sqlite3 db);
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java b/ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java
new file mode 100644
index 0000000..00223f0
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java
@@ -0,0 +1,26 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_busy_handler}.
+*/
+public interface BusyHandlerCallback extends CallbackProxy {
+ /**
+ Must function as documented for the C-level
+ sqlite3_busy_handler() callback argument, minus the (void*)
+ argument the C-level function requires.
+ */
+ int call(int n);
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/CApi.java b/ext/jni/src/org/sqlite/jni/capi/CApi.java
new file mode 100644
index 0000000..b5d0830
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/CApi.java
@@ -0,0 +1,2897 @@
+/*
+** 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 the main JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import java.nio.charset.StandardCharsets;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import org.sqlite.jni.annotation.*;
+import java.util.Arrays;
+
+/**
+ This class contains the entire C-style sqlite3 JNI API binding,
+ minus a few bits and pieces declared in other files. For client-side
+ use, a static import is recommended:
+
+ <pre>{@code
+ import static org.sqlite.jni.capi.CApi.*;
+ }</pre>
+
+ <p>The C-side part can be found in sqlite3-jni.c.
+
+ <p>Only functions which materially differ from their C counterparts
+ are documented here, and only those material differences are
+ documented. The C documentation is otherwise applicable for these
+ APIs:
+
+ <p><a href="https://sqlite.org/c3ref/intro.html">https://sqlite.org/c3ref/intro.html</a>
+
+ <p>A handful of Java-specific APIs have been added which are
+ documented here. A number of convenience overloads are provided
+ which are not documented but whose semantics map 1-to-1 in an
+ intuitive manner. e.g. {@link
+ #sqlite3_result_set(sqlite3_context,int)} is equivalent to {@link
+ #sqlite3_result_int}, and sqlite3_result_set() has many
+ type-specific overloads.
+
+ <p>Notes regarding Java's Modified UTF-8 vs standard UTF-8:
+
+ <p>SQLite internally uses UTF-8 encoding, whereas Java natively uses
+ UTF-16. Java JNI has routines for converting to and from UTF-8,
+ but JNI uses what its docs call modified UTF-8 (see links below)
+ Care must be taken when converting Java strings to or from standard
+ UTF-8 to ensure that the proper conversion is performed. In short,
+ Java's {@code String.getBytes(StandardCharsets.UTF_8)} performs the proper
+ conversion in Java, and there are no JNI C APIs for that conversion
+ (JNI's {@code NewStringUTF()} requires its input to be in MUTF-8).
+
+ <p>The known consequences and limitations this discrepancy places on
+ the SQLite3 JNI binding include:
+
+ <ul>
+
+ <li>C functions which take C-style strings without a length argument
+ require special care when taking input from Java. In particular,
+ Java strings converted to byte arrays for encoding purposes are not
+ NUL-terminated, and conversion to a Java byte array must sometimes
+ be careful to add one. Functions which take a length do not require
+ this so long as the length is provided. Search the CApi class
+ for "\0" for examples.
+
+ </ul>
+
+ <p>Further reading:
+
+ <p><a href="https://stackoverflow.com/questions/57419723">https://stackoverflow.com/questions/57419723</a>
+ <p><a href="https://stackoverflow.com/questions/7921016">https://stackoverflow.com/questions/7921016</a>
+ <p><a href="https://itecnote.com/tecnote/java-getting-true-utf-8-characters-in-java-jni/">https://itecnote.com/tecnote/java-getting-true-utf-8-characters-in-java-jni/</a>
+ <p><a href="https://docs.oracle.com/javase/8/docs/api/java/lang/Character.html#unicode">https://docs.oracle.com/javase/8/docs/api/java/lang/Character.html#unicode</a>
+ <p><a href="https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8">https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8</a>
+
+*/
+public final class CApi {
+ static {
+ System.loadLibrary("sqlite3-jni");
+ }
+ //! Not used
+ private CApi(){}
+ //! Called from static init code.
+ private static native void init();
+
+ /**
+ Returns a nul-terminated copy of s as a UTF-8-encoded byte array,
+ or null if s is null.
+ */
+ private static byte[] nulTerminateUtf8(String s){
+ return null==s ? null : (s+"\0").getBytes(StandardCharsets.UTF_8);
+ }
+
+ /**
+ Each thread which uses the SQLite3 JNI APIs should call
+ sqlite3_jni_uncache_thread() when it is done with the library -
+ either right before it terminates or when it finishes using the
+ SQLite API. This will clean up any cached per-thread info.
+
+ <p>This process does not close any databases or finalize
+ any prepared statements because their ownership does not depend on
+ a given thread. For proper library behavior, and to
+ avoid C-side leaks, be sure to finalize all statements and close
+ all databases before calling this function.
+
+ <p>Calling this from the main application thread is not strictly
+ required. Additional threads must call this before ending or they
+ will leak cache entries in the C heap, which in turn may keep
+ numerous Java-side global references active.
+
+ <p>This routine returns false without side effects if the current
+ JNIEnv is not cached, else returns true, but this information is
+ primarily for testing of the JNI bindings and is not information
+ which client-level code can use to make any informed
+ decisions. Its return type and semantics are not considered
+ stable and may change at any time.
+ */
+ public static native boolean sqlite3_java_uncache_thread();
+
+ /**
+ Returns true if this JVM has JNI-level support for C-level direct
+ memory access using java.nio.ByteBuffer, else returns false.
+ */
+ @Experimental
+ public static native boolean sqlite3_jni_supports_nio();
+
+ /**
+ For internal use only. Sets the given db's error code and
+ (optionally) string. If rc is 0, it defaults to SQLITE_ERROR.
+
+ On success it returns rc. On error it may return a more serious
+ code, such as SQLITE_NOMEM. Returns SQLITE_MISUSE if db is null.
+ */
+ static native int sqlite3_jni_db_error(@NotNull sqlite3 db,
+ int rc, @Nullable String msg);
+
+ /**
+ Convenience overload which uses e.toString() as the error
+ message.
+ */
+ static int sqlite3_jni_db_error(@NotNull sqlite3 db,
+ int rc, @NotNull Exception e){
+ return sqlite3_jni_db_error(db, rc, e.toString());
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ // Maintenance reminder: please keep the sqlite3_.... functions
+ // alphabetized. The SQLITE_... values. on the other hand, are
+ // grouped by category.
+
+ /**
+ Functions exactly like the native form except that (A) the 2nd
+ argument is a boolean instead of an int and (B) the returned
+ value is not a pointer address and is only intended for use as a
+ per-UDF-call lookup key in a higher-level data structure.
+
+ <p>Passing a true second argument is analogous to passing some
+ unspecified small, non-0 positive value to the C API and passing
+ false is equivalent to passing 0 to the C API.
+
+ <p>Like the C API, it returns 0 if allocation fails or if
+ initialize is false and no prior aggregate context was allocated
+ for cx. If initialize is true then it returns 0 only on
+ allocation error. In all casses, 0 is considered the sentinel
+ "not a key" value.
+ */
+ public static native long sqlite3_aggregate_context(sqlite3_context cx, boolean initialize);
+
+ /**
+ Functions almost as documented for the C API, with these
+ exceptions:
+
+ <p>- The callback interface is shorter because of
+ cross-language differences. Specifically, 3rd argument to the C
+ auto-extension callback interface is unnecessary here.
+
+ <p>The C API docs do not specifically say so, but if the list of
+ auto-extensions is manipulated from an auto-extension, it is
+ undefined which, if any, auto-extensions will subsequently
+ execute for the current database. That is, doing so will result
+ in unpredictable, but not undefined, behavior.
+
+ <p>See the AutoExtension class docs for more information.
+ */
+ public static native int sqlite3_auto_extension(@NotNull AutoExtensionCallback callback);
+
+ private static native int sqlite3_backup_finish(@NotNull long ptrToBackup);
+
+ public static int sqlite3_backup_finish(@NotNull sqlite3_backup b){
+ return null==b ? 0 : sqlite3_backup_finish(b.clearNativePointer());
+ }
+
+ private static native sqlite3_backup sqlite3_backup_init(
+ @NotNull long ptrToDbDest, @NotNull String destTableName,
+ @NotNull long ptrToDbSrc, @NotNull String srcTableName
+ );
+
+ public static sqlite3_backup sqlite3_backup_init(
+ @NotNull sqlite3 dbDest, @NotNull String destTableName,
+ @NotNull sqlite3 dbSrc, @NotNull String srcTableName
+ ){
+ return sqlite3_backup_init( dbDest.getNativePointer(), destTableName,
+ dbSrc.getNativePointer(), srcTableName );
+ }
+
+ private static native int sqlite3_backup_pagecount(@NotNull long ptrToBackup);
+
+ public static int sqlite3_backup_pagecount(@NotNull sqlite3_backup b){
+ return sqlite3_backup_pagecount(b.getNativePointer());
+ }
+
+ private static native int sqlite3_backup_remaining(@NotNull long ptrToBackup);
+
+ public static int sqlite3_backup_remaining(@NotNull sqlite3_backup b){
+ return sqlite3_backup_remaining(b.getNativePointer());
+ }
+
+ private static native int sqlite3_backup_step(@NotNull long ptrToBackup, int nPage);
+
+ public static int sqlite3_backup_step(@NotNull sqlite3_backup b, int nPage){
+ return sqlite3_backup_step(b.getNativePointer(), nPage);
+ }
+
+ private static native int sqlite3_bind_blob(
+ @NotNull long ptrToStmt, int ndx, @Nullable byte[] data, int n
+ );
+
+ /**
+ If n is negative, SQLITE_MISUSE is returned. If n>data.length
+ then n is silently truncated to data.length.
+ */
+ public static int sqlite3_bind_blob(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n
+ ){
+ return sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, n);
+ }
+
+ public static int sqlite3_bind_blob(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data
+ ){
+ return (null==data)
+ ? sqlite3_bind_null(stmt.getNativePointer(), ndx)
+ : sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, data.length);
+ }
+
+ /**
+ Convenience overload which is a simple proxy for
+ sqlite3_bind_nio_buffer().
+ */
+ @Experimental
+ /*public*/ static int sqlite3_bind_blob(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data,
+ int begin, int n
+ ){
+ return sqlite3_bind_nio_buffer(stmt, ndx, data, begin, n);
+ }
+
+ /**
+ Convenience overload which is equivalant to passing its arguments
+ to sqlite3_bind_nio_buffer() with the values 0 and -1 for the
+ final two arguments.
+ */
+ @Experimental
+ /*public*/ static int sqlite3_bind_blob(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data
+ ){
+ return sqlite3_bind_nio_buffer(stmt, ndx, data, 0, -1);
+ }
+
+ private static native int sqlite3_bind_double(
+ @NotNull long ptrToStmt, int ndx, double v
+ );
+
+ public static int sqlite3_bind_double(
+ @NotNull sqlite3_stmt stmt, int ndx, double v
+ ){
+ return sqlite3_bind_double(stmt.getNativePointer(), ndx, v);
+ }
+
+ private static native int sqlite3_bind_int(
+ @NotNull long ptrToStmt, int ndx, int v
+ );
+
+ public static int sqlite3_bind_int(
+ @NotNull sqlite3_stmt stmt, int ndx, int v
+ ){
+ return sqlite3_bind_int(stmt.getNativePointer(), ndx, v);
+ }
+
+ private static native int sqlite3_bind_int64(
+ @NotNull long ptrToStmt, int ndx, long v
+ );
+
+ public static int sqlite3_bind_int64(@NotNull sqlite3_stmt stmt, int ndx, long v){
+ return sqlite3_bind_int64( stmt.getNativePointer(), ndx, v );
+ }
+
+ private static native int sqlite3_bind_java_object(
+ @NotNull long ptrToStmt, int ndx, @Nullable Object o
+ );
+
+ /**
+ Binds the contents of the given buffer object as a blob.
+
+ The byte range of the buffer may be restricted by providing a
+ start index and a number of bytes. beginPos may not be negative.
+ Negative howMany is interpretated as the remainder of the buffer
+ past the given start position, up to the buffer's limit() (as
+ opposed its capacity()).
+
+ If beginPos+howMany would extend past the limit() of the buffer
+ then SQLITE_ERROR is returned.
+
+ If any of the following are true, this function behaves like
+ sqlite3_bind_null(): the buffer is null, beginPos is at or past
+ the end of the buffer, howMany is 0, or the calculated slice of
+ the blob has a length of 0.
+
+ If ndx is out of range, it returns SQLITE_RANGE, as documented
+ for sqlite3_bind_blob(). If beginPos is negative or if
+ sqlite3_jni_supports_nio() returns false then SQLITE_MISUSE is
+ returned. Note that this function is bound (as it were) by the
+ SQLITE_LIMIT_LENGTH constraint and SQLITE_TOOBIG is returned if
+ the resulting slice of the buffer exceeds that limit.
+
+ This function does not modify the buffer's streaming-related
+ cursors.
+
+ If the buffer is modified in a separate thread while this
+ operation is running, results are undefined and will likely
+ result in corruption of the bound data or a segmentation fault.
+
+ Design note: this function should arguably take a java.nio.Buffer
+ instead of ByteBuffer, but it can only operate on "direct"
+ buffers and the only such class offered by Java is (apparently)
+ ByteBuffer.
+
+ @see https://docs.oracle.com/javase/8/docs/api/java/nio/Buffer.html
+ */
+ @Experimental
+ /*public*/ static native int sqlite3_bind_nio_buffer(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data,
+ int beginPos, int howMany
+ );
+
+ /**
+ Convenience overload which binds the given buffer's entire
+ contents, up to its limit() (as opposed to its capacity()).
+ */
+ @Experimental
+ /*public*/ static int sqlite3_bind_nio_buffer(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data
+ ){
+ return sqlite3_bind_nio_buffer(stmt, ndx, data, 0, -1);
+ }
+
+ /**
+ Binds the given object at the given index. If o is null then this behaves like
+ sqlite3_bind_null().
+
+ @see #sqlite3_result_java_object
+ */
+ public static int sqlite3_bind_java_object(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable Object o
+ ){
+ return sqlite3_bind_java_object(stmt.getNativePointer(), ndx, o);
+ }
+
+ private static native int sqlite3_bind_null(@NotNull long ptrToStmt, int ndx);
+
+ public static int sqlite3_bind_null(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_bind_null(stmt.getNativePointer(), ndx);
+ }
+
+ private static native int sqlite3_bind_parameter_count(@NotNull long ptrToStmt);
+
+ public static int sqlite3_bind_parameter_count(@NotNull sqlite3_stmt stmt){
+ return sqlite3_bind_parameter_count(stmt.getNativePointer());
+ }
+
+ /**
+ Requires that paramName be a NUL-terminated UTF-8 string.
+
+ This overload is private because: (A) to keep users from
+ inadvertently passing non-NUL-terminated byte arrays (an easy
+ thing to do). (B) it is cheaper to NUL-terminate the
+ String-to-byte-array conversion in the public-facing Java-side
+ overload than to do that in C, so that signature is the
+ public-facing one.
+ */
+ private static native int sqlite3_bind_parameter_index(
+ @NotNull long ptrToStmt, @NotNull byte[] paramName
+ );
+
+ public static int sqlite3_bind_parameter_index(
+ @NotNull sqlite3_stmt stmt, @NotNull String paramName
+ ){
+ final byte[] utf8 = nulTerminateUtf8(paramName);
+ return null==utf8 ? 0 : sqlite3_bind_parameter_index(stmt.getNativePointer(), utf8);
+ }
+
+ private static native String sqlite3_bind_parameter_name(
+ @NotNull long ptrToStmt, int index
+ );
+
+ public static String sqlite3_bind_parameter_name(@NotNull sqlite3_stmt stmt, int index){
+ return sqlite3_bind_parameter_name(stmt.getNativePointer(), index);
+ }
+
+ private static native int sqlite3_bind_text(
+ @NotNull long ptrToStmt, int ndx, @Nullable byte[] utf8, int maxBytes
+ );
+
+ /**
+ Works like the C-level sqlite3_bind_text() but assumes
+ SQLITE_TRANSIENT for the final C API parameter. The byte array is
+ assumed to be in UTF-8 encoding.
+
+ <p>If data is not null and maxBytes>utf8.length then maxBytes is
+ silently truncated to utf8.length. If maxBytes is negative then
+ results are undefined if data is not null and does not contain a
+ NUL byte.
+ */
+ static int sqlite3_bind_text(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] utf8, int maxBytes
+ ){
+ return sqlite3_bind_text(stmt.getNativePointer(), ndx, utf8, maxBytes);
+ }
+
+ /**
+ Converts data, if not null, to a UTF-8-encoded byte array and
+ binds it as such, returning the result of the C-level
+ sqlite3_bind_null() or sqlite3_bind_text().
+ */
+ public static int sqlite3_bind_text(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable String data
+ ){
+ if( null==data ) return sqlite3_bind_null(stmt.getNativePointer(), ndx);
+ final byte[] utf8 = data.getBytes(StandardCharsets.UTF_8);
+ return sqlite3_bind_text(stmt.getNativePointer(), ndx, utf8, utf8.length);
+ }
+
+ /**
+ Requires that utf8 be null or in UTF-8 encoding.
+ */
+ public static int sqlite3_bind_text(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] utf8
+ ){
+ return ( null==utf8 )
+ ? sqlite3_bind_null(stmt.getNativePointer(), ndx)
+ : sqlite3_bind_text(stmt.getNativePointer(), ndx, utf8, utf8.length);
+ }
+
+ private static native int sqlite3_bind_text16(
+ @NotNull long ptrToStmt, int ndx, @Nullable byte[] data, int maxBytes
+ );
+
+ /**
+ Identical to the sqlite3_bind_text() overload with the same
+ signature but requires that its input be encoded in UTF-16 in
+ platform byte order.
+ */
+ static int sqlite3_bind_text16(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes
+ ){
+ return sqlite3_bind_text16(stmt.getNativePointer(), ndx, data, maxBytes);
+ }
+
+ /**
+ Converts its string argument to UTF-16 and binds it as such, returning
+ the result of the C-side function of the same name. The 3rd argument
+ may be null.
+ */
+ public static int sqlite3_bind_text16(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable String data
+ ){
+ if(null == data) return sqlite3_bind_null(stmt, ndx);
+ final byte[] bytes = data.getBytes(StandardCharsets.UTF_16);
+ return sqlite3_bind_text16(stmt.getNativePointer(), ndx, bytes, bytes.length);
+ }
+
+ /**
+ Requires that data be null or in UTF-16 encoding in platform byte
+ order. Returns the result of the C-level sqlite3_bind_null() or
+ sqlite3_bind_text16().
+ */
+ public static int sqlite3_bind_text16(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data
+ ){
+ return (null == data)
+ ? sqlite3_bind_null(stmt.getNativePointer(), ndx)
+ : sqlite3_bind_text16(stmt.getNativePointer(), ndx, data, data.length);
+ }
+
+ private static native int sqlite3_bind_value(@NotNull long ptrToStmt, int ndx, long ptrToValue);
+
+ /**
+ Functions like the C-level sqlite3_bind_value(), or
+ sqlite3_bind_null() if val is null.
+ */
+ public static int sqlite3_bind_value(@NotNull sqlite3_stmt stmt, int ndx, sqlite3_value val){
+ return sqlite3_bind_value(stmt.getNativePointer(), ndx,
+ null==val ? 0L : val.getNativePointer());
+ }
+
+ private static native int sqlite3_bind_zeroblob(@NotNull long ptrToStmt, int ndx, int n);
+
+ public static int sqlite3_bind_zeroblob(@NotNull sqlite3_stmt stmt, int ndx, int n){
+ return sqlite3_bind_zeroblob(stmt.getNativePointer(), ndx, n);
+ }
+
+ private static native int sqlite3_bind_zeroblob64(
+ @NotNull long ptrToStmt, int ndx, long n
+ );
+
+ public static int sqlite3_bind_zeroblob64(@NotNull sqlite3_stmt stmt, int ndx, long n){
+ return sqlite3_bind_zeroblob64(stmt.getNativePointer(), ndx, n);
+ }
+
+ private static native int sqlite3_blob_bytes(@NotNull long ptrToBlob);
+
+ public static int sqlite3_blob_bytes(@NotNull sqlite3_blob blob){
+ return sqlite3_blob_bytes(blob.getNativePointer());
+ }
+
+ private static native int sqlite3_blob_close(@Nullable long ptrToBlob);
+
+ public static int sqlite3_blob_close(@Nullable sqlite3_blob blob){
+ return null==blob ? 0 : sqlite3_blob_close(blob.clearNativePointer());
+ }
+
+ private static native int sqlite3_blob_open(
+ @NotNull long ptrToDb, @NotNull String dbName,
+ @NotNull String tableName, @NotNull String columnName,
+ long iRow, int flags, @NotNull OutputPointer.sqlite3_blob out
+ );
+
+ public static int sqlite3_blob_open(
+ @NotNull sqlite3 db, @NotNull String dbName,
+ @NotNull String tableName, @NotNull String columnName,
+ long iRow, int flags, @NotNull OutputPointer.sqlite3_blob out
+ ){
+ return sqlite3_blob_open(db.getNativePointer(), dbName, tableName,
+ columnName, iRow, flags, out);
+ }
+
+ /**
+ Convenience overload.
+ */
+ public static sqlite3_blob sqlite3_blob_open(
+ @NotNull sqlite3 db, @NotNull String dbName,
+ @NotNull String tableName, @NotNull String columnName,
+ long iRow, int flags ){
+ final OutputPointer.sqlite3_blob out = new OutputPointer.sqlite3_blob();
+ sqlite3_blob_open(db.getNativePointer(), dbName, tableName, columnName,
+ iRow, flags, out);
+ return out.take();
+ };
+
+ private static native int sqlite3_blob_read(
+ @NotNull long ptrToBlob, @NotNull byte[] target, int srcOffset
+ );
+
+ /**
+ As per C's sqlite3_blob_read(), but writes its output to the
+ given byte array. Note that the final argument is the offset of
+ the source buffer, not the target array.
+ */
+ public static int sqlite3_blob_read(
+ @NotNull sqlite3_blob src, @NotNull byte[] target, int srcOffset
+ ){
+ return sqlite3_blob_read(src.getNativePointer(), target, srcOffset);
+ }
+
+ /**
+ An internal level of indirection.
+ */
+ @Experimental
+ private static native int sqlite3_blob_read_nio_buffer(
+ @NotNull long ptrToBlob, int srcOffset,
+ @NotNull java.nio.ByteBuffer tgt, int tgtOffset, int howMany
+ );
+
+ /**
+ Reads howMany bytes from offset srcOffset of src into position
+ tgtOffset of tgt.
+
+ Returns SQLITE_MISUSE if src is null, tgt is null, or
+ sqlite3_jni_supports_nio() returns false. Returns SQLITE_ERROR if
+ howMany or either offset are negative. If argument validation
+ succeeds, it returns the result of the underlying call to
+ sqlite3_blob_read() (0 on success).
+ */
+ @Experimental
+ /*public*/ static int sqlite3_blob_read_nio_buffer(
+ @NotNull sqlite3_blob src, int srcOffset,
+ @NotNull java.nio.ByteBuffer tgt, int tgtOffset, int howMany
+ ){
+ return (JNI_SUPPORTS_NIO && src!=null && tgt!=null)
+ ? sqlite3_blob_read_nio_buffer(
+ src.getNativePointer(), srcOffset, tgt, tgtOffset, howMany
+ )
+ : SQLITE_MISUSE;
+ }
+
+ /**
+ Convenience overload which reads howMany bytes from position
+ srcOffset of src and returns the result as a new ByteBuffer.
+
+ srcOffset may not be negative. If howMany is negative, it is
+ treated as all bytes following srcOffset.
+
+ Returns null if sqlite3_jni_supports_nio(), any arguments are
+ invalid, if the number of bytes to read is 0 or is larger than
+ the src blob, or the underlying call to sqlite3_blob_read() fails
+ for any reason.
+ */
+ @Experimental
+ /*public*/ static java.nio.ByteBuffer sqlite3_blob_read_nio_buffer(
+ @NotNull sqlite3_blob src, int srcOffset, int howMany
+ ){
+ if( !JNI_SUPPORTS_NIO || src==null ) return null;
+ else if( srcOffset<0 ) return null;
+ final int nB = sqlite3_blob_bytes(src);
+ if( srcOffset>=nB ) return null;
+ else if( howMany<0 ) howMany = nB - srcOffset;
+ if( srcOffset + howMany > nB ) return null;
+ final java.nio.ByteBuffer tgt =
+ java.nio.ByteBuffer.allocateDirect(howMany);
+ final int rc = sqlite3_blob_read_nio_buffer(
+ src.getNativePointer(), srcOffset, tgt, 0, howMany
+ );
+ return 0==rc ? tgt : null;
+ }
+
+ /**
+ Overload alias for sqlite3_blob_read_nio_buffer().
+ */
+ @Experimental
+ /*public*/ static int sqlite3_blob_read(
+ @NotNull sqlite3_blob src, int srcOffset,
+ @NotNull java.nio.ByteBuffer tgt,
+ int tgtOffset, int howMany
+ ){
+ return sqlite3_blob_read_nio_buffer(
+ src, srcOffset, tgt, tgtOffset, howMany
+ );
+ }
+
+ /**
+ Convenience overload which uses 0 for both src and tgt offsets
+ and reads a number of bytes equal to the smaller of
+ sqlite3_blob_bytes(src) and tgt.limit().
+
+ On success it sets tgt.limit() to the number of bytes read. On
+ error, tgt.limit() is not modified.
+
+ Returns 0 on success. Returns SQLITE_MISUSE is either argument is
+ null or sqlite3_jni_supports_nio() returns false. Else it returns
+ the result of the underlying call to sqlite3_blob_read().
+ */
+ @Experimental
+ /*public*/ static int sqlite3_blob_read(
+ @NotNull sqlite3_blob src,
+ @NotNull java.nio.ByteBuffer tgt
+ ){
+ if(!JNI_SUPPORTS_NIO || src==null || tgt==null) return SQLITE_MISUSE;
+ final int nSrc = sqlite3_blob_bytes(src);
+ final int nTgt = tgt.limit();
+ final int nRead = nTgt<nSrc ? nTgt : nSrc;
+ final int rc = sqlite3_blob_read_nio_buffer(
+ src.getNativePointer(), 0, tgt, 0, nRead
+ );
+ if( 0==rc && nTgt!=nRead ) tgt.limit( nRead );
+ return rc;
+ }
+
+ private static native int sqlite3_blob_reopen(
+ @NotNull long ptrToBlob, long newRowId
+ );
+
+ public static int sqlite3_blob_reopen(@NotNull sqlite3_blob b, long newRowId){
+ return sqlite3_blob_reopen(b.getNativePointer(), newRowId);
+ }
+
+ private static native int sqlite3_blob_write(
+ @NotNull long ptrToBlob, @NotNull byte[] bytes, int iOffset
+ );
+
+ public static int sqlite3_blob_write(
+ @NotNull sqlite3_blob b, @NotNull byte[] bytes, int iOffset
+ ){
+ return sqlite3_blob_write(b.getNativePointer(), bytes, iOffset);
+ }
+
+ /**
+ An internal level of indirection.
+ */
+ @Experimental
+ private static native int sqlite3_blob_write_nio_buffer(
+ @NotNull long ptrToBlob, int tgtOffset,
+ @NotNull java.nio.ByteBuffer src,
+ int srcOffset, int howMany
+ );
+
+ /**
+ Writes howMany bytes of memory from offset srcOffset of the src
+ buffer at position tgtOffset of b.
+
+ If howMany is negative then it's equivalent to the number of
+ bytes remaining starting at srcOffset.
+
+ Returns SQLITE_MISUSE if tgt is null or sqlite3_jni_supports_nio()
+ returns false.
+
+ Returns SQLITE_MISUSE if src is null or
+ sqlite3_jni_supports_nio() returns false. Returns SQLITE_ERROR if
+ either offset is negative. If argument validation succeeds, it
+ returns the result of the underlying call to sqlite3_blob_read().
+ */
+ @Experimental
+ /*public*/ static int sqlite3_blob_write_nio_buffer(
+ @NotNull sqlite3_blob tgt, int tgtOffset,
+ @NotNull java.nio.ByteBuffer src,
+ int srcOffset, int howMany
+ ){
+ return sqlite3_blob_write_nio_buffer(
+ tgt.getNativePointer(), tgtOffset, src, srcOffset, howMany
+ );
+ }
+
+ /**
+ Overload alias for sqlite3_blob_write_nio_buffer().
+ */
+ @Experimental
+ public static int sqlite3_blob_write(
+ @NotNull sqlite3_blob tgt, int tgtOffset,
+ @NotNull java.nio.ByteBuffer src,
+ int srcOffset, int howMany
+ ){
+ return sqlite3_blob_write_nio_buffer(
+ tgt.getNativePointer(), tgtOffset, src, srcOffset, howMany
+ );
+ }
+
+ /**
+ Convenience overload which writes all of src to the given offset
+ of b.
+ */
+ @Experimental
+ /*public*/ static int sqlite3_blob_write(
+ @NotNull sqlite3_blob tgt, int tgtOffset,
+ @NotNull java.nio.ByteBuffer src
+ ){
+ return sqlite3_blob_write_nio_buffer(
+ tgt.getNativePointer(), tgtOffset, src, 0, -1
+ );
+ }
+
+ /**
+ Convenience overload which writes all of src to offset 0
+ of tgt.
+ */
+ @Experimental
+ /*public*/ static int sqlite3_blob_write(
+ @NotNull sqlite3_blob tgt,
+ @NotNull java.nio.ByteBuffer src
+ ){
+ return sqlite3_blob_write_nio_buffer(
+ tgt.getNativePointer(), 0, src, 0, -1
+ );
+ }
+
+ private static native int sqlite3_busy_handler(
+ @NotNull long ptrToDb, @Nullable BusyHandlerCallback handler
+ );
+
+ /**
+ As for the C-level function of the same name, with a
+ BusyHandlerCallback instance in place of a callback
+ function. Pass it a null handler to clear the busy handler.
+ */
+ public static int sqlite3_busy_handler(
+ @NotNull sqlite3 db, @Nullable BusyHandlerCallback handler
+ ){
+ return sqlite3_busy_handler(db.getNativePointer(), handler);
+ }
+
+ private static native int sqlite3_busy_timeout(@NotNull long ptrToDb, int ms);
+
+ public static int sqlite3_busy_timeout(@NotNull sqlite3 db, int ms){
+ return sqlite3_busy_timeout(db.getNativePointer(), ms);
+ }
+
+ public static native boolean sqlite3_cancel_auto_extension(
+ @NotNull AutoExtensionCallback ax
+ );
+
+ private static native int sqlite3_changes(@NotNull long ptrToDb);
+
+ public static int sqlite3_changes(@NotNull sqlite3 db){
+ return sqlite3_changes(db.getNativePointer());
+ }
+
+ private static native long sqlite3_changes64(@NotNull long ptrToDb);
+
+ public static long sqlite3_changes64(@NotNull sqlite3 db){
+ return sqlite3_changes64(db.getNativePointer());
+ }
+
+ private static native int sqlite3_clear_bindings(@NotNull long ptrToStmt);
+
+ public static int sqlite3_clear_bindings(@NotNull sqlite3_stmt stmt){
+ return sqlite3_clear_bindings(stmt.getNativePointer());
+ }
+
+ private static native int sqlite3_close(@Nullable long ptrToDb);
+
+ public static int sqlite3_close(@Nullable sqlite3 db){
+ return null==db ? 0 : sqlite3_close(db.clearNativePointer());
+ }
+
+ private static native int sqlite3_close_v2(@Nullable long ptrToDb);
+
+ public static int sqlite3_close_v2(@Nullable sqlite3 db){
+ return null==db ? 0 : sqlite3_close_v2(db.clearNativePointer());
+ }
+
+ public static native byte[] sqlite3_column_blob(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ private static native int sqlite3_column_bytes(@NotNull long ptrToStmt, int ndx);
+
+ public static int sqlite3_column_bytes(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_bytes(stmt.getNativePointer(), ndx);
+ }
+
+ private static native int sqlite3_column_bytes16(@NotNull long ptrToStmt, int ndx);
+
+ public static int sqlite3_column_bytes16(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_bytes16(stmt.getNativePointer(), ndx);
+ }
+
+ private static native int sqlite3_column_count(@NotNull long ptrToStmt);
+
+ public static int sqlite3_column_count(@NotNull sqlite3_stmt stmt){
+ return sqlite3_column_count(stmt.getNativePointer());
+ }
+
+ private static native String sqlite3_column_database_name(@NotNull long ptrToStmt, int ndx);
+
+ /**
+ Only available if built with SQLITE_ENABLE_COLUMN_METADATA.
+ */
+ public static String sqlite3_column_database_name(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_database_name(stmt.getNativePointer(), ndx);
+ }
+
+ private static native String sqlite3_column_decltype(@NotNull long ptrToStmt, int ndx);
+
+ public static String sqlite3_column_decltype(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_decltype(stmt.getNativePointer(), ndx);
+ }
+
+ public static native double sqlite3_column_double(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ public static native int sqlite3_column_int(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ public static native long sqlite3_column_int64(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ private static native Object sqlite3_column_java_object(
+ @NotNull long ptrToStmt, int ndx
+ );
+
+ /**
+ If the given result column was bound with
+ sqlite3_bind_java_object() or sqlite3_result_java_object() then
+ that object is returned, else null is returned. This routine
+ requires locking the owning database's mutex briefly in order to
+ extract the object in a thread-safe way.
+ */
+ public static Object sqlite3_column_java_object(
+ @NotNull sqlite3_stmt stmt, int ndx
+ ){
+ return sqlite3_column_java_object( stmt.getNativePointer(), ndx );
+ }
+
+ /**
+ If the two-parameter overload of sqlite3_column_java_object()
+ returns non-null and the returned value is an instance of T then
+ that object is returned, else null is returned.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T sqlite3_column_java_object(
+ @NotNull sqlite3_stmt stmt, int ndx, @NotNull Class<T> type
+ ){
+ final Object o = sqlite3_column_java_object(stmt, ndx);
+ return type.isInstance(o) ? (T)o : null;
+ }
+
+ private static native String sqlite3_column_name(@NotNull long ptrToStmt, int ndx);
+
+ public static String sqlite3_column_name(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_name(stmt.getNativePointer(), ndx);
+ }
+
+ /**
+ A variant of sqlite3_column_blob() which returns the blob as a
+ ByteBuffer object. Returns null if its argument is null, if
+ sqlite3_jni_supports_nio() is false, or if sqlite3_column_blob()
+ would return null for the same inputs.
+ */
+ @Experimental
+ /*public*/ static native java.nio.ByteBuffer sqlite3_column_nio_buffer(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ private static native String sqlite3_column_origin_name(@NotNull long ptrToStmt, int ndx);
+
+ /**
+ Only available if built with SQLITE_ENABLE_COLUMN_METADATA.
+ */
+ public static String sqlite3_column_origin_name(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_origin_name(stmt.getNativePointer(), ndx);
+ }
+
+ private static native String sqlite3_column_table_name(@NotNull long ptrToStmt, int ndx);
+
+ /**
+ Only available if built with SQLITE_ENABLE_COLUMN_METADATA.
+ */
+ public static String sqlite3_column_table_name(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_table_name(stmt.getNativePointer(), ndx);
+ }
+
+ /**
+ Functions identially to the C API, and this note is just to
+ stress that the returned bytes are encoded as UTF-8. It returns
+ null if the underlying C-level sqlite3_column_text() returns NULL
+ or on allocation error.
+
+ @see #sqlite3_column_text16(sqlite3_stmt,int)
+ */
+ public static native byte[] sqlite3_column_text(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ public static native String sqlite3_column_text16(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ // The real utility of this function is questionable.
+ // /**
+ // Returns a Java value representation based on the value of
+ // sqlite_value_type(). For integer types it returns either Integer
+ // or Long, depending on whether the value will fit in an
+ // Integer. For floating-point values it always returns type Double.
+
+ // If the column was bound using sqlite3_result_java_object() then
+ // that value, as an Object, is returned.
+ // */
+ // public static Object sqlite3_column_to_java(@NotNull sqlite3_stmt stmt,
+ // int ndx){
+ // sqlite3_value v = sqlite3_column_value(stmt, ndx);
+ // Object rv = null;
+ // if(null == v) return v;
+ // v = sqlite3_value_dup(v)/*need a protected value*/;
+ // if(null == v) return v /* OOM error in C */;
+ // if(112/* 'p' */ == sqlite3_value_subtype(v)){
+ // rv = sqlite3_value_java_object(v);
+ // }else{
+ // switch(sqlite3_value_type(v)){
+ // case SQLITE_INTEGER: {
+ // final long i = sqlite3_value_int64(v);
+ // rv = (i<=0x7fffffff && i>=-0x7fffffff-1)
+ // ? new Integer((int)i) : new Long(i);
+ // break;
+ // }
+ // case SQLITE_FLOAT: rv = new Double(sqlite3_value_double(v)); break;
+ // case SQLITE_BLOB: rv = sqlite3_value_blob(v); break;
+ // case SQLITE_TEXT: rv = sqlite3_value_text16(v); break;
+ // default: break;
+ // }
+ // }
+ // sqlite3_value_free(v);
+ // return rv;
+ // }
+
+ private static native int sqlite3_column_type(@NotNull long ptrToStmt, int ndx);
+
+ public static int sqlite3_column_type(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_type(stmt.getNativePointer(), ndx);
+ }
+
+ public static native sqlite3_value sqlite3_column_value(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ private static native int sqlite3_collation_needed(
+ @NotNull long ptrToDb, @Nullable CollationNeededCallback callback
+ );
+
+ /**
+ This functions like C's sqlite3_collation_needed16() because
+ Java's string type is inherently compatible with that interface.
+ */
+ public static int sqlite3_collation_needed(
+ @NotNull sqlite3 db, @Nullable CollationNeededCallback callback
+ ){
+ return sqlite3_collation_needed(db.getNativePointer(), callback);
+ }
+
+ private static native CommitHookCallback sqlite3_commit_hook(
+ @NotNull long ptrToDb, @Nullable CommitHookCallback hook
+ );
+
+ public static CommitHookCallback sqlite3_commit_hook(
+ @NotNull sqlite3 db, @Nullable CommitHookCallback hook
+ ){
+ return sqlite3_commit_hook(db.getNativePointer(), hook);
+ }
+
+ public static native String sqlite3_compileoption_get(int n);
+
+ public static native boolean sqlite3_compileoption_used(String optName);
+
+ /**
+ This implementation is private because it's too easy to pass it
+ non-NUL-terminated byte arrays from client code.
+ */
+ private static native int sqlite3_complete(
+ @NotNull byte[] nulTerminatedUtf8Sql
+ );
+
+ /**
+ Unlike the C API, this returns SQLITE_MISUSE if its argument is
+ null (as opposed to invoking UB).
+ */
+ public static int sqlite3_complete(@NotNull String sql){
+ return sqlite3_complete( nulTerminateUtf8(sql) );
+ }
+
+ /**
+ Internal level of indirection for sqlite3_config(int).
+ */
+ private static native int sqlite3_config__enable(int op);
+
+ /**
+ Internal level of indirection for sqlite3_config(ConfigLogCallback).
+ */
+ private static native int sqlite3_config__CONFIG_LOG(
+ @Nullable ConfigLogCallback logger
+ );
+
+ /**
+ Internal level of indirection for sqlite3_config(ConfigSqlLogCallback).
+ */
+ private static native int sqlite3_config__SQLLOG(
+ @Nullable ConfigSqlLogCallback logger
+ );
+
+ /**
+ <p>Works like in the C API with the exception that it only supports
+ the following subset of configution flags:
+
+ <p>SQLITE_CONFIG_SINGLETHREAD
+ SQLITE_CONFIG_MULTITHREAD
+ SQLITE_CONFIG_SERIALIZED
+
+ <p>Others may be added in the future. It returns SQLITE_MISUSE if
+ given an argument it does not handle.
+
+ <p>Note that sqlite3_config() is not threadsafe with regards to
+ the rest of the library. This must not be called when any other
+ library APIs are being called.
+ */
+ public static int sqlite3_config(int op){
+ return sqlite3_config__enable(op);
+ }
+
+ /**
+ If the native library was built with SQLITE_ENABLE_SQLLOG defined
+ then this acts as a proxy for C's
+ sqlite3_config(SQLITE_CONFIG_SQLLOG,...). This sets or clears the
+ logger. If installation of a logger fails, any previous logger is
+ retained.
+
+ <p>If not built with SQLITE_ENABLE_SQLLOG defined, this returns
+ SQLITE_MISUSE.
+
+ <p>Note that sqlite3_config() is not threadsafe with regards to
+ the rest of the library. This must not be called when any other
+ library APIs are being called.
+ */
+ public static int sqlite3_config( @Nullable ConfigSqlLogCallback logger ){
+ return sqlite3_config__SQLLOG(logger);
+ }
+
+ /**
+ The sqlite3_config() overload for handling the SQLITE_CONFIG_LOG
+ option.
+ */
+ public static int sqlite3_config( @Nullable ConfigLogCallback logger ){
+ return sqlite3_config__CONFIG_LOG(logger);
+ }
+
+ /**
+ Unlike the C API, this returns null if its argument is
+ null (as opposed to invoking UB).
+ */
+ public static native sqlite3 sqlite3_context_db_handle(
+ @NotNull sqlite3_context cx
+ );
+
+ public static native int sqlite3_create_collation(
+ @NotNull sqlite3 db, @NotNull String name, int eTextRep,
+ @NotNull CollationCallback col
+ );
+
+ /**
+ The Java counterpart to the C-native sqlite3_create_function(),
+ sqlite3_create_function_v2(), and
+ sqlite3_create_window_function(). Which one it behaves like
+ depends on which methods the final argument implements. See
+ SQLFunction's subclasses (ScalarFunction, AggregateFunction<T>,
+ and WindowFunction<T>) for details.
+
+ <p>Unlike the C API, this returns SQLITE_MISUSE null if its db or
+ functionName arguments are null (as opposed to invoking UB).
+ */
+ public static native int sqlite3_create_function(
+ @NotNull sqlite3 db, @NotNull String functionName,
+ int nArg, int eTextRep, @NotNull SQLFunction func
+ );
+
+ private static native int sqlite3_data_count(@NotNull long ptrToStmt);
+
+ public static int sqlite3_data_count(@NotNull sqlite3_stmt stmt){
+ return sqlite3_data_count(stmt.getNativePointer());
+ }
+
+ /**
+ Overload for sqlite3_db_config() calls which take (int,int*)
+ variadic arguments. Returns SQLITE_MISUSE if op is not one of the
+ SQLITE_DBCONFIG_... options which uses this call form.
+
+ <p>Unlike the C API, this returns SQLITE_MISUSE if its db argument
+ is null (as opposed to invoking UB).
+ */
+ public static native int sqlite3_db_config(
+ @NotNull sqlite3 db, int op, int onOff, @Nullable OutputPointer.Int32 out
+ );
+
+ /**
+ Overload for sqlite3_db_config() calls which take a (const char*)
+ variadic argument. As of SQLite3 v3.43 the only such option is
+ SQLITE_DBCONFIG_MAINDBNAME. Returns SQLITE_MISUSE if op is not
+ SQLITE_DBCONFIG_MAINDBNAME, but that set of options may be
+ extended in future versions.
+ */
+ public static native int sqlite3_db_config(
+ @NotNull sqlite3 db, int op, @NotNull String val
+ );
+
+ private static native String sqlite3_db_name(@NotNull long ptrToDb, int ndx);
+
+ public static String sqlite3_db_name(@NotNull sqlite3 db, int ndx){
+ return null==db ? null : sqlite3_db_name(db.getNativePointer(), ndx);
+ }
+
+ public static native String sqlite3_db_filename(
+ @NotNull sqlite3 db, @NotNull String dbName
+ );
+
+ public static native sqlite3 sqlite3_db_handle(@NotNull sqlite3_stmt stmt);
+
+ public static native int sqlite3_db_readonly(@NotNull sqlite3 db, String dbName);
+
+ public static native int sqlite3_db_release_memory(sqlite3 db);
+
+ public static native int sqlite3_db_status(
+ @NotNull sqlite3 db, int op, @NotNull OutputPointer.Int32 pCurrent,
+ @NotNull OutputPointer.Int32 pHighwater, boolean reset
+ );
+
+ public static native int sqlite3_errcode(@NotNull sqlite3 db);
+
+ public static native String sqlite3_errmsg(@NotNull sqlite3 db);
+
+ private static native int sqlite3_error_offset(@NotNull long ptrToDb);
+
+ /**
+ Note that the returned byte offset values assume UTF-8-encoded
+ inputs, so won't always match character offsets in Java Strings.
+ */
+ public static int sqlite3_error_offset(@NotNull sqlite3 db){
+ return sqlite3_error_offset(db.getNativePointer());
+ }
+
+ public static native String sqlite3_errstr(int resultCode);
+
+ public static native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt);
+
+ private static native int sqlite3_extended_errcode(@NotNull long ptrToDb);
+
+ public static int sqlite3_extended_errcode(@NotNull sqlite3 db){
+ return sqlite3_extended_errcode(db.getNativePointer());
+ }
+
+ public static native int sqlite3_extended_result_codes(
+ @NotNull sqlite3 db, boolean on
+ );
+
+ private static native boolean sqlite3_get_autocommit(@NotNull long ptrToDb);
+
+ public static boolean sqlite3_get_autocommit(@NotNull sqlite3 db){
+ return sqlite3_get_autocommit(db.getNativePointer());
+ }
+
+ public static native Object sqlite3_get_auxdata(
+ @NotNull sqlite3_context cx, int n
+ );
+
+ private static native int sqlite3_finalize(long ptrToStmt);
+
+ public static int sqlite3_finalize(@NotNull sqlite3_stmt stmt){
+ return null==stmt ? 0 : sqlite3_finalize(stmt.clearNativePointer());
+ }
+
+ public static native int sqlite3_initialize();
+
+ public static native void sqlite3_interrupt(@NotNull sqlite3 db);
+
+ public static native boolean sqlite3_is_interrupted(@NotNull sqlite3 db);
+
+ public static native boolean sqlite3_keyword_check(@NotNull String word);
+
+ public static native int sqlite3_keyword_count();
+
+ public static native String sqlite3_keyword_name(int index);
+
+
+ public static native long sqlite3_last_insert_rowid(@NotNull sqlite3 db);
+
+ public static native String sqlite3_libversion();
+
+ public static native int sqlite3_libversion_number();
+
+ public static native int sqlite3_limit(@NotNull sqlite3 db, int id, int newVal);
+
+ /**
+ Only available if built with SQLITE_ENABLE_NORMALIZE. If not, it always
+ returns null.
+ */
+ public static native String sqlite3_normalized_sql(@NotNull sqlite3_stmt stmt);
+
+ /**
+ Works like its C counterpart and makes the native pointer of the
+ underling (sqlite3*) object available via
+ ppDb.getNativePointer(). That pointer is necessary for looking up
+ the JNI-side native, but clients need not pay it any
+ heed. Passing the object to sqlite3_close() or sqlite3_close_v2()
+ will clear that pointer mapping.
+
+ <p>Recall that even if opening fails, the output pointer might be
+ non-null. Any error message about the failure will be in that
+ object and it is up to the caller to sqlite3_close() that
+ db handle.
+ */
+ public static native int sqlite3_open(
+ @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb
+ );
+
+ /**
+ Convenience overload which returns its db handle directly. The returned
+ object might not have been successfully opened: use sqlite3_errcode() to
+ check whether it is in an error state.
+
+ <p>Ownership of the returned value is passed to the caller, who must eventually
+ pass it to sqlite3_close() or sqlite3_close_v2().
+ */
+ public static sqlite3 sqlite3_open(@Nullable String filename){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ sqlite3_open(filename, out);
+ return out.take();
+ };
+
+ public static native int sqlite3_open_v2(
+ @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb,
+ int flags, @Nullable String zVfs
+ );
+
+ /**
+ Has the same semantics as the sqlite3-returning sqlite3_open()
+ but uses sqlite3_open_v2() instead of sqlite3_open().
+ */
+ public static sqlite3 sqlite3_open_v2(@Nullable String filename, int flags,
+ @Nullable String zVfs){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ sqlite3_open_v2(filename, out, flags, zVfs);
+ return out.take();
+ };
+
+ /**
+ The sqlite3_prepare() family of functions require slightly
+ different signatures than their native counterparts, but (A) they
+ retain functionally equivalent semantics and (B) overloading
+ allows us to install several convenience forms.
+
+ <p>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.
+
+ <p>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.
+
+ <p>Results are undefined if maxBytes>sqlUtf8.length.
+
+ <p>This routine is private because its maxBytes value is not
+ strictly necessary in the Java interface, as sqlUtf8.length tells
+ us the information we need. Making this public would give clients
+ more ways to shoot themselves in the foot without providing any
+ real utility.
+ */
+ private static native int sqlite3_prepare(
+ @NotNull long ptrToDb, @NotNull byte[] sqlUtf8, int maxBytes,
+ @NotNull OutputPointer.sqlite3_stmt outStmt,
+ @Nullable OutputPointer.Int32 pTailOffset
+ );
+
+ /**
+ Works like the canonical sqlite3_prepare() but its "tail" output
+ argument is returned as the index offset into the given
+ UTF-8-encoded byte array at which SQL parsing stopped. The
+ semantics are otherwise identical to the C API counterpart.
+
+ <p>Several overloads provided simplified call signatures.
+ */
+ public static int sqlite3_prepare(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+ @NotNull OutputPointer.sqlite3_stmt outStmt,
+ @Nullable OutputPointer.Int32 pTailOffset
+ ){
+ return sqlite3_prepare(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+ outStmt, pTailOffset);
+ }
+
+ public static int sqlite3_prepare(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+ @NotNull OutputPointer.sqlite3_stmt outStmt
+ ){
+ return sqlite3_prepare(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+ outStmt, null);
+ }
+
+ public static int sqlite3_prepare(
+ @NotNull sqlite3 db, @NotNull String sql,
+ @NotNull OutputPointer.sqlite3_stmt outStmt
+ ){
+ final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8);
+ return sqlite3_prepare(db.getNativePointer(), utf8, utf8.length,
+ outStmt, null);
+ }
+
+ /**
+ Convenience overload which returns its statement handle directly,
+ or null on error or when reading only whitespace or
+ comments. sqlite3_errcode() can be used to determine whether
+ there was an error or the input was empty. Ownership of the
+ returned object is passed to the caller, who must eventually pass
+ it to sqlite3_finalize().
+ */
+ public static sqlite3_stmt sqlite3_prepare(
+ @NotNull sqlite3 db, @NotNull String sql
+ ){
+ final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+ sqlite3_prepare(db, sql, out);
+ return out.take();
+ }
+ /**
+ @see #sqlite3_prepare
+ */
+ private static native int sqlite3_prepare_v2(
+ @NotNull long ptrToDb, @NotNull byte[] sqlUtf8, int maxBytes,
+ @NotNull OutputPointer.sqlite3_stmt outStmt,
+ @Nullable OutputPointer.Int32 pTailOffset
+ );
+
+ /**
+ Works like the canonical sqlite3_prepare_v2() but its "tail"
+ output paramter is returned as the index offset into the given
+ byte array at which SQL parsing stopped.
+ */
+ public static int sqlite3_prepare_v2(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+ @NotNull OutputPointer.sqlite3_stmt outStmt,
+ @Nullable OutputPointer.Int32 pTailOffset
+ ){
+ return sqlite3_prepare_v2(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+ outStmt, pTailOffset);
+ }
+
+ public static int sqlite3_prepare_v2(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+ @NotNull OutputPointer.sqlite3_stmt outStmt
+ ){
+ return sqlite3_prepare_v2(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+ outStmt, null);
+ }
+
+ public static int sqlite3_prepare_v2(
+ @NotNull sqlite3 db, @NotNull String sql,
+ @NotNull OutputPointer.sqlite3_stmt outStmt
+ ){
+ final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8);
+ return sqlite3_prepare_v2(db.getNativePointer(), utf8, utf8.length,
+ outStmt, null);
+ }
+
+ /**
+ Works identically to the sqlite3_stmt-returning sqlite3_prepare()
+ but uses sqlite3_prepare_v2().
+ */
+ public static sqlite3_stmt sqlite3_prepare_v2(
+ @NotNull sqlite3 db, @NotNull String sql
+ ){
+ final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+ sqlite3_prepare_v2(db, sql, out);
+ return out.take();
+ }
+
+ /**
+ @see #sqlite3_prepare
+ */
+ private static native int sqlite3_prepare_v3(
+ @NotNull long ptrToDb, @NotNull byte[] sqlUtf8, int maxBytes,
+ int prepFlags, @NotNull OutputPointer.sqlite3_stmt outStmt,
+ @Nullable OutputPointer.Int32 pTailOffset
+ );
+
+ /**
+ Works like the canonical sqlite3_prepare_v2() but its "tail"
+ output paramter is returned as the index offset into the given
+ byte array at which SQL parsing stopped.
+ */
+ public static int sqlite3_prepare_v3(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags,
+ @NotNull OutputPointer.sqlite3_stmt outStmt,
+ @Nullable OutputPointer.Int32 pTailOffset
+ ){
+ return sqlite3_prepare_v3(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+ prepFlags, outStmt, pTailOffset);
+ }
+
+ /**
+ Convenience overload which elides the seldom-used pTailOffset
+ parameter.
+ */
+ public static int sqlite3_prepare_v3(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags,
+ @NotNull OutputPointer.sqlite3_stmt outStmt
+ ){
+ return sqlite3_prepare_v3(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+ prepFlags, outStmt, null);
+ }
+
+ /**
+ Convenience overload which elides the seldom-used pTailOffset
+ parameter and converts the given string to UTF-8 before passing
+ it on.
+ */
+ public static int sqlite3_prepare_v3(
+ @NotNull sqlite3 db, @NotNull String sql, int prepFlags,
+ @NotNull OutputPointer.sqlite3_stmt outStmt
+ ){
+ final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8);
+ return sqlite3_prepare_v3(db.getNativePointer(), utf8, utf8.length,
+ prepFlags, outStmt, null);
+ }
+
+ /**
+ Works identically to the sqlite3_stmt-returning sqlite3_prepare()
+ but uses sqlite3_prepare_v3().
+ */
+ public static sqlite3_stmt sqlite3_prepare_v3(
+ @NotNull sqlite3 db, @NotNull String sql, int prepFlags
+ ){
+ final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+ sqlite3_prepare_v3(db, sql, prepFlags, out);
+ return out.take();
+ }
+
+ /**
+ A convenience wrapper around sqlite3_prepare_v3() which accepts
+ an arbitrary amount of input provided as a UTF-8-encoded byte
+ array. It loops over the input bytes looking for
+ statements. Each one it finds is passed to p.call(), passing
+ ownership of it to that function. If p.call() returns 0, looping
+ continues, else the loop stops and p.call()'s result code is
+ returned. If preparation of any given segment fails, looping
+ stops and that result code is returned.
+
+ <p>If p.call() throws, the exception is converted to a db-level
+ error and a non-0 code is returned, in order to retain the
+ C-style error semantics of the API.
+
+ <p>How each statement is handled, including whether it is finalized
+ or not, is up to the callback object. e.g. the callback might
+ collect them for later use. If it does not collect them then it
+ must finalize them. See PrepareMultiCallback.Finalize for a
+ simple proxy which does that.
+ */
+ public static int sqlite3_prepare_multi(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+ int prepFlags,
+ @NotNull PrepareMultiCallback p){
+ final OutputPointer.Int32 oTail = new OutputPointer.Int32();
+ int pos = 0, n = 1;
+ byte[] sqlChunk = sqlUtf8;
+ int rc = 0;
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+ while( 0==rc && pos<sqlChunk.length ){
+ sqlite3_stmt stmt = null;
+ if( pos>0 ){
+ sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
+ sqlChunk.length);
+ }
+ if( 0==sqlChunk.length ) break;
+ rc = sqlite3_prepare_v3(db, sqlChunk, prepFlags, outStmt, oTail);
+ if( 0!=rc ) break;
+ pos = oTail.value;
+ stmt = outStmt.take();
+ if( null==stmt ){
+ // empty statement (whitespace/comments)
+ continue;
+ }
+ try{
+ rc = p.call(stmt);
+ }catch(Exception e){
+ rc = sqlite3_jni_db_error( db, SQLITE_ERROR, e );
+ }
+ }
+ return rc;
+ }
+
+ /**
+ Convenience overload which accepts its SQL as a String and uses
+ no statement-preparation flags.
+ */
+ public static int sqlite3_prepare_multi(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+ @NotNull PrepareMultiCallback p){
+ return sqlite3_prepare_multi(db, sqlUtf8, 0, p);
+ }
+
+ /**
+ Convenience overload which accepts its SQL as a String.
+ */
+ public static int sqlite3_prepare_multi(
+ @NotNull sqlite3 db, @NotNull String sql, int prepFlags,
+ @NotNull PrepareMultiCallback p){
+ return sqlite3_prepare_multi(
+ db, sql.getBytes(StandardCharsets.UTF_8), prepFlags, p
+ );
+ }
+
+ /**
+ Convenience overload which accepts its SQL as a String and uses
+ no statement-preparation flags.
+ */
+ public static int sqlite3_prepare_multi(
+ @NotNull sqlite3 db, @NotNull String sql,
+ @NotNull PrepareMultiCallback p){
+ return sqlite3_prepare_multi(db, sql, 0, p);
+ }
+
+ /**
+ Convenience overload which accepts its SQL as a String
+ array. They will be concatenated together as-is, with no
+ separator, and passed on to one of the other overloads.
+ */
+ public static int sqlite3_prepare_multi(
+ @NotNull sqlite3 db, @NotNull String[] sql, int prepFlags,
+ @NotNull PrepareMultiCallback p){
+ return sqlite3_prepare_multi(db, String.join("",sql), prepFlags, p);
+ }
+
+ /**
+ Convenience overload which uses no statement-preparation flags.
+ */
+ public static int sqlite3_prepare_multi(
+ @NotNull sqlite3 db, @NotNull String[] sql,
+ @NotNull PrepareMultiCallback p){
+ return sqlite3_prepare_multi(db, sql, 0, p);
+ }
+
+ private static native int sqlite3_preupdate_blobwrite(@NotNull long ptrToDb);
+
+ /**
+ If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+ acts as a proxy for C's sqlite3_preupdate_blobwrite(), else it returns
+ SQLITE_MISUSE with no side effects.
+ */
+ public static int sqlite3_preupdate_blobwrite(@NotNull sqlite3 db){
+ return sqlite3_preupdate_blobwrite(db.getNativePointer());
+ }
+
+ private static native int sqlite3_preupdate_count(@NotNull long ptrToDb);
+
+ /**
+ If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+ acts as a proxy for C's sqlite3_preupdate_count(), else it returns
+ SQLITE_MISUSE with no side effects.
+ */
+ public static int sqlite3_preupdate_count(@NotNull sqlite3 db){
+ return sqlite3_preupdate_count(db.getNativePointer());
+ }
+
+ private static native int sqlite3_preupdate_depth(@NotNull long ptrToDb);
+
+ /**
+ If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+ acts as a proxy for C's sqlite3_preupdate_depth(), else it returns
+ SQLITE_MISUSE with no side effects.
+ */
+ public static int sqlite3_preupdate_depth(@NotNull sqlite3 db){
+ return sqlite3_preupdate_depth(db.getNativePointer());
+ }
+
+ private static native PreupdateHookCallback sqlite3_preupdate_hook(
+ @NotNull long ptrToDb, @Nullable PreupdateHookCallback hook
+ );
+
+ /**
+ If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+ acts as a proxy for C's sqlite3_preupdate_hook(), else it returns null
+ with no side effects.
+ */
+ public static PreupdateHookCallback sqlite3_preupdate_hook(
+ @NotNull sqlite3 db, @Nullable PreupdateHookCallback hook
+ ){
+ return sqlite3_preupdate_hook(db.getNativePointer(), hook);
+ }
+
+ private static native int sqlite3_preupdate_new(@NotNull long ptrToDb, int col,
+ @NotNull OutputPointer.sqlite3_value out);
+
+ /**
+ If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined,
+ this acts as a proxy for C's sqlite3_preupdate_new(), else it
+ returns SQLITE_MISUSE with no side effects.
+
+ WARNING: client code _must not_ hold a reference to the returned
+ sqlite3_value object beyond the scope of the preupdate hook in
+ which this function is called. Doing so will leave the client
+ holding a stale pointer, the address of which could point to
+ anything at all after the pre-update hook is complete. This API
+ has no way to record such objects and clear/invalidate them at
+ the end of a pre-update hook. We "could" add infrastructure to do
+ so, but would require significant levels of bookkeeping.
+ */
+ public static int sqlite3_preupdate_new(@NotNull sqlite3 db, int col,
+ @NotNull OutputPointer.sqlite3_value out){
+ return sqlite3_preupdate_new(db.getNativePointer(), col, out);
+ }
+
+ /**
+ Convenience wrapper for the 3-arg sqlite3_preupdate_new() which returns
+ null on error.
+ */
+ public static sqlite3_value sqlite3_preupdate_new(@NotNull sqlite3 db, int col){
+ final OutputPointer.sqlite3_value out = new OutputPointer.sqlite3_value();
+ sqlite3_preupdate_new(db.getNativePointer(), col, out);
+ return out.take();
+ }
+
+ private static native int sqlite3_preupdate_old(@NotNull long ptrToDb, int col,
+ @NotNull OutputPointer.sqlite3_value out);
+
+ /**
+ If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined,
+ this acts as a proxy for C's sqlite3_preupdate_old(), else it
+ returns SQLITE_MISUSE with no side effects.
+
+ WARNING: see warning in sqlite3_preupdate_new() regarding the
+ potential for stale sqlite3_value handles.
+ */
+ public static int sqlite3_preupdate_old(@NotNull sqlite3 db, int col,
+ @NotNull OutputPointer.sqlite3_value out){
+ return sqlite3_preupdate_old(db.getNativePointer(), col, out);
+ }
+
+ /**
+ Convenience wrapper for the 3-arg sqlite3_preupdate_old() which returns
+ null on error.
+ */
+ public static sqlite3_value sqlite3_preupdate_old(@NotNull sqlite3 db, int col){
+ final OutputPointer.sqlite3_value out = new OutputPointer.sqlite3_value();
+ sqlite3_preupdate_old(db.getNativePointer(), col, out);
+ return out.take();
+ }
+
+ public static native void sqlite3_progress_handler(
+ @NotNull sqlite3 db, int n, @Nullable ProgressHandlerCallback h
+ );
+
+ public static native void sqlite3_randomness(byte[] target);
+
+ public static native int sqlite3_release_memory(int n);
+
+ public static native int sqlite3_reset(@NotNull sqlite3_stmt stmt);
+
+ /**
+ Works like the C API except that it has no side effects if auto
+ extensions are currently running. (The JNI-level list of
+ extensions cannot be manipulated while it is being traversed.)
+ */
+ public static native void sqlite3_reset_auto_extension();
+
+ public static native void sqlite3_result_double(
+ @NotNull sqlite3_context cx, double v
+ );
+
+ /**
+ The main sqlite3_result_error() impl of which all others are
+ proxies. eTextRep must be one of SQLITE_UTF8 or SQLITE_UTF16 and
+ msg must be encoded correspondingly. Any other eTextRep value
+ results in the C-level sqlite3_result_error() being called with a
+ complaint about the invalid argument.
+ */
+ private static native void sqlite3_result_error(
+ @NotNull sqlite3_context cx, @NotNull byte[] msg, int eTextRep
+ );
+
+ public static void sqlite3_result_error(
+ @NotNull sqlite3_context cx, @NotNull byte[] utf8
+ ){
+ sqlite3_result_error(cx, utf8, SQLITE_UTF8);
+ }
+
+ public static void sqlite3_result_error(
+ @NotNull sqlite3_context cx, @NotNull String msg
+ ){
+ final byte[] utf8 = msg.getBytes(StandardCharsets.UTF_8);
+ sqlite3_result_error(cx, utf8, SQLITE_UTF8);
+ }
+
+ public static void sqlite3_result_error16(
+ @NotNull sqlite3_context cx, @NotNull byte[] utf16
+ ){
+ sqlite3_result_error(cx, utf16, SQLITE_UTF16);
+ }
+
+ public static void sqlite3_result_error16(
+ @NotNull sqlite3_context cx, @NotNull String msg
+ ){
+ final byte[] utf16 = msg.getBytes(StandardCharsets.UTF_16);
+ sqlite3_result_error(cx, utf16, SQLITE_UTF16);
+ }
+
+ /**
+ Equivalent to passing e.toString() to {@link
+ #sqlite3_result_error(sqlite3_context,String)}. Note that
+ toString() is used instead of getMessage() because the former
+ prepends the exception type name to the message.
+ */
+ public static void sqlite3_result_error(
+ @NotNull sqlite3_context cx, @NotNull Exception e
+ ){
+ sqlite3_result_error(cx, e.toString());
+ }
+
+ public static native void sqlite3_result_error_toobig(
+ @NotNull sqlite3_context cx
+ );
+
+ public static native void sqlite3_result_error_nomem(
+ @NotNull sqlite3_context cx
+ );
+
+ public static native void sqlite3_result_error_code(
+ @NotNull sqlite3_context cx, int c
+ );
+
+ public static native void sqlite3_result_int(
+ @NotNull sqlite3_context cx, int v
+ );
+
+ public static native void sqlite3_result_int64(
+ @NotNull sqlite3_context cx, long v
+ );
+
+ /**
+ Binds the SQL result to the given object, or {@link
+ #sqlite3_result_null} if {@code o} is null. Use {@link
+ #sqlite3_value_java_object} to fetch it.
+
+ <p>This is implemented in terms of C's sqlite3_result_pointer(),
+ but that function is not exposed to JNI because (A)
+ cross-language semantic mismatch and (B) Java doesn't need that
+ argument for its intended purpose (type safety).
+
+ @see #sqlite3_value_java_object
+ @see #sqlite3_bind_java_object
+ */
+ public static native void sqlite3_result_java_object(
+ @NotNull sqlite3_context cx, @NotNull Object o
+ );
+
+ /**
+ Similar to sqlite3_bind_nio_buffer(), this works like
+ sqlite3_result_blob() but accepts a java.nio.ByteBuffer as its
+ input source. See sqlite3_bind_nio_buffer() for the semantics of
+ the second and subsequent arguments.
+
+ If cx is null then this function will silently fail. If
+ sqlite3_jni_supports_nio() returns false or iBegin is negative,
+ an error result is set. If (begin+n) extends beyond the end of
+ the buffer, it is silently truncated to fit.
+
+ If any of the following apply, this function behaves like
+ sqlite3_result_null(): the blob is null, the resulting slice of
+ the blob is empty.
+
+ If the resulting slice of the buffer exceeds SQLITE_LIMIT_LENGTH
+ then this function behaves like sqlite3_result_error_toobig().
+ */
+ @Experimental
+ /*public*/ static native void sqlite3_result_nio_buffer(
+ @NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob,
+ int begin, int n
+ );
+
+ /**
+ Convenience overload which uses the whole input object
+ as the result blob content.
+ */
+ @Experimental
+ /*public*/ static void sqlite3_result_nio_buffer(
+ @NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob
+ ){
+ sqlite3_result_nio_buffer(cx, blob, 0, -1);
+ }
+
+ public static native void sqlite3_result_null(
+ @NotNull sqlite3_context cx
+ );
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, @NotNull Boolean v
+ ){
+ sqlite3_result_int(cx, v ? 1 : 0);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, boolean v
+ ){
+ sqlite3_result_int(cx, v ? 1 : 0);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, @NotNull Double v
+ ){
+ sqlite3_result_double(cx, v);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, double v
+ ){
+ sqlite3_result_double(cx, v);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, @NotNull Integer v
+ ){
+ sqlite3_result_int(cx, v);
+ }
+
+ public static void sqlite3_result_set(@NotNull sqlite3_context cx, int v){
+ sqlite3_result_int(cx, v);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, @NotNull Long v
+ ){
+ sqlite3_result_int64(cx, v);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, long v
+ ){
+ sqlite3_result_int64(cx, v);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, @Nullable String v
+ ){
+ if( null==v ) sqlite3_result_null(cx);
+ else sqlite3_result_text(cx, v);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, @Nullable byte[] blob
+ ){
+ if( null==blob ) sqlite3_result_null(cx);
+ else sqlite3_result_blob(cx, blob, blob.length);
+ }
+
+ public static native void sqlite3_result_subtype(
+ @NotNull sqlite3_context cx, int val
+ );
+
+ public static native void sqlite3_result_value(
+ @NotNull sqlite3_context cx, @NotNull sqlite3_value v
+ );
+
+ public static native void sqlite3_result_zeroblob(
+ @NotNull sqlite3_context cx, int n
+ );
+
+ public static native int sqlite3_result_zeroblob64(
+ @NotNull sqlite3_context cx, long n
+ );
+
+ /**
+ This overload is private because its final parameter is arguably
+ unnecessary in Java.
+ */
+ private static native void sqlite3_result_blob(
+ @NotNull sqlite3_context cx, @Nullable byte[] blob, int maxLen
+ );
+
+ public static void sqlite3_result_blob(
+ @NotNull sqlite3_context cx, @Nullable byte[] blob
+ ){
+ sqlite3_result_blob(cx, blob, (int)(null==blob ? 0 : blob.length));
+ }
+
+ /**
+ Convenience overload which behaves like
+ sqlite3_result_nio_buffer().
+ */
+ @Experimental
+ /*public*/ static void sqlite3_result_blob(
+ @NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob,
+ int begin, int n
+ ){
+ sqlite3_result_nio_buffer(cx, blob, begin, n);
+ }
+
+ /**
+ Convenience overload which behaves like the two-argument overload of
+ sqlite3_result_nio_buffer().
+ */
+ @Experimental
+ /*public*/ static void sqlite3_result_blob(
+ @NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob
+ ){
+ sqlite3_result_nio_buffer(cx, blob);
+ }
+
+ /**
+ Binds the given text using C's sqlite3_result_blob64() unless:
+
+ <ul>
+
+ <li>@param blob is null: translates to sqlite3_result_null()</li>
+
+ <li>@param blob is too large: translates to
+ sqlite3_result_error_toobig()</li>
+
+ </ul>
+
+ <p>If @param maxLen is larger than blob.length, it is truncated
+ to that value. If it is negative, results are undefined.</p>
+
+ <p>This overload is private because its final parameter is
+ arguably unnecessary in Java.</p>
+ */
+ private static native void sqlite3_result_blob64(
+ @NotNull sqlite3_context cx, @Nullable byte[] blob, long maxLen
+ );
+
+ public static void sqlite3_result_blob64(
+ @NotNull sqlite3_context cx, @Nullable byte[] blob
+ ){
+ sqlite3_result_blob64(cx, blob, (long)(null==blob ? 0 : blob.length));
+ }
+
+ /**
+ This overload is private because its final parameter is
+ arguably unnecessary in Java.
+ */
+ private static native void sqlite3_result_text(
+ @NotNull sqlite3_context cx, @Nullable byte[] utf8, int maxLen
+ );
+
+ public static void sqlite3_result_text(
+ @NotNull sqlite3_context cx, @Nullable byte[] utf8
+ ){
+ sqlite3_result_text(cx, utf8, null==utf8 ? 0 : utf8.length);
+ }
+
+ public static void sqlite3_result_text(
+ @NotNull sqlite3_context cx, @Nullable String text
+ ){
+ if(null == text) sqlite3_result_null(cx);
+ else{
+ final byte[] utf8 = text.getBytes(StandardCharsets.UTF_8);
+ sqlite3_result_text(cx, utf8, utf8.length);
+ }
+ }
+
+ /**
+ Binds the given text using C's sqlite3_result_text64() unless:
+
+ <ul>
+
+ <li>text is null: translates to a call to {@link
+ #sqlite3_result_null}</li>
+
+ <li>text is too large: translates to a call to
+ {@link #sqlite3_result_error_toobig}</li>
+
+ <li>The @param encoding argument has an invalid value: translates to
+ {@link sqlite3_result_error_code} with code SQLITE_FORMAT.</li>
+
+ </ul>
+
+ If maxLength (in bytes, not characters) is larger than
+ text.length, it is silently truncated to text.length. If it is
+ negative, results are undefined. If text is null, the subsequent
+ arguments are ignored.
+
+ This overload is private because its maxLength parameter is
+ arguably unnecessary in Java.
+ */
+ private static native void sqlite3_result_text64(
+ @NotNull sqlite3_context cx, @Nullable byte[] text,
+ long maxLength, int encoding
+ );
+
+ /**
+ Sets the current UDF result to the given bytes, which are assumed
+ be encoded in UTF-16 using the platform's byte order.
+ */
+ public static void sqlite3_result_text16(
+ @NotNull sqlite3_context cx, @Nullable byte[] utf16
+ ){
+ if(null == utf16) sqlite3_result_null(cx);
+ else sqlite3_result_text64(cx, utf16, utf16.length, SQLITE_UTF16);
+ }
+
+ public static void sqlite3_result_text16(
+ @NotNull sqlite3_context cx, @Nullable String text
+ ){
+ if(null == text) sqlite3_result_null(cx);
+ else{
+ final byte[] b = text.getBytes(StandardCharsets.UTF_16);
+ sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16);
+ }
+ }
+
+ private static native RollbackHookCallback sqlite3_rollback_hook(
+ @NotNull long ptrToDb, @Nullable RollbackHookCallback hook
+ );
+
+ public static RollbackHookCallback sqlite3_rollback_hook(
+ @NotNull sqlite3 db, @Nullable RollbackHookCallback hook
+ ){
+ return sqlite3_rollback_hook(db.getNativePointer(), hook);
+ }
+
+ public static native int sqlite3_set_authorizer(
+ @NotNull sqlite3 db, @Nullable AuthorizerCallback auth
+ );
+
+ public static native void sqlite3_set_auxdata(
+ @NotNull sqlite3_context cx, int n, @Nullable Object data
+ );
+
+ public static native void sqlite3_set_last_insert_rowid(
+ @NotNull sqlite3 db, long rowid
+ );
+
+
+ /**
+ In addition to calling the C-level sqlite3_shutdown(), the JNI
+ binding also cleans up all stale per-thread state managed by the
+ library, as well as any registered auto-extensions, and frees up
+ various bits of memory. Calling this while database handles or
+ prepared statements are still active will leak resources. Trying
+ to use those objects after this routine is called invoked
+ undefined behavior.
+ */
+ public static synchronized native int sqlite3_shutdown();
+
+ public static native int sqlite3_sleep(int ms);
+
+ public static native String sqlite3_sourceid();
+
+ public static native String sqlite3_sql(@NotNull sqlite3_stmt stmt);
+
+ //! Consider removing this. We can use sqlite3_status64() instead,
+ // or use that one's impl with this one's name.
+ public static native int sqlite3_status(
+ int op, @NotNull OutputPointer.Int32 pCurrent,
+ @NotNull OutputPointer.Int32 pHighwater, boolean reset
+ );
+
+ public static native int sqlite3_status64(
+ int op, @NotNull OutputPointer.Int64 pCurrent,
+ @NotNull OutputPointer.Int64 pHighwater, boolean reset
+ );
+
+ private static native int sqlite3_step(@NotNull long ptrToStmt);
+
+ public static int sqlite3_step(@NotNull sqlite3_stmt stmt){
+ return null==stmt ? SQLITE_MISUSE : sqlite3_step(stmt.getNativePointer());
+ }
+
+ public static native boolean sqlite3_stmt_busy(@NotNull sqlite3_stmt stmt);
+
+ private static native int sqlite3_stmt_explain(@NotNull long ptrToStmt, int op);
+
+ public static int sqlite3_stmt_explain(@NotNull sqlite3_stmt stmt, int op){
+ return null==stmt ? SQLITE_MISUSE : sqlite3_stmt_explain(stmt.getNativePointer(), op);
+ }
+
+ private static native int sqlite3_stmt_isexplain(@NotNull long ptrToStmt);
+
+ public static int sqlite3_stmt_isexplain(@NotNull sqlite3_stmt stmt){
+ return null==stmt ? 0 : sqlite3_stmt_isexplain(stmt.getNativePointer());
+ }
+
+ public static native boolean sqlite3_stmt_readonly(@NotNull sqlite3_stmt stmt);
+
+ public static native int sqlite3_stmt_status(
+ @NotNull sqlite3_stmt stmt, int op, boolean reset
+ );
+
+ /**
+ Internal impl of the public sqlite3_strglob() method. Neither
+ argument may be null and both must be NUL-terminated UTF-8.
+
+ This overload is private because: (A) to keep users from
+ inadvertently passing non-NUL-terminated byte arrays (an easy
+ thing to do). (B) it is cheaper to NUL-terminate the
+ String-to-byte-array conversion in the Java implementation
+ (sqlite3_strglob(String,String)) than to do that in C, so that
+ signature is the public-facing one.
+ */
+ private static native int sqlite3_strglob(
+ @NotNull byte[] glob, @NotNull byte[] nulTerminatedUtf8
+ );
+
+ public static int sqlite3_strglob(
+ @NotNull String glob, @NotNull String txt
+ ){
+ return sqlite3_strglob(nulTerminateUtf8(glob),
+ nulTerminateUtf8(txt));
+ }
+
+ /**
+ The LIKE counterpart of the private sqlite3_strglob() method.
+ */
+ private static native int sqlite3_strlike(
+ @NotNull byte[] glob, @NotNull byte[] nulTerminatedUtf8,
+ int escChar
+ );
+
+ public static int sqlite3_strlike(
+ @NotNull String glob, @NotNull String txt, char escChar
+ ){
+ return sqlite3_strlike(nulTerminateUtf8(glob),
+ nulTerminateUtf8(txt),
+ (int)escChar);
+ }
+
+ private static native int sqlite3_system_errno(@NotNull long ptrToDb);
+
+ public static int sqlite3_system_errno(@NotNull sqlite3 db){
+ return sqlite3_system_errno(db.getNativePointer());
+ }
+
+ public static native int sqlite3_table_column_metadata(
+ @NotNull sqlite3 db, @NotNull String zDbName,
+ @NotNull String zTableName, @NotNull String zColumnName,
+ @Nullable OutputPointer.String pzDataType,
+ @Nullable OutputPointer.String pzCollSeq,
+ @Nullable OutputPointer.Bool pNotNull,
+ @Nullable OutputPointer.Bool pPrimaryKey,
+ @Nullable OutputPointer.Bool pAutoinc
+ );
+
+ /**
+ Convenience overload which returns its results via a single
+ output object. If this function returns non-0 (error), the the
+ contents of the output object are not modified.
+ */
+ public static int sqlite3_table_column_metadata(
+ @NotNull sqlite3 db, @NotNull String zDbName,
+ @NotNull String zTableName, @NotNull String zColumnName,
+ @NotNull TableColumnMetadata out){
+ return sqlite3_table_column_metadata(
+ db, zDbName, zTableName, zColumnName,
+ out.pzDataType, out.pzCollSeq, out.pNotNull,
+ out.pPrimaryKey, out.pAutoinc);
+ }
+
+ /**
+ Convenience overload which returns the column metadata object on
+ success and null on error.
+ */
+ public static TableColumnMetadata sqlite3_table_column_metadata(
+ @NotNull sqlite3 db, @NotNull String zDbName,
+ @NotNull String zTableName, @NotNull String zColumnName){
+ final TableColumnMetadata out = new TableColumnMetadata();
+ return 0==sqlite3_table_column_metadata(
+ db, zDbName, zTableName, zColumnName, out
+ ) ? out : null;
+ }
+
+ public static native int sqlite3_threadsafe();
+
+ private static native int sqlite3_total_changes(@NotNull long ptrToDb);
+
+ public static int sqlite3_total_changes(@NotNull sqlite3 db){
+ return sqlite3_total_changes(db.getNativePointer());
+ }
+
+ private static native long sqlite3_total_changes64(@NotNull long ptrToDb);
+
+ public static long sqlite3_total_changes64(@NotNull sqlite3 db){
+ return sqlite3_total_changes64(db.getNativePointer());
+ }
+
+ /**
+ Works like C's sqlite3_trace_v2() except that the 3rd argument to that
+ function is elided here because the roles of that functions' 3rd and 4th
+ arguments are encapsulated in the final argument to this function.
+
+ <p>Unlike the C API, which is documented as always returning 0,
+ this implementation returns non-0 if initialization of the tracer
+ mapping state fails (e.g. on OOM).
+ */
+ public static native int sqlite3_trace_v2(
+ @NotNull sqlite3 db, int traceMask, @Nullable TraceV2Callback tracer
+ );
+
+ public static native int sqlite3_txn_state(
+ @NotNull sqlite3 db, @Nullable String zSchema
+ );
+
+ private static native UpdateHookCallback sqlite3_update_hook(
+ @NotNull long ptrToDb, @Nullable UpdateHookCallback hook
+ );
+
+ public static UpdateHookCallback sqlite3_update_hook(
+ @NotNull sqlite3 db, @Nullable UpdateHookCallback hook
+ ){
+ return sqlite3_update_hook(db.getNativePointer(), hook);
+ }
+
+ /*
+ Note that:
+
+ void * sqlite3_user_data(sqlite3_context*)
+
+ Is not relevant in the JNI binding, as its feature is replaced by
+ the ability to pass an object, including any relevant state, to
+ sqlite3_create_function().
+ */
+
+ private static native byte[] sqlite3_value_blob(@NotNull long ptrToValue);
+
+ public static byte[] sqlite3_value_blob(@NotNull sqlite3_value v){
+ return sqlite3_value_blob(v.getNativePointer());
+ }
+
+ private static native int sqlite3_value_bytes(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_bytes(@NotNull sqlite3_value v){
+ return sqlite3_value_bytes(v.getNativePointer());
+ }
+
+ private static native int sqlite3_value_bytes16(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_bytes16(@NotNull sqlite3_value v){
+ return sqlite3_value_bytes16(v.getNativePointer());
+ }
+
+ private static native double sqlite3_value_double(@NotNull long ptrToValue);
+
+ public static double sqlite3_value_double(@NotNull sqlite3_value v){
+ return sqlite3_value_double(v.getNativePointer());
+ }
+
+ private static native sqlite3_value sqlite3_value_dup(@NotNull long ptrToValue);
+
+ public static sqlite3_value sqlite3_value_dup(@NotNull sqlite3_value v){
+ return sqlite3_value_dup(v.getNativePointer());
+ }
+
+ private static native int sqlite3_value_encoding(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_encoding(@NotNull sqlite3_value v){
+ return sqlite3_value_encoding(v.getNativePointer());
+ }
+
+ private static native void sqlite3_value_free(@Nullable long ptrToValue);
+
+ public static void sqlite3_value_free(@Nullable sqlite3_value v){
+ if( null!=v ) sqlite3_value_free(v.clearNativePointer());
+ }
+
+ private static native boolean sqlite3_value_frombind(@NotNull long ptrToValue);
+
+ public static boolean sqlite3_value_frombind(@NotNull sqlite3_value v){
+ return sqlite3_value_frombind(v.getNativePointer());
+ }
+
+ private static native int sqlite3_value_int(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_int(@NotNull sqlite3_value v){
+ return sqlite3_value_int(v.getNativePointer());
+ }
+
+ private static native long sqlite3_value_int64(@NotNull long ptrToValue);
+
+ public static long sqlite3_value_int64(@NotNull sqlite3_value v){
+ return sqlite3_value_int64(v.getNativePointer());
+ }
+
+ private static native Object sqlite3_value_java_object(@NotNull long ptrToValue);
+
+ /**
+ If the given value was set using {@link
+ #sqlite3_result_java_object} then this function returns that
+ object, else it returns null.
+
+ <p>It is up to the caller to inspect the object to determine its
+ type, and cast it if necessary.
+ */
+ public static Object sqlite3_value_java_object(@NotNull sqlite3_value v){
+ return sqlite3_value_java_object(v.getNativePointer());
+ }
+
+ /**
+ A variant of sqlite3_value_java_object() which returns the
+ fetched object cast to T if the object is an instance of the
+ given Class, else it returns null.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T sqlite3_value_java_object(@NotNull sqlite3_value v,
+ @NotNull Class<T> type){
+ final Object o = sqlite3_value_java_object(v);
+ return type.isInstance(o) ? (T)o : null;
+ }
+
+ /**
+ A variant of sqlite3_column_blob() which returns the blob as a
+ ByteBuffer object. Returns null if its argument is null, if
+ sqlite3_jni_supports_nio() is false, or if sqlite3_value_blob()
+ would return null for the same input.
+ */
+ @Experimental
+ /*public*/ static native java.nio.ByteBuffer sqlite3_value_nio_buffer(
+ @NotNull sqlite3_value v
+ );
+
+ private static native int sqlite3_value_nochange(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_nochange(@NotNull sqlite3_value v){
+ return sqlite3_value_nochange(v.getNativePointer());
+ }
+
+ private static native int sqlite3_value_numeric_type(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_numeric_type(@NotNull sqlite3_value v){
+ return sqlite3_value_numeric_type(v.getNativePointer());
+ }
+
+ private static native int sqlite3_value_subtype(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_subtype(@NotNull sqlite3_value v){
+ return sqlite3_value_subtype(v.getNativePointer());
+ }
+
+ private static native byte[] sqlite3_value_text(@NotNull long ptrToValue);
+
+ /**
+ Functions identially to the C API, and this note is just to
+ stress that the returned bytes are encoded as UTF-8. It returns
+ null if the underlying C-level sqlite3_value_text() returns NULL
+ or on allocation error.
+ */
+ public static byte[] sqlite3_value_text(@NotNull sqlite3_value v){
+ return sqlite3_value_text(v.getNativePointer());
+ }
+
+ private static native String sqlite3_value_text16(@NotNull long ptrToValue);
+
+ public static String sqlite3_value_text16(@NotNull sqlite3_value v){
+ return sqlite3_value_text16(v.getNativePointer());
+ }
+
+ private static native int sqlite3_value_type(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_type(@NotNull sqlite3_value v){
+ return sqlite3_value_type(v.getNativePointer());
+ }
+
+ /**
+ This is NOT part of the public API. It exists solely as a place
+ for this code's developers to collect internal metrics and such.
+ It has no stable interface. It may go way or change behavior at
+ any time.
+ */
+ public static native void sqlite3_jni_internal_details();
+
+ //////////////////////////////////////////////////////////////////////
+ // SQLITE_... constants follow...
+
+ // version info
+ public static final int SQLITE_VERSION_NUMBER = sqlite3_libversion_number();
+ public static final String SQLITE_VERSION = sqlite3_libversion();
+ public static final String SQLITE_SOURCE_ID = sqlite3_sourceid();
+
+ // access
+ public static final int SQLITE_ACCESS_EXISTS = 0;
+ public static final int SQLITE_ACCESS_READWRITE = 1;
+ public static final int SQLITE_ACCESS_READ = 2;
+
+ // authorizer
+ public static final int SQLITE_DENY = 1;
+ public static final int SQLITE_IGNORE = 2;
+ public static final int SQLITE_CREATE_INDEX = 1;
+ public static final int SQLITE_CREATE_TABLE = 2;
+ public static final int SQLITE_CREATE_TEMP_INDEX = 3;
+ public static final int SQLITE_CREATE_TEMP_TABLE = 4;
+ public static final int SQLITE_CREATE_TEMP_TRIGGER = 5;
+ public static final int SQLITE_CREATE_TEMP_VIEW = 6;
+ public static final int SQLITE_CREATE_TRIGGER = 7;
+ public static final int SQLITE_CREATE_VIEW = 8;
+ public static final int SQLITE_DELETE = 9;
+ public static final int SQLITE_DROP_INDEX = 10;
+ public static final int SQLITE_DROP_TABLE = 11;
+ public static final int SQLITE_DROP_TEMP_INDEX = 12;
+ public static final int SQLITE_DROP_TEMP_TABLE = 13;
+ public static final int SQLITE_DROP_TEMP_TRIGGER = 14;
+ public static final int SQLITE_DROP_TEMP_VIEW = 15;
+ public static final int SQLITE_DROP_TRIGGER = 16;
+ public static final int SQLITE_DROP_VIEW = 17;
+ public static final int SQLITE_INSERT = 18;
+ public static final int SQLITE_PRAGMA = 19;
+ public static final int SQLITE_READ = 20;
+ public static final int SQLITE_SELECT = 21;
+ public static final int SQLITE_TRANSACTION = 22;
+ public static final int SQLITE_UPDATE = 23;
+ public static final int SQLITE_ATTACH = 24;
+ public static final int SQLITE_DETACH = 25;
+ public static final int SQLITE_ALTER_TABLE = 26;
+ public static final int SQLITE_REINDEX = 27;
+ public static final int SQLITE_ANALYZE = 28;
+ public static final int SQLITE_CREATE_VTABLE = 29;
+ public static final int SQLITE_DROP_VTABLE = 30;
+ public static final int SQLITE_FUNCTION = 31;
+ public static final int SQLITE_SAVEPOINT = 32;
+ public static final int SQLITE_RECURSIVE = 33;
+
+ // blob finalizers: these should, because they are treated as
+ // special pointer values in C, ideally have the same sizeof() as
+ // the platform's (void*), but we can't know that size from here.
+ public static final long SQLITE_STATIC = 0;
+ public static final long SQLITE_TRANSIENT = -1;
+
+ // changeset
+ public static final int SQLITE_CHANGESETSTART_INVERT = 2;
+ public static final int SQLITE_CHANGESETAPPLY_NOSAVEPOINT = 1;
+ public static final int SQLITE_CHANGESETAPPLY_INVERT = 2;
+ public static final int SQLITE_CHANGESETAPPLY_IGNORENOOP = 4;
+ public static final int SQLITE_CHANGESET_DATA = 1;
+ public static final int SQLITE_CHANGESET_NOTFOUND = 2;
+ public static final int SQLITE_CHANGESET_CONFLICT = 3;
+ public static final int SQLITE_CHANGESET_CONSTRAINT = 4;
+ public static final int SQLITE_CHANGESET_FOREIGN_KEY = 5;
+ public static final int SQLITE_CHANGESET_OMIT = 0;
+ public static final int SQLITE_CHANGESET_REPLACE = 1;
+ public static final int SQLITE_CHANGESET_ABORT = 2;
+
+ // config
+ public static final int SQLITE_CONFIG_SINGLETHREAD = 1;
+ public static final int SQLITE_CONFIG_MULTITHREAD = 2;
+ public static final int SQLITE_CONFIG_SERIALIZED = 3;
+ public static final int SQLITE_CONFIG_MALLOC = 4;
+ public static final int SQLITE_CONFIG_GETMALLOC = 5;
+ public static final int SQLITE_CONFIG_SCRATCH = 6;
+ public static final int SQLITE_CONFIG_PAGECACHE = 7;
+ public static final int SQLITE_CONFIG_HEAP = 8;
+ public static final int SQLITE_CONFIG_MEMSTATUS = 9;
+ public static final int SQLITE_CONFIG_MUTEX = 10;
+ public static final int SQLITE_CONFIG_GETMUTEX = 11;
+ public static final int SQLITE_CONFIG_LOOKASIDE = 13;
+ public static final int SQLITE_CONFIG_PCACHE = 14;
+ public static final int SQLITE_CONFIG_GETPCACHE = 15;
+ public static final int SQLITE_CONFIG_LOG = 16;
+ public static final int SQLITE_CONFIG_URI = 17;
+ public static final int SQLITE_CONFIG_PCACHE2 = 18;
+ public static final int SQLITE_CONFIG_GETPCACHE2 = 19;
+ public static final int SQLITE_CONFIG_COVERING_INDEX_SCAN = 20;
+ public static final int SQLITE_CONFIG_SQLLOG = 21;
+ public static final int SQLITE_CONFIG_MMAP_SIZE = 22;
+ public static final int SQLITE_CONFIG_WIN32_HEAPSIZE = 23;
+ public static final int SQLITE_CONFIG_PCACHE_HDRSZ = 24;
+ public static final int SQLITE_CONFIG_PMASZ = 25;
+ public static final int SQLITE_CONFIG_STMTJRNL_SPILL = 26;
+ public static final int SQLITE_CONFIG_SMALL_MALLOC = 27;
+ public static final int SQLITE_CONFIG_SORTERREF_SIZE = 28;
+ public static final int SQLITE_CONFIG_MEMDB_MAXSIZE = 29;
+
+ // data types
+ public static final int SQLITE_INTEGER = 1;
+ public static final int SQLITE_FLOAT = 2;
+ public static final int SQLITE_TEXT = 3;
+ public static final int SQLITE_BLOB = 4;
+ public static final int SQLITE_NULL = 5;
+
+ // db config
+ public static final int SQLITE_DBCONFIG_MAINDBNAME = 1000;
+ public static final int SQLITE_DBCONFIG_LOOKASIDE = 1001;
+ public static final int SQLITE_DBCONFIG_ENABLE_FKEY = 1002;
+ public static final int SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003;
+ public static final int SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004;
+ public static final int SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005;
+ public static final int SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006;
+ public static final int SQLITE_DBCONFIG_ENABLE_QPSG = 1007;
+ public static final int SQLITE_DBCONFIG_TRIGGER_EQP = 1008;
+ public static final int SQLITE_DBCONFIG_RESET_DATABASE = 1009;
+ public static final int SQLITE_DBCONFIG_DEFENSIVE = 1010;
+ public static final int SQLITE_DBCONFIG_WRITABLE_SCHEMA = 1011;
+ public static final int SQLITE_DBCONFIG_LEGACY_ALTER_TABLE = 1012;
+ public static final int SQLITE_DBCONFIG_DQS_DML = 1013;
+ public static final int SQLITE_DBCONFIG_DQS_DDL = 1014;
+ public static final int SQLITE_DBCONFIG_ENABLE_VIEW = 1015;
+ public static final int SQLITE_DBCONFIG_LEGACY_FILE_FORMAT = 1016;
+ public static final int SQLITE_DBCONFIG_TRUSTED_SCHEMA = 1017;
+ public static final int SQLITE_DBCONFIG_STMT_SCANSTATUS = 1018;
+ public static final int SQLITE_DBCONFIG_REVERSE_SCANORDER = 1019;
+ public static final int SQLITE_DBCONFIG_MAX = 1019;
+
+ // db status
+ public static final int SQLITE_DBSTATUS_LOOKASIDE_USED = 0;
+ public static final int SQLITE_DBSTATUS_CACHE_USED = 1;
+ public static final int SQLITE_DBSTATUS_SCHEMA_USED = 2;
+ public static final int SQLITE_DBSTATUS_STMT_USED = 3;
+ public static final int SQLITE_DBSTATUS_LOOKASIDE_HIT = 4;
+ public static final int SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE = 5;
+ public static final int SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL = 6;
+ public static final int SQLITE_DBSTATUS_CACHE_HIT = 7;
+ public static final int SQLITE_DBSTATUS_CACHE_MISS = 8;
+ public static final int SQLITE_DBSTATUS_CACHE_WRITE = 9;
+ public static final int SQLITE_DBSTATUS_DEFERRED_FKS = 10;
+ public static final int SQLITE_DBSTATUS_CACHE_USED_SHARED = 11;
+ public static final int SQLITE_DBSTATUS_CACHE_SPILL = 12;
+ public static final int SQLITE_DBSTATUS_MAX = 12;
+
+ // encodings
+ public static final int SQLITE_UTF8 = 1;
+ public static final int SQLITE_UTF16LE = 2;
+ public static final int SQLITE_UTF16BE = 3;
+ public static final int SQLITE_UTF16 = 4;
+ public static final int SQLITE_UTF16_ALIGNED = 8;
+
+ // fcntl
+ public static final int SQLITE_FCNTL_LOCKSTATE = 1;
+ public static final int SQLITE_FCNTL_GET_LOCKPROXYFILE = 2;
+ public static final int SQLITE_FCNTL_SET_LOCKPROXYFILE = 3;
+ public static final int SQLITE_FCNTL_LAST_ERRNO = 4;
+ public static final int SQLITE_FCNTL_SIZE_HINT = 5;
+ public static final int SQLITE_FCNTL_CHUNK_SIZE = 6;
+ public static final int SQLITE_FCNTL_FILE_POINTER = 7;
+ public static final int SQLITE_FCNTL_SYNC_OMITTED = 8;
+ public static final int SQLITE_FCNTL_WIN32_AV_RETRY = 9;
+ public static final int SQLITE_FCNTL_PERSIST_WAL = 10;
+ public static final int SQLITE_FCNTL_OVERWRITE = 11;
+ public static final int SQLITE_FCNTL_VFSNAME = 12;
+ public static final int SQLITE_FCNTL_POWERSAFE_OVERWRITE = 13;
+ public static final int SQLITE_FCNTL_PRAGMA = 14;
+ public static final int SQLITE_FCNTL_BUSYHANDLER = 15;
+ public static final int SQLITE_FCNTL_TEMPFILENAME = 16;
+ public static final int SQLITE_FCNTL_MMAP_SIZE = 18;
+ public static final int SQLITE_FCNTL_TRACE = 19;
+ public static final int SQLITE_FCNTL_HAS_MOVED = 20;
+ public static final int SQLITE_FCNTL_SYNC = 21;
+ public static final int SQLITE_FCNTL_COMMIT_PHASETWO = 22;
+ public static final int SQLITE_FCNTL_WIN32_SET_HANDLE = 23;
+ public static final int SQLITE_FCNTL_WAL_BLOCK = 24;
+ public static final int SQLITE_FCNTL_ZIPVFS = 25;
+ public static final int SQLITE_FCNTL_RBU = 26;
+ public static final int SQLITE_FCNTL_VFS_POINTER = 27;
+ public static final int SQLITE_FCNTL_JOURNAL_POINTER = 28;
+ public static final int SQLITE_FCNTL_WIN32_GET_HANDLE = 29;
+ public static final int SQLITE_FCNTL_PDB = 30;
+ public static final int SQLITE_FCNTL_BEGIN_ATOMIC_WRITE = 31;
+ public static final int SQLITE_FCNTL_COMMIT_ATOMIC_WRITE = 32;
+ public static final int SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE = 33;
+ public static final int SQLITE_FCNTL_LOCK_TIMEOUT = 34;
+ public static final int SQLITE_FCNTL_DATA_VERSION = 35;
+ public static final int SQLITE_FCNTL_SIZE_LIMIT = 36;
+ public static final int SQLITE_FCNTL_CKPT_DONE = 37;
+ public static final int SQLITE_FCNTL_RESERVE_BYTES = 38;
+ public static final int SQLITE_FCNTL_CKPT_START = 39;
+ public static final int SQLITE_FCNTL_EXTERNAL_READER = 40;
+ public static final int SQLITE_FCNTL_CKSM_FILE = 41;
+ public static final int SQLITE_FCNTL_RESET_CACHE = 42;
+
+ // flock
+ public static final int SQLITE_LOCK_NONE = 0;
+ public static final int SQLITE_LOCK_SHARED = 1;
+ public static final int SQLITE_LOCK_RESERVED = 2;
+ public static final int SQLITE_LOCK_PENDING = 3;
+ public static final int SQLITE_LOCK_EXCLUSIVE = 4;
+
+ // iocap
+ public static final int SQLITE_IOCAP_ATOMIC = 1;
+ public static final int SQLITE_IOCAP_ATOMIC512 = 2;
+ public static final int SQLITE_IOCAP_ATOMIC1K = 4;
+ public static final int SQLITE_IOCAP_ATOMIC2K = 8;
+ public static final int SQLITE_IOCAP_ATOMIC4K = 16;
+ public static final int SQLITE_IOCAP_ATOMIC8K = 32;
+ public static final int SQLITE_IOCAP_ATOMIC16K = 64;
+ public static final int SQLITE_IOCAP_ATOMIC32K = 128;
+ public static final int SQLITE_IOCAP_ATOMIC64K = 256;
+ public static final int SQLITE_IOCAP_SAFE_APPEND = 512;
+ public static final int SQLITE_IOCAP_SEQUENTIAL = 1024;
+ public static final int SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN = 2048;
+ public static final int SQLITE_IOCAP_POWERSAFE_OVERWRITE = 4096;
+ public static final int SQLITE_IOCAP_IMMUTABLE = 8192;
+ public static final int SQLITE_IOCAP_BATCH_ATOMIC = 16384;
+
+ // limits
+ public static final int SQLITE_LIMIT_LENGTH = 0;
+ public static final int SQLITE_LIMIT_SQL_LENGTH = 1;
+ public static final int SQLITE_LIMIT_COLUMN = 2;
+ public static final int SQLITE_LIMIT_EXPR_DEPTH = 3;
+ public static final int SQLITE_LIMIT_COMPOUND_SELECT = 4;
+ public static final int SQLITE_LIMIT_VDBE_OP = 5;
+ public static final int SQLITE_LIMIT_FUNCTION_ARG = 6;
+ public static final int SQLITE_LIMIT_ATTACHED = 7;
+ public static final int SQLITE_LIMIT_LIKE_PATTERN_LENGTH = 8;
+ public static final int SQLITE_LIMIT_VARIABLE_NUMBER = 9;
+ public static final int SQLITE_LIMIT_TRIGGER_DEPTH = 10;
+ public static final int SQLITE_LIMIT_WORKER_THREADS = 11;
+
+ // open flags
+
+ public static final int SQLITE_OPEN_READONLY = 0x00000001 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_READWRITE = 0x00000002 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_CREATE = 0x00000004 /* Ok for sqlite3_open_v2() */;
+ //public static final int SQLITE_OPEN_DELETEONCLOSE = 0x00000008 /* VFS only */;
+ //public static final int SQLITE_OPEN_EXCLUSIVE = 0x00000010 /* VFS only */;
+ //public static final int SQLITE_OPEN_AUTOPROXY = 0x00000020 /* VFS only */;
+ public static final int SQLITE_OPEN_URI = 0x00000040 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_MEMORY = 0x00000080 /* Ok for sqlite3_open_v2() */;
+ //public static final int SQLITE_OPEN_MAIN_DB = 0x00000100 /* VFS only */;
+ //public static final int SQLITE_OPEN_TEMP_DB = 0x00000200 /* VFS only */;
+ //public static final int SQLITE_OPEN_TRANSIENT_DB = 0x00000400 /* VFS only */;
+ //public static final int SQLITE_OPEN_MAIN_JOURNAL = 0x00000800 /* VFS only */;
+ //public static final int SQLITE_OPEN_TEMP_JOURNAL = 0x00001000 /* VFS only */;
+ //public static final int SQLITE_OPEN_SUBJOURNAL = 0x00002000 /* VFS only */;
+ //public static final int SQLITE_OPEN_SUPER_JOURNAL = 0x00004000 /* VFS only */;
+ public static final int SQLITE_OPEN_NOMUTEX = 0x00008000 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_FULLMUTEX = 0x00010000 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_SHAREDCACHE = 0x00020000 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_PRIVATECACHE = 0x00040000 /* Ok for sqlite3_open_v2() */;
+ //public static final int SQLITE_OPEN_WAL = 0x00080000 /* VFS only */;
+ public static final int SQLITE_OPEN_NOFOLLOW = 0x01000000 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_EXRESCODE = 0x02000000 /* Extended result codes */;
+
+ // prepare flags
+ public static final int SQLITE_PREPARE_PERSISTENT = 1;
+ public static final int SQLITE_PREPARE_NO_VTAB = 4;
+
+ // result codes
+ public static final int SQLITE_OK = 0;
+ public static final int SQLITE_ERROR = 1;
+ public static final int SQLITE_INTERNAL = 2;
+ public static final int SQLITE_PERM = 3;
+ public static final int SQLITE_ABORT = 4;
+ public static final int SQLITE_BUSY = 5;
+ public static final int SQLITE_LOCKED = 6;
+ public static final int SQLITE_NOMEM = 7;
+ public static final int SQLITE_READONLY = 8;
+ public static final int SQLITE_INTERRUPT = 9;
+ public static final int SQLITE_IOERR = 10;
+ public static final int SQLITE_CORRUPT = 11;
+ public static final int SQLITE_NOTFOUND = 12;
+ public static final int SQLITE_FULL = 13;
+ public static final int SQLITE_CANTOPEN = 14;
+ public static final int SQLITE_PROTOCOL = 15;
+ public static final int SQLITE_EMPTY = 16;
+ public static final int SQLITE_SCHEMA = 17;
+ public static final int SQLITE_TOOBIG = 18;
+ public static final int SQLITE_CONSTRAINT = 19;
+ public static final int SQLITE_MISMATCH = 20;
+ public static final int SQLITE_MISUSE = 21;
+ public static final int SQLITE_NOLFS = 22;
+ public static final int SQLITE_AUTH = 23;
+ public static final int SQLITE_FORMAT = 24;
+ public static final int SQLITE_RANGE = 25;
+ public static final int SQLITE_NOTADB = 26;
+ public static final int SQLITE_NOTICE = 27;
+ public static final int SQLITE_WARNING = 28;
+ public static final int SQLITE_ROW = 100;
+ public static final int SQLITE_DONE = 101;
+ public static final int SQLITE_ERROR_MISSING_COLLSEQ = 257;
+ public static final int SQLITE_ERROR_RETRY = 513;
+ public static final int SQLITE_ERROR_SNAPSHOT = 769;
+ public static final int SQLITE_IOERR_READ = 266;
+ public static final int SQLITE_IOERR_SHORT_READ = 522;
+ public static final int SQLITE_IOERR_WRITE = 778;
+ public static final int SQLITE_IOERR_FSYNC = 1034;
+ public static final int SQLITE_IOERR_DIR_FSYNC = 1290;
+ public static final int SQLITE_IOERR_TRUNCATE = 1546;
+ public static final int SQLITE_IOERR_FSTAT = 1802;
+ public static final int SQLITE_IOERR_UNLOCK = 2058;
+ public static final int SQLITE_IOERR_RDLOCK = 2314;
+ public static final int SQLITE_IOERR_DELETE = 2570;
+ public static final int SQLITE_IOERR_BLOCKED = 2826;
+ public static final int SQLITE_IOERR_NOMEM = 3082;
+ public static final int SQLITE_IOERR_ACCESS = 3338;
+ public static final int SQLITE_IOERR_CHECKRESERVEDLOCK = 3594;
+ public static final int SQLITE_IOERR_LOCK = 3850;
+ public static final int SQLITE_IOERR_CLOSE = 4106;
+ public static final int SQLITE_IOERR_DIR_CLOSE = 4362;
+ public static final int SQLITE_IOERR_SHMOPEN = 4618;
+ public static final int SQLITE_IOERR_SHMSIZE = 4874;
+ public static final int SQLITE_IOERR_SHMLOCK = 5130;
+ public static final int SQLITE_IOERR_SHMMAP = 5386;
+ public static final int SQLITE_IOERR_SEEK = 5642;
+ public static final int SQLITE_IOERR_DELETE_NOENT = 5898;
+ public static final int SQLITE_IOERR_MMAP = 6154;
+ public static final int SQLITE_IOERR_GETTEMPPATH = 6410;
+ public static final int SQLITE_IOERR_CONVPATH = 6666;
+ public static final int SQLITE_IOERR_VNODE = 6922;
+ public static final int SQLITE_IOERR_AUTH = 7178;
+ public static final int SQLITE_IOERR_BEGIN_ATOMIC = 7434;
+ public static final int SQLITE_IOERR_COMMIT_ATOMIC = 7690;
+ public static final int SQLITE_IOERR_ROLLBACK_ATOMIC = 7946;
+ public static final int SQLITE_IOERR_DATA = 8202;
+ public static final int SQLITE_IOERR_CORRUPTFS = 8458;
+ public static final int SQLITE_LOCKED_SHAREDCACHE = 262;
+ public static final int SQLITE_LOCKED_VTAB = 518;
+ public static final int SQLITE_BUSY_RECOVERY = 261;
+ public static final int SQLITE_BUSY_SNAPSHOT = 517;
+ public static final int SQLITE_BUSY_TIMEOUT = 773;
+ public static final int SQLITE_CANTOPEN_NOTEMPDIR = 270;
+ public static final int SQLITE_CANTOPEN_ISDIR = 526;
+ public static final int SQLITE_CANTOPEN_FULLPATH = 782;
+ public static final int SQLITE_CANTOPEN_CONVPATH = 1038;
+ public static final int SQLITE_CANTOPEN_SYMLINK = 1550;
+ public static final int SQLITE_CORRUPT_VTAB = 267;
+ public static final int SQLITE_CORRUPT_SEQUENCE = 523;
+ public static final int SQLITE_CORRUPT_INDEX = 779;
+ public static final int SQLITE_READONLY_RECOVERY = 264;
+ public static final int SQLITE_READONLY_CANTLOCK = 520;
+ public static final int SQLITE_READONLY_ROLLBACK = 776;
+ public static final int SQLITE_READONLY_DBMOVED = 1032;
+ public static final int SQLITE_READONLY_CANTINIT = 1288;
+ public static final int SQLITE_READONLY_DIRECTORY = 1544;
+ public static final int SQLITE_ABORT_ROLLBACK = 516;
+ public static final int SQLITE_CONSTRAINT_CHECK = 275;
+ public static final int SQLITE_CONSTRAINT_COMMITHOOK = 531;
+ public static final int SQLITE_CONSTRAINT_FOREIGNKEY = 787;
+ public static final int SQLITE_CONSTRAINT_FUNCTION = 1043;
+ public static final int SQLITE_CONSTRAINT_NOTNULL = 1299;
+ public static final int SQLITE_CONSTRAINT_PRIMARYKEY = 1555;
+ public static final int SQLITE_CONSTRAINT_TRIGGER = 1811;
+ public static final int SQLITE_CONSTRAINT_UNIQUE = 2067;
+ public static final int SQLITE_CONSTRAINT_VTAB = 2323;
+ public static final int SQLITE_CONSTRAINT_ROWID = 2579;
+ public static final int SQLITE_CONSTRAINT_PINNED = 2835;
+ public static final int SQLITE_CONSTRAINT_DATATYPE = 3091;
+ public static final int SQLITE_NOTICE_RECOVER_WAL = 283;
+ public static final int SQLITE_NOTICE_RECOVER_ROLLBACK = 539;
+ public static final int SQLITE_WARNING_AUTOINDEX = 284;
+ public static final int SQLITE_AUTH_USER = 279;
+ public static final int SQLITE_OK_LOAD_PERMANENTLY = 256;
+
+ // serialize
+ public static final int SQLITE_SERIALIZE_NOCOPY = 1;
+ public static final int SQLITE_DESERIALIZE_FREEONCLOSE = 1;
+ public static final int SQLITE_DESERIALIZE_READONLY = 4;
+ public static final int SQLITE_DESERIALIZE_RESIZEABLE = 2;
+
+ // session
+ public static final int SQLITE_SESSION_CONFIG_STRMSIZE = 1;
+ public static final int SQLITE_SESSION_OBJCONFIG_SIZE = 1;
+
+ // sqlite3 status
+ public static final int SQLITE_STATUS_MEMORY_USED = 0;
+ public static final int SQLITE_STATUS_PAGECACHE_USED = 1;
+ public static final int SQLITE_STATUS_PAGECACHE_OVERFLOW = 2;
+ public static final int SQLITE_STATUS_MALLOC_SIZE = 5;
+ public static final int SQLITE_STATUS_PARSER_STACK = 6;
+ public static final int SQLITE_STATUS_PAGECACHE_SIZE = 7;
+ public static final int SQLITE_STATUS_MALLOC_COUNT = 9;
+
+ // stmt status
+ public static final int SQLITE_STMTSTATUS_FULLSCAN_STEP = 1;
+ public static final int SQLITE_STMTSTATUS_SORT = 2;
+ public static final int SQLITE_STMTSTATUS_AUTOINDEX = 3;
+ public static final int SQLITE_STMTSTATUS_VM_STEP = 4;
+ public static final int SQLITE_STMTSTATUS_REPREPARE = 5;
+ public static final int SQLITE_STMTSTATUS_RUN = 6;
+ public static final int SQLITE_STMTSTATUS_FILTER_MISS = 7;
+ public static final int SQLITE_STMTSTATUS_FILTER_HIT = 8;
+ public static final int SQLITE_STMTSTATUS_MEMUSED = 99;
+
+ // sync flags
+ public static final int SQLITE_SYNC_NORMAL = 2;
+ public static final int SQLITE_SYNC_FULL = 3;
+ public static final int SQLITE_SYNC_DATAONLY = 16;
+
+ // tracing flags
+ public static final int SQLITE_TRACE_STMT = 1;
+ public static final int SQLITE_TRACE_PROFILE = 2;
+ public static final int SQLITE_TRACE_ROW = 4;
+ public static final int SQLITE_TRACE_CLOSE = 8;
+
+ // transaction state
+ public static final int SQLITE_TXN_NONE = 0;
+ public static final int SQLITE_TXN_READ = 1;
+ public static final int SQLITE_TXN_WRITE = 2;
+
+ // udf flags
+ public static final int SQLITE_DETERMINISTIC = 0x000000800;
+ public static final int SQLITE_DIRECTONLY = 0x000080000;
+ public static final int SQLITE_SUBTYPE = 0x000100000;
+ public static final int SQLITE_INNOCUOUS = 0x000200000;
+ public static final int SQLITE_RESULT_SUBTYPE = 0x001000000;
+
+ // 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 {
+ init();
+ }
+ /* Must come after static init(). */
+ private static final boolean JNI_SUPPORTS_NIO = sqlite3_jni_supports_nio();
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java b/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java
new file mode 100644
index 0000000..7df748e
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java
@@ -0,0 +1,45 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+/**
+ This marker interface exists soley for use as a documentation and
+ class-grouping tool. It should be applied to interfaces or
+ classes which have a call() method implementing some specific
+ callback interface on behalf of the C library.
+
+ <p>Unless very explicitely documented otherwise, callbacks must
+ never throw. Any which do throw but should not might trigger debug
+ output regarding the error, but the exception will not be
+ propagated. For callback interfaces which support returning error
+ info to the core, the JNI binding will convert any exceptions to
+ C-level error information. For callback interfaces which do not
+ support returning error information, all exceptions will
+ necessarily be suppressed in order to retain the C-style no-throw
+ semantics and avoid invoking undefined behavior in the C layer.
+
+ <p>Callbacks of this style follow a common naming convention:
+
+ <p>1) They use the UpperCamelCase form of the C function they're
+ proxying for, minus the {@code sqlite3_} prefix, plus a {@code
+ Callback} suffix. e.g. {@code sqlite3_busy_handler()}'s callback is
+ named {@code BusyHandlerCallback}. Exceptions are made where that
+ would potentially be ambiguous, e.g. {@link ConfigSqllogCallback}
+ instead of {@code ConfigCallback} because the {@code
+ sqlite3_config()} interface may need to support more callback types
+ in the future.
+
+ <p>2) They all have a {@code call()} method but its signature is
+ callback-specific.
+*/
+public interface CallbackProxy {}
diff --git a/ext/jni/src/org/sqlite/jni/capi/CollationCallback.java b/ext/jni/src/org/sqlite/jni/capi/CollationCallback.java
new file mode 100644
index 0000000..ed8bd09
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/CollationCallback.java
@@ -0,0 +1,35 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import org.sqlite.jni.annotation.NotNull;
+
+/**
+ Callback for use with {@link CApi#sqlite3_create_collation}.
+
+ @see AbstractCollationCallback
+*/
+public interface CollationCallback
+ extends CallbackProxy, XDestroyCallback {
+ /**
+ Must compare the given byte arrays and return the result using
+ {@code memcmp()} semantics.
+ */
+ int call(@NotNull byte[] lhs, @NotNull byte[] rhs);
+
+ /**
+ Called by SQLite when the collation is destroyed. If a collation
+ requires custom cleanup, override this method.
+ */
+ void xDestroy();
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java b/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java
new file mode 100644
index 0000000..ffd7fa9
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java
@@ -0,0 +1,29 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_collation_needed}.
+*/
+public interface CollationNeededCallback extends CallbackProxy {
+ /**
+ Has the same semantics as the C-level sqlite3_create_collation()
+ callback.
+
+ <p>Because the C API has no mechanism for reporting errors
+ from this callbacks, any exceptions thrown by this callback
+ are suppressed.
+ */
+ void call(sqlite3 db, int eTextRep, String collationName);
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java b/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java
new file mode 100644
index 0000000..e1e55c7
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java
@@ -0,0 +1,26 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_commit_hook}.
+*/
+public interface CommitHookCallback extends CallbackProxy {
+ /**
+ Works as documented for the C-level sqlite3_commit_hook()
+ callback. If it throws, the exception is translated into
+ a db-level error.
+ */
+ int call();
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java b/ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java
new file mode 100644
index 0000000..6513b07
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java
@@ -0,0 +1,25 @@
+/*
+** 2023-08-23
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A callback for use with sqlite3_config().
+*/
+public interface ConfigLogCallback {
+ /**
+ Must function as described for a C-level callback for
+ {@link CApi#sqlite3_config(ConfigLogCallback)}, with the slight signature change.
+ */
+ void call(int errCode, String msg);
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/ConfigSqlLogCallback.java b/ext/jni/src/org/sqlite/jni/capi/ConfigSqlLogCallback.java
new file mode 100644
index 0000000..a5530b4
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/ConfigSqlLogCallback.java
@@ -0,0 +1,25 @@
+/*
+** 2023-08-23
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A callback for use with sqlite3_config().
+*/
+public interface ConfigSqlLogCallback {
+ /**
+ Must function as described for a C-level callback for
+ {@link CApi#sqlite3_config(ConfigSqlLogCallback)}, with the slight signature change.
+ */
+ void call(sqlite3 db, String msg, int msgType );
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java b/ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java
new file mode 100644
index 0000000..e82909e
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java
@@ -0,0 +1,46 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A helper for passing pointers between JNI C code and Java, in
+ particular for output pointers of high-level object types in the
+ sqlite3 C API, e.g. (sqlite3**) and (sqlite3_stmt**). This is
+ intended to be subclassed and the ContextType is intended to be the
+ class which is doing the subclassing. The intent of the ContextType
+ is strictly to provide some level of type safety by avoiding that
+ NativePointerHolder is not inadvertently passed to an incompatible
+ function signature.
+
+ These objects do not own the pointer they refer to. They are
+ intended simply to communicate that pointer between C and Java.
+*/
+public class NativePointerHolder<ContextType> {
+ //! Only set from JNI, where access permissions don't matter.
+ private volatile long nativePointer = 0;
+ /**
+ For use ONLY by package-level APIs which act as proxies for
+ close/finalize operations. Such ops must call this to zero out
+ the pointer so that this object is not carrying a stale
+ pointer. This function returns the prior value of the pointer and
+ sets it to 0.
+ */
+ final long clearNativePointer() {
+ final long rv = nativePointer;
+ nativePointer= 0;
+ return rv;
+ }
+
+ public final long getNativePointer(){ return nativePointer; }
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java b/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java
new file mode 100644
index 0000000..7bf7529
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java
@@ -0,0 +1,253 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Helper classes for handling JNI output pointers.
+
+ <p>We do not use a generic OutputPointer<T> because working with those
+ from the native JNI code is unduly quirky due to a lack of
+ autoboxing at that level.
+
+ <p>The usage is similar for all of thes types:
+
+ <pre>{@code
+ OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ assert( null==out.get() );
+ int rc = sqlite3_open(":memory:", out);
+ if( 0!=rc ) ... error;
+ assert( null!=out.get() );
+ sqlite3 db = out.take();
+ assert( null==out.get() );
+ }</pre>
+
+ <p>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.
+
+ <p>Warning: do not share instances of these classes across
+ threads. Doing so may lead to corrupting sqlite3-internal state.
+*/
+public final class OutputPointer {
+
+ /**
+ Output pointer for use with routines, such as sqlite3_open(),
+ which return a database handle via an output pointer. These
+ pointers can only be set by the JNI layer, not by client-level
+ code.
+ */
+ public static final class sqlite3 {
+ private org.sqlite.jni.capi.sqlite3 value;
+ /** Initializes with a null value. */
+ public sqlite3(){value = null;}
+ /** Sets the current value to null. */
+ public void clear(){value = null;}
+ /** Returns the current value. */
+ public final org.sqlite.jni.capi.sqlite3 get(){return value;}
+ /** Equivalent to calling get() then clear(). */
+ public final org.sqlite.jni.capi.sqlite3 take(){
+ final org.sqlite.jni.capi.sqlite3 v = value;
+ value = null;
+ return v;
+ }
+ }
+
+ /**
+ Output pointer for sqlite3_blob_open(). These
+ pointers can only be set by the JNI layer, not by client-level
+ code.
+ */
+ public static final class sqlite3_blob {
+ private org.sqlite.jni.capi.sqlite3_blob value;
+ /** Initializes with a null value. */
+ public sqlite3_blob(){value = null;}
+ /** Sets the current value to null. */
+ public void clear(){value = null;}
+ /** Returns the current value. */
+ public final org.sqlite.jni.capi.sqlite3_blob get(){return value;}
+ /** Equivalent to calling get() then clear(). */
+ public final org.sqlite.jni.capi.sqlite3_blob take(){
+ final org.sqlite.jni.capi.sqlite3_blob v = value;
+ value = null;
+ return v;
+ }
+ }
+
+ /**
+ Output pointer for use with routines, such as sqlite3_prepare(),
+ which return a statement handle via an output pointer. These
+ pointers can only be set by the JNI layer, not by client-level
+ code.
+ */
+ public static final class sqlite3_stmt {
+ private org.sqlite.jni.capi.sqlite3_stmt value;
+ /** Initializes with a null value. */
+ public sqlite3_stmt(){value = null;}
+ /** Sets the current value to null. */
+ public void clear(){value = null;}
+ /** Returns the current value. */
+ public final org.sqlite.jni.capi.sqlite3_stmt get(){return value;}
+ /** Equivalent to calling get() then clear(). */
+ public final org.sqlite.jni.capi.sqlite3_stmt take(){
+ final org.sqlite.jni.capi.sqlite3_stmt v = value;
+ value = null;
+ return v;
+ }
+ }
+
+ /**
+ Output pointer for use with routines, such as sqlite3_prepupdate_new(),
+ which return a sqlite3_value handle via an output pointer. These
+ pointers can only be set by the JNI layer, not by client-level
+ code.
+ */
+ public static final class sqlite3_value {
+ private org.sqlite.jni.capi.sqlite3_value value;
+ /** Initializes with a null value. */
+ public sqlite3_value(){value = null;}
+ /** Sets the current value to null. */
+ public void clear(){value = null;}
+ /** Returns the current value. */
+ public final org.sqlite.jni.capi.sqlite3_value get(){return value;}
+ /** Equivalent to calling get() then clear(). */
+ public final org.sqlite.jni.capi.sqlite3_value take(){
+ final org.sqlite.jni.capi.sqlite3_value v = value;
+ value = null;
+ return v;
+ }
+ }
+
+ /**
+ Output pointer for use with native routines which return booleans
+ via integer output pointers.
+ */
+ public static final class Bool {
+ /**
+ This is public for ease of use. Accessors are provided for
+ consistency with the higher-level types.
+ */
+ public boolean value;
+ /** Initializes with the value 0. */
+ public Bool(){this(false);}
+ /** Initializes with the value v. */
+ public Bool(boolean v){value = v;}
+ /** Returns the current value. */
+ public final boolean get(){return value;}
+ /** Sets the current value to v. */
+ public final void set(boolean v){value = v;}
+ }
+
+ /**
+ Output pointer for use with native routines which return integers via
+ output pointers.
+ */
+ public static final class Int32 {
+ /**
+ This is public for ease of use. Accessors are provided for
+ consistency with the higher-level types.
+ */
+ public int value;
+ /** Initializes with the value 0. */
+ public Int32(){this(0);}
+ /** Initializes with the value v. */
+ public Int32(int v){value = v;}
+ /** Returns the current value. */
+ public final int get(){return value;}
+ /** Sets the current value to v. */
+ public final void set(int v){value = v;}
+ }
+
+ /**
+ Output pointer for use with native routines which return 64-bit integers
+ via output pointers.
+ */
+ public static final class Int64 {
+ /**
+ This is public for ease of use. Accessors are provided for
+ consistency with the higher-level types.
+ */
+ public long value;
+ /** Initializes with the value 0. */
+ public Int64(){this(0);}
+ /** Initializes with the value v. */
+ public Int64(long v){value = v;}
+ /** Returns the current value. */
+ public final long get(){return value;}
+ /** Sets the current value. */
+ public final void set(long v){value = v;}
+ }
+
+ /**
+ Output pointer for use with native routines which return strings via
+ output pointers.
+ */
+ public static final class String {
+ /**
+ This is public for ease of use. Accessors are provided for
+ consistency with the higher-level types.
+ */
+ public java.lang.String value;
+ /** Initializes with a null value. */
+ public String(){this(null);}
+ /** Initializes with the value v. */
+ public String(java.lang.String v){value = v;}
+ /** Returns the current value. */
+ public final java.lang.String get(){return value;}
+ /** Sets the current value. */
+ public final void set(java.lang.String v){value = v;}
+ }
+
+ /**
+ Output pointer for use with native routines which return byte
+ arrays via output pointers.
+ */
+ public static final class ByteArray {
+ /**
+ This is public for ease of use. Accessors are provided for
+ consistency with the higher-level types.
+ */
+ public byte[] value;
+ /** Initializes with the value null. */
+ public ByteArray(){this(null);}
+ /** Initializes with the value v. */
+ public ByteArray(byte[] v){value = v;}
+ /** Returns the current value. */
+ public final byte[] get(){return value;}
+ /** Sets the current value. */
+ public final void set(byte[] v){value = v;}
+ }
+
+ /**
+ Output pointer for use with native routines which return
+ blobs via java.nio.ByteBuffer.
+
+ See {@link org.sqlite.jni.capi.CApi#sqlite3_jni_supports_nio}
+ */
+ public static final class ByteBuffer {
+ /**
+ This is public for ease of use. Accessors are provided for
+ consistency with the higher-level types.
+ */
+ public java.nio.ByteBuffer value;
+ /** Initializes with the value null. */
+ public ByteBuffer(){this(null);}
+ /** Initializes with the value v. */
+ public ByteBuffer(java.nio.ByteBuffer v){value = v;}
+ /** Returns the current value. */
+ public final java.nio.ByteBuffer get(){return value;}
+ /** Sets the current value. */
+ public final void set(java.nio.ByteBuffer v){value = v;}
+ }
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java b/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java
new file mode 100644
index 0000000..9f6dd47
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java
@@ -0,0 +1,81 @@
+/*
+** 2023-09-13
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_prepare_multi}.
+*/
+public interface PrepareMultiCallback extends CallbackProxy {
+
+ /**
+ Gets passed a sqlite3_stmt which it may handle in arbitrary ways,
+ transfering ownership of it to this function.
+
+ sqlite3_prepare_multi() will _not_ finalize st - it is up
+ to the call() implementation how st is handled.
+
+ Must return 0 on success or an SQLITE_... code on error. If it
+ throws, sqlite3_prepare_multi() will transform the exception into
+ a db-level error in order to retain the C-style error semantics
+ of the API.
+
+ See the {@link Finalize} class for a wrapper which finalizes the
+ statement after calling a proxy PrepareMultiCallback.
+ */
+ int call(sqlite3_stmt st);
+
+ /**
+ A PrepareMultiCallback impl which wraps a separate impl and finalizes
+ any sqlite3_stmt passed to its callback.
+ */
+ public static final class Finalize implements PrepareMultiCallback {
+ private final PrepareMultiCallback p;
+ /**
+ p is the proxy to call() when this.call() is called.
+ */
+ public Finalize( PrepareMultiCallback p ){
+ this.p = p;
+ }
+ /**
+ Calls the call() method of the proxied callback and either returns its
+ result or propagates an exception. Either way, it passes its argument to
+ sqlite3_finalize() before returning.
+ */
+ @Override public int call(sqlite3_stmt st){
+ try {
+ return this.p.call(st);
+ }finally{
+ CApi.sqlite3_finalize(st);
+ }
+ }
+ }
+
+ /**
+ A PrepareMultiCallback impl which steps entirely through a result set,
+ ignoring all non-error results.
+ */
+ public static final class StepAll implements PrepareMultiCallback {
+ public StepAll(){}
+ /**
+ Calls sqlite3_step() on st until it returns something other than
+ SQLITE_ROW. If the final result is SQLITE_DONE then 0 is returned,
+ else the result of the final step is returned.
+ */
+ @Override public int call(sqlite3_stmt st){
+ int rc = CApi.SQLITE_DONE;
+ while( CApi.SQLITE_ROW == (rc = CApi.sqlite3_step(st)) ){}
+ return CApi.SQLITE_DONE==rc ? 0 : rc;
+ }
+ }
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java b/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java
new file mode 100644
index 0000000..38f7c56
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java
@@ -0,0 +1,27 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_preupdate_hook}.
+*/
+public interface PreupdateHookCallback extends CallbackProxy {
+ /**
+ Must function as described for the C-level sqlite3_preupdate_hook()
+ callback. If it throws, the exception is translated to a
+ db-level error and the exception is suppressed.
+ */
+ void call(sqlite3 db, int op, String dbName, String dbTable,
+ long iKey1, long iKey2 );
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java b/ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java
new file mode 100644
index 0000000..464baa2
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java
@@ -0,0 +1,27 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_progress_handler}.
+*/
+public interface ProgressHandlerCallback extends CallbackProxy {
+ /**
+ Works as documented for the C-level sqlite3_progress_handler() callback.
+
+ <p>If it throws, the exception message is passed on to the db and
+ the exception is suppressed.
+ */
+ int call();
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/ResultCode.java b/ext/jni/src/org/sqlite/jni/capi/ResultCode.java
new file mode 100644
index 0000000..5a8b2e6
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/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.capi;
+
+/**
+ This enum contains all of the core and "extended" result codes used
+ by the sqlite3 library. It is provided not for use with the C-style
+ API (with which it won't work) but for higher-level code which may
+ find it useful to map SQLite result codes to human-readable names.
+*/
+public enum ResultCode {
+ SQLITE_OK(CApi.SQLITE_OK),
+ SQLITE_ERROR(CApi.SQLITE_ERROR),
+ SQLITE_INTERNAL(CApi.SQLITE_INTERNAL),
+ SQLITE_PERM(CApi.SQLITE_PERM),
+ SQLITE_ABORT(CApi.SQLITE_ABORT),
+ SQLITE_BUSY(CApi.SQLITE_BUSY),
+ SQLITE_LOCKED(CApi.SQLITE_LOCKED),
+ SQLITE_NOMEM(CApi.SQLITE_NOMEM),
+ SQLITE_READONLY(CApi.SQLITE_READONLY),
+ SQLITE_INTERRUPT(CApi.SQLITE_INTERRUPT),
+ SQLITE_IOERR(CApi.SQLITE_IOERR),
+ SQLITE_CORRUPT(CApi.SQLITE_CORRUPT),
+ SQLITE_NOTFOUND(CApi.SQLITE_NOTFOUND),
+ SQLITE_FULL(CApi.SQLITE_FULL),
+ SQLITE_CANTOPEN(CApi.SQLITE_CANTOPEN),
+ SQLITE_PROTOCOL(CApi.SQLITE_PROTOCOL),
+ SQLITE_EMPTY(CApi.SQLITE_EMPTY),
+ SQLITE_SCHEMA(CApi.SQLITE_SCHEMA),
+ SQLITE_TOOBIG(CApi.SQLITE_TOOBIG),
+ SQLITE_CONSTRAINT(CApi.SQLITE_CONSTRAINT),
+ SQLITE_MISMATCH(CApi.SQLITE_MISMATCH),
+ SQLITE_MISUSE(CApi.SQLITE_MISUSE),
+ SQLITE_NOLFS(CApi.SQLITE_NOLFS),
+ SQLITE_AUTH(CApi.SQLITE_AUTH),
+ SQLITE_FORMAT(CApi.SQLITE_FORMAT),
+ SQLITE_RANGE(CApi.SQLITE_RANGE),
+ SQLITE_NOTADB(CApi.SQLITE_NOTADB),
+ SQLITE_NOTICE(CApi.SQLITE_NOTICE),
+ SQLITE_WARNING(CApi.SQLITE_WARNING),
+ SQLITE_ROW(CApi.SQLITE_ROW),
+ SQLITE_DONE(CApi.SQLITE_DONE),
+ SQLITE_ERROR_MISSING_COLLSEQ(CApi.SQLITE_ERROR_MISSING_COLLSEQ),
+ SQLITE_ERROR_RETRY(CApi.SQLITE_ERROR_RETRY),
+ SQLITE_ERROR_SNAPSHOT(CApi.SQLITE_ERROR_SNAPSHOT),
+ SQLITE_IOERR_READ(CApi.SQLITE_IOERR_READ),
+ SQLITE_IOERR_SHORT_READ(CApi.SQLITE_IOERR_SHORT_READ),
+ SQLITE_IOERR_WRITE(CApi.SQLITE_IOERR_WRITE),
+ SQLITE_IOERR_FSYNC(CApi.SQLITE_IOERR_FSYNC),
+ SQLITE_IOERR_DIR_FSYNC(CApi.SQLITE_IOERR_DIR_FSYNC),
+ SQLITE_IOERR_TRUNCATE(CApi.SQLITE_IOERR_TRUNCATE),
+ SQLITE_IOERR_FSTAT(CApi.SQLITE_IOERR_FSTAT),
+ SQLITE_IOERR_UNLOCK(CApi.SQLITE_IOERR_UNLOCK),
+ SQLITE_IOERR_RDLOCK(CApi.SQLITE_IOERR_RDLOCK),
+ SQLITE_IOERR_DELETE(CApi.SQLITE_IOERR_DELETE),
+ SQLITE_IOERR_BLOCKED(CApi.SQLITE_IOERR_BLOCKED),
+ SQLITE_IOERR_NOMEM(CApi.SQLITE_IOERR_NOMEM),
+ SQLITE_IOERR_ACCESS(CApi.SQLITE_IOERR_ACCESS),
+ SQLITE_IOERR_CHECKRESERVEDLOCK(CApi.SQLITE_IOERR_CHECKRESERVEDLOCK),
+ SQLITE_IOERR_LOCK(CApi.SQLITE_IOERR_LOCK),
+ SQLITE_IOERR_CLOSE(CApi.SQLITE_IOERR_CLOSE),
+ SQLITE_IOERR_DIR_CLOSE(CApi.SQLITE_IOERR_DIR_CLOSE),
+ SQLITE_IOERR_SHMOPEN(CApi.SQLITE_IOERR_SHMOPEN),
+ SQLITE_IOERR_SHMSIZE(CApi.SQLITE_IOERR_SHMSIZE),
+ SQLITE_IOERR_SHMLOCK(CApi.SQLITE_IOERR_SHMLOCK),
+ SQLITE_IOERR_SHMMAP(CApi.SQLITE_IOERR_SHMMAP),
+ SQLITE_IOERR_SEEK(CApi.SQLITE_IOERR_SEEK),
+ SQLITE_IOERR_DELETE_NOENT(CApi.SQLITE_IOERR_DELETE_NOENT),
+ SQLITE_IOERR_MMAP(CApi.SQLITE_IOERR_MMAP),
+ SQLITE_IOERR_GETTEMPPATH(CApi.SQLITE_IOERR_GETTEMPPATH),
+ SQLITE_IOERR_CONVPATH(CApi.SQLITE_IOERR_CONVPATH),
+ SQLITE_IOERR_VNODE(CApi.SQLITE_IOERR_VNODE),
+ SQLITE_IOERR_AUTH(CApi.SQLITE_IOERR_AUTH),
+ SQLITE_IOERR_BEGIN_ATOMIC(CApi.SQLITE_IOERR_BEGIN_ATOMIC),
+ SQLITE_IOERR_COMMIT_ATOMIC(CApi.SQLITE_IOERR_COMMIT_ATOMIC),
+ SQLITE_IOERR_ROLLBACK_ATOMIC(CApi.SQLITE_IOERR_ROLLBACK_ATOMIC),
+ SQLITE_IOERR_DATA(CApi.SQLITE_IOERR_DATA),
+ SQLITE_IOERR_CORRUPTFS(CApi.SQLITE_IOERR_CORRUPTFS),
+ SQLITE_LOCKED_SHAREDCACHE(CApi.SQLITE_LOCKED_SHAREDCACHE),
+ SQLITE_LOCKED_VTAB(CApi.SQLITE_LOCKED_VTAB),
+ SQLITE_BUSY_RECOVERY(CApi.SQLITE_BUSY_RECOVERY),
+ SQLITE_BUSY_SNAPSHOT(CApi.SQLITE_BUSY_SNAPSHOT),
+ SQLITE_BUSY_TIMEOUT(CApi.SQLITE_BUSY_TIMEOUT),
+ SQLITE_CANTOPEN_NOTEMPDIR(CApi.SQLITE_CANTOPEN_NOTEMPDIR),
+ SQLITE_CANTOPEN_ISDIR(CApi.SQLITE_CANTOPEN_ISDIR),
+ SQLITE_CANTOPEN_FULLPATH(CApi.SQLITE_CANTOPEN_FULLPATH),
+ SQLITE_CANTOPEN_CONVPATH(CApi.SQLITE_CANTOPEN_CONVPATH),
+ SQLITE_CANTOPEN_SYMLINK(CApi.SQLITE_CANTOPEN_SYMLINK),
+ SQLITE_CORRUPT_VTAB(CApi.SQLITE_CORRUPT_VTAB),
+ SQLITE_CORRUPT_SEQUENCE(CApi.SQLITE_CORRUPT_SEQUENCE),
+ SQLITE_CORRUPT_INDEX(CApi.SQLITE_CORRUPT_INDEX),
+ SQLITE_READONLY_RECOVERY(CApi.SQLITE_READONLY_RECOVERY),
+ SQLITE_READONLY_CANTLOCK(CApi.SQLITE_READONLY_CANTLOCK),
+ SQLITE_READONLY_ROLLBACK(CApi.SQLITE_READONLY_ROLLBACK),
+ SQLITE_READONLY_DBMOVED(CApi.SQLITE_READONLY_DBMOVED),
+ SQLITE_READONLY_CANTINIT(CApi.SQLITE_READONLY_CANTINIT),
+ SQLITE_READONLY_DIRECTORY(CApi.SQLITE_READONLY_DIRECTORY),
+ SQLITE_ABORT_ROLLBACK(CApi.SQLITE_ABORT_ROLLBACK),
+ SQLITE_CONSTRAINT_CHECK(CApi.SQLITE_CONSTRAINT_CHECK),
+ SQLITE_CONSTRAINT_COMMITHOOK(CApi.SQLITE_CONSTRAINT_COMMITHOOK),
+ SQLITE_CONSTRAINT_FOREIGNKEY(CApi.SQLITE_CONSTRAINT_FOREIGNKEY),
+ SQLITE_CONSTRAINT_FUNCTION(CApi.SQLITE_CONSTRAINT_FUNCTION),
+ SQLITE_CONSTRAINT_NOTNULL(CApi.SQLITE_CONSTRAINT_NOTNULL),
+ SQLITE_CONSTRAINT_PRIMARYKEY(CApi.SQLITE_CONSTRAINT_PRIMARYKEY),
+ SQLITE_CONSTRAINT_TRIGGER(CApi.SQLITE_CONSTRAINT_TRIGGER),
+ SQLITE_CONSTRAINT_UNIQUE(CApi.SQLITE_CONSTRAINT_UNIQUE),
+ SQLITE_CONSTRAINT_VTAB(CApi.SQLITE_CONSTRAINT_VTAB),
+ SQLITE_CONSTRAINT_ROWID(CApi.SQLITE_CONSTRAINT_ROWID),
+ SQLITE_CONSTRAINT_PINNED(CApi.SQLITE_CONSTRAINT_PINNED),
+ SQLITE_CONSTRAINT_DATATYPE(CApi.SQLITE_CONSTRAINT_DATATYPE),
+ SQLITE_NOTICE_RECOVER_WAL(CApi.SQLITE_NOTICE_RECOVER_WAL),
+ SQLITE_NOTICE_RECOVER_ROLLBACK(CApi.SQLITE_NOTICE_RECOVER_ROLLBACK),
+ SQLITE_WARNING_AUTOINDEX(CApi.SQLITE_WARNING_AUTOINDEX),
+ SQLITE_AUTH_USER(CApi.SQLITE_AUTH_USER),
+ SQLITE_OK_LOAD_PERMANENTLY(CApi.SQLITE_OK_LOAD_PERMANENTLY);
+
+ public final int value;
+
+ ResultCode(int rc){
+ value = rc;
+ ResultCodeMap.set(rc, this);
+ }
+
+ /**
+ Returns the entry from this enum for the given result code, or
+ null if no match is found.
+ */
+ public static ResultCode getEntryForInt(int rc){
+ return ResultCodeMap.get(rc);
+ }
+
+ /**
+ Internal level of indirection required because we cannot initialize
+ static enum members in an enum before the enum constructor is
+ invoked.
+ */
+ private static final class ResultCodeMap {
+ private static final java.util.Map<Integer,ResultCode> 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/capi/RollbackHookCallback.java b/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java
new file mode 100644
index 0000000..cf9c4b6
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java
@@ -0,0 +1,26 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_rollback_hook}.
+*/
+public interface RollbackHookCallback extends CallbackProxy {
+ /**
+ Must function as documented for the C-level sqlite3_rollback_hook()
+ callback. If it throws, the exception is translated into
+ a db-level error.
+ */
+ void call();
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java b/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java
new file mode 100644
index 0000000..7ad1381
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java
@@ -0,0 +1,36 @@
+/*
+** 2023-07-22
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ SQLFunction is used in conjunction with the
+ sqlite3_create_function() JNI-bound API to give that native code
+ access to the callback functions needed in order to implement SQL
+ functions in Java.
+
+ <p>
+
+ 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<T>, and Window<T>. Most simply, clients may subclass
+ those, or create anonymous classes from them, to implement
+ UDFs. Clients are free to create their own classes for use with
+ UDFs, so long as they conform to the public interfaces defined by
+ those three classes. The JNI layer only actively relies on the
+ SQLFunction base class and the method names and signatures used by
+ the UDF callback interfaces.
+*/
+public interface SQLFunction {
+
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/SQLTester.java b/ext/jni/src/org/sqlite/jni/capi/SQLTester.java
new file mode 100644
index 0000000..81d6106
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/SQLTester.java
@@ -0,0 +1,1433 @@
+/*
+** 2023-08-08
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the main application entry pointer for the
+** SQLTester framework.
+*/
+package org.sqlite.jni.capi;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.nio.charset.StandardCharsets;
+import java.util.regex.*;
+import static org.sqlite.jni.capi.CApi.*;
+
+/**
+ Modes for how to escape (or not) column values and names from
+ SQLTester.execSql() to the result buffer output.
+*/
+enum ResultBufferMode {
+ //! Do not append to result buffer
+ NONE,
+ //! Append output escaped.
+ ESCAPED,
+ //! Append output as-is
+ ASIS
+};
+
+/**
+ Modes to specify how to emit multi-row output from
+ SQLTester.execSql() to the result buffer.
+*/
+enum ResultRowMode {
+ //! Keep all result rows on one line, space-separated.
+ ONELINE,
+ //! Add a newline between each result row.
+ NEWLINE
+};
+
+/**
+ Base exception type for test-related failures.
+*/
+class SQLTesterException extends RuntimeException {
+ private boolean bFatal = false;
+
+ SQLTesterException(String msg){
+ super(msg);
+ }
+
+ protected SQLTesterException(String msg, boolean fatal){
+ super(msg);
+ bFatal = fatal;
+ }
+
+ /**
+ Indicates whether the framework should consider this exception
+ type as immediately fatal to the test run or not.
+ */
+ final boolean isFatal(){ return bFatal; }
+}
+
+class DbException extends SQLTesterException {
+ DbException(sqlite3 db, int rc, boolean closeDb){
+ super("DB error #"+rc+": "+sqlite3_errmsg(db),true);
+ if( closeDb ) sqlite3_close_v2(db);
+ }
+ DbException(sqlite3 db, int rc){
+ this(db, rc, false);
+ }
+}
+
+/**
+ Generic test-failed exception.
+ */
+class TestScriptFailed extends SQLTesterException {
+ public TestScriptFailed(TestScript ts, String msg){
+ super(ts.getOutputPrefix()+": "+msg, true);
+ }
+}
+
+/**
+ Thrown when an unknown test command is encountered in a script.
+*/
+class UnknownCommand extends SQLTesterException {
+ public UnknownCommand(TestScript ts, String cmd){
+ super(ts.getOutputPrefix()+": unknown command: "+cmd, false);
+ }
+}
+
+/**
+ Thrown when an "incompatible directive" is found in a script. This
+ can be the presence of a C-preprocessor construct, specific
+ metadata tags within a test script's header, or specific test
+ constructs which are incompatible with this particular
+ implementation.
+*/
+class IncompatibleDirective extends SQLTesterException {
+ public IncompatibleDirective(TestScript ts, String line){
+ super(ts.getOutputPrefix()+": incompatible directive: "+line, false);
+ }
+}
+
+/**
+ Console output utility class.
+*/
+class Outer {
+ private int verbosity = 0;
+
+ static void out(Object val){
+ System.out.print(val);
+ }
+
+ Outer out(Object... vals){
+ for(Object v : vals) out(v);
+ return this;
+ }
+
+ Outer outln(Object... vals){
+ out(vals).out("\n");
+ return this;
+ }
+
+ Outer verbose(Object... vals){
+ if(verbosity>0){
+ out("VERBOSE",(verbosity>1 ? "+: " : ": ")).outln(vals);
+ }
+ return this;
+ }
+
+ void setVerbosity(int level){
+ verbosity = level;
+ }
+
+ int getVerbosity(){
+ return verbosity;
+ }
+
+ public boolean isVerbose(){return verbosity > 0;}
+
+}
+
+/**
+ <p>This class provides an application which aims to implement the
+ rudimentary SQL-driven test tool described in the accompanying
+ {@code test-script-interpreter.md}.
+
+ <p>This class is an internal testing tool, not part of the public
+ interface but is (A) in the same package as the library because
+ access permissions require it to be so and (B) the JDK8 javadoc
+ offers no way to filter individual classes out of the doc
+ generation process (it can only exclude packages, but see (A)).
+
+ <p>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<String> listInFiles = new ArrayList<>();
+ //! Console output utility.
+ private final Outer outer = new Outer();
+ //! Test input buffer.
+ private final StringBuilder inputBuffer = new StringBuilder();
+ //! Test result buffer.
+ private final StringBuilder resultBuffer = new StringBuilder();
+ //! Buffer for REQUIRED_PROPERTIES pragmas.
+ private final StringBuilder dbInitSql = new StringBuilder();
+ //! Output representation of SQL NULL.
+ private String nullView = "nil";
+ //! Total tests run.
+ private int nTotalTest = 0;
+ //! Total test script files run.
+ private int nTestFile = 0;
+ //! Number of scripts which were aborted.
+ private int nAbortedScript = 0;
+ //! Incremented by test case handlers
+ private int nTest = 0;
+ //! True to enable column name output from execSql()
+ private boolean emitColNames;
+ //! True to keep going regardless of how a test fails.
+ private boolean keepGoing = false;
+ //! The list of available db handles.
+ private final sqlite3[] aDb = new sqlite3[7];
+ //! Index into aDb of the current db.
+ private int iCurrentDb = 0;
+ //! Name of the default db, re-created for each script.
+ private final String initialDbName = "test.db";
+
+
+ public SQLTester(){
+ reset();
+ }
+
+ void setVerbosity(int level){
+ this.outer.setVerbosity( level );
+ }
+ int getVerbosity(){
+ return this.outer.getVerbosity();
+ }
+ boolean isVerbose(){
+ return this.outer.isVerbose();
+ }
+
+ void outputColumnNames(boolean b){ emitColNames = b; }
+
+ void verbose(Object... vals){
+ outer.verbose(vals);
+ }
+
+ void outln(Object... vals){
+ outer.outln(vals);
+ }
+
+ void out(Object... vals){
+ outer.out(vals);
+ }
+
+ //! Adds the given test script to the to-test list.
+ public void addTestScript(String filename){
+ listInFiles.add(filename);
+ //verbose("Added file ",filename);
+ }
+
+ private void setupInitialDb() throws DbException {
+ if( null==aDb[0] ){
+ Util.unlink(initialDbName);
+ openDb(0, initialDbName, true);
+ }else{
+ outln("WARNING: setupInitialDb() unexpectedly ",
+ "triggered while it is opened.");
+ }
+ }
+
+ static final String[] startEmoji = {
+ "🚴", "🏄", "🏇", "🤸", "⛹", "🏊", "⛷", "🧗", "🏋"
+ };
+ static final int nStartEmoji = startEmoji.length;
+ static int iStartEmoji = 0;
+
+ private static String nextStartEmoji(){
+ return startEmoji[iStartEmoji++ % nStartEmoji];
+ }
+
+ public void runTests() throws Exception {
+ final long tStart = System.currentTimeMillis();
+ for(String f : listInFiles){
+ reset();
+ ++nTestFile;
+ final TestScript ts = new TestScript(f);
+ outln(nextStartEmoji(), " starting [",f,"]");
+ boolean threw = false;
+ final long timeStart = System.currentTimeMillis();
+ try{
+ ts.run(this);
+ }catch(SQLTesterException e){
+ threw = true;
+ outln("🔥EXCEPTION: ",e.getClass().getSimpleName(),": ",e.getMessage());
+ ++nAbortedScript;
+ if( keepGoing ) outln("Continuing anyway becaure of the keep-going option.");
+ else if( e.isFatal() ) throw e;
+ }finally{
+ final long timeEnd = System.currentTimeMillis();
+ outln("🏁",(threw ? "❌" : "✅")," ",nTest," test(s) in ",
+ (timeEnd-timeStart),"ms.");
+ }
+ }
+ final long tEnd = System.currentTimeMillis();
+ outln("Total run-time: ",(tEnd-tStart),"ms");
+ Util.unlink(initialDbName);
+ }
+
+ private StringBuilder clearBuffer(StringBuilder b){
+ b.setLength(0);;
+ return b;
+ }
+
+ StringBuilder clearInputBuffer(){
+ return clearBuffer(inputBuffer);
+ }
+
+ StringBuilder clearResultBuffer(){
+ return clearBuffer(resultBuffer);
+ }
+
+ StringBuilder getInputBuffer(){ return inputBuffer; }
+
+ void appendInput(String n, boolean addNL){
+ inputBuffer.append(n);
+ if(addNL) inputBuffer.append('\n');
+ }
+
+ void appendResult(String n, boolean addNL){
+ resultBuffer.append(n);
+ if(addNL) resultBuffer.append('\n');
+ }
+
+ void appendDbInitSql(String n) throws DbException {
+ dbInitSql.append(n).append('\n');
+ if( null!=getCurrentDb() ){
+ //outln("RUNNING DB INIT CODE: ",n);
+ execSql(null, true, ResultBufferMode.NONE, null, n);
+ }
+ }
+ String getDbInitSql(){ return dbInitSql.toString(); }
+
+ String getInputText(){ return inputBuffer.toString(); }
+
+ String getResultText(){ return resultBuffer.toString(); }
+
+ private String takeBuffer(StringBuilder b){
+ final String rc = b.toString();
+ clearBuffer(b);
+ return rc;
+ }
+
+ String takeInputBuffer(){ return takeBuffer(inputBuffer); }
+
+ String takeResultBuffer(){ return takeBuffer(resultBuffer); }
+
+ int getCurrentDbId(){ return iCurrentDb; }
+
+ SQLTester affirmDbId(int n) throws IndexOutOfBoundsException {
+ if(n<0 || n>=aDb.length){
+ throw new IndexOutOfBoundsException("illegal db number: "+n);
+ }
+ return this;
+ }
+
+ sqlite3 setCurrentDb(int n) throws Exception{
+ affirmDbId(n);
+ iCurrentDb = n;
+ return this.aDb[n];
+ }
+
+ sqlite3 getCurrentDb(){ return aDb[iCurrentDb]; }
+
+ sqlite3 getDbById(int id) throws Exception{
+ return affirmDbId(id).aDb[id];
+ }
+
+ void closeDb(int id) {
+ final sqlite3 db = affirmDbId(id).aDb[id];
+ if( null != db ){
+ sqlite3_close_v2(db);
+ aDb[id] = null;
+ }
+ }
+
+ void closeDb() { closeDb(iCurrentDb); }
+
+ void closeAllDbs(){
+ for(int i = 0; i<aDb.length; ++i){
+ sqlite3_close_v2(aDb[i]);
+ aDb[i] = null;
+ }
+ }
+
+ sqlite3 openDb(String name, boolean createIfNeeded) throws DbException {
+ closeDb();
+ int flags = SQLITE_OPEN_READWRITE;
+ if( createIfNeeded ) flags |= SQLITE_OPEN_CREATE;
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ int rc = sqlite3_open_v2(name, out, flags, null);
+ final sqlite3 db = out.take();
+ if( 0==rc && dbInitSql.length() > 0){
+ //outln("RUNNING DB INIT CODE: ",dbInitSql.toString());
+ rc = execSql(db, false, ResultBufferMode.NONE,
+ null, dbInitSql.toString());
+ }
+ if( 0!=rc ){
+ throw new DbException(db, rc, true);
+ }
+ return aDb[iCurrentDb] = db;
+ }
+
+ sqlite3 openDb(int slot, String name, boolean createIfNeeded) throws DbException {
+ affirmDbId(slot);
+ iCurrentDb = slot;
+ return openDb(name, createIfNeeded);
+ }
+
+ /**
+ Resets all tester context state except for that related to
+ tracking running totals.
+ */
+ void reset(){
+ clearInputBuffer();
+ clearResultBuffer();
+ clearBuffer(dbInitSql);
+ closeAllDbs();
+ nTest = 0;
+ nullView = "nil";
+ emitColNames = false;
+ iCurrentDb = 0;
+ //dbInitSql.append("SELECT 1;");
+ }
+
+ void setNullValue(String v){nullView = v;}
+
+ /**
+ If true, encountering an unknown command in a script causes the
+ remainder of the script to be skipped, rather than aborting the
+ whole script run.
+ */
+ boolean skipUnknownCommands(){
+ // Currently hard-coded. Potentially a flag someday.
+ return true;
+ }
+
+ void incrementTestCounter(){ ++nTest; ++nTotalTest; }
+
+ //! "Special" characters - we have to escape output if it contains any.
+ static final Pattern patternSpecial = Pattern.compile(
+ "[\\x00-\\x20\\x22\\x5c\\x7b\\x7d]"
+ );
+ //! Either of '{' or '}'.
+ static final Pattern patternSquiggly = Pattern.compile("[{}]");
+
+ /**
+ Returns v or some escaped form of v, as defined in the tester's
+ spec doc.
+ */
+ String escapeSqlValue(String v){
+ if( "".equals(v) ) return "{}";
+ Matcher m = patternSpecial.matcher(v);
+ if( !m.find() ){
+ return v /* no escaping needed */;
+ }
+ m = patternSquiggly.matcher(v);
+ if( !m.find() ){
+ return "{"+v+"}";
+ }
+ final StringBuilder sb = new StringBuilder("\"");
+ final int n = v.length();
+ for(int i = 0; i < n; ++i){
+ final char ch = v.charAt(i);
+ switch(ch){
+ case '\\': sb.append("\\\\"); break;
+ case '"': sb.append("\\\""); break;
+ default:
+ //verbose("CHAR ",(int)ch," ",ch," octal=",String.format("\\%03o", (int)ch));
+ if( (int)ch < 32 ) sb.append(String.format("\\%03o", (int)ch));
+ else sb.append(ch);
+ break;
+ }
+ }
+ sb.append("\"");
+ return sb.toString();
+ }
+
+ private void appendDbErr(sqlite3 db, StringBuilder sb, int rc){
+ sb.append(org.sqlite.jni.capi.ResultCode.getEntryForInt(rc)).append(' ');
+ final String msg = escapeSqlValue(sqlite3_errmsg(db));
+ if( '{' == msg.charAt(0) ){
+ sb.append(msg);
+ }else{
+ sb.append('{').append(msg).append('}');
+ }
+ }
+
+ /**
+ Runs SQL on behalf of test commands and outputs the results following
+ the very specific rules of the test framework.
+
+ If db is null, getCurrentDb() is assumed. If throwOnError is true then
+ any db-side error will result in an exception, else they result in
+ the db's result code.
+
+ appendMode specifies how/whether to append results to the result
+ buffer. rowMode specifies whether to output all results in a
+ single line or one line per row. If appendMode is
+ ResultBufferMode.NONE then rowMode is ignored and may be null.
+ */
+ public int execSql(sqlite3 db, boolean throwOnError,
+ ResultBufferMode appendMode, ResultRowMode rowMode,
+ String sql) throws SQLTesterException {
+ if( null==db && null==aDb[0] ){
+ // Delay opening of the initial db to enable tests to change its
+ // name and inject on-connect code via, e.g., the MEMDB
+ // directive. this setup as the potential to misinteract with
+ // auto-extension timing and must be done carefully.
+ setupInitialDb();
+ }
+ final OutputPointer.Int32 oTail = new OutputPointer.Int32();
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+ final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8);
+ if( null==db ) db = getCurrentDb();
+ int pos = 0, n = 1;
+ byte[] sqlChunk = sqlUtf8;
+ int rc = 0;
+ sqlite3_stmt stmt = null;
+ int spacing = 0 /* emit a space for --result if>0 */ ;
+ final StringBuilder sb = (ResultBufferMode.NONE==appendMode)
+ ? null : resultBuffer;
+ //outln("sqlChunk len= = ",sqlChunk.length);
+ try{
+ while(pos < sqlChunk.length){
+ if(pos > 0){
+ sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
+ sqlChunk.length);
+ }
+ if( 0==sqlChunk.length ) break;
+ rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail);
+ /*outln("PREPARE rc ",rc," oTail=",oTail.get(),": ",
+ new String(sqlChunk,StandardCharsets.UTF_8),"\n<EOSQL>");*/
+ if( 0!=rc ){
+ if(throwOnError){
+ throw new DbException(db, rc);
+ }else if( null!=sb ){
+ appendDbErr(db, sb, rc);
+ }
+ break;
+ }
+ pos = oTail.value;
+ stmt = outStmt.take();
+ if( null == stmt ){
+ // empty statement was parsed.
+ continue;
+ }
+ if( null!=sb ){
+ // Add the output to the result buffer...
+ final int nCol = sqlite3_column_count(stmt);
+ String colName = null, val = null;
+ while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){
+ for(int i = 0; i < nCol; ++i){
+ if( spacing++ > 0 ) sb.append(' ');
+ if( emitColNames ){
+ colName = sqlite3_column_name(stmt, i);
+ switch(appendMode){
+ case ASIS:
+ sb.append( colName );
+ break;
+ case ESCAPED:
+ sb.append( escapeSqlValue(colName) );
+ break;
+ default:
+ throw new SQLTesterException("Unhandled ResultBufferMode: "+appendMode);
+ }
+ sb.append(' ');
+ }
+ val = sqlite3_column_text16(stmt, i);
+ if( null==val ){
+ sb.append( nullView );
+ continue;
+ }
+ switch(appendMode){
+ case ASIS:
+ sb.append( val );
+ break;
+ case ESCAPED:
+ sb.append( escapeSqlValue(val) );
+ break;
+ default:
+ throw new SQLTesterException("Unhandled ResultBufferMode: "+appendMode);
+ }
+ }
+ if( ResultRowMode.NEWLINE == rowMode ){
+ spacing = 0;
+ sb.append('\n');
+ }
+ }
+ }else{
+ while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){}
+ }
+ sqlite3_finalize(stmt);
+ stmt = null;
+ if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0;
+ else if( rc!=0 ){
+ if( null!=sb ){
+ appendDbErr(db, sb, rc);
+ }
+ break;
+ }
+ }
+ }finally{
+ sqlite3_reset(stmt
+ /* In order to trigger an exception in the
+ INSERT...RETURNING locking scenario:
+ https://sqlite.org/forum/forumpost/36f7a2e7494897df */);
+ sqlite3_finalize(stmt);
+ }
+ if( 0!=rc && throwOnError ){
+ throw new DbException(db, rc);
+ }
+ return rc;
+ }
+
+ public static void main(String[] argv) throws Exception{
+ installCustomExtensions();
+ boolean dumpInternals = false;
+ final SQLTester t = new SQLTester();
+ for(String a : argv){
+ if(a.startsWith("-")){
+ final String flag = a.replaceFirst("-+","");
+ if( flag.equals("verbose") ){
+ // Use --verbose up to 3 times
+ t.setVerbosity(t.getVerbosity() + 1);
+ }else if( flag.equals("keep-going") ){
+ t.keepGoing = true;
+ }else if( flag.equals("internals") ){
+ dumpInternals = true;
+ }else{
+ throw new IllegalArgumentException("Unhandled flag: "+flag);
+ }
+ continue;
+ }
+ t.addTestScript(a);
+ }
+ final AutoExtensionCallback ax = new AutoExtensionCallback() {
+ private final SQLTester tester = t;
+ @Override public int call(sqlite3 db){
+ final String init = tester.getDbInitSql();
+ if( !init.isEmpty() ){
+ tester.execSql(db, true, ResultBufferMode.NONE, null, init);
+ }
+ return 0;
+ }
+ };
+ sqlite3_auto_extension(ax);
+ try {
+ t.runTests();
+ }finally{
+ sqlite3_cancel_auto_extension(ax);
+ t.outln("Processed ",t.nTotalTest," test(s) in ",t.nTestFile," file(s).");
+ if( t.nAbortedScript > 0 ){
+ t.outln("Aborted ",t.nAbortedScript," script(s).");
+ }
+ if( dumpInternals ){
+ sqlite3_jni_internal_details();
+ }
+ }
+ }
+
+ /**
+ Internal impl of the public strglob() method. Neither argument
+ may be NULL and both _MUST_ be NUL-terminated.
+ */
+ private static native int strglob(byte[] glob, byte[] txt);
+
+ /**
+ Works essentially the same as sqlite3_strglob() except that the
+ glob character '#' matches a sequence of one or more digits. It
+ does not match when it appears at the start or middle of a series
+ of digits, e.g. "#23" or "1#3", but will match at the end,
+ e.g. "12#".
+ */
+ static int strglob(String glob, String txt){
+ return strglob(
+ (glob+"\0").getBytes(StandardCharsets.UTF_8),
+ (txt+"\0").getBytes(StandardCharsets.UTF_8)
+ );
+ }
+
+ /**
+ Sets up C-side components needed by the test framework. This must
+ not be called until main() is triggered so that it does not
+ interfere with library clients who don't use this class.
+ */
+ static native void installCustomExtensions();
+ static {
+ System.loadLibrary("sqlite3-jni")
+ /* Interestingly, when SQLTester is the main app, we have to
+ load that lib from here. The same load from CApi does
+ not happen early enough. Without this,
+ installCustomExtensions() is an unresolved symbol. */;
+ }
+
+}
+
+/**
+ General utilities for the SQLTester bits.
+*/
+final class Util {
+
+ //! Throws a new T, appending all msg args into a string for the message.
+ static void toss(Class<? extends Exception> errorType, Object... msg) throws Exception {
+ StringBuilder sb = new StringBuilder();
+ for(Object s : msg) sb.append(s);
+ final java.lang.reflect.Constructor<? extends Exception> ctor =
+ errorType.getConstructor(String.class);
+ throw ctor.newInstance(sb.toString());
+ }
+
+ static void toss(Object... msg) throws Exception{
+ toss(RuntimeException.class, msg);
+ }
+
+ //! Tries to delete the given file, silently ignoring failure.
+ static void unlink(String filename){
+ try{
+ final java.io.File f = new java.io.File(filename);
+ f.delete();
+ }catch(Exception e){
+ /* ignore */
+ }
+ }
+
+ /**
+ Appends all entries in argv[1..end] into a space-separated
+ string, argv[0] is not included because it's expected to be a
+ command name.
+ */
+ static String argvToString(String[] argv){
+ StringBuilder sb = new StringBuilder();
+ for(int i = 1; i < argv.length; ++i ){
+ if( i>1 ) sb.append(" ");
+ sb.append( argv[i] );
+ }
+ return sb.toString();
+ }
+
+}
+
+/**
+ Base class for test script commands. It provides a set of utility
+ APIs for concrete command implementations.
+
+ Each subclass must have a public no-arg ctor and must implement
+ the process() method which is abstract in this class.
+
+ Commands are intended to be stateless, except perhaps for counters
+ and similar internals. Specifically, no state which changes the
+ behavior between any two invocations of process() should be
+ retained.
+*/
+abstract class Command {
+ protected Command(){}
+
+ /**
+ Must process one command-unit of work and either return
+ (on success) or throw (on error).
+
+ The first two arguments specify the context of the test. The TestScript
+ provides the content of the test and the SQLTester providers the sandbox
+ in which that script is being evaluated.
+
+ argv is a list with the command name followed by any arguments to
+ that command. The argcCheck() method from this class provides
+ very basic argc validation.
+ */
+ public abstract void process(
+ SQLTester st, TestScript ts, String[] argv
+ ) throws Exception;
+
+ /**
+ If argv.length-1 (-1 because the command's name is in argv[0]) does not
+ fall in the inclusive range (min,max) then this function throws. Use
+ a max value of -1 to mean unlimited.
+ */
+ protected final void argcCheck(TestScript ts, String[] argv, int min, int max) throws Exception{
+ int argc = argv.length-1;
+ if(argc<min || (max>=0 && argc>max)){
+ if( min==max ){
+ ts.toss(argv[0]," requires exactly ",min," argument(s)");
+ }else if(max>0){
+ ts.toss(argv[0]," requires ",min,"-",max," arguments.");
+ }else{
+ ts.toss(argv[0]," requires at least ",min," arguments.");
+ }
+ }
+ }
+
+ /**
+ Equivalent to argcCheck(argv,argc,argc).
+ */
+ protected final void argcCheck(TestScript ts, String[] argv, int argc) throws Exception{
+ argcCheck(ts, argv, argc, argc);
+ }
+}
+
+//! --close command
+class CloseDbCommand extends Command {
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,0,1);
+ Integer id;
+ if(argv.length>1){
+ String arg = argv[1];
+ if("all".equals(arg)){
+ t.closeAllDbs();
+ return;
+ }
+ else{
+ id = Integer.parseInt(arg);
+ }
+ }else{
+ id = t.getCurrentDbId();
+ }
+ t.closeDb(id);
+ }
+}
+
+//! --column-names command
+class ColumnNamesCommand extends Command {
+ public void process(
+ SQLTester st, TestScript ts, String[] argv
+ ) throws Exception{
+ argcCheck(ts,argv,1);
+ st.outputColumnNames( Integer.parseInt(argv[1])!=0 );
+ }
+}
+
+//! --db command
+class DbCommand extends Command {
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,1);
+ t.setCurrentDb( Integer.parseInt(argv[1]) );
+ }
+}
+
+//! --glob command
+class GlobCommand extends Command {
+ private boolean negate = false;
+ public GlobCommand(){}
+ protected GlobCommand(boolean negate){ this.negate = negate; }
+
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,1,-1);
+ t.incrementTestCounter();
+ final String sql = t.takeInputBuffer();
+ int rc = t.execSql(null, true, ResultBufferMode.ESCAPED,
+ ResultRowMode.ONELINE, sql);
+ final String result = t.getResultText();
+ final String sArgs = Util.argvToString(argv);
+ //t2.verbose2(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs);
+ final String glob = Util.argvToString(argv);
+ rc = SQLTester.strglob(glob, result);
+ if( (negate && 0==rc) || (!negate && 0!=rc) ){
+ ts.toss(argv[0], " mismatch: ", glob," vs input: ",result);
+ }
+ }
+}
+
+//! --json command
+class JsonCommand extends ResultCommand {
+ public JsonCommand(){ super(ResultBufferMode.ASIS); }
+}
+
+//! --json-block command
+class JsonBlockCommand extends TableResultCommand {
+ public JsonBlockCommand(){ super(true); }
+}
+
+//! --new command
+class NewDbCommand extends OpenDbCommand {
+ public NewDbCommand(){ super(true); }
+}
+
+//! Placeholder dummy/no-op commands
+class NoopCommand extends Command {
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ }
+}
+
+//! --notglob command
+class NotGlobCommand extends GlobCommand {
+ public NotGlobCommand(){
+ super(true);
+ }
+}
+
+//! --null command
+class NullCommand extends Command {
+ public void process(
+ SQLTester st, TestScript ts, String[] argv
+ ) throws Exception{
+ argcCheck(ts,argv,1);
+ st.setNullValue( argv[1] );
+ }
+}
+
+//! --open command
+class OpenDbCommand extends Command {
+ private boolean createIfNeeded = false;
+ public OpenDbCommand(){}
+ protected OpenDbCommand(boolean c){createIfNeeded = c;}
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,1);
+ t.openDb(argv[1], createIfNeeded);
+ }
+}
+
+//! --print command
+class PrintCommand extends Command {
+ public void process(
+ SQLTester st, TestScript ts, String[] argv
+ ) throws Exception{
+ st.out(ts.getOutputPrefix(),": ");
+ if( 1==argv.length ){
+ st.out( st.getInputText() );
+ }else{
+ st.outln( Util.argvToString(argv) );
+ }
+ }
+}
+
+//! --result command
+class ResultCommand extends Command {
+ private final ResultBufferMode bufferMode;
+ protected ResultCommand(ResultBufferMode bm){ bufferMode = bm; }
+ public ResultCommand(){ this(ResultBufferMode.ESCAPED); }
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,0,-1);
+ t.incrementTestCounter();
+ final String sql = t.takeInputBuffer();
+ //ts.verbose2(argv[0]," SQL =\n",sql);
+ int rc = t.execSql(null, false, bufferMode, ResultRowMode.ONELINE, sql);
+ final String result = t.getResultText().trim();
+ final String sArgs = argv.length>1 ? Util.argvToString(argv) : "";
+ if( !result.equals(sArgs) ){
+ t.outln(argv[0]," FAILED comparison. Result buffer:\n",
+ result,"\nExpected result:\n",sArgs);
+ ts.toss(argv[0]+" comparison failed.");
+ }
+ }
+}
+
+//! --run command
+class RunCommand extends Command {
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,0,1);
+ final sqlite3 db = (1==argv.length)
+ ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) );
+ final String sql = t.takeInputBuffer();
+ final int rc = t.execSql(db, false, ResultBufferMode.NONE,
+ ResultRowMode.ONELINE, sql);
+ if( 0!=rc && t.isVerbose() ){
+ String msg = sqlite3_errmsg(db);
+ ts.verbose1(argv[0]," non-fatal command error #",rc,": ",
+ msg,"\nfor SQL:\n",sql);
+ }
+ }
+}
+
+//! --tableresult command
+class TableResultCommand extends Command {
+ private final boolean jsonMode;
+ protected TableResultCommand(boolean jsonMode){ this.jsonMode = jsonMode; }
+ public TableResultCommand(){ this(false); }
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,0);
+ t.incrementTestCounter();
+ String body = ts.fetchCommandBody(t);
+ if( null==body ) ts.toss("Missing ",argv[0]," body.");
+ body = body.trim();
+ if( !body.endsWith("\n--end") ){
+ ts.toss(argv[0], " must be terminated with --end.");
+ }else{
+ body = body.substring(0, body.length()-6);
+ }
+ final String[] globs = body.split("\\s*\\n\\s*");
+ if( globs.length < 1 ){
+ ts.toss(argv[0], " requires 1 or more ",
+ (jsonMode ? "json snippets" : "globs"),".");
+ }
+ final String sql = t.takeInputBuffer();
+ t.execSql(null, true,
+ jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED,
+ ResultRowMode.NEWLINE, sql);
+ final String rbuf = t.getResultText();
+ final String[] res = rbuf.split("\n");
+ if( res.length != globs.length ){
+ ts.toss(argv[0], " failure: input has ", res.length,
+ " row(s) but expecting ",globs.length);
+ }
+ for(int i = 0; i < res.length; ++i){
+ final String glob = globs[i].replaceAll("\\s+"," ").trim();
+ //ts.verbose2(argv[0]," <<",glob,">> vs <<",res[i],">>");
+ if( jsonMode ){
+ if( !glob.equals(res[i]) ){
+ ts.toss(argv[0], " json <<",glob, ">> does not match: <<",
+ res[i],">>");
+ }
+ }else if( 0 != SQLTester.strglob(glob, res[i]) ){
+ ts.toss(argv[0], " glob <<",glob,">> does not match: <<",res[i],">>");
+ }
+ }
+ }
+}
+
+//! --testcase command
+class TestCaseCommand extends Command {
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,1);
+ ts.setTestCaseName(argv[1]);
+ t.clearResultBuffer();
+ t.clearInputBuffer();
+ }
+}
+
+//! --verbosity command
+class VerbosityCommand extends Command {
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,1);
+ ts.setVerbosity( Integer.parseInt(argv[1]) );
+ }
+}
+
+class CommandDispatcher {
+
+ private static java.util.Map<String,Command> 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 ? "<unnamed>" : moduleName)+"]";
+ if( null!=testCaseName ) rc += "["+testCaseName+"]";
+ if( null!=filename ) rc += "["+filename+"]";
+ return rc + " line "+ cur.lineNo;
+ }
+
+ static final String[] verboseLabel = {"🔈",/*"🔉",*/"🔊","📢"};
+ //! Output vals only if level<=current verbosity level.
+ private TestScript verboseN(int level, Object... vals){
+ final int verbosity = outer.getVerbosity();
+ if(verbosity>=level){
+ outer.out( verboseLabel[level-1], getOutputPrefix(), " ",level,": "
+ ).outln(vals);
+ }
+ return this;
+ }
+
+ TestScript verbose1(Object... vals){return verboseN(1,vals);}
+ TestScript verbose2(Object... vals){return verboseN(2,vals);}
+ TestScript verbose3(Object... vals){return verboseN(3,vals);}
+
+ private void reset(){
+ testCaseName = null;
+ cur.rewind();
+ }
+
+ void setTestCaseName(String n){ testCaseName = n; }
+
+ /**
+ Returns the next line from the buffer, minus the trailing EOL.
+
+ Returns null when all input is consumed. Throws if it reads
+ illegally-encoded input, e.g. (non-)characters in the range
+ 128-256.
+ */
+ String getLine(){
+ if( cur.pos==cur.src.length ){
+ return null /* EOF */;
+ }
+ cur.putbackPos = cur.pos;
+ cur.putbackLineNo = cur.lineNo;
+ cur.sb.setLength(0);
+ final boolean skipLeadingWs = false;
+ byte b = 0, prevB = 0;
+ int i = cur.pos;
+ if(skipLeadingWs) {
+ /* Skip any leading spaces, including newlines. This will eliminate
+ blank lines. */
+ for(; i < cur.src.length; ++i, prevB=b){
+ b = cur.src[i];
+ switch((int)b){
+ case 32/*space*/: case 9/*tab*/: case 13/*CR*/: continue;
+ case 10/*NL*/: ++cur.lineNo; continue;
+ default: break;
+ }
+ break;
+ }
+ if( i==cur.src.length ){
+ return null /* EOF */;
+ }
+ }
+ boolean doBreak = false;
+ final byte[] aChar = {0,0,0,0} /* multi-byte char buffer */;
+ int nChar = 0 /* number of bytes in the char */;
+ for(; i < cur.src.length && !doBreak; ++i){
+ b = cur.src[i];
+ switch( (int)b ){
+ case 13/*CR*/: continue;
+ case 10/*NL*/:
+ ++cur.lineNo;
+ if(cur.sb.length()>0) doBreak = true;
+ // Else it's an empty string
+ break;
+ default:
+ /* Multi-byte chars need to be gathered up and appended at
+ one time. Appending individual bytes to the StringBuffer
+ appends their integer value. */
+ nChar = 1;
+ switch( b & 0xF0 ){
+ case 0xC0: nChar = 2; break;
+ case 0xE0: nChar = 3; break;
+ case 0xF0: nChar = 4; break;
+ default:
+ if( b > 127 ) this.toss("Invalid character (#"+(int)b+").");
+ break;
+ }
+ if( 1==nChar ){
+ cur.sb.append((char)b);
+ }else{
+ for(int x = 0; x < nChar; ++x) aChar[x] = cur.src[i+x];
+ cur.sb.append(new String(Arrays.copyOf(aChar, nChar),
+ StandardCharsets.UTF_8));
+ i += nChar-1;
+ }
+ break;
+ }
+ }
+ cur.pos = i;
+ final String rv = cur.sb.toString();
+ if( i==cur.src.length && 0==rv.length() ){
+ return null /* EOF */;
+ }
+ return rv;
+ }/*getLine()*/
+
+ /**
+ Fetches the next line then resets the cursor to its pre-call
+ state. consumePeeked() can be used to consume this peeked line
+ without having to re-parse it.
+ */
+ String peekLine(){
+ final int oldPos = cur.pos;
+ final int oldPB = cur.putbackPos;
+ final int oldPBL = cur.putbackLineNo;
+ final int oldLine = cur.lineNo;
+ try{ return getLine(); }
+ finally{
+ cur.peekedPos = cur.pos;
+ cur.peekedLineNo = cur.lineNo;
+ cur.pos = oldPos;
+ cur.lineNo = oldLine;
+ cur.putbackPos = oldPB;
+ cur.putbackLineNo = oldPBL;
+ }
+ }
+
+ /**
+ Only valid after calling peekLine() and before calling getLine().
+ This places the cursor to the position it would have been at had
+ the peekLine() had been fetched with getLine().
+ */
+ void consumePeeked(){
+ cur.pos = cur.peekedPos;
+ cur.lineNo = cur.peekedLineNo;
+ }
+
+ /**
+ Restores the cursor to the position it had before the previous
+ call to getLine().
+ */
+ void putbackLine(){
+ cur.pos = cur.putbackPos;
+ cur.lineNo = cur.putbackLineNo;
+ }
+
+ private boolean checkRequiredProperties(SQLTester t, String[] props) throws SQLTesterException{
+ if( true ) return false;
+ int nOk = 0;
+ for(String rp : props){
+ verbose1("REQUIRED_PROPERTIES: ",rp);
+ switch(rp){
+ case "RECURSIVE_TRIGGERS":
+ t.appendDbInitSql("pragma recursive_triggers=on;");
+ ++nOk;
+ break;
+ case "TEMPSTORE_FILE":
+ /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2,
+ which we just happen to know is the case */
+ t.appendDbInitSql("pragma temp_store=1;");
+ ++nOk;
+ break;
+ case "TEMPSTORE_MEM":
+ /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2,
+ which we just happen to know is the case */
+ t.appendDbInitSql("pragma temp_store=0;");
+ ++nOk;
+ break;
+ case "AUTOVACUUM":
+ t.appendDbInitSql("pragma auto_vacuum=full;");
+ ++nOk;
+ case "INCRVACUUM":
+ t.appendDbInitSql("pragma auto_vacuum=incremental;");
+ ++nOk;
+ default:
+ break;
+ }
+ }
+ return props.length == nOk;
+ }
+
+ private static final Pattern patternRequiredProperties =
+ Pattern.compile(" REQUIRED_PROPERTIES:[ \\t]*(\\S.*)\\s*$");
+ private static final Pattern patternScriptModuleName =
+ Pattern.compile(" SCRIPT_MODULE_NAME:[ \\t]*(\\S+)\\s*$");
+ private static final Pattern patternMixedModuleName =
+ Pattern.compile(" ((MIXED_)?MODULE_NAME):[ \\t]*(\\S+)\\s*$");
+ private static final Pattern patternCommand =
+ Pattern.compile("^--(([a-z-]+)( .*)?)$");
+
+ /**
+ Looks for "directives." If a compatible one is found, it is
+ processed and this function returns. If an incompatible one is found,
+ a description of it is returned and processing of the test must
+ end immediately.
+ */
+ private void checkForDirective(
+ SQLTester tester, String line
+ ) throws IncompatibleDirective {
+ if(line.startsWith("#")){
+ throw new IncompatibleDirective(this, "C-preprocessor input: "+line);
+ }else if(line.startsWith("---")){
+ new IncompatibleDirective(this, "triple-dash: "+line);
+ }
+ Matcher m = patternScriptModuleName.matcher(line);
+ if( m.find() ){
+ moduleName = m.group(1);
+ return;
+ }
+ m = patternRequiredProperties.matcher(line);
+ if( m.find() ){
+ final String rp = m.group(1);
+ if( ! checkRequiredProperties( tester, rp.split("\\s+") ) ){
+ throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp);
+ }
+ }
+ m = patternMixedModuleName.matcher(line);
+ if( m.find() ){
+ throw new IncompatibleDirective(this, m.group(1)+": "+m.group(3));
+ }
+ if( line.indexOf("\n|")>=0 ){
+ throw new IncompatibleDirective(this, "newline-pipe combination.");
+ }
+ return;
+ }
+
+ boolean isCommandLine(String line, boolean checkForImpl){
+ final Matcher m = patternCommand.matcher(line);
+ boolean rc = m.find();
+ if( rc && checkForImpl ){
+ rc = null!=CommandDispatcher.getCommandByName(m.group(2));
+ }
+ return rc;
+ }
+
+ /**
+ If line looks like a command, returns an argv for that command
+ invocation, else returns null.
+ */
+ String[] getCommandArgv(String line){
+ final Matcher m = patternCommand.matcher(line);
+ return m.find() ? m.group(1).trim().split("\\s+") : null;
+ }
+
+ /**
+ Fetches lines until the next recognized command. Throws if
+ checkForDirective() does. Returns null if there is no input or
+ it's only whitespace. The returned string retains all whitespace.
+
+ Note that "subcommands", --command-like constructs in the body
+ which do not match a known command name are considered to be
+ content, not commands.
+ */
+ String fetchCommandBody(SQLTester tester){
+ final StringBuilder sb = new StringBuilder();
+ String line;
+ while( (null != (line = peekLine())) ){
+ checkForDirective(tester, line);
+ if( isCommandLine(line, true) ) break;
+ else {
+ sb.append(line).append("\n");
+ consumePeeked();
+ }
+ }
+ line = sb.toString();
+ return line.trim().isEmpty() ? null : line;
+ }
+
+ private void processCommand(SQLTester t, String[] argv) throws Exception{
+ verbose1("running command: ",argv[0], " ", Util.argvToString(argv));
+ if(outer.getVerbosity()>1){
+ final String input = t.getInputText();
+ if( !input.isEmpty() ) verbose3("Input buffer = ",input);
+ }
+ CommandDispatcher.dispatch(t, this, argv);
+ }
+
+ void toss(Object... msg) throws TestScriptFailed {
+ StringBuilder sb = new StringBuilder();
+ for(Object s : msg) sb.append(s);
+ throw new TestScriptFailed(this, sb.toString());
+ }
+
+ /**
+ Runs this test script in the context of the given tester object.
+ */
+ public boolean run(SQLTester tester) throws Exception {
+ reset();
+ setVerbosity(tester.getVerbosity());
+ String line, directive;
+ String[] argv;
+ while( null != (line = getLine()) ){
+ verbose3("input line: ",line);
+ checkForDirective(tester, line);
+ argv = getCommandArgv(line);
+ if( null!=argv ){
+ processCommand(tester, argv);
+ continue;
+ }
+ tester.appendInput(line,true);
+ }
+ return true;
+ }
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java b/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java
new file mode 100644
index 0000000..95541bd
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java
@@ -0,0 +1,33 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+
+/**
+ A SQLFunction implementation for scalar functions.
+*/
+public abstract class ScalarFunction implements SQLFunction {
+ /**
+ As for the xFunc() argument of the C API's
+ sqlite3_create_function(). If this function throws, it is
+ translated into an sqlite3_result_error().
+ */
+ public abstract void xFunc(sqlite3_context cx, sqlite3_value[] args);
+
+ /**
+ Optionally override to be notified when the UDF is finalized by
+ SQLite. This default implementation does nothing.
+ */
+ public void xDestroy() {}
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java b/ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java
new file mode 100644
index 0000000..d8b6226
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java
@@ -0,0 +1,35 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A wrapper object for use with sqlite3_table_column_metadata().
+ They are populated only via that interface.
+*/
+public final class TableColumnMetadata {
+ OutputPointer.Bool pNotNull = new OutputPointer.Bool();
+ OutputPointer.Bool pPrimaryKey = new OutputPointer.Bool();
+ OutputPointer.Bool pAutoinc = new OutputPointer.Bool();
+ OutputPointer.String pzCollSeq = new OutputPointer.String();
+ OutputPointer.String pzDataType = new OutputPointer.String();
+
+ public TableColumnMetadata(){
+ }
+
+ public String getDataType(){ return pzDataType.value; }
+ public String getCollation(){ return pzCollSeq.value; }
+ public boolean isNotNull(){ return pNotNull.value; }
+ public boolean isPrimaryKey(){ return pPrimaryKey.value; }
+ public boolean isAutoincrement(){ return pAutoinc.value; }
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/Tester1.java b/ext/jni/src/org/sqlite/jni/capi/Tester1.java
new file mode 100644
index 0000000..05b1cfe
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/Tester1.java
@@ -0,0 +1,2194 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains a set of tests for the sqlite3 JNI bindings.
+*/
+package org.sqlite.jni.capi;
+import static org.sqlite.jni.capi.CApi.*;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ An annotation for Tester1 tests which we do not want to run in
+ reflection-driven test mode because either they are not suitable
+ for multi-threaded threaded mode or we have to control their execution
+ order.
+*/
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+@interface ManualTest{}
+/**
+ Annotation for Tester1 tests which mark those which must be skipped
+ in multi-threaded mode.
+*/
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+@interface SingleThreadOnly{}
+
+/**
+ Annotation for Tester1 tests which must only be run if
+ sqlite3_jni_supports_nio() is true.
+*/
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+@interface RequiresJniNio{}
+
+public class Tester1 implements Runnable {
+ //! True when running in multi-threaded mode.
+ private static boolean mtMode = false;
+ //! True to sleep briefly between tests.
+ private static boolean takeNaps = false;
+ //! True to shuffle the order of the tests.
+ private static boolean shuffle = false;
+ //! True to dump the list of to-run tests to stdout.
+ private static int listRunTests = 0;
+ //! True to squelch all out() and outln() output.
+ private static boolean quietMode = false;
+ //! Total number of runTests() calls.
+ private static int nTestRuns = 0;
+ //! List of test*() methods to run.
+ private static List<java.lang.reflect.Method> testMethods = null;
+ //! List of exceptions collected by run()
+ private static List<Exception> listErrors = new ArrayList<>();
+ private static final class Metrics {
+ //! Number of times createNewDb() (or equivalent) is invoked.
+ volatile int dbOpen = 0;
+ }
+
+ private Integer tId;
+
+ Tester1(Integer id){
+ tId = id;
+ }
+
+ static final Metrics metrics = new Metrics();
+
+ public static synchronized void outln(){
+ if( !quietMode ){
+ System.out.println("");
+ }
+ }
+
+ public static synchronized void outPrefix(){
+ if( !quietMode ){
+ System.out.print(Thread.currentThread().getName()+": ");
+ }
+ }
+
+ public static synchronized void outln(Object val){
+ if( !quietMode ){
+ outPrefix();
+ System.out.println(val);
+ }
+ }
+
+ public static synchronized void out(Object val){
+ if( !quietMode ){
+ System.out.print(val);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static synchronized void out(Object... vals){
+ if( !quietMode ){
+ outPrefix();
+ for(Object v : vals) out(v);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static synchronized void outln(Object... vals){
+ if( !quietMode ){
+ out(vals); out("\n");
+ }
+ }
+
+ static volatile int affirmCount = 0;
+ public static synchronized int affirm(Boolean v, String comment){
+ ++affirmCount;
+ if( false ) assert( v /* prefer assert over exception if it's enabled because
+ the JNI layer sometimes has to suppress exceptions,
+ so they might be squelched on their way back to the
+ top. */);
+ if( !v ) throw new RuntimeException(comment);
+ return affirmCount;
+ }
+
+ public static void affirm(Boolean v){
+ affirm(v, "Affirmation failed.");
+ }
+
+ @SingleThreadOnly /* because it's thread-agnostic */
+ private void test1(){
+ affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER);
+ }
+
+ public static sqlite3 createNewDb(){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ int rc = sqlite3_open(":memory:", out);
+ ++metrics.dbOpen;
+ sqlite3 db = out.take();
+ if( 0!=rc ){
+ final String msg =
+ null==db ? sqlite3_errstr(rc) : sqlite3_errmsg(db);
+ sqlite3_close(db);
+ throw new RuntimeException("Opening db failed: "+msg);
+ }
+ affirm( null == out.get() );
+ affirm( 0 != db.getNativePointer() );
+ rc = sqlite3_busy_timeout(db, 2000);
+ affirm( 0 == rc );
+ return db;
+ }
+
+ public static void execSql(sqlite3 db, String[] sql){
+ execSql(db, String.join("", sql));
+ }
+
+ public static int execSql(sqlite3 db, boolean throwOnError, String sql){
+ OutputPointer.Int32 oTail = new OutputPointer.Int32();
+ final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8);
+ int pos = 0, n = 1;
+ byte[] sqlChunk = sqlUtf8;
+ int rc = 0;
+ sqlite3_stmt stmt = null;
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+ while(pos < sqlChunk.length){
+ if(pos > 0){
+ sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
+ sqlChunk.length);
+ }
+ if( 0==sqlChunk.length ) break;
+ rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail);
+ if(throwOnError) affirm(0 == rc);
+ else if( 0!=rc ) break;
+ pos = oTail.value;
+ stmt = outStmt.take();
+ if( null == stmt ){
+ // empty statement was parsed.
+ continue;
+ }
+ affirm(0 != stmt.getNativePointer());
+ while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){
+ }
+ sqlite3_finalize(stmt);
+ affirm(0 == stmt.getNativePointer());
+ if(0!=rc && SQLITE_ROW!=rc && SQLITE_DONE!=rc){
+ break;
+ }
+ }
+ sqlite3_finalize(stmt);
+ if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0;
+ if( 0!=rc && throwOnError){
+ throw new RuntimeException("db op failed with rc="
+ +rc+": "+sqlite3_errmsg(db));
+ }
+ return rc;
+ }
+
+ public static void execSql(sqlite3 db, String sql){
+ execSql(db, true, sql);
+ }
+
+ public static sqlite3_stmt prepare(sqlite3 db, boolean throwOnError, String sql){
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+ int rc = sqlite3_prepare_v2(db, sql, outStmt);
+ if( throwOnError ){
+ affirm( 0 == rc );
+ }
+ final sqlite3_stmt rv = outStmt.take();
+ affirm( null == outStmt.get() );
+ if( throwOnError ){
+ affirm( 0 != rv.getNativePointer() );
+ }
+ return rv;
+ }
+
+ public static sqlite3_stmt prepare(sqlite3 db, String sql){
+ return prepare(db, true, sql);
+ }
+
+ private void showCompileOption(){
+ int i = 0;
+ String optName;
+ outln("compile options:");
+ for( ; null != (optName = sqlite3_compileoption_get(i)); ++i){
+ outln("\t"+optName+"\t (used="+
+ sqlite3_compileoption_used(optName)+")");
+ }
+ }
+
+ private void testCompileOption(){
+ int i = 0;
+ String optName;
+ for( ; null != (optName = sqlite3_compileoption_get(i)); ++i){
+ }
+ affirm( i > 10 );
+ affirm( null==sqlite3_compileoption_get(-1) );
+ }
+
+ private void testOpenDb1(){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ int rc = sqlite3_open(":memory:", out);
+ ++metrics.dbOpen;
+ sqlite3 db = out.get();
+ affirm(0 == rc);
+ affirm(db.getNativePointer()!=0);
+ sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, null)
+ /* This function has different mangled names in jdk8 vs jdk19,
+ and this call is here to ensure that the build fails
+ if it cannot find both names. */;
+
+ affirm( 0==sqlite3_db_readonly(db,"main") );
+ affirm( 0==sqlite3_db_readonly(db,null) );
+ affirm( 0>sqlite3_db_readonly(db,"nope") );
+ affirm( 0>sqlite3_db_readonly(null,null) );
+ affirm( 0==sqlite3_last_insert_rowid(null) );
+
+ // These interrupt checks are only to make sure that the JNI binding
+ // has the proper exported symbol names. They don't actually test
+ // anything useful.
+ affirm( !sqlite3_is_interrupted(db) );
+ sqlite3_interrupt(db);
+ affirm( sqlite3_is_interrupted(db) );
+ sqlite3_close_v2(db);
+ affirm(0 == db.getNativePointer());
+ }
+
+ private void testOpenDb2(){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ int rc = sqlite3_open_v2(":memory:", out,
+ SQLITE_OPEN_READWRITE
+ | SQLITE_OPEN_CREATE, null);
+ ++metrics.dbOpen;
+ affirm(0 == rc);
+ sqlite3 db = out.get();
+ affirm(0 != db.getNativePointer());
+ sqlite3_close_v2(db);
+ affirm(0 == db.getNativePointer());
+ }
+
+ private void testPrepare123(){
+ sqlite3 db = createNewDb();
+ int rc;
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+ rc = sqlite3_prepare(db, "CREATE TABLE t1(a);", outStmt);
+ affirm(0 == rc);
+ sqlite3_stmt stmt = outStmt.take();
+ affirm(0 != stmt.getNativePointer());
+ affirm( !sqlite3_stmt_readonly(stmt) );
+ affirm( db == sqlite3_db_handle(stmt) );
+ rc = sqlite3_step(stmt);
+ affirm(SQLITE_DONE == rc);
+ sqlite3_finalize(stmt);
+ affirm( null == sqlite3_db_handle(stmt) );
+ affirm(0 == stmt.getNativePointer());
+
+ { /* Demonstrate how to use the "zTail" option of
+ sqlite3_prepare() family of functions. */
+ OutputPointer.Int32 oTail = new OutputPointer.Int32();
+ final byte[] sqlUtf8 =
+ "CREATE TABLE t2(a); INSERT INTO t2(a) VALUES(1),(2),(3)"
+ .getBytes(StandardCharsets.UTF_8);
+ int pos = 0, n = 1;
+ byte[] sqlChunk = sqlUtf8;
+ while(pos < sqlChunk.length){
+ if(pos > 0){
+ sqlChunk = Arrays.copyOfRange(sqlChunk, pos, sqlChunk.length);
+ }
+ //outln("SQL chunk #"+n+" length = "+sqlChunk.length+", pos = "+pos);
+ if( 0==sqlChunk.length ) break;
+ rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail);
+ affirm(0 == rc);
+ stmt = outStmt.get();
+ pos = oTail.value;
+ /*outln("SQL tail pos = "+pos+". Chunk = "+
+ (new String(Arrays.copyOfRange(sqlChunk,0,pos),
+ StandardCharsets.UTF_8)));*/
+ switch(n){
+ case 1: affirm(19 == pos); break;
+ case 2: affirm(36 == pos); break;
+ default: affirm( false /* can't happen */ );
+
+ }
+ ++n;
+ affirm(0 != stmt.getNativePointer());
+ rc = sqlite3_step(stmt);
+ affirm(SQLITE_DONE == rc);
+ sqlite3_finalize(stmt);
+ affirm(0 == stmt.getNativePointer());
+ }
+ }
+
+
+ rc = sqlite3_prepare_v3(db, "INSERT INTO t2(a) VALUES(1),(2),(3)",
+ 0, outStmt);
+ affirm(0 == rc);
+ stmt = outStmt.get();
+ affirm(0 != stmt.getNativePointer());
+ sqlite3_finalize(stmt);
+ affirm(0 == stmt.getNativePointer() );
+
+ affirm( 0==sqlite3_errcode(db) );
+ stmt = sqlite3_prepare(db, "intentional error");
+ affirm( null==stmt );
+ affirm( 0!=sqlite3_errcode(db) );
+ affirm( 0==sqlite3_errmsg(db).indexOf("near \"intentional\"") );
+ sqlite3_finalize(stmt);
+ stmt = sqlite3_prepare(db, "/* empty input*/\n-- comments only");
+ affirm( null==stmt );
+ affirm( 0==sqlite3_errcode(db) );
+ sqlite3_close_v2(db);
+ }
+
+ private void testBindFetchInt(){
+ sqlite3 db = createNewDb();
+ execSql(db, "CREATE TABLE t(a)");
+
+ sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(:a);");
+ affirm(1 == sqlite3_bind_parameter_count(stmt));
+ final int paramNdx = sqlite3_bind_parameter_index(stmt, ":a");
+ affirm(1 == paramNdx);
+ affirm( ":a".equals(sqlite3_bind_parameter_name(stmt, paramNdx)));
+ int total1 = 0;
+ long rowid = -1;
+ int changes = sqlite3_changes(db);
+ int changesT = sqlite3_total_changes(db);
+ long changes64 = sqlite3_changes64(db);
+ long changesT64 = sqlite3_total_changes64(db);
+ int rc;
+ for(int i = 99; i < 102; ++i ){
+ total1 += i;
+ rc = sqlite3_bind_int(stmt, paramNdx, i);
+ affirm(0 == rc);
+ rc = sqlite3_step(stmt);
+ sqlite3_reset(stmt);
+ affirm(SQLITE_DONE == rc);
+ long x = sqlite3_last_insert_rowid(db);
+ affirm(x > rowid);
+ rowid = x;
+ }
+ sqlite3_finalize(stmt);
+ affirm(300 == total1);
+ affirm(sqlite3_changes(db) > changes);
+ affirm(sqlite3_total_changes(db) > changesT);
+ affirm(sqlite3_changes64(db) > changes64);
+ affirm(sqlite3_total_changes64(db) > changesT64);
+ stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+ affirm( sqlite3_stmt_readonly(stmt) );
+ affirm( !sqlite3_stmt_busy(stmt) );
+ if( sqlite3_compileoption_used("ENABLE_COLUMN_METADATA") ){
+ /* Unlike in native C code, JNI won't trigger an
+ UnsatisfiedLinkError until these are called (on Linux, at
+ least). */
+ affirm("t".equals(sqlite3_column_table_name(stmt,0)));
+ affirm("main".equals(sqlite3_column_database_name(stmt,0)));
+ affirm("a".equals(sqlite3_column_origin_name(stmt,0)));
+ }
+
+ int total2 = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ affirm( sqlite3_stmt_busy(stmt) );
+ total2 += sqlite3_column_int(stmt, 0);
+ sqlite3_value sv = sqlite3_column_value(stmt, 0);
+ affirm( null != sv );
+ affirm( 0 != sv.getNativePointer() );
+ affirm( SQLITE_INTEGER == sqlite3_value_type(sv) );
+ }
+ affirm( !sqlite3_stmt_busy(stmt) );
+ sqlite3_finalize(stmt);
+ affirm(total1 == total2);
+
+ // sqlite3_value_frombind() checks...
+ stmt = prepare(db, "SELECT 1, ?");
+ sqlite3_bind_int(stmt, 1, 2);
+ rc = sqlite3_step(stmt);
+ affirm( SQLITE_ROW==rc );
+ affirm( !sqlite3_value_frombind(sqlite3_column_value(stmt, 0)) );
+ affirm( sqlite3_value_frombind(sqlite3_column_value(stmt, 1)) );
+ sqlite3_finalize(stmt);
+
+ sqlite3_close_v2(db);
+ affirm(0 == db.getNativePointer());
+ }
+
+ private void testBindFetchInt64(){
+ try (sqlite3 db = createNewDb()){
+ execSql(db, "CREATE TABLE t(a)");
+ sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
+ long total1 = 0;
+ for(long i = 0xffffffff; i < 0xffffffff + 3; ++i ){
+ total1 += i;
+ sqlite3_bind_int64(stmt, 1, i);
+ sqlite3_step(stmt);
+ sqlite3_reset(stmt);
+ }
+ sqlite3_finalize(stmt);
+ stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+ long total2 = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ total2 += sqlite3_column_int64(stmt, 0);
+ }
+ sqlite3_finalize(stmt);
+ affirm(total1 == total2);
+ //sqlite3_close_v2(db);
+ }
+ }
+
+ private void testBindFetchDouble(){
+ try (sqlite3 db = createNewDb()){
+ execSql(db, "CREATE TABLE t(a)");
+ sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
+ double total1 = 0;
+ for(double i = 1.5; i < 5.0; i = i + 1.0 ){
+ total1 += i;
+ sqlite3_bind_double(stmt, 1, i);
+ sqlite3_step(stmt);
+ sqlite3_reset(stmt);
+ }
+ sqlite3_finalize(stmt);
+ stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+ double total2 = 0;
+ int counter = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ ++counter;
+ total2 += sqlite3_column_double(stmt, 0);
+ }
+ affirm(4 == counter);
+ sqlite3_finalize(stmt);
+ affirm(total2<=total1+0.01 && total2>=total1-0.01);
+ //sqlite3_close_v2(db);
+ }
+ }
+
+ private void testBindFetchText(){
+ sqlite3 db = createNewDb();
+ execSql(db, "CREATE TABLE t(a)");
+ sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
+ String[] list1 = { "hell🤩", "w😃rld", "!🤩" };
+ int rc;
+ int n = 0;
+ for( String e : list1 ){
+ rc = (0==n)
+ ? sqlite3_bind_text(stmt, 1, e)
+ : sqlite3_bind_text16(stmt, 1, e);
+ affirm(0 == rc);
+ rc = sqlite3_step(stmt);
+ affirm(SQLITE_DONE==rc);
+ sqlite3_reset(stmt);
+ }
+ sqlite3_finalize(stmt);
+ stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+ StringBuilder sbuf = new StringBuilder();
+ n = 0;
+ final boolean tryNio = sqlite3_jni_supports_nio();
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ final sqlite3_value sv = sqlite3_value_dup(sqlite3_column_value(stmt,0));
+ final String txt = sqlite3_column_text16(stmt, 0);
+ sbuf.append( txt );
+ affirm( txt.equals(new String(
+ sqlite3_column_text(stmt, 0),
+ StandardCharsets.UTF_8
+ )) );
+ affirm( txt.length() < sqlite3_value_bytes(sv) );
+ affirm( txt.equals(new String(
+ sqlite3_value_text(sv),
+ StandardCharsets.UTF_8)) );
+ affirm( txt.length() == sqlite3_value_bytes16(sv)/2 );
+ affirm( txt.equals(sqlite3_value_text16(sv)) );
+ if( tryNio ){
+ java.nio.ByteBuffer bu = sqlite3_value_nio_buffer(sv);
+ byte ba[] = sqlite3_value_blob(sv);
+ affirm( ba.length == bu.capacity() );
+ int i = 0;
+ for( byte b : ba ){
+ affirm( b == bu.get(i++) );
+ }
+ }
+ sqlite3_value_free(sv);
+ ++n;
+ }
+ sqlite3_finalize(stmt);
+ affirm(3 == n);
+ affirm("w😃rldhell🤩!🤩".equals(sbuf.toString()));
+
+ try( sqlite3_stmt stmt2 = prepare(db, "SELECT ?, ?") ){
+ rc = sqlite3_bind_text(stmt2, 1, "");
+ affirm( 0==rc );
+ rc = sqlite3_bind_text(stmt2, 2, (String)null);
+ affirm( 0==rc );
+ rc = sqlite3_step(stmt2);
+ affirm( SQLITE_ROW==rc );
+ byte[] colBa = sqlite3_column_text(stmt2, 0);
+ affirm( 0==colBa.length );
+ colBa = sqlite3_column_text(stmt2, 1);
+ affirm( null==colBa );
+ //sqlite3_finalize(stmt);
+ }
+
+ if(true){
+ sqlite3_close_v2(db);
+ }else{
+ // Let the Object.finalize() override deal with it.
+ }
+ }
+
+ private void testBindFetchBlob(){
+ sqlite3 db = createNewDb();
+ execSql(db, "CREATE TABLE t(a)");
+ sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
+ byte[] list1 = { 0x32, 0x33, 0x34 };
+ int rc = sqlite3_bind_blob(stmt, 1, list1);
+ affirm( 0==rc );
+ rc = sqlite3_step(stmt);
+ affirm(SQLITE_DONE == rc);
+ sqlite3_finalize(stmt);
+ stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+ int n = 0;
+ int total = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ byte[] blob = sqlite3_column_blob(stmt, 0);
+ affirm(3 == blob.length);
+ int i = 0;
+ for(byte b : blob){
+ affirm(b == list1[i++]);
+ total += b;
+ }
+ ++n;
+ }
+ sqlite3_finalize(stmt);
+ affirm(1 == n);
+ affirm(total == 0x32 + 0x33 + 0x34);
+ sqlite3_close_v2(db);
+ }
+
+ @RequiresJniNio
+ private void testBindByteBuffer(){
+ /* TODO: these tests need to be much more extensive to check the
+ begin/end range handling. */
+
+ java.nio.ByteBuffer zeroCheck =
+ java.nio.ByteBuffer.allocateDirect(0);
+ affirm( null != zeroCheck );
+ zeroCheck = null;
+ sqlite3 db = createNewDb();
+ execSql(db, "CREATE TABLE t(a)");
+
+ final java.nio.ByteBuffer buf = java.nio.ByteBuffer.allocateDirect(10);
+ buf.put((byte)0x31)/*note that we'll skip this one*/
+ .put((byte)0x32)
+ .put((byte)0x33)
+ .put((byte)0x34)
+ .put((byte)0x35)/*we'll skip this one too*/;
+
+ final int expectTotal = buf.get(1) + buf.get(2) + buf.get(3);
+ sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
+ affirm( SQLITE_ERROR == sqlite3_bind_blob(stmt, 1, buf, -1, 0),
+ "Buffer offset may not be negative." );
+ affirm( 0 == sqlite3_bind_blob(stmt, 1, buf, 1, 3) );
+ affirm( SQLITE_DONE == sqlite3_step(stmt) );
+ sqlite3_finalize(stmt);
+ stmt = prepare(db, "SELECT a FROM t;");
+ int total = 0;
+ affirm( SQLITE_ROW == sqlite3_step(stmt) );
+ byte blob[] = sqlite3_column_blob(stmt, 0);
+ java.nio.ByteBuffer nioBlob =
+ sqlite3_column_nio_buffer(stmt, 0);
+ affirm(3 == blob.length);
+ affirm(blob.length == nioBlob.capacity());
+ affirm(blob.length == nioBlob.limit());
+ int i = 0;
+ for(byte b : blob){
+ affirm( i<=3 );
+ affirm(b == buf.get(1 + i));
+ affirm(b == nioBlob.get(i));
+ ++i;
+ total += b;
+ }
+ affirm( SQLITE_DONE == sqlite3_step(stmt) );
+ sqlite3_finalize(stmt);
+ affirm(total == expectTotal);
+
+ SQLFunction func =
+ new ScalarFunction(){
+ public void xFunc(sqlite3_context cx, sqlite3_value[] args){
+ sqlite3_result_blob(cx, buf, 1, 3);
+ }
+ };
+
+ affirm( 0 == sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func) );
+ stmt = prepare(db, "SELECT myfunc()");
+ affirm( SQLITE_ROW == sqlite3_step(stmt) );
+ blob = sqlite3_column_blob(stmt, 0);
+ affirm(3 == blob.length);
+ i = 0;
+ total = 0;
+ for(byte b : blob){
+ affirm( i<=3 );
+ affirm(b == buf.get(1 + i++));
+ total += b;
+ }
+ affirm( SQLITE_DONE == sqlite3_step(stmt) );
+ sqlite3_finalize(stmt);
+ affirm(total == expectTotal);
+
+ sqlite3_close_v2(db);
+ }
+
+ private void testSql(){
+ sqlite3 db = createNewDb();
+ sqlite3_stmt stmt = prepare(db, "SELECT 1");
+ affirm( "SELECT 1".equals(sqlite3_sql(stmt)) );
+ sqlite3_finalize(stmt);
+ stmt = prepare(db, "SELECT ?");
+ sqlite3_bind_text(stmt, 1, "hell😃");
+ final String expect = "SELECT 'hell😃'";
+ affirm( expect.equals(sqlite3_expanded_sql(stmt)) );
+ String n = sqlite3_normalized_sql(stmt);
+ affirm( null==n || "SELECT?;".equals(n) );
+ sqlite3_finalize(stmt);
+ sqlite3_close(db);
+ }
+
+ private void testCollation(){
+ final sqlite3 db = createNewDb();
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ final ValueHolder<Integer> xDestroyCalled = new ValueHolder<>(0);
+ final CollationCallback myCollation = new CollationCallback() {
+ private String myState =
+ "this is local state. There is much like it, but this is mine.";
+ @Override
+ // Reverse-sorts its inputs...
+ public int call(byte[] lhs, byte[] rhs){
+ int len = lhs.length > rhs.length ? rhs.length : lhs.length;
+ int c = 0, i = 0;
+ for(i = 0; i < len; ++i){
+ c = lhs[i] - rhs[i];
+ if(0 != c) break;
+ }
+ if(0==c){
+ if(i < lhs.length) c = 1;
+ else if(i < rhs.length) c = -1;
+ }
+ return -c;
+ }
+ @Override
+ public void xDestroy() {
+ // Just demonstrates that xDestroy is called.
+ ++xDestroyCalled.value;
+ }
+ };
+ final CollationNeededCallback collLoader = new CollationNeededCallback(){
+ @Override
+ public void call(sqlite3 dbArg, int eTextRep, String collationName){
+ affirm(dbArg == db/* as opposed to a temporary object*/);
+ sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation);
+ }
+ };
+ int rc = sqlite3_collation_needed(db, collLoader);
+ affirm( 0 == rc );
+ rc = sqlite3_collation_needed(db, collLoader);
+ affirm( 0 == rc /* Installing the same object again is a no-op */);
+ sqlite3_stmt stmt = prepare(db, "SELECT a FROM t ORDER BY a COLLATE reversi");
+ int counter = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ final String val = sqlite3_column_text16(stmt, 0);
+ ++counter;
+ //outln("REVERSI'd row#"+counter+": "+val);
+ switch(counter){
+ case 1: affirm("c".equals(val)); break;
+ case 2: affirm("b".equals(val)); break;
+ case 3: affirm("a".equals(val)); break;
+ }
+ }
+ affirm(3 == counter);
+ sqlite3_finalize(stmt);
+ stmt = prepare(db, "SELECT a FROM t ORDER BY a");
+ counter = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ final String val = sqlite3_column_text16(stmt, 0);
+ ++counter;
+ //outln("Non-REVERSI'd row#"+counter+": "+val);
+ switch(counter){
+ case 3: affirm("c".equals(val)); break;
+ case 2: affirm("b".equals(val)); break;
+ case 1: affirm("a".equals(val)); break;
+ }
+ }
+ affirm(3 == counter);
+ sqlite3_finalize(stmt);
+ affirm( 0 == xDestroyCalled.value );
+ rc = sqlite3_collation_needed(db, null);
+ affirm( 0 == rc );
+ sqlite3_close_v2(db);
+ affirm( 0 == db.getNativePointer() );
+ affirm( 1 == xDestroyCalled.value );
+ }
+
+ @SingleThreadOnly /* because it's thread-agnostic */
+ private void testToUtf8(){
+ /**
+ https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html
+
+ Let's ensure that we can convert to standard UTF-8 in Java code
+ (noting that the JNI native API has no way to do this).
+ */
+ final byte[] ba = "a \0 b".getBytes(StandardCharsets.UTF_8);
+ affirm( 5 == ba.length /* as opposed to 6 in modified utf-8 */);
+ }
+
+ private void testStatus(){
+ final OutputPointer.Int64 cur64 = new OutputPointer.Int64();
+ final OutputPointer.Int64 high64 = new OutputPointer.Int64();
+ final OutputPointer.Int32 cur32 = new OutputPointer.Int32();
+ final OutputPointer.Int32 high32 = new OutputPointer.Int32();
+ final sqlite3 db = createNewDb();
+ execSql(db, "create table t(a); insert into t values(1),(2),(3)");
+
+ int rc = sqlite3_status(SQLITE_STATUS_MEMORY_USED, cur32, high32, false);
+ affirm( 0 == rc );
+ affirm( cur32.value > 0 );
+ affirm( high32.value >= cur32.value );
+
+ rc = sqlite3_status64(SQLITE_STATUS_MEMORY_USED, cur64, high64, false);
+ affirm( 0 == rc );
+ affirm( cur64.value > 0 );
+ affirm( high64.value >= cur64.value );
+
+ cur32.value = 0;
+ high32.value = 1;
+ rc = sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, cur32, high32, false);
+ affirm( 0 == rc );
+ affirm( cur32.value > 0 );
+ affirm( high32.value == 0 /* always 0 for SCHEMA_USED */ );
+
+ sqlite3_close_v2(db);
+ }
+
+ private void testUdf1(){
+ final sqlite3 db = createNewDb();
+ // These ValueHolders are just to confirm that the func did what we want...
+ final ValueHolder<Boolean> xDestroyCalled = new ValueHolder<>(false);
+ final ValueHolder<Integer> xFuncAccum = new ValueHolder<>(0);
+ final ValueHolder<sqlite3_value[]> neverEverDoThisInClientCode = new ValueHolder<>(null);
+ final ValueHolder<sqlite3_context> neverEverDoThisInClientCode2 = new ValueHolder<>(null);
+
+ // Create an SQLFunction instance using one of its 3 subclasses:
+ // Scalar, Aggregate, or Window:
+ SQLFunction func =
+ // Each of the 3 subclasses requires a different set of
+ // functions, all of which must be implemented. Anonymous
+ // classes are a convenient way to implement these.
+ new ScalarFunction(){
+ public void xFunc(sqlite3_context cx, sqlite3_value[] args){
+ affirm(db == sqlite3_context_db_handle(cx));
+ if( null==neverEverDoThisInClientCode.value ){
+ /* !!!NEVER!!! hold a reference to an sqlite3_value or
+ sqlite3_context object like this in client code! They
+ are ONLY legal for the duration of their single
+ call. We do it here ONLY to test that the defenses
+ against clients doing this are working. */
+ neverEverDoThisInClientCode2.value = cx;
+ neverEverDoThisInClientCode.value = args;
+ }
+ int result = 0;
+ for( sqlite3_value v : args ) result += sqlite3_value_int(v);
+ xFuncAccum.value += result;// just for post-run testing
+ sqlite3_result_int(cx, result);
+ }
+ /* OPTIONALLY override xDestroy... */
+ public void xDestroy(){
+ xDestroyCalled.value = true;
+ }
+ };
+
+ // Register and use the function...
+ int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func);
+ affirm(0 == rc);
+ affirm(0 == xFuncAccum.value);
+ final sqlite3_stmt stmt = prepare(db, "SELECT myfunc(1,2,3)");
+ int n = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ affirm( 6 == sqlite3_column_int(stmt, 0) );
+ ++n;
+ }
+ sqlite3_finalize(stmt);
+ affirm(1 == n);
+ affirm(6 == xFuncAccum.value);
+ affirm( !xDestroyCalled.value );
+ affirm( null!=neverEverDoThisInClientCode.value );
+ affirm( null!=neverEverDoThisInClientCode2.value );
+ affirm( 0<neverEverDoThisInClientCode.value.length );
+ affirm( 0==neverEverDoThisInClientCode2.value.getNativePointer() );
+ for( sqlite3_value sv : neverEverDoThisInClientCode.value ){
+ affirm( 0==sv.getNativePointer() );
+ }
+ sqlite3_close_v2(db);
+ affirm( xDestroyCalled.value );
+ }
+
+ private void testUdfThrows(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder<Integer> xFuncAccum = new ValueHolder<>(0);
+
+ SQLFunction funcAgg = new AggregateFunction<Integer>(){
+ @Override public void xStep(sqlite3_context cx, sqlite3_value[] args){
+ /** Throwing from here should emit loud noise on stdout or stderr
+ but the exception is supressed because we have no way to inform
+ sqlite about it from these callbacks. */
+ //throw new RuntimeException("Throwing from an xStep");
+ }
+ @Override public void xFinal(sqlite3_context cx){
+ throw new RuntimeException("Throwing from an xFinal");
+ }
+ };
+ int rc = sqlite3_create_function(db, "myagg", 1, SQLITE_UTF8, funcAgg);
+ affirm(0 == rc);
+ affirm(0 == xFuncAccum.value);
+ sqlite3_stmt stmt = prepare(db, "SELECT myagg(1)");
+ rc = sqlite3_step(stmt);
+ sqlite3_finalize(stmt);
+ affirm( 0 != rc );
+ affirm( sqlite3_errmsg(db).indexOf("an xFinal") > 0 );
+
+ SQLFunction funcSc = new ScalarFunction(){
+ @Override public void xFunc(sqlite3_context cx, sqlite3_value[] args){
+ throw new RuntimeException("Throwing from an xFunc");
+ }
+ };
+ rc = sqlite3_create_function(db, "mysca", 0, SQLITE_UTF8, funcSc);
+ affirm(0 == rc);
+ affirm(0 == xFuncAccum.value);
+ stmt = prepare(db, "SELECT mysca()");
+ rc = sqlite3_step(stmt);
+ sqlite3_finalize(stmt);
+ affirm( 0 != rc );
+ affirm( sqlite3_errmsg(db).indexOf("an xFunc") > 0 );
+ rc = sqlite3_create_function(db, "mysca", 1, -1, funcSc);
+ affirm( SQLITE_FORMAT==rc, "invalid encoding value." );
+ sqlite3_close_v2(db);
+ }
+
+ @SingleThreadOnly
+ private void testUdfJavaObject(){
+ affirm( !mtMode );
+ final sqlite3 db = createNewDb();
+ final ValueHolder<sqlite3> testResult = new ValueHolder<>(db);
+ final ValueHolder<Integer> boundObj = new ValueHolder<>(42);
+ final SQLFunction func = new ScalarFunction(){
+ public void xFunc(sqlite3_context cx, sqlite3_value args[]){
+ sqlite3_result_java_object(cx, testResult.value);
+ affirm( sqlite3_value_java_object(args[0]) == boundObj );
+ }
+ };
+ int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func);
+ affirm(0 == rc);
+ sqlite3_stmt stmt = prepare(db, "select myfunc(?)");
+ affirm( 0 != stmt.getNativePointer() );
+ affirm( testResult.value == db );
+ rc = sqlite3_bind_java_object(stmt, 1, boundObj);
+ affirm( 0==rc );
+ int n = 0;
+ if( SQLITE_ROW == sqlite3_step(stmt) ){
+ affirm( testResult.value == sqlite3_column_java_object(stmt, 0) );
+ affirm( testResult.value == sqlite3_column_java_object(stmt, 0, sqlite3.class) );
+ affirm( null == sqlite3_column_java_object(stmt, 0, sqlite3_stmt.class) );
+ affirm( null == sqlite3_column_java_object(stmt,1) );
+ final sqlite3_value v = sqlite3_column_value(stmt, 0);
+ affirm( testResult.value == sqlite3_value_java_object(v) );
+ affirm( testResult.value == sqlite3_value_java_object(v, sqlite3.class) );
+ affirm( testResult.value ==
+ sqlite3_value_java_object(v, testResult.value.getClass()) );
+ affirm( testResult.value == sqlite3_value_java_object(v, Object.class) );
+ affirm( null == sqlite3_value_java_object(v, String.class) );
+ ++n;
+ }
+ sqlite3_finalize(stmt);
+ affirm( 1 == n );
+ affirm( 0==sqlite3_db_release_memory(db) );
+ sqlite3_close_v2(db);
+ }
+
+ private void testUdfAggregate(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder<Boolean> xFinalNull =
+ // To confirm that xFinal() is called with no aggregate state
+ // when the corresponding result set is empty.
+ new ValueHolder<>(false);
+ final ValueHolder<sqlite3_value[]> neverEverDoThisInClientCode = new ValueHolder<>(null);
+ final ValueHolder<sqlite3_context> neverEverDoThisInClientCode2 = new ValueHolder<>(null);
+ SQLFunction func = new AggregateFunction<Integer>(){
+ @Override
+ public void xStep(sqlite3_context cx, sqlite3_value[] args){
+ if( null==neverEverDoThisInClientCode.value ){
+ /* !!!NEVER!!! hold a reference to an sqlite3_value or
+ sqlite3_context object like this in client code! They
+ are ONLY legal for the duration of their single
+ call. We do it here ONLY to test that the defenses
+ against clients doing this are working. */
+ neverEverDoThisInClientCode.value = args;
+ }
+ final ValueHolder<Integer> agg = this.getAggregateState(cx, 0);
+ agg.value += sqlite3_value_int(args[0]);
+ affirm( agg == this.getAggregateState(cx, 0) );
+ }
+ @Override
+ public void xFinal(sqlite3_context cx){
+ if( null==neverEverDoThisInClientCode2.value ){
+ neverEverDoThisInClientCode2.value = cx;
+ }
+ final Integer v = this.takeAggregateState(cx);
+ if(null == v){
+ xFinalNull.value = true;
+ sqlite3_result_null(cx);
+ }else{
+ sqlite3_result_int(cx, v);
+ }
+ }
+ };
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES(1),(2),(3)");
+ int rc = sqlite3_create_function(db, "myfunc", 1, SQLITE_UTF8, func);
+ affirm(0 == rc);
+ sqlite3_stmt stmt = prepare(db, "select myfunc(a), myfunc(a+10) from t");
+ affirm( 0==sqlite3_stmt_status(stmt, SQLITE_STMTSTATUS_RUN, false) );
+ int n = 0;
+ if( SQLITE_ROW == sqlite3_step(stmt) ){
+ int v = sqlite3_column_int(stmt, 0);
+ affirm( 6 == v );
+ int v2 = sqlite3_column_int(stmt, 1);
+ affirm( 30+v == v2 );
+ ++n;
+ }
+ affirm( 1==n );
+ affirm(!xFinalNull.value);
+ affirm( null!=neverEverDoThisInClientCode.value );
+ affirm( null!=neverEverDoThisInClientCode2.value );
+ affirm( 0<neverEverDoThisInClientCode.value.length );
+ affirm( 0==neverEverDoThisInClientCode2.value.getNativePointer() );
+ sqlite3_reset(stmt);
+ affirm( 1==sqlite3_stmt_status(stmt, SQLITE_STMTSTATUS_RUN, false) );
+ // Ensure that the accumulator is reset on subsequent calls...
+ n = 0;
+ if( SQLITE_ROW == sqlite3_step(stmt) ){
+ final int v = sqlite3_column_int(stmt, 0);
+ affirm( 6 == v );
+ ++n;
+ }
+ sqlite3_finalize(stmt);
+ affirm( 1==n );
+
+ stmt = prepare(db, "select myfunc(a), myfunc(a+a) from t order by a");
+ n = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ final int c0 = sqlite3_column_int(stmt, 0);
+ final int c1 = sqlite3_column_int(stmt, 1);
+ ++n;
+ affirm( 6 == c0 );
+ affirm( 12 == c1 );
+ }
+ sqlite3_finalize(stmt);
+ affirm( 1 == n );
+ affirm(!xFinalNull.value);
+
+ execSql(db, "SELECT myfunc(1) WHERE 0");
+ affirm(xFinalNull.value);
+ sqlite3_close_v2(db);
+ }
+
+ private void testUdfWindow(){
+ final sqlite3 db = createNewDb();
+ /* Example window function, table, and results taken from:
+ https://sqlite.org/windowfunctions.html#udfwinfunc */
+ final SQLFunction func = new WindowFunction<Integer>(){
+
+ 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;");
+ int n = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ final String s = sqlite3_column_text16(stmt, 0);
+ final int i = sqlite3_column_int(stmt, 1);
+ switch(++n){
+ case 1: affirm( "a".equals(s) && 9==i ); break;
+ case 2: affirm( "b".equals(s) && 12==i ); break;
+ case 3: affirm( "c".equals(s) && 16==i ); break;
+ case 4: affirm( "d".equals(s) && 12==i ); break;
+ case 5: affirm( "e".equals(s) && 9==i ); break;
+ default: affirm( false /* cannot happen */ );
+ }
+ }
+ sqlite3_finalize(stmt);
+ affirm( 5 == n );
+ sqlite3_close_v2(db);
+ }
+
+ private void listBoundMethods(){
+ if(false){
+ final java.lang.reflect.Field[] declaredFields =
+ CApi.class.getDeclaredFields();
+ outln("Bound constants:\n");
+ for(java.lang.reflect.Field field : declaredFields) {
+ if(java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
+ outln("\t",field.getName());
+ }
+ }
+ }
+ final java.lang.reflect.Method[] declaredMethods =
+ CApi.class.getDeclaredMethods();
+ final java.util.List<String> funcList = new java.util.ArrayList<>();
+ for(java.lang.reflect.Method m : declaredMethods){
+ if((m.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0){
+ final String name = m.getName();
+ if(name.startsWith("sqlite3_")){
+ funcList.add(name);
+ }
+ }
+ }
+ int count = 0;
+ java.util.Collections.sort(funcList);
+ for(String n : funcList){
+ ++count;
+ outln("\t",n,"()");
+ }
+ outln(count," functions named sqlite3_*.");
+ }
+
+ private void testTrace(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder<Integer> counter = new ValueHolder<>(0);
+ /* Ensure that characters outside of the UTF BMP survive the trip
+ from Java to sqlite3 and back to Java. (At no small efficiency
+ penalty.) */
+ final String nonBmpChar = "😃";
+ int rc = sqlite3_trace_v2(
+ db, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE
+ | SQLITE_TRACE_ROW | SQLITE_TRACE_CLOSE,
+ new TraceV2Callback(){
+ @Override public int call(int traceFlag, Object pNative, Object x){
+ ++counter.value;
+ //outln("TRACE "+traceFlag+" pNative = "+pNative.getClass().getName());
+ switch(traceFlag){
+ case SQLITE_TRACE_STMT:
+ affirm(pNative instanceof sqlite3_stmt);
+ //outln("TRACE_STMT sql = "+x);
+ affirm(x instanceof String);
+ affirm( ((String)x).indexOf(nonBmpChar) > 0 );
+ break;
+ case SQLITE_TRACE_PROFILE:
+ affirm(pNative instanceof sqlite3_stmt);
+ affirm(x instanceof Long);
+ //outln("TRACE_PROFILE time = "+x);
+ break;
+ case SQLITE_TRACE_ROW:
+ affirm(pNative instanceof sqlite3_stmt);
+ affirm(null == x);
+ //outln("TRACE_ROW = "+sqlite3_column_text16((sqlite3_stmt)pNative, 0));
+ break;
+ case SQLITE_TRACE_CLOSE:
+ affirm(pNative instanceof sqlite3);
+ affirm(null == x);
+ break;
+ default:
+ affirm(false /*cannot happen*/);
+ break;
+ }
+ return 0;
+ }
+ });
+ affirm( 0==rc );
+ execSql(db, "SELECT coalesce(null,null,'"+nonBmpChar+"'); "+
+ "SELECT 'w"+nonBmpChar+"orld'");
+ affirm( 6 == counter.value );
+ sqlite3_close_v2(db);
+ affirm( 7 == counter.value );
+ }
+
+ @SingleThreadOnly /* because threads inherently break this test */
+ private static void testBusy(){
+ final String dbName = "_busy-handler.db";
+ try{
+ final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3();
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+
+ int rc = sqlite3_open(dbName, outDb);
+ ++metrics.dbOpen;
+ affirm( 0 == rc );
+ final sqlite3 db1 = outDb.get();
+ execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)");
+ rc = sqlite3_open(dbName, outDb);
+ ++metrics.dbOpen;
+ affirm( 0 == rc );
+ affirm( outDb.get() != db1 );
+ final sqlite3 db2 = outDb.get();
+
+ affirm( "main".equals( sqlite3_db_name(db1, 0) ) );
+ rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo");
+ affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) );
+ affirm( "foo".equals( sqlite3_db_name(db1, 0) ) );
+ affirm( SQLITE_MISUSE == sqlite3_db_config(db1, 0, 0, null) );
+
+ final ValueHolder<Integer> xBusyCalled = new ValueHolder<>(0);
+ BusyHandlerCallback handler = new BusyHandlerCallback(){
+ @Override public int call(int n){
+ //outln("busy handler #"+n);
+ return n > 2 ? 0 : ++xBusyCalled.value;
+ }
+ };
+ rc = sqlite3_busy_handler(db2, handler);
+ affirm(0 == rc);
+
+ // Force a locked condition...
+ execSql(db1, "BEGIN EXCLUSIVE");
+ rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt);
+ affirm( SQLITE_BUSY == rc);
+ affirm( null == outStmt.get() );
+ affirm( 3 == xBusyCalled.value );
+ sqlite3_close_v2(db1);
+ sqlite3_close_v2(db2);
+ }finally{
+ try{(new java.io.File(dbName)).delete();}
+ catch(Exception e){/* ignore */}
+ }
+ }
+
+ private void testProgress(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder<Integer> counter = new ValueHolder<>(0);
+ sqlite3_progress_handler(db, 1, new ProgressHandlerCallback(){
+ @Override public int call(){
+ ++counter.value;
+ return 0;
+ }
+ });
+ execSql(db, "SELECT 1; SELECT 2;");
+ affirm( counter.value > 0 );
+ int nOld = counter.value;
+ sqlite3_progress_handler(db, 0, null);
+ execSql(db, "SELECT 1; SELECT 2;");
+ affirm( nOld == counter.value );
+ sqlite3_close_v2(db);
+ }
+
+ private void testCommitHook(){
+ final sqlite3 db = createNewDb();
+ sqlite3_extended_result_codes(db, true);
+ final ValueHolder<Integer> counter = new ValueHolder<>(0);
+ final ValueHolder<Integer> hookResult = new ValueHolder<>(0);
+ final CommitHookCallback theHook = new CommitHookCallback(){
+ @Override public int call(){
+ ++counter.value;
+ return hookResult.value;
+ }
+ };
+ CommitHookCallback oldHook = sqlite3_commit_hook(db, theHook);
+ affirm( null == oldHook );
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ affirm( 2 == counter.value );
+ execSql(db, "BEGIN; SELECT 1; SELECT 2; COMMIT;");
+ affirm( 2 == counter.value /* NOT invoked if no changes are made */ );
+ execSql(db, "BEGIN; update t set a='d' where a='c'; COMMIT;");
+ affirm( 3 == counter.value );
+ oldHook = sqlite3_commit_hook(db, theHook);
+ affirm( theHook == oldHook );
+ execSql(db, "BEGIN; update t set a='e' where a='d'; COMMIT;");
+ affirm( 4 == counter.value );
+ oldHook = sqlite3_commit_hook(db, null);
+ affirm( theHook == oldHook );
+ execSql(db, "BEGIN; update t set a='f' where a='e'; COMMIT;");
+ affirm( 4 == counter.value );
+ oldHook = sqlite3_commit_hook(db, null);
+ affirm( null == oldHook );
+ execSql(db, "BEGIN; update t set a='g' where a='f'; COMMIT;");
+ affirm( 4 == counter.value );
+
+ final CommitHookCallback newHook = new CommitHookCallback(){
+ @Override public int call(){return 0;}
+ };
+ oldHook = sqlite3_commit_hook(db, newHook);
+ affirm( null == oldHook );
+ execSql(db, "BEGIN; update t set a='h' where a='g'; COMMIT;");
+ affirm( 4 == counter.value );
+ oldHook = sqlite3_commit_hook(db, theHook);
+ affirm( newHook == oldHook );
+ execSql(db, "BEGIN; update t set a='i' where a='h'; COMMIT;");
+ affirm( 5 == counter.value );
+ hookResult.value = SQLITE_ERROR;
+ int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;");
+ affirm( SQLITE_CONSTRAINT_COMMITHOOK == rc );
+ affirm( 6 == counter.value );
+ sqlite3_close_v2(db);
+ }
+
+ private void testUpdateHook(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder<Integer> counter = new ValueHolder<>(0);
+ final ValueHolder<Integer> expectedOp = new ValueHolder<>(0);
+ final UpdateHookCallback theHook = new UpdateHookCallback(){
+ @Override
+ public void call(int opId, String dbName, String tableName, long rowId){
+ ++counter.value;
+ if( 0!=expectedOp.value ){
+ affirm( expectedOp.value == opId );
+ }
+ }
+ };
+ UpdateHookCallback oldHook = sqlite3_update_hook(db, theHook);
+ affirm( null == oldHook );
+ expectedOp.value = SQLITE_INSERT;
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ affirm( 3 == counter.value );
+ expectedOp.value = SQLITE_UPDATE;
+ execSql(db, "update t set a='d' where a='c';");
+ affirm( 4 == counter.value );
+ oldHook = sqlite3_update_hook(db, theHook);
+ affirm( theHook == oldHook );
+ expectedOp.value = SQLITE_DELETE;
+ execSql(db, "DELETE FROM t where a='d'");
+ affirm( 5 == counter.value );
+ oldHook = sqlite3_update_hook(db, null);
+ affirm( theHook == oldHook );
+ execSql(db, "update t set a='e' where a='b';");
+ affirm( 5 == counter.value );
+ oldHook = sqlite3_update_hook(db, null);
+ affirm( null == oldHook );
+
+ final UpdateHookCallback newHook = new UpdateHookCallback(){
+ @Override public void call(int opId, String dbName, String tableName, long rowId){
+ }
+ };
+ oldHook = sqlite3_update_hook(db, newHook);
+ affirm( null == oldHook );
+ execSql(db, "update t set a='h' where a='a'");
+ affirm( 5 == counter.value );
+ oldHook = sqlite3_update_hook(db, theHook);
+ affirm( newHook == oldHook );
+ expectedOp.value = SQLITE_UPDATE;
+ execSql(db, "update t set a='i' where a='h'");
+ affirm( 6 == counter.value );
+ sqlite3_close_v2(db);
+ }
+
+ /**
+ This test is functionally identical to testUpdateHook(), only with a
+ different callback type.
+ */
+ private void testPreUpdateHook(){
+ if( !sqlite3_compileoption_used("ENABLE_PREUPDATE_HOOK") ){
+ //outln("Skipping testPreUpdateHook(): no pre-update hook support.");
+ return;
+ }
+ final sqlite3 db = createNewDb();
+ final ValueHolder<Integer> counter = new ValueHolder<>(0);
+ final ValueHolder<Integer> expectedOp = new ValueHolder<>(0);
+ final PreupdateHookCallback theHook = new PreupdateHookCallback(){
+ @Override
+ public void call(sqlite3 db, int opId, String dbName, String dbTable,
+ long iKey1, long iKey2 ){
+ ++counter.value;
+ switch( opId ){
+ case SQLITE_UPDATE:
+ affirm( 0 < sqlite3_preupdate_count(db) );
+ affirm( null != sqlite3_preupdate_new(db, 0) );
+ affirm( null != sqlite3_preupdate_old(db, 0) );
+ break;
+ case SQLITE_INSERT:
+ affirm( null != sqlite3_preupdate_new(db, 0) );
+ break;
+ case SQLITE_DELETE:
+ affirm( null != sqlite3_preupdate_old(db, 0) );
+ break;
+ default:
+ break;
+ }
+ if( 0!=expectedOp.value ){
+ affirm( expectedOp.value == opId );
+ }
+ }
+ };
+ PreupdateHookCallback oldHook = sqlite3_preupdate_hook(db, theHook);
+ affirm( null == oldHook );
+ expectedOp.value = SQLITE_INSERT;
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ affirm( 3 == counter.value );
+ expectedOp.value = SQLITE_UPDATE;
+ execSql(db, "update t set a='d' where a='c';");
+ affirm( 4 == counter.value );
+ oldHook = sqlite3_preupdate_hook(db, theHook);
+ affirm( theHook == oldHook );
+ expectedOp.value = SQLITE_DELETE;
+ execSql(db, "DELETE FROM t where a='d'");
+ affirm( 5 == counter.value );
+ oldHook = sqlite3_preupdate_hook(db, null);
+ affirm( theHook == oldHook );
+ execSql(db, "update t set a='e' where a='b';");
+ affirm( 5 == counter.value );
+ oldHook = sqlite3_preupdate_hook(db, null);
+ affirm( null == oldHook );
+
+ final PreupdateHookCallback newHook = new PreupdateHookCallback(){
+ @Override
+ public void call(sqlite3 db, int opId, String dbName,
+ String tableName, long iKey1, long iKey2){
+ }
+ };
+ oldHook = sqlite3_preupdate_hook(db, newHook);
+ affirm( null == oldHook );
+ execSql(db, "update t set a='h' where a='a'");
+ affirm( 5 == counter.value );
+ oldHook = sqlite3_preupdate_hook(db, theHook);
+ affirm( newHook == oldHook );
+ expectedOp.value = SQLITE_UPDATE;
+ execSql(db, "update t set a='i' where a='h'");
+ affirm( 6 == counter.value );
+
+ sqlite3_close_v2(db);
+ }
+
+ private void testRollbackHook(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder<Integer> counter = new ValueHolder<>(0);
+ final RollbackHookCallback theHook = new RollbackHookCallback(){
+ @Override public void call(){
+ ++counter.value;
+ }
+ };
+ RollbackHookCallback oldHook = sqlite3_rollback_hook(db, theHook);
+ affirm( null == oldHook );
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ affirm( 0 == counter.value );
+ execSql(db, false, "BEGIN; SELECT 1; SELECT 2; ROLLBACK;");
+ affirm( 1 == counter.value /* contra to commit hook, is invoked if no changes are made */ );
+
+ final RollbackHookCallback newHook = new RollbackHookCallback(){
+ @Override public void call(){return;}
+ };
+ oldHook = sqlite3_rollback_hook(db, newHook);
+ affirm( theHook == oldHook );
+ execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+ affirm( 1 == counter.value );
+ oldHook = sqlite3_rollback_hook(db, theHook);
+ affirm( newHook == oldHook );
+ execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+ affirm( 2 == counter.value );
+ int rc = execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+ affirm( 0 == rc );
+ affirm( 3 == counter.value );
+ sqlite3_close_v2(db);
+ }
+
+ /**
+ If FTS5 is available, runs FTS5 tests, else returns with no side
+ effects. If it is available but loading of the FTS5 bits fails,
+ it throws.
+ */
+ @SuppressWarnings("unchecked")
+ @SingleThreadOnly /* because the Fts5 parts are not yet known to be
+ thread-safe */
+ private void testFts5() throws Exception {
+ if( !sqlite3_compileoption_used("ENABLE_FTS5") ){
+ //outln("SQLITE_ENABLE_FTS5 is not set. Skipping FTS5 tests.");
+ return;
+ }
+ Exception err = null;
+ try {
+ Class t = Class.forName("org.sqlite.jni.fts5.TesterFts5");
+ java.lang.reflect.Constructor ctor = t.getConstructor();
+ ctor.setAccessible(true);
+ final long timeStart = System.currentTimeMillis();
+ ctor.newInstance() /* will run all tests */;
+ final long timeEnd = System.currentTimeMillis();
+ outln("FTS5 Tests done in ",(timeEnd - timeStart),"ms");
+ }catch(ClassNotFoundException e){
+ outln("FTS5 classes not loaded.");
+ err = e;
+ }catch(NoSuchMethodException e){
+ outln("FTS5 tester ctor not found.");
+ err = e;
+ }catch(Exception e){
+ outln("Instantiation of FTS5 tester threw.");
+ err = e;
+ }
+ if( null != err ){
+ outln("Exception: "+err);
+ err.printStackTrace();
+ throw err;
+ }
+ }
+
+ private void testAuthorizer(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder<Integer> counter = new ValueHolder<>(0);
+ final ValueHolder<Integer> authRc = new ValueHolder<>(0);
+ final AuthorizerCallback auth = new AuthorizerCallback(){
+ public int call(int op, String s0, String s1, String s2, String s3){
+ ++counter.value;
+ //outln("xAuth(): "+s0+" "+s1+" "+s2+" "+s3);
+ return authRc.value;
+ }
+ };
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ sqlite3_set_authorizer(db, auth);
+ execSql(db, "UPDATE t SET a=1");
+ affirm( 1 == counter.value );
+ authRc.value = SQLITE_DENY;
+ int rc = execSql(db, false, "UPDATE t SET a=2");
+ affirm( SQLITE_AUTH==rc );
+ sqlite3_set_authorizer(db, null);
+ rc = execSql(db, false, "UPDATE t SET a=2");
+ affirm( 0==rc );
+ // TODO: expand these tests considerably
+ sqlite3_close(db);
+ }
+
+ @SingleThreadOnly /* because multiple threads legitimately make these
+ results unpredictable */
+ private synchronized void testAutoExtension(){
+ final ValueHolder<Integer> val = new ValueHolder<>(0);
+ final ValueHolder<String> toss = new ValueHolder<>(null);
+ final AutoExtensionCallback ax = new AutoExtensionCallback(){
+ @Override public int call(sqlite3 db){
+ ++val.value;
+ if( null!=toss.value ){
+ throw new RuntimeException(toss.value);
+ }
+ return 0;
+ }
+ };
+ int rc = sqlite3_auto_extension( ax );
+ affirm( 0==rc );
+ sqlite3_close(createNewDb());
+ affirm( 1==val.value );
+ sqlite3_close(createNewDb());
+ affirm( 2==val.value );
+ sqlite3_reset_auto_extension();
+ sqlite3_close(createNewDb());
+ affirm( 2==val.value );
+ rc = sqlite3_auto_extension( ax );
+ affirm( 0==rc );
+ // Must not add a new entry
+ rc = sqlite3_auto_extension( ax );
+ affirm( 0==rc );
+ sqlite3_close( createNewDb() );
+ affirm( 3==val.value );
+
+ sqlite3 db = createNewDb();
+ affirm( 4==val.value );
+ execSql(db, "ATTACH ':memory:' as foo");
+ affirm( 4==val.value, "ATTACH uses the same connection, not sub-connections." );
+ sqlite3_close(db);
+ db = null;
+
+ affirm( sqlite3_cancel_auto_extension(ax) );
+ affirm( !sqlite3_cancel_auto_extension(ax) );
+ sqlite3_close(createNewDb());
+ affirm( 4==val.value );
+ rc = sqlite3_auto_extension( ax );
+ affirm( 0==rc );
+ Exception err = null;
+ toss.value = "Throwing from auto_extension.";
+ try{
+ sqlite3_close(createNewDb());
+ }catch(Exception e){
+ err = e;
+ }
+ affirm( err!=null );
+ affirm( err.getMessage().indexOf(toss.value)>0 );
+ toss.value = null;
+
+ val.value = 0;
+ final AutoExtensionCallback ax2 = new AutoExtensionCallback(){
+ @Override public int call(sqlite3 db){
+ ++val.value;
+ return 0;
+ }
+ };
+ rc = sqlite3_auto_extension( ax2 );
+ affirm( 0 == rc );
+ sqlite3_close(createNewDb());
+ affirm( 2 == val.value );
+ affirm( sqlite3_cancel_auto_extension(ax) );
+ affirm( !sqlite3_cancel_auto_extension(ax) );
+ sqlite3_close(createNewDb());
+ affirm( 3 == val.value );
+ rc = sqlite3_auto_extension( ax );
+ affirm( 0 == rc );
+ sqlite3_close(createNewDb());
+ affirm( 5 == val.value );
+ affirm( sqlite3_cancel_auto_extension(ax2) );
+ affirm( !sqlite3_cancel_auto_extension(ax2) );
+ sqlite3_close(createNewDb());
+ affirm( 6 == val.value );
+ rc = sqlite3_auto_extension( ax2 );
+ affirm( 0 == rc );
+ sqlite3_close(createNewDb());
+ affirm( 8 == val.value );
+
+ sqlite3_reset_auto_extension();
+ sqlite3_close(createNewDb());
+ affirm( 8 == val.value );
+ affirm( !sqlite3_cancel_auto_extension(ax) );
+ affirm( !sqlite3_cancel_auto_extension(ax2) );
+ sqlite3_close(createNewDb());
+ affirm( 8 == val.value );
+ }
+
+
+ private void testColumnMetadata(){
+ final sqlite3 db = createNewDb();
+ execSql(db, new String[] {
+ "CREATE TABLE t(a duck primary key not null collate noCase); ",
+ "INSERT INTO t(a) VALUES(1),(2),(3);"
+ });
+ OutputPointer.Bool bNotNull = new OutputPointer.Bool();
+ OutputPointer.Bool bPrimaryKey = new OutputPointer.Bool();
+ OutputPointer.Bool bAutoinc = new OutputPointer.Bool();
+ OutputPointer.String zCollSeq = new OutputPointer.String();
+ OutputPointer.String zDataType = new OutputPointer.String();
+ int rc = sqlite3_table_column_metadata(
+ db, "main", "t", "a", zDataType, zCollSeq,
+ bNotNull, bPrimaryKey, bAutoinc);
+ affirm( 0==rc );
+ affirm( bPrimaryKey.value );
+ affirm( !bAutoinc.value );
+ affirm( bNotNull.value );
+ affirm( "noCase".equals(zCollSeq.value) );
+ affirm( "duck".equals(zDataType.value) );
+
+ TableColumnMetadata m =
+ sqlite3_table_column_metadata(db, "main", "t", "a");
+ affirm( null != m );
+ affirm( bPrimaryKey.value == m.isPrimaryKey() );
+ affirm( bAutoinc.value == m.isAutoincrement() );
+ affirm( bNotNull.value == m.isNotNull() );
+ affirm( zCollSeq.value.equals(m.getCollation()) );
+ affirm( zDataType.value.equals(m.getDataType()) );
+
+ affirm( null == sqlite3_table_column_metadata(db, "nope", "t", "a") );
+ affirm( null == sqlite3_table_column_metadata(db, "main", "nope", "a") );
+
+ m = sqlite3_table_column_metadata(db, "main", "t", null)
+ /* Check only for existence of table */;
+ affirm( null != m );
+ affirm( m.isPrimaryKey() );
+ affirm( !m.isAutoincrement() );
+ affirm( !m.isNotNull() );
+ affirm( "BINARY".equalsIgnoreCase(m.getCollation()) );
+ affirm( "INTEGER".equalsIgnoreCase(m.getDataType()) );
+
+ sqlite3_close_v2(db);
+ }
+
+ private void testTxnState(){
+ final sqlite3 db = createNewDb();
+ affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
+ affirm( sqlite3_get_autocommit(db) );
+ execSql(db, "BEGIN;");
+ affirm( !sqlite3_get_autocommit(db) );
+ affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
+ execSql(db, "SELECT * FROM sqlite_schema;");
+ affirm( SQLITE_TXN_READ == sqlite3_txn_state(db, "main") );
+ execSql(db, "CREATE TABLE t(a);");
+ affirm( SQLITE_TXN_WRITE == sqlite3_txn_state(db, null) );
+ execSql(db, "ROLLBACK;");
+ affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
+ sqlite3_close_v2(db);
+ }
+
+
+ private void testExplain(){
+ final sqlite3 db = createNewDb();
+ sqlite3_stmt stmt = prepare(db,"SELECT 1");
+
+ affirm( 0 == sqlite3_stmt_isexplain(stmt) );
+ int rc = sqlite3_stmt_explain(stmt, 1);
+ affirm( 1 == sqlite3_stmt_isexplain(stmt) );
+ rc = sqlite3_stmt_explain(stmt, 2);
+ affirm( 2 == sqlite3_stmt_isexplain(stmt) );
+ sqlite3_finalize(stmt);
+ sqlite3_close_v2(db);
+ }
+
+ private void testLimit(){
+ final sqlite3 db = createNewDb();
+ int v;
+
+ v = sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1);
+ affirm( v > 0 );
+ affirm( v == sqlite3_limit(db, SQLITE_LIMIT_LENGTH, v-1) );
+ affirm( v-1 == sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1) );
+ sqlite3_close_v2(db);
+ }
+
+ private void testComplete(){
+ affirm( 0==sqlite3_complete("select 1") );
+ affirm( 0!=sqlite3_complete("select 1;") );
+ affirm( 0!=sqlite3_complete("nope 'nope' 'nope' 1;"), "Yup" );
+ }
+
+ private void testKeyword(){
+ final int n = sqlite3_keyword_count();
+ affirm( n>0 );
+ affirm( !sqlite3_keyword_check("_nope_") );
+ affirm( sqlite3_keyword_check("seLect") );
+ affirm( null!=sqlite3_keyword_name(0) );
+ affirm( null!=sqlite3_keyword_name(n-1) );
+ affirm( null==sqlite3_keyword_name(n) );
+ }
+
+ private void testBackup(){
+ final sqlite3 dbDest = createNewDb();
+
+ try (sqlite3 dbSrc = createNewDb()) {
+ execSql(dbSrc, new String[]{
+ "pragma page_size=512; VACUUM;",
+ "create table t(a);",
+ "insert into t(a) values(1),(2),(3);"
+ });
+ affirm( null==sqlite3_backup_init(dbSrc,"main",dbSrc,"main") );
+ try (sqlite3_backup b = sqlite3_backup_init(dbDest,"main",dbSrc,"main")) {
+ affirm( null!=b );
+ affirm( b.getNativePointer()!=0 );
+ int rc;
+ while( SQLITE_DONE!=(rc = sqlite3_backup_step(b, 1)) ){
+ affirm( 0==rc );
+ }
+ affirm( sqlite3_backup_pagecount(b) > 0 );
+ rc = sqlite3_backup_finish(b);
+ affirm( 0==rc );
+ affirm( b.getNativePointer()==0 );
+ }
+ }
+
+ try (sqlite3_stmt stmt = prepare(dbDest,"SELECT sum(a) from t")) {
+ sqlite3_step(stmt);
+ affirm( sqlite3_column_int(stmt,0) == 6 );
+ }
+ sqlite3_close_v2(dbDest);
+ }
+
+ private void testRandomness(){
+ byte[] foo = new byte[20];
+ int i = 0;
+ for( byte b : foo ){
+ i += b;
+ }
+ affirm( i==0 );
+ sqlite3_randomness(foo);
+ for( byte b : foo ){
+ if(b!=0) ++i;
+ }
+ affirm( i!=0, "There's a very slight chance that 0 is actually correct." );
+ }
+
+ private void testBlobOpen(){
+ final sqlite3 db = createNewDb();
+
+ execSql(db, "CREATE TABLE T(a BLOB);"
+ +"INSERT INTO t(rowid,a) VALUES(1, 'def'),(2, 'XYZ');"
+ );
+ final OutputPointer.sqlite3_blob pOut = new OutputPointer.sqlite3_blob();
+ int rc = sqlite3_blob_open(db, "main", "t", "a",
+ sqlite3_last_insert_rowid(db), 1, pOut);
+ affirm( 0==rc );
+ sqlite3_blob b = pOut.take();
+ affirm( null!=b );
+ affirm( 0!=b.getNativePointer() );
+ affirm( 3==sqlite3_blob_bytes(b) );
+ rc = sqlite3_blob_write( b, new byte[] {100, 101, 102 /*"DEF"*/}, 0);
+ affirm( 0==rc );
+ rc = sqlite3_blob_close(b);
+ affirm( 0==rc );
+ rc = sqlite3_blob_close(b);
+ affirm( 0!=rc );
+ affirm( 0==b.getNativePointer() );
+ sqlite3_stmt stmt = prepare(db,"SELECT length(a), a FROM t ORDER BY a");
+ affirm( SQLITE_ROW == sqlite3_step(stmt) );
+ affirm( 3 == sqlite3_column_int(stmt,0) );
+ affirm( "def".equals(sqlite3_column_text16(stmt,1)) );
+ sqlite3_finalize(stmt);
+
+ b = sqlite3_blob_open(db, "main", "t", "a",
+ sqlite3_last_insert_rowid(db), 0);
+ affirm( null!=b );
+ rc = sqlite3_blob_reopen(b, 2);
+ affirm( 0==rc );
+ final byte[] tgt = new byte[3];
+ rc = sqlite3_blob_read(b, tgt, 0);
+ affirm( 0==rc );
+ affirm( 100==tgt[0] && 101==tgt[1] && 102==tgt[2], "DEF" );
+ rc = sqlite3_blob_close(b);
+ affirm( 0==rc );
+
+ if( !sqlite3_jni_supports_nio() ){
+ outln("WARNING: skipping tests for ByteBuffer-using sqlite3_blob APIs ",
+ "because this platform lacks that support.");
+ sqlite3_close_v2(db);
+ return;
+ }
+ /* Sanity checks for the java.nio.ByteBuffer-taking overloads of
+ sqlite3_blob_read/write(). */
+ execSql(db, "UPDATE t SET a=zeroblob(10)");
+ b = sqlite3_blob_open(db, "main", "t", "a", 1, 1);
+ affirm( null!=b );
+ java.nio.ByteBuffer bb = java.nio.ByteBuffer.allocateDirect(10);
+ for( byte i = 0; i < 10; ++i ){
+ bb.put((int)i, (byte)(48+i & 0xff));
+ }
+ rc = sqlite3_blob_write(b, 1, bb, 1, 10);
+ affirm( rc==SQLITE_ERROR, "b length < (srcOffset + bb length)" );
+ rc = sqlite3_blob_write(b, -1, bb);
+ affirm( rc==SQLITE_ERROR, "Target offset may not be negative" );
+ rc = sqlite3_blob_write(b, 0, bb, -1, -1);
+ affirm( rc==SQLITE_ERROR, "Source offset may not be negative" );
+ rc = sqlite3_blob_write(b, 1, bb, 1, 8);
+ affirm( rc==0 );
+ // b's contents: 0 49 50 51 52 53 54 55 56 0
+ // ascii: 0 '1' '2' '3' '4' '5' '6' '7' '8' 0
+ byte br[] = new byte[10];
+ java.nio.ByteBuffer bbr =
+ java.nio.ByteBuffer.allocateDirect(bb.limit());
+ rc = sqlite3_blob_read( b, br, 0 );
+ affirm( rc==0 );
+ rc = sqlite3_blob_read( b, bbr );
+ affirm( rc==0 );
+ java.nio.ByteBuffer bbr2 = sqlite3_blob_read_nio_buffer(b, 0, 12);
+ affirm( null==bbr2, "Read size is too big");
+ bbr2 = sqlite3_blob_read_nio_buffer(b, -1, 3);
+ affirm( null==bbr2, "Source offset is negative");
+ bbr2 = sqlite3_blob_read_nio_buffer(b, 5, 6);
+ affirm( null==bbr2, "Read pos+size is too big");
+ bbr2 = sqlite3_blob_read_nio_buffer(b, 4, 7);
+ affirm( null==bbr2, "Read pos+size is too big");
+ bbr2 = sqlite3_blob_read_nio_buffer(b, 4, 6);
+ affirm( null!=bbr2 );
+ java.nio.ByteBuffer bbr3 =
+ java.nio.ByteBuffer.allocateDirect(2 * bb.limit());
+ java.nio.ByteBuffer bbr4 =
+ java.nio.ByteBuffer.allocateDirect(5);
+ rc = sqlite3_blob_read( b, bbr3 );
+ affirm( rc==0 );
+ rc = sqlite3_blob_read( b, bbr4 );
+ affirm( rc==0 );
+ affirm( sqlite3_blob_bytes(b)==bbr3.limit() );
+ affirm( 5==bbr4.limit() );
+ sqlite3_blob_close(b);
+ affirm( 0==br[0] );
+ affirm( 0==br[9] );
+ affirm( 0==bbr.get(0) );
+ affirm( 0==bbr.get(9) );
+ affirm( bbr2.limit() == 6 );
+ affirm( 0==bbr3.get(0) );
+ {
+ Exception ex = null;
+ try{ bbr3.get(11); }
+ catch(Exception e){ex = e;}
+ affirm( ex instanceof IndexOutOfBoundsException,
+ "bbr3.limit() was reset by read()" );
+ ex = null;
+ }
+ affirm( 0==bbr4.get(0) );
+ for( int i = 1; i < 9; ++i ){
+ affirm( br[i] == 48 + i );
+ affirm( br[i] == bbr.get(i) );
+ affirm( br[i] == bbr3.get(i) );
+ if( i>3 ){
+ affirm( br[i] == bbr2.get(i-4) );
+ }
+ if( i < bbr4.limit() ){
+ affirm( br[i] == bbr4.get(i) );
+ }
+ }
+ sqlite3_close_v2(db);
+ }
+
+ private void testPrepareMulti(){
+ final sqlite3 db = createNewDb();
+ final String[] sql = {
+ "create table t(","a)",
+ "; insert into t(a) values(1),(2),(3);",
+ "select a from t;"
+ };
+ final List<sqlite3_stmt> liStmt = new ArrayList<sqlite3_stmt>();
+ final PrepareMultiCallback proxy = new PrepareMultiCallback.StepAll();
+ final ValueHolder<String> toss = new ValueHolder<>(null);
+ PrepareMultiCallback m = new PrepareMultiCallback() {
+ @Override public int call(sqlite3_stmt st){
+ liStmt.add(st);
+ if( null!=toss.value ){
+ throw new RuntimeException(toss.value);
+ }
+ return proxy.call(st);
+ }
+ };
+ int rc = sqlite3_prepare_multi(db, sql, m);
+ affirm( 0==rc );
+ affirm( liStmt.size() == 3 );
+ for( sqlite3_stmt st : liStmt ){
+ sqlite3_finalize(st);
+ }
+ toss.value = "This is an exception.";
+ rc = sqlite3_prepare_multi(db, "SELECT 1", m);
+ affirm( SQLITE_ERROR==rc );
+ affirm( sqlite3_errmsg(db).indexOf(toss.value)>0 );
+ sqlite3_close_v2(db);
+ }
+
+ /* Copy/paste/rename this to add new tests. */
+ private void _testTemplate(){
+ final sqlite3 db = createNewDb();
+ sqlite3_stmt stmt = prepare(db,"SELECT 1");
+ sqlite3_finalize(stmt);
+ sqlite3_close_v2(db);
+ }
+
+
+ @ManualTest /* we really only want to run this test manually */
+ private void testSleep(){
+ out("Sleeping briefly... ");
+ sqlite3_sleep(600);
+ outln("Woke up.");
+ }
+
+ private void nap() throws InterruptedException {
+ if( takeNaps ){
+ Thread.sleep(java.util.concurrent.ThreadLocalRandom.current().nextInt(3, 17), 0);
+ }
+ }
+
+ @ManualTest /* because we only want to run this test on demand */
+ private void testFail(){
+ affirm( false, "Intentional failure." );
+ }
+
+ private void runTests(boolean fromThread) throws Exception {
+ if(false) showCompileOption();
+ List<java.lang.reflect.Method> mlist = testMethods;
+ affirm( null!=mlist );
+ if( shuffle ){
+ mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) );
+ java.util.Collections.shuffle(mlist);
+ }
+ if( (!fromThread && listRunTests>0) || listRunTests>1 ){
+ synchronized(this.getClass()){
+ if( !fromThread ){
+ out("Initial test"," list: ");
+ for(java.lang.reflect.Method m : testMethods){
+ out(m.getName()+" ");
+ }
+ outln();
+ outln("(That list excludes some which are hard-coded to run.)");
+ }
+ out("Running"," tests: ");
+ for(java.lang.reflect.Method m : mlist){
+ out(m.getName()+" ");
+ }
+ outln();
+ }
+ }
+ for(java.lang.reflect.Method m : mlist){
+ nap();
+ try{
+ m.invoke(this);
+ }catch(java.lang.reflect.InvocationTargetException e){
+ outln("FAILURE: ",m.getName(),"(): ", e.getCause());
+ throw e;
+ }
+ }
+ synchronized( this.getClass() ){
+ ++nTestRuns;
+ }
+ }
+
+ public void run() {
+ try {
+ runTests(0!=this.tId);
+ }catch(Exception e){
+ synchronized( listErrors ){
+ listErrors.add(e);
+ }
+ }finally{
+ affirm( sqlite3_java_uncache_thread() );
+ affirm( !sqlite3_java_uncache_thread() );
+ }
+ }
+
+ /**
+ Runs the basic sqlite3 JNI binding sanity-check suite.
+
+ CLI flags:
+
+ -q|-quiet: disables most test output.
+
+ -t|-thread N: runs the tests in N threads
+ concurrently. Default=1.
+
+ -r|-repeat N: repeats the tests in a loop N times, each one
+ consisting of the -thread value's threads.
+
+ -shuffle: randomizes the order of most of the test functions.
+
+ -naps: sleep small random intervals between tests in order to add
+ some chaos for cross-thread contention.
+
+
+ -list-tests: outputs the list of tests being run, minus some
+ which are hard-coded. In multi-threaded mode, use this twice to
+ to emit the list run by each thread (which may differ from the initial
+ list, in particular if -shuffle is used).
+
+ -fail: forces an exception to be thrown during the test run. Use
+ with -shuffle to make its appearance unpredictable.
+
+ -v: emit some developer-mode info at the end.
+ */
+ public static void main(String[] args) throws Exception {
+ Integer nThread = 1;
+ boolean doSomethingForDev = false;
+ Integer nRepeat = 1;
+ boolean forceFail = false;
+ boolean sqlLog = false;
+ boolean configLog = false;
+ boolean squelchTestOutput = false;
+ for( int i = 0; i < args.length; ){
+ String arg = args[i++];
+ if(arg.startsWith("-")){
+ arg = arg.replaceFirst("-+","");
+ if(arg.equals("v")){
+ doSomethingForDev = true;
+ //listBoundMethods();
+ }else if(arg.equals("t") || arg.equals("thread")){
+ nThread = Integer.parseInt(args[i++]);
+ }else if(arg.equals("r") || arg.equals("repeat")){
+ nRepeat = Integer.parseInt(args[i++]);
+ }else if(arg.equals("shuffle")){
+ shuffle = true;
+ }else if(arg.equals("list-tests")){
+ ++listRunTests;
+ }else if(arg.equals("fail")){
+ forceFail = true;
+ }else if(arg.equals("sqllog")){
+ sqlLog = true;
+ }else if(arg.equals("configlog")){
+ configLog = true;
+ }else if(arg.equals("naps")){
+ takeNaps = true;
+ }else if(arg.equals("q") || arg.equals("quiet")){
+ squelchTestOutput = true;
+ }else{
+ throw new IllegalArgumentException("Unhandled flag:"+arg);
+ }
+ }
+ }
+
+ if( sqlLog ){
+ if( sqlite3_compileoption_used("ENABLE_SQLLOG") ){
+ final ConfigSqlLogCallback log = new ConfigSqlLogCallback() {
+ @Override public void call(sqlite3 db, String msg, int op){
+ switch(op){
+ case 0: outln("Opening db: ",db); break;
+ case 1: outln("SQL ",db,": ",msg); break;
+ case 2: outln("Closing db: ",db); break;
+ }
+ }
+ };
+ int rc = sqlite3_config( log );
+ affirm( 0==rc );
+ rc = sqlite3_config( (ConfigSqlLogCallback)null );
+ affirm( 0==rc );
+ rc = sqlite3_config( log );
+ affirm( 0==rc );
+ }else{
+ outln("WARNING: -sqllog is not active because library was built ",
+ "without SQLITE_ENABLE_SQLLOG.");
+ }
+ }
+ if( configLog ){
+ final ConfigLogCallback log = new ConfigLogCallback() {
+ @Override public void call(int code, String msg){
+ outln("ConfigLogCallback: ",ResultCode.getEntryForInt(code),": ", msg);
+ };
+ };
+ int rc = sqlite3_config( log );
+ affirm( 0==rc );
+ rc = sqlite3_config( (ConfigLogCallback)null );
+ affirm( 0==rc );
+ rc = sqlite3_config( log );
+ affirm( 0==rc );
+ }
+
+ quietMode = squelchTestOutput;
+ outln("If you just saw warning messages regarding CallStaticObjectMethod, ",
+ "you are very likely seeing the side effects of a known openjdk8 ",
+ "bug. It is unsightly but does not affect the library.");
+
+ {
+ // Build list of tests to run from the methods named test*().
+ testMethods = new ArrayList<>();
+ int nSkipped = 0;
+ for(final java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){
+ final String name = m.getName();
+ if( name.equals("testFail") ){
+ if( forceFail ){
+ testMethods.add(m);
+ }
+ }else if( m.isAnnotationPresent( RequiresJniNio.class )
+ && !sqlite3_jni_supports_nio() ){
+ outln("Skipping test for lack of JNI java.nio.ByteBuffer support: ",
+ name,"()\n");
+ ++nSkipped;
+ }else if( !m.isAnnotationPresent( ManualTest.class ) ){
+ if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){
+ out("Skipping test in multi-thread mode: ",name,"()\n");
+ ++nSkipped;
+ }else if( name.startsWith("test") ){
+ testMethods.add(m);
+ }
+ }
+ }
+ }
+
+ final long timeStart = System.currentTimeMillis();
+ int nLoop = 0;
+ switch( sqlite3_threadsafe() ){ /* Sanity checking */
+ case 0:
+ affirm( SQLITE_ERROR==sqlite3_config( SQLITE_CONFIG_SINGLETHREAD ),
+ "Could not switch to single-thread mode." );
+ affirm( SQLITE_ERROR==sqlite3_config( SQLITE_CONFIG_MULTITHREAD ),
+ "Could switch to multithread mode." );
+ affirm( SQLITE_ERROR==sqlite3_config( SQLITE_CONFIG_SERIALIZED ),
+ "Could not switch to serialized threading mode." );
+ outln("This is a single-threaded build. Not using threads.");
+ nThread = 1;
+ break;
+ case 1:
+ case 2:
+ affirm( 0==sqlite3_config( SQLITE_CONFIG_SINGLETHREAD ),
+ "Could not switch to single-thread mode." );
+ affirm( 0==sqlite3_config( SQLITE_CONFIG_MULTITHREAD ),
+ "Could not switch to multithread mode." );
+ affirm( 0==sqlite3_config( SQLITE_CONFIG_SERIALIZED ),
+ "Could not switch to serialized threading mode." );
+ break;
+ default:
+ affirm( false, "Unhandled SQLITE_THREADSAFE value." );
+ }
+ outln("libversion_number: ",
+ sqlite3_libversion_number(),"\n",
+ sqlite3_libversion(),"\n",SQLITE_SOURCE_ID,"\n",
+ "SQLITE_THREADSAFE=",sqlite3_threadsafe());
+ outln("JVM NIO support? ",sqlite3_jni_supports_nio() ? "YES" : "NO");
+ final boolean showLoopCount = (nRepeat>1 && nThread>1);
+ if( showLoopCount ){
+ outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each.");
+ }
+ if( takeNaps ) outln("Napping between tests is enabled.");
+ for( int n = 0; n < nRepeat; ++n ){
+ ++nLoop;
+ if( showLoopCount ) out((1==nLoop ? "" : " ")+nLoop);
+ if( nThread<=1 ){
+ new Tester1(0).runTests(false);
+ continue;
+ }
+ Tester1.mtMode = true;
+ final ExecutorService ex = Executors.newFixedThreadPool( nThread );
+ for( int i = 0; i < nThread; ++i ){
+ ex.submit( new Tester1(i), i );
+ }
+ ex.shutdown();
+ try{
+ ex.awaitTermination(nThread*200, java.util.concurrent.TimeUnit.MILLISECONDS);
+ ex.shutdownNow();
+ }catch (InterruptedException ie){
+ ex.shutdownNow();
+ Thread.currentThread().interrupt();
+ }
+ if( !listErrors.isEmpty() ){
+ quietMode = false;
+ outln("TEST ERRORS:");
+ Exception err = null;
+ for( Exception e : listErrors ){
+ e.printStackTrace();
+ if( null==err ) err = e;
+ }
+ if( null!=err ) throw err;
+ }
+ }
+ if( showLoopCount ) outln();
+ quietMode = false;
+
+ final long timeEnd = System.currentTimeMillis();
+ outln("Tests done. Metrics across ",nTestRuns," total iteration(s):");
+ outln("\tAssertions checked: ",affirmCount);
+ outln("\tDatabases opened: ",metrics.dbOpen);
+ if( doSomethingForDev ){
+ sqlite3_jni_internal_details();
+ }
+ affirm( 0==sqlite3_release_memory(1) );
+ sqlite3_shutdown();
+ int nMethods = 0;
+ int nNatives = 0;
+ final java.lang.reflect.Method[] declaredMethods =
+ CApi.class.getDeclaredMethods();
+ for(java.lang.reflect.Method m : declaredMethods){
+ final int mod = m.getModifiers();
+ if( 0!=(mod & java.lang.reflect.Modifier.STATIC) ){
+ final String name = m.getName();
+ if(name.startsWith("sqlite3_")){
+ ++nMethods;
+ if( 0!=(mod & java.lang.reflect.Modifier.NATIVE) ){
+ ++nNatives;
+ }
+ }
+ }
+ }
+ outln("\tCApi.sqlite3_*() methods: "+
+ nMethods+" total, with "+
+ nNatives+" native, "+
+ (nMethods - nNatives)+" Java"
+ );
+ outln("\tTotal test time = "
+ +(timeEnd - timeStart)+"ms");
+ }
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java b/ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java
new file mode 100644
index 0000000..56465a2
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java
@@ -0,0 +1,50 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import org.sqlite.jni.annotation.Nullable;
+
+/**
+ Callback for use with {@link CApi#sqlite3_trace_v2}.
+*/
+public interface TraceV2Callback extends CallbackProxy {
+ /**
+ Called by sqlite3 for various tracing operations, as per
+ sqlite3_trace_v2(). Note that this interface elides the 2nd
+ argument to the native trace callback, as that role is better
+ filled by instance-local state.
+
+ <p>These callbacks may throw, in which case their exceptions are
+ converted to C-level error information.
+
+ <p>The 2nd argument to this function, if non-null, will be a an
+ sqlite3 or sqlite3_stmt object, depending on the first argument
+ (see below).
+
+ <p>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:
+
+ <p>- SQLITE_TRACE_STMT: pNative is a sqlite3_stmt. pX is a String
+ containing the prepared SQL.
+
+ <p>- SQLITE_TRACE_PROFILE: pNative is a sqlite3_stmt. pX is a Long
+ holding an approximate number of nanoseconds the statement took
+ to run.
+
+ <p>- SQLITE_TRACE_ROW: pNative is a sqlite3_stmt. pX is null.
+
+ <p>- SQLITE_TRACE_CLOSE: pNative is a sqlite3. pX is null.
+ */
+ int call(int traceFlag, Object pNative, @Nullable Object pX);
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java b/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java
new file mode 100644
index 0000000..e3d491f
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java
@@ -0,0 +1,26 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_update_hook}.
+*/
+public interface UpdateHookCallback extends CallbackProxy {
+ /**
+ Must function as described for the C-level sqlite3_update_hook()
+ callback. If it throws, the exception is translated into
+ a db-level error.
+ */
+ void call(int opId, String dbName, String tableName, long rowId);
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java b/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java
new file mode 100644
index 0000000..0a469fe
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java
@@ -0,0 +1,27 @@
+/*
+** 2023-10-16
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the ValueHolder utility class for the sqlite3
+** JNI bindings.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A helper class which simply holds a single value. Its primary use
+ is for communicating values out of anonymous classes, as doing so
+ requires a "final" reference, as well as communicating aggregate
+ SQL function state across calls to such functions.
+*/
+public class ValueHolder<T> {
+ public T value;
+ public ValueHolder(){}
+ public ValueHolder(T v){value = v;}
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/WindowFunction.java b/ext/jni/src/org/sqlite/jni/capi/WindowFunction.java
new file mode 100644
index 0000000..eaf1bb9
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/WindowFunction.java
@@ -0,0 +1,39 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+
+/**
+ A SQLFunction implementation for window functions. Note that
+ WindowFunction inherits from {@link AggregateFunction} and each
+ instance is required to implement the inherited abstract methods
+ from that class. See {@link AggregateFunction} for information on
+ managing the UDF's invocation-specific state.
+*/
+public abstract class WindowFunction<T> extends AggregateFunction<T> {
+
+ /**
+ As for the xInverse() argument of the C API's
+ sqlite3_create_window_function(). If this function throws, the
+ exception is not propagated and a warning might be emitted
+ to a debugging channel.
+ */
+ public abstract void xInverse(sqlite3_context cx, sqlite3_value[] args);
+
+ /**
+ As for the xValue() argument of the C API's sqlite3_create_window_function().
+ See xInverse() for the fate of any exceptions this throws.
+ */
+ public abstract void xValue(sqlite3_context cx);
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java b/ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java
new file mode 100644
index 0000000..372e4ec
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.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 declares JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for a hook called by SQLite when certain client-provided
+ state are destroyed. It gets its name from the pervasive use of
+ the symbol name xDestroy() for this purpose in the C API
+ documentation.
+*/
+public interface XDestroyCallback {
+ /**
+ Must perform any cleanup required by this object. Must not
+ throw. Must not call back into the sqlite3 API, else it might
+ invoke a deadlock.
+
+ WARNING: as a rule, it is never safe to register individual
+ instances with this interface multiple times in the
+ library. e.g., do not register the same CollationCallback with
+ multiple arities or names using sqlite3_create_collation(). If
+ this rule is violated, the library will eventually try to free
+ each individual reference, leading to memory corruption or a
+ crash via duplicate free().
+ */
+ public void xDestroy();
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/package-info.java b/ext/jni/src/org/sqlite/jni/capi/package-info.java
new file mode 100644
index 0000000..127f380
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/package-info.java
@@ -0,0 +1,89 @@
+/**
+ This package houses a JNI binding to the SQLite3 C API.
+
+ <p>The primary interfaces are in {@link
+ org.sqlite.jni.capi.CApi}.</p>
+
+ <h1>API Goals and Requirements</h1>
+
+ <ul>
+
+ <li>A 1-to-1(-ish) mapping of the C API to Java via JNI, insofar
+ as cross-language semantics allow for. A closely-related goal is
+ that <a href='https://sqlite.org/c3ref/intro.html'>the C
+ documentation</a> should be usable as-is, insofar as possible,
+ for most of the JNI binding. As a rule, undocumented symbols in
+ the Java interface behave as documented for their C API
+ counterpart. Only semantic differences and Java-specific features
+ are documented here.</li>
+
+ <li>Support Java as far back as version 8 (2014).</li>
+
+ <li>Environment-independent. Should work everywhere both Java and
+ SQLite3 do.</li>
+
+ <li>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.</li>
+
+ </ul>
+
+ <h2>Non-Goals</h2>
+
+ <ul>
+
+ <li>Creation of high-level OO wrapper APIs. Clients are free to
+ create them off of the C-style API.</li>
+
+ <li>Support for mixed-mode operation, where client code accesses
+ SQLite both via the Java-side API and the C API via their own
+ native code. In such cases, proxy functionalities (primarily
+ callback handler wrappers of all sorts) may fail because the
+ C-side use of the SQLite APIs will bypass those proxies.</li>
+
+ </ul>
+
+ <h1>State of this API</h1>
+
+ <p>As of version 3.43, this software is in "tech preview" form. We
+ tentatively plan to stamp it as stable with the 3.44 release.</p>
+
+ <h1>Threading Considerations</h1>
+
+ <p>This API is, if built with SQLITE_THREADSAFE set to 1 or 2,
+ thread-safe, insofar as the C API guarantees, with some addenda:</p>
+
+ <ul>
+
+ <li>It is not legal to use Java-facing SQLite3 resource handles
+ (sqlite3, sqlite3_stmt, etc) from multiple threads concurrently,
+ nor to use any database-specific resources concurrently in a
+ thread separate from the one the database is currently in use
+ in. i.e. do not use a sqlite3_stmt in thread #2 when thread #1 is
+ using the database which prepared that handle.
+
+ <br>Violating this will eventually corrupt the JNI-level bindings
+ between Java's and C's view of the database. This is a limitation
+ of the JNI bindings, not the lower-level library.
+ </li>
+
+ <li>It is legal to use a given handle, and database-specific
+ resources, across threads, so long as no two threads pass
+ resources owned by the same database into the library
+ concurrently.
+ </li>
+
+ </ul>
+
+ <p>Any number of threads may, of course, create and use any number
+ of database handles they wish. Care only needs to be taken when
+ those handles or their associated resources cross threads, or...</p>
+
+ <p>When built with SQLITE_THREADSAFE=0 then no threading guarantees
+ are provided and multi-threaded use of the library will provoke
+ undefined behavior.</p>
+
+*/
+package org.sqlite.jni.capi;
diff --git a/ext/jni/src/org/sqlite/jni/capi/sqlite3.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3.java
new file mode 100644
index 0000000..cc6f2e6
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/sqlite3.java
@@ -0,0 +1,43 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A wrapper for communicating C-level (sqlite3*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java
+ and C via JNI.
+*/
+public final class sqlite3 extends NativePointerHolder<sqlite3>
+ implements AutoCloseable {
+
+ // Only invoked from JNI
+ private sqlite3(){}
+
+ public String toString(){
+ final long ptr = getNativePointer();
+ if( 0==ptr ){
+ return sqlite3.class.getSimpleName()+"@null";
+ }
+ final String fn = CApi.sqlite3_db_filename(this, "main");
+ return sqlite3.class.getSimpleName()
+ +"@"+String.format("0x%08x",ptr)
+ +"["+((null == fn) ? "<unnamed>" : fn)+"]"
+ ;
+ }
+
+ @Override public void close(){
+ CApi.sqlite3_close_v2(this);
+ }
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java
new file mode 100644
index 0000000..0ef75c1
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java
@@ -0,0 +1,31 @@
+/*
+** 2023-09-03
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A wrapper for passing C-level (sqlite3_backup*) instances around in
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+*/
+public final class sqlite3_backup extends NativePointerHolder<sqlite3_backup>
+ implements AutoCloseable {
+ // Only invoked from JNI.
+ private sqlite3_backup(){}
+
+ @Override public void close(){
+ CApi.sqlite3_backup_finish(this);
+ }
+
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java
new file mode 100644
index 0000000..bdc0200
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java
@@ -0,0 +1,30 @@
+/*
+** 2023-09-03
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A wrapper for passing C-level (sqlite3_blob*) instances around in
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+*/
+public final class sqlite3_blob extends NativePointerHolder<sqlite3_blob>
+ implements AutoCloseable {
+ // Only invoked from JNI.
+ private sqlite3_blob(){}
+
+ @Override public void close(){
+ CApi.sqlite3_blob_close(this);
+ }
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java
new file mode 100644
index 0000000..82ec49a
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java
@@ -0,0 +1,79 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ sqlite3_context instances are used in conjunction with user-defined
+ SQL functions (a.k.a. UDFs).
+*/
+public final class sqlite3_context extends NativePointerHolder<sqlite3_context> {
+ private Long aggregateContext = null;
+
+ /**
+ getAggregateContext() corresponds to C's
+ sqlite3_aggregate_context(), with a slightly different interface
+ to account for cross-language differences. It serves the same
+ purposes in a slightly different way: it provides a key which is
+ stable across invocations of a UDF's callbacks, such that all
+ calls into those callbacks can determine which "set" of those
+ calls they belong to.
+
+ <p>Note that use of this method is not a requirement for proper use
+ of this class. sqlite3_aggregate_context() can also be used.
+
+ <p>If the argument is true and the aggregate context has not yet
+ been set up, it will be initialized and fetched on demand, else it
+ won't. The intent is that xStep(), xValue(), and xInverse()
+ methods pass true and xFinal() methods pass false.
+
+ <p>This function treats numeric 0 as null, always returning null instead
+ of 0.
+
+ <p>If this object is being used in the context of an aggregate or
+ window UDF, this function returns a non-0 value which is distinct
+ for each set of UDF callbacks from a single invocation of the
+ UDF, otherwise it returns 0. The returned value is only only
+ valid within the context of execution of a single SQL statement,
+ and must not be re-used by future invocations of the UDF in
+ different SQL statements.
+
+ <p>Consider this SQL, where MYFUNC is a user-defined aggregate function:
+
+ <pre>{@code
+ SELECT MYFUNC(A), MYFUNC(B) FROM T;
+ }</pre>
+
+ <p>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.
+
+ <p>There is one case where this will return null in the context
+ of an aggregate or window function: if the result set has no
+ rows, the UDF's xFinal() will be called without any other x...()
+ members having been called. In that one case, no aggregate
+ context key will have been generated. xFinal() implementations
+ need to be prepared to accept that condition as legal.
+ */
+ public synchronized Long getAggregateContext(boolean initIfNeeded){
+ if( aggregateContext==null ){
+ aggregateContext = CApi.sqlite3_aggregate_context(this, initIfNeeded);
+ if( !initIfNeeded && null==aggregateContext ) aggregateContext = 0L;
+ }
+ return (null==aggregateContext || 0!=aggregateContext) ? aggregateContext : null;
+ }
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java
new file mode 100644
index 0000000..564891c
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java
@@ -0,0 +1,30 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A wrapper for communicating C-level (sqlite3_stmt*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+*/
+public final class sqlite3_stmt extends NativePointerHolder<sqlite3_stmt>
+ implements AutoCloseable {
+ // Only invoked from JNI.
+ private sqlite3_stmt(){}
+
+ @Override public void close(){
+ CApi.sqlite3_finalize(this);
+ }
+}
diff --git a/ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java
new file mode 100644
index 0000000..a4772f0
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/capi/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.capi;
+
+public final class sqlite3_value extends NativePointerHolder<sqlite3_value> {
+ //! Invoked only from JNI.
+ private sqlite3_value(){}
+}
diff --git a/ext/jni/src/org/sqlite/jni/fts5/Fts5.java b/ext/jni/src/org/sqlite/jni/fts5/Fts5.java
new file mode 100644
index 0000000..0dceeaf
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/fts5/Fts5.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.fts5;
+
+/**
+ INCOMPLETE AND COMPLETELY UNTESTED.
+
+ A utility object for holding FTS5-specific types and constants
+ which are used by multiple FTS5 classes.
+*/
+public final class Fts5 {
+ /* Not used */
+ private Fts5(){}
+
+
+ public static final int FTS5_TOKENIZE_QUERY = 0x0001;
+ public static final int FTS5_TOKENIZE_PREFIX = 0x0002;
+ public static final int FTS5_TOKENIZE_DOCUMENT = 0x0004;
+ public static final int FTS5_TOKENIZE_AUX = 0x0008;
+ public static final int FTS5_TOKEN_COLOCATED = 0x0001;
+}
diff --git a/ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java b/ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java
new file mode 100644
index 0000000..439b477
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/fts5/Fts5Context.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.fts5;
+import org.sqlite.jni.capi.*;
+
+/**
+ A wrapper for communicating C-level (Fts5Context*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+*/
+public final class Fts5Context extends NativePointerHolder<Fts5Context> {
+}
diff --git a/ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java
new file mode 100644
index 0000000..594f3ea
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java
@@ -0,0 +1,97 @@
+/*
+** 2023-08-04
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import java.nio.charset.StandardCharsets;
+import org.sqlite.jni.capi.*;
+import org.sqlite.jni.annotation.*;
+
+/**
+*/
+public final class Fts5ExtensionApi extends NativePointerHolder<Fts5ExtensionApi> {
+ //! Only called from JNI
+ private Fts5ExtensionApi(){}
+ private final int iVersion = 2;
+
+ /* Callback type for used by xQueryPhrase(). */
+ public static interface XQueryPhraseCallback {
+ int call(Fts5ExtensionApi fapi, Fts5Context cx);
+ }
+
+ /**
+ Returns the singleton instance of this class.
+ */
+ public static native Fts5ExtensionApi getInstance();
+
+ public native int xColumnCount(@NotNull Fts5Context fcx);
+
+ public native int xColumnSize(@NotNull Fts5Context cx, int iCol,
+ @NotNull OutputPointer.Int32 pnToken);
+
+ public native int xColumnText(@NotNull Fts5Context cx, int iCol,
+ @NotNull OutputPointer.String txt);
+
+ public native int xColumnTotalSize(@NotNull Fts5Context fcx, int iCol,
+ @NotNull OutputPointer.Int64 pnToken);
+
+ public native Object xGetAuxdata(@NotNull Fts5Context cx, boolean clearIt);
+
+ public native int xInst(@NotNull Fts5Context cx, int iIdx,
+ @NotNull OutputPointer.Int32 piPhrase,
+ @NotNull OutputPointer.Int32 piCol,
+ @NotNull OutputPointer.Int32 piOff);
+
+ public native int xInstCount(@NotNull Fts5Context fcx,
+ @NotNull OutputPointer.Int32 pnInst);
+
+ public native int xPhraseCount(@NotNull Fts5Context fcx);
+
+ public native int xPhraseFirst(@NotNull Fts5Context cx, int iPhrase,
+ @NotNull Fts5PhraseIter iter,
+ @NotNull OutputPointer.Int32 iCol,
+ @NotNull OutputPointer.Int32 iOff);
+
+ public native int xPhraseFirstColumn(@NotNull Fts5Context cx, int iPhrase,
+ @NotNull Fts5PhraseIter iter,
+ @NotNull OutputPointer.Int32 iCol);
+ public native void xPhraseNext(@NotNull Fts5Context cx,
+ @NotNull Fts5PhraseIter iter,
+ @NotNull OutputPointer.Int32 iCol,
+ @NotNull OutputPointer.Int32 iOff);
+ public native void xPhraseNextColumn(@NotNull Fts5Context cx,
+ @NotNull Fts5PhraseIter iter,
+ @NotNull OutputPointer.Int32 iCol);
+ public native int xPhraseSize(@NotNull Fts5Context fcx, int iPhrase);
+
+ public native int xQueryPhrase(@NotNull Fts5Context cx, int iPhrase,
+ @NotNull XQueryPhraseCallback callback);
+ public native int xRowCount(@NotNull Fts5Context fcx,
+ @NotNull OutputPointer.Int64 nRow);
+
+ public native long xRowid(@NotNull Fts5Context cx);
+ /* Note that the JNI binding lacks the C version's xDelete()
+ callback argument. Instead, if pAux has an xDestroy() method, it
+ is called if the FTS5 API finalizes the aux state (including if
+ allocation of storage for the auxdata fails). Any reference to
+ pAux held by the JNI layer will be relinquished regardless of
+ whether pAux has an xDestroy() method. */
+
+ public native int xSetAuxdata(@NotNull Fts5Context cx, @Nullable Object pAux);
+
+ public native int xTokenize(@NotNull Fts5Context cx, @NotNull byte[] pText,
+ @NotNull XTokenizeCallback callback);
+
+ public native Object xUserData(Fts5Context cx);
+ //^^^ returns the pointer passed as the 3rd arg to the C-level
+ // fts5_api::xCreateFunction().
+}
diff --git a/ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java b/ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java
new file mode 100644
index 0000000..5774eb5
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java
@@ -0,0 +1,25 @@
+/*
+** 2023-08-04
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.NativePointerHolder;
+
+/**
+ A wrapper for C-level Fts5PhraseIter. They are only modified and
+ inspected by native-level code.
+*/
+public final class Fts5PhraseIter extends NativePointerHolder<Fts5PhraseIter> {
+ //! Updated and used only by native code.
+ private long a;
+ private long b;
+}
diff --git a/ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java b/ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java
new file mode 100644
index 0000000..b72e5d0
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java
@@ -0,0 +1,31 @@
+/*
+** 2023-08-05x
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.NativePointerHolder;
+
+/**
+ INCOMPLETE AND COMPLETELY UNTESTED.
+
+ A wrapper for communicating C-level (Fts5Tokenizer*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+
+ At the C level, the Fts5Tokenizer type is essentially a void
+ pointer used specifically for tokenizers.
+*/
+public final class Fts5Tokenizer extends NativePointerHolder<Fts5Tokenizer> {
+ //! Only called from JNI.
+ private Fts5Tokenizer(){}
+}
diff --git a/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java b/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java
new file mode 100644
index 0000000..095e649
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java
@@ -0,0 +1,842 @@
+/*
+** 2023-08-04
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains a set of tests for the sqlite3 JNI bindings.
+*/
+package org.sqlite.jni.fts5;
+import static org.sqlite.jni.capi.CApi.*;
+import static org.sqlite.jni.capi.Tester1.*;
+import org.sqlite.jni.capi.*;
+import org.sqlite.jni.fts5.*;
+
+import java.util.*;
+
+public class TesterFts5 {
+
+ private static void test1(){
+ final Fts5ExtensionApi fea = Fts5ExtensionApi.getInstance();
+ affirm( null != fea );
+ affirm( fea.getNativePointer() != 0 );
+ affirm( fea == Fts5ExtensionApi.getInstance() )/*singleton*/;
+
+ sqlite3 db = createNewDb();
+ fts5_api fApi = fts5_api.getInstanceForDb(db);
+ affirm( fApi != null );
+ affirm( fApi == fts5_api.getInstanceForDb(db) /* singleton per db */ );
+
+ execSql(db, new String[] {
+ "CREATE VIRTUAL TABLE ft USING fts5(a, b);",
+ "INSERT INTO ft(rowid, a, b) VALUES(1, 'X Y', 'Y Z');",
+ "INSERT INTO ft(rowid, a, b) VALUES(2, 'A Z', 'Y Y');"
+ });
+
+ final String pUserData = "This is pUserData";
+ final int outputs[] = {0, 0};
+ final fts5_extension_function func = new fts5_extension_function(){
+ @Override public void call(Fts5ExtensionApi ext, Fts5Context fCx,
+ sqlite3_context pCx, sqlite3_value argv[]){
+ final int nCols = ext.xColumnCount(fCx);
+ affirm( 2 == nCols );
+ affirm( nCols == argv.length );
+ affirm( ext.xUserData(fCx) == pUserData );
+ final OutputPointer.String op = new OutputPointer.String();
+ final OutputPointer.Int32 colsz = new OutputPointer.Int32();
+ final OutputPointer.Int64 colTotalSz = new OutputPointer.Int64();
+ for(int i = 0; i < nCols; ++i ){
+ int rc = ext.xColumnText(fCx, i, op);
+ affirm( 0 == rc );
+ final String val = op.value;
+ affirm( val.equals(sqlite3_value_text16(argv[i])) );
+ rc = ext.xColumnSize(fCx, i, colsz);
+ affirm( 0==rc );
+ affirm( 3==sqlite3_value_bytes(argv[i]) );
+ rc = ext.xColumnTotalSize(fCx, i, colTotalSz);
+ affirm( 0==rc );
+ }
+ ++outputs[0];
+ }
+ public void xDestroy(){
+ outputs[1] = 1;
+ }
+ };
+
+ int rc = fApi.xCreateFunction("myaux", pUserData, func);
+ affirm( 0==rc );
+
+ affirm( 0==outputs[0] );
+ execSql(db, "select myaux(ft,a,b) from ft;");
+ affirm( 2==outputs[0] );
+ affirm( 0==outputs[1] );
+ sqlite3_close_v2(db);
+ affirm( 1==outputs[1] );
+ }
+
+ /*
+ ** Argument sql is a string containing one or more SQL statements
+ ** separated by ";" characters. This function executes each of these
+ ** statements against the database passed as the first argument. If
+ ** no error occurs, the results of the SQL script are returned as
+ ** an array of strings. If an error does occur, a RuntimeException is
+ ** thrown.
+ */
+ private static String[] sqlite3_exec(sqlite3 db, String sql) {
+ List<String> aOut = new ArrayList<String>();
+
+ /* Iterate through the list of SQL statements. For each, step through
+ ** it and add any results to the aOut[] array. */
+ int rc = sqlite3_prepare_multi(db, sql, new PrepareMultiCallback() {
+ @Override public int call(sqlite3_stmt pStmt){
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ int ii;
+ for(ii=0; ii<sqlite3_column_count(pStmt); ii++){
+ aOut.add( sqlite3_column_text16(pStmt, ii) );
+ }
+ }
+ return sqlite3_finalize(pStmt);
+ }
+ });
+ if( rc!=SQLITE_OK ){
+ throw new RuntimeException(sqlite3_errmsg(db));
+ }
+
+ /* Convert to array and return */
+ String[] arr = new String[aOut.size()];
+ return aOut.toArray(arr);
+ }
+
+ /*
+ ** Execute the SQL script passed as the second parameter via
+ ** sqlite3_exec(). Then affirm() that the results, when converted to
+ ** a string, match the value of the 3rd parameter. Example:
+ **
+ ** do_execsql_test(db, "SELECT 'abc'", "[abc]");
+ **
+ */
+ private static void do_execsql_test(sqlite3 db, String sql, String expect) {
+ String res = Arrays.toString( sqlite3_exec(db, sql) );
+ affirm( res.equals(expect),
+ "got {" + res + "} expected {" + expect + "}"
+ );
+ }
+ private static void do_execsql_test(sqlite3 db, String sql){
+ do_execsql_test(db, sql, "[]");
+ }
+
+ /*
+ ** Create the following custom SQL functions:
+ **
+ ** fts5_rowid()
+ ** fts5_columncount()
+ */
+ private static void create_test_functions(sqlite3 db){
+ /*
+ ** A user-defined-function fts5_rowid() that uses xRowid()
+ */
+ fts5_extension_function fts5_rowid = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ long rowid = ext.xRowid(fCx);
+ sqlite3_result_int64(pCx, rowid);
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_columncount() - xColumnCount()
+ */
+ fts5_extension_function fts5_columncount = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ int nCol = ext.xColumnCount(fCx);
+ sqlite3_result_int(pCx, nCol);
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_columnsize() - xColumnSize()
+ */
+ fts5_extension_function fts5_columnsize = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=1 ){
+ throw new RuntimeException("fts5_columncount: wrong number of args");
+ }
+ int iCol = sqlite3_value_int(argv[0]);
+
+ OutputPointer.Int32 piSz = new OutputPointer.Int32();
+ int rc = ext.xColumnSize(fCx, iCol, piSz);
+ if( rc!=SQLITE_OK ){
+ throw new RuntimeException( sqlite3_errstr(rc) );
+ }
+ sqlite3_result_int(pCx, piSz.get());
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_columntext() - xColumnText()
+ */
+ fts5_extension_function fts5_columntext = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=1 ){
+ throw new RuntimeException("fts5_columntext: wrong number of args");
+ }
+ int iCol = sqlite3_value_int(argv[0]);
+
+ OutputPointer.String pzText = new OutputPointer.String();
+ int rc = ext.xColumnText(fCx, iCol, pzText);
+ if( rc!=SQLITE_OK ){
+ throw new RuntimeException( sqlite3_errstr(rc) );
+ }
+ sqlite3_result_text16(pCx, pzText.get());
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_columntotalsize() - xColumnTotalSize()
+ */
+ fts5_extension_function fts5_columntsize = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=1 ){
+ throw new RuntimeException(
+ "fts5_columntotalsize: wrong number of args"
+ );
+ }
+ int iCol = sqlite3_value_int(argv[0]);
+
+ OutputPointer.Int64 piSz = new OutputPointer.Int64();
+ int rc = ext.xColumnTotalSize(fCx, iCol, piSz);
+ if( rc!=SQLITE_OK ){
+ throw new RuntimeException( sqlite3_errstr(rc) );
+ }
+ sqlite3_result_int64(pCx, piSz.get());
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_aux(<fts>, <value>);
+ */
+ class fts5_aux implements fts5_extension_function {
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length>1 ){
+ throw new RuntimeException("fts5_aux: wrong number of args");
+ }
+
+ boolean bClear = (argv.length==1);
+ Object obj = ext.xGetAuxdata(fCx, bClear);
+ if( obj instanceof String ){
+ sqlite3_result_text16(pCx, (String)obj);
+ }
+
+ if( argv.length==1 ){
+ String val = sqlite3_value_text16(argv[0]);
+ if( !val.equals("") ){
+ ext.xSetAuxdata(fCx, val);
+ }
+ }
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_inst(<fts>);
+ **
+ ** This is used to test the xInstCount() and xInst() APIs. It returns a
+ ** text value containing a Tcl list with xInstCount() elements. Each
+ ** element is itself a list of 3 integers - the phrase number, column
+ ** number and token offset returned by each call to xInst().
+ */
+ fts5_extension_function fts5_inst = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=0 ){
+ throw new RuntimeException("fts5_inst: wrong number of args");
+ }
+
+ OutputPointer.Int32 pnInst = new OutputPointer.Int32();
+ OutputPointer.Int32 piPhrase = new OutputPointer.Int32();
+ OutputPointer.Int32 piCol = new OutputPointer.Int32();
+ OutputPointer.Int32 piOff = new OutputPointer.Int32();
+ String ret = new String();
+
+ int rc = ext.xInstCount(fCx, pnInst);
+ int nInst = pnInst.get();
+ int ii;
+
+ for(ii=0; rc==SQLITE_OK && ii<nInst; ii++){
+ ext.xInst(fCx, ii, piPhrase, piCol, piOff);
+ if( ii>0 ) ret += " ";
+ ret += "{"+piPhrase.get()+" "+piCol.get()+" "+piOff.get()+"}";
+ }
+
+ sqlite3_result_text(pCx, ret);
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_pinst(<fts>);
+ **
+ ** Like SQL function fts5_inst(), except using the following
+ **
+ ** xPhraseCount
+ ** xPhraseFirst
+ ** xPhraseNext
+ */
+ fts5_extension_function fts5_pinst = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=0 ){
+ throw new RuntimeException("fts5_pinst: wrong number of args");
+ }
+
+ OutputPointer.Int32 piCol = new OutputPointer.Int32();
+ OutputPointer.Int32 piOff = new OutputPointer.Int32();
+ String ret = new String();
+ int rc = SQLITE_OK;
+
+ int nPhrase = ext.xPhraseCount(fCx);
+ int ii;
+
+ for(ii=0; rc==SQLITE_OK && ii<nPhrase; ii++){
+ Fts5PhraseIter pIter = new Fts5PhraseIter();
+ for(rc = ext.xPhraseFirst(fCx, ii, pIter, piCol, piOff);
+ rc==SQLITE_OK && piCol.get()>=0;
+ ext.xPhraseNext(fCx, pIter, piCol, piOff)
+ ){
+ if( !ret.equals("") ) ret += " ";
+ ret += "{"+ii+" "+piCol.get()+" "+piOff.get()+"}";
+ }
+ }
+
+ if( rc!=SQLITE_OK ){
+ throw new RuntimeException("fts5_pinst: rc=" + rc);
+ }else{
+ sqlite3_result_text(pCx, ret);
+ }
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_pcolinst(<fts>);
+ **
+ ** Like SQL function fts5_pinst(), except using the following
+ **
+ ** xPhraseFirstColumn
+ ** xPhraseNextColumn
+ */
+ fts5_extension_function fts5_pcolinst = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=0 ){
+ throw new RuntimeException("fts5_pcolinst: wrong number of args");
+ }
+
+ OutputPointer.Int32 piCol = new OutputPointer.Int32();
+ String ret = new String();
+ int rc = SQLITE_OK;
+
+ int nPhrase = ext.xPhraseCount(fCx);
+ int ii;
+
+ for(ii=0; rc==SQLITE_OK && ii<nPhrase; ii++){
+ Fts5PhraseIter pIter = new Fts5PhraseIter();
+ for(rc = ext.xPhraseFirstColumn(fCx, ii, pIter, piCol);
+ rc==SQLITE_OK && piCol.get()>=0;
+ ext.xPhraseNextColumn(fCx, pIter, piCol)
+ ){
+ if( !ret.equals("") ) ret += " ";
+ ret += "{"+ii+" "+piCol.get()+"}";
+ }
+ }
+
+ if( rc!=SQLITE_OK ){
+ throw new RuntimeException("fts5_pcolinst: rc=" + rc);
+ }else{
+ sqlite3_result_text(pCx, ret);
+ }
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_rowcount(<fts>);
+ */
+ fts5_extension_function fts5_rowcount = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=0 ){
+ throw new RuntimeException("fts5_rowcount: wrong number of args");
+ }
+ OutputPointer.Int64 pnRow = new OutputPointer.Int64();
+
+ int rc = ext.xRowCount(fCx, pnRow);
+ if( rc==SQLITE_OK ){
+ sqlite3_result_int64(pCx, pnRow.get());
+ }else{
+ throw new RuntimeException("fts5_rowcount: rc=" + rc);
+ }
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_phrasesize(<fts>);
+ */
+ fts5_extension_function fts5_phrasesize = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=1 ){
+ throw new RuntimeException("fts5_phrasesize: wrong number of args");
+ }
+ int iPhrase = sqlite3_value_int(argv[0]);
+
+ int sz = ext.xPhraseSize(fCx, iPhrase);
+ sqlite3_result_int(pCx, sz);
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_phrasehits(<fts>, <phrase-number>);
+ **
+ ** Use the xQueryPhrase() API to determine how many hits, in total,
+ ** there are for phrase <phrase-number> in the database.
+ */
+ fts5_extension_function fts5_phrasehits = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=1 ){
+ throw new RuntimeException("fts5_phrasesize: wrong number of args");
+ }
+ int iPhrase = sqlite3_value_int(argv[0]);
+ int rc = SQLITE_OK;
+
+ class MyCallback implements Fts5ExtensionApi.XQueryPhraseCallback {
+ public int nRet = 0;
+ public int getRet() { return nRet; }
+
+ @Override
+ public int call(Fts5ExtensionApi fapi, Fts5Context cx){
+ OutputPointer.Int32 pnInst = new OutputPointer.Int32();
+ int rc = fapi.xInstCount(cx, pnInst);
+ nRet += pnInst.get();
+ return rc;
+ }
+ };
+
+ MyCallback xCall = new MyCallback();
+ rc = ext.xQueryPhrase(fCx, iPhrase, xCall);
+ if( rc!=SQLITE_OK ){
+ throw new RuntimeException("fts5_phrasehits: rc=" + rc);
+ }
+ sqlite3_result_int(pCx, xCall.getRet());
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_tokenize(<fts>, <text>)
+ */
+ fts5_extension_function fts5_tokenize = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=1 ){
+ throw new RuntimeException("fts5_tokenize: wrong number of args");
+ }
+ byte[] utf8 = sqlite3_value_text(argv[0]);
+ int rc = SQLITE_OK;
+
+ class MyCallback implements XTokenizeCallback {
+ private List<String> myList = new ArrayList<String>();
+
+ public String getval() {
+ return String.join("+", myList);
+ }
+
+ @Override
+ public int call(int tFlags, byte[] txt, int iStart, int iEnd){
+ try {
+ String str = new String(txt, "UTF-8");
+ myList.add(str);
+ } catch (Exception e) {
+ }
+ return SQLITE_OK;
+ }
+ };
+
+ MyCallback xCall = new MyCallback();
+ ext.xTokenize(fCx, utf8, xCall);
+ sqlite3_result_text16(pCx, xCall.getval());
+
+ if( rc!=SQLITE_OK ){
+ throw new RuntimeException("fts5_tokenize: rc=" + rc);
+ }
+ }
+ public void xDestroy(){ }
+ };
+
+ fts5_api api = fts5_api.getInstanceForDb(db);
+ api.xCreateFunction("fts5_rowid", fts5_rowid);
+ api.xCreateFunction("fts5_columncount", fts5_columncount);
+ api.xCreateFunction("fts5_columnsize", fts5_columnsize);
+ api.xCreateFunction("fts5_columntext", fts5_columntext);
+ api.xCreateFunction("fts5_columntotalsize", fts5_columntsize);
+
+ api.xCreateFunction("fts5_aux1", new fts5_aux());
+ api.xCreateFunction("fts5_aux2", new fts5_aux());
+
+ api.xCreateFunction("fts5_inst", fts5_inst);
+ api.xCreateFunction("fts5_pinst", fts5_pinst);
+ api.xCreateFunction("fts5_pcolinst", fts5_pcolinst);
+ api.xCreateFunction("fts5_rowcount", fts5_rowcount);
+ api.xCreateFunction("fts5_phrasesize", fts5_phrasesize);
+ api.xCreateFunction("fts5_phrasehits", fts5_phrasehits);
+ api.xCreateFunction("fts5_tokenize", fts5_tokenize);
+ }
+ /*
+ ** Test of various Fts5ExtensionApi methods
+ */
+ private static void test2(){
+
+ /* Open db and populate an fts5 table */
+ sqlite3 db = createNewDb();
+ do_execsql_test(db,
+ "CREATE VIRTUAL TABLE ft USING fts5(a, b);" +
+ "INSERT INTO ft(rowid, a, b) VALUES(-9223372036854775808, 'x', 'x');" +
+ "INSERT INTO ft(rowid, a, b) VALUES(0, 'x', 'x');" +
+ "INSERT INTO ft(rowid, a, b) VALUES(1, 'x y z', 'x y z');" +
+ "INSERT INTO ft(rowid, a, b) VALUES(2, 'x y z', 'x z');" +
+ "INSERT INTO ft(rowid, a, b) VALUES(3, 'x y z', 'x y z');" +
+ "INSERT INTO ft(rowid, a, b) VALUES(9223372036854775807, 'x', 'x');"
+ );
+
+ create_test_functions(db);
+
+ /* Test that fts5_rowid() seems to work */
+ do_execsql_test(db,
+ "SELECT rowid==fts5_rowid(ft) FROM ft('x')",
+ "[1, 1, 1, 1, 1, 1]"
+ );
+
+ /* Test fts5_columncount() */
+ do_execsql_test(db,
+ "SELECT fts5_columncount(ft) FROM ft('x')",
+ "[2, 2, 2, 2, 2, 2]"
+ );
+
+ /* Test fts5_columnsize() */
+ do_execsql_test(db,
+ "SELECT fts5_columnsize(ft, 0) FROM ft('x') ORDER BY rowid",
+ "[1, 1, 3, 3, 3, 1]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_columnsize(ft, 1) FROM ft('x') ORDER BY rowid",
+ "[1, 1, 3, 2, 3, 1]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_columnsize(ft, -1) FROM ft('x') ORDER BY rowid",
+ "[2, 2, 6, 5, 6, 2]"
+ );
+
+ /* Test that xColumnSize() returns SQLITE_RANGE if the column number
+ ** is out-of range */
+ try {
+ do_execsql_test(db,
+ "SELECT fts5_columnsize(ft, 2) FROM ft('x') ORDER BY rowid"
+ );
+ } catch( RuntimeException e ){
+ affirm( e.getMessage().matches(".*column index out of range") );
+ }
+
+ /* Test fts5_columntext() */
+ do_execsql_test(db,
+ "SELECT fts5_columntext(ft, 0) FROM ft('x') ORDER BY rowid",
+ "[x, x, x y z, x y z, x y z, x]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_columntext(ft, 1) FROM ft('x') ORDER BY rowid",
+ "[x, x, x y z, x z, x y z, x]"
+ );
+ boolean threw = false;
+ try{
+ /* columntext() used to return NULLs when given an out-of bounds column
+ but now results in a range error. */
+ do_execsql_test(db,
+ "SELECT fts5_columntext(ft, 2) FROM ft('x') ORDER BY rowid",
+ "[null, null, null, null, null, null]"
+ );
+ }catch(Exception e){
+ threw = true;
+ affirm( e.getMessage().matches(".*column index out of range") );
+ }
+ affirm( threw );
+ threw = false;
+
+ /* Test fts5_columntotalsize() */
+ do_execsql_test(db,
+ "SELECT fts5_columntotalsize(ft, 0) FROM ft('x') ORDER BY rowid",
+ "[12, 12, 12, 12, 12, 12]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_columntotalsize(ft, 1) FROM ft('x') ORDER BY rowid",
+ "[11, 11, 11, 11, 11, 11]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_columntotalsize(ft, -1) FROM ft('x') ORDER BY rowid",
+ "[23, 23, 23, 23, 23, 23]"
+ );
+
+ /* Test that xColumnTotalSize() returns SQLITE_RANGE if the column
+ ** number is out-of range */
+ try {
+ do_execsql_test(db,
+ "SELECT fts5_columntotalsize(ft, 2) FROM ft('x') ORDER BY rowid"
+ );
+ } catch( RuntimeException e ){
+ affirm( e.getMessage().matches(".*column index out of range") );
+ }
+
+ do_execsql_test(db,
+ "SELECT rowid, fts5_rowcount(ft) FROM ft('z')",
+ "[1, 6, 2, 6, 3, 6]"
+ );
+
+ sqlite3_close_v2(db);
+ }
+
+ /*
+ ** Test of various Fts5ExtensionApi methods
+ */
+ private static void test3(){
+
+ /* Open db and populate an fts5 table */
+ sqlite3 db = createNewDb();
+ do_execsql_test(db,
+ "CREATE VIRTUAL TABLE ft USING fts5(a, b);" +
+ "INSERT INTO ft(a, b) VALUES('the one', 1);" +
+ "INSERT INTO ft(a, b) VALUES('the two', 2);" +
+ "INSERT INTO ft(a, b) VALUES('the three', 3);" +
+ "INSERT INTO ft(a, b) VALUES('the four', '');"
+ );
+ create_test_functions(db);
+
+ /* Test fts5_aux1() + fts5_aux2() - users of xGetAuxdata and xSetAuxdata */
+ do_execsql_test(db,
+ "SELECT fts5_aux1(ft, a) FROM ft('the')",
+ "[null, the one, the two, the three]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_aux2(ft, b) FROM ft('the')",
+ "[null, 1, 2, 3]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_aux1(ft, a), fts5_aux2(ft, b) FROM ft('the')",
+ "[null, null, the one, 1, the two, 2, the three, 3]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_aux1(ft, b), fts5_aux1(ft) FROM ft('the')",
+ "[null, 1, 1, 2, 2, 3, 3, null]"
+ );
+ }
+
+ /*
+ ** Test of various Fts5ExtensionApi methods
+ */
+ private static void test4(){
+
+ /* Open db and populate an fts5 table */
+ sqlite3 db = createNewDb();
+ create_test_functions(db);
+ do_execsql_test(db,
+ "CREATE VIRTUAL TABLE ft USING fts5(a, b);" +
+ "INSERT INTO ft(a, b) VALUES('one two three', 'two three four');" +
+ "INSERT INTO ft(a, b) VALUES('two three four', 'three four five');" +
+ "INSERT INTO ft(a, b) VALUES('three four five', 'four five six');"
+ );
+
+
+ do_execsql_test(db,
+ "SELECT fts5_inst(ft) FROM ft('two')",
+ "[{0 0 1} {0 1 0}, {0 0 0}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_inst(ft) FROM ft('four')",
+ "[{0 1 2}, {0 0 2} {0 1 1}, {0 0 1} {0 1 0}]"
+ );
+
+ do_execsql_test(db,
+ "SELECT fts5_inst(ft) FROM ft('a OR b OR four')",
+ "[{2 1 2}, {2 0 2} {2 1 1}, {2 0 1} {2 1 0}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_inst(ft) FROM ft('two four')",
+ "[{0 0 1} {0 1 0} {1 1 2}, {0 0 0} {1 0 2} {1 1 1}]"
+ );
+
+ do_execsql_test(db,
+ "SELECT fts5_pinst(ft) FROM ft('two')",
+ "[{0 0 1} {0 1 0}, {0 0 0}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_pinst(ft) FROM ft('four')",
+ "[{0 1 2}, {0 0 2} {0 1 1}, {0 0 1} {0 1 0}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_pinst(ft) FROM ft('a OR b OR four')",
+ "[{2 1 2}, {2 0 2} {2 1 1}, {2 0 1} {2 1 0}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_pinst(ft) FROM ft('two four')",
+ "[{0 0 1} {0 1 0} {1 1 2}, {0 0 0} {1 0 2} {1 1 1}]"
+ );
+
+ do_execsql_test(db,
+ "SELECT fts5_pcolinst(ft) FROM ft('two')",
+ "[{0 0} {0 1}, {0 0}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_pcolinst(ft) FROM ft('four')",
+ "[{0 1}, {0 0} {0 1}, {0 0} {0 1}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_pcolinst(ft) FROM ft('a OR b OR four')",
+ "[{2 1}, {2 0} {2 1}, {2 0} {2 1}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_pcolinst(ft) FROM ft('two four')",
+ "[{0 0} {0 1} {1 1}, {0 0} {1 0} {1 1}]"
+ );
+
+ do_execsql_test(db,
+ "SELECT fts5_phrasesize(ft, 0) FROM ft('four five six') LIMIT 1;",
+ "[1]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_phrasesize(ft, 0) FROM ft('four + five + six') LIMIT 1;",
+ "[3]"
+ );
+
+
+ sqlite3_close_v2(db);
+ }
+
+ private static void test5(){
+ /* Open db and populate an fts5 table */
+ sqlite3 db = createNewDb();
+ create_test_functions(db);
+ do_execsql_test(db,
+ "CREATE VIRTUAL TABLE ft USING fts5(x, b);" +
+ "INSERT INTO ft(x) VALUES('one two three four five six seven eight');" +
+ "INSERT INTO ft(x) VALUES('one two one four one six one eight');" +
+ "INSERT INTO ft(x) VALUES('one two three four five six seven eight');"
+ );
+
+ do_execsql_test(db,
+ "SELECT fts5_phrasehits(ft, 0) FROM ft('one') LIMIT 1",
+ "[6]"
+ );
+
+ sqlite3_close_v2(db);
+ }
+
+ private static void test6(){
+ sqlite3 db = createNewDb();
+ create_test_functions(db);
+ do_execsql_test(db,
+ "CREATE VIRTUAL TABLE ft USING fts5(x, b);" +
+ "INSERT INTO ft(x) VALUES('one two three four five six seven eight');"
+ );
+
+ do_execsql_test(db,
+ "SELECT fts5_tokenize(ft, 'abc def ghi') FROM ft('one')",
+ "[abc+def+ghi]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_tokenize(ft, 'it''s BEEN a...') FROM ft('one')",
+ "[it+s+been+a]"
+ );
+
+ sqlite3_close_v2(db);
+ }
+
+ private static synchronized void runTests(){
+ test1();
+ test2();
+ test3();
+ test4();
+ test5();
+ test6();
+ }
+
+ public TesterFts5(){
+ runTests();
+ }
+}
diff --git a/ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java b/ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java
new file mode 100644
index 0000000..3aa514f
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java
@@ -0,0 +1,22 @@
+/*
+** 2023-08-04
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+
+
+/**
+ Callback type for use with xTokenize() variants.
+*/
+public interface XTokenizeCallback {
+ int call(int tFlags, byte[] txt, int iStart, int iEnd);
+}
diff --git a/ext/jni/src/org/sqlite/jni/fts5/fts5_api.java b/ext/jni/src/org/sqlite/jni/fts5/fts5_api.java
new file mode 100644
index 0000000..d7d2da4
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/fts5/fts5_api.java
@@ -0,0 +1,76 @@
+/*
+** 2023-08-05
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.annotation.*;
+import org.sqlite.jni.capi.*;
+
+/**
+ A wrapper for communicating C-level (fts5_api*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+*/
+public final class fts5_api extends NativePointerHolder<fts5_api> {
+ /* Only invoked from JNI */
+ private fts5_api(){}
+
+ public static final int iVersion = 2;
+
+ /**
+ Returns the fts5_api instance associated with the given db, or
+ null if something goes horribly wrong.
+ */
+ public static synchronized native fts5_api getInstanceForDb(@NotNull sqlite3 db);
+
+ public synchronized native int xCreateFunction(@NotNull String name,
+ @Nullable Object userData,
+ @NotNull fts5_extension_function xFunction);
+
+ /**
+ Convenience overload which passes null as the 2nd argument to the
+ 3-parameter form.
+ */
+ public int xCreateFunction(@NotNull String name,
+ @NotNull fts5_extension_function xFunction){
+ return xCreateFunction(name, null, xFunction);
+ }
+
+ // /* Create a new auxiliary function */
+ // int (*xCreateFunction)(
+ // fts5_api *pApi,
+ // const char *zName,
+ // void *pContext,
+ // fts5_extension_function xFunction,
+ // void (*xDestroy)(void*)
+ // );
+
+ // Still potentially todo:
+
+ // int (*xCreateTokenizer)(
+ // fts5_api *pApi,
+ // const char *zName,
+ // void *pContext,
+ // fts5_tokenizer *pTokenizer,
+ // void (*xDestroy)(void*)
+ // );
+
+ // /* Find an existing tokenizer */
+ // int (*xFindTokenizer)(
+ // fts5_api *pApi,
+ // const char *zName,
+ // void **ppContext,
+ // fts5_tokenizer *pTokenizer
+ // );
+
+}
diff --git a/ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java b/ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java
new file mode 100644
index 0000000..5e47633
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java
@@ -0,0 +1,47 @@
+/*
+** 2023-08-05
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.sqlite3_context;
+import org.sqlite.jni.capi.sqlite3_value;
+
+/**
+ JNI-level wrapper for C's fts5_extension_function type.
+*/
+public interface fts5_extension_function {
+ // typedef void (*fts5_extension_function)(
+ // const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
+ // Fts5Context *pFts, /* First arg to pass to pApi functions */
+ // sqlite3_context *pCtx, /* Context for returning result/error */
+ // int nVal, /* Number of values in apVal[] array */
+ // sqlite3_value **apVal /* Array of trailing arguments */
+ // );
+
+ /**
+ The callback implementation, corresponding to the xFunction
+ argument of C's fts5_api::xCreateFunction().
+ */
+ void call(Fts5ExtensionApi ext, Fts5Context fCx,
+ sqlite3_context pCx, sqlite3_value argv[]);
+ /**
+ Is called when this function is destroyed by sqlite3. Typically
+ this function will be empty.
+ */
+ void xDestroy();
+
+ public static abstract class Abstract implements fts5_extension_function {
+ @Override public abstract void call(Fts5ExtensionApi ext, Fts5Context fCx,
+ sqlite3_context pCx, sqlite3_value argv[]);
+ @Override public void xDestroy(){}
+ }
+}
diff --git a/ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java b/ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java
new file mode 100644
index 0000000..f4ada4d
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/fts5/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.fts5;
+import org.sqlite.jni.capi.NativePointerHolder;
+import org.sqlite.jni.annotation.NotNull;
+
+/**
+ A wrapper for communicating C-level (fts5_tokenizer*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+*/
+public final class fts5_tokenizer extends NativePointerHolder<fts5_tokenizer> {
+ /* Only invoked by JNI */
+ private fts5_tokenizer(){}
+
+ // int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut);
+ // void (*xDelete)(Fts5Tokenizer*);
+
+ public native int xTokenize(@NotNull Fts5Tokenizer t, int tokFlags,
+ @NotNull byte pText[],
+ @NotNull XTokenizeCallback callback);
+
+
+ // int (*xTokenize)(Fts5Tokenizer*,
+ // void *pCtx,
+ // int flags, /* Mask of FTS5_TOKENIZE_* flags */
+ // const char *pText, int nText,
+ // int (*xToken)(
+ // void *pCtx, /* Copy of 2nd argument to xTokenize() */
+ // int tflags, /* Mask of FTS5_TOKEN_* flags */
+ // const char *pToken, /* Pointer to buffer containing token */
+ // int nToken, /* Size of token in bytes */
+ // int iStart, /* Byte offset of token within input text */
+ // int iEnd /* Byte offset of end of token within input text */
+ // )
+ // );
+}
diff --git a/ext/jni/src/org/sqlite/jni/test-script-interpreter.md b/ext/jni/src/org/sqlite/jni/test-script-interpreter.md
new file mode 100644
index 0000000..a51d64d
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/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/org/sqlite/jni/wrapper1/AggregateFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java
new file mode 100644
index 0000000..fc63b53
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java
@@ -0,0 +1,144 @@
+/*
+** 2023-10-16
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the wrapper1 interface for sqlite3.
+*/
+package org.sqlite.jni.wrapper1;
+
+/**
+ EXPERIMENTAL/INCOMPLETE/UNTESTED
+
+ A SqlFunction implementation for aggregate functions. The T type
+ represents the type of data accumulated by this aggregate while it
+ works. e.g. a SUM()-like UDF might use Integer or Long and a
+ CONCAT()-like UDF might use a StringBuilder or a List<String>.
+*/
+public abstract class AggregateFunction<T> implements SqlFunction {
+
+ /**
+ As for the xStep() argument of the C API's
+ sqlite3_create_function(). If this function throws, the
+ exception is reported via sqlite3_result_error().
+ */
+ public abstract void xStep(SqlFunction.Arguments args);
+
+ /**
+ As for the xFinal() argument of the C API's
+ sqlite3_create_function(). If this function throws, it is
+ translated into sqlite3_result_error().
+
+ Note that the passed-in object will not actually contain any
+ arguments for xFinal() but will contain the context object needed
+ for setting the call's result or error state.
+ */
+ public abstract void xFinal(SqlFunction.Arguments args);
+
+ /**
+ Optionally override to be notified when the UDF is finalized by
+ SQLite.
+ */
+ public void xDestroy() {}
+
+ /**
+ PerContextState assists aggregate and window functions in
+ managing their accumulator state across calls to the UDF's
+ callbacks.
+
+ <p>T must be of a type which can be legally stored as a value in
+ java.util.HashMap<KeyType,T>.
+
+ <p>If a given aggregate or window function is called multiple times
+ in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)...,
+ then the clients need some way of knowing which call is which so
+ that they can map their state between their various UDF callbacks
+ and reset it via xFinal(). This class takes care of such
+ mappings.
+
+ <p>This class works by mapping
+ sqlite3_context.getAggregateContext() to a single piece of
+ state, of a client-defined type (the T part of this class), which
+ persists across a "matching set" of the UDF's callbacks.
+
+ <p>This class is a helper providing commonly-needed functionality
+ - it is not required for use with aggregate or window functions.
+ Client UDFs are free to perform such mappings using custom
+ approaches. The provided {@link AggregateFunction} and {@link
+ WindowFunction} classes use this.
+ */
+ public static final class PerContextState<T> {
+ private final java.util.Map<Long,ValueHolder<T>> map
+ = new java.util.HashMap<>();
+
+ /**
+ Should be called from a UDF's xStep(), xValue(), and xInverse()
+ methods, passing it that method's first argument and an initial
+ value for the persistent state. If there is currently no
+ mapping for the given context within the map, one is created
+ using the given initial value, else the existing one is used
+ and the 2nd argument is ignored. It returns a ValueHolder<T>
+ which can be used to modify that state directly without
+ requiring that the client update the underlying map's entry.
+
+ <p>The caller is obligated to eventually call
+ takeAggregateState() to clear the mapping.
+ */
+ public ValueHolder<T> getAggregateState(SqlFunction.Arguments args, T initialValue){
+ final Long key = args.getContext().getAggregateContext(true);
+ ValueHolder<T> rc = null==key ? null : map.get(key);
+ if( null==rc ){
+ map.put(key, rc = new ValueHolder<>(initialValue));
+ }
+ return rc;
+ }
+
+ /**
+ Should be called from a UDF's xFinal() method and passed that
+ method's first argument. This function removes the value
+ associated with with the arguments' aggregate context from the
+ map and returns it, returning null if no other UDF method has
+ been called to set up such a mapping. The latter condition will
+ be the case if a UDF is used in a statement which has no result
+ rows.
+ */
+ public T takeAggregateState(SqlFunction.Arguments args){
+ final ValueHolder<T> h = map.remove(args.getContext().getAggregateContext(false));
+ return null==h ? null : h.value;
+ }
+ }
+
+ /** Per-invocation state for the UDF. */
+ private final PerContextState<T> map = new PerContextState<>();
+
+ /**
+ To be called from the implementation's xStep() method, as well
+ as the xValue() and xInverse() methods of the {@link WindowFunction}
+ subclass, to fetch the current per-call UDF state. On the
+ first call to this method for any given sqlite3_context
+ argument, the context is set to the given initial value. On all other
+ calls, the 2nd argument is ignored.
+
+ @see SQLFunction.PerContextState#getAggregateState
+ */
+ protected final ValueHolder<T> getAggregateState(SqlFunction.Arguments args, T initialValue){
+ return map.getAggregateState(args, initialValue);
+ }
+
+ /**
+ To be called from the implementation's xFinal() method to fetch
+ the final state of the UDF and remove its mapping.
+
+ see SQLFunction.PerContextState#takeAggregateState
+ */
+ protected final T takeAggregateState(SqlFunction.Arguments args){
+ return map.takeAggregateState(args);
+ }
+
+}
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java
new file mode 100644
index 0000000..067a698
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java
@@ -0,0 +1,37 @@
+/*
+** 2023-10-16
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the wrapper1 interface for sqlite3.
+*/
+package org.sqlite.jni.wrapper1;
+import org.sqlite.jni.capi.CApi;
+import org.sqlite.jni.annotation.*;
+import org.sqlite.jni.capi.sqlite3_context;
+import org.sqlite.jni.capi.sqlite3_value;
+
+/**
+ The SqlFunction type for scalar SQL functions.
+*/
+public abstract class ScalarFunction implements SqlFunction {
+ /**
+ As for the xFunc() argument of the C API's
+ sqlite3_create_function(). If this function throws, it is
+ translated into an sqlite3_result_error().
+ */
+ public abstract void xFunc(SqlFunction.Arguments args);
+
+ /**
+ Optionally override to be notified when the UDF is finalized by
+ SQLite. This default implementation does nothing.
+ */
+ public void xDestroy() {}
+
+}
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java
new file mode 100644
index 0000000..dcfc2eb
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java
@@ -0,0 +1,318 @@
+/*
+** 2023-10-16
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the wrapper1 interface for sqlite3.
+*/
+package org.sqlite.jni.wrapper1;
+import org.sqlite.jni.capi.CApi;
+import org.sqlite.jni.capi.sqlite3_context;
+import org.sqlite.jni.capi.sqlite3_value;
+
+/**
+ Base marker interface for SQLite's three types of User-Defined SQL
+ Functions (UDFs): Scalar, Aggregate, and Window functions.
+*/
+public interface SqlFunction {
+
+ public static final int DETERMINISTIC = CApi.SQLITE_DETERMINISTIC;
+ public static final int INNOCUOUS = CApi.SQLITE_INNOCUOUS;
+ public static final int DIRECTONLY = CApi.SQLITE_DIRECTONLY;
+ public static final int SUBTYPE = CApi.SQLITE_SUBTYPE;
+ public static final int RESULT_SUBTYPE = CApi.SQLITE_RESULT_SUBTYPE;
+ public static final int UTF8 = CApi.SQLITE_UTF8;
+ public static final int UTF16 = CApi.SQLITE_UTF16;
+
+ /**
+ The Arguments type is an abstraction on top of the lower-level
+ UDF function argument types. It provides _most_ of the functionality
+ of the lower-level interface, insofar as possible without "leaking"
+ those types into this API.
+ */
+ public final static class Arguments implements Iterable<SqlFunction.Arguments.Arg>{
+ private final sqlite3_context cx;
+ private final sqlite3_value args[];
+ public final int length;
+
+ /**
+ Must be passed the context and arguments for the UDF call this
+ object is wrapping. Intended to be used by internal proxy
+ classes which "convert" the lower-level interface into this
+ package's higher-level interface, e.g. ScalarAdapter and
+ AggregateAdapter.
+
+ Passing null for the args is equivalent to passing a length-0
+ array.
+ */
+ Arguments(sqlite3_context cx, sqlite3_value args[]){
+ this.cx = cx;
+ this.args = args==null ? new sqlite3_value[0] : args;
+ this.length = this.args.length;
+ }
+
+ /**
+ Returns the sqlite3_value at the given argument index or throws
+ an IllegalArgumentException exception if ndx is out of range.
+ */
+ private sqlite3_value valueAt(int ndx){
+ if(ndx<0 || ndx>=args.length){
+ throw new IllegalArgumentException(
+ "SQL function argument index "+ndx+" is out of range."
+ );
+ }
+ return args[ndx];
+ }
+
+ //! Returns the underlying sqlite3_context for these arguments.
+ sqlite3_context getContext(){return cx;}
+
+ /**
+ Returns the Sqlite (db) object associated with this UDF call,
+ or null if the UDF is somehow called without such an object or
+ the db has been closed in an untimely manner (e.g. closed by a
+ UDF call).
+ */
+ public Sqlite getDb(){
+ return Sqlite.fromNative( CApi.sqlite3_context_db_handle(cx) );
+ }
+
+ public int getArgCount(){ return args.length; }
+
+ public int getInt(int argNdx){return CApi.sqlite3_value_int(valueAt(argNdx));}
+ public long getInt64(int argNdx){return CApi.sqlite3_value_int64(valueAt(argNdx));}
+ public double getDouble(int argNdx){return CApi.sqlite3_value_double(valueAt(argNdx));}
+ public byte[] getBlob(int argNdx){return CApi.sqlite3_value_blob(valueAt(argNdx));}
+ public byte[] getText(int argNdx){return CApi.sqlite3_value_text(valueAt(argNdx));}
+ public String getText16(int argNdx){return CApi.sqlite3_value_text16(valueAt(argNdx));}
+ public int getBytes(int argNdx){return CApi.sqlite3_value_bytes(valueAt(argNdx));}
+ public int getBytes16(int argNdx){return CApi.sqlite3_value_bytes16(valueAt(argNdx));}
+ public Object getObject(int argNdx){return CApi.sqlite3_value_java_object(valueAt(argNdx));}
+ public <T> T getObject(int argNdx, Class<T> type){
+ return CApi.sqlite3_value_java_object(valueAt(argNdx), type);
+ }
+
+ public int getType(int argNdx){return CApi.sqlite3_value_type(valueAt(argNdx));}
+ public int getSubtype(int argNdx){return CApi.sqlite3_value_subtype(valueAt(argNdx));}
+ public int getNumericType(int argNdx){return CApi.sqlite3_value_numeric_type(valueAt(argNdx));}
+ public int getNoChange(int argNdx){return CApi.sqlite3_value_nochange(valueAt(argNdx));}
+ public boolean getFromBind(int argNdx){return CApi.sqlite3_value_frombind(valueAt(argNdx));}
+ public int getEncoding(int argNdx){return CApi.sqlite3_value_encoding(valueAt(argNdx));}
+
+ public void resultInt(int v){ CApi.sqlite3_result_int(cx, v); }
+ public void resultInt64(long v){ CApi.sqlite3_result_int64(cx, v); }
+ public void resultDouble(double v){ CApi.sqlite3_result_double(cx, v); }
+ public void resultError(String msg){CApi.sqlite3_result_error(cx, msg);}
+ public void resultError(Exception e){CApi.sqlite3_result_error(cx, e);}
+ public void resultErrorTooBig(){CApi.sqlite3_result_error_toobig(cx);}
+ public void resultErrorCode(int rc){CApi.sqlite3_result_error_code(cx, rc);}
+ public void resultObject(Object o){CApi.sqlite3_result_java_object(cx, o);}
+ public void resultNull(){CApi.sqlite3_result_null(cx);}
+ /**
+ Analog to sqlite3_result_value(), using the Value object at the
+ given argument index.
+ */
+ public void resultArg(int argNdx){CApi.sqlite3_result_value(cx, valueAt(argNdx));}
+ public void resultSubtype(int subtype){CApi.sqlite3_result_subtype(cx, subtype);}
+ public void resultZeroBlob(long n){
+ // Throw on error? If n is too big,
+ // sqlite3_result_error_toobig() is automatically called.
+ CApi.sqlite3_result_zeroblob64(cx, n);
+ }
+
+ public void resultBlob(byte[] blob){CApi.sqlite3_result_blob(cx, blob);}
+ public void resultText(byte[] utf8){CApi.sqlite3_result_text(cx, utf8);}
+ public void resultText(String txt){CApi.sqlite3_result_text(cx, txt);}
+ public void resultText16(byte[] utf16){CApi.sqlite3_result_text16(cx, utf16);}
+ public void resultText16(String txt){CApi.sqlite3_result_text16(cx, txt);}
+
+ /**
+ Callbacks should invoke this on OOM errors, instead of throwing
+ OutOfMemoryError, because the latter cannot be propagated
+ through the C API.
+ */
+ public void resultNoMem(){CApi.sqlite3_result_error_nomem(cx);}
+
+ /**
+ Analog to sqlite3_set_auxdata() but throws if argNdx is out of
+ range.
+ */
+ public void setAuxData(int argNdx, Object o){
+ /* From the API docs: https://www.sqlite.org/c3ref/get_auxdata.html
+
+ The value of the N parameter to these interfaces should be
+ non-negative. Future enhancements may make use of negative N
+ values to define new kinds of function caching behavior.
+ */
+ valueAt(argNdx);
+ CApi.sqlite3_set_auxdata(cx, argNdx, o);
+ }
+
+ /**
+ Analog to sqlite3_get_auxdata() but throws if argNdx is out of
+ range.
+ */
+ public Object getAuxData(int argNdx){
+ valueAt(argNdx);
+ return CApi.sqlite3_get_auxdata(cx, argNdx);
+ }
+
+ /**
+ Represents a single SqlFunction argument. Primarily intended
+ for use with the Arguments class's Iterable interface.
+ */
+ public final static class Arg {
+ private final Arguments a;
+ private final int ndx;
+ /* Only for use by the Arguments class. */
+ private Arg(Arguments a, int ndx){
+ this.a = a;
+ this.ndx = ndx;
+ }
+ /** Returns this argument's index in its parent argument list. */
+ public int getIndex(){return ndx;}
+ public int getInt(){return a.getInt(ndx);}
+ public long getInt64(){return a.getInt64(ndx);}
+ public double getDouble(){return a.getDouble(ndx);}
+ public byte[] getBlob(){return a.getBlob(ndx);}
+ public byte[] getText(){return a.getText(ndx);}
+ public String getText16(){return a.getText16(ndx);}
+ public int getBytes(){return a.getBytes(ndx);}
+ public int getBytes16(){return a.getBytes16(ndx);}
+ public Object getObject(){return a.getObject(ndx);}
+ public <T> T getObject(Class<T> type){ return a.getObject(ndx, type); }
+ public int getType(){return a.getType(ndx);}
+ public Object getAuxData(){return a.getAuxData(ndx);}
+ public void setAuxData(Object o){a.setAuxData(ndx, o);}
+ }
+
+ @Override
+ public java.util.Iterator<SqlFunction.Arguments.Arg> iterator(){
+ final Arg[] proxies = new Arg[args.length];
+ for( int i = 0; i < args.length; ++i ){
+ proxies[i] = new Arg(this, i);
+ }
+ return java.util.Arrays.stream(proxies).iterator();
+ }
+
+ }
+
+ /**
+ Internal-use adapter for wrapping this package's ScalarFunction
+ for use with the org.sqlite.jni.capi.ScalarFunction interface.
+ */
+ static final class ScalarAdapter extends org.sqlite.jni.capi.ScalarFunction {
+ private final ScalarFunction impl;
+ ScalarAdapter(ScalarFunction impl){
+ this.impl = impl;
+ }
+ /**
+ Proxies this.impl.xFunc(), adapting the call arguments to that
+ function's signature. If the proxy throws, it's translated to
+ sqlite_result_error() with the exception's message.
+ */
+ public void xFunc(sqlite3_context cx, sqlite3_value[] args){
+ try{
+ impl.xFunc( new SqlFunction.Arguments(cx, args) );
+ }catch(Exception e){
+ CApi.sqlite3_result_error(cx, e);
+ }
+ }
+
+ public void xDestroy(){
+ impl.xDestroy();
+ }
+ }
+
+ /**
+ Internal-use adapter for wrapping this package's AggregateFunction
+ for use with the org.sqlite.jni.capi.AggregateFunction interface.
+ */
+ static /*cannot be final without duplicating the whole body in WindowAdapter*/
+ class AggregateAdapter extends org.sqlite.jni.capi.AggregateFunction {
+ private final AggregateFunction impl;
+ AggregateAdapter(AggregateFunction impl){
+ this.impl = impl;
+ }
+
+ /**
+ Proxies this.impl.xStep(), adapting the call arguments to that
+ function's signature. If the proxied function throws, it is
+ translated to sqlite_result_error() with the exception's
+ message.
+ */
+ public void xStep(sqlite3_context cx, sqlite3_value[] args){
+ try{
+ impl.xStep( new SqlFunction.Arguments(cx, args) );
+ }catch(Exception e){
+ CApi.sqlite3_result_error(cx, e);
+ }
+ }
+
+ /**
+ As for the xFinal() argument of the C API's
+ sqlite3_create_function(). If the proxied function throws, it
+ is translated into a sqlite3_result_error().
+ */
+ public void xFinal(sqlite3_context cx){
+ try{
+ impl.xFinal( new SqlFunction.Arguments(cx, null) );
+ }catch(Exception e){
+ CApi.sqlite3_result_error(cx, e);
+ }
+ }
+
+ public void xDestroy(){
+ impl.xDestroy();
+ }
+ }
+
+ /**
+ Internal-use adapter for wrapping this package's WindowFunction
+ for use with the org.sqlite.jni.capi.WindowFunction interface.
+ */
+ static final class WindowAdapter extends AggregateAdapter {
+ private final WindowFunction impl;
+ WindowAdapter(WindowFunction impl){
+ super(impl);
+ this.impl = impl;
+ }
+
+ /**
+ Proxies this.impl.xInverse(), adapting the call arguments to that
+ function's signature. If the proxied function throws, it is
+ translated to sqlite_result_error() with the exception's
+ message.
+ */
+ public void xInverse(sqlite3_context cx, sqlite3_value[] args){
+ try{
+ impl.xInverse( new SqlFunction.Arguments(cx, args) );
+ }catch(Exception e){
+ CApi.sqlite3_result_error(cx, e);
+ }
+ }
+
+ /**
+ As for the xValue() argument of the C API's sqlite3_create_window_function().
+ If the proxied function throws, it is translated into a sqlite3_result_error().
+ */
+ public void xValue(sqlite3_context cx){
+ try{
+ impl.xValue( new SqlFunction.Arguments(cx, null) );
+ }catch(Exception e){
+ CApi.sqlite3_result_error(cx, e);
+ }
+ }
+
+ public void xDestroy(){
+ impl.xDestroy();
+ }
+ }
+
+}
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java b/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java
new file mode 100644
index 0000000..de131e8
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java
@@ -0,0 +1,1991 @@
+/*
+** 2023-10-09
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the wrapper1 interface for sqlite3.
+*/
+package org.sqlite.jni.wrapper1;
+import java.nio.charset.StandardCharsets;
+import org.sqlite.jni.capi.CApi;
+import org.sqlite.jni.capi.sqlite3;
+import org.sqlite.jni.capi.sqlite3_stmt;
+import org.sqlite.jni.capi.sqlite3_backup;
+import org.sqlite.jni.capi.sqlite3_blob;
+import org.sqlite.jni.capi.OutputPointer;
+import java.nio.ByteBuffer;
+
+/**
+ This class represents a database connection, analog to the C-side
+ sqlite3 class but with added argument validation, exceptions, and
+ similar "smoothing of sharp edges" to make the API safe to use from
+ Java. It also acts as a namespace for other types for which
+ individual instances are tied to a specific database connection.
+*/
+public final class Sqlite implements AutoCloseable {
+ private sqlite3 db;
+ private static final boolean JNI_SUPPORTS_NIO =
+ CApi.sqlite3_jni_supports_nio();
+
+ // Result codes
+ public static final int OK = CApi.SQLITE_OK;
+ public static final int ERROR = CApi.SQLITE_ERROR;
+ public static final int INTERNAL = CApi.SQLITE_INTERNAL;
+ public static final int PERM = CApi.SQLITE_PERM;
+ public static final int ABORT = CApi.SQLITE_ABORT;
+ public static final int BUSY = CApi.SQLITE_BUSY;
+ public static final int LOCKED = CApi.SQLITE_LOCKED;
+ public static final int NOMEM = CApi.SQLITE_NOMEM;
+ public static final int READONLY = CApi.SQLITE_READONLY;
+ public static final int INTERRUPT = CApi.SQLITE_INTERRUPT;
+ public static final int IOERR = CApi.SQLITE_IOERR;
+ public static final int CORRUPT = CApi.SQLITE_CORRUPT;
+ public static final int NOTFOUND = CApi.SQLITE_NOTFOUND;
+ public static final int FULL = CApi.SQLITE_FULL;
+ public static final int CANTOPEN = CApi.SQLITE_CANTOPEN;
+ public static final int PROTOCOL = CApi.SQLITE_PROTOCOL;
+ public static final int EMPTY = CApi.SQLITE_EMPTY;
+ public static final int SCHEMA = CApi.SQLITE_SCHEMA;
+ public static final int TOOBIG = CApi.SQLITE_TOOBIG;
+ public static final int CONSTRAINT = CApi. SQLITE_CONSTRAINT;
+ public static final int MISMATCH = CApi.SQLITE_MISMATCH;
+ public static final int MISUSE = CApi.SQLITE_MISUSE;
+ public static final int NOLFS = CApi.SQLITE_NOLFS;
+ public static final int AUTH = CApi.SQLITE_AUTH;
+ public static final int FORMAT = CApi.SQLITE_FORMAT;
+ public static final int RANGE = CApi.SQLITE_RANGE;
+ public static final int NOTADB = CApi.SQLITE_NOTADB;
+ public static final int NOTICE = CApi.SQLITE_NOTICE;
+ public static final int WARNING = CApi.SQLITE_WARNING;
+ public static final int ROW = CApi.SQLITE_ROW;
+ public static final int DONE = CApi.SQLITE_DONE;
+ public static final int ERROR_MISSING_COLLSEQ = CApi.SQLITE_ERROR_MISSING_COLLSEQ;
+ public static final int ERROR_RETRY = CApi.SQLITE_ERROR_RETRY;
+ public static final int ERROR_SNAPSHOT = CApi.SQLITE_ERROR_SNAPSHOT;
+ public static final int IOERR_READ = CApi.SQLITE_IOERR_READ;
+ public static final int IOERR_SHORT_READ = CApi.SQLITE_IOERR_SHORT_READ;
+ public static final int IOERR_WRITE = CApi.SQLITE_IOERR_WRITE;
+ public static final int IOERR_FSYNC = CApi.SQLITE_IOERR_FSYNC;
+ public static final int IOERR_DIR_FSYNC = CApi.SQLITE_IOERR_DIR_FSYNC;
+ public static final int IOERR_TRUNCATE = CApi.SQLITE_IOERR_TRUNCATE;
+ public static final int IOERR_FSTAT = CApi.SQLITE_IOERR_FSTAT;
+ public static final int IOERR_UNLOCK = CApi.SQLITE_IOERR_UNLOCK;
+ public static final int IOERR_RDLOCK = CApi.SQLITE_IOERR_RDLOCK;
+ public static final int IOERR_DELETE = CApi.SQLITE_IOERR_DELETE;
+ public static final int IOERR_BLOCKED = CApi.SQLITE_IOERR_BLOCKED;
+ public static final int IOERR_NOMEM = CApi.SQLITE_IOERR_NOMEM;
+ public static final int IOERR_ACCESS = CApi.SQLITE_IOERR_ACCESS;
+ public static final int IOERR_CHECKRESERVEDLOCK = CApi.SQLITE_IOERR_CHECKRESERVEDLOCK;
+ public static final int IOERR_LOCK = CApi.SQLITE_IOERR_LOCK;
+ public static final int IOERR_CLOSE = CApi.SQLITE_IOERR_CLOSE;
+ public static final int IOERR_DIR_CLOSE = CApi.SQLITE_IOERR_DIR_CLOSE;
+ public static final int IOERR_SHMOPEN = CApi.SQLITE_IOERR_SHMOPEN;
+ public static final int IOERR_SHMSIZE = CApi.SQLITE_IOERR_SHMSIZE;
+ public static final int IOERR_SHMLOCK = CApi.SQLITE_IOERR_SHMLOCK;
+ public static final int IOERR_SHMMAP = CApi.SQLITE_IOERR_SHMMAP;
+ public static final int IOERR_SEEK = CApi.SQLITE_IOERR_SEEK;
+ public static final int IOERR_DELETE_NOENT = CApi.SQLITE_IOERR_DELETE_NOENT;
+ public static final int IOERR_MMAP = CApi.SQLITE_IOERR_MMAP;
+ public static final int IOERR_GETTEMPPATH = CApi.SQLITE_IOERR_GETTEMPPATH;
+ public static final int IOERR_CONVPATH = CApi.SQLITE_IOERR_CONVPATH;
+ public static final int IOERR_VNODE = CApi.SQLITE_IOERR_VNODE;
+ public static final int IOERR_AUTH = CApi.SQLITE_IOERR_AUTH;
+ public static final int IOERR_BEGIN_ATOMIC = CApi.SQLITE_IOERR_BEGIN_ATOMIC;
+ public static final int IOERR_COMMIT_ATOMIC = CApi.SQLITE_IOERR_COMMIT_ATOMIC;
+ public static final int IOERR_ROLLBACK_ATOMIC = CApi.SQLITE_IOERR_ROLLBACK_ATOMIC;
+ public static final int IOERR_DATA = CApi.SQLITE_IOERR_DATA;
+ public static final int IOERR_CORRUPTFS = CApi.SQLITE_IOERR_CORRUPTFS;
+ public static final int LOCKED_SHAREDCACHE = CApi.SQLITE_LOCKED_SHAREDCACHE;
+ public static final int LOCKED_VTAB = CApi.SQLITE_LOCKED_VTAB;
+ public static final int BUSY_RECOVERY = CApi.SQLITE_BUSY_RECOVERY;
+ public static final int BUSY_SNAPSHOT = CApi.SQLITE_BUSY_SNAPSHOT;
+ public static final int BUSY_TIMEOUT = CApi.SQLITE_BUSY_TIMEOUT;
+ public static final int CANTOPEN_NOTEMPDIR = CApi.SQLITE_CANTOPEN_NOTEMPDIR;
+ public static final int CANTOPEN_ISDIR = CApi.SQLITE_CANTOPEN_ISDIR;
+ public static final int CANTOPEN_FULLPATH = CApi.SQLITE_CANTOPEN_FULLPATH;
+ public static final int CANTOPEN_CONVPATH = CApi.SQLITE_CANTOPEN_CONVPATH;
+ public static final int CANTOPEN_SYMLINK = CApi.SQLITE_CANTOPEN_SYMLINK;
+ public static final int CORRUPT_VTAB = CApi.SQLITE_CORRUPT_VTAB;
+ public static final int CORRUPT_SEQUENCE = CApi.SQLITE_CORRUPT_SEQUENCE;
+ public static final int CORRUPT_INDEX = CApi.SQLITE_CORRUPT_INDEX;
+ public static final int READONLY_RECOVERY = CApi.SQLITE_READONLY_RECOVERY;
+ public static final int READONLY_CANTLOCK = CApi.SQLITE_READONLY_CANTLOCK;
+ public static final int READONLY_ROLLBACK = CApi.SQLITE_READONLY_ROLLBACK;
+ public static final int READONLY_DBMOVED = CApi.SQLITE_READONLY_DBMOVED;
+ public static final int READONLY_CANTINIT = CApi.SQLITE_READONLY_CANTINIT;
+ public static final int READONLY_DIRECTORY = CApi.SQLITE_READONLY_DIRECTORY;
+ public static final int ABORT_ROLLBACK = CApi.SQLITE_ABORT_ROLLBACK;
+ public static final int CONSTRAINT_CHECK = CApi.SQLITE_CONSTRAINT_CHECK;
+ public static final int CONSTRAINT_COMMITHOOK = CApi.SQLITE_CONSTRAINT_COMMITHOOK;
+ public static final int CONSTRAINT_FOREIGNKEY = CApi.SQLITE_CONSTRAINT_FOREIGNKEY;
+ public static final int CONSTRAINT_FUNCTION = CApi.SQLITE_CONSTRAINT_FUNCTION;
+ public static final int CONSTRAINT_NOTNULL = CApi.SQLITE_CONSTRAINT_NOTNULL;
+ public static final int CONSTRAINT_PRIMARYKEY = CApi.SQLITE_CONSTRAINT_PRIMARYKEY;
+ public static final int CONSTRAINT_TRIGGER = CApi.SQLITE_CONSTRAINT_TRIGGER;
+ public static final int CONSTRAINT_UNIQUE = CApi.SQLITE_CONSTRAINT_UNIQUE;
+ public static final int CONSTRAINT_VTAB = CApi.SQLITE_CONSTRAINT_VTAB;
+ public static final int CONSTRAINT_ROWID = CApi.SQLITE_CONSTRAINT_ROWID;
+ public static final int CONSTRAINT_PINNED = CApi.SQLITE_CONSTRAINT_PINNED;
+ public static final int CONSTRAINT_DATATYPE = CApi.SQLITE_CONSTRAINT_DATATYPE;
+ public static final int NOTICE_RECOVER_WAL = CApi.SQLITE_NOTICE_RECOVER_WAL;
+ public static final int NOTICE_RECOVER_ROLLBACK = CApi.SQLITE_NOTICE_RECOVER_ROLLBACK;
+ public static final int WARNING_AUTOINDEX = CApi.SQLITE_WARNING_AUTOINDEX;
+ public static final int AUTH_USER = CApi.SQLITE_AUTH_USER;
+ public static final int OK_LOAD_PERMANENTLY = CApi.SQLITE_OK_LOAD_PERMANENTLY;
+
+ // sqlite3_open() flags
+ public static final int OPEN_READWRITE = CApi.SQLITE_OPEN_READWRITE;
+ public static final int OPEN_CREATE = CApi.SQLITE_OPEN_CREATE;
+ public static final int OPEN_EXRESCODE = CApi.SQLITE_OPEN_EXRESCODE;
+
+ // transaction state
+ public static final int TXN_NONE = CApi.SQLITE_TXN_NONE;
+ public static final int TXN_READ = CApi.SQLITE_TXN_READ;
+ public static final int TXN_WRITE = CApi.SQLITE_TXN_WRITE;
+
+ // sqlite3_status() ops
+ public static final int STATUS_MEMORY_USED = CApi.SQLITE_STATUS_MEMORY_USED;
+ public static final int STATUS_PAGECACHE_USED = CApi.SQLITE_STATUS_PAGECACHE_USED;
+ public static final int STATUS_PAGECACHE_OVERFLOW = CApi.SQLITE_STATUS_PAGECACHE_OVERFLOW;
+ public static final int STATUS_MALLOC_SIZE = CApi.SQLITE_STATUS_MALLOC_SIZE;
+ public static final int STATUS_PARSER_STACK = CApi.SQLITE_STATUS_PARSER_STACK;
+ public static final int STATUS_PAGECACHE_SIZE = CApi.SQLITE_STATUS_PAGECACHE_SIZE;
+ public static final int STATUS_MALLOC_COUNT = CApi.SQLITE_STATUS_MALLOC_COUNT;
+
+ // sqlite3_db_status() ops
+ public static final int DBSTATUS_LOOKASIDE_USED = CApi.SQLITE_DBSTATUS_LOOKASIDE_USED;
+ public static final int DBSTATUS_CACHE_USED = CApi.SQLITE_DBSTATUS_CACHE_USED;
+ public static final int DBSTATUS_SCHEMA_USED = CApi.SQLITE_DBSTATUS_SCHEMA_USED;
+ public static final int DBSTATUS_STMT_USED = CApi.SQLITE_DBSTATUS_STMT_USED;
+ public static final int DBSTATUS_LOOKASIDE_HIT = CApi.SQLITE_DBSTATUS_LOOKASIDE_HIT;
+ public static final int DBSTATUS_LOOKASIDE_MISS_SIZE = CApi.SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE;
+ public static final int DBSTATUS_LOOKASIDE_MISS_FULL = CApi.SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL;
+ public static final int DBSTATUS_CACHE_HIT = CApi.SQLITE_DBSTATUS_CACHE_HIT;
+ public static final int DBSTATUS_CACHE_MISS = CApi.SQLITE_DBSTATUS_CACHE_MISS;
+ public static final int DBSTATUS_CACHE_WRITE = CApi.SQLITE_DBSTATUS_CACHE_WRITE;
+ public static final int DBSTATUS_DEFERRED_FKS = CApi.SQLITE_DBSTATUS_DEFERRED_FKS;
+ public static final int DBSTATUS_CACHE_USED_SHARED = CApi.SQLITE_DBSTATUS_CACHE_USED_SHARED;
+ public static final int DBSTATUS_CACHE_SPILL = CApi.SQLITE_DBSTATUS_CACHE_SPILL;
+
+ // Limits
+ public static final int LIMIT_LENGTH = CApi.SQLITE_LIMIT_LENGTH;
+ public static final int LIMIT_SQL_LENGTH = CApi.SQLITE_LIMIT_SQL_LENGTH;
+ public static final int LIMIT_COLUMN = CApi.SQLITE_LIMIT_COLUMN;
+ public static final int LIMIT_EXPR_DEPTH = CApi.SQLITE_LIMIT_EXPR_DEPTH;
+ public static final int LIMIT_COMPOUND_SELECT = CApi.SQLITE_LIMIT_COMPOUND_SELECT;
+ public static final int LIMIT_VDBE_OP = CApi.SQLITE_LIMIT_VDBE_OP;
+ public static final int LIMIT_FUNCTION_ARG = CApi.SQLITE_LIMIT_FUNCTION_ARG;
+ public static final int LIMIT_ATTACHED = CApi.SQLITE_LIMIT_ATTACHED;
+ public static final int LIMIT_LIKE_PATTERN_LENGTH = CApi.SQLITE_LIMIT_LIKE_PATTERN_LENGTH;
+ public static final int LIMIT_VARIABLE_NUMBER = CApi.SQLITE_LIMIT_VARIABLE_NUMBER;
+ public static final int LIMIT_TRIGGER_DEPTH = CApi.SQLITE_LIMIT_TRIGGER_DEPTH;
+ public static final int LIMIT_WORKER_THREADS = CApi.SQLITE_LIMIT_WORKER_THREADS;
+
+ // sqlite3_prepare_v3() flags
+ public static final int PREPARE_PERSISTENT = CApi.SQLITE_PREPARE_PERSISTENT;
+ public static final int PREPARE_NO_VTAB = CApi.SQLITE_PREPARE_NO_VTAB;
+
+ // sqlite3_trace_v2() flags
+ public static final int TRACE_STMT = CApi.SQLITE_TRACE_STMT;
+ public static final int TRACE_PROFILE = CApi.SQLITE_TRACE_PROFILE;
+ public static final int TRACE_ROW = CApi.SQLITE_TRACE_ROW;
+ public static final int TRACE_CLOSE = CApi.SQLITE_TRACE_CLOSE;
+ public static final int TRACE_ALL = TRACE_STMT | TRACE_PROFILE | TRACE_ROW | TRACE_CLOSE;
+
+ // sqlite3_db_config() ops
+ public static final int DBCONFIG_ENABLE_FKEY = CApi.SQLITE_DBCONFIG_ENABLE_FKEY;
+ public static final int DBCONFIG_ENABLE_TRIGGER = CApi.SQLITE_DBCONFIG_ENABLE_TRIGGER;
+ public static final int DBCONFIG_ENABLE_FTS3_TOKENIZER = CApi.SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER;
+ public static final int DBCONFIG_ENABLE_LOAD_EXTENSION = CApi.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION;
+ public static final int DBCONFIG_NO_CKPT_ON_CLOSE = CApi.SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE;
+ public static final int DBCONFIG_ENABLE_QPSG = CApi.SQLITE_DBCONFIG_ENABLE_QPSG;
+ public static final int DBCONFIG_TRIGGER_EQP = CApi.SQLITE_DBCONFIG_TRIGGER_EQP;
+ public static final int DBCONFIG_RESET_DATABASE = CApi.SQLITE_DBCONFIG_RESET_DATABASE;
+ public static final int DBCONFIG_DEFENSIVE = CApi.SQLITE_DBCONFIG_DEFENSIVE;
+ public static final int DBCONFIG_WRITABLE_SCHEMA = CApi.SQLITE_DBCONFIG_WRITABLE_SCHEMA;
+ public static final int DBCONFIG_LEGACY_ALTER_TABLE = CApi.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE;
+ public static final int DBCONFIG_DQS_DML = CApi.SQLITE_DBCONFIG_DQS_DML;
+ public static final int DBCONFIG_DQS_DDL = CApi.SQLITE_DBCONFIG_DQS_DDL;
+ public static final int DBCONFIG_ENABLE_VIEW = CApi.SQLITE_DBCONFIG_ENABLE_VIEW;
+ public static final int DBCONFIG_LEGACY_FILE_FORMAT = CApi.SQLITE_DBCONFIG_LEGACY_FILE_FORMAT;
+ public static final int DBCONFIG_TRUSTED_SCHEMA = CApi.SQLITE_DBCONFIG_TRUSTED_SCHEMA;
+ public static final int DBCONFIG_STMT_SCANSTATUS = CApi.SQLITE_DBCONFIG_STMT_SCANSTATUS;
+ public static final int DBCONFIG_REVERSE_SCANORDER = CApi.SQLITE_DBCONFIG_REVERSE_SCANORDER;
+
+ // sqlite3_config() ops
+ public static final int CONFIG_SINGLETHREAD = CApi.SQLITE_CONFIG_SINGLETHREAD;
+ public static final int CONFIG_MULTITHREAD = CApi.SQLITE_CONFIG_MULTITHREAD;
+ public static final int CONFIG_SERIALIZED = CApi.SQLITE_CONFIG_SERIALIZED;
+
+ // Encodings
+ public static final int UTF8 = CApi.SQLITE_UTF8;
+ public static final int UTF16 = CApi.SQLITE_UTF16;
+ public static final int UTF16LE = CApi.SQLITE_UTF16LE;
+ public static final int UTF16BE = CApi.SQLITE_UTF16BE;
+ /* We elide the UTF16_ALIGNED from this interface because it
+ is irrelevant for the Java interface. */
+
+ // SQL data type IDs
+ public static final int INTEGER = CApi.SQLITE_INTEGER;
+ public static final int FLOAT = CApi.SQLITE_FLOAT;
+ public static final int TEXT = CApi.SQLITE_TEXT;
+ public static final int BLOB = CApi.SQLITE_BLOB;
+ public static final int NULL = CApi.SQLITE_NULL;
+
+ // Authorizer codes.
+ public static final int DENY = CApi.SQLITE_DENY;
+ public static final int IGNORE = CApi.SQLITE_IGNORE;
+ public static final int CREATE_INDEX = CApi.SQLITE_CREATE_INDEX;
+ public static final int CREATE_TABLE = CApi.SQLITE_CREATE_TABLE;
+ public static final int CREATE_TEMP_INDEX = CApi.SQLITE_CREATE_TEMP_INDEX;
+ public static final int CREATE_TEMP_TABLE = CApi.SQLITE_CREATE_TEMP_TABLE;
+ public static final int CREATE_TEMP_TRIGGER = CApi.SQLITE_CREATE_TEMP_TRIGGER;
+ public static final int CREATE_TEMP_VIEW = CApi.SQLITE_CREATE_TEMP_VIEW;
+ public static final int CREATE_TRIGGER = CApi.SQLITE_CREATE_TRIGGER;
+ public static final int CREATE_VIEW = CApi.SQLITE_CREATE_VIEW;
+ public static final int DELETE = CApi.SQLITE_DELETE;
+ public static final int DROP_INDEX = CApi.SQLITE_DROP_INDEX;
+ public static final int DROP_TABLE = CApi.SQLITE_DROP_TABLE;
+ public static final int DROP_TEMP_INDEX = CApi.SQLITE_DROP_TEMP_INDEX;
+ public static final int DROP_TEMP_TABLE = CApi.SQLITE_DROP_TEMP_TABLE;
+ public static final int DROP_TEMP_TRIGGER = CApi.SQLITE_DROP_TEMP_TRIGGER;
+ public static final int DROP_TEMP_VIEW = CApi.SQLITE_DROP_TEMP_VIEW;
+ public static final int DROP_TRIGGER = CApi.SQLITE_DROP_TRIGGER;
+ public static final int DROP_VIEW = CApi.SQLITE_DROP_VIEW;
+ public static final int INSERT = CApi.SQLITE_INSERT;
+ public static final int PRAGMA = CApi.SQLITE_PRAGMA;
+ public static final int READ = CApi.SQLITE_READ;
+ public static final int SELECT = CApi.SQLITE_SELECT;
+ public static final int TRANSACTION = CApi.SQLITE_TRANSACTION;
+ public static final int UPDATE = CApi.SQLITE_UPDATE;
+ public static final int ATTACH = CApi.SQLITE_ATTACH;
+ public static final int DETACH = CApi.SQLITE_DETACH;
+ public static final int ALTER_TABLE = CApi.SQLITE_ALTER_TABLE;
+ public static final int REINDEX = CApi.SQLITE_REINDEX;
+ public static final int ANALYZE = CApi.SQLITE_ANALYZE;
+ public static final int CREATE_VTABLE = CApi.SQLITE_CREATE_VTABLE;
+ public static final int DROP_VTABLE = CApi.SQLITE_DROP_VTABLE;
+ public static final int FUNCTION = CApi.SQLITE_FUNCTION;
+ public static final int SAVEPOINT = CApi.SQLITE_SAVEPOINT;
+ public static final int RECURSIVE = CApi.SQLITE_RECURSIVE;
+
+ //! Used only by the open() factory functions.
+ private Sqlite(sqlite3 db){
+ this.db = db;
+ }
+
+ /** Maps org.sqlite.jni.capi.sqlite3 to Sqlite instances. */
+ private static final java.util.Map<org.sqlite.jni.capi.sqlite3, Sqlite> nativeToWrapper
+ = new java.util.HashMap<>();
+
+
+ /**
+ When any given thread is done using the SQLite library, calling
+ this will free up any native-side resources which may be
+ associated specifically with that thread. This is not strictly
+ necessary, in particular in applications which only use SQLite
+ from a single thread, but may help free some otherwise errant
+ resources.
+
+ Calling into SQLite from a given thread after this has been
+ called in that thread is harmless. The library will simply start
+ to re-cache certain state for that thread.
+
+ Contrariwise, failing to call this will effectively leak a small
+ amount of cached state for the thread, which may add up to
+ significant amounts if the application uses SQLite from many
+ threads.
+
+ This must never be called while actively using SQLite from this
+ thread, e.g. from within a query loop or a callback which is
+ operating on behalf of the library.
+ */
+ static void uncacheThread(){
+ CApi.sqlite3_java_uncache_thread();
+ }
+
+ /**
+ Returns the Sqlite object associated with the given sqlite3
+ object, or null if there is no such mapping.
+ */
+ static Sqlite fromNative(sqlite3 low){
+ synchronized(nativeToWrapper){
+ return nativeToWrapper.get(low);
+ }
+ }
+
+ /**
+ Returns a newly-opened db connection or throws SqliteException if
+ opening fails. All arguments are as documented for
+ sqlite3_open_v2().
+
+ Design question: do we want static factory functions or should
+ this be reformulated as a constructor?
+ */
+ public static Sqlite open(String filename, int flags, String vfsName){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ final int rc = CApi.sqlite3_open_v2(filename, out, flags, vfsName);
+ final sqlite3 n = out.take();
+ if( 0!=rc ){
+ if( null==n ) throw new SqliteException(rc);
+ final SqliteException ex = new SqliteException(n);
+ n.close();
+ throw ex;
+ }
+ final Sqlite rv = new Sqlite(n);
+ synchronized(nativeToWrapper){
+ nativeToWrapper.put(n, rv);
+ }
+ runAutoExtensions(rv);
+ return rv;
+ }
+
+ public static Sqlite open(String filename, int flags){
+ return open(filename, flags, null);
+ }
+
+ public static Sqlite open(String filename){
+ return open(filename, OPEN_READWRITE|OPEN_CREATE, null);
+ }
+
+ public static String libVersion(){
+ return CApi.sqlite3_libversion();
+ }
+
+ public static int libVersionNumber(){
+ return CApi.sqlite3_libversion_number();
+ }
+
+ public static String libSourceId(){
+ return CApi.sqlite3_sourceid();
+ }
+
+ /**
+ Returns the value of the native library's build-time value of the
+ SQLITE_THREADSAFE build option.
+ */
+ public static int libThreadsafe(){
+ return CApi.sqlite3_threadsafe();
+ }
+
+ /**
+ Analog to sqlite3_compileoption_get().
+ */
+ public static String compileOptionGet(int n){
+ return CApi.sqlite3_compileoption_get(n);
+ }
+
+ /**
+ Analog to sqlite3_compileoption_used().
+ */
+ public static boolean compileOptionUsed(String optName){
+ return CApi.sqlite3_compileoption_used(optName);
+ }
+
+ private static boolean hasNormalizeSql =
+ compileOptionUsed("ENABLE_NORMALIZE");
+
+ private static boolean hasSqlLog =
+ compileOptionUsed("ENABLE_SQLLOG");
+
+ /**
+ Throws UnsupportedOperationException if check is false.
+ flag is expected to be the name of an SQLITE_ENABLE_...
+ build flag.
+ */
+ private static void checkSupported(boolean check, String flag){
+ if( !check ){
+ throw new UnsupportedOperationException(
+ "Library was built without "+flag
+ );
+ }
+ }
+
+ /**
+ Analog to sqlite3_complete().
+ */
+ public static boolean isCompleteStatement(String sql){
+ switch(CApi.sqlite3_complete(sql)){
+ case 0: return false;
+ case CApi.SQLITE_MISUSE:
+ throw new IllegalArgumentException("Input may not be null.");
+ case CApi.SQLITE_NOMEM:
+ throw new OutOfMemoryError();
+ default:
+ return true;
+ }
+ }
+
+ public static int keywordCount(){
+ return CApi.sqlite3_keyword_count();
+ }
+
+ public static boolean keywordCheck(String word){
+ return CApi.sqlite3_keyword_check(word);
+ }
+
+ public static String keywordName(int index){
+ return CApi.sqlite3_keyword_name(index);
+ }
+
+ public static boolean strglob(String glob, String txt){
+ return 0==CApi.sqlite3_strglob(glob, txt);
+ }
+
+ public static boolean strlike(String glob, String txt, char escChar){
+ return 0==CApi.sqlite3_strlike(glob, txt, escChar);
+ }
+
+ /**
+ Output object for use with status() and libStatus().
+ */
+ public static final class Status {
+ /** The current value for the requested status() or libStatus() metric. */
+ long current;
+ /** The peak value for the requested status() or libStatus() metric. */
+ long peak;
+ };
+
+ /**
+ As per sqlite3_status64(), but returns its current and high-water
+ results as a Status object. Throws if the first argument is
+ not one of the STATUS_... constants.
+ */
+ public static Status libStatus(int op, boolean resetStats){
+ org.sqlite.jni.capi.OutputPointer.Int64 pCurrent =
+ new org.sqlite.jni.capi.OutputPointer.Int64();
+ org.sqlite.jni.capi.OutputPointer.Int64 pHighwater =
+ new org.sqlite.jni.capi.OutputPointer.Int64();
+ checkRcStatic( CApi.sqlite3_status64(op, pCurrent, pHighwater, resetStats) );
+ final Status s = new Status();
+ s.current = pCurrent.value;
+ s.peak = pHighwater.value;
+ return s;
+ }
+
+ /**
+ As per sqlite3_db_status(), but returns its current and
+ high-water results as a Status object. Throws if the first
+ argument is not one of the DBSTATUS_... constants or on any other
+ misuse.
+ */
+ public Status status(int op, boolean resetStats){
+ org.sqlite.jni.capi.OutputPointer.Int32 pCurrent =
+ new org.sqlite.jni.capi.OutputPointer.Int32();
+ org.sqlite.jni.capi.OutputPointer.Int32 pHighwater =
+ new org.sqlite.jni.capi.OutputPointer.Int32();
+ checkRc( CApi.sqlite3_db_status(thisDb(), op, pCurrent, pHighwater, resetStats) );
+ final Status s = new Status();
+ s.current = pCurrent.value;
+ s.peak = pHighwater.value;
+ return s;
+ }
+
+ @Override public void close(){
+ if(null!=this.db){
+ synchronized(nativeToWrapper){
+ nativeToWrapper.remove(this.db);
+ }
+ this.db.close();
+ this.db = null;
+ }
+ }
+
+ /**
+ Returns this object's underlying native db handle, or null if
+ this instance has been closed. This is very specifically not
+ public.
+ */
+ sqlite3 nativeHandle(){ return this.db; }
+
+ private sqlite3 thisDb(){
+ if( null==db || 0==db.getNativePointer() ){
+ throw new IllegalArgumentException("This database instance is closed.");
+ }
+ return this.db;
+ }
+
+ // private byte[] stringToUtf8(String s){
+ // return s==null ? null : s.getBytes(StandardCharsets.UTF_8);
+ // }
+
+ /**
+ If rc!=0, throws an SqliteException. If this db is currently
+ opened and has non-0 sqlite3_errcode(), the error state is
+ extracted from it, else only the string form of rc is used. It is
+ the caller's responsibility to filter out non-error codes such as
+ SQLITE_ROW and SQLITE_DONE before calling this.
+
+ As a special case, if rc is SQLITE_NOMEM, an OutOfMemoryError is
+ thrown.
+ */
+ private void checkRc(int rc){
+ if( 0!=rc ){
+ if( CApi.SQLITE_NOMEM==rc ){
+ throw new OutOfMemoryError();
+ }else if( null==db || 0==CApi.sqlite3_errcode(db) ){
+ throw new SqliteException(rc);
+ }else{
+ throw new SqliteException(db);
+ }
+ }
+ }
+
+ /**
+ Like checkRc() but behaves as if that function were
+ called with a null db object.
+ */
+ private static void checkRcStatic(int rc){
+ if( 0!=rc ){
+ if( CApi.SQLITE_NOMEM==rc ){
+ throw new OutOfMemoryError();
+ }else{
+ throw new SqliteException(rc);
+ }
+ }
+ }
+
+ /**
+ Toggles the use of extended result codes on or off. By default
+ they are turned off, but they can be enabled by default by
+ including the OPEN_EXRESCODE flag when opening a database.
+
+ Because this API reports db-side errors using exceptions,
+ enabling this may change the values returned by
+ SqliteException.errcode().
+ */
+ public void useExtendedResultCodes(boolean on){
+ checkRc( CApi.sqlite3_extended_result_codes(thisDb(), on) );
+ }
+
+ /**
+ Analog to sqlite3_prepare_v3(), this prepares the first SQL
+ statement from the given input string and returns it as a
+ Stmt. It throws an SqliteException if preparation fails or an
+ IllegalArgumentException if the input is empty (e.g. contains
+ only comments or whitespace).
+
+ The first argument must be SQL input in UTF-8 encoding.
+
+ prepFlags must be 0 or a bitmask of the PREPARE_... constants.
+
+ For processing multiple statements from a single input, use
+ prepareMulti().
+
+ Design note: though the C-level API succeeds with a null
+ statement object for empty inputs, that approach is cumbersome to
+ use in higher-level APIs because every prepared statement has to
+ be checked for null before using it.
+ */
+ public Stmt prepare(byte utf8Sql[], int prepFlags){
+ final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+ final int rc = CApi.sqlite3_prepare_v3(thisDb(), utf8Sql, prepFlags, out);
+ checkRc(rc);
+ final sqlite3_stmt q = out.take();
+ if( null==q ){
+ /* The C-level API treats input which is devoid of SQL
+ statements (e.g. all comments or an empty string) as success
+ but returns a NULL sqlite3_stmt object. In higher-level APIs,
+ wrapping a "successful NULL" object that way is tedious to
+ use because it forces clients and/or wrapper-level code to
+ check for that unusual case. In practice, higher-level
+ bindings are generally better-served by treating empty SQL
+ input as an error. */
+ throw new IllegalArgumentException("Input contains no SQL statements.");
+ }
+ return new Stmt(this, q);
+ }
+
+ /**
+ Equivalent to prepare(X, prepFlags), where X is
+ sql.getBytes(StandardCharsets.UTF_8).
+ */
+ public Stmt prepare(String sql, int prepFlags){
+ return prepare( sql.getBytes(StandardCharsets.UTF_8), prepFlags );
+ }
+
+ /**
+ Equivalent to prepare(sql, 0).
+ */
+ public Stmt prepare(String sql){
+ return prepare(sql, 0);
+ }
+
+
+ /**
+ Callback type for use with prepareMulti().
+ */
+ public interface PrepareMulti {
+ /**
+ Gets passed a Stmt which it may handle in arbitrary ways.
+ Ownership of st is passed to this function. It must throw on
+ error.
+ */
+ void call(Sqlite.Stmt st);
+ }
+
+ /**
+ A PrepareMulti implementation which calls another PrepareMulti
+ object and then finalizes its statement.
+ */
+ public static class PrepareMultiFinalize implements PrepareMulti {
+ private final PrepareMulti pm;
+ /**
+ Proxies the given PrepareMulti via this object's call() method.
+ */
+ public PrepareMultiFinalize(PrepareMulti proxy){
+ this.pm = proxy;
+ }
+ /**
+ Passes st to the call() method of the object this one proxies,
+ then finalizes st, propagating any exceptions from call() after
+ finalizing st.
+ */
+ @Override public void call(Stmt st){
+ try{ pm.call(st); }
+ finally{ st.finalizeStmt(); }
+ }
+ }
+
+ /**
+ Equivalent to prepareMulti(sql,0,visitor).
+ */
+ public void prepareMulti(String sql, PrepareMulti visitor){
+ prepareMulti( sql, 0, visitor );
+ }
+
+ /**
+ Equivallent to prepareMulti(X,prepFlags,visitor), where X is
+ sql.getBytes(StandardCharsets.UTF_8).
+ */
+ public void prepareMulti(String sql, int prepFlags, PrepareMulti visitor){
+ prepareMulti(sql.getBytes(StandardCharsets.UTF_8), prepFlags, visitor);
+ }
+
+ /**
+ A variant of prepare() which can handle multiple SQL statements
+ in a single input string. For each statement in the given string,
+ the statement is passed to visitor.call() a single time, passing
+ ownership of the statement to that function. This function does
+ not step() or close() statements - those operations are left to
+ caller or the visitor function.
+
+ Unlike prepare(), this function does not fail if the input
+ contains only whitespace or SQL comments. In that case it is up
+ to the caller to arrange for that to be an error (if desired).
+
+ PrepareMultiFinalize offers a proxy which finalizes each
+ statement after it is passed to another client-defined visitor.
+
+ Be aware that certain legal SQL constructs may fail in the
+ preparation phase, before the corresponding statement can be
+ stepped. Most notably, authorizer checks which disallow access to
+ something in a statement behave that way.
+ */
+ public void prepareMulti(byte sqlUtf8[], int prepFlags, PrepareMulti visitor){
+ int pos = 0, n = 1;
+ byte[] sqlChunk = sqlUtf8;
+ final org.sqlite.jni.capi.OutputPointer.sqlite3_stmt outStmt =
+ new org.sqlite.jni.capi.OutputPointer.sqlite3_stmt();
+ final org.sqlite.jni.capi.OutputPointer.Int32 oTail =
+ new org.sqlite.jni.capi.OutputPointer.Int32();
+ while( pos < sqlChunk.length ){
+ sqlite3_stmt stmt = null;
+ if( pos>0 ){
+ sqlChunk = java.util.Arrays.copyOfRange(sqlChunk, pos, sqlChunk.length);
+ }
+ if( 0==sqlChunk.length ) break;
+ checkRc(
+ CApi.sqlite3_prepare_v3(db, sqlChunk, prepFlags, outStmt, oTail)
+ );
+ pos = oTail.value;
+ stmt = outStmt.take();
+ if( null==stmt ){
+ /* empty statement, e.g. only comments or whitespace, was parsed. */
+ continue;
+ }
+ visitor.call(new Stmt(this, stmt));
+ }
+ }
+
+ public void createFunction(String name, int nArg, int eTextRep, ScalarFunction f){
+ int rc = CApi.sqlite3_create_function(thisDb(), name, nArg, eTextRep,
+ new SqlFunction.ScalarAdapter(f));
+ if( 0!=rc ) throw new SqliteException(db);
+ }
+
+ public void createFunction(String name, int nArg, ScalarFunction f){
+ this.createFunction(name, nArg, CApi.SQLITE_UTF8, f);
+ }
+
+ public void createFunction(String name, int nArg, int eTextRep, AggregateFunction f){
+ int rc = CApi.sqlite3_create_function(thisDb(), name, nArg, eTextRep,
+ new SqlFunction.AggregateAdapter(f));
+ if( 0!=rc ) throw new SqliteException(db);
+ }
+
+ public void createFunction(String name, int nArg, AggregateFunction f){
+ this.createFunction(name, nArg, CApi.SQLITE_UTF8, f);
+ }
+
+ public void createFunction(String name, int nArg, int eTextRep, WindowFunction f){
+ int rc = CApi.sqlite3_create_function(thisDb(), name, nArg, eTextRep,
+ new SqlFunction.WindowAdapter(f));
+ if( 0!=rc ) throw new SqliteException(db);
+ }
+
+ public void createFunction(String name, int nArg, WindowFunction f){
+ this.createFunction(name, nArg, CApi.SQLITE_UTF8, f);
+ }
+
+ public long changes(){
+ return CApi.sqlite3_changes64(thisDb());
+ }
+
+ public long totalChanges(){
+ return CApi.sqlite3_total_changes64(thisDb());
+ }
+
+ public long lastInsertRowId(){
+ return CApi.sqlite3_last_insert_rowid(thisDb());
+ }
+
+ public void setLastInsertRowId(long rowId){
+ CApi.sqlite3_set_last_insert_rowid(thisDb(), rowId);
+ }
+
+ public void interrupt(){
+ CApi.sqlite3_interrupt(thisDb());
+ }
+
+ public boolean isInterrupted(){
+ return CApi.sqlite3_is_interrupted(thisDb());
+ }
+
+ public boolean isAutoCommit(){
+ return CApi.sqlite3_get_autocommit(thisDb());
+ }
+
+ /**
+ Analog to sqlite3_txn_state(). Returns one of TXN_NONE, TXN_READ,
+ or TXN_WRITE to denote this database's current transaction state
+ for the given schema name (or the most restrictive state of any
+ schema if zSchema is null).
+ */
+ public int transactionState(String zSchema){
+ return CApi.sqlite3_txn_state(thisDb(), zSchema);
+ }
+
+ /**
+ Analog to sqlite3_db_name(). Returns null if passed an unknown
+ index.
+ */
+ public String dbName(int dbNdx){
+ return CApi.sqlite3_db_name(thisDb(), dbNdx);
+ }
+
+ /**
+ Analog to sqlite3_db_filename(). Returns null if passed an
+ unknown db name.
+ */
+ public String dbFileName(String dbName){
+ return CApi.sqlite3_db_filename(thisDb(), dbName);
+ }
+
+ /**
+ Analog to sqlite3_db_config() for the call forms which take one
+ of the boolean-type db configuration flags (namely the
+ DBCONFIG_... constants defined in this class). On success it
+ returns the result of that underlying call. Throws on error.
+ */
+ public boolean dbConfig(int op, boolean on){
+ org.sqlite.jni.capi.OutputPointer.Int32 pOut =
+ new org.sqlite.jni.capi.OutputPointer.Int32();
+ checkRc( CApi.sqlite3_db_config(thisDb(), op, on ? 1 : 0, pOut) );
+ return pOut.get()!=0;
+ }
+
+ /**
+ Analog to the variant of sqlite3_db_config() for configuring the
+ SQLITE_DBCONFIG_MAINDBNAME option. Throws on error.
+ */
+ public void setMainDbName(String name){
+ checkRc(
+ CApi.sqlite3_db_config(thisDb(), CApi.SQLITE_DBCONFIG_MAINDBNAME,
+ name)
+ );
+ }
+
+ /**
+ Analog to sqlite3_db_readonly() but throws an SqliteException
+ with result code SQLITE_NOTFOUND if given an unknown database
+ name.
+ */
+ public boolean readOnly(String dbName){
+ final int rc = CApi.sqlite3_db_readonly(thisDb(), dbName);
+ if( 0==rc ) return false;
+ else if( rc>0 ) return true;
+ throw new SqliteException(CApi.SQLITE_NOTFOUND);
+ }
+
+ /**
+ Analog to sqlite3_db_release_memory().
+ */
+ public void releaseMemory(){
+ CApi.sqlite3_db_release_memory(thisDb());
+ }
+
+ /**
+ Analog to sqlite3_release_memory().
+ */
+ public static int libReleaseMemory(int n){
+ return CApi.sqlite3_release_memory(n);
+ }
+
+ /**
+ Analog to sqlite3_limit(). limitId must be one of the
+ LIMIT_... constants.
+
+ Returns the old limit for the given option. If newLimit is
+ negative, it returns the old limit without modifying the limit.
+
+ If sqlite3_limit() returns a negative value, this function throws
+ an SqliteException with the SQLITE_RANGE result code but no
+ further error info (because that case does not qualify as a
+ db-level error). Such errors may indicate an invalid argument
+ value or an invalid range for newLimit (the underlying function
+ does not differentiate between those).
+ */
+ public int limit(int limitId, int newLimit){
+ final int rc = CApi.sqlite3_limit(thisDb(), limitId, newLimit);
+ if( rc<0 ){
+ throw new SqliteException(CApi.SQLITE_RANGE);
+ }
+ return rc;
+ }
+
+ /**
+ Analog to sqlite3_errstr().
+ */
+ static String errstr(int resultCode){
+ return CApi.sqlite3_errstr(resultCode);
+ }
+
+ /**
+ A wrapper object for use with tableColumnMetadata(). They are
+ created and populated only via that interface.
+ */
+ public final class TableColumnMetadata {
+ Boolean pNotNull = null;
+ Boolean pPrimaryKey = null;
+ Boolean pAutoinc = null;
+ String pzCollSeq = null;
+ String pzDataType = null;
+
+ private TableColumnMetadata(){}
+
+ public String getDataType(){ return pzDataType; }
+ public String getCollation(){ return pzCollSeq; }
+ public boolean isNotNull(){ return pNotNull; }
+ public boolean isPrimaryKey(){ return pPrimaryKey; }
+ public boolean isAutoincrement(){ return pAutoinc; }
+ }
+
+ /**
+ Returns data about a database, table, and (optionally) column
+ (which may be null), as per sqlite3_table_column_metadata().
+ Throws if passed invalid arguments, else returns the result as a
+ new TableColumnMetadata object.
+ */
+ TableColumnMetadata tableColumnMetadata(
+ String zDbName, String zTableName, String zColumnName
+ ){
+ org.sqlite.jni.capi.OutputPointer.String pzDataType
+ = new org.sqlite.jni.capi.OutputPointer.String();
+ org.sqlite.jni.capi.OutputPointer.String pzCollSeq
+ = new org.sqlite.jni.capi.OutputPointer.String();
+ org.sqlite.jni.capi.OutputPointer.Bool pNotNull
+ = new org.sqlite.jni.capi.OutputPointer.Bool();
+ org.sqlite.jni.capi.OutputPointer.Bool pPrimaryKey
+ = new org.sqlite.jni.capi.OutputPointer.Bool();
+ org.sqlite.jni.capi.OutputPointer.Bool pAutoinc
+ = new org.sqlite.jni.capi.OutputPointer.Bool();
+ final int rc = CApi.sqlite3_table_column_metadata(
+ thisDb(), zDbName, zTableName, zColumnName,
+ pzDataType, pzCollSeq, pNotNull, pPrimaryKey, pAutoinc
+ );
+ checkRc(rc);
+ TableColumnMetadata rv = new TableColumnMetadata();
+ rv.pzDataType = pzDataType.value;
+ rv.pzCollSeq = pzCollSeq.value;
+ rv.pNotNull = pNotNull.value;
+ rv.pPrimaryKey = pPrimaryKey.value;
+ rv.pAutoinc = pAutoinc.value;
+ return rv;
+ }
+
+ public interface TraceCallback {
+ /**
+ Called by sqlite3 for various tracing operations, as per
+ sqlite3_trace_v2(). Note that this interface elides the 2nd
+ argument to the native trace callback, as that role is better
+ filled by instance-local state.
+
+ <p>These callbacks may throw, in which case their exceptions are
+ converted to C-level error information.
+
+ <p>The 2nd argument to this function, if non-null, will be a an
+ Sqlite or Sqlite.Stmt object, depending on the first argument
+ (see below).
+
+ <p>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:
+
+ <p>- SQLITE_TRACE_STMT: pNative is a Sqlite.Stmt. pX is a String
+ containing the prepared SQL.
+
+ <p>- SQLITE_TRACE_PROFILE: pNative is a sqlite3_stmt. pX is a Long
+ holding an approximate number of nanoseconds the statement took
+ to run.
+
+ <p>- SQLITE_TRACE_ROW: pNative is a sqlite3_stmt. pX is null.
+
+ <p>- SQLITE_TRACE_CLOSE: pNative is a sqlite3. pX is null.
+ */
+ void call(int traceFlag, Object pNative, Object pX);
+ }
+
+ /**
+ Analog to sqlite3_trace_v2(). traceMask must be a mask of the
+ TRACE_... constants. Pass a null callback to remove tracing.
+
+ Throws on error.
+ */
+ public void trace(int traceMask, TraceCallback callback){
+ final Sqlite self = this;
+ final org.sqlite.jni.capi.TraceV2Callback tc =
+ (null==callback) ? null : new org.sqlite.jni.capi.TraceV2Callback(){
+ @SuppressWarnings("unchecked")
+ @Override public int call(int flag, Object pNative, Object pX){
+ switch(flag){
+ case TRACE_ROW:
+ case TRACE_PROFILE:
+ case TRACE_STMT:
+ callback.call(flag, Sqlite.Stmt.fromNative((sqlite3_stmt)pNative), pX);
+ break;
+ case TRACE_CLOSE:
+ callback.call(flag, self, pX);
+ break;
+ }
+ return 0;
+ }
+ };
+ checkRc( CApi.sqlite3_trace_v2(thisDb(), traceMask, tc) );
+ };
+
+ /**
+ Corresponds to the sqlite3_stmt class. Use Sqlite.prepare() to
+ create new instances.
+ */
+ public static final class Stmt implements AutoCloseable {
+ private Sqlite _db = null;
+ private sqlite3_stmt stmt = null;
+
+ /** Only called by the prepare() factory functions. */
+ Stmt(Sqlite db, sqlite3_stmt stmt){
+ this._db = db;
+ this.stmt = stmt;
+ synchronized(nativeToWrapper){
+ nativeToWrapper.put(this.stmt, this);
+ }
+ }
+
+ sqlite3_stmt nativeHandle(){
+ return stmt;
+ }
+
+ /** Maps org.sqlite.jni.capi.sqlite3_stmt to Stmt instances. */
+ private static final java.util.Map<org.sqlite.jni.capi.sqlite3_stmt, Stmt> nativeToWrapper
+ = new java.util.HashMap<>();
+
+ /**
+ Returns the Stmt object associated with the given sqlite3_stmt
+ object, or null if there is no such mapping.
+ */
+ static Stmt fromNative(sqlite3_stmt low){
+ synchronized(nativeToWrapper){
+ return nativeToWrapper.get(low);
+ }
+ }
+
+ /**
+ If this statement is still opened, its low-level handle is
+ returned, else an IllegalArgumentException is thrown.
+ */
+ private sqlite3_stmt thisStmt(){
+ if( null==stmt || 0==stmt.getNativePointer() ){
+ throw new IllegalArgumentException("This Stmt has been finalized.");
+ }
+ return stmt;
+ }
+
+ /** Throws if n is out of range of this statement's result column
+ count. Intended to be used by the columnXyz() methods. */
+ private sqlite3_stmt checkColIndex(int n){
+ if(n<0 || n>=columnCount()){
+ throw new IllegalArgumentException("Column index "+n+" is out of range.");
+ }
+ return thisStmt();
+ }
+
+ /**
+ Corresponds to sqlite3_finalize(), but we cannot override the
+ name finalize() here because this one requires a different
+ signature. It does not throw on error here because "destructors
+ do not throw." If it returns non-0, the object is still
+ finalized, but the result code is an indication that something
+ went wrong in a prior call into the statement's API, as
+ documented for sqlite3_finalize().
+ */
+ public int finalizeStmt(){
+ int rc = 0;
+ if( null!=stmt ){
+ synchronized(nativeToWrapper){
+ nativeToWrapper.remove(this.stmt);
+ }
+ CApi.sqlite3_finalize(stmt);
+ stmt = null;
+ _db = null;
+ }
+ return rc;
+ }
+
+ @Override public void close(){
+ finalizeStmt();
+ }
+
+ /**
+ Throws if rc is any value other than 0, SQLITE_ROW, or
+ SQLITE_DONE, else returns rc. Error state for the exception is
+ extracted from this statement object (if it's opened) or the
+ string form of rc.
+ */
+ private int checkRc(int rc){
+ switch(rc){
+ case 0:
+ case CApi.SQLITE_ROW:
+ case CApi.SQLITE_DONE: return rc;
+ default:
+ if( null==stmt ) throw new SqliteException(rc);
+ else throw new SqliteException(this);
+ }
+ }
+
+ /**
+ Works like sqlite3_step() but returns true for SQLITE_ROW,
+ false for SQLITE_DONE, and throws SqliteException for any other
+ result.
+ */
+ public boolean step(){
+ switch(checkRc(CApi.sqlite3_step(thisStmt()))){
+ case CApi.SQLITE_ROW: return true;
+ case CApi.SQLITE_DONE: return false;
+ default:
+ throw new IllegalStateException(
+ "This \"cannot happen\": all possible result codes were checked already."
+ );
+ }
+ }
+
+ /**
+ Works like sqlite3_step(), returning the same result codes as
+ that function unless throwOnError is true, in which case it
+ will throw an SqliteException for any result codes other than
+ Sqlite.ROW or Sqlite.DONE.
+
+ The utility of this overload over the no-argument one is the
+ ability to handle BUSY and LOCKED errors more easily.
+ */
+ public int step(boolean throwOnError){
+ final int rc = (null==stmt)
+ ? Sqlite.MISUSE
+ : CApi.sqlite3_step(stmt);
+ return throwOnError ? checkRc(rc) : rc;
+ }
+
+ /**
+ Returns the Sqlite which prepared this statement, or null if
+ this statement has been finalized.
+ */
+ public Sqlite getDb(){ return this._db; }
+
+ /**
+ Works like sqlite3_reset() but throws on error.
+ */
+ public void reset(){
+ checkRc(CApi.sqlite3_reset(thisStmt()));
+ }
+
+ public boolean isBusy(){
+ return CApi.sqlite3_stmt_busy(thisStmt());
+ }
+
+ public boolean isReadOnly(){
+ return CApi.sqlite3_stmt_readonly(thisStmt());
+ }
+
+ public String sql(){
+ return CApi.sqlite3_sql(thisStmt());
+ }
+
+ public String expandedSql(){
+ return CApi.sqlite3_expanded_sql(thisStmt());
+ }
+
+ /**
+ Analog to sqlite3_stmt_explain() but throws if op is invalid.
+ */
+ public void explain(int op){
+ checkRc(CApi.sqlite3_stmt_explain(thisStmt(), op));
+ }
+
+ /**
+ Analog to sqlite3_stmt_isexplain().
+ */
+ public int isExplain(){
+ return CApi.sqlite3_stmt_isexplain(thisStmt());
+ }
+
+ /**
+ Analog to sqlite3_normalized_sql(), but throws
+ UnsupportedOperationException if the library was built without
+ the SQLITE_ENABLE_NORMALIZE flag.
+ */
+ public String normalizedSql(){
+ Sqlite.checkSupported(hasNormalizeSql, "SQLITE_ENABLE_NORMALIZE");
+ return CApi.sqlite3_normalized_sql(thisStmt());
+ }
+
+ public void clearBindings(){
+ CApi.sqlite3_clear_bindings( thisStmt() );
+ }
+ public void bindInt(int ndx, int val){
+ checkRc(CApi.sqlite3_bind_int(thisStmt(), ndx, val));
+ }
+ public void bindInt64(int ndx, long val){
+ checkRc(CApi.sqlite3_bind_int64(thisStmt(), ndx, val));
+ }
+ public void bindDouble(int ndx, double val){
+ checkRc(CApi.sqlite3_bind_double(thisStmt(), ndx, val));
+ }
+ public void bindObject(int ndx, Object o){
+ checkRc(CApi.sqlite3_bind_java_object(thisStmt(), ndx, o));
+ }
+ public void bindNull(int ndx){
+ checkRc(CApi.sqlite3_bind_null(thisStmt(), ndx));
+ }
+ public int bindParameterCount(){
+ return CApi.sqlite3_bind_parameter_count(thisStmt());
+ }
+ public int bindParameterIndex(String paramName){
+ return CApi.sqlite3_bind_parameter_index(thisStmt(), paramName);
+ }
+ public String bindParameterName(int ndx){
+ return CApi.sqlite3_bind_parameter_name(thisStmt(), ndx);
+ }
+ public void bindText(int ndx, byte[] utf8){
+ checkRc(CApi.sqlite3_bind_text(thisStmt(), ndx, utf8));
+ }
+ public void bindText(int ndx, String asUtf8){
+ checkRc(CApi.sqlite3_bind_text(thisStmt(), ndx, asUtf8));
+ }
+ public void bindText16(int ndx, byte[] utf16){
+ checkRc(CApi.sqlite3_bind_text16(thisStmt(), ndx, utf16));
+ }
+ public void bindText16(int ndx, String asUtf16){
+ checkRc(CApi.sqlite3_bind_text16(thisStmt(), ndx, asUtf16));
+ }
+ public void bindZeroBlob(int ndx, int n){
+ checkRc(CApi.sqlite3_bind_zeroblob(thisStmt(), ndx, n));
+ }
+ public void bindBlob(int ndx, byte[] bytes){
+ checkRc(CApi.sqlite3_bind_blob(thisStmt(), ndx, bytes));
+ }
+
+ public byte[] columnBlob(int ndx){
+ return CApi.sqlite3_column_blob( checkColIndex(ndx), ndx );
+ }
+ public byte[] columnText(int ndx){
+ return CApi.sqlite3_column_text( checkColIndex(ndx), ndx );
+ }
+ public String columnText16(int ndx){
+ return CApi.sqlite3_column_text16( checkColIndex(ndx), ndx );
+ }
+ public int columnBytes(int ndx){
+ return CApi.sqlite3_column_bytes( checkColIndex(ndx), ndx );
+ }
+ public int columnBytes16(int ndx){
+ return CApi.sqlite3_column_bytes16( checkColIndex(ndx), ndx );
+ }
+ public int columnInt(int ndx){
+ return CApi.sqlite3_column_int( checkColIndex(ndx), ndx );
+ }
+ public long columnInt64(int ndx){
+ return CApi.sqlite3_column_int64( checkColIndex(ndx), ndx );
+ }
+ public double columnDouble(int ndx){
+ return CApi.sqlite3_column_double( checkColIndex(ndx), ndx );
+ }
+ public int columnType(int ndx){
+ return CApi.sqlite3_column_type( checkColIndex(ndx), ndx );
+ }
+ public String columnDeclType(int ndx){
+ return CApi.sqlite3_column_decltype( checkColIndex(ndx), ndx );
+ }
+ /**
+ Analog to sqlite3_column_count() but throws if this statement
+ has been finalized.
+ */
+ public int columnCount(){
+ /* We cannot reliably cache the column count in a class
+ member because an ALTER TABLE from a separate statement
+ can invalidate that count and we have no way, short of
+ installing a COMMIT handler or the like, of knowing when
+ to re-read it. We cannot install such a handler without
+ interfering with a client's ability to do so. */
+ return CApi.sqlite3_column_count(thisStmt());
+ }
+ public int columnDataCount(){
+ return CApi.sqlite3_data_count( thisStmt() );
+ }
+ public Object columnObject(int ndx){
+ return CApi.sqlite3_column_java_object( checkColIndex(ndx), ndx );
+ }
+ public <T> T columnObject(int ndx, Class<T> type){
+ return CApi.sqlite3_column_java_object( checkColIndex(ndx), ndx, type );
+ }
+ public String columnName(int ndx){
+ return CApi.sqlite3_column_name( checkColIndex(ndx), ndx );
+ }
+ public String columnDatabaseName(int ndx){
+ return CApi.sqlite3_column_database_name( checkColIndex(ndx), ndx );
+ }
+ public String columnOriginName(int ndx){
+ return CApi.sqlite3_column_origin_name( checkColIndex(ndx), ndx );
+ }
+ public String columnTableName(int ndx){
+ return CApi.sqlite3_column_table_name( checkColIndex(ndx), ndx );
+ }
+ } /* Stmt class */
+
+ /**
+ Interface for auto-extensions, as per the
+ sqlite3_auto_extension() API.
+
+ Design note: the chicken/egg timing of auto-extension execution
+ requires that this feature be entirely re-implemented in Java
+ because the C-level API has no access to the Sqlite type so
+ cannot pass on an object of that type while the database is being
+ opened. One side effect of this reimplementation is that this
+ class's list of auto-extensions is 100% independent of the
+ C-level list so, e.g., clearAutoExtensions() will have no effect
+ on auto-extensions added via the C-level API and databases opened
+ from that level of API will not be passed to this level's
+ AutoExtension instances.
+ */
+ public interface AutoExtension {
+ public void call(Sqlite db);
+ }
+
+ private static final java.util.Set<AutoExtension> autoExtensions =
+ new java.util.LinkedHashSet<>();
+
+ /**
+ Passes db to all auto-extensions. If any one of them throws,
+ db.close() is called before the exception is propagated.
+ */
+ private static void runAutoExtensions(Sqlite db){
+ AutoExtension list[];
+ synchronized(autoExtensions){
+ /* Avoid that modifications to the AutoExtension list from within
+ auto-extensions affect this execution of this list. */
+ list = autoExtensions.toArray(new AutoExtension[0]);
+ }
+ try {
+ for( AutoExtension ax : list ) ax.call(db);
+ }catch(Exception e){
+ db.close();
+ throw e;
+ }
+ }
+
+ /**
+ Analog to sqlite3_auto_extension(), adds the given object to the
+ list of auto-extensions if it is not already in that list. The
+ given object will be run as part of Sqlite.open(), and passed the
+ being-opened database. If the extension throws then open() will
+ fail.
+
+ This API does not guaranty whether or not manipulations made to
+ the auto-extension list from within auto-extension callbacks will
+ affect the current traversal of the auto-extension list. Whether
+ or not they do is unspecified and subject to change between
+ versions. e.g. if an AutoExtension calls addAutoExtension(),
+ whether or not the new extension will be run on the being-opened
+ database is undefined.
+
+ Note that calling Sqlite.open() from an auto-extension will
+ necessarily result in recursion loop and (eventually) a stack
+ overflow.
+ */
+ public static void addAutoExtension( AutoExtension e ){
+ if( null==e ){
+ throw new IllegalArgumentException("AutoExtension may not be null.");
+ }
+ synchronized(autoExtensions){
+ autoExtensions.add(e);
+ }
+ }
+
+ /**
+ Removes the given object from the auto-extension list if it is in
+ that list, otherwise this has no side-effects beyond briefly
+ locking that list.
+ */
+ public static void removeAutoExtension( AutoExtension e ){
+ synchronized(autoExtensions){
+ autoExtensions.remove(e);
+ }
+ }
+
+ /**
+ Removes all auto-extensions which were added via addAutoExtension().
+ */
+ public static void clearAutoExtensions(){
+ synchronized(autoExtensions){
+ autoExtensions.clear();
+ }
+ }
+
+ /**
+ Encapsulates state related to the sqlite3 backup API. Use
+ Sqlite.initBackup() to create new instances.
+ */
+ public static final class Backup implements AutoCloseable {
+ private sqlite3_backup b = null;
+ private Sqlite dbTo = null;
+ private Sqlite dbFrom = null;
+
+ Backup(Sqlite dbDest, String schemaDest,Sqlite dbSrc, String schemaSrc){
+ this.dbTo = dbDest;
+ this.dbFrom = dbSrc;
+ b = CApi.sqlite3_backup_init(dbDest.nativeHandle(), schemaDest,
+ dbSrc.nativeHandle(), schemaSrc);
+ if(null==b) toss();
+ }
+
+ private void toss(){
+ int rc = CApi.sqlite3_errcode(dbTo.nativeHandle());
+ if(0!=rc) throw new SqliteException(dbTo);
+ rc = CApi.sqlite3_errcode(dbFrom.nativeHandle());
+ if(0!=rc) throw new SqliteException(dbFrom);
+ throw new SqliteException(CApi.SQLITE_ERROR);
+ }
+
+ private sqlite3_backup getNative(){
+ if( null==b ) throw new IllegalStateException("This Backup is already closed.");
+ return b;
+ }
+ /**
+ If this backup is still active, this completes the backup and
+ frees its native resources, otherwise it this is a no-op.
+ */
+ public void finish(){
+ if( null!=b ){
+ CApi.sqlite3_backup_finish(b);
+ b = null;
+ dbTo = null;
+ dbFrom = null;
+ }
+ }
+
+ /** Equivalent to finish(). */
+ @Override public void close(){
+ this.finish();
+ }
+
+ /**
+ Analog to sqlite3_backup_step(). Returns 0 if stepping succeeds
+ or, Sqlite.DONE if the end is reached, Sqlite.BUSY if one of
+ the databases is busy, Sqlite.LOCKED if one of the databases is
+ locked, and throws for any other result code or if this object
+ has been closed. Note that BUSY and LOCKED are not necessarily
+ permanent errors, so do not trigger an exception.
+ */
+ public int step(int pageCount){
+ final int rc = CApi.sqlite3_backup_step(getNative(), pageCount);
+ switch(rc){
+ case 0:
+ case Sqlite.DONE:
+ case Sqlite.BUSY:
+ case Sqlite.LOCKED:
+ return rc;
+ default:
+ toss();
+ return CApi.SQLITE_ERROR/*not reached*/;
+ }
+ }
+
+ /**
+ Analog to sqlite3_backup_pagecount().
+ */
+ public int pageCount(){
+ return CApi.sqlite3_backup_pagecount(getNative());
+ }
+
+ /**
+ Analog to sqlite3_backup_remaining().
+ */
+ public int remaining(){
+ return CApi.sqlite3_backup_remaining(getNative());
+ }
+ }
+
+ /**
+ Analog to sqlite3_backup_init(). If schemaSrc is null, "main" is
+ assumed. Throws if either this db or dbSrc (the source db) are
+ not opened, if either of schemaDest or schemaSrc are null, or if
+ the underlying call to sqlite3_backup_init() fails.
+
+ The returned object must eventually be cleaned up by either
+ arranging for it to be auto-closed (e.g. using
+ try-with-resources) or by calling its finish() method.
+ */
+ public Backup initBackup(String schemaDest, Sqlite dbSrc, String schemaSrc){
+ thisDb();
+ dbSrc.thisDb();
+ if( null==schemaSrc || null==schemaDest ){
+ throw new IllegalArgumentException(
+ "Neither the source nor destination schema name may be null."
+ );
+ }
+ return new Backup(this, schemaDest, dbSrc, schemaSrc);
+ }
+
+
+ /**
+ Callback type for use with createCollation().
+ */
+ public interface Collation {
+ /**
+ Called by the SQLite core to compare inputs. Implementations
+ must compare its two arguments using memcmp(3) semantics.
+
+ Warning: the SQLite core has no mechanism for reporting errors
+ from custom collations and its workflow does not accommodate
+ propagation of exceptions from callbacks. Any exceptions thrown
+ from collations will be silently supressed and sorting results
+ will be unpredictable.
+ */
+ int call(byte[] lhs, byte[] rhs);
+ }
+
+ /**
+ Analog to sqlite3_create_collation().
+
+ Throws if name is null or empty, c is null, or the encoding flag
+ is invalid. The encoding must be one of the UTF8, UTF16, UTF16LE,
+ or UTF16BE constants.
+ */
+ public void createCollation(String name, int encoding, Collation c){
+ thisDb();
+ if( null==name || 0==name.length()){
+ throw new IllegalArgumentException("Collation name may not be null or empty.");
+ }
+ if( null==c ){
+ throw new IllegalArgumentException("Collation may not be null.");
+ }
+ switch(encoding){
+ case UTF8:
+ case UTF16:
+ case UTF16LE:
+ case UTF16BE:
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid Collation encoding.");
+ }
+ checkRc(
+ CApi.sqlite3_create_collation(
+ thisDb(), name, encoding, new org.sqlite.jni.capi.CollationCallback(){
+ @Override public int call(byte[] lhs, byte[] rhs){
+ try{return c.call(lhs, rhs);}
+ catch(Exception e){return 0;}
+ }
+ @Override public void xDestroy(){}
+ }
+ )
+ );
+ }
+
+ /**
+ Callback for use with onCollationNeeded().
+ */
+ public interface CollationNeeded {
+ /**
+ Must behave as documented for the callback for
+ sqlite3_collation_needed().
+
+ Warning: the C API has no mechanism for reporting or
+ propagating errors from this callback, so any exceptions it
+ throws are suppressed.
+ */
+ void call(Sqlite db, int encoding, String collationName);
+ }
+
+ /**
+ Sets up the given object to be called by the SQLite core when it
+ encounters a collation name which it does not know. Pass a null
+ object to disconnect the object from the core. This replaces any
+ existing collation-needed loader, or is a no-op if the given
+ object is already registered. Throws if registering the loader
+ fails.
+ */
+ public void onCollationNeeded( CollationNeeded cn ){
+ org.sqlite.jni.capi.CollationNeededCallback cnc = null;
+ if( null!=cn ){
+ cnc = new org.sqlite.jni.capi.CollationNeededCallback(){
+ @Override public void call(sqlite3 db, int encoding, String collationName){
+ final Sqlite xdb = Sqlite.fromNative(db);
+ if(null!=xdb) cn.call(xdb, encoding, collationName);
+ }
+ };
+ }
+ checkRc( CApi.sqlite3_collation_needed(thisDb(), cnc) );
+ }
+
+ /**
+ Callback for use with busyHandler().
+ */
+ public interface BusyHandler {
+ /**
+ Must function as documented for the C-level
+ sqlite3_busy_handler() callback argument, minus the (void*)
+ argument the C-level function requires.
+
+ If this function throws, it is translated to a database-level
+ error.
+ */
+ int call(int n);
+ }
+
+ /**
+ Analog to sqlite3_busy_timeout().
+ */
+ public void setBusyTimeout(int ms){
+ checkRc(CApi.sqlite3_busy_timeout(thisDb(), ms));
+ }
+
+ /**
+ Analog to sqlite3_busy_handler(). If b is null then any
+ current handler is cleared.
+ */
+ public void setBusyHandler( BusyHandler b ){
+ org.sqlite.jni.capi.BusyHandlerCallback bhc = null;
+ if( null!=b ){
+ bhc = new org.sqlite.jni.capi.BusyHandlerCallback(){
+ @Override public int call(int n){
+ return b.call(n);
+ }
+ };
+ }
+ checkRc( CApi.sqlite3_busy_handler(thisDb(), bhc) );
+ }
+
+ public interface CommitHook {
+ /**
+ Must behave as documented for the C-level sqlite3_commit_hook()
+ callback. If it throws, the exception is translated into
+ a db-level error.
+ */
+ int call();
+ }
+
+ /**
+ A level of indirection to permit setCommitHook() to have similar
+ semantics as the C API, returning the previous hook. The caveat
+ is that if the low-level API is used to install a hook, it will
+ have a different hook type than Sqlite.CommitHook so
+ setCommitHook() will return null instead of that object.
+ */
+ private static class CommitHookProxy
+ implements org.sqlite.jni.capi.CommitHookCallback {
+ final CommitHook commitHook;
+ CommitHookProxy(CommitHook ch){
+ this.commitHook = ch;
+ }
+ @Override public int call(){
+ return commitHook.call();
+ }
+ }
+
+ /**
+ Analog to sqlite3_commit_hook(). Returns the previous hook, if
+ any (else null). Throws if this db is closed.
+
+ Minor caveat: if a commit hook is set on this object's underlying
+ db handle using the lower-level SQLite API, this function may
+ return null when replacing it, despite there being a hook,
+ because it will have a different callback type. So long as the
+ handle is only manipulated via the high-level API, this caveat
+ does not apply.
+ */
+ public CommitHook setCommitHook( CommitHook c ){
+ CommitHookProxy chp = null;
+ if( null!=c ){
+ chp = new CommitHookProxy(c);
+ }
+ final org.sqlite.jni.capi.CommitHookCallback rv =
+ CApi.sqlite3_commit_hook(thisDb(), chp);
+ return (rv instanceof CommitHookProxy)
+ ? ((CommitHookProxy)rv).commitHook
+ : null;
+ }
+
+
+ public interface RollbackHook {
+ /**
+ Must behave as documented for the C-level sqlite3_rollback_hook()
+ callback. If it throws, the exception is translated into
+ a db-level error.
+ */
+ void call();
+ }
+
+ /**
+ A level of indirection to permit setRollbackHook() to have similar
+ semantics as the C API, returning the previous hook. The caveat
+ is that if the low-level API is used to install a hook, it will
+ have a different hook type than Sqlite.RollbackHook so
+ setRollbackHook() will return null instead of that object.
+ */
+ private static class RollbackHookProxy
+ implements org.sqlite.jni.capi.RollbackHookCallback {
+ final RollbackHook rollbackHook;
+ RollbackHookProxy(RollbackHook ch){
+ this.rollbackHook = ch;
+ }
+ @Override public void call(){rollbackHook.call();}
+ }
+
+ /**
+ Analog to sqlite3_rollback_hook(). Returns the previous hook, if
+ any (else null). Throws if this db is closed.
+
+ Minor caveat: if a rollback hook is set on this object's underlying
+ db handle using the lower-level SQLite API, this function may
+ return null when replacing it, despite there being a hook,
+ because it will have a different callback type. So long as the
+ handle is only manipulated via the high-level API, this caveat
+ does not apply.
+ */
+ public RollbackHook setRollbackHook( RollbackHook c ){
+ RollbackHookProxy chp = null;
+ if( null!=c ){
+ chp = new RollbackHookProxy(c);
+ }
+ final org.sqlite.jni.capi.RollbackHookCallback rv =
+ CApi.sqlite3_rollback_hook(thisDb(), chp);
+ return (rv instanceof RollbackHookProxy)
+ ? ((RollbackHookProxy)rv).rollbackHook
+ : null;
+ }
+
+ public interface UpdateHook {
+ /**
+ Must function as described for the C-level sqlite3_update_hook()
+ callback.
+ */
+ void call(int opId, String dbName, String tableName, long rowId);
+ }
+
+ /**
+ A level of indirection to permit setUpdateHook() to have similar
+ semantics as the C API, returning the previous hook. The caveat
+ is that if the low-level API is used to install a hook, it will
+ have a different hook type than Sqlite.UpdateHook so
+ setUpdateHook() will return null instead of that object.
+ */
+ private static class UpdateHookProxy
+ implements org.sqlite.jni.capi.UpdateHookCallback {
+ final UpdateHook updateHook;
+ UpdateHookProxy(UpdateHook ch){
+ this.updateHook = ch;
+ }
+ @Override public void call(int opId, String dbName, String tableName, long rowId){
+ updateHook.call(opId, dbName, tableName, rowId);
+ }
+ }
+
+ /**
+ Analog to sqlite3_update_hook(). Returns the previous hook, if
+ any (else null). Throws if this db is closed.
+
+ Minor caveat: if a update hook is set on this object's underlying
+ db handle using the lower-level SQLite API, this function may
+ return null when replacing it, despite there being a hook,
+ because it will have a different callback type. So long as the
+ handle is only manipulated via the high-level API, this caveat
+ does not apply.
+ */
+ public UpdateHook setUpdateHook( UpdateHook c ){
+ UpdateHookProxy chp = null;
+ if( null!=c ){
+ chp = new UpdateHookProxy(c);
+ }
+ final org.sqlite.jni.capi.UpdateHookCallback rv =
+ CApi.sqlite3_update_hook(thisDb(), chp);
+ return (rv instanceof UpdateHookProxy)
+ ? ((UpdateHookProxy)rv).updateHook
+ : null;
+ }
+
+
+ /**
+ Callback interface for use with setProgressHandler().
+ */
+ public interface ProgressHandler {
+ /**
+ Must behave as documented for the C-level sqlite3_progress_handler()
+ callback. If it throws, the exception is translated into
+ a db-level error.
+ */
+ int call();
+ }
+
+ /**
+ Analog to sqlite3_progress_handler(), sets the current progress
+ handler or clears it if p is null.
+
+ Note that this API, in contrast to setUpdateHook(),
+ setRollbackHook(), and setCommitHook(), cannot return the
+ previous handler. That inconsistency is part of the lower-level C
+ API.
+ */
+ public void setProgressHandler( int n, ProgressHandler p ){
+ org.sqlite.jni.capi.ProgressHandlerCallback phc = null;
+ if( null!=p ){
+ phc = new org.sqlite.jni.capi.ProgressHandlerCallback(){
+ @Override public int call(){ return p.call(); }
+ };
+ }
+ CApi.sqlite3_progress_handler( thisDb(), n, phc );
+ }
+
+
+ /**
+ Callback for use with setAuthorizer().
+ */
+ public interface Authorizer {
+ /**
+ Must function as described for the C-level
+ sqlite3_set_authorizer() callback. If it throws, the error is
+ converted to a db-level error and the exception is suppressed.
+ */
+ int call(int opId, String s1, String s2, String s3, String s4);
+ }
+
+ /**
+ Analog to sqlite3_set_authorizer(), this sets the current
+ authorizer callback, or clears if it passed null.
+ */
+ public void setAuthorizer( Authorizer a ) {
+ org.sqlite.jni.capi.AuthorizerCallback ac = null;
+ if( null!=a ){
+ ac = new org.sqlite.jni.capi.AuthorizerCallback(){
+ @Override public int call(int opId, String s1, String s2, String s3, String s4){
+ return a.call(opId, s1, s2, s3, s4);
+ }
+ };
+ }
+ checkRc( CApi.sqlite3_set_authorizer( thisDb(), ac ) );
+ }
+
+ /**
+ Object type for use with blobOpen()
+ */
+ public final class Blob implements AutoCloseable {
+ private Sqlite db;
+ private sqlite3_blob b;
+ Blob(Sqlite db, sqlite3_blob b){
+ this.db = db;
+ this.b = b;
+ }
+
+ /**
+ If this blob is still opened, its low-level handle is
+ returned, else an IllegalArgumentException is thrown.
+ */
+ private sqlite3_blob thisBlob(){
+ if( null==b || 0==b.getNativePointer() ){
+ throw new IllegalArgumentException("This Blob has been finalized.");
+ }
+ return b;
+ }
+
+ /**
+ Analog to sqlite3_blob_close().
+ */
+ @Override public void close(){
+ if( null!=b ){
+ CApi.sqlite3_blob_close(b);
+ b = null;
+ db = null;
+ }
+ }
+
+ /**
+ Throws if the JVM does not have JNI-level support for
+ ByteBuffer.
+ */
+ private void checkNio(){
+ if( !Sqlite.JNI_SUPPORTS_NIO ){
+ throw new UnsupportedOperationException(
+ "This JVM does not support JNI access to ByteBuffer."
+ );
+ }
+ }
+ /**
+ Analog to sqlite3_blob_reopen() but throws on error.
+ */
+ public void reopen(long newRowId){
+ db.checkRc( CApi.sqlite3_blob_reopen(thisBlob(), newRowId) );
+ }
+
+ /**
+ Analog to sqlite3_blob_write() but throws on error.
+ */
+ public void write( byte[] bytes, int atOffset ){
+ db.checkRc( CApi.sqlite3_blob_write(thisBlob(), bytes, atOffset) );
+ }
+
+ /**
+ Analog to sqlite3_blob_read() but throws on error.
+ */
+ public void read( byte[] dest, int atOffset ){
+ db.checkRc( CApi.sqlite3_blob_read(thisBlob(), dest, atOffset) );
+ }
+
+ /**
+ Analog to sqlite3_blob_bytes().
+ */
+ public int bytes(){
+ return CApi.sqlite3_blob_bytes(thisBlob());
+ }
+ }
+
+ /**
+ Analog to sqlite3_blob_open(). Returns a Blob object for the
+ given database, table, column, and rowid. The blob is opened for
+ read-write mode if writeable is true, else it is read-only.
+
+ The returned object must eventually be freed, before this
+ database is closed, by either arranging for it to be auto-closed
+ or calling its close() method.
+
+ Throws on error.
+ */
+ public Blob blobOpen(String dbName, String tableName, String columnName,
+ long iRow, boolean writeable){
+ final OutputPointer.sqlite3_blob out = new OutputPointer.sqlite3_blob();
+ checkRc(
+ CApi.sqlite3_blob_open(thisDb(), dbName, tableName, columnName,
+ iRow, writeable ? 1 : 0, out)
+ );
+ return new Blob(this, out.take());
+ }
+
+ /**
+ Callback for use with libConfigLog().
+ */
+ public interface ConfigLog {
+ /**
+ Must function as described for a C-level callback for
+ sqlite3_config()'s SQLITE_CONFIG_LOG callback, with the slight
+ signature change. Any exceptions thrown from this callback are
+ necessarily suppressed.
+ */
+ void call(int errCode, String msg);
+ }
+
+ /**
+ Analog to sqlite3_config() with the SQLITE_CONFIG_LOG option,
+ this sets or (if log is null) clears the current logger.
+ */
+ public static void libConfigLog(ConfigLog log){
+ final org.sqlite.jni.capi.ConfigLogCallback l =
+ null==log
+ ? null
+ : new org.sqlite.jni.capi.ConfigLogCallback() {
+ @Override public void call(int errCode, String msg){
+ log.call(errCode, msg);
+ }
+ };
+ checkRcStatic(CApi.sqlite3_config(l));
+ }
+
+ /**
+ Callback for use with libConfigSqlLog().
+ */
+ public interface ConfigSqlLog {
+ /**
+ Must function as described for a C-level callback for
+ sqlite3_config()'s SQLITE_CONFIG_SQLLOG callback, with the
+ slight signature change. Any exceptions thrown from this
+ callback are necessarily suppressed.
+ */
+ void call(Sqlite db, String msg, int msgType);
+ }
+
+ /**
+ Analog to sqlite3_config() with the SQLITE_CONFIG_SQLLOG option,
+ this sets or (if log is null) clears the current logger.
+
+ If SQLite is built without SQLITE_ENABLE_SQLLOG defined then this
+ will throw an UnsupportedOperationException.
+ */
+ public static void libConfigSqlLog(ConfigSqlLog log){
+ Sqlite.checkSupported(hasNormalizeSql, "SQLITE_ENABLE_SQLLOG");
+ final org.sqlite.jni.capi.ConfigSqlLogCallback l =
+ null==log
+ ? null
+ : new org.sqlite.jni.capi.ConfigSqlLogCallback() {
+ @Override public void call(sqlite3 db, String msg, int msgType){
+ try{
+ log.call(fromNative(db), msg, msgType);
+ }catch(Exception e){
+ /* Suppressed */
+ }
+ }
+ };
+ checkRcStatic(CApi.sqlite3_config(l));
+ }
+
+ /**
+ Analog to the C-level sqlite3_config() with one of the
+ SQLITE_CONFIG_... constants defined as CONFIG_... in this
+ class. Throws on error, including passing of an unknown option or
+ if a specified option is not supported by the underlying build of
+ the SQLite library.
+ */
+ public static void libConfigOp( int op ){
+ checkRcStatic(CApi.sqlite3_config(op));
+ }
+
+}
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java b/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java
new file mode 100644
index 0000000..9b4440f
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java
@@ -0,0 +1,85 @@
+/*
+** 2023-10-09
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the wrapper1 interface for sqlite3.
+*/
+package org.sqlite.jni.wrapper1;
+import org.sqlite.jni.capi.CApi;
+import org.sqlite.jni.capi.sqlite3;
+
+/**
+ A wrapper for communicating C-level (sqlite3*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java
+ and C via JNI.
+*/
+public final class SqliteException extends java.lang.RuntimeException {
+ private int errCode = CApi.SQLITE_ERROR;
+ private int xerrCode = CApi.SQLITE_ERROR;
+ private int errOffset = -1;
+ private int sysErrno = 0;
+
+ /**
+ Records the given error string and uses SQLITE_ERROR for both the
+ error code and extended error code.
+ */
+ public SqliteException(String msg){
+ super(msg);
+ }
+
+ /**
+ Uses sqlite3_errstr(sqlite3ResultCode) for the error string and
+ sets both the error code and extended error code to the given
+ value. This approach includes no database-level information and
+ systemErrno() will be 0, so is intended only for use with sqlite3
+ APIs for which a result code is not an error but which the
+ higher-level wrapper should treat as one.
+ */
+ public SqliteException(int sqlite3ResultCode){
+ super(CApi.sqlite3_errstr(sqlite3ResultCode));
+ errCode = xerrCode = sqlite3ResultCode;
+ }
+
+ /**
+ Records the current error state of db (which must not be null and
+ must refer to an opened db object). Note that this does not close
+ the db.
+
+ Design note: closing the db on error is really only useful during
+ a failed db-open operation, and the place(s) where that can
+ happen are inside this library, not client-level code.
+ */
+ SqliteException(sqlite3 db){
+ super(CApi.sqlite3_errmsg(db));
+ errCode = CApi.sqlite3_errcode(db);
+ xerrCode = CApi.sqlite3_extended_errcode(db);
+ errOffset = CApi.sqlite3_error_offset(db);
+ sysErrno = CApi.sqlite3_system_errno(db);
+ }
+
+ /**
+ Records the current error state of db (which must not be null and
+ must refer to an open database).
+ */
+ public SqliteException(Sqlite db){
+ this(db.nativeHandle());
+ }
+
+ public SqliteException(Sqlite.Stmt stmt){
+ this(stmt.getDb());
+ }
+
+ public int errcode(){ return errCode; }
+ public int extendedErrcode(){ return xerrCode; }
+ public int errorOffset(){ return errOffset; }
+ public int systemErrno(){ return sysErrno; }
+
+}
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java b/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java
new file mode 100644
index 0000000..5ac4132
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java
@@ -0,0 +1,1213 @@
+/*
+** 2023-10-09
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains a set of tests for the sqlite3 JNI bindings.
+*/
+package org.sqlite.jni.wrapper1;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import org.sqlite.jni.capi.CApi;
+
+/**
+ An annotation for Tester2 tests which we do not want to run in
+ reflection-driven test mode because either they are not suitable
+ for multi-threaded threaded mode or we have to control their execution
+ order.
+*/
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+@interface ManualTest{}
+/**
+ Annotation for Tester2 tests which mark those which must be skipped
+ in multi-threaded mode.
+*/
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+@interface SingleThreadOnly{}
+
+public class Tester2 implements Runnable {
+ //! True when running in multi-threaded mode.
+ private static boolean mtMode = false;
+ //! True to sleep briefly between tests.
+ private static boolean takeNaps = false;
+ //! True to shuffle the order of the tests.
+ private static boolean shuffle = false;
+ //! True to dump the list of to-run tests to stdout.
+ private static int listRunTests = 0;
+ //! True to squelch all out() and outln() output.
+ private static boolean quietMode = false;
+ //! Total number of runTests() calls.
+ private static int nTestRuns = 0;
+ //! List of test*() methods to run.
+ private static List<java.lang.reflect.Method> testMethods = null;
+ //! List of exceptions collected by run()
+ private static List<Exception> listErrors = new ArrayList<>();
+ private static final class Metrics {
+ //! Number of times createNewDb() (or equivalent) is invoked.
+ volatile int dbOpen = 0;
+ }
+
+ //! Instance ID.
+ private Integer tId;
+
+ Tester2(Integer id){
+ tId = id;
+ }
+
+ static final Metrics metrics = new Metrics();
+
+ public static synchronized void outln(){
+ if( !quietMode ){
+ System.out.println("");
+ }
+ }
+
+ public static synchronized void outPrefix(){
+ if( !quietMode ){
+ System.out.print(Thread.currentThread().getName()+": ");
+ }
+ }
+
+ public static synchronized void outln(Object val){
+ if( !quietMode ){
+ outPrefix();
+ System.out.println(val);
+ }
+ }
+
+ public static synchronized void out(Object val){
+ if( !quietMode ){
+ System.out.print(val);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static synchronized void out(Object... vals){
+ if( !quietMode ){
+ outPrefix();
+ for(Object v : vals) out(v);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static synchronized void outln(Object... vals){
+ if( !quietMode ){
+ out(vals); out("\n");
+ }
+ }
+
+ static volatile int affirmCount = 0;
+ public static synchronized int affirm(Boolean v, String comment){
+ ++affirmCount;
+ if( false ) assert( v /* prefer assert over exception if it's enabled because
+ the JNI layer sometimes has to suppress exceptions,
+ so they might be squelched on their way back to the
+ top. */);
+ if( !v ) throw new RuntimeException(comment);
+ return affirmCount;
+ }
+
+ public static void affirm(Boolean v){
+ affirm(v, "Affirmation failed.");
+ }
+
+
+ public static void execSql(Sqlite db, String sql[]){
+ execSql(db, String.join("", sql));
+ }
+
+ /**
+ Executes all SQL statements in the given string. If throwOnError
+ is true then it will throw for any prepare/step errors, else it
+ will return the corresponding non-0 result code.
+ */
+ public static int execSql(Sqlite dbw, boolean throwOnError, String sql){
+ final ValueHolder<Integer> rv = new ValueHolder<>(0);
+ final Sqlite.PrepareMulti pm = new Sqlite.PrepareMulti(){
+ @Override public void call(Sqlite.Stmt stmt){
+ try{
+ while( Sqlite.ROW == (rv.value = stmt.step(throwOnError)) ){}
+ }
+ finally{ stmt.finalizeStmt(); }
+ }
+ };
+ try {
+ dbw.prepareMulti(sql, pm);
+ }catch(SqliteException se){
+ if( throwOnError ){
+ throw se;
+ }else{
+ /* This error (likely) happened in the prepare() phase and we
+ need to preempt it. */
+ rv.value = se.errcode();
+ }
+ }
+ return (rv.value==Sqlite.DONE) ? 0 : rv.value;
+ }
+
+ static void execSql(Sqlite db, String sql){
+ execSql(db, true, sql);
+ }
+
+ @SingleThreadOnly /* because it's thread-agnostic */
+ private void test1(){
+ affirm(Sqlite.libVersionNumber() == CApi.SQLITE_VERSION_NUMBER);
+ }
+
+ private void nap() throws InterruptedException {
+ if( takeNaps ){
+ Thread.sleep(java.util.concurrent.ThreadLocalRandom.current().nextInt(3, 17), 0);
+ }
+ }
+
+ Sqlite openDb(String name){
+ final Sqlite db = Sqlite.open(name, Sqlite.OPEN_READWRITE|
+ Sqlite.OPEN_CREATE|
+ Sqlite.OPEN_EXRESCODE);
+ ++metrics.dbOpen;
+ return db;
+ }
+
+ Sqlite openDb(){ return openDb(":memory:"); }
+
+ void testOpenDb1(){
+ Sqlite db = openDb();
+ affirm( 0!=db.nativeHandle().getNativePointer() );
+ affirm( "main".equals( db.dbName(0) ) );
+ db.setMainDbName("foo");
+ affirm( "foo".equals( db.dbName(0) ) );
+ affirm( db.dbConfig(Sqlite.DBCONFIG_DEFENSIVE, true)
+ /* The underlying function has different mangled names in jdk8
+ vs jdk19, and this call is here to ensure that the build
+ fails if it cannot find both names. */ );
+ affirm( !db.dbConfig(Sqlite.DBCONFIG_DEFENSIVE, false) );
+ SqliteException ex = null;
+ try{ db.dbConfig(0, false); }
+ catch(SqliteException e){ ex = e; }
+ affirm( null!=ex );
+ ex = null;
+ db.close();
+ affirm( null==db.nativeHandle() );
+
+ try{ db = openDb("/no/such/dir/.../probably"); }
+ catch(SqliteException e){ ex = e; }
+ affirm( ex!=null );
+ affirm( ex.errcode() != 0 );
+ affirm( ex.extendedErrcode() != 0 );
+ affirm( ex.errorOffset() < 0 );
+ // there's no reliable way to predict what ex.systemErrno() might be
+ }
+
+ void testPrepare1(){
+ try (Sqlite db = openDb()) {
+ Sqlite.Stmt stmt = db.prepare("SELECT ?1");
+ Exception e = null;
+ affirm( null!=stmt.nativeHandle() );
+ affirm( db == stmt.getDb() );
+ affirm( 1==stmt.bindParameterCount() );
+ affirm( "?1".equals(stmt.bindParameterName(1)) );
+ affirm( null==stmt.bindParameterName(2) );
+ stmt.bindInt64(1, 1);
+ stmt.bindDouble(1, 1.1);
+ stmt.bindObject(1, db);
+ stmt.bindNull(1);
+ stmt.bindText(1, new byte[] {32,32,32});
+ stmt.bindText(1, "123");
+ stmt.bindText16(1, "123".getBytes(StandardCharsets.UTF_16));
+ stmt.bindText16(1, "123");
+ stmt.bindZeroBlob(1, 8);
+ stmt.bindBlob(1, new byte[] {1,2,3,4});
+ stmt.bindInt(1, 17);
+ try{ stmt.bindInt(2,1); }
+ catch(Exception ex){ e = ex; }
+ affirm( null!=e );
+ e = null;
+ affirm( stmt.step() );
+ try{ stmt.columnInt(1); }
+ catch(Exception ex){ e = ex; }
+ affirm( null!=e );
+ e = null;
+ affirm( 17 == stmt.columnInt(0) );
+ affirm( 17L == stmt.columnInt64(0) );
+ affirm( 17.0 == stmt.columnDouble(0) );
+ affirm( "17".equals(stmt.columnText16(0)) );
+ affirm( !stmt.step() );
+ stmt.reset();
+ affirm( Sqlite.ROW==stmt.step(false) );
+ affirm( !stmt.step() );
+ affirm( 0 == stmt.finalizeStmt() );
+ affirm( null==stmt.nativeHandle() );
+
+ stmt = db.prepare("SELECT ?");
+ stmt.bindObject(1, db);
+ affirm( Sqlite.ROW == stmt.step(false) );
+ affirm( db==stmt.columnObject(0) );
+ affirm( db==stmt.columnObject(0, Sqlite.class ) );
+ affirm( null==stmt.columnObject(0, Sqlite.Stmt.class ) );
+ affirm( 0==stmt.finalizeStmt() )
+ /* getting a non-0 out of sqlite3_finalize() is tricky */;
+ affirm( null==stmt.nativeHandle() );
+ }
+ }
+
+ void testUdfScalar(){
+ final ValueHolder<Integer> xDestroyCalled = new ValueHolder<>(0);
+ try (Sqlite db = openDb()) {
+ execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)");
+ final ValueHolder<Integer> vh = new ValueHolder<>(0);
+ final ScalarFunction f = new ScalarFunction(){
+ public void xFunc(SqlFunction.Arguments args){
+ affirm( db == args.getDb() );
+ for( SqlFunction.Arguments.Arg arg : args ){
+ vh.value += arg.getInt();
+ }
+ args.resultInt(vh.value);
+ }
+ public void xDestroy(){
+ ++xDestroyCalled.value;
+ }
+ };
+ db.createFunction("myfunc", -1, f);
+ Sqlite.Stmt q = db.prepare("select myfunc(1,2,3)");
+ affirm( q.step() );
+ affirm( 6 == vh.value );
+ affirm( 6 == q.columnInt(0) );
+ q.finalizeStmt();
+ affirm( 0 == xDestroyCalled.value );
+ vh.value = 0;
+ q = db.prepare("select myfunc(-1,-2,-3)");
+ affirm( q.step() );
+ affirm( -6 == vh.value );
+ affirm( -6 == q.columnInt(0) );
+ affirm( 0 == xDestroyCalled.value );
+ q.finalizeStmt();
+ }
+ affirm( 1 == xDestroyCalled.value );
+ }
+
+ void testUdfAggregate(){
+ final ValueHolder<Integer> xDestroyCalled = new ValueHolder<>(0);
+ Sqlite.Stmt q = null;
+ try (Sqlite db = openDb()) {
+ execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)");
+ final AggregateFunction f = new AggregateFunction<Integer>(){
+ public void xStep(SqlFunction.Arguments args){
+ final ValueHolder<Integer> agg = this.getAggregateState(args, 0);
+ for( SqlFunction.Arguments.Arg arg : args ){
+ agg.value += arg.getInt();
+ }
+ }
+ public void xFinal(SqlFunction.Arguments args){
+ final Integer v = this.takeAggregateState(args);
+ if( null==v ) args.resultNull();
+ else args.resultInt(v);
+ }
+ public void xDestroy(){
+ ++xDestroyCalled.value;
+ }
+ };
+ db.createFunction("summer", 1, f);
+ q = db.prepare(
+ "with cte(v) as ("+
+ "select 3 union all select 5 union all select 7"+
+ ") select summer(v), summer(v+1) from cte"
+ /* ------------------^^^^^^^^^^^ ensures that we're handling
+ sqlite3_aggregate_context() properly. */
+ );
+ affirm( q.step() );
+ affirm( 15==q.columnInt(0) );
+ q.finalizeStmt();
+ q = null;
+ affirm( 0 == xDestroyCalled.value );
+ db.createFunction("summerN", -1, f);
+
+ q = db.prepare("select summerN(1,8,9), summerN(2,3,4)");
+ affirm( q.step() );
+ affirm( 18==q.columnInt(0) );
+ affirm( 9==q.columnInt(1) );
+ q.finalizeStmt();
+ q = null;
+
+ }/*db*/
+ finally{
+ if( null!=q ) q.finalizeStmt();
+ }
+ affirm( 2 == xDestroyCalled.value
+ /* because we've bound the same instance twice */ );
+ }
+
+ private void testUdfWindow(){
+ final Sqlite db = openDb();
+ /* Example window function, table, and results taken from:
+ https://sqlite.org/windowfunctions.html#udfwinfunc */
+ final WindowFunction func = new WindowFunction<Integer>(){
+ //! Impl of xStep() and xInverse()
+ private void xStepInverse(SqlFunction.Arguments args, int v){
+ this.getAggregateState(args,0).value += v;
+ }
+ @Override public void xStep(SqlFunction.Arguments args){
+ this.xStepInverse(args, args.getInt(0));
+ }
+ @Override public void xInverse(SqlFunction.Arguments args){
+ this.xStepInverse(args, -args.getInt(0));
+ }
+ //! Impl of xFinal() and xValue()
+ private void xFinalValue(SqlFunction.Arguments args, Integer v){
+ if(null == v) args.resultNull();
+ else args.resultInt(v);
+ }
+ @Override public void xFinal(SqlFunction.Arguments args){
+ xFinalValue(args, this.takeAggregateState(args));
+ affirm( null == this.getAggregateState(args,null).value );
+ }
+ @Override public void xValue(SqlFunction.Arguments args){
+ xFinalValue(args, this.getAggregateState(args,null).value);
+ }
+ };
+ db.createFunction("winsumint", 1, func);
+ execSql(db, new String[] {
+ "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES",
+ "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)"
+ });
+ final Sqlite.Stmt stmt = db.prepare(
+ "SELECT x, winsumint(y) OVER ("+
+ "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING"+
+ ") AS sum_y "+
+ "FROM twin ORDER BY x;"
+ );
+ int n = 0;
+ while( stmt.step() ){
+ final String s = stmt.columnText16(0);
+ final int i = stmt.columnInt(1);
+ switch(++n){
+ case 1: affirm( "a".equals(s) && 9==i ); break;
+ case 2: affirm( "b".equals(s) && 12==i ); break;
+ case 3: affirm( "c".equals(s) && 16==i ); break;
+ case 4: affirm( "d".equals(s) && 12==i ); break;
+ case 5: affirm( "e".equals(s) && 9==i ); break;
+ default: affirm( false /* cannot happen */ );
+ }
+ }
+ stmt.close();
+ affirm( 5 == n );
+ db.close();
+ }
+
+ private void testKeyword(){
+ final int n = Sqlite.keywordCount();
+ affirm( n>0 );
+ affirm( !Sqlite.keywordCheck("_nope_") );
+ affirm( Sqlite.keywordCheck("seLect") );
+ affirm( null!=Sqlite.keywordName(0) );
+ affirm( null!=Sqlite.keywordName(n-1) );
+ affirm( null==Sqlite.keywordName(n) );
+ }
+
+
+ private void testExplain(){
+ final Sqlite db = openDb();
+ Sqlite.Stmt q = db.prepare("SELECT 1");
+ affirm( 0 == q.isExplain() );
+ q.explain(0);
+ affirm( 0 == q.isExplain() );
+ q.explain(1);
+ affirm( 1 == q.isExplain() );
+ q.explain(2);
+ affirm( 2 == q.isExplain() );
+ Exception ex = null;
+ try{
+ q.explain(-1);
+ }catch(Exception e){
+ ex = e;
+ }
+ affirm( ex instanceof SqliteException );
+ q.finalizeStmt();
+ db.close();
+ }
+
+
+ private void testTrace(){
+ final Sqlite db = openDb();
+ final ValueHolder<Integer> counter = new ValueHolder<>(0);
+ /* Ensure that characters outside of the UTF BMP survive the trip
+ from Java to sqlite3 and back to Java. (At no small efficiency
+ penalty.) */
+ final String nonBmpChar = "😃";
+ db.trace(
+ Sqlite.TRACE_ALL,
+ new Sqlite.TraceCallback(){
+ @Override public void call(int traceFlag, Object pNative, Object x){
+ ++counter.value;
+ //outln("TRACE "+traceFlag+" pNative = "+pNative.getClass().getName());
+ switch(traceFlag){
+ case Sqlite.TRACE_STMT:
+ affirm(pNative instanceof Sqlite.Stmt);
+ //outln("TRACE_STMT sql = "+x);
+ affirm(x instanceof String);
+ affirm( ((String)x).indexOf(nonBmpChar) > 0 );
+ break;
+ case Sqlite.TRACE_PROFILE:
+ affirm(pNative instanceof Sqlite.Stmt);
+ affirm(x instanceof Long);
+ //outln("TRACE_PROFILE time = "+x);
+ break;
+ case Sqlite.TRACE_ROW:
+ affirm(pNative instanceof Sqlite.Stmt);
+ affirm(null == x);
+ //outln("TRACE_ROW = "+sqlite3_column_text16((sqlite3_stmt)pNative, 0));
+ break;
+ case Sqlite.TRACE_CLOSE:
+ affirm(pNative instanceof Sqlite);
+ affirm(null == x);
+ break;
+ default:
+ affirm(false /*cannot happen*/);
+ break;
+ }
+ }
+ });
+ execSql(db, "SELECT coalesce(null,null,'"+nonBmpChar+"'); "+
+ "SELECT 'w"+nonBmpChar+"orld'");
+ affirm( 6 == counter.value );
+ db.close();
+ affirm( 7 == counter.value );
+ }
+
+ private void testStatus(){
+ final Sqlite db = openDb();
+ execSql(db, "create table t(a); insert into t values(1),(2),(3)");
+
+ Sqlite.Status s = Sqlite.libStatus(Sqlite.STATUS_MEMORY_USED, false);
+ affirm( s.current > 0 );
+ affirm( s.peak >= s.current );
+
+ s = db.status(Sqlite.DBSTATUS_SCHEMA_USED, false);
+ affirm( s.current > 0 );
+ affirm( s.peak == 0 /* always 0 for SCHEMA_USED */ );
+
+ db.close();
+ }
+
+ @SingleThreadOnly /* because multiple threads legitimately make these
+ results unpredictable */
+ private synchronized void testAutoExtension(){
+ final ValueHolder<Integer> val = new ValueHolder<>(0);
+ final ValueHolder<String> toss = new ValueHolder<>(null);
+ final Sqlite.AutoExtension ax = new Sqlite.AutoExtension(){
+ @Override public void call(Sqlite db){
+ ++val.value;
+ if( null!=toss.value ){
+ throw new RuntimeException(toss.value);
+ }
+ }
+ };
+ Sqlite.addAutoExtension(ax);
+ openDb().close();
+ affirm( 1==val.value );
+ openDb().close();
+ affirm( 2==val.value );
+ Sqlite.clearAutoExtensions();
+ openDb().close();
+ affirm( 2==val.value );
+
+ Sqlite.addAutoExtension( ax );
+ Sqlite.addAutoExtension( ax ); // Must not add a second entry
+ Sqlite.addAutoExtension( ax ); // or a third one
+ openDb().close();
+ affirm( 3==val.value );
+
+ Sqlite db = openDb();
+ affirm( 4==val.value );
+ execSql(db, "ATTACH ':memory:' as foo");
+ affirm( 4==val.value, "ATTACH uses the same connection, not sub-connections." );
+ db.close();
+ db = null;
+
+ Sqlite.removeAutoExtension(ax);
+ openDb().close();
+ affirm( 4==val.value );
+ Sqlite.addAutoExtension(ax);
+ Exception err = null;
+ toss.value = "Throwing from auto_extension.";
+ try{
+ openDb();
+ }catch(Exception e){
+ err = e;
+ }
+ affirm( err!=null );
+ affirm( err.getMessage().indexOf(toss.value)>=0 );
+ toss.value = null;
+
+ val.value = 0;
+ final Sqlite.AutoExtension ax2 = new Sqlite.AutoExtension(){
+ @Override public void call(Sqlite db){
+ ++val.value;
+ }
+ };
+ Sqlite.addAutoExtension(ax2);
+ openDb().close();
+ affirm( 2 == val.value );
+ Sqlite.removeAutoExtension(ax);
+ openDb().close();
+ affirm( 3 == val.value );
+ Sqlite.addAutoExtension(ax);
+ openDb().close();
+ affirm( 5 == val.value );
+ Sqlite.removeAutoExtension(ax2);
+ openDb().close();
+ affirm( 6 == val.value );
+ Sqlite.addAutoExtension(ax2);
+ openDb().close();
+ affirm( 8 == val.value );
+
+ Sqlite.clearAutoExtensions();
+ openDb().close();
+ affirm( 8 == val.value );
+ }
+
+ private void testBackup(){
+ final Sqlite dbDest = openDb();
+
+ try (Sqlite dbSrc = openDb()) {
+ execSql(dbSrc, new String[]{
+ "pragma page_size=512; VACUUM;",
+ "create table t(a);",
+ "insert into t(a) values(1),(2),(3);"
+ });
+ Exception e = null;
+ try {
+ dbSrc.initBackup("main",dbSrc,"main");
+ }catch(Exception x){
+ e = x;
+ }
+ affirm( e instanceof SqliteException );
+ e = null;
+ try (Sqlite.Backup b = dbDest.initBackup("main",dbSrc,"main")) {
+ affirm( null!=b );
+ int rc;
+ while( Sqlite.DONE!=(rc = b.step(1)) ){
+ affirm( 0==rc );
+ }
+ affirm( b.pageCount() > 0 );
+ b.finish();
+ }
+ }
+
+ try (Sqlite.Stmt q = dbDest.prepare("SELECT sum(a) from t")) {
+ q.step();
+ affirm( q.columnInt(0) == 6 );
+ }
+ dbDest.close();
+ }
+
+ private void testCollation(){
+ final Sqlite db = openDb();
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ final Sqlite.Collation myCollation = new Sqlite.Collation() {
+ private String myState =
+ "this is local state. There is much like it, but this is mine.";
+ @Override
+ // Reverse-sorts its inputs...
+ public int call(byte[] lhs, byte[] rhs){
+ int len = lhs.length > rhs.length ? rhs.length : lhs.length;
+ int c = 0, i = 0;
+ for(i = 0; i < len; ++i){
+ c = lhs[i] - rhs[i];
+ if(0 != c) break;
+ }
+ if(0==c){
+ if(i < lhs.length) c = 1;
+ else if(i < rhs.length) c = -1;
+ }
+ return -c;
+ }
+ };
+ final Sqlite.CollationNeeded collLoader = new Sqlite.CollationNeeded(){
+ @Override
+ public void call(Sqlite dbArg, int eTextRep, String collationName){
+ affirm(dbArg == db);
+ db.createCollation("reversi", eTextRep, myCollation);
+ }
+ };
+ db.onCollationNeeded(collLoader);
+ Sqlite.Stmt stmt = db.prepare("SELECT a FROM t ORDER BY a COLLATE reversi");
+ int counter = 0;
+ while( stmt.step() ){
+ final String val = stmt.columnText16(0);
+ ++counter;
+ switch(counter){
+ case 1: affirm("c".equals(val)); break;
+ case 2: affirm("b".equals(val)); break;
+ case 3: affirm("a".equals(val)); break;
+ }
+ }
+ affirm(3 == counter);
+ stmt.finalizeStmt();
+ stmt = db.prepare("SELECT a FROM t ORDER BY a");
+ counter = 0;
+ while( stmt.step() ){
+ final String val = stmt.columnText16(0);
+ ++counter;
+ //outln("Non-REVERSI'd row#"+counter+": "+val);
+ switch(counter){
+ case 3: affirm("c".equals(val)); break;
+ case 2: affirm("b".equals(val)); break;
+ case 1: affirm("a".equals(val)); break;
+ }
+ }
+ affirm(3 == counter);
+ stmt.finalizeStmt();
+ db.onCollationNeeded(null);
+ db.close();
+ }
+
+ @SingleThreadOnly /* because threads inherently break this test */
+ private void testBusy(){
+ final String dbName = "_busy-handler.db";
+ try{
+ Sqlite db1 = openDb(dbName);
+ ++metrics.dbOpen;
+ execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)");
+ Sqlite db2 = openDb(dbName);
+ ++metrics.dbOpen;
+
+ final ValueHolder<Integer> xBusyCalled = new ValueHolder<>(0);
+ Sqlite.BusyHandler handler = new Sqlite.BusyHandler(){
+ @Override public int call(int n){
+ return n > 2 ? 0 : ++xBusyCalled.value;
+ }
+ };
+ db2.setBusyHandler(handler);
+
+ // Force a locked condition...
+ execSql(db1, "BEGIN EXCLUSIVE");
+ int rc = 0;
+ SqliteException ex = null;
+ try{
+ db2.prepare("SELECT * from t");
+ }catch(SqliteException x){
+ ex = x;
+ }
+ affirm( null!=ex );
+ affirm( Sqlite.BUSY == ex.errcode() );
+ affirm( 3 == xBusyCalled.value );
+ db1.close();
+ db2.close();
+ }finally{
+ try{(new java.io.File(dbName)).delete();}
+ catch(Exception e){/* ignore */}
+ }
+ }
+
+ private void testCommitHook(){
+ final Sqlite db = openDb();
+ final ValueHolder<Integer> counter = new ValueHolder<>(0);
+ final ValueHolder<Integer> hookResult = new ValueHolder<>(0);
+ final Sqlite.CommitHook theHook = new Sqlite.CommitHook(){
+ @Override public int call(){
+ ++counter.value;
+ return hookResult.value;
+ }
+ };
+ Sqlite.CommitHook oldHook = db.setCommitHook(theHook);
+ affirm( null == oldHook );
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ affirm( 2 == counter.value );
+ execSql(db, "BEGIN; SELECT 1; SELECT 2; COMMIT;");
+ affirm( 2 == counter.value /* NOT invoked if no changes are made */ );
+ execSql(db, "BEGIN; update t set a='d' where a='c'; COMMIT;");
+ affirm( 3 == counter.value );
+ oldHook = db.setCommitHook(theHook);
+ affirm( theHook == oldHook );
+ execSql(db, "BEGIN; update t set a='e' where a='d'; COMMIT;");
+ affirm( 4 == counter.value );
+ oldHook = db.setCommitHook(null);
+ affirm( theHook == oldHook );
+ execSql(db, "BEGIN; update t set a='f' where a='e'; COMMIT;");
+ affirm( 4 == counter.value );
+ oldHook = db.setCommitHook(null);
+ affirm( null == oldHook );
+ execSql(db, "BEGIN; update t set a='g' where a='f'; COMMIT;");
+ affirm( 4 == counter.value );
+
+ final Sqlite.CommitHook newHook = new Sqlite.CommitHook(){
+ @Override public int call(){return 0;}
+ };
+ oldHook = db.setCommitHook(newHook);
+ affirm( null == oldHook );
+ execSql(db, "BEGIN; update t set a='h' where a='g'; COMMIT;");
+ affirm( 4 == counter.value );
+ oldHook = db.setCommitHook(theHook);
+ affirm( newHook == oldHook );
+ execSql(db, "BEGIN; update t set a='i' where a='h'; COMMIT;");
+ affirm( 5 == counter.value );
+ hookResult.value = Sqlite.ERROR;
+ int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;");
+ affirm( Sqlite.CONSTRAINT_COMMITHOOK == rc );
+ affirm( 6 == counter.value );
+ db.close();
+ }
+
+ private void testRollbackHook(){
+ final Sqlite db = openDb();
+ final ValueHolder<Integer> counter = new ValueHolder<>(0);
+ final Sqlite.RollbackHook theHook = new Sqlite.RollbackHook(){
+ @Override public void call(){
+ ++counter.value;
+ }
+ };
+ Sqlite.RollbackHook oldHook = db.setRollbackHook(theHook);
+ affirm( null == oldHook );
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ affirm( 0 == counter.value );
+ execSql(db, false, "BEGIN; SELECT 1; SELECT 2; ROLLBACK;");
+ affirm( 1 == counter.value /* contra to commit hook, is invoked if no changes are made */ );
+
+ final Sqlite.RollbackHook newHook = new Sqlite.RollbackHook(){
+ @Override public void call(){}
+ };
+ oldHook = db.setRollbackHook(newHook);
+ affirm( theHook == oldHook );
+ execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+ affirm( 1 == counter.value );
+ oldHook = db.setRollbackHook(theHook);
+ affirm( newHook == oldHook );
+ execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+ affirm( 2 == counter.value );
+ int rc = execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+ affirm( 0 == rc );
+ affirm( 3 == counter.value );
+ db.close();
+ }
+
+ private void testUpdateHook(){
+ final Sqlite db = openDb();
+ final ValueHolder<Integer> counter = new ValueHolder<>(0);
+ final ValueHolder<Integer> expectedOp = new ValueHolder<>(0);
+ final Sqlite.UpdateHook theHook = new Sqlite.UpdateHook(){
+ @Override
+ public void call(int opId, String dbName, String tableName, long rowId){
+ ++counter.value;
+ if( 0!=expectedOp.value ){
+ affirm( expectedOp.value == opId );
+ }
+ }
+ };
+ Sqlite.UpdateHook oldHook = db.setUpdateHook(theHook);
+ affirm( null == oldHook );
+ expectedOp.value = Sqlite.INSERT;
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ affirm( 3 == counter.value );
+ expectedOp.value = Sqlite.UPDATE;
+ execSql(db, "update t set a='d' where a='c';");
+ affirm( 4 == counter.value );
+ oldHook = db.setUpdateHook(theHook);
+ affirm( theHook == oldHook );
+ expectedOp.value = Sqlite.DELETE;
+ execSql(db, "DELETE FROM t where a='d'");
+ affirm( 5 == counter.value );
+ oldHook = db.setUpdateHook(null);
+ affirm( theHook == oldHook );
+ execSql(db, "update t set a='e' where a='b';");
+ affirm( 5 == counter.value );
+ oldHook = db.setUpdateHook(null);
+ affirm( null == oldHook );
+
+ final Sqlite.UpdateHook newHook = new Sqlite.UpdateHook(){
+ @Override public void call(int opId, String dbName, String tableName, long rowId){
+ }
+ };
+ oldHook = db.setUpdateHook(newHook);
+ affirm( null == oldHook );
+ execSql(db, "update t set a='h' where a='a'");
+ affirm( 5 == counter.value );
+ oldHook = db.setUpdateHook(theHook);
+ affirm( newHook == oldHook );
+ expectedOp.value = Sqlite.UPDATE;
+ execSql(db, "update t set a='i' where a='h'");
+ affirm( 6 == counter.value );
+ db.close();
+ }
+
+ private void testProgress(){
+ final Sqlite db = openDb();
+ final ValueHolder<Integer> counter = new ValueHolder<>(0);
+ db.setProgressHandler(1, new Sqlite.ProgressHandler(){
+ @Override public int call(){
+ ++counter.value;
+ return 0;
+ }
+ });
+ execSql(db, "SELECT 1; SELECT 2;");
+ affirm( counter.value > 0 );
+ int nOld = counter.value;
+ db.setProgressHandler(0, null);
+ execSql(db, "SELECT 1; SELECT 2;");
+ affirm( nOld == counter.value );
+ db.close();
+ }
+
+ private void testAuthorizer(){
+ final Sqlite db = openDb();
+ final ValueHolder<Integer> counter = new ValueHolder<>(0);
+ final ValueHolder<Integer> authRc = new ValueHolder<>(0);
+ final Sqlite.Authorizer auth = new Sqlite.Authorizer(){
+ public int call(int op, String s0, String s1, String s2, String s3){
+ ++counter.value;
+ //outln("xAuth(): "+s0+" "+s1+" "+s2+" "+s3);
+ return authRc.value;
+ }
+ };
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ db.setAuthorizer(auth);
+ execSql(db, "UPDATE t SET a=1");
+ affirm( 1 == counter.value );
+ authRc.value = Sqlite.DENY;
+ int rc = execSql(db, false, "UPDATE t SET a=2");
+ affirm( Sqlite.AUTH==rc );
+ db.setAuthorizer(null);
+ rc = execSql(db, false, "UPDATE t SET a=2");
+ affirm( 0==rc );
+ db.close();
+ }
+
+ private void testBlobOpen(){
+ final Sqlite db = openDb();
+
+ execSql(db, "CREATE TABLE T(a BLOB);"
+ +"INSERT INTO t(rowid,a) VALUES(1, 'def'),(2, 'XYZ');"
+ );
+ Sqlite.Blob b = db.blobOpen("main", "t", "a",
+ db.lastInsertRowId(), true);
+ affirm( 3==b.bytes() );
+ b.write(new byte[] {100, 101, 102 /*"DEF"*/}, 0);
+ b.close();
+ Sqlite.Stmt stmt = db.prepare("SELECT length(a), a FROM t ORDER BY a");
+ affirm( stmt.step() );
+ affirm( 3 == stmt.columnInt(0) );
+ affirm( "def".equals(stmt.columnText16(1)) );
+ stmt.finalizeStmt();
+
+ b = db.blobOpen("main", "t", "a", db.lastInsertRowId(), false);
+ final byte[] tgt = new byte[3];
+ b.read( tgt, 0 );
+ affirm( 100==tgt[0] && 101==tgt[1] && 102==tgt[2], "DEF" );
+ execSql(db,"UPDATE t SET a=zeroblob(10) WHERE rowid=2");
+ b.close();
+ b = db.blobOpen("main", "t", "a", db.lastInsertRowId(), true);
+ byte[] bw = new byte[]{
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
+ };
+ b.write(bw, 0);
+ byte[] br = new byte[10];
+ b.read(br, 0);
+ for( int i = 0; i < br.length; ++i ){
+ affirm(bw[i] == br[i]);
+ }
+ b.close();
+ db.close();
+ }
+
+ void testPrepareMulti(){
+ final ValueHolder<Integer> fCount = new ValueHolder<>(0);
+ final ValueHolder<Integer> mCount = new ValueHolder<>(0);
+ try (Sqlite db = openDb()) {
+ execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)");
+ db.createFunction("counter", -1, new ScalarFunction(){
+ @Override public void xFunc(SqlFunction.Arguments args){
+ ++fCount.value;
+ args.resultNull();
+ }
+ }
+ );
+ final Sqlite.PrepareMulti pm = new Sqlite.PrepareMultiFinalize(
+ new Sqlite.PrepareMulti() {
+ @Override public void call(Sqlite.Stmt q){
+ ++mCount.value;
+ while(q.step()){}
+ }
+ }
+ );
+ final String sql = "select counter(*) from t;"+
+ "select counter(*) from t; /* comment */"+
+ "select counter(*) from t; -- comment\n"
+ ;
+ db.prepareMulti(sql, pm);
+ }
+ affirm( 3 == mCount.value );
+ affirm( 9 == fCount.value );
+ }
+
+
+ /* Copy/paste/rename this to add new tests. */
+ private void _testTemplate(){
+ try (Sqlite db = openDb()) {
+ Sqlite.Stmt stmt = db.prepare("SELECT 1");
+ stmt.finalizeStmt();
+ }
+ }
+
+ private void runTests(boolean fromThread) throws Exception {
+ List<java.lang.reflect.Method> mlist = testMethods;
+ affirm( null!=mlist );
+ if( shuffle ){
+ mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) );
+ java.util.Collections.shuffle(mlist);
+ }
+ if( (!fromThread && listRunTests>0) || listRunTests>1 ){
+ synchronized(this.getClass()){
+ if( !fromThread ){
+ out("Initial test"," list: ");
+ for(java.lang.reflect.Method m : testMethods){
+ out(m.getName()+" ");
+ }
+ outln();
+ outln("(That list excludes some which are hard-coded to run.)");
+ }
+ out("Running"," tests: ");
+ for(java.lang.reflect.Method m : mlist){
+ out(m.getName()+" ");
+ }
+ outln();
+ }
+ }
+ for(java.lang.reflect.Method m : mlist){
+ nap();
+ try{
+ m.invoke(this);
+ }catch(java.lang.reflect.InvocationTargetException e){
+ outln("FAILURE: ",m.getName(),"(): ", e.getCause());
+ throw e;
+ }
+ }
+ synchronized( this.getClass() ){
+ ++nTestRuns;
+ }
+ }
+
+ public void run() {
+ try {
+ runTests(0!=this.tId);
+ }catch(Exception e){
+ synchronized( listErrors ){
+ listErrors.add(e);
+ }
+ }finally{
+ Sqlite.uncacheThread();
+ }
+ }
+
+ /**
+ Runs the basic sqlite3 JNI binding sanity-check suite.
+
+ CLI flags:
+
+ -q|-quiet: disables most test output.
+
+ -t|-thread N: runs the tests in N threads
+ concurrently. Default=1.
+
+ -r|-repeat N: repeats the tests in a loop N times, each one
+ consisting of the -thread value's threads.
+
+ -shuffle: randomizes the order of most of the test functions.
+
+ -naps: sleep small random intervals between tests in order to add
+ some chaos for cross-thread contention.
+
+ -list-tests: outputs the list of tests being run, minus some
+ which are hard-coded. In multi-threaded mode, use this twice to
+ to emit the list run by each thread (which may differ from the initial
+ list, in particular if -shuffle is used).
+
+ -fail: forces an exception to be thrown during the test run. Use
+ with -shuffle to make its appearance unpredictable.
+
+ -v: emit some developer-mode info at the end.
+ */
+ public static void main(String[] args) throws Exception {
+ Integer nThread = 1;
+ boolean doSomethingForDev = false;
+ Integer nRepeat = 1;
+ boolean forceFail = false;
+ boolean sqlLog = false;
+ boolean configLog = false;
+ boolean squelchTestOutput = false;
+ for( int i = 0; i < args.length; ){
+ String arg = args[i++];
+ if(arg.startsWith("-")){
+ arg = arg.replaceFirst("-+","");
+ if(arg.equals("v")){
+ doSomethingForDev = true;
+ //listBoundMethods();
+ }else if(arg.equals("t") || arg.equals("thread")){
+ nThread = Integer.parseInt(args[i++]);
+ }else if(arg.equals("r") || arg.equals("repeat")){
+ nRepeat = Integer.parseInt(args[i++]);
+ }else if(arg.equals("shuffle")){
+ shuffle = true;
+ }else if(arg.equals("list-tests")){
+ ++listRunTests;
+ }else if(arg.equals("fail")){
+ forceFail = true;
+ }else if(arg.equals("sqllog")){
+ sqlLog = true;
+ }else if(arg.equals("configlog")){
+ configLog = true;
+ }else if(arg.equals("naps")){
+ takeNaps = true;
+ }else if(arg.equals("q") || arg.equals("quiet")){
+ squelchTestOutput = true;
+ }else{
+ throw new IllegalArgumentException("Unhandled flag:"+arg);
+ }
+ }
+ }
+
+ if( sqlLog ){
+ if( Sqlite.compileOptionUsed("ENABLE_SQLLOG") ){
+ Sqlite.libConfigSqlLog( new Sqlite.ConfigSqlLog() {
+ @Override public void call(Sqlite db, String msg, int op){
+ switch(op){
+ case 0: outln("Opening db: ",db); break;
+ case 1: outln("SQL ",db,": ",msg); break;
+ case 2: outln("Closing db: ",db); break;
+ }
+ }
+ }
+ );
+ }else{
+ outln("WARNING: -sqllog is not active because library was built ",
+ "without SQLITE_ENABLE_SQLLOG.");
+ }
+ }
+ if( configLog ){
+ Sqlite.libConfigLog( new Sqlite.ConfigLog() {
+ @Override public void call(int code, String msg){
+ outln("ConfigLog: ",Sqlite.errstr(code),": ", msg);
+ };
+ }
+ );
+ }
+
+ quietMode = squelchTestOutput;
+ outln("If you just saw warning messages regarding CallStaticObjectMethod, ",
+ "you are very likely seeing the side effects of a known openjdk8 ",
+ "bug. It is unsightly but does not affect the library.");
+
+ {
+ // Build list of tests to run from the methods named test*().
+ testMethods = new ArrayList<>();
+ int nSkipped = 0;
+ for(final java.lang.reflect.Method m : Tester2.class.getDeclaredMethods()){
+ final String name = m.getName();
+ if( name.equals("testFail") ){
+ if( forceFail ){
+ testMethods.add(m);
+ }
+ }else if( !m.isAnnotationPresent( ManualTest.class ) ){
+ if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){
+ if( 0==nSkipped++ ){
+ out("Skipping tests in multi-thread mode:");
+ }
+ out(" "+name+"()");
+ }else if( name.startsWith("test") ){
+ testMethods.add(m);
+ }
+ }
+ }
+ if( nSkipped>0 ) out("\n");
+ }
+
+ final long timeStart = System.currentTimeMillis();
+ outln("libversion_number: ",
+ Sqlite.libVersionNumber(),"\n",
+ Sqlite.libVersion(),"\n",Sqlite.libSourceId(),"\n",
+ "SQLITE_THREADSAFE=",CApi.sqlite3_threadsafe());
+ final boolean showLoopCount = (nRepeat>1 && nThread>1);
+ if( showLoopCount ){
+ outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each.");
+ }
+ if( takeNaps ) outln("Napping between tests is enabled.");
+ int nLoop = 0;
+ for( int n = 0; n < nRepeat; ++n ){
+ ++nLoop;
+ if( showLoopCount ) out((1==nLoop ? "" : " ")+nLoop);
+ if( nThread<=1 ){
+ new Tester2(0).runTests(false);
+ continue;
+ }
+ Tester2.mtMode = true;
+ final ExecutorService ex = Executors.newFixedThreadPool( nThread );
+ for( int i = 0; i < nThread; ++i ){
+ ex.submit( new Tester2(i), i );
+ }
+ ex.shutdown();
+ try{
+ ex.awaitTermination(nThread*200, java.util.concurrent.TimeUnit.MILLISECONDS);
+ ex.shutdownNow();
+ }catch (InterruptedException ie){
+ ex.shutdownNow();
+ Thread.currentThread().interrupt();
+ }
+ if( !listErrors.isEmpty() ){
+ quietMode = false;
+ outln("TEST ERRORS:");
+ Exception err = null;
+ for( Exception e : listErrors ){
+ e.printStackTrace();
+ if( null==err ) err = e;
+ }
+ if( null!=err ) throw err;
+ }
+ }
+ if( showLoopCount ) outln();
+ quietMode = false;
+
+ final long timeEnd = System.currentTimeMillis();
+ outln("Tests done. Metrics across ",nTestRuns," total iteration(s):");
+ outln("\tAssertions checked: ",affirmCount);
+ outln("\tDatabases opened: ",metrics.dbOpen);
+ if( doSomethingForDev ){
+ CApi.sqlite3_jni_internal_details();
+ }
+ affirm( 0==Sqlite.libReleaseMemory(1) );
+ CApi.sqlite3_shutdown();
+ int nMethods = 0;
+ int nNatives = 0;
+ int nCanonical = 0;
+ final java.lang.reflect.Method[] declaredMethods =
+ CApi.class.getDeclaredMethods();
+ for(java.lang.reflect.Method m : declaredMethods){
+ final int mod = m.getModifiers();
+ if( 0!=(mod & java.lang.reflect.Modifier.STATIC) ){
+ final String name = m.getName();
+ if(name.startsWith("sqlite3_")){
+ ++nMethods;
+ if( 0!=(mod & java.lang.reflect.Modifier.NATIVE) ){
+ ++nNatives;
+ }
+ }
+ }
+ }
+ outln("\tCApi.sqlite3_*() methods: "+
+ nMethods+" total, with "+
+ nNatives+" native, "+
+ (nMethods - nNatives)+" Java"
+ );
+ outln("\tTotal test time = "
+ +(timeEnd - timeStart)+"ms");
+ }
+}
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java b/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java
new file mode 100644
index 0000000..7549bb9
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java
@@ -0,0 +1,25 @@
+/*
+** 2023-10-16
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the ValueHolder utility class.
+*/
+package org.sqlite.jni.wrapper1;
+
+/**
+ A helper class which simply holds a single value. Its primary use
+ is for communicating values out of anonymous callbacks, as doing so
+ requires a "final" reference.
+*/
+public class ValueHolder<T> {
+ public T value;
+ public ValueHolder(){}
+ public ValueHolder(T v){value = v;}
+}
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java
new file mode 100644
index 0000000..a390556
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java
@@ -0,0 +1,42 @@
+/*
+** 2023-10-16
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the wrapper1 interface for sqlite3.
+*/
+package org.sqlite.jni.wrapper1;
+
+/**
+ A SqlFunction implementation for window functions. The T type
+ represents the type of data accumulated by this function while it
+ works. e.g. a SUM()-like UDF might use Integer or Long and a
+ CONCAT()-like UDF might use a StringBuilder or a List<String>.
+*/
+public abstract class WindowFunction<T> extends AggregateFunction<T> {
+
+ /**
+ As for the xInverse() argument of the C API's
+ sqlite3_create_window_function(). If this function throws, the
+ exception is reported via sqlite3_result_error().
+ */
+ public abstract void xInverse(SqlFunction.Arguments args);
+
+ /**
+ As for the xValue() argument of the C API's
+ sqlite3_create_window_function(). If this function throws, it is
+ translated into sqlite3_result_error().
+
+ Note that the passed-in object will not actually contain any
+ arguments for xValue() but will contain the context object needed
+ for setting the call's result or error state.
+ */
+ public abstract void xValue(SqlFunction.Arguments args);
+
+}
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 0000000..4ccbece
--- /dev/null
+++ b/ext/jni/src/tests/000-000-sanity.test
@@ -0,0 +1,53 @@
+/*
+** This is a comment. There are many like it but this one is mine.
+**
+** SCRIPT_MODULE_NAME: sanity-check
+** xMIXED_MODULE_NAME: mixed-module
+** xMODULE_NAME: module-name
+** xREQUIRED_PROPERTIES: small fast reliable
+** xREQUIRED_PROPERTIES: RECURSIVE_TRIGGERS
+** xREQUIRED_PROPERTIES: TEMPSTORE_FILE TEMPSTORE_MEM
+** xREQUIRED_PROPERTIES: AUTOVACUUM INCRVACUUM
+**
+*/
+--print starting up 😃
+--close all
+--oom
+--db 0
+--new my.db
+--null zilch
+--testcase 1.0
+SELECT 1, null;
+--result 1 zilch
+--glob *zil*
+--notglob *ZIL*
+SELECT 1, 2;
+intentional error;
+--run
+--testcase json-1
+SELECT json_array(1,2,3)
+--json [1,2,3]
+--testcase tableresult-1
+ select 1, 'a';
+ select 2, 'b';
+--tableresult
+ # [a-z]
+ 2 b
+--end
+--testcase json-block-1
+ select json_array(1,2,3);
+ select json_object('a',1,'b',2);
+--json-block
+ [1,2,3]
+ {"a":1,"b":2}
+--end
+--testcase col-names-on
+--column-names 1
+ select 1 as 'a', 2 as 'b';
+--result a 1 b 2
+--testcase col-names-off
+--column-names 0
+ select 1 as 'a', 2 as 'b';
+--result 1 2
+--close
+--print reached the end 😃
diff --git a/ext/jni/src/tests/000-001-ignored.test b/ext/jni/src/tests/000-001-ignored.test
new file mode 100644
index 0000000..5af852e
--- /dev/null
+++ b/ext/jni/src/tests/000-001-ignored.test
@@ -0,0 +1,9 @@
+/*
+** This script must be marked as ignored because it contains
+** content which triggers that condition.
+**
+** SCRIPT_MODULE_NAME: ignored
+**
+*/
+
+|
diff --git a/ext/jni/src/tests/900-001-fts.test b/ext/jni/src/tests/900-001-fts.test
new file mode 100644
index 0000000..65285e8
--- /dev/null
+++ b/ext/jni/src/tests/900-001-fts.test
@@ -0,0 +1,12 @@
+/*
+** SCRIPT_MODULE_NAME: fts5-sanity-checks
+** xREQUIRED_PROPERTIES: FTS5
+**
+*/
+
+--testcase 1.0
+CREATE VIRTUAL TABLE email USING fts5(sender, title, body);
+insert into email values('fred','Help!','Dear Sir...');
+insert into email values('barney','Assistance','Dear Madam...');
+select * from email where email match 'assistance';
+--result barney Assistance {Dear Madam...}