diff options
84 files changed, 6822 insertions, 3922 deletions
diff --git a/Makefile.in b/Makefile.in index 6907ceee8..6043c869f 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1291,11 +1291,11 @@ fulltestonly: $(TESTPROGS) fuzztest ./testfixture$(TEXE) $(TOP)/test/full.test # Fuzz testing -fuzztest: fuzzcheck$(TEXE) $(FUZZDATA) sessionfuzz$(TEXE) $(TOP)/test/sessionfuzz-data1.db +fuzztest: fuzzcheck$(TEXE) $(FUZZDATA) sessionfuzz$(TEXE) ./fuzzcheck$(TEXE) $(FUZZDATA) ./sessionfuzz$(TEXE) run $(TOP)/test/sessionfuzz-data1.db -valgrindfuzz: fuzzcheck$(TEXT) $(FUZZDATA) sessionfuzz$(TEXE) $(TOP)/test/sessionfuzz-data1.db +valgrindfuzz: fuzzcheck$(TEXT) $(FUZZDATA) sessionfuzz$(TEXE) valgrind ./fuzzcheck$(TEXE) --cell-size-check --limit-mem 10M $(FUZZDATA) valgrind ./sessionfuzz$(TEXE) run $(TOP)/test/sessionfuzz-data1.db @@ -1317,6 +1317,9 @@ devtest: testfixture$(TEXE) fuzztest testrunner mdevtest: $(TCLSH_CMD) $(TOP)/test/testrunner.tcl mdevtest +sdevtest: + $(TCLSH_CMD) $(TOP)/test/testrunner.tcl sdevtest + # Testing for a release # releasetest: testfixture$(TEXE) diff --git a/Makefile.msc b/Makefile.msc index 95f0eee0d..3179e301c 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -225,6 +225,12 @@ WIN32HEAP = 0 OSTRACE = 0 !ENDIF +# enable address sanitizer using ASAN=1 on the command-line. +# +!IFNDEF ASAN +ASAN = 0 +!ENDIF + # Set this to one of the following values to enable various debugging # features. Each level includes the debugging options from the previous # levels. Currently, the recognized values for DEBUG are: @@ -891,6 +897,13 @@ RCC = $(RCC) -DSQLITE_WIN32_MALLOC_VALIDATE=1 !ENDIF !ENDIF + +# Address sanitizer if ASAN=1 +# +!IF $(ASAN)>0 +TCC = $(TCC) /fsanitize=address +!ENDIF + # <<mark>> # The locations of the Tcl header and library files. Also, the library that # non-stubs enabled programs using Tcl must link against. These variables @@ -41,11 +41,10 @@ email to drh at sqlite dot org. The SQLite source code is in the public domain. See <https://sqlite.org/copyright.html> for details. -Because SQLite is in the public domain, -we cannot accept pull requests, because -if we did accept a pull request, the changes in that pull request would -carry a copyright and the SQLite source code would no longer be fully in -the public domain. +Because SQLite is in the public domain, we do not normally accept pull +requests, because if we did take a pull request, the changes in that +pull request might carry a copyright and the SQLite source code would +then no longer be fully in the public domain. ## Obtaining The SQLite Source Code @@ -116,7 +115,7 @@ script does not work out for you, there is a generic makefile named can copy and edit to suit your needs. Comments on the generic makefile show what changes are needed. -## Using MSVC for Windows systems +## Compiling for Windows Using MSVC On Windows, all applicable build products can be compiled with MSVC. You will also need a working installation of TCL. diff --git a/autoconf/Makefile.msc b/autoconf/Makefile.msc index 261ac61bd..13663d877 100644 --- a/autoconf/Makefile.msc +++ b/autoconf/Makefile.msc @@ -187,6 +187,12 @@ WIN32HEAP = 0 OSTRACE = 0 !ENDIF +# enable address sanitizer using ASAN=1 on the command-line. +# +!IFNDEF ASAN +ASAN = 0 +!ENDIF + # Set this to one of the following values to enable various debugging # features. Each level includes the debugging options from the previous # levels. Currently, the recognized values for DEBUG are: @@ -732,6 +738,13 @@ RCC = $(RCC) -DSQLITE_WIN32_MALLOC_VALIDATE=1 !ENDIF +# Address sanitizer if ASAN=1 +# +!IF $(ASAN)>0 +TCC = $(TCC) /fsanitize=address +!ENDIF + + # Compiler options needed for programs that use the readline() library. # !IFNDEF READLINE_FLAGS diff --git a/autoconf/tea/configure.ac b/autoconf/tea/configure.ac index e26780e2e..093c99d67 100644 --- a/autoconf/tea/configure.ac +++ b/autoconf/tea/configure.ac @@ -19,7 +19,7 @@ dnl to configure the system for the local environment. # so that we create the export library with the dll. #----------------------------------------------------------------------- -AC_INIT([sqlite],[3.42.0]) +AC_INIT([sqlite],[3.43.0]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. diff --git a/doc/compile-for-windows.md b/doc/compile-for-windows.md index b4f8e04c5..0bd39d21f 100644 --- a/doc/compile-for-windows.md +++ b/doc/compile-for-windows.md @@ -25,18 +25,18 @@ canonical source on a new Windows 11 PC, as of 2023-08-16: TCL version 8.6 or later. <ol type="a"> <li>Get the TCL source archive, perhaps from - <https://www.tcl.tk/software/tcltk/download.html>. + [https://www.tcl.tk/software/tcltk/download.html](https://www.tcl.tk/software/tcltk/download.html). <li>Untar or unzip the source archive. CD into the "win/" subfolder of the source tree. <li>Run: `nmake /f makefile.vc release` <li>Run: `nmake /f makefile.vc INSTALLDIR=c:\Tcl install` - <li>CD to c:\\Tcl\\lib. In that subfolder make a copy of the + <li>CD to `c:\Tcl\lib`. In that subfolder make a copy of the "`tcl86t.lib`" file to the alternative name "`tcl86.lib`" (omitting the second 't'). Leave the copy in the same directory as the original. - <li>CD to c:\\Tcl\\bin. Make a copy of the "`tclsh86t.exe`" + <li>CD to `c:\Tcl\bin`. Make a copy of the "`tclsh86t.exe`" file into "`tclsh.exe`" (without the "86t") in the same directory. - <li>Add c:\\Tcl\\bin to your %PATH%. To do this, go to Settings + <li>Add `c:\Tcl\bin` to your %PATH%. To do this, go to Settings and search for "path". Select "edit environment variables for your account" and modify your default PATH accordingly. You will need to close and reopen your command prompts after @@ -60,3 +60,26 @@ canonical source on a new Windows 11 PC, as of 2023-08-16: <li> `nmake /f makefile.msc devtest` <li> `nmake /f makefile.msc releasetest` </ul> + +## 32-bit Builds + +Doing a 32-bit build is just like doing a 64-bit build with the +following minor changes: + + 1. Use the "x86 Native Tools Command Prompt" instead of + "x64 Native Tools Command Prompt". "**x86**" instead of "**x64**". + + 2. Use a different installation directory for TCL. + The recommended directory is `c:\tcl32`. Thus you end up + with two TCL builds: + <ul> + <li> `c:\tcl` ← 64-bit (the default) + <li> `c:\tcl32` ← 32-bit + </ul> + + 3. Ensure that `c:\tcl32\bin` comes before `c:\tcl\bin` on + your PATH environment variable. You can achieve this using + a command like: + <ul> + <li> `set PATH=c:\tcl32\bin;%PATH%` + </ul> diff --git a/doc/lemon.html b/doc/lemon.html index 16aea8784..66665f46f 100644 --- a/doc/lemon.html +++ b/doc/lemon.html @@ -1229,8 +1229,8 @@ first syntax error, of course, if there are no instances of the <p>Lemon was originally written by Richard Hipp sometime in the late 1980s on a Sun4 Workstation using K&R C. -There was a companion LL(1) parser generator program named "Lime", the -source code to which as been lost.</p> +There was a companion LL(1) parser generator program named "Lime". +The Lime source code has been lost.</p> <p>The lemon.c source file was originally many separate files that were compiled together to generate the "lemon" executable. Sometime in the diff --git a/doc/testrunner.md b/doc/testrunner.md new file mode 100644 index 000000000..d828fd76d --- /dev/null +++ b/doc/testrunner.md @@ -0,0 +1,284 @@ + + +# The testrunner.tcl Script + +# 1. Overview + +testrunner.tcl is a Tcl script used to run multiple SQLite tests using +multiple jobs. It supports the following types of tests: + + * Tcl test scripts. + + * Tests run with [make] commands. Specifically, at time of writing, + [make fuzztest], [make mptest], [make sourcetest] and [make threadtest]. + +testrunner.tcl pipes the output of all tests and builds run into log file +**testrunner.log**, created in the cwd directory. Searching this file for +"failed" is a good way to find the output of a failed test. + +testrunner.tcl also populates SQLite database **testrunner.db**. This database +contains details of all tests run, running and to be run. A useful query +might be: + +``` + SELECT * FROM script WHERE state='failed' +``` + +Running the command: + +``` + ./testfixture $(TESTDIR)/testrunner.tcl status +``` + +in the directory containing the testrunner.db database runs various queries +to produce a succinct report on the state of a running testrunner.tcl script. +Running: + +``` + watch ./testfixture $(TESTDIR)/testrunner.tcl status +``` + +in another terminal is a good way to keep an eye on a long running test. + +Sometimes testrunner.tcl uses the [testfixture] binary that it is run with +to run tests (see "Binary Tests" below). Sometimes it builds testfixture and +other binaries in specific configurations to test (see "Source Tests"). + +# 2. Binary Tests + +The commands described in this section all run various combinations of the Tcl +test scripts using the [testfixture] binary used to run the testrunner.tcl +script (i.e. they do not invoke the compiler to build new binaries, or the +[make] command to run tests that are not Tcl scripts). The procedure to run +these tests is therefore: + + 1. Build the "testfixture" (or "testfixture.exe" for windows) binary using + whatever method seems convenient. + + 2. Test the binary built in step 1 by running testrunner.tcl with it, + perhaps with various options. + +The following sub-sections describe the various options that can be +passed to testrunner.tcl to test binary testfixture builds. + +## 2.1. Organization of Tcl Tests + +Tcl tests are stored in files that match the pattern *\*.test*. They are +found in both the $TOP/test/ directory, and in the various sub-directories +of the $TOP/ext/ directory of the source tree. Not all *\*.test* files +contain Tcl tests - a handful are Tcl scripts designed to invoke other +*\*.test* files. + +The **veryquick** set of tests is a subset of all Tcl test scripts in the +source tree. In includes most tests, but excludes some that are very slow. +Almost all fault-injection tests (those that test the response of the library +to OOM or IO errors) are excluded. It is defined in source file +*test/permutations.test*. + +The **full** set of tests includes all Tcl test scripts in the source tree. +To run a "full" test is to run all Tcl test scripts that can be found in the +source tree. + +File *permutations.test* defines various test "permutations". A permutation +consists of: + + * A subset of Tcl test scripts, and + + * Runtime configuration to apply before running each test script + (e.g. enabling auto-vacuum, or disable lookaside). + +Running **all** tests is to run all tests in the full test set, plus a dozen +or so permutations. The specific permutations that are run as part of "all" +are defined in file *testrunner_data.tcl*. + +## 2.2. Commands to Run Tests + +To run the "veryquick" test set, use either of the following: + +``` + ./testfixture $TESTDIR/testrunner.tcl + ./testfixture $TESTDIR/testrunner.tcl veryquick +``` + +To run the "full" test suite: + +``` + ./testfixture $TESTDIR/testrunner.tcl full +``` + +To run the subset of the "full" test suite for which the test file name matches +a specified pattern (e.g. all tests that start with "fts5"), either of: + +``` + ./testfixture $TESTDIR/testrunner.tcl fts5% + ./testfixture $TESTDIR/testrunner.tcl 'fts5*' +``` + +To run "all" tests (full + permutations): + +``` + ./testfixture $TESTDIR/testrunner.tcl all +``` + +<a name=binary_test_failures></a> +## 2.3. Investigating Binary Test Failures + +If a test fails, testrunner.tcl reports name of the Tcl test script and, if +applicable, the name of the permutation, to stdout. This information can also +be retrieved from either *testrunner.log* or *testrunner.db*. + +If there is no permutation, the individual test script may be run with: + +``` + ./testfixture $PATH_TO_SCRIPT +``` + +Or, if the failure occured as part of a permutation: + +``` + ./testfixture $TESTDIR/testrunner.tcl $PERMUTATION $PATH_TO_SCRIPT +``` + +TODO: An example instead of "$PERMUTATION" and $PATH\_TO\_SCRIPT? + +# 3. Source Code Tests + +The commands described in this section invoke the C compiler to build +binaries from the source tree, then use those binaries to run Tcl and +other tests. The advantages of this are that: + + * it is possible to test multiple build configurations with a single + command, and + + * it ensures that tests are always run using binaries created with the + same set of compiler options. + +The testrunner.tcl commands described in this section may be run using +either a *testfixture* (or testfixture.exe) build, or with any other Tcl +shell that supports SQLite 3.31.1 or newer via "package require sqlite3". + +TODO: ./configure + Makefile.msc build systems. + +## Commands to Run SQLite Tests + +The **mdevtest** command is equivalent to running the veryquick tests and +the [make fuzztest] target once for each of two --enable-all builds - one +with debugging enabled and one without: + +``` + tclsh $TESTDIR/testrunner.tcl mdevtest +``` + +In other words, it is equivalent to running: + +``` + $TOP/configure --enable-all --enable-debug + make fuzztest + make testfixture + ./testfixture $TOP/test/testrunner.tcl veryquick + + # Then, after removing files created by the tests above: + $TOP/configure --enable-all OPTS="-O0" + make fuzztest + make testfixture + ./testfixture $TOP/test/testrunner.tcl veryquick +``` + +The **sdevtest** command is identical to the mdevtest command, except that the +second of the two builds is a sanitizer build. Specifically, this means that +OPTS="-fsanitize=address,undefined" is specified instead of OPTS="-O0": + +``` + tclsh $TESTDIR/testrunner.tcl sdevtest +``` + +The **release** command runs lots of tests under lots of builds. It runs +different combinations of builds and tests depending on whether it is run +on Linux, Windows or OSX. Refer to *testrunner\_data.tcl* for the details +of the specific tests run. + +``` + tclsh $TESTDIR/testrunner.tcl release +``` + +## Running ZipVFS Tests + +testrunner.tcl can build a zipvfs-enabled testfixture and use it to run +tests from the Zipvfs project with the following command: + +``` + tclsh $TESTDIR/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS +``` + +This can be combined with any of "mdevtest", "sdevtest" or "release" to +test both SQLite and Zipvfs with a single command: + +``` + tclsh $TESTDIR/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS mdevtest +``` + +## Investigating Source Code Test Failures + +Investigating a test failure that occurs during source code testing is a +two step process: + + 1. Recreating the build configuration in which the test failed, and + + 2. Re-running the actual test. + +To recreate a build configuration, use the testrunner.tcl **script** command +to create a build script. A build script is a bash script on Linux or OSX, or +a dos \*.bat file on windows. For example: + +``` + # Create a script that recreates build configuration "Device-One" on + # Linux or OSX: + tclsh $TESTDIR/testrunner.tcl script Device-One > make.sh + + # Create a script that recreates build configuration "Have-Not" on Windows: + tclsh $TESTDIR/testrunner.tcl script Have-Not > make.bat +``` + +The generated bash or \*.bat file script accepts a single argument - a makefile +target to build. This may be used either to run a [make] command test directly, +or else to build a testfixture (or testfixture.exe) binary with which to +run a Tcl test script, as <a href=#binary_test_failures>described above</a>. + + + +# 4. Controlling CPU Core Utilization + +When running either binary or source code tests, testrunner.tcl reports the +number of jobs it intends to use to stdout. e.g. + +``` + $ ./testfixture $TESTDIR/testrunner.tcl + splitting work across 16 jobs + ... more output ... +``` + +By default, testfixture.tcl attempts to set the number of jobs to the number +of real cores on the machine. This can be overridden using the "--jobs" (or -j) +switch: + +``` + $ ./testfixture $TESTDIR/testrunner.tcl --jobs 8 + splitting work across 8 jobs + ... more output ... +``` + +The number of jobs may also be changed while an instance of testrunner.tcl is +running by exucuting the following command from the directory containing the +testrunner.log and testrunner.db files: + +``` + $ ./testfixture $TESTDIR/testrunner.tcl njob $NEW_NUMBER_OF_JOBS +``` + + + + + + + + diff --git a/ext/fts5/fts5.h b/ext/fts5/fts5.h index 2309460f0..323d73a28 100644 --- a/ext/fts5/fts5.h +++ b/ext/fts5/fts5.h @@ -492,8 +492,8 @@ struct Fts5ExtensionApi { ** as separate queries of the FTS index are required for each synonym. ** ** When using methods (2) or (3), it is important that the tokenizer only -** provide synonyms when tokenizing document text (method (2)) or query -** text (method (3)), not both. Doing so will not cause any errors, but is +** provide synonyms when tokenizing document text (method (3)) or query +** text (method (2)), not both. Doing so will not cause any errors, but is ** inefficient. */ typedef struct Fts5Tokenizer Fts5Tokenizer; diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index dd9be16e8..267489a7e 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -5421,7 +5421,8 @@ static void fts5FlushOneHash(Fts5Index *p){ writer.bFirstRowidInPage = 0; fts5WriteDlidxAppend(p, &writer, iRowid); }else{ - pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid-iPrev); + u64 iRowidDelta = (u64)iRowid - (u64)iPrev; + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowidDelta); } if( p->rc!=SQLITE_OK ) break; assert( pBuf->n<=pBuf->nSpace ); diff --git a/ext/fts5/test/fts5secure6.test b/ext/fts5/test/fts5secure6.test index e561a43f7..5ab17c4f3 100644 --- a/ext/fts5/test/fts5secure6.test +++ b/ext/fts5/test/fts5secure6.test @@ -50,6 +50,25 @@ do_test 1.3 { expr $phc(1)*5 < $phc(2) } {1} +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t1 USING fts5(x); + INSERT INTO t1(t1, rank) VALUES('secure-delete', $sd) +} + +do_execsql_test 2.1 { + BEGIN; + INSERT INTO t1(rowid, x) VALUES(-100000, 'abc def ghi'); + INSERT INTO t1(rowid, x) VALUES(-99999, 'abc def ghi'); + INSERT INTO t1(rowid, x) VALUES(9223372036854775800, 'abc def ghi'); + COMMIT; +} + +do_execsql_test 2.2 { + SELECT rowid FROM t1('def') +} {-100000 -99999 9223372036854775800} finish_test diff --git a/ext/fts5/test/fts5synonym2.test b/ext/fts5/test/fts5synonym2.test index 8bbfb0756..2c2705e34 100644 --- a/ext/fts5/test/fts5synonym2.test +++ b/ext/fts5/test/fts5synonym2.test @@ -122,6 +122,9 @@ foreach {tn expr} { 4.1 "NEAR(one two, 2)" 4.2 "NEAR(one two three, 2)" 4.3 "NEAR(eight nine, 1) OR NEAR(six seven, 1)" + + 5.1 "one + two" + 5.2 "1 + two" } { if {[fts5_expr_ok $expr ss]==0} { do_test 1.$tok.$tn.OMITTED { list } [list] diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 1be4c0443..44a0d7401 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -6,9 +6,10 @@ JAVA_HOME ?= $(HOME)/jdk/current # e.g. /usr/lib/jvm/default-javajava-19-openjdk-amd64 JDK_HOME ?= $(JAVA_HOME) # ^^^ JDK_HOME is not as widely used as JAVA_HOME -bin.javac := $(JDK_HOME)/bin/javac -bin.java := $(JDK_HOME)/bin/java -bin.jar := $(JDK_HOME)/bin/jar +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 @@ -17,18 +18,18 @@ $(MAKEFILE): package.jar := sqlite3-jni.jar -dir.top := ../.. -dir.tool := ../../tool -dir.jni := $(patsubst %/,%,$(dir $(MAKEFILE))) - +dir.top := ../.. +dir.tool := ../../tool +dir.jni := $(patsubst %/,%,$(dir $(MAKEFILE))) dir.src := $(dir.jni)/src dir.src.c := $(dir.src)/c dir.bld := $(dir.jni)/bld dir.bld.c := $(dir.bld) dir.src.jni := $(dir.src)/org/sqlite/jni dir.src.jni.tester := $(dir.src.jni)/tester +mkdir := mkdir -p $(dir.bld.c): - mkdir -p $@ + $(mkdir) $@ classpath := $(dir.src) CLEAN_FILES := $(package.jar) @@ -58,25 +59,39 @@ $(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile # Be explicit about which Java files to compile so that we can work on # in-progress files without requiring them to be in a compilable statae. JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/%,\ - BusyHandler.java \ - Collation.java \ - CollationNeeded.java \ - CommitHook.java \ + annotation/Canonical.java \ + annotation/NotNull.java \ + annotation/Nullable.java \ + annotation/package-info.java \ + AbstractCollationCallback.java \ + AggregateFunction.java \ + AuthorizerCallback.java \ + AutoExtensionCallback.java \ + BusyHandlerCallback.java \ + CollationCallback.java \ + CollationNeededCallback.java \ + CommitHookCallback.java \ + ConfigSqllogCallback.java \ NativePointerHolder.java \ OutputPointer.java \ - ProgressHandler.java \ + PreupdateHookCallback.java \ + ProgressHandlerCallback.java \ ResultCode.java \ - RollbackHook.java \ + RollbackHookCallback.java \ + ScalarFunction.java \ SQLFunction.java \ - sqlite3_context.java \ - sqlite3.java \ SQLite3Jni.java \ - sqlite3_stmt.java \ - sqlite3_value.java \ Tester1.java \ - Tracer.java \ - UpdateHook.java \ + TraceV2Callback.java \ + UpdateHookCallback.java \ ValueHolder.java \ + WindowFunction.java \ + XDestroyCallback.java \ + package-info.java \ + sqlite3.java \ + sqlite3_context.java \ + sqlite3_stmt.java \ + sqlite3_value.java \ ) ifeq (1,$(enable.fts5)) JAVA_FILES.main += $(patsubst %,$(dir.src.jni)/%,\ @@ -152,6 +167,8 @@ $(sqlite3.h): $(MAKE) -C $(dir.top) sqlite3.c $(sqlite3.c): $(sqlite3.h) +opt.threadsafe ?= 1 +opt.oom ?= 0 SQLITE_OPT = \ -DSQLITE_ENABLE_RTREE \ -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ @@ -160,17 +177,17 @@ SQLITE_OPT = \ -DSQLITE_ENABLE_DBSTAT_VTAB \ -DSQLITE_ENABLE_BYTECODE_VTAB \ -DSQLITE_ENABLE_OFFSET_SQL_FUNC \ + -DSQLITE_ENABLE_PREUPDATE_HOOK \ + -DSQLITE_ENABLE_SQLLOG \ -DSQLITE_OMIT_LOAD_EXTENSION \ -DSQLITE_OMIT_DEPRECATED \ -DSQLITE_OMIT_SHARED_CACHE \ - -DSQLITE_THREADSAFE=0 \ + -DSQLITE_THREADSAFE=$(opt.threadsafe) \ -DSQLITE_TEMP_STORE=2 \ -DSQLITE_USE_URI=1 \ - -DSQLITE_C=$(sqlite3.c) -# -DSQLITE_DEBUG -# -DSQLITE_DEBUG is just to work around a -Wall warning -# for a var which gets set in all builds but only read -# via assert(). + -DSQLITE_C=$(sqlite3.c) \ + -DSQLITE_JNI_FATAL_OOM=$(opt.oom) \ + -DSQLITE_DEBUG SQLITE_OPT += -g -DDEBUG -UNDEBUG @@ -216,7 +233,7 @@ sqlite3-jni.dll.cflags = \ # include path for client-level code. ######################################################################## ifeq (1,$(enable.tester)) - sqlite3-jni.dll.cflags += -DS3JNI_ENABLE_SQLTester + sqlite3-jni.dll.cflags += -DSQLITE_JNI_ENABLE_SQLTester endif $(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE) cat $(sqlite3-jni.h.in) > $@ @@ -226,12 +243,23 @@ $(sqlite3-jni.dll): $(dir.bld.c) $(sqlite3-jni.c) $(SQLite3Jni.java) $(MAKEFILE) $(sqlite3-jni.c) -shared -o $@ all: $(sqlite3-jni.dll) -.PHONY: test -test.flags ?= -v -test: $(SQLite3Jni.class) $(sqlite3-jni.dll) - $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ - $(java.flags) -cp $(classpath) \ - org.sqlite.jni.Tester1 $(if $(test.flags),-- $(test.flags),) +.PHONY: test test-one +test.flags ?= +test.main.flags = -ea -Djava.library.path=$(dir.bld.c) \ + $(java.flags) -cp $(classpath) \ + org.sqlite.jni.Tester1 +test.deps := $(SQLite3Jni.class) $(sqlite3-jni.dll) +test-one: $(test.deps) + $(bin.java) $(test.main.flags) $(test.flags) +test-sqllog: $(test.deps) + @echo "Testing with -sqllog..." + $(bin.java) $(test.main.flags) -sqllog +test-mt: $(test.deps) + @echo "Testing in multi-threaded mode:"; + $(bin.java) $(test.main.flags) -t 11 -r 50 -shuffle $(test.flags) + +test: test-one test-mt +tests: test test-sqllog tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test)) tester.flags ?= # --verbose @@ -267,13 +295,26 @@ endif tester-ext: tester-local tester: tester-ext -tests: test tester +tests: tester + +######################################################################## +# Build each SQLITE_THREADMODE variant and run all tests against them. +multitest: clean + $(MAKE) opt.threadsafe=0 opt.oom=1 tests clean + $(MAKE) opt.threadsafe=0 opt.oom=0 tests clean + $(MAKE) opt.threadsafe=1 opt.oom=1 tests clean + $(MAKE) opt.threadsafe=1 opt.oom=0 tests clean + $(MAKE) opt.threadsafe=2 opt.oom=1 tests clean + $(MAKE) opt.threadsafe=2 opt.oom=0 tests clean + + +######################################################################## +# jar bundle... package.jar.in := $(abspath $(dir.src)/jar.in) CLEAN_FILES += $(package.jar.in) $(package.jar.in): $(MAKEFILE) $(CLASS_FILES.main) cd $(dir.src); ls -1 org/sqlite/jni/*.java org/sqlite/jni/*.class > $@ - @ls -la $@ - @echo "To use this jar you will need the -Djava.library.path=DIR/WITH/libsqlite3-jni.so flag." + @echo "To use this jar you will need the -Djava.library.path=DIR/CONTAINING/libsqlite3-jni.so flag." @echo "e.g. java -jar $@ -Djava.library.path=bld" $(package.jar): $(CLASS_FILES) $(MAKEFILE) $(package.jar.in) @@ -282,6 +323,28 @@ $(package.jar): $(CLASS_FILES) $(MAKEFILE) $(package.jar.in) jar: $(package.jar) +######################################################################## +# javadoc... +dir.doc := $(dir.jni)/javadoc +doc.index := $(dir.doc)/index.html +$(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 -exclude org.sqlite.jni.tester + @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.tester)/*.class \ @@ -293,7 +356,7 @@ clean: -rm -f $(CLEAN_FILES) distclean: clean -rm -f $(DISTCLEAN_FILES) - -rm -fr $(dir.bld.c) + -rm -fr $(dir.bld.c) $(dir.doc) ######################################################################## # disttribution bundle rules... @@ -317,6 +380,10 @@ 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)/. diff --git a/ext/jni/README.md b/ext/jni/README.md index cb51a21cd..d655a46b2 100644 --- a/ext/jni/README.md +++ b/ext/jni/README.md @@ -15,7 +15,10 @@ Technical support is available in the forum: > **FOREWARNING:** this subproject is very much in development and subject to any number of changes. Please do not rely on any - information about its API until this disclaimer is removed. + information about its API until this disclaimer is removed. The JNI + bindings released with version 3.43 are a "tech preview" and 3.44 + will be "final," at which point strong backward compatibility + guarantees will apply. Project goals/requirements: @@ -40,13 +43,41 @@ Non-goals: - Creation of high-level OO wrapper APIs. Clients are free to create them off of the C-style API. - -Significant TODOs -======================================================================== - -- The initial beta release with version 3.43 has severe threading - limitations. Namely, two threads cannot call into the JNI-bound API - at once. This limitation will be remove in a subsequent release. +- Support for mixed-mode operation, where client code accesses SQLite + both via the Java-side API and the C API via their own native + code. In such cases, proxy functionalities (primarily callback + handler wrappers of all sorts) may fail because the C-side use of + the SQLite APIs will bypass those proxies. + + +Hello World +----------------------------------------------------------------------- + +```java +import org.sqlite.jni.*; +import static SQLite3Jni.*; + +... + +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 @@ -60,55 +91,97 @@ The canonical builds assumes a Linux-like environment and requires: 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. Exceptions are +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. - -Golden Rule: _Never_ Throw from Callbacks +to use in Java. In all cases, this binding makes every effort to +provide semantics compatible with the C API documentation even if the +interface to those semantics is slightly different. Any cases which +deviate from those semantics (either removing or adding semantics) are +clearly documented. + +Where it makes sense to do so for usability, Java-side overloads are +provided which accept or return data in alternative forms or provide +sensible default argument values. In all such cases they are thin +proxies around the corresponding C APIs and do not introduce new +semantics. + +In some very few cases, Java-specific capabilities have been added in +new APIs, all of which have "_java" somewhere in their names. +Examples include: + +- `sqlite3_result_java_object()` +- `sqlite3_column_java_object()` +- `sqlite3_column_java_casted()` +- `sqlite3_value_java_object()` +- `sqlite3_value_java_casted()` + +which, as one might surmise, collectively enable the passing of +arbitrary Java objects from user-defined SQL functions through to the +caller. + + +Golden Rule: Garbage Collection Cannot Free SQLite Resources ------------------------------------------------------------------------ -JNI bindings which accept client-defined functions _must never throw -exceptions_ unless _very explicitly documented_ as being -throw-safe. Exceptions are generally reserved for higher-level -bindings which are constructed to specifically deal with them and -ensure that they do not leak C-level resources. Some of the JNI -bindings are provided as Java functions which expect this rule to -always hold. - -UTF-8(-ish) ------------------------------------------------------------------------- +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. -SQLite internally uses UTF-8 encoding, whereas Java natively uses -UTF-16. Java JNI has routines for converting to and from UTF-8, _but_ -Java uses what its docs call "[modified UTF-8][modutf8]." Care must be -taken when converting Java strings to UTF-8 to ensure that the proper -conversion is performed. In short, -`String.getBytes(StandardCharsets.UTF_8)` performs the proper -conversion in Java, and there is no JNI C API for that conversion -(JNI's `NewStringUTF()` returns MUTF-8). -Known consequences and limitations of this discrepancy include: - -- Names of databases, tables, and collations must not contain - characters which differ in MUTF-8 and UTF-8, or certain APIs will - mis-translate them on their way between languages. APIs which - transfer other client-side data to Java take extra care to - convert the data at the cost of performance. +Golden Rule #2: _Never_ Throw from Callbacks (Unless...) +------------------------------------------------------------------------ -[modutf8]: https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8 +All routines in this API, barring explicitly documented exceptions, +retain C-like semantics. For example, they are not permitted to throw +or propagate exceptions and must return error information (if any) via +result codes or `null`. The only cases where the C-style APIs may +throw is through client-side misuse, e.g. passing in a null where it +shouldn't be used. The APIs clearly mark function parameters which +should not be null, but does not actively defend itself against such +misuse. Some C-style APIs explicitly accept `null` as a no-op for +usability's sake, and some of the JNI APIs deliberately return an +error code, instead of segfaulting, when passed a `null`. + +Client-defined callbacks _must never throw exceptions_ unless _very +explicitly documented_ as being throw-safe. Exceptions are generally +reserved for higher-level bindings which are constructed to +specifically deal with them and ensure that they do not leak C-level +resources. In some cases, callback handlers are permitted to throw, in +which cases they get translated to C-level result codes and/or +messages. If a callback which is not permitted to throw throws, its +exception may trigger debug output but will otherwise be suppressed. + +The reason some callbacks are permitted to throw and others not is +because all such callbacks act as proxies for C function callback +interfaces and some of those interfaces have no error-reporting +mechanism. Those which are capable of propagating errors back through +the library convert exceptions from callbacks into corresponding +C-level error information. Those which cannot propagate errors +necessarily suppress any exceptions in order to maintain the C-style +semantics of the APIs. Unwieldy Constructs are Re-mapped @@ -126,7 +199,7 @@ 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, @@ -145,7 +218,7 @@ passed that object as their first argument. That data is passed around 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); @@ -160,20 +233,20 @@ 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, - Collation collation); + SomeCallbackType collation); ``` -Where the `Collation` class has an abstract `xCompare()` method and +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: -``` -int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new Collation(){ +```java +int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new SomeCallbackType(){ // Required comparison function: - @Override public int xCompare(byte[] lhs, byte[] rhs){ ... } + @Override public int call(byte[] lhs, byte[] rhs){ ... } // Optional finalizer function: @Override public void xDestroy(){ ... } @@ -188,8 +261,8 @@ int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new Collation( Noting that: -- It is still possible to bind in call-scope-local state via closures, - if desired. +- 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. @@ -200,6 +273,7 @@ Noting 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) @@ -207,12 +281,13 @@ 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 JS? +> 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: @@ -230,5 +305,9 @@ Search [`Tester1.java`](/file/ext/jni/src/org/sqlite/jni/Tester1.java) for Reminder: see the disclaimer at the top of this document regarding the in-flux nature of this API. -[jsrc]: /file/ -[www]: https://sqlite.org +### 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 index 23a26e4a8..7596c99f3 100644 --- a/ext/jni/jar-dist.make +++ b/ext/jni/jar-dist.make @@ -6,7 +6,9 @@ # 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. +# 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 @@ -31,10 +33,11 @@ SQLITE_OPT = \ -DSQLITE_OMIT_LOAD_EXTENSION \ -DSQLITE_OMIT_DEPRECATED \ -DSQLITE_OMIT_SHARED_CACHE \ - -DSQLITE_THREADSAFE=0 \ + -DSQLITE_THREADSAFE=1 \ -DSQLITE_TEMP_STORE=2 \ -DSQLITE_USE_URI=1 \ - -DSQLITE_ENABLE_FTS5 + -DSQLITE_ENABLE_FTS5 \ + -DSQLITE_DEBUG sqlite3-jni.dll = libsqlite3-jni.so $(sqlite3-jni.dll): @@ -46,8 +49,10 @@ $(sqlite3-jni.dll): 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 -Djava.library.path=. sqlite3-jni-*.jar + java -jar $(test.flags) + java -jar $(test.flags) -t 7 -r 10 -shuffle clean: -rm -f $(sqlite3-jni.dll) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index b28ea7114..f18fa6f5d 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -13,15 +13,15 @@ ** org.sqlite.jni.SQLiteJni (from which sqlite3-jni.h is generated). */ -/** - If you found this comment by searching the code for - CallStaticObjectMethod then you're the victim of an OpenJDK bug: - - https://bugs.openjdk.org/browse/JDK-8130659 - - It's known to happen with OpenJDK v8 but not with v19. - - This code does not use JNI's CallStaticObjectMethod(). +/* +** If you found this comment by searching the code for +** CallStaticObjectMethod then you're the victim of an OpenJDK bug: +** +** https://bugs.openjdk.org/browse/JDK-8130659 +** +** It's known to happen with OpenJDK v8 but not with v19. +** +** This code does not use JNI's CallStaticObjectMethod(). */ /* @@ -61,9 +61,6 @@ #ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC # define SQLITE_ENABLE_OFFSET_SQL_FUNC 1 #endif -#ifndef SQLITE_ENABLE_PREUPDATE_HOOK -# define SQLITE_ENABLE_PREUPDATE_HOOK 1 /*required by session extension*/ -#endif #ifndef SQLITE_ENABLE_RTREE # define SQLITE_ENABLE_RTREE 1 #endif @@ -78,6 +75,14 @@ //#endif /**********************************************************************/ +/* SQLITE_J... */ +#ifdef SQLITE_JNI_FATAL_OOM +#if !SQLITE_JNI_FATAL_OOM +#undef SQLITE_JNI_FATAL_OOM +#endif +#endif + +/**********************************************************************/ /* SQLITE_M... */ #ifndef SQLITE_MAX_ALLOCATION_SIZE # define SQLITE_MAX_ALLOCATION_SIZE 0x1fffffff @@ -105,7 +110,15 @@ # define SQLITE_TEMP_STORE 2 #endif #ifndef SQLITE_THREADSAFE -# define SQLITE_THREADSAFE 0 +# define SQLITE_THREADSAFE 1 +#endif + +/* +** 2023-08-25: initial attempts at running with SQLITE_THREADSAFE=0 +** lead to as-yet-uninvestigated bad reference errors from JNI. +*/ +#if 0 && SQLITE_THREADSAFE==0 +# error "This code currently requires SQLITE_THREADSAFE!=0." #endif /**********************************************************************/ @@ -133,9 +146,13 @@ #undef INC__STRINGIFY #undef SQLITE_C +/* +** End of the sqlite3 lib setup. What follows is JNI-specific. +*/ + #include "sqlite3-jni.h" -#include <stdio.h> /* only for testing/debugging */ #include <assert.h> +#include <stdio.h> /* only for testing/debugging */ /* Only for debugging */ #define MARKER(pfexp) \ @@ -143,335 +160,287 @@ printf pfexp; \ } while(0) -/* Creates a verbose JNI function name. */ -#define JFuncName(Suffix) \ +/* +** 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_SQLite3Jni_sqlite3_ ## Suffix -/* Prologue for JNI functions. */ -#define JDECL(ReturnType,Suffix) \ - JNIEXPORT ReturnType JNICALL \ - JFuncName(Suffix) -/** - Shortcuts for the first 2 parameters to all JNI bindings. - - The type of the jSelf arg differs, but no docs seem to mention - this: for static methods it's of type jclass and for non-static - it's jobject. jobject actually works for all funcs, in the sense - that it compiles and runs so long as we don't use jSelf (which is - only rarely needed in this code), but to be pedantically correct we - need the proper type in the signature. +/* Prologue for JNI function declarations and definitions. */ +#define JniDecl(ReturnType,Suffix) \ + JNIEXPORT ReturnType JNICALL JniFuncName(Suffix) - Not even the official docs mention this discrepancy: +/* +** 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. + */ +#define S3JniApi(CFunc,ReturnType,Suffix) JniDecl(ReturnType,Suffix) - https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers +/* +** 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 JENV_OSELF JNIEnv * const env, jobject jSelf -#define JENV_CSELF JNIEnv * const env, jclass jKlazz -/* Helpers to account for -Xcheck:jni warnings about not having - checked for exceptions. */ -#define IFTHREW if((*env)->ExceptionCheck(env)) -#define EXCEPTION_IGNORE (void)((*env)->ExceptionCheck(env)) -#define EXCEPTION_CLEAR (*env)->ExceptionClear(env) -#define EXCEPTION_REPORT (*env)->ExceptionDescribe(env) -#define EXCEPTION_WARN_CALLBACK_THREW(STR) \ +#define JniArgsEnvObj JNIEnv * const env, jobject jSelf +#define JniArgsEnvClass JNIEnv * const env, jclass jKlazz +/* +** Helpers to account for -Xcheck:jni warnings about not having +** checked for exceptions. +*/ +#define S3JniIfThrew if( (*env)->ExceptionCheck(env) ) +#define S3JniExceptionClear (*env)->ExceptionClear(env) +#define S3JniExceptionReport (*env)->ExceptionDescribe(env) +#define S3JniExceptionIgnore S3JniIfThrew S3JniExceptionClear +#define S3JniExceptionWarnIgnore \ + S3JniIfThrew {S3JniExceptionReport; S3JniExceptionClear;}(void)0 +#define S3JniExceptionWarnCallbackThrew(STR) \ MARKER(("WARNING: " STR " MUST NOT THROW.\n")); \ (*env)->ExceptionDescribe(env) -#define IFTHREW_REPORT IFTHREW EXCEPTION_REPORT -#define IFTHREW_CLEAR IFTHREW EXCEPTION_CLEAR /** To be used for cases where we're _really_ not expecting an exception, e.g. looking up well-defined Java class members. */ -#define EXCEPTION_IS_FATAL(MSG) IFTHREW {\ - EXCEPTION_REPORT; EXCEPTION_CLEAR; \ +#define S3JniExceptionIsFatal(MSG) S3JniIfThrew {\ + S3JniExceptionReport; S3JniExceptionClear; \ (*env)->FatalError(env, MSG); \ } -/** Helpers for extracting pointers from jobjects, noting that the - corresponding Java interfaces have already done the type-checking. - */ -#define PtrGet_sqlite3(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3) -#define PtrGet_sqlite3_stmt(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_stmt) -#define PtrGet_sqlite3_value(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_value) -#define PtrGet_sqlite3_context(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_context) -/* Helpers for Java value reference management. */ -static inline jobject new_global_ref(JNIEnv * const env, jobject const v){ - return v ? (*env)->NewGlobalRef(env, v) : NULL; -} -static inline void delete_global_ref(JNIEnv * const env, jobject const v){ - if(v) (*env)->DeleteGlobalRef(env, v); +/* +** 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. +*/ +#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 */; } -static inline void delete_local_ref(JNIEnv * const env, jobject const v){ - if(v) (*env)->DeleteLocalRef(env, v); + +/* +** 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; } -#define REF_G(VAR) new_global_ref(env, (VAR)) -#define REF_L(VAR) (*env)->NewLocalRef(env, VAR) -#define UNREF_G(VAR) delete_global_ref(env,(VAR)) -#define UNREF_L(VAR) delete_local_ref(env,(VAR)) -/** - Constant string class names used as keys for S3JniGlobal_nph_cache(), -S3Jni - and - friends. +/* +** Works like sqlite3_malloc() unless built with SQLITE_JNI_FATAL_OOM, +** in which case it calls s3jni_oom() on OOM. */ -static const struct { - const char * const sqlite3; - const char * const sqlite3_stmt; - const char * const sqlite3_context; - const char * const sqlite3_value; - const char * const OutputPointer_Int32; - const char * const OutputPointer_Int64; - const char * const OutputPointer_String; - const char * const OutputPointer_ByteArray; - const char * const OutputPointer_sqlite3; - const char * const OutputPointer_sqlite3_stmt; -#ifdef SQLITE_ENABLE_FTS5 - const char * const Fts5Context; - const char * const Fts5ExtensionApi; - const char * const fts5_api; - const char * const fts5_tokenizer; - const char * const Fts5Tokenizer; -#endif -} S3JniClassNames = { - "org/sqlite/jni/sqlite3", - "org/sqlite/jni/sqlite3_stmt", - "org/sqlite/jni/sqlite3_context", - "org/sqlite/jni/sqlite3_value", - "org/sqlite/jni/OutputPointer$Int32", - "org/sqlite/jni/OutputPointer$Int64", - "org/sqlite/jni/OutputPointer$String", - "org/sqlite/jni/OutputPointer$ByteArray", - "org/sqlite/jni/OutputPointer$sqlite3", - "org/sqlite/jni/OutputPointer$sqlite3_stmt", -#ifdef SQLITE_ENABLE_FTS5 - "org/sqlite/jni/Fts5Context", - "org/sqlite/jni/Fts5ExtensionApi", - "org/sqlite/jni/fts5_api", - "org/sqlite/jni/fts5_tokenizer", - "org/sqlite/jni/Fts5Tokenizer" +#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))) #endif -}; - -/** Create a trivial JNI wrapper for (int CName(void)). */ -#define WRAP_INT_VOID(JniNameSuffix,CName) \ - JDECL(jint,JniNameSuffix)(JENV_CSELF){ \ - return (jint)CName(); \ - } - -/** Create a trivial JNI wrapper for (int CName(int)). */ -#define WRAP_INT_INT(JniNameSuffix,CName) \ - JDECL(jint,JniNameSuffix)(JENV_CSELF, jint arg){ \ - return (jint)CName((int)arg); \ - } -/** Create a trivial JNI wrapper for (const mutf8_string * - CName(void)). This is only valid for functions which are known to - return ASCII or text which is equivalent in UTF-8 and MUTF-8. */ -#define WRAP_MUTF8_VOID(JniNameSuffix,CName) \ - JDECL(jstring,JniNameSuffix)(JENV_CSELF){ \ - return (*env)->NewStringUTF( env, CName() ); \ - } -/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*)). */ -#define WRAP_INT_STMT(JniNameSuffix,CName) \ - JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject jpStmt){ \ - jint const rc = (jint)CName(PtrGet_sqlite3_stmt(jpStmt)); \ - EXCEPTION_IGNORE /* squelch -Xcheck:jni */; \ - return rc; \ - } -/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*,int)). */ -#define WRAP_INT_STMT_INT(JniNameSuffix,CName) \ - JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject pStmt, jint n){ \ - return (jint)CName(PtrGet_sqlite3_stmt(pStmt), (int)n); \ - } -/** Create a trivial JNI wrapper for (jstring CName(sqlite3_stmt*,int)). */ -#define WRAP_STR_STMT_INT(JniNameSuffix,CName) \ - JDECL(jstring,JniNameSuffix)(JENV_CSELF, jobject pStmt, jint ndx){ \ - return (*env)->NewStringUTF(env, CName(PtrGet_sqlite3_stmt(pStmt), (int)ndx)); \ - } -/** Create a trivial JNI wrapper for (int CName(sqlite3*)). */ -#define WRAP_INT_DB(JniNameSuffix,CName) \ - JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject pDb){ \ - return (jint)CName(PtrGet_sqlite3(pDb)); \ - } -/** Create a trivial JNI wrapper for (int64 CName(sqlite3*)). */ -#define WRAP_INT64_DB(JniNameSuffix,CName) \ - JDECL(jlong,JniNameSuffix)(JENV_CSELF, jobject pDb){ \ - return (jlong)CName(PtrGet_sqlite3(pDb)); \ - } -/** Create a trivial JNI wrapper for (int CName(sqlite3_value*)). */ -#define WRAP_INT_SVALUE(JniNameSuffix,CName) \ - JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject jpSValue){ \ - return (jint)CName(PtrGet_sqlite3_value(jpSValue)); \ - } - -/* Helpers for jstring and jbyteArray. */ -#define JSTR_TOC(ARG) (*env)->GetStringUTFChars(env, ARG, NULL) -#define JSTR_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseStringUTFChars(env, ARG, VAR) -#define JBA_TOC(ARG) (*env)->GetByteArrayElements(env,ARG, NULL) -#define JBA_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseByteArrayElements(env, ARG, VAR, JNI_ABORT) - -/* Marker for code which needs(?) to be made thread-safe. REASON is a - terse reminder about why that function requires a mutex. +/* +** Works like sqlite3_realloc() unless built with SQLITE_JNI_FATAL_OOM, +** in which case it calls s3jni_oom() on OOM. */ -#define FIXME_THREADING(REASON) +#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 -enum { - /** - Size of the NativePointerHolder cache. Need enough space for - (only) the library's NativePointerHolder types, a fixed count - known at build-time. If we add more than this a fatal error will - be triggered with a reminder to increase this. This value needs - to be exactly the number of entries in the S3JniClassNames - object. The S3JniClassNames entries are the keys for this particular - cache. - */ - NphCache_SIZE = sizeof(S3JniClassNames) / sizeof(char const *) -}; +/* 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 -/** - Cache entry for NativePointerHolder lookups. +/* 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)) + +/* +** Key type for use with S3JniGlobal_nph(). */ -typedef struct S3JniNphCache S3JniNphCache; -struct S3JniNphCache { - const char * zClassName /* "full/class/Name". Must be a static - string pointer from the S3JniClassNames - struct. */; - jclass klazz /* global ref to the concrete - NativePointerHolder subclass represented by - zClassName */; - jmethodID midCtor /* klazz's no-arg constructor. Used by - new_NativePointerHolder_object(). */; - jfieldID fidValue /* NativePointerHolder.nativePointer and - OutputPointer.X.value */; - jfieldID fidSetAgg /* sqlite3_context::aggregateContext. Used only - by the sqlite3_context binding. */; +typedef struct S3JniNphRef S3JniNphRef; +struct S3JniNphRef { + 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 */; }; -/** - Cache for per-JNIEnv data. - - Potential TODO: move the jclass entries to global space because, - per https://developer.android.com/training/articles/perf-jni: - - > once you have a valid jclass global reference you can use it from - any attached thread. - - Whereas we cache new refs for each thread. +/* +** Cache keys for each concrete NativePointerHolder subclass and +** OutputPointer.T type. The members are to be used with +** S3JniGlobal_nph() and friends, and each one's member->index +** corresponds to its index in the S3JniGlobal.nph[] array. */ -typedef struct S3JniEnvCache S3JniEnvCache; -struct S3JniEnvCache { - JNIEnv *env /* env in which this cache entry was created */; - //! The various refs to global classes might be cacheable a single - // time globally. Information online seems inconsistent on that - // point. - struct { - jclass cObj /* global ref to java.lang.Object */; - jclass cLong /* global ref to java.lang.Long */; - jclass cString /* global ref to java.lang.String */; - jobject oCharsetUtf8 /* global ref to StandardCharset.UTF_8 */; - jmethodID ctorLong1 /* the Long(long) constructor */; - jmethodID ctorStringBA /* the String(byte[],Charset) constructor */; - jmethodID stringGetBytes /* the String.getBytes(Charset) method */; - } g /* refs to global Java state */; +static const struct { + const S3JniNphRef sqlite3; + const S3JniNphRef sqlite3_stmt; + const S3JniNphRef sqlite3_context; + const S3JniNphRef sqlite3_value; + const S3JniNphRef OutputPointer_Int32; + const S3JniNphRef OutputPointer_Int64; + const S3JniNphRef OutputPointer_sqlite3; + const S3JniNphRef OutputPointer_sqlite3_stmt; + const S3JniNphRef OutputPointer_sqlite3_value; #ifdef SQLITE_ENABLE_FTS5 - jobject jFtsExt /* Global ref to Java singleton for the - Fts5ExtensionApi instance. */; - struct { - jclass klazz; - jfieldID fidA; - jfieldID fidB; - } jPhraseIter; + const S3JniNphRef OutputPointer_String; + const S3JniNphRef OutputPointer_ByteArray; + const S3JniNphRef Fts5Context; + const S3JniNphRef Fts5ExtensionApi; + const S3JniNphRef fts5_api; + const S3JniNphRef fts5_tokenizer; + const S3JniNphRef Fts5Tokenizer; #endif - S3JniEnvCache * pPrev /* Previous entry in the linked list */; - S3JniEnvCache * pNext /* Next entry in the linked list */; - /** TODO?: S3JniNphCache *pNphHit; - - and always set it to the most recent cache search result. +} S3JniNphRefs = { +#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, "sqlite3"), + RefN(1, "sqlite3_stmt"), + RefN(2, "sqlite3_context"), + RefN(3, "sqlite3_value"), + RefO(4, "OutputPointer$Int32", "I"), + RefO(5, "OutputPointer$Int64", "J"), + RefO(6, "OutputPointer$sqlite3", + "Lorg/sqlite/jni/sqlite3;"), + RefO(7, "OutputPointer$sqlite3_stmt", + "Lorg/sqlite/jni/sqlite3_stmt;"), + RefO(8, "OutputPointer$sqlite3_value", + "Lorg/sqlite/jni/sqlite3_value;"), +#ifdef SQLITE_ENABLE_FTS5 + RefO(9, "OutputPointer$String", "Ljava/lang/String;"), + RefO(10, "OutputPointer$ByteArray", "[B"), + RefN(11, "Fts5Context"), + RefN(12, "Fts5ExtensionApi"), + RefN(13, "fts5_api"), + RefN(14, "fts5_tokenizer"), + RefN(15, "Fts5Tokenizer") +#endif +#undef MkRef +#undef RefN +#undef RefO +}; - The intent would be to help fast-track cache lookups and would - speed up, e.g., the sqlite3_value-to-Java-array loop in a - multi-threaded app. +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 S3JniNphRef entries in the S3JniNphRefs + ** object. */ - S3JniNphCache nph[NphCache_SIZE]; + S3Jni_NphCache_size = sizeof(S3JniNphRefs) / sizeof(S3JniNphRef) }; -static void S3JniNphCache_clear(JNIEnv * const env, S3JniNphCache * const p){ - UNREF_G(p->klazz); - memset(p, 0, sizeof(S3JniNphCache)); -} - -#define S3JNI_ENABLE_AUTOEXT 1 -#if S3JNI_ENABLE_AUTOEXT /* - Whether auto extensions are feasible here is currently unknown due - to... - - 1) JNIEnv/threading issues. A db instance is mapped to a specific - JNIEnv object but auto extensions may be added from any thread. In - such contexts, which JNIEnv do we use for the JNI APIs? - - 2) a chicken/egg problem involving the Java/C mapping of the db: - when auto extensions are run, the db has not yet been connected to - Java. If we do that during the auto-ext, sqlite3_open(_v2)() will not behave - properly because they have a different jobject and the API - guarantees the user that _that_ object is the one the API will bind - the native to. - - If we change the open(_v2()) interfaces to use OutputPointer.sqlite3 - instead of the client passing in an instance, we could work around - (2). +** Cache entry for NativePointerHolder subclasses and OutputPointer +** types. The pRef and klazz fields are set up the first time the +** entry is fetched using S3JniGlobal_nph(). The other fields are +** populated as needed by the routines which use them. */ -typedef struct S3JniAutoExtension S3JniAutoExtension; -typedef void (*S3JniAutoExtension_xEntryPoint)(sqlite3*); -struct S3JniAutoExtension { - jobject jObj; - jmethodID midFunc; - S3JniAutoExtension_xEntryPoint xEntryPoint; - S3JniAutoExtension *pNext /* next linked-list entry */; - S3JniAutoExtension *pPrev /* previous linked-list entry */; +typedef struct S3JniNphClass S3JniNphClass; +struct S3JniNphClass { + volatile const S3JniNphRef * pRef /* Entry from S3JniNphRefs. */; + jclass klazz /* global ref to the concrete + ** NativePointerHolder subclass + ** represented by zClassName */; + volatile jmethodID midCtor /* klazz's no-arg constructor. Used by + ** new_NativePointerHolder_object(). */; + volatile jfieldID fidValue /* NativePointerHolder.nativePointer or + ** OutputPointer.T.value */; + volatile jfieldID fidAggCtx /* sqlite3_context.aggregateContext, used only + ** by the sqlite3_context binding. */; }; -#endif -/** State for various hook callbacks. */ +/* +** 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 */; - jclass klazz /* global ref to jObj's class. Only needed - by hooks which have an xDestroy() method, - as lookup of that method is deferred - until the object requires cleanup. */; + ** jObj's type */; + /* We lookup the jObj.xDestroy() method as-needed for contexts which + ** have custom finalizers. */ }; +/* For clean bitwise-copy init of local instances. */ +static const S3JniHook S3JniHook_empty = {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. It is freed during sqlite3_shutdown(). +/* +** Per-(sqlite3*) state for various JNI bindings. This state is +** allocated as needed, cleaned up in sqlite3_close(_v2)(), and +** recycled when possible. */ typedef struct S3JniDb S3JniDb; struct S3JniDb { - JNIEnv *env /* The associated JNIEnv handle */; sqlite3 *pDb /* The associated db handle */; - jobject jDb /* A global ref of the object which was passed to - sqlite3_open(_v2)(). We need this in order to have - an object to pass to sqlite3_collation_needed()'s - callback, or else we have to dynamically create one - for that purpose, which would be fine except that - it would be a different instance (and maybe even a - different class) than the one the user may expect - to receive. */; - char * zMainDbName /* Holds any string allocated on behave of + 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. */; - S3JniHook busyHandler; - S3JniHook collation; - S3JniHook collationNeeded; - S3JniHook commitHook; - S3JniHook progress; - S3JniHook rollbackHook; - S3JniHook trace; - S3JniHook updateHook; - S3JniHook authHook; + struct { + S3JniHook busyHandler; + S3JniHook collation; + 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 jobject jFtsApi /* global ref to s3jni_fts5_api_from_db() */; #endif @@ -479,151 +448,410 @@ struct S3JniDb { S3JniDb * pPrev /* Previous entry in the available/free list */; }; -/** - Global state, e.g. caches and metrics. +/* +** Cache for per-JNIEnv (i.e. per-thread) data. */ -static struct { - /** - According to: https://developer.ibm.com/articles/j-jni/ - - > A thread can get a JNIEnv by calling GetEnv() using the JNI - invocation interface through a JavaVM object. The JavaVM object - itself can be obtained by calling the JNI GetJavaVM() method - using a JNIEnv object and can be cached and shared across - threads. Caching a copy of the JavaVM object enables any thread - with access to the cached object to get access to its own - JNIEnv when necessary. +typedef struct S3JniEnv S3JniEnv; +struct S3JniEnv { + JNIEnv *env /* env 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 * pPrev /* Previous entry in the linked list */; + S3JniEnv * pNext /* Next entry in the linked list */; +}; + +/* +** 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; + /** Method IDs for the various UDF methods. */ + jmethodID jmidxFunc /* xFunc method */; + jmethodID jmidxStep /* xStep method */; + jmethodID jmidxFinal /* xFinal method */; + jmethodID jmidxValue /* xValue method */; + jmethodID jmidxInverse /* xInverse method */; + S3JniUdf * pNext /* Next entry in SJG.udf.aFree. */; +}; + +#if !defined(SQLITE_JNI_OMIT_METRICS) && !defined(SQLITE_JNI_ENABLE_METRICS) +# ifdef SQLITE_DEBUG +# define SQLITE_JNI_ENABLE_METRICS +# endif +#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. */ + sqlite3_mutex * mutex; + /* + ** Cache of Java refs and method IDs for NativePointerHolder + ** subclasses and OutputPointer.T types. + */ + S3JniNphClass nph[S3Jni_NphCache_size]; + /* + ** Cache of per-thread state. + */ struct { - S3JniEnvCache * aHead /* Linked list of in-use instances */; - S3JniEnvCache * aFree /* Linked list of free instances */; + S3JniEnv * aHead /* Linked list of in-use instances */; + S3JniEnv * aFree /* Linked list of free instances */; + sqlite3_mutex * mutex /* mutex for aHead and aFree, first-time + inits of nph[] entries, and + NativePointerHolder_get/set(). */; + 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 * aUsed /* Linked list of in-use instances */; + S3JniDb * aHead /* Linked list of in-use instances */; S3JniDb * aFree /* Linked list of free instances */; + sqlite3_mutex * mutex /* mutex for aHead and aFree */; + void const * locker /* perDb mutex is held on this object's + behalf. Used only for sanity checking. */; } perDb; struct { - unsigned nphCacheHits; - unsigned nphCacheMisses; - unsigned envCacheHits; - unsigned envCacheMisses; - unsigned nDestroy /* xDestroy() calls across all types */; + S3JniUdf * aFree /* Head of the free-item list. Guarded by global + mutex. */; + } udf; + /* + ** Refs to global classes and methods. Obtained during static init + ** and never released. + */ + struct { + jclass cLong /* global ref to java.lang.Long */; + jclass cString /* global ref to java.lang.String */; + jobject oCharsetUtf8 /* global ref to StandardCharset.UTF_8 */; + jmethodID ctorLong1 /* the Long(long) constructor */; + jmethodID ctorStringBA /* the String(byte[],Charset) constructor */; + jmethodID stringGetBytes /* the String.getBytes(Charset) method */; + } g; + /* + ** The list of Java-side auto-extensions + ** (org.sqlite.jni.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 */; + 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 jFtsExt /* Global ref to Java singleton for the + Fts5ExtensionApi instance. */; + struct { + jfieldID fidA /* Fts5Phrase::a member */; + jfieldID fidB /* Fts5Phrase::b member */; + } jPhraseIter; + } fts5; +#endif +#ifdef SQLITE_ENABLE_SQLLOG + struct { + S3JniHook sqllog /* sqlite3_config(SQLITE_CONFIG_SQLLOG) callback */; + } hooks; +#endif +#ifdef SQLITE_JNI_ENABLE_METRICS + /* Internal metrics. */ + struct { + volatile unsigned nEnvHit; + volatile unsigned nEnvMiss; + volatile unsigned nEnvAlloc; + volatile unsigned nNphInit; + volatile unsigned nMutexEnv /* number of times envCache.mutex was entered for + a S3JniEnv operation. */; + volatile unsigned nMutexEnv2 /* number of times envCache.mutex 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. */; struct { /* Number of calls for each type of UDF callback. */ - unsigned nFunc; - unsigned nStep; - unsigned nFinal; - unsigned nValue; - unsigned nInverse; + 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; -#if S3JNI_ENABLE_AUTOEXT - struct { - S3JniAutoExtension *pHead /* Head of the auto-extension list */; - S3JniDb * psOpening /* handle to the being-opened db. We - need this so that auto extensions - can have a consistent view of the - cross-language db connection and - behave property if they call further - db APIs. */; - int isRunning /* True while auto extensions are - running. This is used to prohibit - manipulation of the auto-extension - list while extensions are - running. */; - } autoExt; +#endif /* SQLITE_JNI_ENABLE_METRICS */ +}; +static S3JniGlobalType S3JniGlobal = {}; +#define SJG S3JniGlobal + +/* +** 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,OBJ) NativePointerHolder_get(OBJ, &S3JniNphRefs.T) +#define PtrGet_sqlite3(OBJ) PtrGet_T(sqlite3, OBJ) +#define PtrGet_sqlite3_stmt(OBJ) PtrGet_T(sqlite3_stmt, OBJ) +#define PtrGet_sqlite3_value(OBJ) PtrGet_T(sqlite3_value, OBJ) +#define PtrGet_sqlite3_context(OBJ) PtrGet_T(sqlite3_context, OBJ) + +/* 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 S3JniMutex_Env_assertLocked \ + assert( 0 != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) +#define S3JniMutex_Env_assertLocker \ + assert( (env) == SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) +#define S3JniMutex_Env_assertNotLocker \ + assert( (env) != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) + +#define S3JniMutex_Env_enter \ + S3JniMutex_Env_assertNotLocker; \ + sqlite3_mutex_enter( SJG.envCache.mutex ); \ + s3jni_incr(&SJG.metrics.nMutexEnv); \ + SJG.envCache.locker = env +#define S3JniMutex_Env_leave \ + S3JniMutex_Env_assertLocker; \ + SJG.envCache.locker = 0; \ + sqlite3_mutex_leave( SJG.envCache.mutex ) + +#define S3JniMutex_Ext_enter \ + sqlite3_mutex_enter( SJG.autoExt.mutex ); \ + SJG.autoExt.locker = env; \ + s3jni_incr( &SJG.metrics.nMutexAutoExt ) +#define S3JniMutex_Ext_leave \ + assert( env == SJG.autoExt.locker && "Misuse of S3JniGlobal.autoExt.mutex" ); \ + sqlite3_mutex_leave( SJG.autoExt.mutex ) +#define S3JniMutex_Ext_assertLocker \ + assert( env == SJG.autoExt.locker && "Misuse of S3JniGlobal.autoExt.mutex" ) + +#define S3JniMutex_Global_enter \ + sqlite3_mutex_enter( SJG.mutex ); \ + s3jni_incr(&SJG.metrics.nMutexGlobal); +#define S3JniMutex_Global_leave \ + sqlite3_mutex_leave( SJG.mutex ) + +#define S3JniMutex_Nph_enter \ + S3JniMutex_Env_assertNotLocker; \ + sqlite3_mutex_enter( SJG.envCache.mutex ); \ + s3jni_incr( &SJG.metrics.nMutexEnv2 ); \ + SJG.envCache.locker = env +#define S3JniMutex_Nph_leave \ + S3JniMutex_Env_assertLocker; \ + SJG.envCache.locker = 0; \ + sqlite3_mutex_leave( SJG.envCache.mutex ) + +#define S3JniMutex_S3JniDb_assertLocker \ + assert( (env) == SJG.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" ) +#define S3JniMutex_S3JniDb_enter \ + sqlite3_mutex_enter( SJG.perDb.mutex ); \ + assert( 0==SJG.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" ); \ + s3jni_incr( &SJG.metrics.nMutexPerDb ); \ + SJG.perDb.locker = env; +#define S3JniMutex_S3JniDb_leave \ + assert( env == SJG.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" ); \ + SJG.perDb.locker = 0; \ + sqlite3_mutex_leave( SJG.perDb.mutex ) + +#else /* SQLITE_THREADSAFE==0 */ +#define S3JniMutex_Env_assertLocked +#define S3JniMutex_Env_assertLocker +#define S3JniMutex_Env_assertNotLocker +#define S3JniMutex_Env_enter +#define S3JniMutex_Env_leave +#define S3JniMutex_Ext_assertLocker +#define S3JniMutex_Ext_enter +#define S3JniMutex_Ext_leave +#define S3JniMutex_Global_enter +#define S3JniMutex_Global_leave +#define S3JniMutex_Nph_enter +#define S3JniMutex_Nph_leave +#define S3JniMutex_S3JniDb_assertLocker +#define S3JniMutex_S3JniDb_enter +#define S3JniMutex_S3JniDb_leave #endif -} S3JniGlobal; -#define OOM_CHECK(VAR) if(!(VAR)) s3jni_oom(env) -static void s3jni_oom(JNIEnv * const env){ - (*env)->FatalError(env, "Out of memory.") /* does not return */; +/* Helpers for jstring and jbyteArray. */ +static const char * s3jni__jstring_to_mutf8_bytes(JNIEnv * const env, jstring v ){ + const char *z = v ? (*env)->GetStringUTFChars(env, v, NULL) : 0; + s3jni_oom_check( v ? !!z : !z ); + return z; } -/** - sqlite3_malloc() proxy which fails fatally on OOM. This should - only be used for routines which manage global state and have no - recovery strategy for OOM. For sqlite3 API which can reasonably - return SQLITE_NOMEM, sqlite3_malloc() should be used instead. -*/ -static void * s3jni_malloc(JNIEnv * const env, size_t n){ - void * const rv = sqlite3_malloc(n); - if(n && !rv) s3jni_oom(env); +#define s3jni_jstring_to_mutf8(ARG) s3jni__jstring_to_mutf8_bytes(env, (ARG)) +#define s3jni_mutf8_release(ARG,VAR) if( VAR ) (*env)->ReleaseStringUTFChars(env, ARG, VAR) + +static jbyte * s3jni__jbytearray_bytes(JNIEnv * const env, jbyteArray jBA ){ + jbyte * const rv = jBA ? (*env)->GetByteArrayElements(env, jBA, NULL) : 0; + s3jni_oom_check( jBA ? !!rv : 1 ); return rv; } -/** - Fetches the S3JniGlobal.envCache row for the given env, allocing - a row if needed. When a row is allocated, its state is initialized - insofar as possible. Calls (*env)->FatalError() if allocation of - an entry fails. That's hypothetically possible but "shouldn't happen." +#define s3jni_jbytearray_bytes(jByteArray) s3jni__jbytearray_bytes(env, (jByteArray)) +#define s3jni_jbytearray_release(jByteArray,jBytes) \ + if( jBytes ) (*env)->ReleaseByteArrayElements(env, jByteArray, jBytes, JNI_ABORT) + +/* +** 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." */ -FIXME_THREADING(S3JniEnvCache) -static S3JniEnvCache * S3JniGlobal_env_cache(JNIEnv * const env){ - struct S3JniEnvCache * row = S3JniGlobal.envCache.aHead; +static S3JniEnv * S3JniEnv__get(JNIEnv * const env){ + struct S3JniEnv * row; + S3JniMutex_Env_enter; + row = SJG.envCache.aHead; for( ; row; row = row->pNext ){ if( row->env == env ){ - ++S3JniGlobal.metrics.envCacheHits; + s3jni_incr( &SJG.metrics.nEnvHit ); + S3JniMutex_Env_leave; return row; } } - ++S3JniGlobal.metrics.envCacheMisses; - row = S3JniGlobal.envCache.aFree; + s3jni_incr( &SJG.metrics.nEnvMiss ); + row = SJG.envCache.aFree; if( row ){ assert(!row->pPrev); - S3JniGlobal.envCache.aFree = row->pNext; + SJG.envCache.aFree = row->pNext; if( row->pNext ) row->pNext->pPrev = 0; }else{ - row = sqlite3_malloc(sizeof(S3JniEnvCache)); - if( !row ){ - (*env)->FatalError(env, "Maintenance required: S3JniEnvCache is full.") - /* Does not return, but cc doesn't know that */; - return NULL; - } + row = s3jni_malloc_or_die(env, sizeof(*row)); + s3jni_incr( &SJG.metrics.nEnvAlloc ); } memset(row, 0, sizeof(*row)); - row->pNext = S3JniGlobal.envCache.aHead; - if(row->pNext) row->pNext->pPrev = row; - S3JniGlobal.envCache.aHead = row; + row->pNext = SJG.envCache.aHead; + if( row->pNext ) row->pNext->pPrev = row; + SJG.envCache.aHead = row; row->env = env; - /* Grab references to various global classes and objects... */ - row->g.cObj = REF_G((*env)->FindClass(env,"java/lang/Object")); - EXCEPTION_IS_FATAL("Error getting reference to Object class."); - - row->g.cLong = REF_G((*env)->FindClass(env,"java/lang/Long")); - EXCEPTION_IS_FATAL("Error getting reference to Long class."); - row->g.ctorLong1 = (*env)->GetMethodID(env, row->g.cLong, - "<init>", "(J)V"); - EXCEPTION_IS_FATAL("Error getting reference to Long constructor."); - - row->g.cString = REF_G((*env)->FindClass(env,"java/lang/String")); - EXCEPTION_IS_FATAL("Error getting reference to String class."); - row->g.ctorStringBA = - (*env)->GetMethodID(env, row->g.cString, - "<init>", "([BLjava/nio/charset/Charset;)V"); - EXCEPTION_IS_FATAL("Error getting reference to String(byte[],Charset) ctor."); - row->g.stringGetBytes = - (*env)->GetMethodID(env, row->g.cString, - "getBytes", "(Ljava/nio/charset/Charset;)[B"); - EXCEPTION_IS_FATAL("Error getting reference to String.getBytes(Charset)."); - - { /* StandardCharsets.UTF_8 */ - jfieldID fUtf8; - jclass const klazzSC = - (*env)->FindClass(env,"java/nio/charset/StandardCharsets"); - EXCEPTION_IS_FATAL("Error getting reference to StndardCharsets class."); - fUtf8 = (*env)->GetStaticFieldID(env, klazzSC, "UTF_8", - "Ljava/nio/charset/Charset;"); - EXCEPTION_IS_FATAL("Error getting StndardCharsets.UTF_8 field."); - row->g.oCharsetUtf8 = - REF_G((*env)->GetStaticObjectField(env, klazzSC, fUtf8)); - EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets.UTF_8."); - } + S3JniMutex_Env_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. @@ -638,46 +866,56 @@ static S3JniEnvCache * S3JniGlobal_env_cache(JNIEnv * const env){ ** ** Returns err_code. */ -static int s3jni_db_error(sqlite3* const db, int err_code, const char * const zMsg){ +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. - */ -static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env, const unsigned char * const p, int nP){ +/* +** 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); - if(jba){ + + s3jni_oom_check( jba ); + if( jba && p ){ (*env)->SetByteArrayRegion(env, jba, 0, (jint)nP, (const jbyte*)p); } return jba; } -/** - Uses the java.lang.String(byte[],Charset) constructor to create a - new String from UTF-8 string z. n is the number of bytes to - copy. If n<0 then sqlite3Strlen30() is used to calculate it. +#define s3jni_new_jbyteArray(P,n) s3jni__new_jbyteArray(env, P, n) - 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. +/* +** Uses the java.lang.String(byte[],Charset) constructor to create a +** new String from UTF-8 string z. n is the number of bytes to +** copy. If n<0 then sqlite3Strlen30() is used to calculate it. +** +** Returns NULL if z is NULL or on OOM, else returns a new jstring +** owned by the caller. +** +** Sidebar: this is a painfully inefficient way to convert from +** standard UTF-8 to a Java string, but JNI offers only algorithms for +** working with MUTF-8, not UTF-8. */ -static jstring s3jni_utf8_to_jstring(S3JniEnvCache * const jc, - const char * const z, int n){ +static jstring s3jni__utf8_to_jstring(JNIEnv * const env, + const char * const z, int n){ jstring rv = NULL; - JNIEnv * const env = jc->env; if( 0==n || (n<0 && z && !z[0]) ){ /* Fast-track the empty-string case via the MUTF-8 API. We could hypothetically do this for any strings where n<4 and z is @@ -686,120 +924,128 @@ static jstring s3jni_utf8_to_jstring(S3JniEnvCache * const jc, }else if( z ){ jbyteArray jba; if( n<0 ) n = sqlite3Strlen30(z); - jba = s3jni_new_jbyteArray(env, (unsigned const char *)z, (jsize)n); + jba = s3jni_new_jbyteArray((unsigned const char *)z, n); if( jba ){ - rv = (*env)->NewObject(env, jc->g.cString, jc->g.ctorStringBA, - jba, jc->g.oCharsetUtf8); - UNREF_L(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. +/* +** Converts the given java.lang.String object into a NUL-terminated +** UTF-8 C-string by calling jstr.getBytes(StandardCharset.UTF_8). +** Returns NULL if jstr is NULL or on allocation error. If jstr is not +** NULL and nLen is not NULL then nLen is set to the length of the +** returned string, not including the terminating NUL. If jstr is not +** NULL and it returns NULL, this indicates an allocation error. In +** that case, if nLen is not NULL then it is either set to 0 (if +** fetching of jstr's bytes fails to allocate) or set to what would +** have been the length of the string had C-string allocation +** succeeded. +** +** The returned memory is allocated from sqlite3_malloc() and +** ownership is transferred to the caller. */ -static char * s3jni_jstring_to_utf8(S3JniEnvCache * const jc, +static char * s3jni__jstring_to_utf8(JNIEnv * const env, jstring jstr, int *nLen){ - JNIEnv * const env = jc->env; jbyteArray jba; jsize nBa; char *rv; - if(!jstr) return 0; - jba = (*env)->CallObjectMethod(env, jstr, jc->g.stringGetBytes, - jc->g.oCharsetUtf8); + if( !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 */ ) { - EXCEPTION_REPORT; + S3JniExceptionReport; + s3jni_oom_check( jba ); if( nLen ) *nLen = 0; return 0; } nBa = (*env)->GetArrayLength(env, jba); if( nLen ) *nLen = (int)nBa; - rv = sqlite3_malloc( nBa + 1 ); + rv = s3jni_malloc( nBa + 1 ); if( rv ){ (*env)->GetByteArrayRegion(env, jba, 0, nBa, (jbyte*)rv); rv[nBa] = 0; } - UNREF_L(jba); + 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. +/* +** Expects to be passed a pointer from sqlite3_column_text16() or +** sqlite3_value_text16() and a byte-length value from +** sqlite3_column_bytes16() or sqlite3_value_bytes16(). It creates a +** Java String of exactly half that character length, returning NULL +** if !p or (*env)->NewString() fails. */ static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, int nP){ - return p + jstring const rv = p ? (*env)->NewString(env, (const jchar *)p, (jsize)(nP/2)) : NULL; + s3jni_oom_check( p ? !!rv : 1 ); + return rv; } -/** - Requires jx to be a Throwable. Calls its toString() method and - returns its value converted to a UTF-8 string. The caller owns the - returned string and must eventually sqlite3_free() it. Returns 0 - if there is a problem fetching the info or on OOM. - - Design note: we use toString() instead of getMessage() because the - former includes the exception type's name: - - Exception e = new RuntimeException("Hi"); - System.out.println(e.toString()); // java.lang.RuntimeException: Hi - System.out.println(e.getMessage()); // Hi - } +/* +** Requires jx to be a Throwable. Calls its toString() method and +** returns its value converted to a UTF-8 string. The caller owns the +** returned string and must eventually sqlite3_free() it. Returns 0 +** if there is a problem fetching the info or on OOM. +** +** Design note: we use toString() instead of getMessage() because the +** former includes the exception type's name: +** +** Exception e = new RuntimeException("Hi"); +** System.out.println(e.toString()); // java.lang.RuntimeException: Hi +** System.out.println(e.getMessage()); // Hi */ -FIXME_THREADING(S3JniEnvCache) -static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){ - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); +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;"); - IFTHREW{ - EXCEPTION_REPORT; - EXCEPTION_CLEAR; + S3JniUnrefLocal(klazz); + S3JniIfThrew{ + S3JniExceptionReport; + S3JniExceptionClear; return 0; } msg = (*env)->CallObjectMethod(env, jx, mid); - IFTHREW{ - EXCEPTION_REPORT; - EXCEPTION_CLEAR; + S3JniIfThrew{ + S3JniExceptionReport; + S3JniExceptionClear; return 0; } - zMsg = s3jni_jstring_to_utf8(jc, msg, 0); - UNREF_L(msg); + zMsg = s3jni_jstring_to_utf8( msg, 0); + S3JniUnrefLocal(msg); return zMsg; } -/** - Extracts the current JNI exception, sets ps->pDb's error message to - its message string, and clears the exception. If errCode is non-0, - it is used as-is, else SQLITE_ERROR is assumed. If there's a - problem extracting the exception's message, it's treated as - non-fatal and zDfltMsg is used in its place. - - This must only be called if a JNI exception is pending. - - Returns errCode unless it is 0, in which case SQLITE_ERROR is - returned. +/* +** 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. +** +** This must only be called if a JNI exception is pending. +** +** Returns errCode unless it is 0, in which case SQLITE_ERROR is +** returned. */ static int s3jni_db_exception(JNIEnv * const env, S3JniDb * const ps, int errCode, const char *zDfltMsg){ @@ -808,204 +1054,195 @@ static int s3jni_db_exception(JNIEnv * const env, S3JniDb * const ps, if( 0==errCode ) errCode = SQLITE_ERROR; if( ex ){ char * zMsg; - EXCEPTION_CLEAR; + S3JniExceptionClear; zMsg = s3jni_exception_error_msg(env, ex); s3jni_db_error(ps->pDb, errCode, zMsg ? zMsg : zDfltMsg); sqlite3_free(zMsg); - UNREF_L(ex); + S3JniUnrefLocal(ex); } - return errCode; + return errCode; } -/** - Extracts the (void xDestroy()) method from the given jclass and - applies it to jobj. If jObj is NULL, this is a no-op. If klazz is - NULL then it's derived from jobj. The lack of an xDestroy() method - is silently ignored and any exceptions thrown by the method trigger - a warning to stdout or stderr and then the exception is suppressed. +/* +** 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, jclass klazz){ - if(jObj){ - jmethodID method; - if(!klazz){ - klazz = (*env)->GetObjectClass(env, jObj); - assert(klazz); - } - method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V"); - //MARKER(("jObj=%p, klazz=%p, method=%p\n", jObj, klazz, method)); - if(method){ - ++S3JniGlobal.metrics.nDestroy; +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); - IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("xDestroy() callback"); - EXCEPTION_CLEAR; + S3JniIfThrew{ + S3JniExceptionWarnCallbackThrew("xDestroy() callback"); + S3JniExceptionClear; } }else{ - EXCEPTION_CLEAR; + /* Non-fatal. */ + S3JniExceptionClear; } } } -/** - Removes any Java references from s and clears its state. If - doXDestroy is true and s->klazz and s->jObj are not NULL, s->jObj's - s is passed to s3jni_call_xDestroy() before any references are - cleared. It is legal to call this when the object has no Java - references. +/* +** Removes any Java references from s and clears its state. If +** doXDestroy is true and s->jObj is not NULL, s->jObj's +** s is passed to s3jni_call_xDestroy() before any references are +** cleared. It is legal to call this when the object has no Java +** references. */ -static void S3JniHook_unref(JNIEnv * const env, S3JniHook * const s, int doXDestroy){ - if(doXDestroy && s->klazz && s->jObj){ - s3jni_call_xDestroy(env, s->jObj, s->klazz); +static void S3JniHook_unref_impl(JNIEnv * const env, S3JniHook * const s, + int doXDestroy){ + if( s->jObj ){ + if( doXDestroy ){ + s3jni_call_xDestroy(env, s->jObj); + } + S3JniUnrefGlobal(s->jObj); } - UNREF_G(s->jObj); - UNREF_G(s->klazz); memset(s, 0, sizeof(*s)); } +#define S3JniHook_unref(H,X) S3JniHook_unref_impl(env, (H), (X)) -/** - Clears s's state and moves it to the free-list. +/* +** Internal helper for many hook callback impls. Locks the S3JniDb +** mutex, makes a copy of src into dest, with one change: if src->jObj +** is not NULL then dest->jObj will be a new LOCAL ref to src->jObj +** instead of a copy of the prior GLOBAL ref. Then it unlocks the +** mutex. +** +** 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 is a +** GLOBAL ref (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 ){ + S3JniMutex_S3JniDb_enter; + *dest = *src; + if(dest->jObj) dest->jObj = S3JniRefLocal(dest->jObj); + S3JniMutex_S3JniDb_leave; +} +#define S3JniHook_localundup(HOOK) S3JniUnrefLocal(HOOK.jObj) + +/* +** 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. */ -FIXME_THREADING(perDb) -static void S3JniDb_set_aside(S3JniDb * const s){ - if(s){ - JNIEnv * const env = s->env; - assert(s->pDb && "Else this object is already in the free-list."); - //MARKER(("state@%p for db@%p setting aside\n", s, s->pDb)); +static void S3JniDb_clear(JNIEnv * const env, S3JniDb * const s){ + S3JniMutex_S3JniDb_assertLocker; + sqlite3_free( s->zMainDbName ); +#define UNHOOK(MEMBER) S3JniHook_unref(&s->hooks.MEMBER, 0) + UNHOOK(auth); + UNHOOK(busyHandler); + UNHOOK(collation); + 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 ); + if( s ){ + S3JniMutex_S3JniDb_assertLocker; assert(s->pPrev != s); assert(s->pNext != s); assert(s->pPrev ? (s->pPrev!=s->pNext) : 1); if(s->pNext) s->pNext->pPrev = s->pPrev; if(s->pPrev) s->pPrev->pNext = s->pNext; - else if(S3JniGlobal.perDb.aUsed == s){ + else if(SJG.perDb.aHead == s){ assert(!s->pPrev); - S3JniGlobal.perDb.aUsed = s->pNext; + SJG.perDb.aHead = s->pNext; } - sqlite3_free( s->zMainDbName ); -#define UNHOOK(MEMBER,XDESTROY) S3JniHook_unref(env, &s->MEMBER, XDESTROY) - UNHOOK(trace, 0); - UNHOOK(progress, 0); - UNHOOK(commitHook, 0); - UNHOOK(rollbackHook, 0); - UNHOOK(updateHook, 0); - UNHOOK(authHook, 0); - UNHOOK(collation, 1); - UNHOOK(collationNeeded, 1); - UNHOOK(busyHandler, 1); -#undef UNHOOK - UNREF_G(s->jDb); -#ifdef SQLITE_ENABLE_FTS5 - UNREF_G(s->jFtsApi); -#endif - memset(s, 0, sizeof(S3JniDb)); - s->pNext = S3JniGlobal.perDb.aFree; + S3JniDb_clear(env, s); + s->pNext = SJG.perDb.aFree; if(s->pNext) s->pNext->pPrev = s; - S3JniGlobal.perDb.aFree = s; - //MARKER(("%p->pPrev@%p, pNext@%p\n", s, s->pPrev, s->pNext)); - //if(s->pNext) MARKER(("next: %p->pPrev@%p\n", s->pNext, s->pNext->pPrev)); + SJG.perDb.aFree = s; } } -/** - Requires that p has been snipped from any linked list it is - in. Clears all Java refs p holds and zeroes out p. -*/ -static void S3JniEnvCache_clear(S3JniEnvCache * const p){ - JNIEnv * const env = p->env; - if(env){ - int i; - UNREF_G(p->g.cObj); - UNREF_G(p->g.cLong); - UNREF_G(p->g.cString); - UNREF_G(p->g.oCharsetUtf8); -#ifdef SQLITE_ENABLE_FTS5 - UNREF_G(p->jFtsExt); - UNREF_G(p->jPhraseIter.klazz); -#endif - for( i = 0; i < NphCache_SIZE; ++i ){ - S3JniNphCache_clear(env, &p->nph[i]); - } - memset(p, 0, sizeof(S3JniEnvCache)); - } -} - -/** - Cleans up all state in S3JniGlobal.perDb for th given JNIEnv. - Results are undefined if a Java-side db uses the API - from the given JNIEnv after this call. -*/ -FIXME_THREADING(perDb) -static void S3JniDb_free_for_env(JNIEnv *env){ - S3JniDb * ps = S3JniGlobal.perDb.aUsed; - S3JniDb * pNext = 0; - for( ; ps; ps = pNext ){ - pNext = ps->pNext; - if(ps->env == env){ - S3JniDb * const pPrev = ps->pPrev; - S3JniDb_set_aside(ps); - assert(pPrev ? pPrev->pNext!=ps : 1); - pNext = pPrev; - } - } +static void S3JniDb__set_aside(JNIEnv * const env, S3JniDb * const s){ + S3JniMutex_S3JniDb_enter; + S3JniDb__set_aside_unlocked(env, s); + S3JniMutex_S3JniDb_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. - - Also passes env to S3JniDb_free_for_env() to free up - what would otherwise be stale references. +/* +** 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 given object is +** passed over to this function, which makes it free for re-use. +** +** Requires that the Env mutex be locked. */ -static int S3JniGlobal_env_uncache(JNIEnv * const env){ - struct S3JniEnvCache * row = S3JniGlobal.envCache.aHead; +static int S3JniEnv_uncache(JNIEnv * const env){ + struct S3JniEnv * row; + S3JniMutex_Env_assertLocked; + row = SJG.envCache.aHead; for( ; row; row = row->pNext ){ if( row->env == env ){ break; } } - if( !row ) return 0; + if( !row ){ + return 0; + } if( row->pNext ) row->pNext->pPrev = row->pPrev; if( row->pPrev ) row->pPrev->pNext = row->pNext; - if( S3JniGlobal.envCache.aHead == row ){ + if( SJG.envCache.aHead == row ){ assert( !row->pPrev ); - S3JniGlobal.envCache.aHead = row->pNext; + SJG.envCache.aHead = row->pNext; } - S3JniEnvCache_clear(row); - assert( !row->pNext ); - assert( !row->pPrev ); - row->pNext = S3JniGlobal.envCache.aFree; + memset(row, 0, sizeof(S3JniEnv)); + row->pNext = SJG.envCache.aFree; if( row->pNext ) row->pNext->pPrev = row; - S3JniGlobal.envCache.aFree = row; - S3JniDb_free_for_env(env); + SJG.envCache.aFree = row; return 1; } -static void S3JniGlobal_S3JniEnvCache_clear(void){ - while( S3JniGlobal.envCache.aHead ){ - S3JniGlobal_env_uncache( S3JniGlobal.envCache.aHead->env ); - } -} - -/** - Searches the NativePointerHolder cache for the given combination. - If it finds one, it returns it as-is. If it doesn't AND the cache - has a free slot, it populates that slot with (env, zClassName, - klazz) and returns it. If the cache is full with no match it - returns NULL. - - It is up to the caller to populate the other members of the returned - object if needed. - - zClassName must be a static string so we can use its address as a - cache key. - - This simple cache catches >99% of searches in the current - (2023-07-31) tests. +/* +** Searches the NativePointerHolder cache for the given combination of +** args. It returns a cache entry 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 S3JniMutex_Nph_enter/leave. +** +** This simple cache catches >99% of searches in the current +** (2023-07-31) tests. */ -FIXME_THREADING(S3JniEnvCache) -static S3JniNphCache * S3JniGlobal_nph_cache(JNIEnv * const env, const char *zClassName){ +static S3JniNphClass * S3JniGlobal__nph(JNIEnv * const env, S3JniNphRef const* pRef){ /** - According to: + According to: https://developer.ibm.com/articles/j-jni/ @@ -1019,432 +1256,322 @@ static S3JniNphCache * S3JniGlobal_nph_cache(JNIEnv * const env, const char *zCl looking up class objects can be expensive, so they should be cached as well. */ - struct S3JniEnvCache * const envRow = S3JniGlobal_env_cache(env); - S3JniNphCache * freeSlot = 0; - S3JniNphCache * pCache = 0; - int i; - assert(envRow); - for( i = 0; i < NphCache_SIZE; ++i ){ - pCache = &envRow->nph[i]; - if(zClassName == pCache->zClassName){ - ++S3JniGlobal.metrics.nphCacheHits; -#define DUMP_NPH_CACHES 0 -#if DUMP_NPH_CACHES - MARKER(("Cache hit #%u %s klazz@%p nativePointer field@%p, ctor@%p\n", - S3JniGlobal.metrics.nphCacheHits, zClassName, pCache->klazz, pCache->fidValue, - pCache->midCtor)); -#endif - assert(pCache->klazz); - return pCache; - }else if(!freeSlot && !pCache->zClassName){ - freeSlot = pCache; + S3JniNphClass * const pNC = &SJG.nph[pRef->index]; + assert( (void*)pRef>=(void*)&S3JniNphRefs && (void*)pRef<(void*)(&S3JniNphRefs + 1) + && "pRef is out of range." ); + assert( pRef->index>=0 + && (pRef->index < (sizeof(S3JniNphRefs) / sizeof(S3JniNphRef))) ); + if( !pNC->pRef ){ + S3JniMutex_Nph_enter; + s3jni_incr( &SJG.metrics.nNphInit ); + if( !pNC->pRef ){ + jclass const klazz = (*env)->FindClass(env, pRef->zName); + S3JniExceptionIsFatal("FindClass() unexpectedly threw"); + pNC->klazz = S3JniRefGlobal(klazz); + pNC->pRef = pRef + /* Must come last to avoid a race condition where pNC->klass + can be NULL after this function returns. */; } + S3JniMutex_Nph_leave; } - if(freeSlot){ - freeSlot->zClassName = zClassName; - freeSlot->klazz = (*env)->FindClass(env, zClassName); - EXCEPTION_IS_FATAL("FindClass() unexpectedly threw"); - freeSlot->klazz = REF_G(freeSlot->klazz); - ++S3JniGlobal.metrics.nphCacheMisses; -#if DUMP_NPH_CACHES - static unsigned int cacheMisses = 0; - MARKER(("Cache miss #%u %s klazz@%p nativePointer field@%p, ctor@%p\n", - S3JniGlobal.metrics.nphCacheMisses, zClassName, freeSlot->klazz, - freeSlot->fidValue, freeSlot->midCtor)); -#endif -#undef DUMP_NPH_CACHES - }else{ - (*env)->FatalError(env, "MAINTENANCE REQUIRED: NphCache_SIZE is too low."); - } - return freeSlot; + assert( pNC->klazz ); + return pNC; } -/** - Returns the ID of the "nativePointer" field from the given - NativePointerHolder<T> class. - */ -static jfieldID NativePointerHolder_getField(JNIEnv * const env, jclass klazz){ - jfieldID rv = (*env)->GetFieldID(env, klazz, "nativePointer", "J"); - EXCEPTION_IS_FATAL("Code maintenance required: missing nativePointer field."); - return rv; -} +#define S3JniGlobal_nph(PREF) S3JniGlobal__nph(env, PREF) -/** - Sets a native ptr value in NativePointerHolder object ppOut. - zClassName must be a static string so we can use its address - as a cache key. +/* +** Common code for accessor functions for NativePointerHolder and +** OutputPointer types. pRef must be a pointer from S3JniNphRefs. 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[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 void NativePointerHolder_set(JNIEnv * env, jobject ppOut, const void * p, - const char *zClassName){ - jfieldID setter = 0; - S3JniNphCache * const pCache = S3JniGlobal_nph_cache(env, zClassName); - if(pCache && pCache->klazz && pCache->fidValue){ - assert(zClassName == pCache->zClassName); - setter = pCache->fidValue; - assert(setter); - }else{ - jclass const klazz = - pCache ? pCache->klazz : (*env)->GetObjectClass(env, ppOut); - setter = NativePointerHolder_getField(env, klazz); - if(pCache){ - assert(pCache->klazz); - assert(!pCache->fidValue); - assert(zClassName == pCache->zClassName); - pCache->fidValue = setter; +static jfieldID s3jni_nphop_field(JNIEnv * const env, S3JniNphRef const* pRef){ + S3JniNphClass * const pNC = S3JniGlobal_nph(pRef); + + if( !pNC->fidValue ){ + S3JniMutex_Nph_enter; + s3jni_incr( &SJG.metrics.nNphInit ); + if( !pNC->fidValue ){ + pNC->fidValue = (*env)->GetFieldID(env, pNC->klazz, + pRef->zMember, pRef->zTypeSig); + S3JniExceptionIsFatal("Code maintenance required: missing " + "required S3JniNphClass::fidValue."); } + S3JniMutex_Nph_leave; } - (*env)->SetLongField(env, ppOut, setter, (jlong)p); - EXCEPTION_IS_FATAL("Could not set NativePointerHolder.nativePointer."); + assert( pNC->fidValue ); + return pNC->fidValue; } -/** - Fetches a native ptr value from NativePointerHolder object ppOut. - zClassName must be a static string so we can use its address as a - cache key. +/* +** Sets a native ptr value in NativePointerHolder object ppOut. +** zClassName must be a static string so we can use its address +** as a cache key. */ -static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, const char *zClassName){ +static void NativePointerHolder__set(JNIEnv * env, S3JniNphRef const* pRef, + jobject ppOut, const void * p){ + jfieldID const fid = s3jni_nphop_field(env, pRef); + + S3JniMutex_Nph_enter; + (*env)->SetLongField(env, ppOut, fid, (jlong)p); + S3JniMutex_Nph_leave; + S3JniExceptionIsFatal("Could not set NativePointerHolder.nativePointer."); +} + +#define NativePointerHolder_set(PREF,PPOUT,P) \ + NativePointerHolder__set(env, PREF, PPOUT, P) + +/* +** Fetches a native ptr value from NativePointerHolder object pObj, +** which must be of the native type described by pRef. This is a +** no-op if pObj is NULL. +*/ +static void * NativePointerHolder__get(JNIEnv * env, jobject pObj, + S3JniNphRef const* pRef){ + void * rv = 0; if( pObj ){ - jfieldID getter = 0; - void * rv = 0; - S3JniNphCache * const pCache = S3JniGlobal_nph_cache(env, zClassName); - if(pCache && pCache->fidValue){ - getter = pCache->fidValue; - }else{ - jclass const klazz = - pCache ? pCache->klazz : (*env)->GetObjectClass(env, pObj); - getter = NativePointerHolder_getField(env, klazz); - if(pCache){ - assert(pCache->klazz); - assert(zClassName == pCache->zClassName); - pCache->fidValue = getter; - } - } - rv = (void*)(*env)->GetLongField(env, pObj, getter); - IFTHREW_REPORT; - return rv; - }else{ - return 0; + jfieldID const fid = s3jni_nphop_field(env, pRef); + S3JniMutex_Nph_enter; + rv = (void*)(*env)->GetLongField(env, pObj, fid); + S3JniMutex_Nph_leave; + S3JniExceptionIsFatal("Cannot fetch NativePointerHolder.nativePointer."); } + return rv; } -/** - Extracts the new S3JniDb instance from the free-list, or - allocates one if needed, associats it with pDb, and returns. - Returns NULL on OOM. pDb MUST be associated with jDb via - NativePointerHolder_set(). +#define NativePointerHolder_get(JOBJ,NPHREF) \ + NativePointerHolder__get(env, (JOBJ), (NPHREF)) + +/* +** Extracts the new S3JniDb instance from the free-list, or allocates +** one if needed, associats it with pDb, and returns. Returns NULL on +** OOM. pDb MUST, on success of the calling operation, subsequently be +** associated with jDb via NativePointerHolder_set(). */ -static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb, - jobject jDb){ +static S3JniDb * S3JniDb_alloc(JNIEnv * const env, jobject jDb){ S3JniDb * rv; - if(S3JniGlobal.perDb.aFree){ - rv = S3JniGlobal.perDb.aFree; - //MARKER(("state@%p for db allocating for db@%p from free-list\n", rv, pDb)); - //MARKER(("%p->pPrev@%p, pNext@%p\n", rv, rv->pPrev, rv->pNext)); - S3JniGlobal.perDb.aFree = rv->pNext; + S3JniMutex_S3JniDb_enter; + if( SJG.perDb.aFree ){ + rv = SJG.perDb.aFree; + SJG.perDb.aFree = rv->pNext; assert(rv->pNext != rv); - assert(rv->pPrev != rv); - assert(rv->pPrev ? (rv->pPrev!=rv->pNext) : 1); - if(rv->pNext){ + assert(!rv->pPrev); + if( rv->pNext ){ assert(rv->pNext->pPrev == rv); - assert(rv->pPrev ? (rv->pNext == rv->pPrev->pNext) : 1); rv->pNext->pPrev = 0; rv->pNext = 0; } + s3jni_incr( &SJG.metrics.nPdbRecycled ); }else{ - rv = s3jni_malloc(env, sizeof(S3JniDb)); - //MARKER(("state@%p for db allocating for db@%p from heap\n", rv, pDb)); - if(rv){ + rv = s3jni_malloc( sizeof(S3JniDb)); + if( rv ){ memset(rv, 0, sizeof(S3JniDb)); + s3jni_incr( &SJG.metrics.nPdbAlloc ); } } - if(rv){ - rv->pNext = S3JniGlobal.perDb.aUsed; - S3JniGlobal.perDb.aUsed = rv; - if(rv->pNext){ + if( rv ){ + rv->pNext = SJG.perDb.aHead; + SJG.perDb.aHead = rv; + if( rv->pNext ){ assert(!rv->pNext->pPrev); rv->pNext->pPrev = rv; } - rv->jDb = REF_G(jDb); - rv->pDb = pDb; - rv->env = env; + rv->jDb = S3JniRefGlobal(jDb); } + S3JniMutex_S3JniDb_leave; return rv; } -#if 0 -static void S3JniDb_dump(S3JniDb *s){ - MARKER(("S3JniDb->env @ %p\n", s->env)); - MARKER(("S3JniDb->pDb @ %p\n", s->pDb)); - MARKER(("S3JniDb->trace.jObj @ %p\n", s->trace.jObj)); - MARKER(("S3JniDb->progress.jObj @ %p\n", s->progress.jObj)); - MARKER(("S3JniDb->commitHook.jObj @ %p\n", s->commitHook.jObj)); - MARKER(("S3JniDb->rollbackHook.jObj @ %p\n", s->rollbackHook.jObj)); - MARKER(("S3JniDb->busyHandler.jObj @ %p\n", s->busyHandler.jObj)); - MARKER(("S3JniDb->env @ %p\n", s->env)); -} -#endif +/* Short-lived code consolidator. */ +#define S3JniDb_search \ + s = SJG.perDb.aHead; \ + for( ; pDb && s; s = s->pNext){ \ + if( s->pDb == pDb ) break; \ + } -/** - Returns the S3JniDb object for the given db. If allocIfNeeded is - true then a new instance will be allocated if no mapping currently - exists, else NULL is returned if no mapping is found. - - The 3rd and 4th args should normally only be non-0 for - sqlite3_open(_v2)(). For most other cases, they must be 0, in which - case the db handle will be fished out of the jDb object and NULL is - returned if jDb does not have any associated S3JniDb. - - If called with a NULL jDb and non-NULL pDb then allocIfNeeded MUST - be false and it will look for a matching db object. That usage is - required for functions, like sqlite3_context_db_handle(), which - return a (sqlite3*) but do not take one as an argument. +/* +** Returns the S3JniDb object for the given org.sqlite.jni.sqlite3 +** object, or NULL if jDb is NULL, no pointer can be extracted +** from it, or no matching entry can be found. +** +** Requires locking the S3JniDb mutex. */ -FIXME_THREADING(S3JniEnvCache) -FIXME_THREADING(perDb) -static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, - sqlite3 *pDb, int allocIfNeeded){ - S3JniDb * s = S3JniGlobal.perDb.aUsed; - if(!jDb){ - if(pDb){ - assert(!allocIfNeeded); - }else{ - return 0; - } - } - assert(allocIfNeeded ? !!pDb : 1); - if(!allocIfNeeded && !pDb){ - pDb = PtrGet_sqlite3(jDb); - } - for( ; pDb && s; s = s->pNext){ - if(s->pDb == pDb) return s; - } - if(allocIfNeeded){ - s = S3JniDb_alloc(env, pDb, jDb); - } +static S3JniDb * S3JniDb__from_java(JNIEnv * const env, jobject jDb){ + S3JniDb * s = 0; + sqlite3 * pDb = 0; + + S3JniMutex_S3JniDb_enter; + if( jDb ) pDb = PtrGet_sqlite3(jDb); + S3JniDb_search; + S3JniMutex_S3JniDb_leave; return s; } -#if 0 -/** - An alternative form which searches for the S3JniDb instance for - pDb with no JNIEnv-specific info. This can be (but _should_ it be?) - called from the context of a separate JNIEnv than the one mapped - to in the returned object. Returns 0 if no match is found. -*/ -FIXME_THREADING(perDb) -static S3JniDb * S3JniDb_for_db2(sqlite3 *pDb){ - S3JniDb * s = S3JniGlobal.perDb.aUsed; - for( ; pDb && s; s = s->pNext){ - if(s->pDb == pDb) return s; - } - return 0; +/* An experiment */ +//#define CLOSE_DB_LOCKED +#if defined(CLOSE_DB_LOCKED) +static S3JniDb * S3JniDb__from_java_unlocked(JNIEnv * const env, jobject jDb){ + S3JniDb * s = 0; + sqlite3 * pDb = 0; + + S3JniMutex_S3JniDb_assertLocker; + if( jDb ) pDb = PtrGet_sqlite3(jDb); + S3JniDb_search; + return s; } #endif -#if S3JNI_ENABLE_AUTOEXT -/** - Unlink ax from S3JniGlobal.autoExt and free it. +/* +** Returns the S3JniDb object for the sqlite3 object, or NULL if pDb +** is NULL, or no matching entry +** can be found. +** +** Requires locking the S3JniDb mutex. */ -static void S3JniAutoExtension_free(JNIEnv * const env, - S3JniAutoExtension * const ax){ - if( ax ){ - if( ax->pNext ) ax->pNext->pPrev = ax->pPrev; - if( ax == S3JniGlobal.autoExt.pHead ){ - assert( !ax->pNext ); - S3JniGlobal.autoExt.pHead = ax->pNext; - }else if( ax->pPrev ){ - ax->pPrev->pNext = ax->pNext; - } - ax->pNext = ax->pPrev = 0; - UNREF_G(ax->jObj); - sqlite3_free(ax); - } +static S3JniDb * S3JniDb__from_c(JNIEnv * const env, sqlite3 *pDb){ + S3JniDb * s = 0; + + S3JniMutex_S3JniDb_enter; + S3JniDb_search; + S3JniMutex_S3JniDb_leave; + return s; } -/** - Allocates a new auto extension and plugs it in to S3JniGlobal.autoExt. - Returns 0 on OOM or if there is an error collecting the required - state from jAutoExt (which must be an AutoExtension object). +#define S3JniDb_from_java(jObject) S3JniDb__from_java(env,(jObject)) +#define S3JniDb_from_c(sqlite3Ptr) S3JniDb__from_c(env,(sqlite3Ptr)) + +/* +** Unref any Java-side state in (S3JniAutoExtension*) AX and zero out +** AX. */ -static S3JniAutoExtension * S3JniAutoExtension_alloc(JNIEnv *const env, - jobject const jAutoExt){ - S3JniAutoExtension * const ax = sqlite3_malloc(sizeof(*ax)); - if( ax ){ - jclass klazz; - memset(ax, 0, sizeof(*ax)); - klazz = (*env)->GetObjectClass(env, jAutoExt); - if(!klazz){ - S3JniAutoExtension_free(env, ax); - return 0; - } - ax->midFunc = (*env)->GetMethodID(env, klazz, "xEntryPoint", - "(Lorg/sqlite/jni/sqlite3;)I"); - if(!ax->midFunc){ - MARKER(("Error getting xEntryPoint(sqlite3) from object.")); - S3JniAutoExtension_free(env, ax); - return 0; - } - ax->jObj = REF_G(jAutoExt); - ax->pNext = S3JniGlobal.autoExt.pHead; - if( ax->pNext ) ax->pNext->pPrev = ax; - S3JniGlobal.autoExt.pHead = ax; - } - return ax; -} -#endif /* S3JNI_ENABLE_AUTOEXT */ +#define S3JniAutoExtension_clear(AX) S3JniHook_unref(AX, 0); -/** - Requires that jCx be a Java-side sqlite3_context wrapper for pCx. - This function calls sqlite3_aggregate_context() to allocate a tiny - sliver of memory, the address of which is set in - jCx->aggregateContext. The memory is only used as a key for - mapping client-side results of aggregate result sets across - calls to the UDF's callbacks. - - isFinal must be 1 for xFinal() calls and 0 for all others, the - difference being that the xFinal() invocation will not allocate - new memory if it was not already, resulting in a value of 0 - for jCx->aggregateContext. - - Returns 0 on success. Returns SQLITE_NOMEM on allocation error, - noting that it will not allocate when isFinal is true. It returns - SQLITE_ERROR if there's a serious internal error in dealing with - the JNI state. +/* +** 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 udf_setAggregateContext(JNIEnv * env, jobject jCx, - sqlite3_context * pCx, - int isFinal){ - jfieldID member; - void * pAgg; - int rc = 0; - S3JniNphCache * const pCache = - S3JniGlobal_nph_cache(env, S3JniClassNames.sqlite3_context); - if(pCache && pCache->klazz && pCache->fidSetAgg){ - member = pCache->fidSetAgg; - assert(member); - }else{ - jclass const klazz = - pCache ? pCache->klazz : (*env)->GetObjectClass(env, jCx); - member = (*env)->GetFieldID(env, klazz, "aggregateContext", "J"); - if( !member ){ - IFTHREW{ EXCEPTION_REPORT; EXCEPTION_CLEAR; } - return s3jni_db_error(sqlite3_context_db_handle(pCx), - SQLITE_ERROR, - "Internal error: cannot find " - "sqlite3_context::aggregateContext field."); - } - if(pCache){ - assert(pCache->klazz); - assert(!pCache->fidSetAgg); - pCache->fidSetAgg = member; - } - } - pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : 4); - if( pAgg || isFinal ){ - (*env)->SetLongField(env, jCx, member, (jlong)pAgg); - }else{ - assert(!pAgg); - rc = SQLITE_NOMEM; +static int S3JniAutoExtension_init(JNIEnv *const env, + S3JniAutoExtension * const ax, + jobject const jAutoExt){ + jclass const klazz = (*env)->GetObjectClass(env, jAutoExt); + + S3JniMutex_Ext_assertLocker; + ax->midCallback = (*env)->GetMethodID(env, klazz, "call", + "(Lorg/sqlite/jni/sqlite3;)I"); + S3JniUnrefLocal(klazz); + S3JniExceptionWarnIgnore; + if( !ax->midCallback ){ + S3JniAutoExtension_clear(ax); + return SQLITE_ERROR; } - return rc; + ax->jObj = S3JniRefGlobal(jAutoExt); + return 0; } -/** - Common init for OutputPointer_set_Int32() and friends. zClassName must be a - pointer from S3JniClassNames. jOut must be an instance of that - class. Fetches the jfieldID for jOut's [value] property, which must - be of the type represented by the JNI type signature zTypeSig, and - stores it in pFieldId. Fails fatally if the property is not found, - as that presents a serious internal misuse. - - Property lookups are cached on a per-zClassName basis. Do not use - this routine with the same zClassName but different zTypeSig: it - will misbehave. +/* +** Sets the value property of the OutputPointer.Int32 jOut object to +** v. */ -static void setupOutputPointer(JNIEnv * const env, const char *zClassName, - const char * const zTypeSig, - jobject const jOut, jfieldID * const pFieldId){ - jfieldID setter = 0; - S3JniNphCache * const pCache = - S3JniGlobal_nph_cache(env, zClassName); - if(pCache && pCache->klazz && pCache->fidValue){ - setter = pCache->fidValue; - }else{ - const jclass klazz = (*env)->GetObjectClass(env, jOut); - /*MARKER(("%s => %s\n", zClassName, zTypeSig));*/ - setter = (*env)->GetFieldID(env, klazz, "value", zTypeSig); - EXCEPTION_IS_FATAL("setupOutputPointer() could not find OutputPointer.*.value"); - if(pCache){ - assert(!pCache->fidValue); - pCache->fidValue = setter; - } - } - *pFieldId = setter; +static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut, + int v){ + (*env)->SetIntField(env, jOut, + s3jni_nphop_field( + env, &S3JniNphRefs.OutputPointer_Int32 + ), (jint)v); + S3JniExceptionIsFatal("Cannot set OutputPointer.Int32.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){ - jfieldID setter = 0; - setupOutputPointer(env, S3JniClassNames.OutputPointer_Int32, "I", jOut, &setter); - (*env)->SetIntField(env, jOut, setter, (jint)v); - EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int32.value"); +/* +** Sets the value property of the OutputPointer.Int64 jOut object to +** v. +*/ +static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, + jlong v){ + (*env)->SetLongField(env, jOut, + s3jni_nphop_field( + env, &S3JniNphRefs.OutputPointer_Int64 + ), v); + S3JniExceptionIsFatal("Cannot set OutputPointer.Int64.value"); } -/* Sets the value property of the OutputPointer.Int64 jOut object - to v. */ -static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, jlong v){ - jfieldID setter = 0; - setupOutputPointer(env, S3JniClassNames.OutputPointer_Int64, "J", jOut, &setter); - (*env)->SetLongField(env, jOut, setter, v); - EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int64.value"); +/* +** Internal helper for OutputPointer_set_TYPE() where TYPE is an +** Object type. +*/ +static void OutputPointer_set_obj(JNIEnv * const env, + S3JniNphRef const * const pRef, + jobject const jOut, + jobject v){ + (*env)->SetObjectField(env, jOut, s3jni_nphop_field(env, pRef), v); + S3JniExceptionIsFatal("Cannot set OutputPointer.T.value"); } +/* +** Sets the value property of the OutputPointer.sqlite3 jOut object to +** v. +*/ static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut, - jobject jDb){ - jfieldID setter = 0; - setupOutputPointer(env, S3JniClassNames.OutputPointer_sqlite3, - "Lorg/sqlite/jni/sqlite3;", jOut, &setter); - (*env)->SetObjectField(env, jOut, setter, jDb); - EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3.value"); + jobject jDb){ + OutputPointer_set_obj(env, &S3JniNphRefs.OutputPointer_sqlite3, jOut, jDb); } +/* +** Sets the value property of the OutputPointer.sqlite3_stmt jOut object to +** v. +*/ static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOut, - jobject jStmt){ - jfieldID setter = 0; - setupOutputPointer(env, S3JniClassNames.OutputPointer_sqlite3_stmt, - "Lorg/sqlite/jni/sqlite3_stmt;", jOut, &setter); - (*env)->SetObjectField(env, jOut, setter, jStmt); - EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3_stmt.value"); + jobject jStmt){ + OutputPointer_set_obj(env, &S3JniNphRefs.OutputPointer_sqlite3_stmt, jOut, jStmt); } +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +/* +** Sets the value property of the OutputPointer.sqlite3_value jOut object to +** v. +*/ +static void OutputPointer_set_sqlite3_value(JNIEnv * const env, jobject const jOut, + jobject jValue){ + OutputPointer_set_obj(env, &S3JniNphRefs.OutputPointer_sqlite3_value, jOut, jValue); +} +#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ + #ifdef SQLITE_ENABLE_FTS5 #if 0 -/* Sets the value property of the OutputPointer.ByteArray jOut object - to v. */ +/* +** Sets the value property of the OutputPointer.ByteArray jOut object +** to v. +*/ static void OutputPointer_set_ByteArray(JNIEnv * const env, jobject const jOut, - jbyteArray const v){ - jfieldID setter = 0; - setupOutputPointer(env, S3JniClassNames.OutputPointer_ByteArray, "[B", - jOut, &setter); - (*env)->SetObjectField(env, jOut, setter, v); - EXCEPTION_IS_FATAL("Cannot set OutputPointer.ByteArray.value"); + jbyteArray const v){ + OutputPointer_set_obj(env, &S3JniNphRefs.OutputPointer_ByteArray, jOut, v); } #endif -/* Sets the value property of the OutputPointer.String jOut object - to v. */ + +/* +** Sets the value property of the OutputPointer.String jOut object to +** v. +*/ static void OutputPointer_set_String(JNIEnv * const env, jobject const jOut, - jstring const v){ - jfieldID setter = 0; - setupOutputPointer(env, S3JniClassNames.OutputPointer_String, - "Ljava/lang/String;", jOut, &setter); - (*env)->SetObjectField(env, jOut, setter, v); - EXCEPTION_IS_FATAL("Cannot set OutputPointer.String.value"); + jstring const v){ + OutputPointer_set_obj(env, &S3JniNphRefs.OutputPointer_String, jOut, v); } #endif /* SQLITE_ENABLE_FTS5 */ +/* +** Returns true if eTextRep is a valid sqlite3 encoding constant, else +** returns false. +*/ static int encodingTypeIsValid(int eTextRep){ - switch(eTextRep){ + switch( eTextRep ){ case SQLITE_UTF8: case SQLITE_UTF16: case SQLITE_UTF16LE: case SQLITE_UTF16BE: return 1; @@ -1453,216 +1580,164 @@ static int encodingTypeIsValid(int eTextRep){ } } +/* +** Proxy for Java-side CollationCallback.xCompare() callbacks. +*/ static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs, int nRhs, const void *rhs){ S3JniDb * const ps = pArg; - JNIEnv * env = ps->env; + S3JniDeclLocal_env; jint rc = 0; - jbyteArray jbaLhs = (*env)->NewByteArray(env, (jint)nLhs); - jbyteArray jbaRhs = jbaLhs ? (*env)->NewByteArray(env, (jint)nRhs) : NULL; - //MARKER(("native xCompare nLhs=%d nRhs=%d\n", nLhs, nRhs)); - if(!jbaRhs){ - s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); - return 0; - //(*env)->FatalError(env, "Out of memory. Cannot allocate arrays for collation."); - } - (*env)->SetByteArrayRegion(env, jbaLhs, 0, (jint)nLhs, (const jbyte*)lhs); - (*env)->SetByteArrayRegion(env, jbaRhs, 0, (jint)nRhs, (const jbyte*)rhs); - rc = (*env)->CallIntMethod(env, ps->collation.jObj, ps->collation.midCallback, - jbaLhs, jbaRhs); - EXCEPTION_IGNORE; - UNREF_L(jbaLhs); - UNREF_L(jbaRhs); + S3JniHook hook; + + S3JniHook_localdup(env, &ps->hooks.collation, &hook ); + if( hook.jObj ){ + jbyteArray jbaLhs = s3jni_new_jbyteArray(lhs, (jint)nLhs); + jbyteArray jbaRhs = jbaLhs + ? s3jni_new_jbyteArray(rhs, (jint)nRhs) : 0; + if( !jbaRhs ){ + S3JniUnrefLocal(jbaLhs); + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + return 0; + } + rc = (*env)->CallIntMethod(env, ps->hooks.collation.jObj, + ps->hooks.collation.midCallback, + jbaLhs, jbaRhs); + S3JniExceptionIgnore; + S3JniUnrefLocal(jbaLhs); + S3JniUnrefLocal(jbaRhs); + S3JniHook_localundup(hook); + } return (int)rc; } /* Collation finalizer for use by the sqlite3 internals. */ static void CollationState_xDestroy(void *pArg){ S3JniDb * const ps = pArg; - S3JniHook_unref( ps->env, &ps->collation, 1 ); -} - -/* State for sqlite3_result_java_object() and - sqlite3_value_java_object(). */ -typedef struct { - /* The JNI docs say: - - https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html - - > The VM is guaranteed to pass the same interface pointer to a - native method when it makes multiple calls to the native method - from the same Java thread. - - Per the accompanying diagram, the "interface pointer" is the - pointer-to-pointer which is passed to all JNI calls - (`JNIEnv *env`), implying that we need to be caching that. The - verbiage "interface pointer" implies, however, that we should be - storing the dereferenced `(*env)` pointer. - - This posts claims it's unsafe to cache JNIEnv at all, even when - it's always used in the same thread: - - https://stackoverflow.com/questions/12420463 - - And this one seems to contradict that: - - https://stackoverflow.com/questions/13964608 - - For later reference: - - https://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/design.html#wp1242 - - https://developer.android.com/training/articles/perf-jni - - The later has the following say about caching: + S3JniDeclLocal_env; - > If performance is important, it's useful to look the - [class/method ID] values up once and cache the results in your - native code. Because there is a limit of one JavaVM per - process, it's reasonable to store this data in a static local - structure. ... The class references, field IDs, and method IDs - are guaranteed valid until the class is unloaded. Classes are - only unloaded if all classes associated with a ClassLoader can - be garbage collected, which is rare but will not be impossible - in Android. Note however that the jclass is a class reference - and must be protected with a call to NewGlobalRef (see the next - section). - */ - JNIEnv * env; - jobject jObj; -} ResultJavaVal; + S3JniMutex_S3JniDb_enter; + S3JniHook_unref(&ps->hooks.collation, 1); + S3JniMutex_S3JniDb_leave; +} /* For use with sqlite3_result/value_pointer() */ -#define RESULT_JAVA_VAL_STRING "ResultJavaVal" +#define ResultJavaValuePtrStr "org.sqlite.jni.ResultJavaVal" -static ResultJavaVal * ResultJavaVal_alloc(JNIEnv * const env, jobject jObj){ - ResultJavaVal * rv = sqlite3_malloc(sizeof(ResultJavaVal)); - if(rv){ - rv->env = env; - rv->jObj = jObj ? REF_G(jObj) : 0; - } - return rv; -} - -static void ResultJavaVal_finalizer(void *v){ - if(v){ - ResultJavaVal * const rv = (ResultJavaVal*)v; - if(rv->jObj) (*(rv->env))->DeleteGlobalRef(rv->env, rv->jObj); - sqlite3_free(rv); +/* +** If v is not NULL, it must be a jobject global reference. Its +** reference is relinquished and v is freed. +*/ +static void ResultJavaValue_finalizer(void *v){ + if( v ){ + S3JniDeclLocal_env; + S3JniUnrefGlobal((jobject)v); } } -/** - Returns a new Java instance of the class named by zClassName, which - MUST be interface-compatible with NativePointerHolder and MUST have - a no-arg constructor. The NativePointerHolder_set() method is - passed the new Java object and pNative. Hypothetically returns NULL - if Java fails to allocate, but the JNI docs are not entirely clear - on that detail. - - Always use a static string pointer from S3JniClassNames for the 2nd - argument so that we can use its address as a cache key. +/* +** Returns a new Java instance of the class named by zClassName, which +** MUST be interface-compatible with NativePointerHolder and MUST have +** a no-arg constructor. The NativePointerHolder_set() method is +** passed the new Java object and pNative. Hypothetically returns NULL +** if Java fails to allocate, but the JNI docs are not entirely clear +** on that detail. +** +** Always use a static pointer from the S3JniNphRefs struct for the 2nd +** argument so that we can use pRef->index as an O(1) cache key. */ -static jobject new_NativePointerHolder_object(JNIEnv * const env, const char *zClassName, +static jobject new_NativePointerHolder_object(JNIEnv * const env, S3JniNphRef const * pRef, const void * pNative){ jobject rv = 0; - jclass klazz = 0; - jmethodID ctor = 0; - S3JniNphCache * const pCache = - S3JniGlobal_nph_cache(env, zClassName); - if(pCache && pCache->midCtor){ - assert( pCache->klazz ); - klazz = pCache->klazz; - ctor = pCache->midCtor; - }else{ - klazz = pCache - ? pCache->klazz - : (*env)->FindClass(env, zClassName); - ctor = klazz ? (*env)->GetMethodID(env, klazz, "<init>", "()V") : 0; - EXCEPTION_IS_FATAL("Cannot find constructor for class."); - if(pCache){ - assert(zClassName == pCache->zClassName); - assert(pCache->klazz); - assert(!pCache->midCtor); - pCache->midCtor = ctor; + S3JniNphClass * const pNC = S3JniGlobal_nph(pRef); + if( !pNC->midCtor ){ + S3JniMutex_Nph_enter; + s3jni_incr( &SJG.metrics.nNphInit ); + if( !pNC->midCtor ){ + pNC->midCtor = (*env)->GetMethodID(env, pNC->klazz, "<init>", "()V"); + S3JniExceptionIsFatal("Cannot find constructor for class."); } + S3JniMutex_Nph_leave; } - assert(klazz); - assert(ctor); - rv = (*env)->NewObject(env, klazz, ctor); - EXCEPTION_IS_FATAL("No-arg constructor threw."); - if(rv) NativePointerHolder_set(env, rv, pNative, zClassName); + 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_sqlite3_wrapper(JNIEnv * const env, sqlite3 *sv){ - return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3, sv); + return new_NativePointerHolder_object(env, &S3JniNphRefs.sqlite3, sv); } static inline jobject new_sqlite3_context_wrapper(JNIEnv * const env, sqlite3_context *sv){ - return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_context, sv); + return new_NativePointerHolder_object(env, &S3JniNphRefs.sqlite3_context, sv); } static inline jobject new_sqlite3_stmt_wrapper(JNIEnv * const env, sqlite3_stmt *sv){ - return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_stmt, sv); + return new_NativePointerHolder_object(env, &S3JniNphRefs.sqlite3_stmt, sv); } static inline jobject new_sqlite3_value_wrapper(JNIEnv * const env, sqlite3_value *sv){ - return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_value, sv); + return new_NativePointerHolder_object(env, &S3JniNphRefs.sqlite3_value, sv); } -enum UDFType { - UDF_SCALAR = 1, - UDF_AGGREGATE, - UDF_WINDOW, - UDF_UNKNOWN_TYPE/*for error propagation*/ -}; - +/* 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**);*/ -/** - State for binding Java-side UDFs. +/* +** 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(). */ -typedef struct S3JniUdf S3JniUdf; -struct S3JniUdf { - JNIEnv * env; /* env registered from */; - jobject jObj /* SQLFunction instance */; - jclass klazz /* jObj's class */; - char * zFuncName /* Only for error reporting and debug logging */; - enum UDFType type; - /** Method IDs for the various UDF methods. */ - jmethodID jmidxFunc; - jmethodID jmidxStep; - jmethodID jmidxFinal; - jmethodID jmidxValue; - jmethodID jmidxInverse; -}; - static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){ - S3JniUdf * const s = sqlite3_malloc(sizeof(S3JniUdf)); - if(s){ + S3JniUdf * s = 0; + + S3JniMutex_Global_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); + } + S3JniMutex_Global_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/sqlite3_context;[Lorg/sqlite/jni/sqlite3_value;)V"; const char * zFV = /* signature for xFinal, xValue */ "(Lorg/sqlite/jni/sqlite3_context;)V"; - memset(s, 0, sizeof(S3JniUdf)); - s->env = env; - s->jObj = REF_G(jObj); - s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); -#define FGET(FuncName,FuncType,Field) \ - s->Field = (*env)->GetMethodID(env, s->klazz, FuncName, FuncType); \ - if(!s->Field) (*env)->ExceptionClear(env) + 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 - if(s->jmidxFunc) s->type = UDF_SCALAR; - else if(s->jmidxStep && s->jmidxFinal){ + + S3JniUnrefLocal(klazz); + if( s->jmidxFunc ) s->type = UDF_SCALAR; + else if( s->jmidxStep && s->jmidxFinal ){ s->type = s->jmidxValue ? UDF_WINDOW : UDF_AGGREGATE; }else{ s->type = UDF_UNKNOWN_TYPE; @@ -1671,40 +1746,49 @@ static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){ return s; } -static void S3JniUdf_free(S3JniUdf * s){ - JNIEnv * const env = s->env; - if(env){ - //MARKER(("UDF cleanup: %s\n", s->zFuncName)); - s3jni_call_xDestroy(env, s->jObj, s->klazz); - UNREF_G(s->jObj); - UNREF_G(s->klazz); - } +/* +** 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 ); + s3jni_call_xDestroy(env, s->jObj); + S3JniUnrefGlobal(s->jObj); sqlite3_free(s->zFuncName); - sqlite3_free(s); + assert( !s->pNext ); + memset(s, 0, sizeof(*s)); + if( cacheIt ){ + S3JniMutex_Global_enter; + s->pNext = S3JniGlobal.udf.aFree; + S3JniGlobal.udf.aFree = s; + S3JniMutex_Global_leave; + }else{ + sqlite3_free( s ); + } } +/* Finalizer for sqlite3_create_function() and friends. */ static void S3JniUdf_finalizer(void * s){ - //MARKER(("UDF finalizer @ %p\n", s)); - if(s) S3JniUdf_free((S3JniUdf*)s); + S3JniUdf_free(s3jni_env(), (S3JniUdf*)s, 1); } -/** - Helper for processing args to UDF handlers - with signature (sqlite3_context*,int,sqlite3_value**). +/* +** Helper for processing args to UDF handlers with signature +** (sqlite3_context*,int,sqlite3_value**). */ typedef struct { - jobject jcx; - jobjectArray jargv; + jobject jcx /* sqlite3_context */; + jobjectArray jargv /* sqlite3_value[] */; } udf_jargs; -/** - Converts the given (cx, argc, argv) into arguments for the given - UDF, placing the result in the final argument. Returns 0 on - success, SQLITE_NOMEM on allocation error. - - TODO: see what we can do to optimize the - new_sqlite3_value_wrapper() call. e.g. find the ctor a single time - and call it here, rather than looking it up repeatedly. +/* +** 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. */ static int udf_args(JNIEnv *env, sqlite3_context * const cx, @@ -1715,139 +1799,215 @@ static int udf_args(JNIEnv *env, jint i; *jCx = 0; *jArgv = 0; - if(!jcx) goto error_oom; - ja = (*env)->NewObjectArray(env, argc, - S3JniGlobal_env_cache(env)->g.cObj, - NULL); - if(!ja) goto error_oom; + if( !jcx ) goto error_oom; + ja = (*env)->NewObjectArray( + env, argc, S3JniGlobal_nph(&S3JniNphRefs.sqlite3_value)->klazz, + NULL); + s3jni_oom_check( ja ); + if( !ja ) goto error_oom; for(i = 0; i < argc; ++i){ jobject jsv = new_sqlite3_value_wrapper(env, argv[i]); - if(!jsv) goto error_oom; + if( !jsv ) goto error_oom; (*env)->SetObjectArrayElement(env, ja, i, jsv); - UNREF_L(jsv)/*array has a ref*/; + S3JniUnrefLocal(jsv)/*ja has a ref*/; } *jCx = jcx; *jArgv = ja; return 0; error_oom: - sqlite3_result_error_nomem(cx); - UNREF_L(jcx); - UNREF_L(ja); + S3JniUnrefLocal(jcx); + S3JniUnrefLocal(ja); return SQLITE_NOMEM; } -static int udf_report_exception(sqlite3_context * cx, - const char *zFuncName, - const char *zFuncType){ - int rc; - char * z = - sqlite3_mprintf("Client-defined function %s.%s() threw. It should " - "not do that.", - zFuncName ? zFuncName : "<unnamed>", zFuncType); - if(z){ - sqlite3_result_error(cx, z, -1); - sqlite3_free(z); - rc = SQLITE_ERROR; +/* +** 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{ - sqlite3_result_error_nomem(cx); - rc = SQLITE_NOMEM; + 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. +/* +** Sets up the state for calling a Java-side xFunc/xStep/xInverse() +** UDF, calls it, and returns 0 on success. */ -static int udf_xFSI(sqlite3_context* pCx, int argc, - sqlite3_value** argv, - S3JniUdf * s, - jmethodID xMethodID, - const char * zFuncType){ - JNIEnv * const env = s->env; +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(s->env, pCx, argc, argv, &args.jcx, &args.jargv); - //MARKER(("%s.%s() pCx = %p\n", s->zFuncName, zFuncType, pCx)); - if(rc) return rc; - //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType)); - if( UDF_SCALAR != s->type ){ - rc = udf_setAggregateContext(env, args.jcx, pCx, 0); - } + 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); - IFTHREW{ - rc = udf_report_exception(pCx, s->zFuncName, zFuncType); + S3JniIfThrew{ + rc = udf_report_exception(env, 'F'==zFuncType[1]/*xFunc*/, pCx, + s->zFuncName, zFuncType); } } - UNREF_L(args.jcx); - UNREF_L(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. +/* +** Sets up the state for calling a Java-side xFinal/xValue() UDF, +** calls it, and returns 0 on success. */ static int udf_xFV(sqlite3_context* cx, S3JniUdf * s, jmethodID xMethodID, const char *zFuncType){ - JNIEnv * const env = s->env; - jobject jcx = new_sqlite3_context_wrapper(s->env, cx); + S3JniDeclLocal_env; + jobject jcx = new_sqlite3_context_wrapper(env, cx); int rc = 0; - //MARKER(("%s.%s() cx = %p\n", s->zFuncName, zFuncType, cx)); - if(!jcx){ - sqlite3_result_error_nomem(cx); - return SQLITE_NOMEM; - } - //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType)); - if( UDF_SCALAR != s->type ){ - rc = udf_setAggregateContext(env, jcx, cx, 1); - } - if( 0 == rc ){ + int const isFinal = 'F'==zFuncType[1]/*xFinal*/; + + if( jcx ){ (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx); - IFTHREW{ - rc = udf_report_exception(cx,s->zFuncName, zFuncType); + S3JniIfThrew{ + rc = udf_report_exception(env, isFinal, cx, s->zFuncName, + zFuncType); } + S3JniUnrefLocal(jcx); + }else{ + if( isFinal ) sqlite3_result_error_nomem(cx); + rc = SQLITE_NOMEM; } - UNREF_L(jcx); 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); - ++S3JniGlobal.metrics.udf.nFunc; + 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); - ++S3JniGlobal.metrics.udf.nStep; + 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); - ++S3JniGlobal.metrics.udf.nFinal; + 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); - ++S3JniGlobal.metrics.udf.nValue; + 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); - ++S3JniGlobal.metrics.udf.nInverse; + 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 here -// at the front... +// 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, jobject jpStmt){ \ + jint const rc = (jint)CName(PtrGet_sqlite3_stmt(jpStmt)); \ + S3JniExceptionIgnore /* squelch -Xcheck:jni */; \ + return rc; \ + } +/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*,int)). */ +#define WRAP_INT_STMT_INT(JniNameSuffix,CName) \ + JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jobject pStmt, jint n){ \ + return (jint)CName(PtrGet_sqlite3_stmt(pStmt), (int)n); \ + } +/** Create a trivial JNI wrapper for (jstring CName(sqlite3_stmt*,int)). */ +#define WRAP_STR_STMT_INT(JniNameSuffix,CName) \ + JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass, jobject pStmt, jint ndx){ \ + return s3jni_utf8_to_jstring( \ + CName(PtrGet_sqlite3_stmt(pStmt), (int)ndx), \ + -1); \ + } +/** Create a trivial JNI wrapper for (int CName(sqlite3*)). */ +#define WRAP_INT_DB(JniNameSuffix,CName) \ + JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jobject pDb){ \ + return (jint)CName(PtrGet_sqlite3(pDb)); \ + } +/** Create a trivial JNI wrapper for (int64 CName(sqlite3*)). */ +#define WRAP_INT64_DB(JniNameSuffix,CName) \ + JniDecl(jlong,JniNameSuffix)(JniArgsEnvClass, jobject pDb){ \ + return (jlong)CName(PtrGet_sqlite3(pDb)); \ + } +/** Create a trivial JNI wrapper for (int CName(sqlite3_value*)). */ +#define WRAP_INT_SVALUE(JniNameSuffix,CName) \ + JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jobject jpSValue){ \ + return (jint)CName(PtrGet_sqlite3_value(jpSValue)); \ + } + WRAP_INT_STMT(1bind_1parameter_1count, sqlite3_bind_parameter_count) WRAP_INT_DB(1changes, sqlite3_changes) WRAP_INT64_DB(1changes64, sqlite3_changes64) @@ -1866,6 +2026,11 @@ WRAP_INT_DB(1error_1offset, sqlite3_error_offset) WRAP_INT_DB(1extended_1errcode, sqlite3_extended_errcode) WRAP_MUTF8_VOID(1libversion, sqlite3_libversion) WRAP_INT_VOID(1libversion_1number, sqlite3_libversion_number) +#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(1sleep, sqlite3_sleep) WRAP_MUTF8_VOID(1sourceid, sqlite3_sourceid) WRAP_INT_VOID(1threadsafe, sqlite3_threadsafe) @@ -1880,262 +2045,416 @@ WRAP_INT_SVALUE(1value_1numeric_1type, sqlite3_value_numeric_type) WRAP_INT_SVALUE(1value_1subtype, sqlite3_value_subtype) WRAP_INT_SVALUE(1value_1type, sqlite3_value_type) -#if S3JNI_ENABLE_AUTOEXT +#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 + +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 (jlong)p; +} + /* Central auto-extension handler. */ -FIXME_THREADING(autoExt) -static int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr, - const struct sqlite3_api_routines *ignored){ - S3JniAutoExtension const * pAX = S3JniGlobal.autoExt.pHead; - int rc; +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 * const ps = S3JniGlobal.autoExt.psOpening; - //MARKER(("auto-extension on open()ing ps@%p db@%p\n", ps, pDb)); - S3JniGlobal.autoExt.psOpening = 0; - if( !pAX ){ - assert( 0==S3JniGlobal.autoExt.isRunning ); - return 0; - } - else if( S3JniGlobal.autoExt.isRunning ){ - /* Necessary to avoid certain endless loop/stack overflow cases. */ - *pzErr = sqlite3_mprintf("Auto-extensions must not be triggered while " - "auto-extensions are running."); - return SQLITE_MISUSE; - } - else if(!ps){ - MARKER(("Internal error: cannot find S3JniDb for auto-extension\n")); - return SQLITE_ERROR; - }else if( (*S3JniGlobal.jvm)->GetEnv(S3JniGlobal.jvm, (void **)&env, JNI_VERSION_1_8) ){ - assert(!"Cannot get JNIEnv"); - *pzErr = sqlite3_mprintf("Could not get current JNIEnv."); + S3JniDb * ps; + S3JniEnv * jc; + + if( 0==SJG.autoExt.nExt ) return 0; + env = s3jni_env(); + jc = S3JniEnv_get(); + ps = jc->pdbOpening; + if( !ps ){ + *pzErr = sqlite3_mprintf("Unexpected arrival of null S3JniDb in " + "auto-extension runner."); return SQLITE_ERROR; } - assert( !ps->pDb /* it's still being opened */ ); - ps->pDb = pDb; + jc->pdbOpening = 0; + assert( !ps->pDb && "it's still being opened" ); assert( ps->jDb ); - NativePointerHolder_set(env, ps->jDb, pDb, S3JniClassNames.sqlite3); - ++S3JniGlobal.autoExt.isRunning; - for( ; pAX; pAX = pAX->pNext ){ - rc = (*env)->CallIntMethod(env, pAX->jObj, pAX->midFunc, ps->jDb); - IFTHREW { - jthrowable const ex = (*env)->ExceptionOccurred(env); - char * zMsg; - EXCEPTION_CLEAR; - zMsg = s3jni_exception_error_msg(env, ex); - UNREF_L(ex); - *pzErr = sqlite3_mprintf("auto-extension threw: %s", zMsg); - sqlite3_free(zMsg); - rc = rc ? rc : SQLITE_ERROR; - break; - }else if( rc ){ - break; + ps->pDb = pDb; + NativePointerHolder_set(&S3JniNphRefs.sqlite3, ps->jDb, pDb) + /* As of here, the Java/C connection is complete */; + for( i = 0; go && 0==rc; ++i ){ + S3JniAutoExtension ax = {0,0} + /* 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 points to. */; + S3JniMutex_Ext_enter; + if( i >= SJG.autoExt.nExt ){ + go = 0; + }else{ + ax.jObj = S3JniRefLocal(SJG.autoExt.aExt[i].jObj); + ax.midCallback = SJG.autoExt.aExt[i].midCallback; + } + S3JniMutex_Ext_leave; + if( ax.jObj ){ + rc = (*env)->CallIntMethod(env, ax.jObj, ax.midCallback, ps->jDb); + S3JniUnrefLocal(ax.jObj); + 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); + if( !rc ) rc = SQLITE_ERROR; + } } } - --S3JniGlobal.autoExt.isRunning; return rc; } -FIXME_THREADING(autoExt) -JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){ - static int once = 0; +S3JniApi(sqlite3_auto_extension(),jint,1auto_1extension)( + JniArgsEnvClass, jobject jAutoExt +){ + int i; S3JniAutoExtension * ax; + int rc = 0; if( !jAutoExt ) return SQLITE_MISUSE; - else if( 0==once && ++once ){ - sqlite3_auto_extension( (void(*)(void))s3jni_auto_extension ); + S3JniMutex_Ext_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. */ + S3JniMutex_Ext_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) ); + } } - ax = S3JniGlobal.autoExt.pHead; - for( ; ax; ax = ax->pNext ){ - if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ - return 0 /* C API treats this as a no-op. */; + 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; } } - return S3JniAutoExtension_alloc(env, jAutoExt) ? 0 : SQLITE_NOMEM; + S3JniMutex_Ext_leave; + return rc; } -#endif /* S3JNI_ENABLE_AUTOEXT */ -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt, - jint ndx, jbyteArray baData, jint nMax){ +S3JniApi(sqlite3_bind_blob(),jint,1bind_1blob)( + JniArgsEnvClass, jobject jpStmt, jint ndx, jbyteArray baData, jint nMax +){ + jbyte * const pBuf = baData ? s3jni_jbytearray_bytes(baData) : 0; int rc; - if(!baData){ - rc = sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), ndx); + if( pBuf ){ + rc = sqlite3_bind_blob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, + pBuf, (int)nMax, SQLITE_TRANSIENT); + s3jni_jbytearray_release(baData, pBuf); }else{ - jbyte * const pBuf = JBA_TOC(baData); - rc = sqlite3_bind_blob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, pBuf, (int)nMax, - SQLITE_TRANSIENT); - JBA_RELEASE(baData,pBuf); + rc = baData + ? SQLITE_NOMEM + : sqlite3_bind_null( PtrGet_sqlite3_stmt(jpStmt), ndx ); } return (jint)rc; } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1bind_1double)(JENV_CSELF, jobject jpStmt, - jint ndx, jdouble val){ +S3JniApi(sqlite3_bind_double(),jint,1bind_1double)( + JniArgsEnvClass, jobject jpStmt, jint ndx, jdouble val +){ return (jint)sqlite3_bind_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (double)val); } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1bind_1int)(JENV_CSELF, jobject jpStmt, - jint ndx, jint val){ +S3JniApi(sqlite3_bind_int(),jint,1bind_1int)( + JniArgsEnvClass, jobject jpStmt, jint ndx, jint val +){ return (jint)sqlite3_bind_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)val); } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1bind_1int64)(JENV_CSELF, jobject jpStmt, - jint ndx, jlong val){ +S3JniApi(sqlite3_bind_int64(),jint,1bind_1int64)( + JniArgsEnvClass, jobject jpStmt, jint ndx, jlong val +){ return (jint)sqlite3_bind_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val); } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1bind_1null)(JENV_CSELF, jobject jpStmt, - jint ndx){ +/* +** Bind a new global ref to Object `val` using sqlite3_bind_pointer(). +*/ +S3JniApi(sqlite3_bind_java_object(),jint,1bind_1java_1object)( + JniArgsEnvClass, jobject jpStmt, jint ndx, jobject val +){ + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); + int rc = 0; + + if(pStmt){ + jobject const rv = val ? S3JniRefGlobal(val) : 0; + if( rv ){ + rc = sqlite3_bind_pointer(pStmt, ndx, rv, ResultJavaValuePtrStr, + ResultJavaValue_finalizer); + }else if(val){ + rc = SQLITE_NOMEM; + } + }else{ + rc = SQLITE_MISUSE; + } + return rc; +} + +S3JniApi(sqlite3_bind_null(),jint,1bind_1null)( + JniArgsEnvClass, jobject jpStmt, jint ndx +){ return (jint)sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1bind_1parameter_1index)(JENV_CSELF, jobject jpStmt, jbyteArray jName){ +S3JniApi(sqlite3_bind_parameter_index(),jint,1bind_1parameter_1index)( + JniArgsEnvClass, jobject jpStmt, jbyteArray jName +){ int rc = 0; - jbyte * const pBuf = JBA_TOC(jName); - if(pBuf){ + jbyte * const pBuf = s3jni_jbytearray_bytes(jName); + if( pBuf ){ rc = sqlite3_bind_parameter_index(PtrGet_sqlite3_stmt(jpStmt), (const char *)pBuf); - JBA_RELEASE(jName, pBuf); + s3jni_jbytearray_release(jName, pBuf); } return rc; } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1bind_1text)(JENV_CSELF, jobject jpStmt, - jint ndx, jbyteArray baData, jint nMax){ - if(baData){ - jbyte * const pBuf = JBA_TOC(baData); - int rc = sqlite3_bind_text(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (const char *)pBuf, - (int)nMax, SQLITE_TRANSIENT); - JBA_RELEASE(baData, pBuf); - return (jint)rc; - }else{ - return sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); - } +S3JniApi(sqlite3_bind_text(),jint,1bind_1text)( + JniArgsEnvClass, jobject jpStmt, jint ndx, jbyteArray baData, jint nMax +){ + jbyte * const pBuf = baData ? s3jni_jbytearray_bytes(baData) : 0; + int const rc = sqlite3_bind_text(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, + (const char *)pBuf, + (int)nMax, SQLITE_TRANSIENT); + s3jni_jbytearray_release(baData, pBuf); + return (jint)rc; } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1bind_1zeroblob)(JENV_CSELF, jobject jpStmt, - jint ndx, jint n){ +S3JniApi(sqlite3_text16(),jint,1bind_1text16)( + JniArgsEnvClass, jobject jpStmt, jint ndx, jbyteArray baData, jint nMax +){ + jbyte * const pBuf = baData ? s3jni_jbytearray_bytes(baData) : 0; + int const rc = sqlite3_bind_text16(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, + pBuf, (int)nMax, SQLITE_TRANSIENT); + s3jni_jbytearray_release(baData, pBuf); + return (jint)rc; +} + +S3JniApi(sqlite3_bind_zeroblob(),jint,1bind_1zeroblob)( + JniArgsEnvClass, jobject jpStmt, jint ndx, jint n +){ return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)n); } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1bind_1zeroblob64)(JENV_CSELF, jobject jpStmt, - jint ndx, jlong n){ +S3JniApi(sqlite3_bind_zeroblob(),jint,1bind_1zeroblob64)( + JniArgsEnvClass, jobject jpStmt, jint ndx, jlong n +){ return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_uint64)n); } +/* Central C-to-Java busy handler proxy. */ static int s3jni_busy_handler(void* pState, int n){ S3JniDb * const ps = (S3JniDb *)pState; int rc = 0; - if( ps->busyHandler.jObj ){ - JNIEnv * const env = ps->env; - rc = (*env)->CallIntMethod(env, ps->busyHandler.jObj, - ps->busyHandler.midCallback, (jint)n); - IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("busy-handler callback"); - EXCEPTION_CLEAR; - rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "busy-handle callback threw."); + S3JniDeclLocal_env; + S3JniHook hook; + + S3JniHook_localdup(env, &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(env, ps, SQLITE_ERROR, + "sqlite3_busy_handler() callback threw."); } + S3JniHook_localundup(hook); } return rc; } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); +S3JniApi(sqlite3_busy_handler(),jint,1busy_1handler)( + JniArgsEnvClass, jobject jDb, jobject jBusy +){ + S3JniDb * const ps = S3JniDb_from_java(jDb); + S3JniHook * const pHook = ps ? &ps->hooks.busyHandler : 0; int rc = 0; - if(!ps) return (jint)SQLITE_NOMEM; - if(jBusy){ - S3JniHook * const pHook = &ps->busyHandler; - if(pHook->jObj && (*env)->IsSameObject(env, pHook->jObj, jBusy)){ + if( !ps ) return (jint)SQLITE_MISUSE; + S3JniMutex_S3JniDb_enter; + if( jBusy ){ + if( pHook->jObj && (*env)->IsSameObject(env, pHook->jObj, jBusy) ){ /* Same object - this is a no-op. */ + S3JniMutex_S3JniDb_leave; return 0; } - S3JniHook_unref(env, pHook, 1); - pHook->jObj = REF_G(jBusy); - pHook->klazz = REF_G((*env)->GetObjectClass(env, jBusy)); - pHook->midCallback = (*env)->GetMethodID(env, pHook->klazz, "xCallback", "(I)I"); - IFTHREW { - S3JniHook_unref(env, pHook, 0); + jclass klazz; + S3JniHook_unref(pHook, 0); + pHook->jObj = S3JniRefGlobal(jBusy); + klazz = (*env)->GetObjectClass(env, jBusy); + pHook->midCallback = (*env)->GetMethodID(env, klazz, "call", "(I)I"); + S3JniUnrefLocal(klazz); + S3JniIfThrew { + S3JniHook_unref(pHook, 0); rc = SQLITE_ERROR; } - if(rc){ - return rc; - } }else{ - S3JniHook_unref(env, &ps->busyHandler, 1); + S3JniHook_unref(pHook, 0); } - return jBusy - ? sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps) - : sqlite3_busy_handler(ps->pDb, 0, 0); + if( 0==rc ){ + rc = jBusy + ? sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps) + : sqlite3_busy_handler(ps->pDb, 0, 0); + } + S3JniMutex_S3JniDb_leave; + return rc; } -FIXME_THREADING(S3JniEnvCache) -FIXME_THREADING(perDb) -JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); +S3JniApi(sqlite3_busy_timeout(),jint,1busy_1timeout)( + JniArgsEnvClass, jobject jDb, jint ms +){ + S3JniDb * const ps = S3JniDb_from_java(jDb); + int rc = SQLITE_MISUSE; if( ps ){ - S3JniHook_unref(env, &ps->busyHandler, 1); - return sqlite3_busy_timeout(ps->pDb, (int)ms); + S3JniMutex_S3JniDb_enter; + S3JniHook_unref(&ps->hooks.busyHandler, 0); + rc = sqlite3_busy_timeout(ps->pDb, (int)ms); + S3JniMutex_S3JniDb_leave; } - return SQLITE_MISUSE; + return rc; } -#if S3JNI_ENABLE_AUTOEXT -FIXME_THREADING(autoExt) -JDECL(jboolean,1cancel_1auto_1extension)(JENV_CSELF, jobject jAutoExt){ - S3JniAutoExtension * ax;; - if( S3JniGlobal.autoExt.isRunning ) return JNI_FALSE; - for( ax = S3JniGlobal.autoExt.pHead; ax; ax = ax->pNext ){ - if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ - S3JniAutoExtension_free(env, ax); - return JNI_TRUE; +S3JniApi(sqlite3_cancel_auto_extension(),jboolean,1cancel_1auto_1extension)( + JniArgsEnvClass, jobject jAutoExt +){ + S3JniAutoExtension * ax; + jboolean rc = JNI_FALSE; + int i; + S3JniMutex_Ext_enter; + /* This algo mirrors 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]; + memset(&SJG.autoExt.aExt[SJG.autoExt.nExt], 0, + sizeof(S3JniAutoExtension)); + assert(! SJG.autoExt.aExt[SJG.autoExt.nExt].jObj ); + rc = JNI_TRUE; + break; } } - return JNI_FALSE; + S3JniMutex_Ext_leave; + return rc; } -#endif /* S3JNI_ENABLE_AUTOEXT */ -/** - Wrapper for sqlite3_close(_v2)(). -*/ +/* Wrapper for sqlite3_close(_v2)(). */ static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ int rc = 0; - S3JniDb * ps = 0; + +#ifndef CLOSE_DB_LOCKED + S3JniDb * const ps = S3JniDb_from_java(jDb); assert(version == 1 || version == 2); - ps = S3JniDb_for_db(env, jDb, 0, 0); - if(ps){ - //MARKER(("close()ing db@%p\n", ps->pDb)); - rc = 1==version ? (jint)sqlite3_close(ps->pDb) : (jint)sqlite3_close_v2(ps->pDb); - S3JniDb_set_aside(ps) - /* MUST come after close() because of ps->trace. */; - NativePointerHolder_set(env, jDb, 0, S3JniClassNames.sqlite3); + if( ps ){ + rc = 1==version + ? (jint)sqlite3_close(ps->pDb) + : (jint)sqlite3_close_v2(ps->pDb); + if( 0==rc ){ + S3JniDb_set_aside(ps) + /* MUST come after close() because of ps->trace. */; + NativePointerHolder_set(&S3JniNphRefs.sqlite3, jDb, 0); + } + } +#else + /* This impl leads to an assertion in sqlite3_close[_v2]() + + pthreadMutexEnter: Assertion `p->id==SQLITE_MUTEX_RECURSIVE + || pthreadMutexNotheld(p)' failed. + + For reasons not yet fully understood. + */ + assert(version == 1 || version == 2); + if( 0!=jDb ){ + S3JniDb * ps; + S3JniMutex_S3JniDb_enter; + ps = S3JniDb__from_java_unlocked(env, jDb); + if( ps && ps->pDb ){ + rc = 1==version + ? (jint)sqlite3_close(ps->pDb) + : (jint)sqlite3_close_v2(ps->pDb); + if( 0==rc ){ + S3JniDb__set_aside_unlocked(env,ps) + /* MUST come after close() because of ps->trace. */; + NativePointerHolder_set(&S3JniNphRefs.sqlite3, jDb, 0); + } + }else{ + /* ps is from S3Global.perDb.aFree. */ + } + S3JniMutex_S3JniDb_leave; } +#endif return (jint)rc; } -FIXME_THREADING(S3JniEnvCache) -FIXME_THREADING(perDb) -JDECL(jint,1close_1v2)(JENV_CSELF, jobject pDb){ +S3JniApi(sqlite3_close_v2(),jint,1close_1v2)( + JniArgsEnvClass, jobject pDb +){ return s3jni_close_db(env, pDb, 2); } -FIXME_THREADING(S3JniEnvCache) -FIXME_THREADING(perDb) -JDECL(jint,1close)(JENV_CSELF, jobject pDb){ +S3JniApi(sqlite3_close(),jint,1close)( + JniArgsEnvClass, jobject pDb +){ return s3jni_close_db(env, pDb, 1); } -/** - Assumes z is an array of unsigned short and returns the index in - that array of the first element with the value 0. +/* +** 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; @@ -2144,135 +2463,156 @@ static unsigned int s3jni_utf16_strlen(void const * z){ return i; } -/** - sqlite3_collation_needed16() hook impl. - */ +/* Central C-to-Java sqlite3_collation_needed16() hook impl. */ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, int eTextRep, const void * z16Name){ S3JniDb * const ps = pState; - JNIEnv * const env = ps->env; - unsigned int const nName = s3jni_utf16_strlen(z16Name); - jstring jName = (*env)->NewString(env, (jchar const *)z16Name, nName); - IFTHREW{ - s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); - EXCEPTION_CLEAR; - }else{ - (*env)->CallVoidMethod(env, ps->collationNeeded.jObj, - ps->collationNeeded.midCallback, - ps->jDb, (jint)eTextRep, jName); - IFTHREW{ - s3jni_db_exception(env, ps, 0, "collation-needed callback threw"); + S3JniDeclLocal_env; + S3JniHook hook; + + S3JniHook_localdup(env, &ps->hooks.collationNeeded, &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 ); + S3JniIfThrew{ + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + S3JniExceptionClear; + }else{ + (*env)->CallVoidMethod(env, ps->hooks.collationNeeded.jObj, + ps->hooks.collationNeeded.midCallback, + ps->jDb, (jint)eTextRep, jName); + S3JniIfThrew{ + S3JniExceptionWarnCallbackThrew("sqlite3_collation_needed() callback"); + s3jni_db_exception(env, ps, 0, "sqlite3_collation_needed() callback threw"); + } + S3JniUnrefLocal(jName); } + S3JniHook_localundup(hook); } - UNREF_L(jName); } -FIXME_THREADING(S3JniEnvCache) -FIXME_THREADING(perDb) -JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); - jclass klazz; - jobject pOld = 0; - jmethodID xCallback; - S3JniHook * const pHook = &ps->collationNeeded; - int rc; +S3JniApi(sqlite3_collation_needed(),jint,1collation_1needed)( + JniArgsEnvClass, jobject jDb, jobject jHook +){ + S3JniDb * const ps = S3JniDb_from_java(jDb); + S3JniHook * const pHook = &ps->hooks.collationNeeded; + int rc = 0; if( !ps ) return SQLITE_MISUSE; - pOld = pHook->jObj; - if(pOld && jHook && - (*env)->IsSameObject(env, pOld, jHook)){ - return 0; - } - if( !jHook ){ - UNREF_G(pOld); - memset(pHook, 0, sizeof(S3JniHook)); + S3JniMutex_S3JniDb_enter; + if( pHook->jObj && jHook && + (*env)->IsSameObject(env, pHook->jObj, jHook) ){ + /* no-op */ + }else if( !jHook ){ + S3JniHook_unref(pHook, 0); sqlite3_collation_needed(ps->pDb, 0, 0); - return 0; - } - klazz = (*env)->GetObjectClass(env, jHook); - xCallback = (*env)->GetMethodID(env, klazz, "xCollationNeeded", - "(Lorg/sqlite/jni/sqlite3;ILjava/lang/String;)I"); - IFTHREW { - rc = s3jni_db_exception(env, ps, SQLITE_MISUSE, - "Cannot not find matching callback on " - "collation-needed hook object."); }else{ - pHook->midCallback = xCallback; - pHook->jObj = REF_G(jHook); - UNREF_G(pOld); - rc = sqlite3_collation_needed16(ps->pDb, ps, s3jni_collation_needed_impl16); + jclass const klazz = (*env)->GetObjectClass(env, jHook); + jmethodID const xCallback = (*env)->GetMethodID( + env, klazz, "call", "(Lorg/sqlite/jni/sqlite3;ILjava/lang/String;)I" + ); + S3JniUnrefLocal(klazz); + S3JniIfThrew { + rc = s3jni_db_exception(env, ps, SQLITE_MISUSE, + "Cannot not find matching callback on " + "collation-needed hook object."); + }else{ + rc = sqlite3_collation_needed16(ps->pDb, ps, s3jni_collation_needed_impl16); + if( rc ){ + }else{ + S3JniHook_unref(pHook, 0); + pHook->midCallback = xCallback; + pHook->jObj = S3JniRefGlobal(jHook); + } + } } + S3JniMutex_S3JniDb_leave; return rc; } -FIXME_THREADING(S3JniEnvCache) -JDECL(jbyteArray,1column_1blob)(JENV_CSELF, jobject jpStmt, - jint ndx){ +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; - if( 0==p ) return NULL; - else{ - jbyteArray const jba = (*env)->NewByteArray(env, n); - (*env)->SetByteArrayRegion(env, jba, 0, n, (const jbyte *)p); - return jba; - } + + return p ? s3jni_new_jbyteArray(p, n) : 0; } -FIXME_THREADING(S3JniEnvCache) -JDECL(jdouble,1column_1double)(JENV_CSELF, jobject jpStmt, - jint ndx){ +S3JniApi(sqlite3_column_double(),jdouble,1column_1double)( + JniArgsEnvClass, jobject jpStmt, jint ndx +){ return (jdouble)sqlite3_column_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -FIXME_THREADING(S3JniEnvCache) -JDECL(jint,1column_1int)(JENV_CSELF, jobject jpStmt, - jint ndx){ +S3JniApi(sqlite3_column_int(),jint,1column_1int)( + JniArgsEnvClass, jobject jpStmt, jint ndx +){ return (jint)sqlite3_column_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -FIXME_THREADING(S3JniEnvCache) -JDECL(jlong,1column_1int64)(JENV_CSELF, jobject jpStmt, - jint ndx){ +S3JniApi(sqlite3_column_int64(),jlong,1column_1int64)( + JniArgsEnvClass, jobject jpStmt, jint ndx +){ return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -FIXME_THREADING(S3JniEnvCache) -JDECL(jbyteArray,1column_1text)(JENV_CSELF, jobject jpStmt, - jint ndx){ +S3JniApi(sqlite3_column_text(),jbyteArray,1column_1text_1utf8)( + JniArgsEnvClass, jobject jpStmt, jint ndx +){ + sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); + const int n = sqlite3_column_bytes(stmt, (int)ndx); + const unsigned char * const p = sqlite3_column_text(stmt, (int)ndx); + return p ? s3jni_new_jbyteArray(p, n) : NULL; +} + +S3JniApi(sqlite3_column_text(),jstring,1column_1text)( + JniArgsEnvClass, jobject jpStmt, jint ndx +){ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); const int n = sqlite3_column_bytes(stmt, (int)ndx); const unsigned char * const p = sqlite3_column_text(stmt, (int)ndx); - return s3jni_new_jbyteArray(env, p, n); + return p ? s3jni_utf8_to_jstring( (const char *)p, n) : 0; } -FIXME_THREADING(S3JniEnvCache) -JDECL(jstring,1column_1text16)(JENV_CSELF, jobject jpStmt, - jint ndx){ +S3JniApi(sqlite3_column_text16(),jstring,1column_1text16)( + JniArgsEnvClass, jobject jpStmt, jint ndx +){ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); const int n = sqlite3_column_bytes16(stmt, (int)ndx); const void * const p = sqlite3_column_text16(stmt, (int)ndx); return s3jni_text16_to_jstring(env, p, n); } -FIXME_THREADING(S3JniEnvCache) -JDECL(jobject,1column_1value)(JENV_CSELF, jobject jpStmt, - jint ndx){ - sqlite3_value * const sv = sqlite3_column_value(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); +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); return new_sqlite3_value_wrapper(env, sv); } - -static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){ - JNIEnv * const env = ps->env; - int rc = isCommit - ? (int)(*env)->CallIntMethod(env, ps->commitHook.jObj, - ps->commitHook.midCallback) - : (int)((*env)->CallVoidMethod(env, ps->rollbackHook.jObj, - ps->rollbackHook.midCallback), 0); - IFTHREW{ - EXCEPTION_CLEAR; - rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "hook callback threw."); +static int s3jni_commit_rollback_hook_impl(int isCommit, + S3JniDb * const ps){ + S3JniDeclLocal_env; + int rc = 0; + S3JniHook hook; + + S3JniHook_localdup( env, 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{ + S3JniExceptionClear; + rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "hook callback threw."); + } + S3JniHook_localundup(hook); } return rc; } @@ -2285,27 +2625,27 @@ static void s3jni_rollback_hook_impl(void *pP){ (void)s3jni_commit_rollback_hook_impl(0, pP); } -FIXME_THREADING(perDb) -static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,jobject jDb, - jobject jHook){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); +static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env, + jobject jDb, jobject jHook){ + S3JniDb * const ps = S3JniDb_from_java(jDb); jclass klazz; jobject pOld = 0; jmethodID xCallback; - S3JniHook * const pHook = isCommit ? &ps->commitHook : &ps->rollbackHook; - if(!ps){ + S3JniHook * const pHook = + isCommit ? &ps->hooks.commit : &ps->hooks.rollback; + if( !ps ){ s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); return 0; } pOld = pHook->jObj; - if(pOld && jHook && - (*env)->IsSameObject(env, pOld, jHook)){ + if( pOld && jHook && + (*env)->IsSameObject(env, pOld, jHook) ){ return pOld; } if( !jHook ){ - if(pOld){ - jobject tmp = REF_L(pOld); - UNREF_G(pOld); + if( pOld ){ + jobject tmp = S3JniRefLocal(pOld); + S3JniUnrefGlobal(pOld); pOld = tmp; } memset(pHook, 0, sizeof(S3JniHook)); @@ -2314,108 +2654,219 @@ static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,jobje return pOld; } klazz = (*env)->GetObjectClass(env, jHook); - xCallback = (*env)->GetMethodID(env, klazz, - isCommit ? "xCommitHook" : "xRollbackHook", + xCallback = (*env)->GetMethodID(env, klazz, "call", isCommit ? "()I" : "()V"); - IFTHREW { - EXCEPTION_REPORT; - EXCEPTION_CLEAR; + S3JniUnrefLocal(klazz); + S3JniIfThrew { + S3JniExceptionReport; + S3JniExceptionClear; s3jni_db_error(ps->pDb, SQLITE_ERROR, "Cannot not find matching callback on " "hook object."); }else{ pHook->midCallback = xCallback; - pHook->jObj = REF_G(jHook); + 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 = REF_L(pOld); - UNREF_G(pOld); + if( pOld ){ + jobject tmp = S3JniRefLocal(pOld); + S3JniUnrefGlobal(pOld); pOld = tmp; } } return pOld; } -JDECL(jobject,1commit_1hook)(JENV_CSELF,jobject jDb, jobject jHook){ +S3JniApi(sqlite3_commit_hook(),jobject,1commit_1hook)( + JniArgsEnvClass,jobject jDb, jobject jHook +){ return s3jni_commit_rollback_hook(1, env, jDb, jHook); } - -JDECL(jstring,1compileoption_1get)(JENV_CSELF, jint n){ - return (*env)->NewStringUTF( env, sqlite3_compileoption_get(n) ); +S3JniApi(sqlite3_compileoption_get(),jstring,1compileoption_1get)( + JniArgsEnvClass, jint n +){ + jstring const rv = (*env)->NewStringUTF( env, sqlite3_compileoption_get(n) ) + /* We know these to be ASCII, so MUTF-8 is fine. */; + s3jni_oom_check(rv); + return rv; } -JDECL(jboolean,1compileoption_1used)(JENV_CSELF, jstring name){ - const char *zUtf8 = JSTR_TOC(name); +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. */; const jboolean rc = 0==sqlite3_compileoption_used(zUtf8) ? JNI_FALSE : JNI_TRUE; - JSTR_RELEASE(name, zUtf8); + s3jni_mutf8_release(name, zUtf8); + return rc; +} + +S3JniApi(sqlite3_config() /*for a small subset of options.*/, + jint,1config__I)(JniArgsEnvClass, jint n){ + switch( n ){ + case SQLITE_CONFIG_SINGLETHREAD: + case SQLITE_CONFIG_MULTITHREAD: + case SQLITE_CONFIG_SERIALIZED: + return sqlite3_config( n ); + default: + return SQLITE_MISUSE; + } +} + +#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(env, &SJG.hooks.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 SQLLOG */, + jint, 1config__Lorg_sqlite_jni_ConfigSqllogCallback_2)( + JniArgsEnvClass, jobject jLog + ){ +#ifndef SQLITE_ENABLE_SQLLOG + return SQLITE_MISUSE; +#else + S3JniHook * const pHook = &SJG.hooks.sqllog; + int rc = 0; + + S3JniMutex_Global_enter; + if( !jLog ){ + S3JniHook_unref(pHook, 0); + }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/sqlite3;" + "Ljava/lang/String;" + "I)V"); + S3JniUnrefLocal(klazz); + if( midCallback ){ + rc = sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, 0 ); + if( 0==rc ){ + S3JniHook_unref(pHook, 0); + pHook->midCallback = midCallback; + pHook->jObj = S3JniRefGlobal(jLog); + } + }else{ + S3JniExceptionWarnIgnore; + rc = SQLITE_ERROR; + } + } + S3JniMutex_Global_leave; return rc; +#endif } -FIXME_THREADING(perDb) -JDECL(jobject,1context_1db_1handle)(JENV_CSELF, jobject jpCx){ - sqlite3 * const pDb = sqlite3_context_db_handle(PtrGet_sqlite3_context(jpCx)); - S3JniDb * const ps = pDb ? S3JniDb_for_db(env, 0, pDb, 0) : 0; +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; } -JDECL(jint,1create_1collation)(JENV_CSELF, jobject jDb, - jstring name, jint eTextRep, - jobject oCollation){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); - jclass klazz; +S3JniApi(sqlite3_create_collation() sqlite3_create_collation_v2(), + jint,1create_1collation +)(JniArgsEnvClass, jobject jDb, jstring name, jint eTextRep, + jobject oCollation){ int rc; - const char *zName; - S3JniHook * pHook; - if(!ps) return (jint)SQLITE_NOMEM; - pHook = &ps->collation; + jclass klazz; + S3JniDb * const ps = S3JniDb_from_java(jDb); + jmethodID midCallback; + + if( !ps ) return SQLITE_MISUSE; klazz = (*env)->GetObjectClass(env, oCollation); - pHook->midCallback = (*env)->GetMethodID(env, klazz, "xCompare", - "([B[B)I"); - IFTHREW{ - EXCEPTION_REPORT; - return s3jni_db_error(ps->pDb, SQLITE_ERROR, - "Could not get xCompare() method for object."); - } - zName = JSTR_TOC(name); - rc = sqlite3_create_collation_v2(ps->pDb, zName, (int)eTextRep, - ps, CollationState_xCompare, - CollationState_xDestroy); - JSTR_RELEASE(name, zName); - if( 0==rc ){ - pHook->jObj = REF_G(oCollation); - pHook->klazz = REF_G(klazz); + midCallback = (*env)->GetMethodID(env, klazz, "call", "([B[B)I"); + S3JniUnrefLocal(klazz); + S3JniIfThrew{ + S3JniUnrefLocal(klazz); + rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Could not get xCompare() method for object."); }else{ - S3JniHook_unref(env, pHook, 1); + char * const zName = s3jni_jstring_to_utf8( name, 0); + if( zName ){ + S3JniMutex_S3JniDb_enter; + rc = sqlite3_create_collation_v2(ps->pDb, zName, (int)eTextRep, + ps, CollationState_xCompare, + CollationState_xDestroy); + sqlite3_free(zName); + if( 0==rc ){ + S3JniHook_unref( &ps->hooks.collation, 1 ); + ps->hooks.collation.midCallback = midCallback; + ps->hooks.collation.jObj = S3JniRefGlobal(oCollation); + } + S3JniMutex_S3JniDb_leave; + }else{ + rc = SQLITE_NOMEM; + } } return (jint)rc; } -static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName, - jint nArg, jint eTextRep, jobject jFunctor){ +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); - const char * zFuncName = 0; + char * zFuncName = 0; if( !encodingTypeIsValid(eTextRep) ){ return s3jni_db_error(pDb, SQLITE_FORMAT, - "Invalid function encoding option."); + "Invalid function encoding option."); } s = S3JniUdf_alloc(env, jFunctor); if( !s ) return SQLITE_NOMEM; - else if( UDF_UNKNOWN_TYPE==s->type ){ + + if( UDF_UNKNOWN_TYPE==s->type ){ rc = s3jni_db_error(pDb, SQLITE_MISUSE, "Cannot unambiguously determine function type."); - S3JniUdf_free(s); + S3JniUdf_free(env, s, 1); goto error_cleanup; } - zFuncName = JSTR_TOC(jFuncName); - if(!zFuncName){ + zFuncName = s3jni_jstring_to_utf8(jFuncName,0); + if( !zFuncName ){ rc = SQLITE_NOMEM; - S3JniUdf_free(s); + S3JniUdf_free(env, s, 1); goto error_cleanup; } if( UDF_WINDOW == s->type ){ @@ -2436,34 +2887,57 @@ static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName, rc = sqlite3_create_function_v2(pDb, zFuncName, nArg, eTextRep, s, xFunc, xStep, xFinal, S3JniUdf_finalizer); } - if( 0==rc ){ - s->zFuncName = sqlite3_mprintf("%s", zFuncName) - /* OOM here is non-fatal. Ignore it. Handling it would require - re-calling the appropriate create_function() func with 0 - for all xAbc args so that s would be finalized. */; - } error_cleanup: - JSTR_RELEASE(jFuncName, zFuncName); - /* on create_function() error, s will be destroyed via create_function() */ + /* Reminder: on sqlite3_create_function() error, s will be + ** destroyed via create_function(). */ + sqlite3_free(zFuncName); return (jint)rc; } -JDECL(jint,1create_1function)(JENV_CSELF, jobject jDb, jstring jFuncName, - jint nArg, jint eTextRep, jobject jFunctor){ - return create_function(env, jDb, jFuncName, nArg, eTextRep, jFunctor); +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; } -/* sqlite3_db_config() for (int,const char *) */ -JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( - JENV_CSELF, jobject jDb, jint op, jstring jStr +S3JniApi(sqlite3_db_handle(),jobject,1db_1handle)( + JniArgsEnvClass, jobject jpStmt ){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + 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_config() /*for MAINDBNAME*/, + jint,1db_1config__Lorg_sqlite_jni_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: - zStr = s3jni_jstring_to_utf8(S3JniGlobal_env_cache(env), jStr, 0); + S3JniMutex_S3JniDb_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 ){ @@ -2475,6 +2949,7 @@ JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( }else{ rc = SQLITE_NOMEM; } + S3JniMutex_S3JniDb_leave; break; default: rc = SQLITE_MISUSE; @@ -2482,14 +2957,16 @@ JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( return rc; } -FIXME_THREADING(perDb) -/* sqlite3_db_config() for (int,int*) */ -/* ACHTUNG: openjdk v19 creates a different mangled name for this - function than openjdk v8 does. */ -JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)( - JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut +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_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2 +)( + JniArgsEnvClass, jobject jDb, jint op, jint onOff, jobject jOut ){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_from_java(jDb); int rc; switch( ps ? op : 0 ){ case SQLITE_DBCONFIG_ENABLE_FKEY: @@ -2523,44 +3000,25 @@ JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer 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. +/* +** This is a workaround for openjdk v19 (and possibly others) encoding +** this function's name differently than JDK v8 does. If we do not +** install both names for this function then Java will not be able to +** find the function in both environments. */ -JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_00024Int32_2)( - JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut +JniDecl(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_00024Int32_2)( + JniArgsEnvClass, jobject jDb, jint op, jint onOff, jobject jOut ){ - return JFuncName(1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)( + return JniFuncName(1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)( env, jKlazz, jDb, op, onOff, jOut ); } -JDECL(jstring,1db_1filename)(JENV_CSELF, jobject jDb, jstring jDbName){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); - char *zDbName; - jstring jRv = 0; - int nStr = 0; - - if( !ps || !jDbName ){ - return 0; - } - zDbName = s3jni_jstring_to_utf8(jc, jDbName, &nStr); - if( zDbName ){ - char const * zRv = sqlite3_db_filename(ps->pDb, zDbName); - sqlite3_free(zDbName); - if( zRv ){ - jRv = s3jni_utf8_to_jstring(jc, zRv, -1); - } - } - return jRv; -} - -JDECL(jint,1db_1status)(JENV_CSELF, jobject jDb, jint op, jobject jOutCurrent, - jobject jOutHigh, jboolean reset ){ +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 ); @@ -2571,116 +3029,171 @@ JDECL(jint,1db_1status)(JENV_CSELF, jobject jDb, jint op, jobject jOutCurrent, return (jint)rc; } - -JDECL(jint,1errcode)(JENV_CSELF, jobject jpDb){ +S3JniApi(sqlite3_errcode(),jint,1errcode)( + JniArgsEnvClass, jobject jpDb +){ sqlite3 * const pDb = PtrGet_sqlite3(jpDb); return pDb ? sqlite3_errcode(pDb) : SQLITE_MISUSE; } -JDECL(jstring,1errmsg)(JENV_CSELF, jobject jpDb){ +S3JniApi(sqlite3_errmsg(),jstring,1errmsg)( + JniArgsEnvClass, jobject jpDb +){ sqlite3 * const pDb = PtrGet_sqlite3(jpDb); - S3JniEnvCache * const jc = pDb ? S3JniGlobal_env_cache(env) : 0; - return jc ? s3jni_utf8_to_jstring(jc, sqlite3_errmsg(pDb), -1) : 0; + return pDb ? s3jni_utf8_to_jstring( sqlite3_errmsg(pDb), -1) : 0; } -JDECL(jstring,1errstr)(JENV_CSELF, jint rcCode){ - return (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode)) - /* We know these values to be plain ASCII, so pose no - MUTF-8 incompatibility */; +S3JniApi(sqlite3_errstr(),jstring,1errstr)( + JniArgsEnvClass, jint rcCode +){ + jstring const rv = (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode)) + /* We know these values to be plain ASCII, so pose no MUTF-8 + ** incompatibility */; + s3jni_oom_check( rv ); + return rv; } -JDECL(jstring,1expanded_1sql)(JENV_CSELF, jobject jpStmt){ +S3JniApi(sqlite3_expanded_sql(),jstring,1expanded_1sql)( + JniArgsEnvClass, jobject jpStmt +){ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); jstring rv = 0; if( pStmt ){ - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); char * zSql = sqlite3_expanded_sql(pStmt); - OOM_CHECK(zSql); + s3jni_oom_fatal(zSql); if( zSql ){ - rv = s3jni_utf8_to_jstring(jc, zSql, -1); + rv = s3jni_utf8_to_jstring( zSql, -1); sqlite3_free(zSql); } } return rv; } -JDECL(jboolean,1extended_1result_1codes)(JENV_CSELF, jobject jpDb, - jboolean onoff){ +S3JniApi(sqlite3_extended_result_codes(),jboolean,1extended_1result_1codes)( + JniArgsEnvClass, jobject jpDb, jboolean onoff +){ int const rc = sqlite3_extended_result_codes(PtrGet_sqlite3(jpDb), onoff ? 1 : 0); return rc ? JNI_TRUE : JNI_FALSE; } -JDECL(jint,1initialize)(JENV_CSELF){ - return sqlite3_initialize(); -} - -JDECL(jint,1finalize)(JENV_CSELF, jobject jpStmt){ +S3JniApi(sqlite3_finalize(),jint,1finalize)( + JniArgsEnvClass, jobject jpStmt +){ int rc = 0; sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); if( pStmt ){ rc = sqlite3_finalize(pStmt); - NativePointerHolder_set(env, jpStmt, 0, S3JniClassNames.sqlite3_stmt); + NativePointerHolder_set(&S3JniNphRefs.sqlite3_stmt, jpStmt, 0); } return rc; } +S3JniApi(sqlite3_initialize(),jint,1initialize)( + JniArgsEnvClass +){ + return sqlite3_initialize(); +} + +S3JniApi(sqlite3_interrupt(),void,1interrupt)( + JniArgsEnvClass, jobject jpDb +){ + sqlite3 * const pDb = PtrGet_sqlite3(jpDb); + if( pDb ) sqlite3_interrupt(pDb); +} + +S3JniApi(sqlite3_is_interrupted(),jboolean,1is_1interrupted)( + JniArgsEnvClass, jobject jpDb +){ + int rc = 0; + sqlite3 * const pDb = PtrGet_sqlite3(jpDb); + if( pDb ){ + rc = sqlite3_is_interrupted(pDb); + } + return rc ? JNI_TRUE : JNI_FALSE; +} + +/* +** Uncaches the current JNIEnv from the S3JniGlobal state, clearing +** any resources owned by that cache entry and making that slot +** available for re-use. +*/ +JniDecl(jboolean,1java_1uncache_1thread)(JniArgsEnvClass){ + int rc; + S3JniMutex_Env_enter; + rc = S3JniEnv_uncache(env); + S3JniMutex_Env_leave; + return rc ? JNI_TRUE : JNI_FALSE; +} + -JDECL(jlong,1last_1insert_1rowid)(JENV_CSELF, jobject jpDb){ +S3JniApi(sqlite3_last_insert_rowid(),jlong,1last_1insert_1rowid)( + JniArgsEnvClass, jobject jpDb +){ return (jlong)sqlite3_last_insert_rowid(PtrGet_sqlite3(jpDb)); } -//! Pre-open() code common to sqlite3_open(_v2)(). -static int s3jni_open_pre(JNIEnv * const env, S3JniEnvCache **jc, +/* Pre-open() code common to sqlite3_open[_v2](). */ +static int s3jni_open_pre(JNIEnv * const env, S3JniEnv **jc, jstring jDbName, char **zDbName, - S3JniDb ** ps, jobject *jDb){ - *jc = S3JniGlobal_env_cache(env); - if(!*jc) return SQLITE_NOMEM; - *zDbName = jDbName ? s3jni_jstring_to_utf8(*jc, jDbName, 0) : 0; - if(jDbName && !*zDbName) return SQLITE_NOMEM; - *jDb = new_sqlite3_wrapper(env, 0); - if( !*jDb ){ + 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_sqlite3_wrapper(env, 0); + if( !jDb ){ sqlite3_free(*zDbName); *zDbName = 0; - return SQLITE_NOMEM; + rc = SQLITE_NOMEM; + goto end; } - *ps = S3JniDb_alloc(env, 0, *jDb); -#if S3JNI_ENABLE_AUTOEXT - if(*ps){ - assert(!S3JniGlobal.autoExt.psOpening); - S3JniGlobal.autoExt.psOpening = *ps; + *ps = S3JniDb_alloc(env, jDb); + if( *ps ){ + (*jc)->pdbOpening = *ps; + }else{ + S3JniUnrefLocal(jDb); + rc = SQLITE_NOMEM; } -#endif - //MARKER(("pre-open ps@%p\n", *ps)); - return *ps ? 0 : 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.sqlite3 object which will hold the db's native - pointer. theRc must be the result code of the open() op. If - *ppDb is NULL then ps is set aside and its state cleared, - else ps is associated with *ppDb. If *ppDb is not NULL then - ps->jDb is stored in jOut (an OutputPointer.sqlite3 instance). - - Returns theRc. +/* +** Post-open() code common to both the sqlite3_open() and +** sqlite3_open_v2() bindings. ps->jDb must be the +** org.sqlite.jni.sqlite3 object which will hold the db's native +** pointer. theRc must be the result code of the open() op. If +** *ppDb is NULL then ps is set aside and its state cleared, +** else ps is associated with *ppDb. If *ppDb is not NULL then +** ps->jDb is stored in jOut (an OutputPointer.sqlite3 instance). +** +** 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, S3JniDb * ps, - sqlite3 **ppDb, jobject jOut, int theRc){ - //MARKER(("post-open() ps@%p db@%p\n", ps, *ppDb)); -#if S3JNI_ENABLE_AUTOEXT - assert( S3JniGlobal.autoExt.pHead ? ps!=S3JniGlobal.autoExt.psOpening : 1 ); - S3JniGlobal.autoExt.psOpening = 0; -#endif - if(*ppDb){ +static int s3jni_open_post(JNIEnv * const env, S3JniEnv * const jc, + S3JniDb * ps, sqlite3 **ppDb, + jobject jOut, int theRc){ + jc->pdbOpening = 0; + if( *ppDb ){ assert(ps->jDb); -#if S3JNI_ENABLE_AUTOEXT - //MARKER(("*autoExt.pHead=%p, ppDb=%p, ps->pDb=%p\n", S3JniGlobal.autoExt.pHead, *ppDb, ps->pDb)); - // invalid when an autoext triggers another open(): - // assert( S3JniGlobal.autoExt.pHead ? *ppDb==ps->pDb : 0==ps->pDb ); -#endif - ps->pDb = *ppDb; - NativePointerHolder_set(env, ps->jDb, *ppDb, S3JniClassNames.sqlite3); + if( 0==ps->pDb ){ + ps->pDb = *ppDb; + NativePointerHolder_set(&S3JniNphRefs.sqlite3, ps->jDb, *ppDb) + /* As of here, the Java/C connection is complete */; + }else{ + assert( ps->pDb==*ppDb + && "Set up via s3jni_run_java_auto_extensions()" ); + } }else{ S3JniDb_set_aside(ps); ps = 0; @@ -2689,70 +3202,66 @@ static int s3jni_open_post(JNIEnv * const env, S3JniDb * ps, return theRc; } -JDECL(jint,1open)(JENV_CSELF, jstring strName, jobject jOut){ +S3JniApi(sqlite3_open(),jint,1open)( + JniArgsEnvClass, jstring strName, jobject jOut +){ sqlite3 * pOut = 0; char *zName = 0; - jobject jDb = 0; S3JniDb * ps = 0; - S3JniEnvCache * jc = 0; - S3JniDb * const prevOpening = S3JniGlobal.autoExt.psOpening; - int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); + S3JniEnv * jc = 0; + int rc; + rc = s3jni_open_pre(env, &jc, strName, &zName, &ps); if( 0==rc ){ - rc = sqlite3_open(zName, &pOut); - //MARKER(("env=%p, *env=%p\n", env, *env)); - //MARKER(("open() ps@%p db@%p\n", ps, pOut)); - rc = s3jni_open_post(env, ps, &pOut, jOut, rc); + rc = s3jni_open_post(env, jc, ps, &pOut, jOut, + sqlite3_open(zName, &pOut)); assert(rc==0 ? pOut!=0 : 1); sqlite3_free(zName); } - S3JniGlobal.autoExt.psOpening = prevOpening; return (jint)rc; } -JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName, - jobject jOut, jint flags, jstring strVfs){ +S3JniApi(sqlite3_open_v2(),jint,1open_1v2)( + JniArgsEnvClass, jstring strName, + jobject jOut, jint flags, jstring strVfs +){ sqlite3 * pOut = 0; char *zName = 0; - jobject jDb = 0; S3JniDb * ps = 0; - S3JniEnvCache * jc = 0; + S3JniEnv * jc = 0; char *zVfs = 0; - S3JniDb * const prevOpening = S3JniGlobal.autoExt.psOpening; - int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); - if( 0==rc && strVfs ){ - zVfs = s3jni_jstring_to_utf8(jc, strVfs, 0); - if( !zVfs ){ - rc = SQLITE_NOMEM; - } - } + int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps); if( 0==rc ){ - rc = sqlite3_open_v2(zName, &pOut, (int)flags, zVfs); + 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); } - //MARKER(("open_v2() ps@%p db@%p\n", ps, pOut)); - /*MARKER(("zName=%s, zVfs=%s, pOut=%p, flags=%d, nrc=%d\n", - zName, zVfs, pOut, (int)flags, nrc));*/ - rc = s3jni_open_post(env, ps, &pOut, jOut, rc); assert(rc==0 ? pOut!=0 : 1); sqlite3_free(zName); sqlite3_free(zVfs); - S3JniGlobal.autoExt.psOpening = prevOpening; return (jint)rc; } /* Proxy for the sqlite3_prepare[_v2/3]() family. */ -static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv * const env, jclass self, - jobject jDb, jbyteArray baSql, - jint nMax, jint prepFlags, - jobject jOutStmt, jobject outTail){ +jint sqlite3_jni_prepare_v123( int prepVersion, JNIEnv * const env, jclass self, + jobject jDb, jbyteArray baSql, + jint nMax, jint prepFlags, + jobject jOutStmt, jobject outTail){ sqlite3_stmt * pStmt = 0; jobject jStmt = 0; const char * zTail = 0; - jbyte * const pBuf = JBA_TOC(baSql); + jbyte * const pBuf = s3jni_jbytearray_bytes(baSql); int rc = SQLITE_ERROR; assert(prepVersion==1 || prepVersion==2 || prepVersion==3); if( !pBuf ){ - rc = baSql ? SQLITE_MISUSE : SQLITE_NOMEM; - goto end; + rc = baSql ? SQLITE_NOMEM : SQLITE_MISUSE; + goto end; } jStmt = new_sqlite3_stmt_wrapper(env, 0); if( !jStmt ){ @@ -2774,120 +3283,341 @@ static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv * const env, jclass assert(0 && "Invalid prepare() version"); } end: - JBA_RELEASE(baSql,pBuf); + s3jni_jbytearray_release(baSql,pBuf); if( 0==rc ){ if( 0!=outTail ){ - /* Noting that pBuf is deallocated now but its address is all we need. */ + /* 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(env, jStmt, pStmt, S3JniClassNames.sqlite3_stmt); + NativePointerHolder_set(&S3JniNphRefs.sqlite3_stmt, jStmt, pStmt); }else{ - /* Happens for comments and whitespace */ - UNREF_L(jStmt); + /* Happens for comments and whitespace. */ + S3JniUnrefLocal(jStmt); jStmt = 0; } }else{ - UNREF_L(jStmt); + S3JniUnrefLocal(jStmt); jStmt = 0; } -#if 0 - if( 0!=rc ){ - MARKER(("prepare rc = %d\n", rc)); - } -#endif OutputPointer_set_sqlite3_stmt(env, jOutStmt, jStmt); return (jint)rc; } -JDECL(jint,1prepare)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql, - jint nMax, jobject jOutStmt, jobject outTail){ +S3JniApi(sqlite3_prepare(),jint,1prepare)( + JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql, + jint nMax, jobject jOutStmt, jobject outTail +){ return sqlite3_jni_prepare_v123(1, env, self, jDb, baSql, nMax, 0, jOutStmt, outTail); } -JDECL(jint,1prepare_1v2)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql, - jint nMax, jobject jOutStmt, jobject outTail){ +S3JniApi(sqlite3_prepare_v2(),jint,1prepare_1v2)( + JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql, + jint nMax, jobject jOutStmt, jobject outTail +){ return sqlite3_jni_prepare_v123(2, env, self, jDb, baSql, nMax, 0, jOutStmt, outTail); } -JDECL(jint,1prepare_1v3)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql, - jint nMax, jint prepFlags, jobject jOutStmt, jobject outTail){ +S3JniApi(sqlite3_prepare_v3(),jint,1prepare_1v3)( + JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql, + jint nMax, jint prepFlags, jobject jOutStmt, jobject outTail +){ return sqlite3_jni_prepare_v123(3, env, self, jDb, baSql, nMax, prepFlags, jOutStmt, outTail); } +/* +** 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(env, 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(env, ps, 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); +} + +#ifndef SQLITE_ENABLE_PREUPDATE_HOOK +/* We need no-op impls for preupdate_{count,depth,blobwrite}() */ +S3JniApi(sqlite3_preupdate_blobwrite(),int,1preupdate_1blobwrite)( + JniArgsEnvClass, jobject jDb){ return SQLITE_MISUSE; } +S3JniApi(sqlite3_preupdate_count(),int,1preupdate_1count)( + JniArgsEnvClass, jobject jDb){ return SQLITE_MISUSE; } +S3JniApi(sqlite3_preupdate_depth(),int,1preupdate_1depth)( + JniArgsEnvClass, jobject 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, jobject jDb, jobject jHook){ + S3JniDb * const ps = S3JniDb_from_java(jDb); + jclass klazz; + jobject pOld = 0; + jmethodID xCallback; + S3JniHook * pHook; + + if( !ps ) return 0; + S3JniMutex_S3JniDb_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; + } + memset(pHook, 0, sizeof(S3JniHook)); +#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/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: + S3JniMutex_S3JniDb_leave; + return pOld; +} + +S3JniApi(sqlite3_preupdate_hook(),jobject,1preupdate_1hook)( + JniArgsEnvClass, jobject jDb, jobject jHook +){ +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + return s3jni_updatepre_hook(env, 1, jDb, 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, jobject jDb, + jint iCol, jobject jOut){ +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + sqlite3_value * pOut = 0; + sqlite3 * const pDb = PtrGet_sqlite3(jDb); + int rc; + 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_sqlite3_value_wrapper(env, pOut); + if( pWrap ){ + OutputPointer_set_sqlite3_value(env, jOut, pWrap); + S3JniUnrefLocal(pWrap); + }else{ + rc = SQLITE_NOMEM; + } + } + return rc; +#else + return SQLITE_MISUSE; +#endif +} + +S3JniApi(sqlite3_preupdate_new(),jint,1preupdate_1new)( + JniArgsEnvClass, jobject jDb, jint iCol, jobject jOut +){ + return s3jni_preupdate_newold(env, 1, jDb, iCol, jOut); +} + +S3JniApi(sqlite3_preupdate_old(),jint,1preupdate_1old)( + JniArgsEnvClass, jobject jDb, jint iCol, jobject jOut +){ + return s3jni_preupdate_newold(env, 0, jDb, iCol, jOut); +} + + +/* Central C-to-Java sqlite3_progress_handler() proxy. */ static int s3jni_progress_handler_impl(void *pP){ S3JniDb * const ps = (S3JniDb *)pP; - JNIEnv * const env = ps->env; - int rc = (int)(*env)->CallIntMethod(env, ps->progress.jObj, - ps->progress.midCallback); - IFTHREW{ - rc = s3jni_db_exception(env, ps, rc, - "sqlite3_progress_handler() callback threw"); + int rc = 0; + S3JniDeclLocal_env; + S3JniHook hook; + + S3JniHook_localdup( env, &ps->hooks.progress, &hook ); + if( hook.jObj ){ + rc = (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback); + S3JniIfThrew{ + rc = s3jni_db_exception(env, ps, rc, + "sqlite3_progress_handler() callback threw"); + } + S3JniHook_localundup(hook); } return rc; } -JDECL(void,1progress_1handler)(JENV_CSELF,jobject jDb, jint n, jobject jProgress){ - S3JniDb * ps = S3JniDb_for_db(env, jDb, 0, 0); +S3JniApi(sqlite3_progress_handler(),void,1progress_1handler)( + JniArgsEnvClass,jobject jDb, jint n, jobject jProgress +){ + S3JniDb * const ps = S3JniDb_from_java(jDb); jclass klazz; jmethodID xCallback; + S3JniHook * const pHook = ps ? &ps->hooks.progress : 0; + + if( !ps ) return; + S3JniMutex_S3JniDb_enter; if( n<1 || !jProgress ){ - if(ps){ - UNREF_G(ps->progress.jObj); - memset(&ps->progress, 0, sizeof(ps->progress)); - } + S3JniHook_unref(pHook, 0); sqlite3_progress_handler(ps->pDb, 0, 0, 0); - return; - } - if(!ps){ - s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + S3JniMutex_S3JniDb_leave; return; } klazz = (*env)->GetObjectClass(env, jProgress); - xCallback = (*env)->GetMethodID(env, klazz, "xCallback", "()I"); - IFTHREW { - EXCEPTION_CLEAR; + 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{ - UNREF_G(ps->progress.jObj); - ps->progress.midCallback = xCallback; - ps->progress.jObj = REF_G(jProgress); + S3JniUnrefGlobal(pHook->jObj); + pHook->midCallback = xCallback; + pHook->jObj = S3JniRefGlobal(jProgress); sqlite3_progress_handler(ps->pDb, (int)n, s3jni_progress_handler_impl, ps); } + S3JniMutex_S3JniDb_leave; } - -JDECL(jint,1reset)(JENV_CSELF, jobject jpStmt){ - int rc = 0; +S3JniApi(sqlite3_reset(),jint,1reset)( + JniArgsEnvClass, jobject jpStmt +){ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); - if( pStmt ){ - rc = sqlite3_reset(pStmt); - } - return rc; + return pStmt ? sqlite3_reset(pStmt) : SQLITE_MISUSE; } -#if S3JNI_ENABLE_AUTOEXT -JDECL(void,1reset_1auto_1extension)(JENV_CSELF){ - if(!S3JniGlobal.autoExt.isRunning){ - while( S3JniGlobal.autoExt.pHead ){ - S3JniAutoExtension_free(env, S3JniGlobal.autoExt.pHead); - } +/* Clears all entries from S3JniGlobal.autoExt. */ +static void s3jni_reset_auto_extension(JNIEnv *env){ + int i; + S3JniMutex_Ext_enter; + for( i = 0; i < SJG.autoExt.nExt; ++i ){ + S3JniAutoExtension_clear( &SJG.autoExt.aExt[i] ); } + SJG.autoExt.nExt = 0; + S3JniMutex_Ext_leave; } -#endif /* S3JNI_ENABLE_AUTOEXT */ -/* sqlite3_result_text/blob() and friends. */ -static void result_blob_text(int asBlob, int as64, - int eTextRep/*only for (asBlob=0)*/, +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){ - if(jBa){ - jbyte * const pBuf = JBA_TOC(jBa); + int const asBlob = 0==eTextRep; + if( jBa ){ + jbyte * const pBuf = s3jni_jbytearray_bytes(jBa); jsize nBa = (*env)->GetArrayLength(env, jBa); if( nMax>=0 && nBa>(jsize)nMax ){ nBa = (jsize)nMax; @@ -2907,17 +3637,16 @@ static void result_blob_text(int asBlob, int as64, have result values). */ } - if(as64){ /* 64-bit... */ + if( as64 ){ /* 64-bit... */ static const jsize nLimit64 = - SQLITE_MAX_ALLOCATION_SIZE/*only _kinda_ arbitrary!*/ - /* jsize is int32, not int64! */; - if(nBa > nLimit64){ + SQLITE_MAX_ALLOCATION_SIZE/*only _kinda_ arbitrary*/; + if( nBa > nLimit64 ){ sqlite3_result_error_toobig(pCx); - }else if(asBlob){ + }else if( asBlob ){ sqlite3_result_blob64(pCx, pBuf, (sqlite3_uint64)nBa, SQLITE_TRANSIENT); }else{ /* text64... */ - if(encodingTypeIsValid(eTextRep)){ + if( encodingTypeIsValid(eTextRep) ){ sqlite3_result_text64(pCx, (const char *)pBuf, (sqlite3_uint64)nBa, SQLITE_TRANSIENT, eTextRep); @@ -2927,13 +3656,13 @@ static void result_blob_text(int asBlob, int as64, } }else{ /* 32-bit... */ static const jsize nLimit = SQLITE_MAX_ALLOCATION_SIZE; - if(nBa > nLimit){ + if( nBa > nLimit ){ sqlite3_result_error_toobig(pCx); - }else if(asBlob){ + }else if( asBlob ){ sqlite3_result_blob(pCx, pBuf, (int)nBa, SQLITE_TRANSIENT); }else{ - switch(eTextRep){ + switch( eTextRep ){ case SQLITE_UTF8: sqlite3_result_text(pCx, (const char *)pBuf, (int)nBa, SQLITE_TRANSIENT); @@ -2952,39 +3681,46 @@ static void result_blob_text(int asBlob, int as64, break; } } - JBA_RELEASE(jBa, pBuf); + s3jni_jbytearray_release(jBa, pBuf); } }else{ sqlite3_result_null(pCx); } } -JDECL(void,1result_1blob)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jint nMax){ - return result_blob_text(1, 0, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +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); } -JDECL(void,1result_1blob64)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jlong nMax){ - return result_blob_text(1, 1, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +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); } -JDECL(void,1result_1double)(JENV_CSELF, jobject jpCx, jdouble v){ +S3JniApi(sqlite3_result_double(),void,1result_1double)( + JniArgsEnvClass, jobject jpCx, jdouble v +){ sqlite3_result_double(PtrGet_sqlite3_context(jpCx), v); } -JDECL(void,1result_1error)(JENV_CSELF, jobject jpCx, jbyteArray baMsg, - int eTextRep){ +S3JniApi(sqlite3_result_error(),void,1result_1error)( + JniArgsEnvClass, jobject jpCx, jbyteArray baMsg, int eTextRep +){ const char * zUnspecified = "Unspecified error."; jsize const baLen = (*env)->GetArrayLength(env, baMsg); - jbyte * const pjBuf = baMsg ? JBA_TOC(baMsg) : NULL; - switch(pjBuf ? eTextRep : SQLITE_UTF8){ + 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; - sqlite3_result_error(PtrGet_sqlite3_context(jpCx), zMsg, (int)baLen); + 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 - ? (const void *)pjBuf : (const void *)zUnspecified; + const void *zMsg = pjBuf; sqlite3_result_error16(PtrGet_sqlite3_context(jpCx), zMsg, (int)baLen); break; } @@ -2994,35 +3730,47 @@ JDECL(void,1result_1error)(JENV_CSELF, jobject jpCx, jbyteArray baMsg, "to sqlite3_result_error().", -1); break; } - JBA_RELEASE(baMsg,pjBuf); + s3jni_jbytearray_release(baMsg,pjBuf); } -JDECL(void,1result_1error_1code)(JENV_CSELF, jobject jpCx, jint v){ +S3JniApi(sqlite3_result_error_code(),void,1result_1error_1code)( + JniArgsEnvClass, jobject jpCx, jint v +){ sqlite3_result_error_code(PtrGet_sqlite3_context(jpCx), v ? (int)v : SQLITE_ERROR); } -JDECL(void,1result_1error_1nomem)(JENV_CSELF, jobject jpCx){ +S3JniApi(sqlite3_result_error_nomem(),void,1result_1error_1nomem)( + JniArgsEnvClass, jobject jpCx +){ sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx)); } -JDECL(void,1result_1error_1toobig)(JENV_CSELF, jobject jpCx){ +S3JniApi(sqlite3_result_error_toobig(),void,1result_1error_1toobig)( + JniArgsEnvClass, jobject jpCx +){ sqlite3_result_error_toobig(PtrGet_sqlite3_context(jpCx)); } -JDECL(void,1result_1int)(JENV_CSELF, jobject jpCx, jint v){ +S3JniApi(sqlite3_result_int(),void,1result_1int)( + JniArgsEnvClass, jobject jpCx, jint v +){ sqlite3_result_int(PtrGet_sqlite3_context(jpCx), (int)v); } -JDECL(void,1result_1int64)(JENV_CSELF, jobject jpCx, jlong v){ +S3JniApi(sqlite3_result_int64(),void,1result_1int64)( + JniArgsEnvClass, jobject jpCx, jlong v +){ sqlite3_result_int64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v); } -JDECL(void,1result_1java_1object)(JENV_CSELF, jobject jpCx, jobject v){ - if(v){ - ResultJavaVal * const rjv = ResultJavaVal_alloc(env, v); - if(rjv){ - sqlite3_result_pointer(PtrGet_sqlite3_context(jpCx), rjv, RESULT_JAVA_VAL_STRING, - ResultJavaVal_finalizer); +S3JniApi(sqlite3_result_java_object(),void,1result_1java_1object)( + JniArgsEnvClass, jobject jpCx, jobject v +){ + if( v ){ + jobject const rjv = S3JniRefGlobal(v); + if( rjv ){ + sqlite3_result_pointer(PtrGet_sqlite3_context(jpCx), rjv, + ResultJavaValuePtrStr, ResultJavaValue_finalizer); }else{ sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx)); } @@ -3031,108 +3779,139 @@ JDECL(void,1result_1java_1object)(JENV_CSELF, jobject jpCx, jobject v){ } } -JDECL(void,1result_1null)(JENV_CSELF, jobject jpCx){ +S3JniApi(sqlite3_result_null(),void,1result_1null)( + JniArgsEnvClass, jobject jpCx +){ sqlite3_result_null(PtrGet_sqlite3_context(jpCx)); } -JDECL(void,1result_1text)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jint nMax){ - return result_blob_text(0, 0, SQLITE_UTF8, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +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); } -JDECL(void,1result_1text64)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jlong nMax, - jint eTextRep){ - return result_blob_text(0, 1, eTextRep, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +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); } -JDECL(void,1result_1value)(JENV_CSELF, jobject jpCx, jobject jpSVal){ - sqlite3_result_value(PtrGet_sqlite3_context(jpCx), PtrGet_sqlite3_value(jpSVal)); +S3JniApi(sqlite3_result_value(),void,1result_1value)( + JniArgsEnvClass, jobject jpCx, jobject jpSVal +){ + sqlite3_result_value(PtrGet_sqlite3_context(jpCx), + PtrGet_sqlite3_value(jpSVal)); } -JDECL(void,1result_1zeroblob)(JENV_CSELF, jobject jpCx, jint v){ +S3JniApi(sqlite3_result_zeroblob(),void,1result_1zeroblob)( + JniArgsEnvClass, jobject jpCx, jint v +){ sqlite3_result_zeroblob(PtrGet_sqlite3_context(jpCx), (int)v); } -JDECL(jint,1result_1zeroblob64)(JENV_CSELF, jobject jpCx, jlong v){ - return (jint)sqlite3_result_zeroblob64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v); +S3JniApi(sqlite3_result_zeroblob64(),jint,1result_1zeroblob64)( + JniArgsEnvClass, jobject jpCx, jlong v +){ + return (jint)sqlite3_result_zeroblob64(PtrGet_sqlite3_context(jpCx), + (sqlite3_int64)v); } -JDECL(jobject,1rollback_1hook)(JENV_CSELF,jobject jDb, jobject jHook){ +S3JniApi(sqlite3_rollback_hook(),jobject,1rollback_1hook)( + JniArgsEnvClass, jobject jDb, jobject jHook +){ return s3jni_commit_rollback_hook(0, env, jDb, jHook); } -/* sqlite3_set_authorizer() callback proxy. */ -static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1, - const char*z2,const char*z3){ +/* 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; - JNIEnv * const env = ps->env; - jstring const s0 = z0 ? (*env)->NewStringUTF(env, z0) : 0; - jstring const s1 = z1 ? (*env)->NewStringUTF(env, z1) : 0; - jstring const s2 = z2 ? (*env)->NewStringUTF(env, z2) : 0; - jstring const s3 = z3 ? (*env)->NewStringUTF(env, z3) : 0; - S3JniHook const * const pHook = &ps->authHook; - int rc; + S3JniDeclLocal_env; + S3JniHook hook; + int rc = 0; - assert( pHook->jObj ); - rc = (*env)->CallIntMethod(env, pHook->jObj, pHook->midCallback, (jint)op, - s0, s1, s3, s3); - IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("sqlite3_set_authorizer() callback"); - EXCEPTION_CLEAR; - } - UNREF_L(s0); - UNREF_L(s1); - UNREF_L(s2); - UNREF_L(s3); + S3JniHook_localdup(env, &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(env, ps, rc, "sqlite3_set_authorizer() callback"); + } + S3JniUnrefLocal(s0); + S3JniUnrefLocal(s1); + S3JniUnrefLocal(s2); + S3JniUnrefLocal(s3); + S3JniHook_localundup(hook); + } return rc; } -JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); - S3JniHook * const pHook = ps ? &ps->authHook : 0; +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; - else if( !jHook ){ - S3JniHook_unref(env, pHook, 0); - return (jint)sqlite3_set_authorizer( ps->pDb, 0, 0 ); + S3JniMutex_S3JniDb_enter; + if( !jHook ){ + S3JniHook_unref(pHook, 0); + rc = sqlite3_set_authorizer( ps->pDb, 0, 0 ); }else{ - int rc = 0; + jclass klazz; if( pHook->jObj ){ if( (*env)->IsSameObject(env, pHook->jObj, jHook) ){ /* Same object - this is a no-op. */ + S3JniMutex_S3JniDb_leave; return 0; } - S3JniHook_unref(env, pHook, 0); + S3JniHook_unref(pHook, 0); } - pHook->jObj = REF_G(jHook); - pHook->klazz = REF_G((*env)->GetObjectClass(env, jHook)); - pHook->midCallback = (*env)->GetMethodID(env, pHook->klazz, - "xAuth", + 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"); - IFTHREW { - S3JniHook_unref(env, pHook, 0); - return s3jni_db_error(ps->pDb, SQLITE_ERROR, - "Error setting up Java parts of authorizer hook."); + 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); } - rc = sqlite3_set_authorizer(ps->pDb, s3jni_xAuth, ps); - if( rc ) S3JniHook_unref(env, pHook, 0); - return rc; + if( rc ) S3JniHook_unref(pHook, 0); } + S3JniMutex_S3JniDb_leave; + return rc; } -JDECL(void,1set_1last_1insert_1rowid)(JENV_CSELF, jobject jpDb, jlong rowId){ - sqlite3_set_last_insert_rowid(PtrGet_sqlite3_context(jpDb), +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); } -FIXME_THREADING(nphCache) -JDECL(jint,1status)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh, - jboolean reset ){ +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 ){ @@ -3142,9 +3921,10 @@ JDECL(jint,1status)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh, return (jint)rc; } -FIXME_THREADING(nphCache) -JDECL(jint,1status64)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh, - jboolean reset ){ +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 ){ @@ -3157,53 +3937,100 @@ JDECL(jint,1status64)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh static int s3jni_strlike_glob(int isLike, JNIEnv *const env, jbyteArray baG, jbyteArray baT, jint escLike){ int rc = 0; - jbyte * const pG = JBA_TOC(baG); - jbyte * const pT = pG ? JBA_TOC(baT) : 0; - OOM_CHECK(pT); + 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 = isLike ? sqlite3_strlike((const char *)pG, (const char *)pT, (unsigned int)escLike) : sqlite3_strglob((const char *)pG, (const char *)pT); - JBA_RELEASE(baG, pG); - JBA_RELEASE(baT, pT); + s3jni_jbytearray_release(baG, pG); + s3jni_jbytearray_release(baT, pT); return rc; } -JDECL(jint,1strglob)(JENV_CSELF, jbyteArray baG, jbyteArray baT){ - return s3jni_strlike_glob(0, env, baG, baT, 0); +S3JniApi(sqlite3_shutdown(),jint,1shutdown)( + JniArgsEnvClass +){ + s3jni_reset_auto_extension(env); + /* Free up env cache. */ + S3JniMutex_Env_enter; + while( SJG.envCache.aHead ){ + S3JniEnv_uncache( SJG.envCache.aHead->env ); + } + S3JniMutex_Env_leave; + /* Free up S3JniUdf recycling bin. */ + S3JniMutex_Global_enter; + while( S3JniGlobal.udf.aFree ){ + S3JniUdf * const u = S3JniGlobal.udf.aFree; + S3JniGlobal.udf.aFree = u->pNext; + u->pNext = 0; + S3JniUdf_free(env, u, 0); + } + S3JniMutex_Global_leave; + /* Free up S3JniDb recycling bin. */ + S3JniMutex_S3JniDb_enter; + while( S3JniGlobal.perDb.aFree ){ + S3JniDb * const d = S3JniGlobal.perDb.aFree; + S3JniGlobal.perDb.aFree = d->pNext; + d->pNext = 0; + S3JniDb_clear(env, d); + sqlite3_free(d); + } + S3JniMutex_S3JniDb_leave; + +#if 0 + /* + ** Is automatically closing any still-open dbs a good idea? We will + ** get rid of the perDb list once sqlite3 gets a per-db client + ** state, at which point we won't have a central list of databases + ** to close. + */ + S3JniMutex_S3JniDb_enter; + while( SJG.perDb.pHead ){ + s3jni_close_db(env, SJG.perDb.pHead->jDb, 2); + } + S3JniMutex_S3JniDb_leave; +#endif + + /* Do not clear S3JniGlobal.jvm: it's legal to restart the lib. */ + return sqlite3_shutdown(); } -JDECL(jint,1strlike)(JENV_CSELF, jbyteArray baG, jbyteArray baT, jint escChar){ - return s3jni_strlike_glob(1, env, baG, baT, escChar); +S3JniApi(sqlite3_strglob(),jint,1strglob)( + JniArgsEnvClass, jbyteArray baG, jbyteArray baT +){ + return s3jni_strlike_glob(0, env, baG, baT, 0); } -JDECL(jint,1shutdown)(JENV_CSELF){ - S3JniGlobal_S3JniEnvCache_clear(); - /* Do not clear S3JniGlobal.jvm: it's legal to call - sqlite3_initialize() again to restart the lib. */ - return sqlite3_shutdown(); +S3JniApi(sqlite3_strlike(),jint,1strlike)( + JniArgsEnvClass, jbyteArray baG, jbyteArray baT, jint escChar +){ + return s3jni_strlike_glob(1, env, baG, baT, escChar); } -JDECL(jstring,1sql)(JENV_CSELF, jobject jpStmt){ +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; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); zSql = sqlite3_sql(pStmt); - rv = s3jni_utf8_to_jstring(jc, zSql, -1); - OOM_CHECK(rv); + rv = s3jni_utf8_to_jstring( zSql, -1); } return rv; } -JDECL(jint,1step)(JENV_CSELF,jobject jStmt){ +S3JniApi(sqlite3_step(),jint,1step)( + JniArgsEnvClass,jobject jStmt +){ int rc = SQLITE_MISUSE; sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt); - if(pStmt){ + if( pStmt ){ rc = sqlite3_step(pStmt); } return rc; @@ -3211,252 +4038,190 @@ JDECL(jint,1step)(JENV_CSELF,jobject jStmt){ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ S3JniDb * const ps = (S3JniDb *)pC; - JNIEnv * const env = ps->env; + 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 */; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); - int rc; - int createStmt = 0; - switch(traceflag){ + int rc = 0; + S3JniHook hook; + + S3JniHook_localdup( env, &ps->hooks.trace, &hook ); + if( !hook.jObj ){ + return 0; + } + switch( traceflag ){ case SQLITE_TRACE_STMT: - jX = s3jni_utf8_to_jstring(jc, (const char *)pX, -1); - if(!jX) return SQLITE_NOMEM; - /*MARKER(("TRACE_STMT@%p SQL=%p / %s\n", pP, jX, (const char *)pX));*/ - createStmt = 1; + jX = s3jni_utf8_to_jstring( (const char *)pX, -1); + if( !jX ) rc = SQLITE_NOMEM; break; case SQLITE_TRACE_PROFILE: - jX = (*env)->NewObject(env, jc->g.cLong, jc->g.ctorLong1, + 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))); - if(!jX) return SQLITE_NOMEM; - createStmt = 1; + s3jni_oom_check( jX ); + if( !jX ) rc = SQLITE_NOMEM; break; case SQLITE_TRACE_ROW: - createStmt = 1; break; case SQLITE_TRACE_CLOSE: jP = ps->jDb; break; default: assert(!"cannot happen - unkown trace flag"); - return SQLITE_ERROR; + rc = SQLITE_ERROR; } - if( createStmt ){ - jP = jPUnref = new_sqlite3_stmt_wrapper(env, pP); - if(!jP){ - UNREF_L(jX); - return SQLITE_NOMEM; + if( 0==rc ){ + if( !jP ){ + /* Create a new temporary sqlite3_stmt wrapper */ + jP = jPUnref = new_sqlite3_stmt_wrapper(env, pP); + if( !jP ){ + S3JniUnrefLocal(jX); + rc = SQLITE_NOMEM; + } + } + assert(jP); + rc = (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback, + (jint)traceflag, jP, jX); + S3JniIfThrew{ + rc = s3jni_db_exception(env, ps, SQLITE_ERROR, + "sqlite3_trace_v2() callback threw."); } } - assert(jP); - rc = (int)(*env)->CallIntMethod(env, ps->trace.jObj, - ps->trace.midCallback, - (jint)traceflag, jP, jX); - IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("sqlite3_trace_v2() callback"); - EXCEPTION_CLEAR; - rc = SQLITE_ERROR; - } - UNREF_L(jPUnref); - UNREF_L(jX); + S3JniUnrefLocal(jPUnref); + S3JniUnrefLocal(jX); + S3JniHook_localundup(hook); return rc; } -JDECL(jint,1trace_1v2)(JENV_CSELF,jobject jDb, jint traceMask, jobject jTracer){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); - jclass klazz; - if( !traceMask || !jTracer ){ - if(ps){ - UNREF_G(ps->trace.jObj); - memset(&ps->trace, 0, sizeof(ps->trace)); - } - return (jint)sqlite3_trace_v2(ps->pDb, 0, 0, 0); - } - if(!ps) return SQLITE_NOMEM; - klazz = (*env)->GetObjectClass(env, jTracer); - ps->trace.midCallback = (*env)->GetMethodID(env, klazz, "xCallback", - "(ILjava/lang/Object;Ljava/lang/Object;)I"); - IFTHREW { - EXCEPTION_CLEAR; - return s3jni_db_error(ps->pDb, SQLITE_ERROR, - "Cannot not find matching xCallback() on Tracer object."); - } - ps->trace.jObj = REF_G(jTracer); - return sqlite3_trace_v2(ps->pDb, (unsigned)traceMask, s3jni_trace_impl, ps); -} +S3JniApi(sqlite3_trace_v2(),jint,1trace_1v2)( + JniArgsEnvClass,jobject jDb, jint traceMask, jobject jTracer +){ + S3JniDb * const ps = S3JniDb_from_java(jDb); + int rc; -static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, - const char *zTable, sqlite3_int64 nRowid){ - S3JniDb * const ps = pState; - JNIEnv * const env = ps->env; - /* ACHTUNG: this will break if zDb or zTable contain chars which are - different in MUTF-8 than UTF-8. That seems like a low risk, - but it's possible. */ - jstring jDbName; - jstring jTable; - jDbName = (*env)->NewStringUTF(env, zDb); - jTable = jDbName ? (*env)->NewStringUTF(env, zTable) : 0; - IFTHREW { - s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + if( !ps ) return SQLITE_MISUSE; + if( !traceMask || !jTracer ){ + S3JniMutex_S3JniDb_enter; + rc = (jint)sqlite3_trace_v2(ps->pDb, 0, 0, 0); + S3JniHook_unref(&ps->hooks.trace, 0); + S3JniMutex_S3JniDb_leave; }else{ - (*env)->CallVoidMethod(env, ps->updateHook.jObj, - ps->updateHook.midCallback, - (jint)opId, jDbName, jTable, (jlong)nRowid); - IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("update hook"); - EXCEPTION_CLEAR; - s3jni_db_error(ps->pDb, SQLITE_ERROR, "update hook callback threw."); + 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{ + S3JniMutex_S3JniDb_enter; + hook.jObj = S3JniRefGlobal(jTracer); + rc = sqlite3_trace_v2(ps->pDb, (unsigned)traceMask, s3jni_trace_impl, ps); + if( 0==rc ){ + S3JniHook_unref(&ps->hooks.trace, 0); + ps->hooks.trace = hook; + }else{ + S3JniHook_unref(&hook, 0); + } + S3JniMutex_S3JniDb_leave; } } - UNREF_L(jDbName); - UNREF_L(jTable); + return rc; } - -JDECL(jobject,1update_1hook)(JENV_CSELF, jobject jDb, jobject jHook){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); - jclass klazz; - jobject pOld = 0; - jmethodID xCallback; - S3JniHook * const pHook = &ps->updateHook; - if(!ps){ - s3jni_db_error(ps->pDb, SQLITE_MISUSE, 0); - return 0; - } - pOld = pHook->jObj; - if(pOld && jHook && - (*env)->IsSameObject(env, pOld, jHook)){ - return pOld; - } - if( !jHook ){ - if(pOld){ - jobject tmp = REF_L(pOld); - UNREF_G(pOld); - pOld = tmp; - } - memset(pHook, 0, sizeof(S3JniHook)); - sqlite3_update_hook(ps->pDb, 0, 0); - return pOld; - } - klazz = (*env)->GetObjectClass(env, jHook); - xCallback = (*env)->GetMethodID(env, klazz, "xUpdateHook", - "(ILjava/lang/String;Ljava/lang/String;J)V"); - IFTHREW { - EXCEPTION_CLEAR; - s3jni_db_error(ps->pDb, SQLITE_ERROR, - "Cannot not find matching callback on " - "update hook object."); - }else{ - pHook->midCallback = xCallback; - pHook->jObj = REF_G(jHook); - sqlite3_update_hook(ps->pDb, s3jni_update_hook_impl, ps); - if(pOld){ - jobject tmp = REF_L(pOld); - UNREF_G(pOld); - pOld = tmp; - } - } - return pOld; +S3JniApi(sqlite3_update_hook(),jobject,1update_1hook)( + JniArgsEnvClass, jobject jDb, jobject jHook +){ + return s3jni_updatepre_hook(env, 0, jDb, jHook); } -JDECL(jbyteArray,1value_1blob)(JENV_CSELF, jobject jpSVal){ +S3JniApi(sqlite3_value_blob(),jbyteArray,1value_1blob)( + JniArgsEnvClass, jobject jpSVal +){ sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); int const nLen = sqlite3_value_bytes(sv); const jbyte * pBytes = sqlite3_value_blob(sv); - jbyteArray const jba = pBytes - ? (*env)->NewByteArray(env, (jsize)nLen) + + s3jni_oom_check( nLen ? !!pBytes : 1 ); + return pBytes + ? s3jni_new_jbyteArray(pBytes, nLen) : NULL; - if(jba){ - (*env)->SetByteArrayRegion(env, jba, 0, nLen, pBytes); - } - return jba; } -JDECL(jdouble,1value_1double)(JENV_CSELF, jobject jpSVal){ +S3JniApi(sqlite3_value_double(),jdouble,1value_1double)( + JniArgsEnvClass, jobject jpSVal +){ return (jdouble) sqlite3_value_double(PtrGet_sqlite3_value(jpSVal)); } -JDECL(jobject,1value_1dup)(JENV_CSELF, jobject jpSVal){ +S3JniApi(sqlite3_value_dup(),jobject,1value_1dup)( + JniArgsEnvClass, jobject jpSVal +){ sqlite3_value * const sv = sqlite3_value_dup(PtrGet_sqlite3_value(jpSVal)); return sv ? new_sqlite3_value_wrapper(env, sv) : 0; } -JDECL(void,1value_1free)(JENV_CSELF, jobject jpSVal){ +S3JniApi(sqlite3_value_free(),void,1value_1free)( + JniArgsEnvClass, jobject jpSVal +){ sqlite3_value_free(PtrGet_sqlite3_value(jpSVal)); } -JDECL(jint,1value_1int)(JENV_CSELF, jobject jpSVal){ +S3JniApi(sqlite3_value_int(),jint,1value_1int)( + JniArgsEnvClass, jobject jpSVal +){ return (jint) sqlite3_value_int(PtrGet_sqlite3_value(jpSVal)); } -JDECL(jlong,1value_1int64)(JENV_CSELF, jobject jpSVal){ +S3JniApi(sqlite3_value_int64(),jlong,1value_1int64)( + JniArgsEnvClass, jobject jpSVal +){ return (jlong) sqlite3_value_int64(PtrGet_sqlite3_value(jpSVal)); } -JDECL(jobject,1value_1java_1object)(JENV_CSELF, jobject jpSVal){ - ResultJavaVal * const rv = sqlite3_value_pointer(PtrGet_sqlite3_value(jpSVal), RESULT_JAVA_VAL_STRING); - return rv ? rv->jObj : NULL; +S3JniApi(sqlite3_value_java_object(),jobject,1value_1java_1object)( + JniArgsEnvClass, jobject jpSVal +){ + return sqlite3_value_pointer(PtrGet_sqlite3_value(jpSVal), + ResultJavaValuePtrStr); } -JDECL(jstring,1value_1text)(JENV_CSELF, jobject jpSVal){ +S3JniApi(sqlite3_value_text_utf8(),jbyteArray,1value_1text_1utf8)( + JniArgsEnvClass, jobject jpSVal +){ sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); - int const n = sqlite3_value_bytes16(sv); - const void * const p = sqlite3_value_text16(sv); - return s3jni_text16_to_jstring(env, p, n); + int const n = sqlite3_value_bytes(sv); + const unsigned char * const p = sqlite3_value_text(sv); + return p ? s3jni_new_jbyteArray(p, n) : 0; } -JDECL(jbyteArray,1value_1text_1utf8)(JENV_CSELF, jobject jpSVal){ +S3JniApi(sqlite3_value_text(),jstring,1value_1text)( + JniArgsEnvClass, jobject jpSVal +){ sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); int const n = sqlite3_value_bytes(sv); const unsigned char * const p = sqlite3_value_text(sv); - return s3jni_new_jbyteArray(env, p, n); + return p ? s3jni_utf8_to_jstring( (const char *)p, n) : 0; } -static jbyteArray value_text16(int mode, JNIEnv * const env, jobject jpSVal){ - int const nLen = sqlite3_value_bytes16(PtrGet_sqlite3_value(jpSVal)); - jbyteArray jba; - const jbyte * pBytes; - switch(mode){ - case SQLITE_UTF16: - pBytes = sqlite3_value_text16(PtrGet_sqlite3_value(jpSVal)); - break; - case SQLITE_UTF16LE: - pBytes = sqlite3_value_text16le(PtrGet_sqlite3_value(jpSVal)); - break; - case SQLITE_UTF16BE: - pBytes = sqlite3_value_text16be(PtrGet_sqlite3_value(jpSVal)); - break; - default: - assert(!"not possible"); - return NULL; - } - jba = pBytes - ? (*env)->NewByteArray(env, (jsize)nLen) - : NULL; - if(jba){ - (*env)->SetByteArrayRegion(env, jba, 0, nLen, pBytes); - } - return jba; -} - -JDECL(jbyteArray,1value_1text16)(JENV_CSELF, jobject jpSVal){ - return value_text16(SQLITE_UTF16, env, jpSVal); -} - -JDECL(jbyteArray,1value_1text16le)(JENV_CSELF, jobject jpSVal){ - return value_text16(SQLITE_UTF16LE, env, jpSVal); -} - -JDECL(jbyteArray,1value_1text16be)(JENV_CSELF, jobject jpSVal){ - return value_text16(SQLITE_UTF16BE, env, jpSVal); +S3JniApi(sqlite3_value_text16(),jstring,1value_1text16)( + JniArgsEnvClass, jobject jpSVal +){ + sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); + const int n = sqlite3_value_bytes16(sv); + const void * const p = sqlite3_value_text16(sv); + return s3jni_text16_to_jstring(env, p, n); } -JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ +JniDecl(void,1jni_1internal_1details)(JniArgsEnvClass){ MARKER(("\nVarious bits of internal info:\n")); puts("FTS5 is " #ifdef SQLITE_ENABLE_FTS5 @@ -3469,29 +4234,58 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ puts("sizeofs:"); #define SO(T) printf("\tsizeof(" #T ") = %u\n", (unsigned)sizeof(T)) SO(void*); - SO(S3JniEnvCache); + SO(jmethodID); + SO(jfieldID); + SO(S3JniEnv); SO(S3JniHook); SO(S3JniDb); - SO(S3JniClassNames); + SO(S3JniNphRefs); printf("\t(^^^ %u NativePointerHolder subclasses)\n", - (unsigned)(sizeof(S3JniClassNames) / sizeof(const char *))); + (unsigned)S3Jni_NphCache_size); SO(S3JniGlobal); SO(S3JniAutoExtension); SO(S3JniUdf); +#undef SO +#ifdef SQLITE_JNI_ENABLE_METRICS printf("Cache info:\n"); - printf("\tNativePointerHolder cache: %u misses, %u hits\n", - S3JniGlobal.metrics.nphCacheMisses, - S3JniGlobal.metrics.nphCacheHits); - printf("\tJNIEnv cache %u misses, %u hits\n", - S3JniGlobal.metrics.envCacheMisses, - S3JniGlobal.metrics.envCacheHits); + 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 (%u for S3JniNphClass init, rest for " + "native pointer access)" + "\n\tperDb = %u" + "\n\tautoExt list = %u" + "\n\tS3JniUdf free-list = %u" + "\n\tmetrics = %u\n", + SJG.metrics.nMutexGlobal, SJG.metrics.nMutexEnv, + SJG.metrics.nMutexEnv2, SJG.metrics.nNphInit, + 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("\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, S3JniGlobal.metrics.udf.n##T) +#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", - S3JniGlobal.metrics.nDestroy); -#undef SO + SJG.metrics.nDestroy); +#else + puts("Built without SQLITE_JNI_ENABLE_METRICS."); +#endif } //////////////////////////////////////////////////////////////////////// @@ -3500,36 +4294,34 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ #ifdef SQLITE_ENABLE_FTS5 /* Creates a verbose JNI Fts5 function name. */ -#define JFuncNameFtsXA(Suffix) \ +#define JniFuncNameFtsXA(Suffix) \ Java_org_sqlite_jni_Fts5ExtensionApi_ ## Suffix -#define JFuncNameFtsApi(Suffix) \ +#define JniFuncNameFtsApi(Suffix) \ Java_org_sqlite_jni_fts5_1api_ ## Suffix -#define JFuncNameFtsTok(Suffix) \ +#define JniFuncNameFtsTok(Suffix) \ Java_org_sqlite_jni_fts5_tokenizer_ ## Suffix -#define JDECLFtsXA(ReturnType,Suffix) \ +#define JniDeclFtsXA(ReturnType,Suffix) \ JNIEXPORT ReturnType JNICALL \ - JFuncNameFtsXA(Suffix) -#define JDECLFtsApi(ReturnType,Suffix) \ + JniFuncNameFtsXA(Suffix) +#define JniDeclFtsApi(ReturnType,Suffix) \ JNIEXPORT ReturnType JNICALL \ - JFuncNameFtsApi(Suffix) -#define JDECLFtsTok(ReturnType,Suffix) \ + JniFuncNameFtsApi(Suffix) +#define JniDeclFtsTok(ReturnType,Suffix) \ JNIEXPORT ReturnType JNICALL \ - JFuncNameFtsTok(Suffix) + JniFuncNameFtsTok(Suffix) -#define PtrGet_fts5_api(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.fts5_api) -#define PtrGet_fts5_tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.fts5_tokenizer) -#define PtrGet_Fts5Context(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.Fts5Context) -#define PtrGet_Fts5Tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.Fts5Tokenizer) +#define PtrGet_fts5_api(OBJ) NativePointerHolder_get(OBJ,&S3JniNphRefs.fts5_api) +#define PtrGet_fts5_tokenizer(OBJ) NativePointerHolder_get(OBJ,&S3JniNphRefs.fts5_tokenizer) +#define PtrGet_Fts5Context(OBJ) NativePointerHolder_get(OBJ,&S3JniNphRefs.Fts5Context) +#define PtrGet_Fts5Tokenizer(OBJ) NativePointerHolder_get(OBJ,&S3JniNphRefs.Fts5Tokenizer) #define Fts5ExtDecl Fts5ExtensionApi const * const fext = s3jni_ftsext() /** State for binding Java-side FTS5 auxiliary functions. */ typedef struct { - JNIEnv * env; /* env registered from */; jobject jObj /* functor instance */; - jclass klazz /* jObj's class */; jobject jUserData /* 2nd arg to JNI binding of xCreateFunction(), ostensibly the 3rd arg to the lib-level xCreateFunction(), except @@ -3540,38 +4332,38 @@ typedef struct { } Fts5JniAux; static void Fts5JniAux_free(Fts5JniAux * const s){ - JNIEnv * const env = s->env; - if(env){ + S3JniDeclLocal_env; + if( env ){ /*MARKER(("FTS5 aux function cleanup: %s\n", s->zFuncName));*/ - s3jni_call_xDestroy(env, s->jObj, s->klazz); - UNREF_G(s->jObj); - UNREF_G(s->klazz); - UNREF_G(s->jUserData); + s3jni_call_xDestroy(env, 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); + if( p ) Fts5JniAux_free(p); } static Fts5JniAux * Fts5JniAux_alloc(JNIEnv * const env, jobject jObj){ - Fts5JniAux * s = sqlite3_malloc(sizeof(Fts5JniAux)); - if(s){ - const char * zSig = - "(Lorg/sqlite/jni/Fts5ExtensionApi;" - "Lorg/sqlite/jni/Fts5Context;" - "Lorg/sqlite/jni/sqlite3_context;" - "[Lorg/sqlite/jni/sqlite3_value;)V"; + Fts5JniAux * s = s3jni_malloc( sizeof(Fts5JniAux)); + + if( s ){ + jclass klazz; memset(s, 0, sizeof(Fts5JniAux)); - s->env = env; - s->jObj = REF_G(jObj); - s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); - s->jmid = (*env)->GetMethodID(env, s->klazz, "xFunction", zSig); - IFTHREW{ - EXCEPTION_REPORT; - EXCEPTION_CLEAR; + s->jObj = S3JniRefGlobal(jObj); + klazz = (*env)->GetObjectClass(env, jObj); + s->jmid = (*env)->GetMethodID(env, klazz, "xFunction", + "(Lorg/sqlite/jni/Fts5ExtensionApi;" + "Lorg/sqlite/jni/Fts5Context;" + "Lorg/sqlite/jni/sqlite3_context;" + "[Lorg/sqlite/jni/sqlite3_value;)V"); + S3JniUnrefLocal(klazz); + S3JniIfThrew{ + S3JniExceptionReport; + S3JniExceptionClear; Fts5JniAux_free(s); s = 0; } @@ -3584,29 +4376,36 @@ static inline Fts5ExtensionApi const * s3jni_ftsext(void){ } static inline jobject new_Fts5Context_wrapper(JNIEnv * const env, Fts5Context *sv){ - return new_NativePointerHolder_object(env, S3JniClassNames.Fts5Context, sv); + return new_NativePointerHolder_object(env, &S3JniNphRefs.Fts5Context, sv); } static inline jobject new_fts5_api_wrapper(JNIEnv * const env, fts5_api *sv){ - return new_NativePointerHolder_object(env, S3JniClassNames.fts5_api, sv); + return new_NativePointerHolder_object(env, &S3JniNphRefs.fts5_api, sv); } -/** - Returns a per-JNIEnv global ref to the Fts5ExtensionApi singleton - instance, or NULL on OOM. +/* +** Returns a per-JNIEnv global ref to the Fts5ExtensionApi singleton +** instance, or NULL on OOM. */ static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){ - S3JniEnvCache * const row = S3JniGlobal_env_cache(env); - if( !row->jFtsExt ){ - row->jFtsExt = new_NativePointerHolder_object(env, S3JniClassNames.Fts5ExtensionApi, - s3jni_ftsext()); - if(row->jFtsExt) row->jFtsExt = REF_G(row->jFtsExt); + if( !SJG.fts5.jFtsExt ){ + jobject pNPH = new_NativePointerHolder_object( + env, &S3JniNphRefs.Fts5ExtensionApi, s3jni_ftsext() + ); + S3JniMutex_Env_enter; + if( pNPH ){ + if( !SJG.fts5.jFtsExt ){ + SJG.fts5.jFtsExt = S3JniRefGlobal(pNPH); + } + S3JniUnrefLocal(pNPH); + } + S3JniMutex_Env_leave; } - return row->jFtsExt; + return SJG.fts5.jFtsExt; } /* -** Return a pointer to the fts5_api instance for database connection -** db. If an error occurs, return NULL and leave an error in the +** 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){ @@ -3620,33 +4419,33 @@ static fts5_api *s3jni_fts5_api_from_db(sqlite3 *db){ return pRet; } -JDECLFtsApi(jobject,getInstanceForDb)(JENV_CSELF,jobject jDb){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); +JniDeclFtsApi(jobject,getInstanceForDb)(JniArgsEnvClass,jobject jDb){ + S3JniDb * const ps = S3JniDb_from_java(jDb); jobject rv = 0; - if(!ps) return 0; - else if(ps->jFtsApi){ + if( !ps ) return 0; + else if( ps->jFtsApi ){ rv = ps->jFtsApi; }else{ fts5_api * const pApi = s3jni_fts5_api_from_db(ps->pDb); if( pApi ){ rv = new_fts5_api_wrapper(env, pApi); - ps->jFtsApi = rv ? REF_G(rv) : 0; + ps->jFtsApi = rv ? S3JniRefGlobal(rv) : 0; } } return rv; } -JDECLFtsXA(jobject,getInstance)(JENV_CSELF){ +JniDeclFtsXA(jobject,getInstance)(JniArgsEnvClass){ return s3jni_getFts5ExensionApi(env); } -JDECLFtsXA(jint,xColumnCount)(JENV_OSELF,jobject jCtx){ +JniDeclFtsXA(jint,xColumnCount)(JniArgsEnvObj,jobject jCtx){ Fts5ExtDecl; return (jint)fext->xColumnCount(PtrGet_Fts5Context(jCtx)); } -JDECLFtsXA(jint,xColumnSize)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOut32){ +JniDeclFtsXA(jint,xColumnSize)(JniArgsEnvObj,jobject jCtx, jint iIdx, jobject jOut32){ Fts5ExtDecl; int n1 = 0; int const rc = fext->xColumnSize(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1); @@ -3654,7 +4453,7 @@ JDECLFtsXA(jint,xColumnSize)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOut32) return rc; } -JDECLFtsXA(jint,xColumnText)(JENV_OSELF,jobject jCtx, jint iCol, +JniDeclFtsXA(jint,xColumnText)(JniArgsEnvObj,jobject jCtx, jint iCol, jobject jOut){ Fts5ExtDecl; const char *pz = 0; @@ -3662,12 +4461,11 @@ JDECLFtsXA(jint,xColumnText)(JENV_OSELF,jobject jCtx, jint iCol, int rc = fext->xColumnText(PtrGet_Fts5Context(jCtx), (int)iCol, &pz, &pn); if( 0==rc ){ - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); - jstring jstr = pz ? s3jni_utf8_to_jstring(jc, pz, pn) : 0; + jstring jstr = pz ? s3jni_utf8_to_jstring( pz, pn) : 0; if( pz ){ if( jstr ){ OutputPointer_set_String(env, jOut, jstr); - UNREF_L(jstr)/*jOut has a reference*/; + S3JniUnrefLocal(jstr)/*jOut has a reference*/; }else{ rc = SQLITE_NOMEM; } @@ -3676,7 +4474,7 @@ JDECLFtsXA(jint,xColumnText)(JENV_OSELF,jobject jCtx, jint iCol, return (jint)rc; } -JDECLFtsXA(jint,xColumnTotalSize)(JENV_OSELF,jobject jCtx, jint iCol, jobject jOut64){ +JniDeclFtsXA(jint,xColumnTotalSize)(JniArgsEnvObj,jobject jCtx, jint iCol, jobject jOut64){ Fts5ExtDecl; sqlite3_int64 nOut = 0; int const rc = fext->xColumnTotalSize(PtrGet_Fts5Context(jCtx), (int)iCol, &nOut); @@ -3684,9 +4482,9 @@ JDECLFtsXA(jint,xColumnTotalSize)(JENV_OSELF,jobject jCtx, jint iCol, jobject jO return (jint)rc; } -/** - Proxy for fts5_extension_function instances plugged in via - fts5_api::xCreateFunction(). +/* +** Proxy for fts5_extension_function instances plugged in via +** fts5_api::xCreateFunction(). */ static void s3jni_fts5_extension_function(Fts5ExtensionApi const *pApi, Fts5Context *pFts, @@ -3694,46 +4492,46 @@ static void s3jni_fts5_extension_function(Fts5ExtensionApi const *pApi, int argc, sqlite3_value **argv){ Fts5JniAux * const pAux = pApi->xUserData(pFts); - JNIEnv *env; jobject jpCx = 0; jobjectArray jArgv = 0; jobject jpFts = 0; jobject jFXA; int rc; + S3JniDeclLocal_env; + assert(pAux); - env = pAux->env; jFXA = s3jni_getFts5ExensionApi(env); if( !jFXA ) goto error_oom; jpFts = new_Fts5Context_wrapper(env, pFts); - if(!jpFts) goto error_oom; + if( !jpFts ) goto error_oom; rc = udf_args(env, pCx, argc, argv, &jpCx, &jArgv); - if(rc) goto error_oom; + if( rc ) goto error_oom; (*env)->CallVoidMethod(env, pAux->jObj, pAux->jmid, jFXA, jpFts, jpCx, jArgv); - IFTHREW{ - EXCEPTION_CLEAR; - udf_report_exception(pCx, pAux->zFuncName, "xFunction"); + S3JniIfThrew{ + udf_report_exception(env, 1, pCx, pAux->zFuncName, "xFunction"); } - UNREF_L(jpFts); - UNREF_L(jpCx); - UNREF_L(jArgv); + S3JniUnrefLocal(jpFts); + S3JniUnrefLocal(jpCx); + S3JniUnrefLocal(jArgv); return; error_oom: assert( !jArgv ); assert( !jpCx ); - UNREF_L(jpFts); + S3JniUnrefLocal(jpFts); sqlite3_result_error_nomem(pCx); return; } -JDECLFtsApi(jint,xCreateFunction)(JENV_OSELF, jstring jName, +JniDeclFtsApi(jint,xCreateFunction)(JniArgsEnvObj, jstring jName, jobject jUserData, jobject jFunc){ fts5_api * const pApi = PtrGet_fts5_api(jSelf); int rc; - char const * zName; + char * zName; Fts5JniAux * pAux; + assert(pApi); - zName = JSTR_TOC(jName); + zName = s3jni_jstring_to_utf8( jName, 0); if(!zName) return SQLITE_NOMEM; pAux = Fts5JniAux_alloc(env, jFunc); if( pAux ){ @@ -3744,42 +4542,45 @@ JDECLFtsApi(jint,xCreateFunction)(JENV_OSELF, jstring jName, rc = SQLITE_NOMEM; } if( 0==rc ){ - pAux->jUserData = jUserData ? REF_G(jUserData) : 0; - pAux->zFuncName = sqlite3_mprintf("%s", zName) - /* OOM here is non-fatal. Ignore it. */; + pAux->jUserData = jUserData ? S3JniRefGlobal(jUserData) : 0; + pAux->zFuncName = zName; + }else{ + sqlite3_free(zName); } - JSTR_RELEASE(jName, zName); return (jint)rc; } -typedef struct s3jni_fts5AuxData s3jni_fts5AuxData; -struct s3jni_fts5AuxData { - JNIEnv *env; +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 s3jni_fts5AuxData_xDestroy(void *x){ - if(x){ - s3jni_fts5AuxData * const p = x; - if(p->jObj){ - JNIEnv *env = p->env; - s3jni_call_xDestroy(env, p->jObj, 0); - UNREF_G(p->jObj); +static void S3JniFts5AuxData_xDestroy(void *x){ + if( x ){ + S3JniFts5AuxData * const p = x; + if( p->jObj ){ + S3JniDeclLocal_env; + s3jni_call_xDestroy(env, p->jObj); + S3JniUnrefGlobal(p->jObj); } sqlite3_free(x); } } -JDECLFtsXA(jobject,xGetAuxdata)(JENV_OSELF,jobject jCtx, jboolean bClear){ +JniDeclFtsXA(jobject,xGetAuxdata)(JniArgsEnvObj,jobject jCtx, jboolean bClear){ Fts5ExtDecl; jobject rv = 0; - s3jni_fts5AuxData * const pAux = fext->xGetAuxdata(PtrGet_Fts5Context(jCtx), bClear); - if(pAux){ - if(bClear){ + S3JniFts5AuxData * const pAux = fext->xGetAuxdata(PtrGet_Fts5Context(jCtx), bClear); + if( pAux ){ + if( bClear ){ if( pAux->jObj ){ - rv = REF_L(pAux->jObj); - UNREF_G(pAux->jObj); + rv = S3JniRefLocal(pAux->jObj); + S3JniUnrefGlobal(pAux->jObj); } /* Note that we do not call xDestroy() in this case. */ sqlite3_free(pAux); @@ -3790,7 +4591,7 @@ JDECLFtsXA(jobject,xGetAuxdata)(JENV_OSELF,jobject jCtx, jboolean bClear){ return rv; } -JDECLFtsXA(jint,xInst)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOutPhrase, +JniDeclFtsXA(jint,xInst)(JniArgsEnvObj,jobject jCtx, jint iIdx, jobject jOutPhrase, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; int n1 = 0, n2 = 2, n3 = 0; @@ -3803,7 +4604,7 @@ JDECLFtsXA(jint,xInst)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOutPhrase, return rc; } -JDECLFtsXA(jint,xInstCount)(JENV_OSELF,jobject jCtx, jobject jOut32){ +JniDeclFtsXA(jint,xInstCount)(JniArgsEnvObj,jobject jCtx, jobject jOut32){ Fts5ExtDecl; int nOut = 0; int const rc = fext->xInstCount(PtrGet_Fts5Context(jCtx), &nOut); @@ -3811,124 +4612,99 @@ JDECLFtsXA(jint,xInstCount)(JENV_OSELF,jobject jCtx, jobject jOut32){ return (jint)rc; } -JDECLFtsXA(jint,xPhraseCount)(JENV_OSELF,jobject jCtx){ +JniDeclFtsXA(jint,xPhraseCount)(JniArgsEnvObj,jobject jCtx){ Fts5ExtDecl; return (jint)fext->xPhraseCount(PtrGet_Fts5Context(jCtx)); } -/** - Initializes jc->jPhraseIter if it needed it. -*/ -static void s3jni_phraseIter_init(JNIEnv *const env, S3JniEnvCache * const jc, - jobject jIter){ - if(!jc->jPhraseIter.klazz){ - jclass klazz = (*env)->GetObjectClass(env, jIter); - jc->jPhraseIter.klazz = REF_G(klazz); - jc->jPhraseIter.fidA = (*env)->GetFieldID(env, klazz, "a", "J"); - EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field."); - jc->jPhraseIter.fidB = (*env)->GetFieldID(env, klazz, "a", "J"); - EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field."); - } -} - /* Copy the 'a' and 'b' fields from pSrc to Fts5PhraseIter object jIter. */ -static void s3jni_phraseIter_NToJ(JNIEnv *const env, S3JniEnvCache const * const jc, - Fts5PhraseIter const * const pSrc, - jobject jIter){ - assert(jc->jPhraseIter.klazz); - (*env)->SetLongField(env, jIter, jc->jPhraseIter.fidA, (jlong)pSrc->a); - EXCEPTION_IS_FATAL("Cannot set Fts5PhraseIter.a field."); - (*env)->SetLongField(env, jIter, jc->jPhraseIter.fidB, (jlong)pSrc->b); - EXCEPTION_IS_FATAL("Cannot set Fts5PhraseIter.b field."); +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, (jlong)pSrc->a); + S3JniExceptionIsFatal("Cannot set Fts5PhraseIter.a field."); + (*env)->SetLongField(env, jIter, g->fts5.jPhraseIter.fidB, (jlong)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, S3JniEnvCache const * const jc, - jobject jIter, Fts5PhraseIter * const pDest){ - assert(jc->jPhraseIter.klazz); +static void s3jni_phraseIter_JToN(JNIEnv *const env, jobject jIter, + Fts5PhraseIter * const pDest){ + S3JniGlobalType * const g = &S3JniGlobal; + assert(g->fts5.jPhraseIter.fidA); pDest->a = - (const unsigned char *)(*env)->GetLongField(env, jIter, jc->jPhraseIter.fidA); - EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field."); + (const unsigned char *)(*env)->GetLongField(env, jIter, g->fts5.jPhraseIter.fidA); + S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.a field."); pDest->b = - (const unsigned char *)(*env)->GetLongField(env, jIter, jc->jPhraseIter.fidB); - EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field."); + (const unsigned char *)(*env)->GetLongField(env, jIter, g->fts5.jPhraseIter.fidB); + S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.b field."); } -JDECLFtsXA(jint,xPhraseFirst)(JENV_OSELF,jobject jCtx, jint iPhrase, +JniDeclFtsXA(jint,xPhraseFirst)(JniArgsEnvObj,jobject jCtx, jint iPhrase, jobject jIter, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); Fts5PhraseIter iter; int rc, iCol = 0, iOff = 0; - s3jni_phraseIter_init(env, jc, jIter); rc = fext->xPhraseFirst(PtrGet_Fts5Context(jCtx), (int)iPhrase, &iter, &iCol, &iOff); if( 0==rc ){ OutputPointer_set_Int32(env, jOutCol, iCol); OutputPointer_set_Int32(env, jOutOff, iOff); - s3jni_phraseIter_NToJ(env, jc, &iter, jIter); + s3jni_phraseIter_NToJ(env, &iter, jIter); } return rc; } -JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_OSELF,jobject jCtx, jint iPhrase, +JniDeclFtsXA(jint,xPhraseFirstColumn)(JniArgsEnvObj,jobject jCtx, jint iPhrase, jobject jIter, jobject jOutCol){ Fts5ExtDecl; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); Fts5PhraseIter iter; int rc, iCol = 0; - s3jni_phraseIter_init(env, jc, jIter); rc = fext->xPhraseFirstColumn(PtrGet_Fts5Context(jCtx), (int)iPhrase, &iter, &iCol); if( 0==rc ){ OutputPointer_set_Int32(env, jOutCol, iCol); - s3jni_phraseIter_NToJ(env, jc, &iter, jIter); + s3jni_phraseIter_NToJ(env, &iter, jIter); } return rc; } -JDECLFtsXA(void,xPhraseNext)(JENV_OSELF,jobject jCtx, jobject jIter, +JniDeclFtsXA(void,xPhraseNext)(JniArgsEnvObj,jobject jCtx, jobject jIter, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); Fts5PhraseIter iter; int iCol = 0, iOff = 0; - if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/; - s3jni_phraseIter_JToN(env, jc, jIter, &iter); - fext->xPhraseNext(PtrGet_Fts5Context(jCtx), - &iter, &iCol, &iOff); + s3jni_phraseIter_JToN(env, jIter, &iter); + fext->xPhraseNext(PtrGet_Fts5Context(jCtx), &iter, &iCol, &iOff); OutputPointer_set_Int32(env, jOutCol, iCol); OutputPointer_set_Int32(env, jOutOff, iOff); - s3jni_phraseIter_NToJ(env, jc, &iter, jIter); + s3jni_phraseIter_NToJ(env, &iter, jIter); } -JDECLFtsXA(void,xPhraseNextColumn)(JENV_OSELF,jobject jCtx, jobject jIter, +JniDeclFtsXA(void,xPhraseNextColumn)(JniArgsEnvObj,jobject jCtx, jobject jIter, jobject jOutCol){ Fts5ExtDecl; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); Fts5PhraseIter iter; int iCol = 0; - if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/; - s3jni_phraseIter_JToN(env, jc, jIter, &iter); + s3jni_phraseIter_JToN(env, jIter, &iter); fext->xPhraseNextColumn(PtrGet_Fts5Context(jCtx), &iter, &iCol); OutputPointer_set_Int32(env, jOutCol, iCol); - s3jni_phraseIter_NToJ(env, jc, &iter, jIter); + s3jni_phraseIter_NToJ(env, &iter, jIter); } -JDECLFtsXA(jint,xPhraseSize)(JENV_OSELF,jobject jCtx, jint iPhrase){ +JniDeclFtsXA(jint,xPhraseSize)(JniArgsEnvObj,jobject jCtx, jint iPhrase){ Fts5ExtDecl; return (jint)fext->xPhraseSize(PtrGet_Fts5Context(jCtx), (int)iPhrase); } -/** - State for use with xQueryPhrase() and xTokenize(). -*/ +/* State for use with xQueryPhrase() and xTokenize(). */ struct s3jni_xQueryPhraseState { - JNIEnv *env; Fts5ExtensionApi const * fext; - S3JniEnvCache const * jc; + S3JniEnv const * jc; jmethodID midCallback; jobject jCallback; jobject jFcx; @@ -3946,25 +4722,25 @@ static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi, guaranteed to be the same one passed to xQueryPhrase(). If it's not, we'll have to create a new wrapper object on every call. */ struct s3jni_xQueryPhraseState const * s = pData; - JNIEnv * const env = s->env; + S3JniDeclLocal_env; int rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback, - s->jc->jFtsExt, s->jFcx); - IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("xQueryPhrase callback"); - EXCEPTION_CLEAR; + SJG.fts5.jFtsExt, s->jFcx); + S3JniIfThrew{ + S3JniExceptionWarnCallbackThrew("xQueryPhrase() callback"); + S3JniExceptionClear; rc = SQLITE_ERROR; } return rc; } -JDECLFtsXA(jint,xQueryPhrase)(JENV_OSELF,jobject jFcx, jint iPhrase, +JniDeclFtsXA(jint,xQueryPhrase)(JniArgsEnvObj,jobject jFcx, jint iPhrase, jobject jCallback){ Fts5ExtDecl; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + S3JniEnv * const jc = S3JniEnv_get(); struct s3jni_xQueryPhraseState s; jclass klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL; + if( !klazz ) return SQLITE_MISUSE; - s.env = env; s.jc = jc; s.jCallback = jCallback; s.jFcx = jFcx; @@ -3972,13 +4748,14 @@ JDECLFtsXA(jint,xQueryPhrase)(JENV_OSELF,jobject jFcx, jint iPhrase, s.midCallback = (*env)->GetMethodID(env, klazz, "xCallback", "(Lorg.sqlite.jni.Fts5ExtensionApi;" "Lorg.sqlite.jni.Fts5Context;)I"); - EXCEPTION_IS_FATAL("Could not extract xQueryPhraseCallback.xCallback method."); + S3JniUnrefLocal(klazz); + S3JniExceptionIsFatal("Could not extract xQueryPhraseCallback.xCallback method."); return (jint)fext->xQueryPhrase(PtrGet_Fts5Context(jFcx), iPhrase, &s, s3jni_xQueryPhrase); } -JDECLFtsXA(jint,xRowCount)(JENV_OSELF,jobject jCtx, jobject jOut64){ +JniDeclFtsXA(jint,xRowCount)(JniArgsEnvObj,jobject jCtx, jobject jOut64){ Fts5ExtDecl; sqlite3_int64 nOut = 0; int const rc = fext->xRowCount(PtrGet_Fts5Context(jCtx), &nOut); @@ -3986,52 +4763,48 @@ JDECLFtsXA(jint,xRowCount)(JENV_OSELF,jobject jCtx, jobject jOut64){ return (jint)rc; } -JDECLFtsXA(jlong,xRowid)(JENV_OSELF,jobject jCtx){ +JniDeclFtsXA(jlong,xRowid)(JniArgsEnvObj,jobject jCtx){ Fts5ExtDecl; return (jlong)fext->xRowid(PtrGet_Fts5Context(jCtx)); } -JDECLFtsXA(int,xSetAuxdata)(JENV_OSELF,jobject jCtx, jobject jAux){ +JniDeclFtsXA(int,xSetAuxdata)(JniArgsEnvObj,jobject jCtx, jobject jAux){ Fts5ExtDecl; int rc; - s3jni_fts5AuxData * pAux; - pAux = sqlite3_malloc(sizeof(*pAux)); - if(!pAux){ - if(jAux){ - // Emulate how xSetAuxdata() behaves when it cannot alloc - // its auxdata wrapper. - s3jni_call_xDestroy(env, jAux, 0); + 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(env, jAux); } return SQLITE_NOMEM; } - pAux->env = env; - pAux->jObj = REF_G(jAux); + pAux->jObj = S3JniRefGlobal(jAux); rc = fext->xSetAuxdata(PtrGet_Fts5Context(jCtx), pAux, - s3jni_fts5AuxData_xDestroy); + S3JniFts5AuxData_xDestroy); return rc; } -/** - xToken() impl for xTokenize(). -*/ +/* 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; - JNIEnv * const env = s->env; jbyteArray jba; + if( s->tok.zPrev == z && s->tok.nPrev == nZ ){ jba = s->tok.jba; }else{ - if(s->tok.jba){ - UNREF_L(s->tok.jba); - } + S3JniUnrefLocal(s->tok.jba); s->tok.zPrev = z; s->tok.nPrev = nZ; - s->tok.jba = (*env)->NewByteArray(env, (jint)nZ); + s->tok.jba = s3jni_new_jbyteArray(z, nZ); if( !s->tok.jba ) return SQLITE_NOMEM; jba = s->tok.jba; - (*env)->SetByteArrayRegion(env, jba, 0, (jint)nZ, (const jbyte*)z); } rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback, (jint)tFlags, jba, (jint)iStart, @@ -4039,41 +4812,43 @@ static int s3jni_xTokenize_xToken(void *p, int tFlags, const char* z, return rc; } -/** - Proxy for Fts5ExtensionApi.xTokenize() and fts5_tokenizer.xTokenize() +/* +** Proxy for Fts5ExtensionApi.xTokenize() and +** fts5_tokenizer.xTokenize() */ -static jint s3jni_fts5_xTokenize(JENV_OSELF, const char *zClassName, +static jint s3jni_fts5_xTokenize(JniArgsEnvObj, S3JniNphRef const *pRef, jint tokFlags, jobject jFcx, jbyteArray jbaText, jobject jCallback){ Fts5ExtDecl; - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + S3JniEnv * const jc = S3JniEnv_get(); struct s3jni_xQueryPhraseState s; int rc = 0; - jbyte * const pText = jCallback ? JBA_TOC(jbaText) : 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.env = env; s.jc = jc; s.jCallback = jCallback; s.jFcx = jFcx; s.fext = fext; - s.midCallback = (*env)->GetMethodID(env, klazz, "xToken", "(I[BII)I"); - IFTHREW { - EXCEPTION_REPORT; - EXCEPTION_CLEAR; - JBA_RELEASE(jbaText, pText); + 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 = REF_L(jbaText); + s.tok.jba = S3JniRefLocal(jbaText); s.tok.zPrev = (const char *)pText; s.tok.nPrev = (int)nText; - if( zClassName == S3JniClassNames.Fts5ExtensionApi ){ + if( pRef == &S3JniNphRefs.Fts5ExtensionApi ){ rc = fext->xTokenize(PtrGet_Fts5Context(jFcx), (const char *)pText, (int)nText, &s, s3jni_xTokenize_xToken); - }else if( zClassName == S3JniClassNames.fts5_tokenizer ){ + }else if( pRef == &S3JniNphRefs.fts5_tokenizer ){ fts5_tokenizer * const pTok = PtrGet_fts5_tokenizer(jSelf); rc = pTok->xTokenize(PtrGet_Fts5Tokenizer(jFcx), &s, tokFlags, (const char *)pText, (int)nText, @@ -4081,28 +4856,28 @@ static jint s3jni_fts5_xTokenize(JENV_OSELF, const char *zClassName, }else{ (*env)->FatalError(env, "This cannot happen. Maintenance required."); } - if(s.tok.jba){ + if( s.tok.jba ){ assert( s.tok.zPrev ); - UNREF_L(s.tok.jba); + S3JniUnrefLocal(s.tok.jba); } - JBA_RELEASE(jbaText, pText); + s3jni_jbytearray_release(jbaText, pText); return (jint)rc; } -JDECLFtsXA(jint,xTokenize)(JENV_OSELF,jobject jFcx, jbyteArray jbaText, - jobject jCallback){ - return s3jni_fts5_xTokenize(env, jSelf, S3JniClassNames.Fts5ExtensionApi, +JniDeclFtsXA(jint,xTokenize)(JniArgsEnvObj,jobject jFcx, jbyteArray jbaText, + jobject jCallback){ + return s3jni_fts5_xTokenize(env, jSelf, &S3JniNphRefs.Fts5ExtensionApi, 0, jFcx, jbaText, jCallback); } -JDECLFtsTok(jint,xTokenize)(JENV_OSELF,jobject jFcx, jint tokFlags, - jbyteArray jbaText, jobject jCallback){ - return s3jni_fts5_xTokenize(env, jSelf, S3JniClassNames.Fts5Tokenizer, +JniDeclFtsTok(jint,xTokenize)(JniArgsEnvObj,jobject jFcx, jint tokFlags, + jbyteArray jbaText, jobject jCallback){ + return s3jni_fts5_xTokenize(env, jSelf, &S3JniNphRefs.Fts5Tokenizer, tokFlags, jFcx, jbaText, jCallback); } -JDECLFtsXA(jobject,xUserData)(JENV_OSELF,jobject jFcx){ +JniDeclFtsXA(jobject,xUserData)(JniArgsEnvObj,jobject jFcx){ Fts5ExtDecl; Fts5JniAux * const pAux = fext->xUserData(PtrGet_Fts5Context(jFcx)); return pAux ? pAux->jUserData : 0; @@ -4114,7 +4889,7 @@ JDECLFtsXA(jobject,xUserData)(JENV_OSELF,jobject jFcx){ // End of the main API bindings. Start of SQLTester bits... //////////////////////////////////////////////////////////////////////// -#ifdef S3JNI_ENABLE_SQLTester +#ifdef SQLITE_JNI_ENABLE_SQLTester typedef struct SQLTesterJni SQLTesterJni; struct SQLTesterJni { sqlite3_int64 nDup; @@ -4151,9 +4926,10 @@ static void SQLTester_dup_func( 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 = sqlite3_malloc( (n+16)&~7 ))!=0 ){ + 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); @@ -4280,18 +5056,18 @@ static int SQLTester_strnotglob(const char *zGlob, const char *z){ JNIEXPORT jint JNICALL Java_org_sqlite_jni_tester_SQLTester_strglob( - JENV_CSELF, jbyteArray baG, jbyteArray baT + JniArgsEnvClass, jbyteArray baG, jbyteArray baT ){ int rc = 0; - jbyte * const pG = JBA_TOC(baG); - jbyte * const pT = pG ? JBA_TOC(baT) : 0; - OOM_CHECK(pT); + 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); - JBA_RELEASE(baG, pG); - JBA_RELEASE(baT, pT); + s3jni_jbytearray_release(baG, pG); + s3jni_jbytearray_release(baT, pT); return rc; } @@ -4306,36 +5082,24 @@ static int SQLTester_auto_extension(sqlite3 *pDb, const char **pzErr, } JNIEXPORT void JNICALL -Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions(JENV_CSELF){ +Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions(JniArgsEnvClass){ sqlite3_auto_extension( (void(*)(void))SQLTester_auto_extension ); } -#endif /* S3JNI_ENABLE_SQLTester */ +#endif /* SQLITE_JNI_ENABLE_SQLTester */ //////////////////////////////////////////////////////////////////////// // End of SQLTester bindings. Start of lower-level bits. //////////////////////////////////////////////////////////////////////// - -/** - Uncaches the current JNIEnv from the S3JniGlobal state, clearing any - resources owned by that cache entry and making that slot available - for re-use. It is important that the Java-side decl of this - function be declared as synchronous. -*/ -JNIEXPORT jboolean JNICALL -Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JENV_CSELF){ - return S3JniGlobal_env_uncache(env) ? JNI_TRUE : JNI_FALSE; -} - -/** - Called during static init of the SQLite3Jni class to sync certain - compile-time constants to Java-space. - - This routine is part of the reason why we have to #include - sqlite3.c instead of sqlite3.h. +/* +** Called during static init of the SQLite3Jni class to sync certain +** compile-time constants to Java-space. +** +** This routine is part of the reason why we have to #include +** sqlite3.c instead of sqlite3.h. */ JNIEXPORT void JNICALL -Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ +Java_org_sqlite_jni_SQLite3Jni_init(JniArgsEnvClass){ enum JType { JTYPE_INT, JTYPE_BOOL @@ -4346,13 +5110,6 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ int value; } ConfigFlagEntry; const ConfigFlagEntry aLimits[] = { - {"SQLITE_ENABLE_FTS5", JTYPE_BOOL, -#ifdef SQLITE_ENABLE_FTS5 - 1 -#else - 0 -#endif - }, {"SQLITE_MAX_ALLOCATION_SIZE", JTYPE_INT, SQLITE_MAX_ALLOCATION_SIZE}, {"SQLITE_LIMIT_LENGTH", JTYPE_INT, SQLITE_LIMIT_LENGTH}, {"SQLITE_MAX_LENGTH", JTYPE_INT, SQLITE_MAX_LENGTH}, @@ -4378,35 +5135,91 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ {"SQLITE_MAX_TRIGGER_DEPTH", JTYPE_INT, SQLITE_MAX_TRIGGER_DEPTH}, {"SQLITE_LIMIT_WORKER_THREADS", JTYPE_INT, SQLITE_LIMIT_WORKER_THREADS}, {"SQLITE_MAX_WORKER_THREADS", JTYPE_INT, SQLITE_MAX_WORKER_THREADS}, + {"SQLITE_THREADSAFE", JTYPE_INT, SQLITE_THREADSAFE}, {0,0} }; jfieldID fieldId; + jclass klazz; const ConfigFlagEntry * pConfFlag; +#if 0 + if( 0==sqlite3_threadsafe() ){ + (*env)->FatalError(env, "sqlite3 currently requires SQLITE_THREADSAFE!=0."); + return; + } +#endif memset(&S3JniGlobal, 0, sizeof(S3JniGlobal)); - if( (*env)->GetJavaVM(env, &S3JniGlobal.jvm) ){ + if( (*env)->GetJavaVM(env, &SJG.jvm) ){ (*env)->FatalError(env, "GetJavaVM() failure shouldn't be possible."); return; } -#if 0 - /* Just for sanity checking... */ - (void)S3JniGlobal_env_cache(env); - if( !S3JniGlobal.envCache.aHead ){ - (*env)->FatalError(env, "Could not allocate JNIEnv-specific cache."); - return; + + /* 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)."); + + { /* 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); } - assert( 1 == S3JniGlobal.metrics.envCacheMisses ); - assert( env == S3JniGlobal.envCache.aHead->env ); - assert( 0 != S3JniGlobal.envCache.aHead->g.cObj ); + +#ifdef SQLITE_ENABLE_FTS5 + klazz = (*env)->FindClass(env, "org/sqlite/jni/Fts5PhraseIter"); + S3JniExceptionIsFatal("Error getting reference to org.sqlite.jni.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.envCache.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + s3jni_oom_fatal( SJG.envCache.mutex ); + SJG.perDb.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + s3jni_oom_fatal( SJG.perDb.mutex ); + SJG.autoExt.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + s3jni_oom_fatal( SJG.autoExt.mutex ); + +#if S3JNI_METRICS_MUTEX + SJG.metrics.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + s3jni_oom_fatal( SJG.metrics.mutex ); #endif + sqlite3_shutdown() + /* So that it becomes legal for Java-level code to call + ** sqlite3_config(), if it's ever implemented. */; + + /* Set up static "consts" of the SQLite3Jni class. */ for( pConfFlag = &aLimits[0]; pConfFlag->zName; ++pConfFlag ){ char const * zSig = (JTYPE_BOOL == pConfFlag->jtype) ? "Z" : "I"; fieldId = (*env)->GetStaticFieldID(env, jKlazz, pConfFlag->zName, zSig); - EXCEPTION_IS_FATAL("Missing an expected static member of the SQLite3Jni class."); - //MARKER(("Setting %s (field=%p) = %d\n", pConfFlag->zName, fieldId, pConfFlag->value)); + S3JniExceptionIsFatal("Missing an expected static member of the SQLite3Jni class."); assert(fieldId); - switch(pConfFlag->jtype){ + switch( pConfFlag->jtype ){ case JTYPE_INT: (*env)->SetStaticIntField(env, jKlazz, fieldId, (jint)pConfFlag->value); break; @@ -4415,6 +5228,6 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ pConfFlag->value ? JNI_TRUE : JNI_FALSE); break; } - EXCEPTION_IS_FATAL("Seting a static member of the SQLite3Jni class failed."); + S3JniExceptionIsFatal("Seting a static member of the SQLite3Jni class failed."); } } diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index bcb55c4f1..40dd44990 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -765,16 +765,24 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_init /* * Class: org_sqlite_jni_SQLite3Jni - * Method: uncacheJniEnv + * Method: sqlite3_java_uncache_thread * Signature: ()Z */ -JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1java_1uncache_1thread (JNIEnv *, jclass); /* * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_aggregate_context + * Signature: (Lorg/sqlite/jni/sqlite3_context;Z)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1aggregate_1context + (JNIEnv *, jclass, jobject, jboolean); + +/* + * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_auto_extension - * Signature: (Lorg/sqlite/jni/AutoExtension;)I + * Signature: (Lorg/sqlite/jni/AutoExtensionCallback;)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1auto_1extension (JNIEnv *, jclass, jobject); @@ -813,6 +821,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1int64 /* * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_java_object + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;ILjava/lang/Object;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1java_1object + (JNIEnv *, jclass, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_bind_null * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I */ @@ -845,6 +861,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1text /* * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_text16 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I[BI)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1text16 + (JNIEnv *, jclass, jobject, jint, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_bind_zeroblob * Signature: (Lorg/sqlite/jni/sqlite3_stmt;II)I */ @@ -862,7 +886,7 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1zeroblob64 /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_busy_handler - * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/BusyHandler;)I + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/BusyHandlerCallback;)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1busy_1handler (JNIEnv *, jclass, jobject, jobject); @@ -878,7 +902,7 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1busy_1timeout /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_cancel_auto_extension - * Signature: (Lorg/sqlite/jni/AutoExtension;)Z + * Signature: (Lorg/sqlite/jni/AutoExtensionCallback;)Z */ JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1cancel_1auto_1extension (JNIEnv *, jclass, jobject); @@ -1013,18 +1037,26 @@ JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1table_ /* * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_column_text16 - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + * Method: sqlite3_column_text_utf8 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)[B */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text16 +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text_1utf8 (JNIEnv *, jclass, jobject, jint); /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_column_text - * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)[B + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; */ -JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_text16 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text16 (JNIEnv *, jclass, jobject, jint); /* @@ -1046,7 +1078,7 @@ JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1value /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_collation_needed - * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/CollationNeeded;)I + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/CollationNeededCallback;)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1collation_1needed (JNIEnv *, jclass, jobject, jobject); @@ -1062,7 +1094,7 @@ JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1context_1db_1h /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_commit_hook - * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/CommitHook;)Lorg/sqlite/jni/CommitHook; + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/CommitHookCallback;)Lorg/sqlite/jni/CommitHookCallback; */ JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1commit_1hook (JNIEnv *, jclass, jobject, jobject); @@ -1085,8 +1117,24 @@ JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1compileoption /* * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_config + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1config__I + (JNIEnv *, jclass, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_config + * Signature: (Lorg/sqlite/jni/ConfigSqllogCallback;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1config__Lorg_sqlite_jni_ConfigSqllogCallback_2 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_create_collation - * Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;ILorg/sqlite/jni/Collation;)I + * Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;ILorg/sqlite/jni/CollationCallback;)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1create_1collation (JNIEnv *, jclass, jobject, jstring, jint, jobject); @@ -1109,14 +1157,6 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1data_1count /* * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_db_filename - * Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1filename - (JNIEnv *, jclass, jobject, jstring); - -/* - * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_db_config * Signature: (Lorg/sqlite/jni/sqlite3;IILorg/sqlite/jni/OutputPointer/Int32;)I */ @@ -1133,6 +1173,22 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_ /* * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_db_filename + * Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1filename + (JNIEnv *, jclass, jobject, jstring); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_db_handle + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)Lorg/sqlite/jni/sqlite3; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1handle + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_db_status * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;Z)I */ @@ -1213,6 +1269,22 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1initialize /* * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_interrupt + * Signature: (Lorg/sqlite/jni/sqlite3;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1interrupt + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_is_interrupted + * Signature: (Lorg/sqlite/jni/sqlite3;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1is_1interrupted + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_last_insert_rowid * Signature: (Lorg/sqlite/jni/sqlite3;)J */ @@ -1277,8 +1349,56 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v3 /* * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_preupdate_blobwrite + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1preupdate_1blobwrite + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_preupdate_count + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1preupdate_1count + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_preupdate_depth + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1preupdate_1depth + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_preupdate_hook + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/PreupdateHookCallback;)Lorg/sqlite/jni/PreupdateHookCallback; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1preupdate_1hook + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_preupdate_new + * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/OutputPointer/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1preupdate_1new + (JNIEnv *, jclass, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_preupdate_old + * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/OutputPointer/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1preupdate_1old + (JNIEnv *, jclass, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_progress_handler - * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/ProgressHandler;)V + * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/ProgressHandlerCallback;)V */ JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1progress_1handler (JNIEnv *, jclass, jobject, jint, jobject); @@ -1429,24 +1549,8 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text64 /* * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_status - * Signature: (ILorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;Z)I - */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1status - (JNIEnv *, jclass, jint, jobject, jobject, jboolean); - -/* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_status64 - * Signature: (ILorg/sqlite/jni/OutputPointer/Int64;Lorg/sqlite/jni/OutputPointer/Int64;Z)I - */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1status64 - (JNIEnv *, jclass, jint, jobject, jobject, jboolean); - -/* - * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_rollback_hook - * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/RollbackHook;)Lorg/sqlite/jni/RollbackHook; + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/RollbackHookCallback;)Lorg/sqlite/jni/RollbackHookCallback; */ JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1rollback_1hook (JNIEnv *, jclass, jobject, jobject); @@ -1454,7 +1558,7 @@ JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1rollback_1hook /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_set_authorizer - * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/Authorizer;)I + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/AuthorizerCallback;)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1set_1authorizer (JNIEnv *, jclass, jobject, jobject); @@ -1469,6 +1573,14 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1set_1last_1insert /* * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_shutdown + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1shutdown + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_sleep * Signature: (I)I */ @@ -1493,6 +1605,22 @@ JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1sql /* * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_status + * Signature: (ILorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;Z)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1status + (JNIEnv *, jclass, jint, jobject, jobject, jboolean); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_status64 + * Signature: (ILorg/sqlite/jni/OutputPointer/Int64;Lorg/sqlite/jni/OutputPointer/Int64;Z)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1status64 + (JNIEnv *, jclass, jint, jobject, jobject, jboolean); + +/* + * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_step * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I */ @@ -1542,7 +1670,7 @@ JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1total_1changes64 /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_trace_v2 - * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/Tracer;)I + * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/TraceV2Callback;)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1trace_1v2 (JNIEnv *, jclass, jobject, jint, jobject); @@ -1550,7 +1678,7 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1trace_1v2 /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_update_hook - * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/UpdateHook;)Lorg/sqlite/jni/UpdateHook; + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/UpdateHookCallback;)Lorg/sqlite/jni/UpdateHookCallback; */ JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1update_1hook (JNIEnv *, jclass, jobject, jobject); @@ -1589,10 +1717,10 @@ JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1double /* * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_value_dupe + * Method: sqlite3_value_dup * Signature: (Lorg/sqlite/jni/sqlite3_value;)Lorg/sqlite/jni/sqlite3_value; */ -JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1dupe +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1dup (JNIEnv *, jclass, jobject); /* @@ -1637,14 +1765,6 @@ JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1java_1o /* * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_value_text - * Signature: (Lorg/sqlite/jni/sqlite3_value;)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text - (JNIEnv *, jclass, jobject); - -/* - * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_value_text_utf8 * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B */ @@ -1653,26 +1773,18 @@ JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text /* * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_value_text16 - * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B - */ -JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text16 - (JNIEnv *, jclass, jobject); - -/* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_value_text16le - * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + * Method: sqlite3_value_text + * Signature: (Lorg/sqlite/jni/sqlite3_value;)Ljava/lang/String; */ -JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text16le +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text (JNIEnv *, jclass, jobject); /* * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_value_text16be - * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + * Method: sqlite3_value_text16 + * Signature: (Lorg/sqlite/jni/sqlite3_value;)Ljava/lang/String; */ -JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text16be +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text16 (JNIEnv *, jclass, jobject); /* @@ -1717,18 +1829,10 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1subtype /* * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_shutdown - * Signature: ()I - */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1shutdown - (JNIEnv *, jclass); - -/* - * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_do_something_for_developer + * Method: sqlite3_jni_internal_details * Signature: ()V */ -JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1do_1something_1for_1developer +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1jni_1internal_1details (JNIEnv *, jclass); #ifdef __cplusplus @@ -1891,7 +1995,7 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xSetAuxdata /* * Class: org_sqlite_jni_Fts5ExtensionApi * Method: xTokenize - * Signature: (Lorg/sqlite/jni/Fts5Context;[BLorg/sqlite/jni/Fts5/xTokenizeCallback;)I + * Signature: (Lorg/sqlite/jni/Fts5Context;[BLorg/sqlite/jni/Fts5/xTokenize_callback;)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xTokenize (JNIEnv *, jobject, jobject, jbyteArray, jobject); @@ -1949,7 +2053,7 @@ extern "C" { /* * Class: org_sqlite_jni_fts5_tokenizer * Method: xTokenize - * Signature: (Lorg/sqlite/jni/Fts5Tokenizer;I[BLorg/sqlite/jni/Fts5/xTokenizeCallback;)I + * Signature: (Lorg/sqlite/jni/Fts5Tokenizer;I[BLorg/sqlite/jni/Fts5/xTokenize_callback;)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_1tokenizer_xTokenize (JNIEnv *, jobject, jobject, jint, jbyteArray, jobject); diff --git a/ext/jni/src/org/sqlite/jni/AbstractCollationCallback.java b/ext/jni/src/org/sqlite/jni/AbstractCollationCallback.java new file mode 100644 index 000000000..63cac66a5 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/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; +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/AggregateFunction.java b/ext/jni/src/org/sqlite/jni/AggregateFunction.java new file mode 100644 index 000000000..502cde12f --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/AggregateFunction.java @@ -0,0 +1,72 @@ +/* +** 2023-08-25 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; + + +/** + 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() {} + + /** Per-invocation state for the UDF. */ + private final SQLFunction.PerContextState<T> map = + new SQLFunction.PerContextState<>(); + + /** + To be called from the implementation's xStep() method, as well + as the xValue() and xInverse() methods of the {@link WindowFunction} + subclass, to fetch the current per-call UDF state. On the + first call to this method for any given sqlite3_context + argument, the context is set to the given initial value. On all other + calls, the 2nd argument is ignored. + + @see SQLFunction.PerContextState#getAggregateState + */ + protected final ValueHolder<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/Authorizer.java b/ext/jni/src/org/sqlite/jni/Authorizer.java deleted file mode 100644 index 114c27fc6..000000000 --- a/ext/jni/src/org/sqlite/jni/Authorizer.java +++ /dev/null @@ -1,32 +0,0 @@ -/* -** 2023-08-05 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** This file is part of the JNI bindings for the sqlite3 C API. -*/ -package org.sqlite.jni; - -/** - A callback for use with sqlite3_set_authorizer(). -*/ -public interface Authorizer { - /** - Must functions as described for the sqlite3_set_authorizer() - callback, with one caveat: the string values passed here were - initially (at the C level) encoded in standard UTF-8. If they - contained any constructs which are not compatible with MUTF-8, - these strings will not have the expected values. For further - details, see the documentation for the SQLite3Jni class. - - Must not throw. - */ - int xAuth(int opId, @Nullable String s1, @Nullable String s2, - @Nullable String s3, @Nullable String s4); -} diff --git a/ext/jni/src/org/sqlite/jni/AuthorizerCallback.java b/ext/jni/src/org/sqlite/jni/AuthorizerCallback.java new file mode 100644 index 000000000..a9f15fc6c --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/AuthorizerCallback.java @@ -0,0 +1,28 @@ +/* +** 2023-08-25 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; +import org.sqlite.jni.annotation.*; + +/** + Callback for use with {@link SQLite3Jni#sqlite3_set_authorizer}. +*/ +public interface AuthorizerCallback extends SQLite3CallbackProxy { + /** + Must function as described for the C-level + sqlite3_set_authorizer() callback. + */ + int call(int opId, @Nullable String s1, @Nullable String s2, + @Nullable String s3, @Nullable String s4); + +} diff --git a/ext/jni/src/org/sqlite/jni/AutoExtension.java b/ext/jni/src/org/sqlite/jni/AutoExtension.java deleted file mode 100644 index a2ab6a0f7..000000000 --- a/ext/jni/src/org/sqlite/jni/AutoExtension.java +++ /dev/null @@ -1,31 +0,0 @@ -/* -** 2023-08-05 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** This file is part of the JNI bindings for the sqlite3 C API. -*/ -package org.sqlite.jni; - -/** - A callback for use with sqlite3_auto_extension(). -*/ -public interface AutoExtension { - /** - Must function as described for the sqlite3_auto_extension(), - with the caveat that the signature is more limited. - - As an exception (as it were) to the callbacks-must-not-throw - rule, AutoExtensions may do so and the exception's error message - will be set as the db's error string. - - Results are undefined if db is closed by an auto-extension. - */ - int xEntryPoint(sqlite3 db); -} diff --git a/ext/jni/src/org/sqlite/jni/AutoExtensionCallback.java b/ext/jni/src/org/sqlite/jni/AutoExtensionCallback.java new file mode 100644 index 000000000..1f8ace2fb --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/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; + +/** + Callback for use with the {@link SQLite3Jni#sqlite3_auto_extension} + family of APIs. +*/ +public interface AutoExtensionCallback extends SQLite3CallbackProxy { + /** + 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/BusyHandler.java b/ext/jni/src/org/sqlite/jni/BusyHandler.java deleted file mode 100644 index 8ce729c90..000000000 --- a/ext/jni/src/org/sqlite/jni/BusyHandler.java +++ /dev/null @@ -1,45 +0,0 @@ -/* -** 2023-07-22 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** This file is part of the JNI bindings for the sqlite3 C API. -*/ -package org.sqlite.jni; - -/** - Callback proxy for use with sqlite3_busy_handler(). -*/ -public abstract class BusyHandler { - /** - Must function as documented for the sqlite3_busy_handler() - callback argument, minus the (void*) argument the C-level - function requires. - - Any exceptions thrown by this callback are suppressed in order to - retain the C-style API semantics of the JNI bindings. - */ - public abstract int xCallback(int n); - - /** - Optionally override to perform any cleanup when this busy - handler is destroyed. It is destroyed when: - - - The associated db is passed to sqlite3_close() or - sqlite3_close_v2(). - - - sqlite3_busy_handler() is called to replace the handler, - whether it's passed a null handler or any other instance of - this class. - - - sqlite3_busy_timeout() is called, which implicitly installs - a busy handler. - */ - public void xDestroy(){} -} diff --git a/ext/jni/src/org/sqlite/jni/BusyHandlerCallback.java b/ext/jni/src/org/sqlite/jni/BusyHandlerCallback.java new file mode 100644 index 000000000..db9295bb6 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/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; + +/** + Callback for use with {@link SQLite3Jni#sqlite3_busy_handler}. +*/ +public interface BusyHandlerCallback extends SQLite3CallbackProxy { + /** + 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/CollationCallback.java b/ext/jni/src/org/sqlite/jni/CollationCallback.java new file mode 100644 index 000000000..481c6cd95 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/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; +import org.sqlite.jni.annotation.NotNull; + +/** + Callback for use with {@link SQLite3Jni#sqlite3_create_collation}. + + @see AbstractCollationCallback +*/ +public interface CollationCallback + extends SQLite3CallbackProxy, 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/CollationNeeded.java b/ext/jni/src/org/sqlite/jni/CollationNeededCallback.java index 85214a1d2..e6c917a2c 100644 --- a/ext/jni/src/org/sqlite/jni/CollationNeeded.java +++ b/ext/jni/src/org/sqlite/jni/CollationNeededCallback.java @@ -1,5 +1,5 @@ /* -** 2023-07-30 +** 2023-08-25 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -14,15 +14,15 @@ package org.sqlite.jni; /** - Callback proxy for use with sqlite3_collation_needed(). + Callback for use with {@link SQLite3Jni#sqlite3_collation_needed}. */ -public interface CollationNeeded { +public interface CollationNeededCallback extends SQLite3CallbackProxy { /** Has the same semantics as the C-level sqlite3_create_collation() callback. - If it throws, the exception message is passed on to the db and + <p>If it throws, the exception message is passed on to the db and the exception is suppressed. */ - int xCollationNeeded(sqlite3 db, int eTextRep, String collationName); + int call(sqlite3 db, int eTextRep, String collationName); } diff --git a/ext/jni/src/org/sqlite/jni/RollbackHook.java b/ext/jni/src/org/sqlite/jni/CommitHookCallback.java index 4ce3cb93e..253d0b8cf 100644 --- a/ext/jni/src/org/sqlite/jni/RollbackHook.java +++ b/ext/jni/src/org/sqlite/jni/CommitHookCallback.java @@ -1,5 +1,5 @@ /* -** 2023-07-22 +** 2023-08-25 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -14,12 +14,12 @@ package org.sqlite.jni; /** - Callback proxy for use with sqlite3_rollback_hook(). + Callback for use with {@link SQLite3Jni#sqlite3_commit_hook}. */ -public interface RollbackHook { +public interface CommitHookCallback extends SQLite3CallbackProxy { /** - Works as documented for the sqlite3_rollback_hook() callback. - Must not throw. + Works as documented for the C-level sqlite3_commit_hook() + callback. Must not throw. */ - void xRollbackHook(); + int call(); } diff --git a/ext/jni/src/org/sqlite/jni/ConfigSqllogCallback.java b/ext/jni/src/org/sqlite/jni/ConfigSqllogCallback.java new file mode 100644 index 000000000..9bdd209a7 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/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; + +/** + A callback for use with sqlite3_config(). +*/ +public interface ConfigSqllogCallback { + /** + Must function as described for a C-level callback for + {@link SQLite3Jni#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/Fts5.java b/ext/jni/src/org/sqlite/jni/Fts5.java index 102cf575a..3135db96d 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5.java +++ b/ext/jni/src/org/sqlite/jni/Fts5.java @@ -25,9 +25,11 @@ public final class Fts5 { /* Not used */ private Fts5(){} - //! Callback type for use with xTokenize() variants - public static interface xTokenizeCallback { - int xToken(int tFlags, byte txt[], int iStart, int iEnd); + /** + Callback type for use with xTokenize() variants + */ + public static interface xTokenize_callback { + int call(int tFlags, byte[] txt, int iStart, int iEnd); } public static final int FTS5_TOKENIZE_QUERY = 0x0001; diff --git a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java index ac041e300..ab2995f37 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java +++ b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java @@ -13,6 +13,7 @@ */ package org.sqlite.jni; import java.nio.charset.StandardCharsets; +import org.sqlite.jni.annotation.*; /** ALMOST COMPLETELY UNTESTED. @@ -23,7 +24,7 @@ import java.nio.charset.StandardCharsets; public final class Fts5ExtensionApi extends NativePointerHolder<Fts5ExtensionApi> { //! Only called from JNI private Fts5ExtensionApi(){} - private int iVersion = 2; + private final int iVersion = 2; /* Callback type for used by xQueryPhrase(). */ public static interface xQueryPhraseCallback { @@ -33,54 +34,54 @@ public final class Fts5ExtensionApi extends NativePointerHolder<Fts5ExtensionApi /** Returns the singleton instance of this class. */ - public static synchronized native Fts5ExtensionApi getInstance(); + public static native Fts5ExtensionApi getInstance(); - public synchronized native int xColumnCount(@NotNull Fts5Context fcx); - public synchronized native int xColumnSize(@NotNull Fts5Context cx, int iCol, + public native int xColumnCount(@NotNull Fts5Context fcx); + public native int xColumnSize(@NotNull Fts5Context cx, int iCol, @NotNull OutputPointer.Int32 pnToken); - public synchronized native int xColumnText(@NotNull Fts5Context cx, int iCol, + public native int xColumnText(@NotNull Fts5Context cx, int iCol, @NotNull OutputPointer.String txt); - public synchronized native int xColumnTotalSize(@NotNull Fts5Context fcx, int iCol, + public native int xColumnTotalSize(@NotNull Fts5Context fcx, int iCol, @NotNull OutputPointer.Int64 pnToken); - public synchronized native Object xGetAuxdata(@NotNull Fts5Context cx, boolean clearIt); - public synchronized native int xInst(@NotNull Fts5Context cx, int iIdx, + 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 synchronized native int xInstCount(@NotNull Fts5Context fcx, + public native int xInstCount(@NotNull Fts5Context fcx, @NotNull OutputPointer.Int32 pnInst); - public synchronized native int xPhraseCount(@NotNull Fts5Context fcx); - public synchronized native int xPhraseFirst(@NotNull Fts5Context cx, int iPhrase, + 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 synchronized native int xPhraseFirstColumn(@NotNull Fts5Context cx, int iPhrase, + public native int xPhraseFirstColumn(@NotNull Fts5Context cx, int iPhrase, @NotNull Fts5PhraseIter iter, @NotNull OutputPointer.Int32 iCol); - public synchronized native void xPhraseNext(@NotNull Fts5Context cx, + public native void xPhraseNext(@NotNull Fts5Context cx, @NotNull Fts5PhraseIter iter, @NotNull OutputPointer.Int32 iCol, @NotNull OutputPointer.Int32 iOff); - public synchronized native void xPhraseNextColumn(@NotNull Fts5Context cx, + public native void xPhraseNextColumn(@NotNull Fts5Context cx, @NotNull Fts5PhraseIter iter, @NotNull OutputPointer.Int32 iCol); - public synchronized native int xPhraseSize(@NotNull Fts5Context fcx, int iPhrase); - public synchronized native int xQueryPhrase(@NotNull Fts5Context cx, int iPhrase, + public native int xPhraseSize(@NotNull Fts5Context fcx, int iPhrase); + public native int xQueryPhrase(@NotNull Fts5Context cx, int iPhrase, @NotNull xQueryPhraseCallback callback); - public synchronized native int xRowCount(@NotNull Fts5Context fcx, + public native int xRowCount(@NotNull Fts5Context fcx, @NotNull OutputPointer.Int64 nRow); - public synchronized native long xRowid(@NotNull Fts5Context cx); + 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 synchronized native int xSetAuxdata(@NotNull Fts5Context cx, @Nullable Object pAux); - public synchronized native int xTokenize(@NotNull Fts5Context cx, @NotNull byte pText[], - @NotNull Fts5.xTokenizeCallback callback); + public native int xSetAuxdata(@NotNull Fts5Context cx, @Nullable Object pAux); + public native int xTokenize(@NotNull Fts5Context cx, @NotNull byte[] pText, + @NotNull Fts5.xTokenize_callback callback); - public synchronized native Object xUserData(Fts5Context cx); + public native Object xUserData(Fts5Context cx); //^^^ returns the pointer passed as the 3rd arg to the C-level - // fts5_api::xCreateFunction. + // fts5_api::xCreateFunction(). } diff --git a/ext/jni/src/org/sqlite/jni/NativePointerHolder.java b/ext/jni/src/org/sqlite/jni/NativePointerHolder.java index afe2618a0..251eb7faa 100644 --- a/ext/jni/src/org/sqlite/jni/NativePointerHolder.java +++ b/ext/jni/src/org/sqlite/jni/NativePointerHolder.java @@ -23,11 +23,11 @@ package org.sqlite.jni; NativePointerHolder is not inadvertently passed to an incompatible function signature. - These objects do not _own_ the pointer they refer to. They are + 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 long nativePointer = 0; + private volatile long nativePointer = 0; public final long getNativePointer(){ return nativePointer; } } diff --git a/ext/jni/src/org/sqlite/jni/OutputPointer.java b/ext/jni/src/org/sqlite/jni/OutputPointer.java index 82a90c918..8a59de574 100644 --- a/ext/jni/src/org/sqlite/jni/OutputPointer.java +++ b/ext/jni/src/org/sqlite/jni/OutputPointer.java @@ -16,13 +16,13 @@ package org.sqlite.jni; /** Helper classes for handling JNI output pointers. - We do not use a generic OutputPointer<T> because working with those + <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. - The usage is similar for all of thes types: + <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); @@ -30,12 +30,15 @@ package org.sqlite.jni; assert( null!=out.get() ); sqlite3 db = out.take(); assert( null==out.get() ); - ``` + }</pre> - With the minor exception that the primitive types permit direct + <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 { @@ -47,13 +50,13 @@ public final class OutputPointer { */ public static final class sqlite3 { private org.sqlite.jni.sqlite3 value; - //! Initializes with a null value. + /** Initializes with a null value. */ public sqlite3(){value = null;} - //! Sets the current value to null. + /** Sets the current value to null. */ public void clear(){value = null;} - //! Returns the current value. + /** Returns the current value. */ public final org.sqlite.jni.sqlite3 get(){return value;} - //! Equivalent to calling get() then clear(). + /** Equivalent to calling get() then clear(). */ public final org.sqlite.jni.sqlite3 take(){ final org.sqlite.jni.sqlite3 v = value; value = null; @@ -69,13 +72,13 @@ public final class OutputPointer { */ public static final class sqlite3_stmt { private org.sqlite.jni.sqlite3_stmt value; - //! Initializes with a null value. + /** Initializes with a null value. */ public sqlite3_stmt(){value = null;} - //! Sets the current value to null. + /** Sets the current value to null. */ public void clear(){value = null;} - //! Returns the current value. + /** Returns the current value. */ public final org.sqlite.jni.sqlite3_stmt get(){return value;} - //! Equivalent to calling get() then clear(). + /** Equivalent to calling get() then clear(). */ public final org.sqlite.jni.sqlite3_stmt take(){ final org.sqlite.jni.sqlite3_stmt v = value; value = null; @@ -84,6 +87,28 @@ public final class OutputPointer { } /** + 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.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.sqlite3_value get(){return value;} + /** Equivalent to calling get() then clear(). */ + public final org.sqlite.jni.sqlite3_value take(){ + final org.sqlite.jni.sqlite3_value v = value; + value = null; + return v; + } + } + + /** Output pointer for use with native routines which return integers via output pointers. */ @@ -93,13 +118,13 @@ public final class OutputPointer { consistency with the higher-level types. */ public int value; - //! Initializes with the value 0. + /** Initializes with the value 0. */ public Int32(){this(0);} - //! Initializes with the value v. + /** Initializes with the value v. */ public Int32(int v){value = v;} - //! Returns the current value. + /** Returns the current value. */ public final int get(){return value;} - //! Sets the current value to v. + /** Sets the current value to v. */ public final void set(int v){value = v;} } @@ -113,13 +138,13 @@ public final class OutputPointer { consistency with the higher-level types. */ public long value; - //! Initializes with the value 0. + /** Initializes with the value 0. */ public Int64(){this(0);} - //! Initializes with the value v. + /** Initializes with the value v. */ public Int64(long v){value = v;} - //! Returns the current value. + /** Returns the current value. */ public final long get(){return value;} - //! Sets the current value. + /** Sets the current value. */ public final void set(long v){value = v;} } @@ -133,13 +158,13 @@ public final class OutputPointer { consistency with the higher-level types. */ public java.lang.String value; - //! Initializes with a null value. + /** Initializes with a null value. */ public String(){this(null);} - //! Initializes with the value v. + /** Initializes with the value v. */ public String(java.lang.String v){value = v;} - //! Returns the current value. + /** Returns the current value. */ public final java.lang.String get(){return value;} - //! Sets the current value. + /** Sets the current value. */ public final void set(java.lang.String v){value = v;} } @@ -153,13 +178,13 @@ public final class OutputPointer { consistency with the higher-level types. */ public byte[] value; - //! Initializes with the value null. + /** Initializes with the value null. */ public ByteArray(){this(null);} - //! Initializes with the value v. + /** Initializes with the value v. */ public ByteArray(byte[] v){value = v;} - //! Returns the current value. + /** Returns the current value. */ public final byte[] get(){return value;} - //! Sets the current value. + /** Sets the current value. */ public final void set(byte[] v){value = v;} } } diff --git a/ext/jni/src/org/sqlite/jni/Collation.java b/ext/jni/src/org/sqlite/jni/PreupdateHookCallback.java index a05b8ef9e..b68dd4b6d 100644 --- a/ext/jni/src/org/sqlite/jni/Collation.java +++ b/ext/jni/src/org/sqlite/jni/PreupdateHookCallback.java @@ -1,5 +1,5 @@ /* -** 2023-07-22 +** 2023-08-25 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -14,15 +14,13 @@ package org.sqlite.jni; /** + Callback for use with {@link SQLite3Jni#sqlite3_preupdate_hook}. */ -public abstract class Collation { +public interface PreupdateHookCallback extends SQLite3CallbackProxy { /** - Must compare the given byte arrays using memcmp() semantics. + Must function as described for the C-level sqlite3_preupdate_hook() + callback. */ - public abstract int xCompare(byte[] lhs, byte[] rhs); - /** - Called by SQLite when the collation is destroyed. If a Collation - requires custom cleanup, override this method. - */ - public void xDestroy() {} + void call(sqlite3 db, int op, String dbName, String dbTable, + long iKey1, long iKey2 ); } diff --git a/ext/jni/src/org/sqlite/jni/ProgressHandler.java b/ext/jni/src/org/sqlite/jni/ProgressHandlerCallback.java index c806eebca..d15bf31a1 100644 --- a/ext/jni/src/org/sqlite/jni/ProgressHandler.java +++ b/ext/jni/src/org/sqlite/jni/ProgressHandlerCallback.java @@ -1,5 +1,5 @@ /* -** 2023-07-22 +** 2023-08-25 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -14,14 +14,14 @@ package org.sqlite.jni; /** - Callback proxy for use with sqlite3_progress_handler(). + Callback for use with {@link SQLite3Jni#sqlite3_progress_handler}. */ -public interface ProgressHandler { +public interface ProgressHandlerCallback extends SQLite3CallbackProxy { /** - Works as documented for the sqlite3_progress_handler() callback. + Works as documented for the C-level sqlite3_progress_handler() callback. - If it throws, the exception message is passed on to the db and + <p>If it throws, the exception message is passed on to the db and the exception is suppressed. */ - int xCallback(); + int call(); } diff --git a/ext/jni/src/org/sqlite/jni/CommitHook.java b/ext/jni/src/org/sqlite/jni/RollbackHookCallback.java index eaa75a004..3bf9f79a1 100644 --- a/ext/jni/src/org/sqlite/jni/CommitHook.java +++ b/ext/jni/src/org/sqlite/jni/RollbackHookCallback.java @@ -1,5 +1,5 @@ /* -** 2023-07-22 +** 2023-08-25 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -14,12 +14,12 @@ package org.sqlite.jni; /** - Callback proxy for use with sqlite3_commit_hook(). + Callback for use with {@link SQLite3Jni#sqlite3_rollback_hook}. */ -public interface CommitHook { +public interface RollbackHookCallback extends SQLite3CallbackProxy { /** - Works as documented for the sqlite3_commit_hook() callback. - Must not throw. + Works as documented for the C-level sqlite3_rollback_hook() + callback. */ - int xCommitHook(); + void call(); } diff --git a/ext/jni/src/org/sqlite/jni/SQLFunction.java b/ext/jni/src/org/sqlite/jni/SQLFunction.java index 21e5fe622..66119ebe5 100644 --- a/ext/jni/src/org/sqlite/jni/SQLFunction.java +++ b/ext/jni/src/org/sqlite/jni/SQLFunction.java @@ -19,38 +19,45 @@ package org.sqlite.jni; 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 create - anonymous classes from those to implement UDFs. Clients are free to - create their own classes for use with UDFs, so long as they conform - to the public interfaces defined by those three classes. The JNI - layer only actively relies on the SQLFunction base class. + 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 abstract class SQLFunction { +public interface SQLFunction { /** PerContextState assists aggregate and window functions in - managinga their accumulator state across calls to the UDF's + managing their accumulator state across calls to the UDF's callbacks. - If a given aggregate or window function is called multiple times + <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. - This class works by mapping + <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. - This class is a helper providing commonly-needed functionality - - it is not required for use with aggregate or window functions. + <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 Aggregate<T> and Window<T> classes - use this. + 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 @@ -60,20 +67,20 @@ public abstract class SQLFunction { Should be called from a UDF's xStep(), xValue(), and xInverse() methods, passing it that method's first argument and an initial value for the persistent state. If there is currently no - mapping for cx.getAggregateContext() within the map, one is - created using the given initial value, else the existing one is - used and the 2nd argument is ignored. It returns a - ValueHolder<T> which can be used to modify that state directly - without requiring that the client update the underlying map's - entry. - - T must be of a type which can be legally stored as a value in - java.util.HashMap<KeyType,T>. + 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){ - ValueHolder<T> rc = map.get(cx.getAggregateContext()); - if(null == rc){ - map.put(cx.getAggregateContext(), rc = new ValueHolder<>(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; } @@ -88,85 +95,9 @@ public abstract class SQLFunction { rows. */ public T takeAggregateState(sqlite3_context cx){ - final ValueHolder<T> h = map.remove(cx.getAggregateContext()); + final ValueHolder<T> h = map.remove(cx.getAggregateContext(false)); return null==h ? null : h.value; } } - //! Subclass for creating scalar functions. - public static abstract class Scalar extends SQLFunction { - - //! As for the xFunc() argument of the C API's sqlite3_create_function() - public abstract void xFunc(sqlite3_context cx, sqlite3_value[] args); - - /** - Optionally override to be notified when the UDF is finalized by - SQLite. - */ - public void xDestroy() {} - } - - /** - SQLFunction Subclass for creating aggregate functions. Its T is - the data type of its "accumulator" state, an instance of which is - intended to be be managed using the getAggregateState() and - takeAggregateState() methods. - */ - public static abstract class Aggregate<T> extends SQLFunction { - - //! As for the xStep() argument of the C API's sqlite3_create_function() - public abstract void xStep(sqlite3_context cx, sqlite3_value[] args); - - //! As for the xFinal() argument of the C API's sqlite3_create_function() - public abstract void xFinal(sqlite3_context cx); - - /** - Optionally override to be notified when the UDF is finalized by - SQLite. - */ - public void xDestroy() {} - - //! Per-invocation state for the UDF. - private final PerContextState<T> map = new PerContextState<>(); - - /** - To be called from the implementation's xStep() method, as well - as the xValue() and xInverse() methods of the Window<T> - subclass, to fetch the current per-call UDF state. On the - first call to this method for any given sqlite3_context - argument, the context is set to the given initial value. On all other - calls, the 2nd argument is ignored. - - @see PerContextState<T>#takeAggregateState() - */ - 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 PerContextState<T>#takeAggregateState() - */ - protected final T takeAggregateState(sqlite3_context cx){ - return map.takeAggregateState(cx); - } - } - - /** - An SQLFunction subclass for creating window functions. Note that - Window<T> inherits from Aggregate<T> and each instance is - required to implement the inherited abstract methods from that - class. See Aggregate<T> for information on managing the UDF's - invocation-specific state. - */ - public static abstract class Window<T> extends Aggregate<T> { - - //! As for the xInverse() argument of the C API's sqlite3_create_window_function() - public abstract void xInverse(sqlite3_context cx, sqlite3_value[] args); - - //! As for the xValue() argument of the C API's sqlite3_create_window_function() - public abstract void xValue(sqlite3_context cx); - } } diff --git a/ext/jni/src/org/sqlite/jni/SQLite3CallbackProxy.java b/ext/jni/src/org/sqlite/jni/SQLite3CallbackProxy.java new file mode 100644 index 000000000..505266493 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/SQLite3CallbackProxy.java @@ -0,0 +1,44 @@ +/* +** 2023-08-25 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file is part of the JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; +/** + 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, all exceptions will necessarily be suppressed in order to + retain the C-style no-throw semantics. + + <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 SQLite3CallbackProxy {} diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 9b2a17650..d32320b26 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -18,105 +18,74 @@ 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.*; /** - This annotation is for flagging parameters which may legally be - null, noting that they may behave differently if passed null but - are prepared to expect null as a value. + This 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: - This annotation is solely for the reader's information. -*/ -@Documented -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.PARAMETER) -@interface Nullable{} - -/** - This annotation is for flagging parameters which may not legally be - null. Note that the C-style API does _not_ throw any - NullPointerExceptions on its own because it has a no-throw policy - in order to retain its C-style semantics. - - This annotation is solely for the reader's information. No policy - is in place to programmatically ensure that NotNull is conformed to - in client code. -*/ -@Documented -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.PARAMETER) -@interface NotNull{} - -/** - This class contains the entire sqlite3 JNI API binding. For - client-side use, a static import is recommended: - - ``` + <pre>{@code import static org.sqlite.jni.SQLite3Jni.*; - ``` + }</pre> - The C-side part can be found in sqlite3-jni.c. + <p>The C-side part can be found in sqlite3-jni.c. - Only functions which materially differ from their C counterparts - are documented here. The C documetation is otherwise applicable - here: + <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: - https://sqlite.org/c3ref/intro.html + <p><a href="https://sqlite.org/c3ref/intro.html">https://sqlite.org/c3ref/intro.html</a> - A handful of Java-specific APIs have been added. + <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>Though most of the {@code SQLITE_abc...} C macros represented by + this class are defined as final, a few are necessarily non-final + because they cannot be set until static class-level initialization + is run. Modifying them at runtime has no effect on the library but + may confuse any client-level code which uses them. - ****************************************************************** - *** Warning regarding Java's Modified UTF-8 vs standard UTF-8: *** - ****************************************************************** + <p>Notes regarding Java's Modified UTF-8 vs standard UTF-8: - SQLite internally uses UTF-8 encoding, whereas Java natively uses + <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) + but JNI uses what its docs call modified UTF-8 (see links below) Care must be taken when converting Java strings to or from standard UTF-8 to ensure that the proper conversion is performed. In short, - Java's `String.getBytes(StandardCharsets.UTF_8)` performs the proper + 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 `NewStringUTF()` requires its input to be in MUTF-8). + (JNI's {@code NewStringUTF()} requires its input to be in MUTF-8). - The known consequences and limitations this discrepancy places on + <p>The known consequences and limitations this discrepancy places on the SQLite3 JNI binding include: - - Any functions which return client-side data from a database - take extra care to perform proper conversion, at the cost of - efficiency. - - - Functions which return database identifiers require those - identifiers to have identical representations in UTF-8 and - MUTF-8. They do not perform such conversions (A) because of the - much lower risk of an encoding discrepancy and (B) to avoid - significant extra code involved (see both the Java- and C-side - implementations of sqlite3_db_filename() for an example). Names - of databases, tables, columns, collations, and functions MUST NOT - contain characters which differ in MUTF-8 and UTF-8, or certain - APIs will mis-translate them on their way between languages - (possibly leading to a crash). - - - C functions which take C-style strings without a length argument - require special care when taking input from Java. In particular, - Java strings converted to byte arrays for encoding purposes are - not NUL-terminated, and conversion to a Java byte array must be - careful to add one. Functions which take a length do not require - this. Search the SQLite3Jni class for "\0" for many examples. - - - Similarly, C-side code which deals with strings which might not be - NUL-terminated (e.g. while tokenizing in FTS5-related code) cannot - use JNI's new-string functions to return them to Java because none - of those APIs take a string-length argument. Such cases must - return byte arrays instead of strings. - - Further reading: - - - https://stackoverflow.com/questions/57419723 - - https://stackoverflow.com/questions/7921016 - - https://itecnote.com/tecnote/java-getting-true-utf-8-characters-in-java-jni/ - - https://docs.oracle.com/javase/8/docs/api/java/lang/Character.html#unicode - - https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8 + <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 SQLite3Jni class + for "\0" for many 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 SQLite3Jni { @@ -130,113 +99,131 @@ public final class SQLite3Jni { /** Each thread which uses the SQLite3 JNI APIs should call - uncacheJniEnv() when it is done with the library - either right - before it terminates or when it is finished using the SQLite API. - This will clean up any cached per-JNIEnv info. Calling into the - library again after that "should" re-initialize the cache on - demand, but that's untested. - - This call forcibly wipes out all cached information for the - current JNIEnv, a side-effect of which is that behavior is - undefined if any database objects are (A) still active at the - time it is called _and_ (B) calls are subsequently made into the - library with such a database. Doing so will, at best, lead to a - crash. Azt worst, it will lead to the db possibly misbehaving - because some of its Java-bound state has been cleared. There is - no immediate harm in (A) so long as condition (B) is not met. - This process does _not_ actually close any databases or finalize - any prepared statements. For proper library behavior, and to - avoid C-side leaks, be sure to close them before calling this - function. - - Calling this from the main application thread is not strictly - required but is "polite." Additional threads must call this - before ending or they will leak cache entries in the C heap, - which in turn may keep numerous Java-side global references - active. - - This routine returns false without side effects if the current + 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 should use to make any informed decisions. */ - public static synchronized native boolean uncacheJniEnv(); + public static native boolean sqlite3_java_uncache_thread(); ////////////////////////////////////////////////////////////////////// // Maintenance reminder: please keep the sqlite3_.... functions // alphabetized. The SQLITE_... values. on the other hand, are // grouped by category. + /** + Functions exactly like the native form except that (A) the 2nd + argument is a boolean instead of an int and (B) the returned + value is not a pointer address and is only intended for use as a + per-UDF-call lookup key in a higher-level data structure. + + <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. + */ + @Canonical + public static native long sqlite3_aggregate_context(sqlite3_context cx, boolean initialize); /** Functions almost as documented for the C API, with these exceptions: - - The callback interface is more limited because of - cross-language differences. Specifically, auto-extensions do - not have access to the sqlite3_api object which native - auto-extensions do. + <p>- The callback interface is is shorter because of + cross-language differences. Specifically, 3rd argument to the C + auto-extension callback interface is unnecessary here. - - If an auto-extension opens a db, thereby triggering recursion - in the auto-extension handler, it will fail with a message - explaining that recursion is not permitted. - - All of the other auto extension routines will fail without side - effects if invoked from within the execution of an - auto-extension. i.e. auto extensions can neither be added, - removed, nor cleared while one registered with this function is - running. Auto-extensions registered directly with the library - via C code, as opposed to indirectly via Java, do not have that - limitation. + <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. - See the AutoExtension class docs for more information. + <p>See the AutoExtension class docs for more information. + */ + @Canonical + public static native int sqlite3_auto_extension(@NotNull AutoExtensionCallback callback); - Achtung: it is as yet unknown whether auto extensions registered - from one JNIEnv (thread) can be safely called from another. + /** + Results are undefined if data is not null and n<0 || n>=data.length. */ - public static synchronized native int sqlite3_auto_extension(@NotNull AutoExtension callback); + @Canonical + public static native int sqlite3_bind_blob( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n + ); public static int sqlite3_bind_blob( @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data ){ - return (null == data) + return (null==data) ? sqlite3_bind_null(stmt, ndx) : sqlite3_bind_blob(stmt, ndx, data, data.length); } - private static synchronized native int sqlite3_bind_blob( - @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n - ); - - public static synchronized native int sqlite3_bind_double( + @Canonical + public static native int sqlite3_bind_double( @NotNull sqlite3_stmt stmt, int ndx, double v ); - public static synchronized native int sqlite3_bind_int( + @Canonical + public static native int sqlite3_bind_int( @NotNull sqlite3_stmt stmt, int ndx, int v ); - public static synchronized native int sqlite3_bind_int64( + @Canonical + public static native int sqlite3_bind_int64( @NotNull sqlite3_stmt stmt, int ndx, long v ); - public static synchronized native int sqlite3_bind_null( + /** + Binds the given object at the given index. + + @see #sqlite3_result_java_object + */ + public static native int sqlite3_bind_java_object( + @NotNull sqlite3_stmt cx, int ndx, @Nullable Object o + ); + + @Canonical + public static native int sqlite3_bind_null( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native int sqlite3_bind_parameter_count( + @Canonical + public static native int sqlite3_bind_parameter_count( @NotNull sqlite3_stmt stmt ); - - /** A level of indirection required to ensure that the input to the - C-level function of the same name is a NUL-terminated UTF-8 - string. */ - private static synchronized native int sqlite3_bind_parameter_index( + /** + Requires that paramName be a NUL-terminated UTF-8 string. + */ + @Canonical + public static native int sqlite3_bind_parameter_index( @NotNull sqlite3_stmt stmt, byte[] paramName ); + @Canonical public static int sqlite3_bind_parameter_index( @NotNull sqlite3_stmt stmt, @NotNull String paramName ){ @@ -244,6 +231,25 @@ public final class SQLite3Jni { return sqlite3_bind_parameter_index(stmt, utf8); } + /** + 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>Results are undefined if data is not null and + maxBytes>=utf8.length. If maxBytes is negative then results are + undefined if data is not null and does not contain a NUL byte. + */ + @Canonical + public static native int sqlite3_bind_text( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] utf8, int 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 ){ @@ -252,161 +258,187 @@ public final class SQLite3Jni { return sqlite3_bind_text(stmt, 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[] data + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] utf8 ){ - return (null == data) + return (null == utf8) ? sqlite3_bind_null(stmt, ndx) - : sqlite3_bind_text(stmt, ndx, data, data.length); + : sqlite3_bind_text(stmt, ndx, utf8, utf8.length); } /** - Works like the C-level sqlite3_bind_text() but (A) assumes - SQLITE_TRANSIENT for the final parameter and (B) behaves like - sqlite3_bind_null() if the data argument is null. + 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. */ - private static synchronized native int sqlite3_bind_text( + @Canonical + public static native int sqlite3_bind_text16( @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes ); - public static synchronized native int sqlite3_bind_zeroblob( + /** + 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, 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, ndx) + : sqlite3_bind_text16(stmt, ndx, data, data.length); + } + + @Canonical + public static native int sqlite3_bind_zeroblob( @NotNull sqlite3_stmt stmt, int ndx, int n ); - public static synchronized native int sqlite3_bind_zeroblob64( + @Canonical + public static native int sqlite3_bind_zeroblob64( @NotNull sqlite3_stmt stmt, int ndx, long n ); /** - As for the C-level function of the same name, with a BusyHandler + 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. Calling this multiple times with the - same object is a no-op on the second and subsequent calls. + to clear the busy handler. */ - public static synchronized native int sqlite3_busy_handler( - @NotNull sqlite3 db, @Nullable BusyHandler handler + @Canonical + public static native int sqlite3_busy_handler( + @NotNull sqlite3 db, @Nullable BusyHandlerCallback handler ); - public static synchronized native int sqlite3_busy_timeout( + @Canonical + public static native int sqlite3_busy_timeout( @NotNull sqlite3 db, int ms ); - /** - Works like the C API except that it returns false, without side - effects, if auto extensions are currently running. (The JNI-level - list of extensions cannot be manipulated while it is being traversed.) - */ - public static synchronized native boolean sqlite3_cancel_auto_extension( - @NotNull AutoExtension ax + @Canonical + public static native boolean sqlite3_cancel_auto_extension( + @NotNull AutoExtensionCallback ax ); - public static synchronized native int sqlite3_changes( + @Canonical + public static native int sqlite3_changes( @NotNull sqlite3 db ); - public static synchronized native long sqlite3_changes64( + @Canonical + public static native long sqlite3_changes64( @NotNull sqlite3 db ); - public static synchronized native int sqlite3_clear_bindings( + @Canonical + public static native int sqlite3_clear_bindings( @NotNull sqlite3_stmt stmt ); - public static synchronized native int sqlite3_close( - @NotNull sqlite3 db + @Canonical + public static native int sqlite3_close( + @Nullable sqlite3 db ); - public static synchronized native int sqlite3_close_v2( - @NotNull sqlite3 db + @Canonical + public static native int sqlite3_close_v2( + @Nullable sqlite3 db ); - public static synchronized native byte[] sqlite3_column_blob( + @Canonical + public static native byte[] sqlite3_column_blob( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native int sqlite3_column_bytes( + @Canonical + public static native int sqlite3_column_bytes( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native int sqlite3_column_bytes16( + @Canonical + public static native int sqlite3_column_bytes16( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native int sqlite3_column_count( + @Canonical + public static native int sqlite3_column_count( @NotNull sqlite3_stmt stmt ); - public static synchronized native double sqlite3_column_double( + @Canonical + public static native double sqlite3_column_double( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native int sqlite3_column_int( + @Canonical + public static native int sqlite3_column_int( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native long sqlite3_column_int64( + @Canonical + public static native long sqlite3_column_int64( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native String sqlite3_column_name( + @Canonical + public static native String sqlite3_column_name( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native String sqlite3_column_database_name( + @Canonical + public static native String sqlite3_column_database_name( @NotNull sqlite3_stmt stmt, int ndx ); - /** - Column counterpart of sqlite3_value_java_object(). - */ - public static Object sqlite3_column_java_object( - @NotNull sqlite3_stmt stmt, int ndx - ){ - Object rv = null; - sqlite3_value v = sqlite3_column_value(stmt, ndx); - if(null!=v){ - v = sqlite3_value_dupe(v) /* we need a "protected" value */; - if(null!=v){ - rv = sqlite3_value_java_object(v); - sqlite3_value_free(v); - } - } - return rv; - } - - /** - Column counterpart of sqlite3_value_java_casted(). - */ - @SuppressWarnings("unchecked") - public static <T> T sqlite3_column_java_casted( - @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; - } - - public static synchronized native String sqlite3_column_origin_name( + @Canonical + public static native String sqlite3_column_origin_name( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native String sqlite3_column_table_name( + @Canonical + public static native String sqlite3_column_table_name( @NotNull sqlite3_stmt stmt, int ndx ); /** - To extract _standard_ UTF-8, use sqlite3_column_text(). - This API includes no functions for working with Java's Modified - UTF-8. + Returns the given column's contents as UTF-8-encoded (not MUTF-8) + text. Returns null if the C-level sqlite3_column_text() returns + NULL. + + @see #sqlite3_column_text */ - public static synchronized native String sqlite3_column_text16( + @Canonical(cname="sqlite3_column_text") + public static native byte[] sqlite3_column_text_utf8( @NotNull sqlite3_stmt stmt, int ndx ); /** - Returns the given column's contents as UTF-8-encoded (not MUTF-8) text. - Use sqlite3_column_text16() to fetch the text + Provides the same feature as the same-named C API but returns the + text in Java-native encoding rather than the C API's UTF-8. + + @see #sqlite3_column_text16 */ - public static synchronized native byte[] sqlite3_column_text( + public static native String sqlite3_column_text( + @NotNull sqlite3_stmt stmt, int ndx + ); + + @Canonical + public static native String sqlite3_column_text16( @NotNull sqlite3_stmt stmt, int ndx ); @@ -447,11 +479,13 @@ public final class SQLite3Jni { // return rv; // } - public static synchronized native int sqlite3_column_type( + @Canonical + public static native int sqlite3_column_type( @NotNull sqlite3_stmt stmt, int ndx ); - public static synchronized native sqlite3_value sqlite3_column_value( + @Canonical + public static native sqlite3_value sqlite3_column_value( @NotNull sqlite3_stmt stmt, int ndx ); @@ -459,33 +493,74 @@ public final class SQLite3Jni { This functions like C's sqlite3_collation_needed16() because Java's string type is compatible with that interface. */ - public static synchronized native int sqlite3_collation_needed( - @NotNull sqlite3 db, @Nullable CollationNeeded callback + @Canonical + public static native int sqlite3_collation_needed( + @NotNull sqlite3 db, @Nullable CollationNeededCallback callback ); - /** - Returns the db handle passed to sqlite3_open() or - sqlite3_open_v2(), as opposed to a new wrapper object. - */ - public static synchronized native sqlite3 sqlite3_context_db_handle( + @Canonical + public static native sqlite3 sqlite3_context_db_handle( @NotNull sqlite3_context cx ); - public static synchronized native CommitHook sqlite3_commit_hook( - @NotNull sqlite3 db, @Nullable CommitHook hook + @Canonical + public static native CommitHookCallback sqlite3_commit_hook( + @NotNull sqlite3 db, @Nullable CommitHookCallback hook ); + @Canonical public static native String sqlite3_compileoption_get( int n ); + @Canonical public static native boolean sqlite3_compileoption_used( @NotNull String optName ); - public static synchronized native int sqlite3_create_collation( + /** + <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. + + */ + @Canonical(comment="Option subset: "+ + "SQLITE_CONFIG_SINGLETHREAD, SQLITE_CONFIG_MULTITHREAD, "+ + "SQLITE_CONFIG_SERIALIZED") + public static native int sqlite3_config(int op); + + /** + If the native library was built with SQLITE_ENABLE_SQLLOG defined + then this acts as a proxy for C's + sqlite3_config(SQLITE_ENABLE_SQLLOG,...). This sets or clears the + logger. If installation of a logger fails, any previous logger is + retained. + + <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. + + */ + @Canonical(comment="Option subset: SQLITE_CONFIG_SQLLOG") + public static native int sqlite3_config( @Nullable ConfigSqllogCallback logger ); + + @Canonical + public static native int sqlite3_create_collation( @NotNull sqlite3 db, @NotNull String name, int eTextRep, - @NotNull Collation col + @NotNull CollationCallback col ); /** @@ -493,28 +568,27 @@ public final class SQLite3Jni { sqlite3_create_function_v2(), and sqlite3_create_window_function(). Which one it behaves like depends on which methods the final argument implements. See - SQLFunction's inner classes (Scalar, Aggregate<T>, and Window<T>) - for details. - */ - public static synchronized native int sqlite3_create_function( + SQLFunction's subclasses (ScalarFunction, AggregateFunction<T>, + and WindowFunction<T>) for details. + */ + @Canonical + public static native int sqlite3_create_function( @NotNull sqlite3 db, @NotNull String functionName, int nArg, int eTextRep, @NotNull SQLFunction func ); - public static synchronized native int sqlite3_data_count( + @Canonical + public static native int sqlite3_data_count( @NotNull sqlite3_stmt stmt ); - public static synchronized native String sqlite3_db_filename( - @NotNull sqlite3 db, @NotNull String dbName - ); - /** Overload for sqlite3_db_config() calls which take (int,int*) variadic arguments. Returns SQLITE_MISUSE if op is not one of the SQLITE_DBCONFIG_... options which uses this call form. */ - public static synchronized native int sqlite3_db_config( + @Canonical + public static native int sqlite3_db_config( @NotNull sqlite3 db, int op, int onOff, @Nullable OutputPointer.Int32 out ); @@ -525,43 +599,72 @@ public final class SQLite3Jni { SQLITE_DBCONFIG_MAINDBNAME, but that set of options may be extended in future versions. */ - public static synchronized native int sqlite3_db_config( + @Canonical(comment="Supports only a subset of options.") + public static native int sqlite3_db_config( @NotNull sqlite3 db, int op, @NotNull String val ); - public static synchronized native int sqlite3_db_status( + @Canonical + public static native String sqlite3_db_filename( + @NotNull sqlite3 db, @NotNull String dbName + ); + + @Canonical + public static native sqlite3 sqlite3_db_handle( @NotNull sqlite3_stmt stmt ); + + @Canonical + public static native int sqlite3_db_status( @NotNull sqlite3 db, int op, @NotNull OutputPointer.Int32 pCurrent, @NotNull OutputPointer.Int32 pHighwater, boolean reset ); - public static synchronized native int sqlite3_errcode(@NotNull sqlite3 db); + @Canonical + public static native int sqlite3_errcode(@NotNull sqlite3 db); - public static synchronized native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt); + @Canonical + public static native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt); - public static synchronized native int sqlite3_extended_errcode(@NotNull sqlite3 db); + @Canonical + public static native int sqlite3_extended_errcode(@NotNull sqlite3 db); - public static synchronized native boolean sqlite3_extended_result_codes( + @Canonical + public static native boolean sqlite3_extended_result_codes( @NotNull sqlite3 db, boolean onoff ); - public static synchronized native String sqlite3_errmsg(@NotNull sqlite3 db); + @Canonical + public static native String sqlite3_errmsg(@NotNull sqlite3 db); - public static synchronized native String sqlite3_errstr(int resultCode); + @Canonical + public static native String sqlite3_errstr(int resultCode); /** - Note that the offset values assume UTF-8-encoded SQL. + Note that the returned byte offset values assume UTF-8-encoded + inputs, so won't always match character offsets in Java Strings. */ - public static synchronized native int sqlite3_error_offset(@NotNull sqlite3 db); + @Canonical + public static native int sqlite3_error_offset(@NotNull sqlite3 db); + + @Canonical + public static native int sqlite3_finalize(@NotNull sqlite3_stmt stmt); - public static synchronized native int sqlite3_finalize(@NotNull sqlite3_stmt stmt); + @Canonical + public static native int sqlite3_initialize(); - public static synchronized native int sqlite3_initialize(); + @Canonical + public static native void sqlite3_interrupt(@NotNull sqlite3 db); - public static synchronized native long sqlite3_last_insert_rowid(@NotNull sqlite3 db); + @Canonical + public static native boolean sqlite3_is_interrupted(@NotNull sqlite3 db); - public static synchronized native String sqlite3_libversion(); + @Canonical + public static native long sqlite3_last_insert_rowid(@NotNull sqlite3 db); - public static synchronized native int sqlite3_libversion_number(); + @Canonical + public static native String sqlite3_libversion(); + + @Canonical + public static native int sqlite3_libversion_number(); /** Works like its C counterpart and makes the native pointer of the @@ -571,35 +674,57 @@ public final class SQLite3Jni { heed. Passing the object to sqlite3_close() or sqlite3_close_v2() will clear that pointer mapping. - Recall that even if opening fails, the output pointer might be + <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. - - Pedantic note: though any number of Java-level sqlite3 objects - may refer to/wrap a single C-level (sqlite3*), the JNI internals - take a reference to the object which is passed to sqlite3_open() - or sqlite3_open_v2() so that they have a predictible object to - pass to, e.g., the sqlite3_collation_needed() callback. */ - public static synchronized native int sqlite3_open( + @Canonical + public static native int sqlite3_open( @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb ); - public static synchronized native int sqlite3_open_v2( + /** + 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(); + }; + + @Canonical + 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 - overloading allows us to install several convenience forms. + different signatures than their native counterparts, but (A) they + retain functionally equivalent semantics and (B) overloading + allows us to install several convenience forms. - All of them which take their SQL in the form of a byte[] require + <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. - The forms which take a "tail" output pointer return (via that + <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 @@ -609,13 +734,30 @@ public final class SQLite3Jni { 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 synchronized native int sqlite3_prepare( + @Canonical + private static native int sqlite3_prepare( @NotNull sqlite3 db, @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, @@ -639,12 +781,37 @@ public final class SQLite3Jni { return sqlite3_prepare(db, utf8, utf8.length, outStmt, null); } - private static synchronized native int sqlite3_prepare_v2( + /** + 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 + */ + @Canonical + private static native int sqlite3_prepare_v2( @NotNull sqlite3 db, @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, @@ -668,12 +835,33 @@ public final class SQLite3Jni { return sqlite3_prepare_v2(db, utf8, utf8.length, outStmt, null); } - private static synchronized native int sqlite3_prepare_v3( + /** + 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 + */ + @Canonical + private static native int sqlite3_prepare_v3( @NotNull sqlite3 db, @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, @@ -697,22 +885,106 @@ public final class SQLite3Jni { return sqlite3_prepare_v3(db, utf8, utf8.length, prepFlags, outStmt, null); } - public static synchronized native void sqlite3_progress_handler( - @NotNull sqlite3 db, int n, @Nullable ProgressHandler h + /** + 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(); + } + + /** + 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. + */ + @Canonical + public static native int sqlite3_preupdate_blobwrite(@NotNull sqlite3 db); + /** + 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. + */ + @Canonical + public static native int sqlite3_preupdate_count(@NotNull sqlite3 db); + /** + 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. + */ + @Canonical + public static native int sqlite3_preupdate_depth(@NotNull sqlite3 db); + + /** + 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. + */ + @Canonical + public static native PreupdateHookCallback sqlite3_preupdate_hook( + @NotNull sqlite3 db, @Nullable PreupdateHookCallback hook ); - //TODO??? void *sqlite3_preupdate_hook(...) and friends + /** + 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. + */ + @Canonical + public static native int sqlite3_preupdate_new(@NotNull sqlite3 db, int col, + @NotNull OutputPointer.sqlite3_value out); - public static synchronized native int sqlite3_reset(@NotNull sqlite3_stmt stmt); + /** + 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, col, out); + return out.take(); + } + + /** + 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. + */ + @Canonical + public static native int sqlite3_preupdate_old(@NotNull sqlite3 db, int col, + @NotNull OutputPointer.sqlite3_value 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, col, out); + return out.take(); + } + + @Canonical + public static native void sqlite3_progress_handler( + @NotNull sqlite3 db, int n, @Nullable ProgressHandlerCallback h + ); + + @Canonical + 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 synchronized native void sqlite3_reset_auto_extension(); + @Canonical + public static native void sqlite3_reset_auto_extension(); - public static synchronized native void sqlite3_result_double( + @Canonical + public static native void sqlite3_result_double( @NotNull sqlite3_context cx, double v ); @@ -723,9 +995,9 @@ public final class SQLite3Jni { results in the C-level sqlite3_result_error() being called with a complaint about the invalid argument. */ - private static synchronized native void sqlite3_result_error( - @NotNull sqlite3_context cx, @Nullable byte[] msg, - int eTextRep + @Canonical + private static native void sqlite3_result_error( + @NotNull sqlite3_context cx, @NotNull byte[] msg, int eTextRep ); public static void sqlite3_result_error( @@ -737,12 +1009,12 @@ public final class SQLite3Jni { public static void sqlite3_result_error( @NotNull sqlite3_context cx, @NotNull String msg ){ - final byte[] utf8 = (msg+"\0").getBytes(StandardCharsets.UTF_8); + 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, @Nullable byte[] utf16 + @NotNull sqlite3_context cx, @NotNull byte[] utf16 ){ sqlite3_result_error(cx, utf16, SQLITE_UTF16); } @@ -750,133 +1022,149 @@ public final class SQLite3Jni { public static void sqlite3_result_error16( @NotNull sqlite3_context cx, @NotNull String msg ){ - final byte[] utf8 = (msg+"\0").getBytes(StandardCharsets.UTF_16); - sqlite3_result_error(cx, utf8, SQLITE_UTF16); + 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.getMessage()); + sqlite3_result_error(cx, e.toString()); } - public static void sqlite3_result_error16( - @NotNull sqlite3_context cx, @NotNull Exception e - ){ - sqlite3_result_error16(cx, e.getMessage()); - } - - public static synchronized native void sqlite3_result_error_toobig( + @Canonical + public static native void sqlite3_result_error_toobig( @NotNull sqlite3_context cx ); - public static synchronized native void sqlite3_result_error_nomem( + @Canonical + public static native void sqlite3_result_error_nomem( @NotNull sqlite3_context cx ); - public static synchronized native void sqlite3_result_error_code( + @Canonical + public static native void sqlite3_result_error_code( @NotNull sqlite3_context cx, int c ); - public static synchronized native void sqlite3_result_null( + @Canonical + public static native void sqlite3_result_null( @NotNull sqlite3_context cx ); - public static synchronized native void sqlite3_result_int( + @Canonical + public static native void sqlite3_result_int( @NotNull sqlite3_context cx, int v ); - public static synchronized native void sqlite3_result_int64( + @Canonical + public static native void sqlite3_result_int64( @NotNull sqlite3_context cx, long v ); /** - Binds the SQL result to the given object, or - sqlite3_result_null() if o is null. Use - sqlite3_value_java_object() or sqlite3_column_java_object() to - fetch it. - - This is implemented in terms of sqlite3_result_pointer(), but - that function is not exposed to JNI because its 3rd argument must - be a constant string (the library does not copy it), which we - cannot implement cross-language here unless, in the JNI layer, we - allocate such strings and store them somewhere for long-term use - (leaking them more likely than not). Even then, passing around a - pointer via Java like that has little practical use. - - Note that there is no sqlite3_bind_java_object() counterpart. + 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). + + <p>Note that there is no sqlite3_column_java_object(), as the + C-level API has no sqlite3_column_pointer() to proxy. + + @see #sqlite3_value_java_object + @see #sqlite3_bind_java_object */ - public static synchronized native void sqlite3_result_java_object( + public static native void sqlite3_result_java_object( @NotNull sqlite3_context cx, @NotNull Object o ); public static void sqlite3_result_set( - @NotNull sqlite3_context cx, @NotNull Integer v + @NotNull sqlite3_context cx, @NotNull Boolean v ){ - sqlite3_result_int(cx, v); + sqlite3_result_int(cx, v ? 1 : 0); } public static void sqlite3_result_set( - @NotNull sqlite3_context cx, int v + @NotNull sqlite3_context cx, boolean v ){ - sqlite3_result_int(cx, v); + sqlite3_result_int(cx, v ? 1 : 0); } public static void sqlite3_result_set( - @NotNull sqlite3_context cx, @NotNull Boolean v + @NotNull sqlite3_context cx, @NotNull Double v ){ - sqlite3_result_int(cx, v ? 1 : 0); + sqlite3_result_double(cx, v); } public static void sqlite3_result_set( - @NotNull sqlite3_context cx, boolean v + @NotNull sqlite3_context cx, double v ){ - sqlite3_result_int(cx, v ? 1 : 0); + sqlite3_result_double(cx, v); } public static void sqlite3_result_set( - @NotNull sqlite3_context cx, @NotNull Long v + @NotNull sqlite3_context cx, @NotNull Integer v ){ - sqlite3_result_int64(cx, 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, long v + @NotNull sqlite3_context cx, @NotNull Long v ){ sqlite3_result_int64(cx, v); } public static void sqlite3_result_set( - @NotNull sqlite3_context cx, @NotNull Double v + @NotNull sqlite3_context cx, long v ){ - sqlite3_result_double(cx, v); + sqlite3_result_int64(cx, v); } public static void sqlite3_result_set( - @NotNull sqlite3_context cx, double v + @NotNull sqlite3_context cx, @Nullable String v ){ - sqlite3_result_double(cx, 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 String v + @NotNull sqlite3_context cx, @Nullable byte[] blob ){ - sqlite3_result_text(cx, v); + if( null==blob ) sqlite3_result_null(cx); + else sqlite3_result_blob(cx, blob, blob.length); } - public static synchronized native void sqlite3_result_value( + @Canonical + public static native void sqlite3_result_value( @NotNull sqlite3_context cx, @NotNull sqlite3_value v ); - public static synchronized native void sqlite3_result_zeroblob( + @Canonical + public static native void sqlite3_result_zeroblob( @NotNull sqlite3_context cx, int n ); - public static synchronized native int sqlite3_result_zeroblob64( + @Canonical + public static native int sqlite3_result_zeroblob64( @NotNull sqlite3_context cx, long n ); - private static synchronized native void sqlite3_result_blob( + @Canonical + private static native void sqlite3_result_blob( @NotNull sqlite3_context cx, @Nullable byte[] blob, int maxLen ); @@ -889,14 +1177,20 @@ public final class SQLite3Jni { /** Binds the given text using C's sqlite3_result_blob64() unless: - - blob is null ==> sqlite3_result_null() + <ul> + + <li>@param blob is null: translates to sqlite3_result_null()</li> - - blob is too large ==> sqlite3_result_error_toobig() + <li>@param blob is too large: translates to + sqlite3_result_error_toobig()</li> - If maxLen is larger than blob.length, it is truncated to that - value. If it is negative, results are undefined. + </ul> + + If @param maxLen is larger than blob.length, it is truncated to + that value. If it is negative, results are undefined. */ - private static synchronized native void sqlite3_result_blob64( + @Canonical + private static native void sqlite3_result_blob64( @NotNull sqlite3_context cx, @Nullable byte[] blob, long maxLen ); @@ -906,14 +1200,15 @@ public final class SQLite3Jni { sqlite3_result_blob64(cx, blob, (long)(null==blob ? 0 : blob.length)); } - private static synchronized native void sqlite3_result_text( - @NotNull sqlite3_context cx, @Nullable byte[] text, int maxLen + @Canonical + 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[] text + @NotNull sqlite3_context cx, @Nullable byte[] utf8 ){ - sqlite3_result_text(cx, text, null==text ? 0 : text.length); + sqlite3_result_text(cx, utf8, null==utf8 ? 0 : utf8.length); } public static void sqlite3_result_text( @@ -929,40 +1224,37 @@ public final class SQLite3Jni { /** Binds the given text using C's sqlite3_result_text64() unless: - - text is null ==> sqlite3_result_null() + <ul> + + <li>text is null: translates to a call to sqlite3_result_null()</li> - - text is too large ==> sqlite3_result_error_toobig() + <li>text is too large: translates to a call to + {@link #sqlite3_result_error_toobig}</li> - - The `encoding` argument has an invalid value ==> - sqlite3_result_error_code() with SQLITE_FORMAT + <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. + negative, results are undefined. If text is null, the subsequent + arguments are ignored. */ - private static synchronized native void sqlite3_result_text64( + @Canonical + private static native void sqlite3_result_text64( @NotNull sqlite3_context cx, @Nullable byte[] text, long maxLength, int encoding ); - public static synchronized native int sqlite3_status( - int op, @NotNull OutputPointer.Int32 pCurrent, - @NotNull OutputPointer.Int32 pHighwater, boolean reset - ); - - public static synchronized native int sqlite3_status64( - int op, @NotNull OutputPointer.Int64 pCurrent, - @NotNull OutputPointer.Int64 pHighwater, boolean reset - ); - /** Sets the current UDF result to the given bytes, which are assumed be encoded in UTF-16 using the platform's byte order. */ public static void sqlite3_result_text16( - @NotNull sqlite3_context cx, @Nullable byte[] text + @NotNull sqlite3_context cx, @Nullable byte[] utf16 ){ - sqlite3_result_text64(cx, text, text.length, SQLITE_UTF16); + sqlite3_result_text64(cx, utf16, utf16.length, SQLITE_UTF16); } public static void sqlite3_result_text16( @@ -975,63 +1267,65 @@ public final class SQLite3Jni { } } - /** - Sets the current UDF result to the given bytes, which are assumed - be encoded in UTF-16LE. - */ - public static void sqlite3_result_text16le( - @NotNull sqlite3_context cx, @Nullable String text - ){ - if(null == text) sqlite3_result_null(cx); - else{ - final byte[] b = text.getBytes(StandardCharsets.UTF_16LE); - sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16LE); - } - } - - /** - Sets the current UDF result to the given bytes, which are assumed - be encoded in UTF-16BE. - */ - public static void sqlite3_result_text16be( - @NotNull sqlite3_context cx, @Nullable byte[] text - ){ - sqlite3_result_text64(cx, text, text.length, SQLITE_UTF16BE); - } - - public static void sqlite3_result_text16be( - @NotNull sqlite3_context cx, @NotNull String text - ){ - final byte[] b = text.getBytes(StandardCharsets.UTF_16BE); - sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16BE); - } - - public static synchronized native RollbackHook sqlite3_rollback_hook( - @NotNull sqlite3 db, @Nullable RollbackHook hook + @Canonical + public static native RollbackHookCallback sqlite3_rollback_hook( + @NotNull sqlite3 db, @Nullable RollbackHookCallback hook ); - //! Sets or unsets (if auth is null) the current authorizer. - public static synchronized native int sqlite3_set_authorizer( - @NotNull sqlite3 db, @Nullable Authorizer auth + @Canonical + public static native int sqlite3_set_authorizer( + @NotNull sqlite3 db, @Nullable AuthorizerCallback auth ); - public static synchronized native void sqlite3_set_last_insert_rowid( + @Canonical + public static native void sqlite3_set_last_insert_rowid( @NotNull sqlite3 db, long rowid ); - public static synchronized native int sqlite3_sleep(int ms); - public static synchronized native String sqlite3_sourceid(); + /** + 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. + */ + @Canonical + public static synchronized native int sqlite3_shutdown(); + + @Canonical + public static native int sqlite3_sleep(int ms); + + @Canonical + public static native String sqlite3_sourceid(); + + @Canonical + public static native String sqlite3_sql(@NotNull sqlite3_stmt stmt); + - public static synchronized native String sqlite3_sql(@NotNull sqlite3_stmt stmt); + @Canonical + public static native int sqlite3_status( + int op, @NotNull OutputPointer.Int32 pCurrent, + @NotNull OutputPointer.Int32 pHighwater, boolean reset + ); + + @Canonical + public static native int sqlite3_status64( + int op, @NotNull OutputPointer.Int64 pCurrent, + @NotNull OutputPointer.Int64 pHighwater, boolean reset + ); - public static synchronized native int sqlite3_step(@NotNull sqlite3_stmt stmt); + @Canonical + public static native int sqlite3_step(@NotNull sqlite3_stmt stmt); /** - Internal impl of the public sqlite3_strglob() method. Neither argument - may be NULL and both _MUST_ be NUL-terminated. + Internal impl of the public sqlite3_strglob() method. Neither + argument may be NULL and both MUST be NUL-terminated UTF-8. */ - private static synchronized native int sqlite3_strglob( + @Canonical + private static native int sqlite3_strglob( @NotNull byte[] glob, @NotNull byte[] txt ); @@ -1046,9 +1340,10 @@ public final class SQLite3Jni { /** Internal impl of the public sqlite3_strlike() method. Neither - argument may be NULL and both _MUST_ be NUL-terminated. + argument may be NULL and both MUST be NUL-terminated UTF-8. */ - private static synchronized native int sqlite3_strlike( + @Canonical + private static native int sqlite3_strlike( @NotNull byte[] glob, @NotNull byte[] txt, int escChar ); @@ -1062,65 +1357,79 @@ public final class SQLite3Jni { ); } - public static synchronized native int sqlite3_threadsafe(); + @Canonical + public static native int sqlite3_threadsafe(); - public static synchronized native int sqlite3_total_changes(@NotNull sqlite3 db); + @Canonical + public static native int sqlite3_total_changes(@NotNull sqlite3 db); - public static synchronized native long sqlite3_total_changes64(@NotNull sqlite3 db); + @Canonical + public static native long sqlite3_total_changes64(@NotNull sqlite3 db); /** Works like C's sqlite3_trace_v2() except that the 3rd argument to that function is elided here because the roles of that functions' 3rd and 4th arguments are encapsulated in the final argument to this function. - Unlike the C API, which is documented as always returning 0, this - implementation returns SQLITE_NOMEM if allocation of per-db - mapping state fails and SQLITE_ERROR if the given callback object - cannot be processed propertly (i.e. an internal error). + <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. */ - public static synchronized native int sqlite3_trace_v2( - @NotNull sqlite3 db, int traceMask, @Nullable Tracer tracer + @Canonical + public static native int sqlite3_trace_v2( + @NotNull sqlite3 db, int traceMask, @Nullable TraceV2Callback tracer ); - public static synchronized native UpdateHook sqlite3_update_hook( - sqlite3 db, UpdateHook hook + @Canonical + public static native UpdateHookCallback sqlite3_update_hook( + sqlite3 db, UpdateHookCallback hook ); - public static synchronized native byte[] sqlite3_value_blob(@NotNull sqlite3_value v); + @Canonical + public static native byte[] sqlite3_value_blob(@NotNull sqlite3_value v); - public static synchronized native int sqlite3_value_bytes(@NotNull sqlite3_value v); + @Canonical + public static native int sqlite3_value_bytes(@NotNull sqlite3_value v); - public static synchronized native int sqlite3_value_bytes16(@NotNull sqlite3_value v); + @Canonical + public static native int sqlite3_value_bytes16(@NotNull sqlite3_value v); - public static synchronized native double sqlite3_value_double(@NotNull sqlite3_value v); + @Canonical + public static native double sqlite3_value_double(@NotNull sqlite3_value v); - public static synchronized native sqlite3_value sqlite3_value_dupe( + @Canonical + public static native sqlite3_value sqlite3_value_dup( @NotNull sqlite3_value v ); - public static synchronized native int sqlite3_value_encoding(@NotNull sqlite3_value v); + @Canonical + public static native int sqlite3_value_encoding(@NotNull sqlite3_value v); - public static synchronized native void sqlite3_value_free(@Nullable sqlite3_value v); + @Canonical + public static native void sqlite3_value_free(@Nullable sqlite3_value v); - public static synchronized native int sqlite3_value_int(@NotNull sqlite3_value v); + @Canonical + public static native int sqlite3_value_int(@NotNull sqlite3_value v); - public static synchronized native long sqlite3_value_int64(@NotNull sqlite3_value v); + @Canonical + public static native long sqlite3_value_int64(@NotNull sqlite3_value v); /** - If the given value was set using sqlite3_result_java_value() then - this function returns that object, else it returns null. + If the given value was set using {@link + #sqlite3_result_java_object} then this function returns that + object, else it returns null. - It is up to the caller to inspect the object to determine its + <p>It is up to the caller to inspect the object to determine its type, and cast it if necessary. */ - public static synchronized native Object sqlite3_value_java_object( + public static native Object sqlite3_value_java_object( @NotNull sqlite3_value v ); /** A variant of sqlite3_value_java_object() which returns the fetched object cast to T if the object is an instance of the - given Class. It returns null in all other cases. + given Class, else it returns null. */ @SuppressWarnings("unchecked") public static <T> T sqlite3_value_java_casted(@NotNull sqlite3_value v, @@ -1130,45 +1439,55 @@ public final class SQLite3Jni { } /** - See sqlite3_column_text() for notes about encoding conversions. - See sqlite3_value_text_utf8() for how to extract text in standard - UTF-8. + Returns the given value as UTF-8-encoded bytes, or null if the + underlying C-level sqlite3_value_text() returns NULL. */ - public static synchronized native String sqlite3_value_text(@NotNull sqlite3_value v); + @Canonical(cname="sqlite3_value_text", + comment="Renamed because its String-returning overload would "+ + "otherwise be ambiguous.") + public static native byte[] sqlite3_value_text_utf8(@NotNull sqlite3_value v); /** - The sqlite3_value counterpart of sqlite3_column_text_utf8(). - */ - public static synchronized native byte[] sqlite3_value_text_utf8(@NotNull sqlite3_value v); + Provides the same feature as the same-named C API but returns the + text in Java-native encoding rather than the C API's UTF-8. - public static synchronized native byte[] sqlite3_value_text16(@NotNull sqlite3_value v); - - public static synchronized native byte[] sqlite3_value_text16le(@NotNull sqlite3_value v); - - public static synchronized native byte[] sqlite3_value_text16be(@NotNull sqlite3_value v); + @see #sqlite3_value_text16 + */ + public static native String sqlite3_value_text(@NotNull sqlite3_value v); - public static synchronized native int sqlite3_value_type(@NotNull sqlite3_value v); + /** + In the Java layer, sqlite3_value_text() and + sqlite3_value_text16() are functionally equivalent, the + difference being only where the encoding to UTF-16 (if necessary) + takes place. This function does it via SQLite and + sqlite3_value_text() fetches UTF-8 (SQLite's default encoding) + and converts it to UTF-16 in Java. + */ + @Canonical + public static native String sqlite3_value_text16(@NotNull sqlite3_value v); - public static synchronized native int sqlite3_value_numeric_type(@NotNull sqlite3_value v); + @Canonical + public static native int sqlite3_value_type(@NotNull sqlite3_value v); - public static synchronized native int sqlite3_value_nochange(@NotNull sqlite3_value v); + @Canonical + public static native int sqlite3_value_numeric_type(@NotNull sqlite3_value v); - public static synchronized native int sqlite3_value_frombind(@NotNull sqlite3_value v); + @Canonical + public static native int sqlite3_value_nochange(@NotNull sqlite3_value v); - public static synchronized native int sqlite3_value_subtype(@NotNull sqlite3_value v); + @Canonical + public static native int sqlite3_value_frombind(@NotNull sqlite3_value v); - /** - Cleans up all per-JNIEnv and per-db state managed by the library - then calls the C-native sqlite3_shutdown(). - */ - public static synchronized native int sqlite3_shutdown(); + @Canonical + public static native int sqlite3_value_subtype(@NotNull sqlite3_value v); /** This is NOT part of the public API. It exists solely as a place - to hook in arbitrary C-side code during development and testing - of this library. + 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 synchronized native void sqlite3_do_something_for_developer(); + public static native void sqlite3_jni_internal_details(); ////////////////////////////////////////////////////////////////////// // SQLITE_... constants follow... @@ -1178,10 +1497,9 @@ public final class SQLite3Jni { public static final String SQLITE_VERSION = sqlite3_libversion(); public static final String SQLITE_SOURCE_ID = sqlite3_sourceid(); - //! Feature flags which are initialized at lib startup. Necessarily - // non-final so that lib init can fill out the proper values, - // but modifying them from client code has no effect. - public static boolean SQLITE_ENABLE_FTS5 = false; + // Initialized at static init time to the build-time value of + // SQLITE_THREADSAFE. + public static int SQLITE_THREADSAFE = -1; // access public static final int SQLITE_ACCESS_EXISTS = 0; diff --git a/ext/jni/src/org/sqlite/jni/ScalarFunction.java b/ext/jni/src/org/sqlite/jni/ScalarFunction.java new file mode 100644 index 000000000..73fb58cda --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/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; + + +/** + 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 implementation does nothing. + */ + public void xDestroy() {} +} diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index dca49faf6..3d60362f6 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -15,65 +15,123 @@ package org.sqlite.jni; import static org.sqlite.jni.SQLite3Jni.*; import java.nio.charset.StandardCharsets; import java.util.Arrays; - -public class Tester1 { +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +/** + An annotation for Tester1 tests which we do not want to run in + reflection-driven test mode because either they are not suitable + for multi-threaded threaded mode or we have to control their execution + order. +*/ +@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) +@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) +@interface ManualTest{} +/** + Annotation for Tester1 tests which mark those which must be skipped + in multi-threaded mode. +*/ +@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) +@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) +@interface SingleThreadOnly{} + +public class Tester1 implements Runnable { + //! True when running in multi-threaded mode. + private static boolean mtMode = false; + //! True to sleep briefly between tests. + private static boolean takeNaps = false; + //! True to shuffle the order of the tests. + private static boolean shuffle = false; + //! True to dump the list of to-run tests to stdout. + private static boolean listRunTests = false; + //! True to squelch all out() and outln() output. + private static boolean quietMode = false; + //! Total number of runTests() calls. + private static int nTestRuns = 0; + //! List of test*() methods to run. + private static List<java.lang.reflect.Method> testMethods = null; + //! List of exceptions collected by run() + private static List<Exception> listErrors = new ArrayList<>(); private static final class Metrics { - int dbOpen; + //! 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(); - private static final OutputPointer.sqlite3_stmt outStmt - = new OutputPointer.sqlite3_stmt(); - public static void out(Object val){ - System.out.print(val); + public synchronized static void outln(){ + if( !quietMode ){ + System.out.println(""); + } } - public static void outln(Object val){ - System.out.println(val); + public synchronized static void outln(Object val){ + if( !quietMode ){ + System.out.print(Thread.currentThread().getName()+": "); + System.out.println(val); + } + } + + public synchronized static void out(Object val){ + if( !quietMode ){ + System.out.print(val); + } } @SuppressWarnings("unchecked") - public static void out(Object... vals){ - int n = 0; - for(Object v : vals) out((n++>0 ? " " : "")+v); + public synchronized static void out(Object... vals){ + if( !quietMode ){ + System.out.print(Thread.currentThread().getName()+": "); + for(Object v : vals) out(v); + } } @SuppressWarnings("unchecked") - public static void outln(Object... vals){ - out(vals); out("\n"); + public synchronized static void outln(Object... vals){ + if( !quietMode ){ + out(vals); out("\n"); + } } - static int affirmCount = 0; - public static void affirm(Boolean v){ + static volatile int affirmCount = 0; + public synchronized static void affirm(Boolean v, String comment){ ++affirmCount; - assert( v /* prefer assert over exception if it's enabled because - the JNI layer sometimes has to suppress exceptions. */); - if( !v ) throw new RuntimeException("Assertion failed."); + 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); } - private static void test1(){ - outln("libversion_number:", - sqlite3_libversion_number() - + "\n" - + sqlite3_libversion() - + "\n" - + SQLITE_SOURCE_ID); + public static void affirm(Boolean v){ + affirm(v, "Affirmation failed."); + } + + @ManualTest /* because testing this for threading is pointless */ + private void test1(){ affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER); - //outln("threadsafe = "+sqlite3_threadsafe()); affirm(SQLITE_MAX_LENGTH > 0); affirm(SQLITE_MAX_TRIGGER_DEPTH>0); } - public static sqlite3 createNewDb(){ + static sqlite3 createNewDb(){ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); int rc = sqlite3_open(":memory:", out); ++metrics.dbOpen; sqlite3 db = out.take(); if( 0!=rc ){ - final String msg = db.getNativePointer()==0 - ? sqlite3_errstr(rc) - : sqlite3_errmsg(db); + 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() ); @@ -83,17 +141,18 @@ public class Tester1 { return db; } - public static void execSql(sqlite3 db, String[] sql){ + static void execSql(sqlite3 db, String[] sql){ execSql(db, String.join("", sql)); } - public static int execSql(sqlite3 db, boolean throwOnError, String sql){ + 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, @@ -127,21 +186,27 @@ public class Tester1 { return rc; } - public static void execSql(sqlite3 db, String sql){ + static void execSql(sqlite3 db, String sql){ execSql(db, true, sql); } - public static sqlite3_stmt prepare(sqlite3 db, String sql){ - outStmt.clear(); + static sqlite3_stmt prepare(sqlite3 db, boolean throwOnError, String sql){ + final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); int rc = sqlite3_prepare(db, sql, outStmt); - affirm( 0 == rc ); + if( throwOnError ){ + affirm( 0 == rc ); + } final sqlite3_stmt rv = outStmt.take(); affirm( null == outStmt.get() ); - affirm( 0 != rv.getNativePointer() ); + if( throwOnError ){ + affirm( 0 != rv.getNativePointer() ); + } return rv; } - - private static void testCompileOption(){ + static sqlite3_stmt prepare(sqlite3 db, String sql){ + return prepare(db, true, sql); + } + private void showCompileOption(){ int i = 0; String optName; outln("compile options:"); @@ -152,7 +217,7 @@ public class Tester1 { } - private static void testOpenDb1(){ + private void testOpenDb1(){ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); int rc = sqlite3_open(":memory:", out); ++metrics.dbOpen; @@ -163,11 +228,18 @@ public class Tester1 { /* 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. */; + + // 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 static void testOpenDb2(){ + private void testOpenDb2(){ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); int rc = sqlite3_open_v2(":memory:", out, SQLITE_OPEN_READWRITE @@ -180,16 +252,22 @@ public class Tester1 { affirm(0 == db.getNativePointer()); } - private static void testPrepare123(){ + 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.get(); + sqlite3_stmt stmt = outStmt.take(); affirm(0 != stmt.getNativePointer()); + affirm( db == sqlite3_db_handle(stmt) ); rc = sqlite3_step(stmt); + if( SQLITE_DONE != rc ){ + outln("step failed ??? ",rc, " ",sqlite3_errmsg(db)); + } 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 @@ -236,10 +314,20 @@ public class Tester1 { 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 static void testBindFetchInt(){ + private void testBindFetchInt(){ sqlite3 db = createNewDb(); execSql(db, "CREATE TABLE t(a)"); @@ -286,7 +374,7 @@ public class Tester1 { affirm(0 == db.getNativePointer()); } - private static void testBindFetchInt64(){ + private void testBindFetchInt64(){ sqlite3 db = createNewDb(); execSql(db, "CREATE TABLE t(a)"); sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); @@ -308,7 +396,7 @@ public class Tester1 { sqlite3_close_v2(db); } - private static void testBindFetchDouble(){ + private void testBindFetchDouble(){ sqlite3 db = createNewDb(); execSql(db, "CREATE TABLE t(a)"); sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); @@ -333,14 +421,17 @@ public class Tester1 { sqlite3_close_v2(db); } - private static void testBindFetchText(){ + 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 = sqlite3_bind_text(stmt, 1, e); + 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); @@ -349,11 +440,15 @@ public class Tester1 { sqlite3_finalize(stmt); stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); StringBuilder sbuf = new StringBuilder(); - int n = 0; + n = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ - String txt = sqlite3_column_text16(stmt, 0); - //outln("txt = "+txt); + 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(sqlite3_column_text(stmt, 0)) ); + affirm( txt.equals(sqlite3_value_text(sv)) ); + affirm( txt.equals(sqlite3_value_text16(sv)) ); + sqlite3_value_free(sv); ++n; } sqlite3_finalize(stmt); @@ -362,7 +457,7 @@ public class Tester1 { sqlite3_close_v2(db); } - private static void testBindFetchBlob(){ + private void testBindFetchBlob(){ sqlite3 db = createNewDb(); execSql(db, "CREATE TABLE t(a)"); sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); @@ -391,7 +486,7 @@ public class Tester1 { sqlite3_close_v2(db); } - private static void testSql(){ + private void testSql(){ sqlite3 db = createNewDb(); sqlite3_stmt stmt = prepare(db, "SELECT 1"); affirm( "SELECT 1".equals(sqlite3_sql(stmt)) ); @@ -400,18 +495,19 @@ public class Tester1 { sqlite3_bind_text(stmt, 1, "hell😃"); affirm( "SELECT 'hell😃'".equals(sqlite3_expanded_sql(stmt)) ); sqlite3_finalize(stmt); + sqlite3_close(db); } - private static void testCollation(){ + private void testCollation(){ final sqlite3 db = createNewDb(); execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); - final ValueHolder<Boolean> xDestroyCalled = new ValueHolder<>(false); - final Collation myCollation = new Collation() { + 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 xCompare(byte[] lhs, byte[] rhs){ + 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){ @@ -427,11 +523,12 @@ public class Tester1 { @Override public void xDestroy() { // Just demonstrates that xDestroy is called. - xDestroyCalled.value = true; + ++xDestroyCalled.value; } }; - final CollationNeeded collLoader = new CollationNeeded(){ - public int xCollationNeeded(sqlite3 dbArg, int eTextRep, String collationName){ + final CollationNeededCallback collLoader = new CollationNeededCallback(){ + @Override + public int call(sqlite3 dbArg, int eTextRep, String collationName){ affirm(dbArg == db/* as opposed to a temporary object*/); return sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation); } @@ -468,18 +565,17 @@ public class Tester1 { } affirm(3 == counter); sqlite3_finalize(stmt); - affirm(!xDestroyCalled.value); + affirm( 0 == xDestroyCalled.value ); rc = sqlite3_collation_needed(db, null); affirm( 0 == rc ); sqlite3_close_v2(db); - affirm(xDestroyCalled.value); + affirm( 0 == db.getNativePointer() ); + affirm( 1 == xDestroyCalled.value ); } - private static void testToUtf8(){ + @ManualTest /* because threading is meaningless here */ + private void testToUtf8(){ /** - Java docs seem contradictory, claiming to use "modified UTF-8" - encoding while also claiming to export using RFC 2279: - https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html Let's ensure that we can convert to standard UTF-8 in Java code @@ -489,7 +585,7 @@ public class Tester1 { affirm( 5 == ba.length /* as opposed to 6 in modified utf-8 */); } - private static void testStatus(){ + private void testStatus(){ final OutputPointer.Int64 cur64 = new OutputPointer.Int64(); final OutputPointer.Int64 high64 = new OutputPointer.Int64(); final OutputPointer.Int32 cur32 = new OutputPointer.Int32(); @@ -517,7 +613,7 @@ public class Tester1 { sqlite3_close_v2(db); } - private static void testUdf1(){ + 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); @@ -529,7 +625,7 @@ public class Tester1 { // Each of the 3 subclasses requires a different set of // functions, all of which must be implemented. Anonymous // classes are a convenient way to implement these. - new SQLFunction.Scalar(){ + new ScalarFunction(){ public void xFunc(sqlite3_context cx, sqlite3_value[] args){ affirm(db == sqlite3_context_db_handle(cx)); int result = 0; @@ -561,19 +657,67 @@ public class Tester1 { affirm( xDestroyCalled.value ); } - private static void testUdfJavaObject(){ + 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 SQLFunction func = new SQLFunction.Scalar(){ + 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); - final sqlite3_stmt stmt = prepare(db, "select myfunc()"); + sqlite3_stmt stmt = prepare(db, "select myfunc(?)"); affirm( 0 != stmt.getNativePointer() ); affirm( testResult.value == db ); + rc = sqlite3_bind_java_object(stmt, 1, boundObj); + affirm( 0==rc ); int n = 0; if( SQLITE_ROW == sqlite3_step(stmt) ){ final sqlite3_value v = sqlite3_column_value(stmt, 0); @@ -590,16 +734,18 @@ public class Tester1 { sqlite3_close_v2(db); } - private static void testUdfAggregate(){ + 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); - SQLFunction func = new SQLFunction.Aggregate<Integer>(){ + SQLFunction func = new AggregateFunction<Integer>(){ @Override public void xStep(sqlite3_context cx, sqlite3_value[] args){ - this.getAggregateState(cx, 0).value += sqlite3_value_int(args[0]); + 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){ @@ -616,15 +762,19 @@ public class Tester1 { 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( null != stmt ); int n = 0; if( SQLITE_ROW == sqlite3_step(stmt) ){ - final int v = sqlite3_column_int(stmt, 0); + int v = sqlite3_column_int(stmt, 0); affirm( 6 == v ); + int v2 = sqlite3_column_int(stmt, 1); + affirm( 30+v == v2 ); ++n; } + affirm( 1==n ); affirm(!xFinalNull.value); sqlite3_reset(stmt); - // Ensure that the accumulator is reset... + // 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); @@ -643,20 +793,20 @@ public class Tester1 { affirm( 6 == c0 ); affirm( 12 == c1 ); } + sqlite3_finalize(stmt); affirm( 1 == n ); affirm(!xFinalNull.value); - sqlite3_finalize(stmt); execSql(db, "SELECT myfunc(1) WHERE 0"); affirm(xFinalNull.value); sqlite3_close_v2(db); } - private static void testUdfWindow(){ + 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 SQLFunction.Window<Integer>(){ + final SQLFunction func = new WindowFunction<Integer>(){ private void xStepInverse(sqlite3_context cx, int v){ this.getAggregateState(cx,0).value += v; @@ -709,14 +859,14 @@ public class Tester1 { sqlite3_close_v2(db); } - private static void listBoundMethods(){ + private void listBoundMethods(){ if(false){ final java.lang.reflect.Field[] declaredFields = SQLite3Jni.class.getDeclaredFields(); outln("Bound constants:\n"); for(java.lang.reflect.Field field : declaredFields) { if(java.lang.reflect.Modifier.isStatic(field.getModifiers())) { - outln("\t"+field.getName()); + outln("\t",field.getName()); } } } @@ -735,23 +885,23 @@ public class Tester1 { java.util.Collections.sort(funcList); for(String n : funcList){ ++count; - outln("\t"+n+"()"); + outln("\t",n,"()"); } - outln(count+" functions named sqlite3_*."); + outln(count," functions named sqlite3_*."); } - private static void testTrace(){ + 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 = "😃"; - sqlite3_trace_v2( + int rc = sqlite3_trace_v2( db, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE | SQLITE_TRACE_ROW | SQLITE_TRACE_CLOSE, - new Tracer(){ - public int xCallback(int traceFlag, Object pNative, Object x){ + new TraceV2Callback(){ + @Override public int call(int traceFlag, Object pNative, Object x){ ++counter.value; //outln("TRACE "+traceFlag+" pNative = "+pNative.getClass().getName()); switch(traceFlag){ @@ -782,6 +932,7 @@ public class Tester1 { return 0; } }); + affirm( 0==rc ); execSql(db, "SELECT coalesce(null,null,'"+nonBmpChar+"'); "+ "SELECT 'w"+nonBmpChar+"orld'"); affirm( 6 == counter.value ); @@ -789,9 +940,11 @@ public class Tester1 { affirm( 7 == counter.value ); } - private static void testBusy(){ + @ManualTest /* because threads inherently break this test */ + private void testBusy(){ final String dbName = "_busy-handler.db"; final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3(); + final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); int rc = sqlite3_open(dbName, outDb); ++metrics.dbOpen; @@ -806,31 +959,24 @@ public class Tester1 { rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo"); affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) ); - final ValueHolder<Boolean> xDestroyed = new ValueHolder<>(false); final ValueHolder<Integer> xBusyCalled = new ValueHolder<>(0); - BusyHandler handler = new BusyHandler(){ - @Override public int xCallback(int n){ + BusyHandlerCallback handler = new BusyHandlerCallback(){ + @Override public int call(int n){ //outln("busy handler #"+n); return n > 2 ? 0 : ++xBusyCalled.value; } - @Override public void xDestroy(){ - xDestroyed.value = true; - } }; rc = sqlite3_busy_handler(db2, handler); affirm(0 == rc); // Force a locked condition... execSql(db1, "BEGIN EXCLUSIVE"); - affirm(!xDestroyed.value); rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt); affirm( SQLITE_BUSY == rc); assert( null == outStmt.get() ); affirm( 3 == xBusyCalled.value ); sqlite3_close_v2(db1); - affirm(!xDestroyed.value); sqlite3_close_v2(db2); - affirm(xDestroyed.value); try{ final java.io.File f = new java.io.File(dbName); f.delete(); @@ -839,11 +985,11 @@ public class Tester1 { } } - private static void testProgress(){ + private void testProgress(){ final sqlite3 db = createNewDb(); final ValueHolder<Integer> counter = new ValueHolder<>(0); - sqlite3_progress_handler(db, 1, new ProgressHandler(){ - public int xCallback(){ + sqlite3_progress_handler(db, 1, new ProgressHandlerCallback(){ + @Override public int call(){ ++counter.value; return 0; } @@ -857,17 +1003,17 @@ public class Tester1 { sqlite3_close_v2(db); } - private static void testCommitHook(){ + private void testCommitHook(){ final sqlite3 db = createNewDb(); final ValueHolder<Integer> counter = new ValueHolder<>(0); final ValueHolder<Integer> hookResult = new ValueHolder<>(0); - final CommitHook theHook = new CommitHook(){ - public int xCommitHook(){ + final CommitHookCallback theHook = new CommitHookCallback(){ + @Override public int call(){ ++counter.value; return hookResult.value; } }; - CommitHook oldHook = sqlite3_commit_hook(db, theHook); + 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 ); @@ -888,8 +1034,8 @@ public class Tester1 { execSql(db, "BEGIN; update t set a='g' where a='f'; COMMIT;"); affirm( 4 == counter.value ); - final CommitHook newHook = new CommitHook(){ - public int xCommitHook(){return 0;} + final CommitHookCallback newHook = new CommitHookCallback(){ + @Override public int call(){return 0;} }; oldHook = sqlite3_commit_hook(db, newHook); affirm( null == oldHook ); @@ -906,20 +1052,20 @@ public class Tester1 { sqlite3_close_v2(db); } - private static void testUpdateHook(){ + private void testUpdateHook(){ final sqlite3 db = createNewDb(); final ValueHolder<Integer> counter = new ValueHolder<>(0); final ValueHolder<Integer> expectedOp = new ValueHolder<>(0); - final UpdateHook theHook = new UpdateHook(){ - @SuppressWarnings("unchecked") - public void xUpdateHook(int opId, String dbName, String tableName, long rowId){ + 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 ); } } }; - UpdateHook oldHook = sqlite3_update_hook(db, theHook); + 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')"); @@ -939,8 +1085,8 @@ public class Tester1 { oldHook = sqlite3_update_hook(db, null); affirm( null == oldHook ); - final UpdateHook newHook = new UpdateHook(){ - public void xUpdateHook(int opId, String dbName, String tableName, long rowId){ + final UpdateHookCallback newHook = new UpdateHookCallback(){ + @Override public void call(int opId, String dbName, String tableName, long rowId){ } }; oldHook = sqlite3_update_hook(db, newHook); @@ -955,23 +1101,99 @@ public class Tester1 { sqlite3_close_v2(db); } - private static void testRollbackHook(){ + /** + 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 RollbackHook theHook = new RollbackHook(){ - public void xRollbackHook(){ + final RollbackHookCallback theHook = new RollbackHookCallback(){ + @Override public void call(){ ++counter.value; } }; - RollbackHook oldHook = sqlite3_rollback_hook(db, theHook); + 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 RollbackHook newHook = new RollbackHook(){ - public void xRollbackHook(){return;} + final RollbackHookCallback newHook = new RollbackHookCallback(){ + @Override public void call(){return;} }; oldHook = sqlite3_rollback_hook(db, newHook); affirm( theHook == oldHook ); @@ -993,9 +1215,11 @@ public class Tester1 { it throws. */ @SuppressWarnings("unchecked") - private static void testFts5() throws Exception { - if( !SQLITE_ENABLE_FTS5 ){ - outln("SQLITE_ENABLE_FTS5 is not set. Skipping FTS5 tests."); + @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; @@ -1021,12 +1245,12 @@ public class Tester1 { } } - private static void testAuthorizer(){ + private void testAuthorizer(){ final sqlite3 db = createNewDb(); final ValueHolder<Integer> counter = new ValueHolder<>(0); final ValueHolder<Integer> authRc = new ValueHolder<>(0); - final Authorizer auth = new Authorizer(){ - public int xAuth(int op, String s0, String s1, String s2, String s3){ + 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; @@ -1043,11 +1267,13 @@ public class Tester1 { sqlite3_close(db); } - private static void testAutoExtension(){ + @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 AutoExtension ax = new AutoExtension(){ - public synchronized int xEntryPoint(sqlite3 db){ + final AutoExtensionCallback ax = new AutoExtensionCallback(){ + @Override public synchronized int call(sqlite3 db){ ++val.value; if( null!=toss.value ){ throw new RuntimeException(toss.value); @@ -1071,73 +1297,323 @@ public class Tester1 { 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( 3==val.value ); + affirm( 4==val.value ); rc = sqlite3_auto_extension( ax ); affirm( 0==rc ); Exception err = null; - toss.value = "Throwing from AutoExtension."; + toss.value = "Throwing from auto_extension."; try{ - createNewDb(); + sqlite3_close(createNewDb()); }catch(Exception e){ err = e; } affirm( err!=null ); affirm( err.getMessage().indexOf(toss.value)>0 ); + toss.value = null; + + val.value = 0; + final AutoExtensionCallback ax2 = new AutoExtensionCallback(){ + @Override public synchronized int call(sqlite3 db){ + ++val.value; + return 0; + } + }; + rc = sqlite3_auto_extension( ax2 ); + affirm( 0 == rc ); + sqlite3_close(createNewDb()); + affirm( 2 == val.value ); affirm( sqlite3_cancel_auto_extension(ax) ); + affirm( !sqlite3_cancel_auto_extension(ax) ); + sqlite3_close(createNewDb()); + affirm( 3 == val.value ); + rc = sqlite3_auto_extension( ax ); + affirm( 0 == rc ); + sqlite3_close(createNewDb()); + affirm( 5 == val.value ); + affirm( sqlite3_cancel_auto_extension(ax2) ); + affirm( !sqlite3_cancel_auto_extension(ax2) ); + sqlite3_close(createNewDb()); + affirm( 6 == val.value ); + rc = sqlite3_auto_extension( ax2 ); + affirm( 0 == rc ); + sqlite3_close(createNewDb()); + affirm( 8 == val.value ); + + sqlite3_reset_auto_extension(); + sqlite3_close(createNewDb()); + affirm( 8 == val.value ); + affirm( !sqlite3_cancel_auto_extension(ax) ); + affirm( !sqlite3_cancel_auto_extension(ax2) ); + sqlite3_close(createNewDb()); + affirm( 8 == val.value ); } - private static void testSleep(){ + @ManualTest /* we really only want to run this test manually. */ + private void testSleep(){ out("Sleeping briefly... "); sqlite3_sleep(600); outln("Woke up."); } - public static void main(String[] args) throws Exception { - final long timeStart = System.nanoTime(); - test1(); - if(false) testCompileOption(); - final java.util.List<String> liArgs = - java.util.Arrays.asList(args); - testOpenDb1(); - testOpenDb2(); - testPrepare123(); - testBindFetchInt(); - testBindFetchInt64(); - testBindFetchDouble(); - testBindFetchText(); - testBindFetchBlob(); - testSql(); - testCollation(); + 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( listRunTests ){ + synchronized(this.getClass()){ + if( !fromThread ){ + out("Initial test"," list: "); + for(java.lang.reflect.Method m : testMethods){ + out(m.getName()+" "); + } + outln(); + outln("(That list excludes some which are hard-coded to run.)"); + } + out("Running"," tests: "); + for(java.lang.reflect.Method m : mlist){ + out(m.getName()+" "); + } + outln(); + } + } testToUtf8(); - testStatus(); - testUdf1(); - testUdfJavaObject(); - testUdfAggregate(); - testUdfWindow(); - testTrace(); - testBusy(); - testProgress(); - testCommitHook(); - testRollbackHook(); - testUpdateHook(); - testAuthorizer(); - testFts5(); - testAutoExtension(); - //testSleep(); - if(liArgs.indexOf("-v")>0){ - sqlite3_do_something_for_developer(); - //listBoundMethods(); + test1(); + 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; + } + } + if( !fromThread ){ + testBusy(); + } + synchronized( this.getClass() ){ + ++nTestRuns; + } + } + + public void run() { + try { + runTests(0!=this.tId); + }catch(Exception e){ + synchronized( listErrors ){ + listErrors.add(e); + } + }finally{ + affirm( sqlite3_java_uncache_thread() ); + affirm( !sqlite3_java_uncache_thread() ); + } + } + + /** + Runs the basic sqlite3 JNI binding sanity-check suite. + + CLI flags: + + -q|-quiet: disables most test output. + + -t|-thread N: runs the tests in N threads + concurrently. Default=1. + + -r|-repeat N: repeats the tests in a loop N times, each one + consisting of the -thread value's threads. + + -shuffle: randomizes the order of most of the test functions. + + -naps: sleep small random intervals between tests in order to add + some chaos for cross-thread contention. + + -list-tests: outputs the list of tests being run, minus some + which are hard-coded. This is noisy in multi-threaded mode. + + -fail: forces an exception to be thrown during the test run. Use + with -shuffle to make its appearance unpredictable. + + -v: emit some developer-mode info at the end. + */ + public static void main(String[] args) throws Exception { + Integer nThread = 1; + boolean doSomethingForDev = false; + Integer nRepeat = 1; + boolean forceFail = false; + boolean sqlLog = false; + boolean squelchTestOutput = false; + for( int i = 0; i < args.length; ){ + String arg = args[i++]; + if(arg.startsWith("-")){ + arg = arg.replaceFirst("-+",""); + if(arg.equals("v")){ + doSomethingForDev = true; + //listBoundMethods(); + }else if(arg.equals("t") || arg.equals("thread")){ + nThread = Integer.parseInt(args[i++]); + }else if(arg.equals("r") || arg.equals("repeat")){ + nRepeat = Integer.parseInt(args[i++]); + }else if(arg.equals("shuffle")){ + shuffle = true; + }else if(arg.equals("list-tests")){ + listRunTests = true; + }else if(arg.equals("fail")){ + forceFail = true; + }else if(arg.equals("sqllog")){ + sqlLog = true; + }else if(arg.equals("naps")){ + takeNaps = true; + }else if(arg.equals("q") || arg.equals("quiet")){ + squelchTestOutput = true; + }else{ + throw new IllegalArgumentException("Unhandled flag:"+arg); + } + } } - final long timeEnd = System.nanoTime(); - affirm( SQLite3Jni.uncacheJniEnv() ); - affirm( !SQLite3Jni.uncacheJniEnv() ); - outln("Tests done. Metrics:"); - outln("\tAssertions checked: "+affirmCount); - outln("\tDatabases opened: "+metrics.dbOpen); + if( sqlLog ){ + if( sqlite3_compileoption_used("ENABLE_SQLLOG") ){ + int rc = sqlite3_config( 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; + } + } + }); + affirm( 0==rc ); + }else{ + outln("WARNING: -sqllog is not active because library was built ", + "without SQLITE_ENABLE_SQLLOG."); + } + } + + 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<>(); + out("Skipping tests in multi-thread mode:"); + for(final java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){ + final String name = m.getName(); + if( name.equals("testFail") ){ + if( forceFail ){ + testMethods.add(m); + } + }else if( !m.isAnnotationPresent( ManualTest.class ) ){ + if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){ + out(" "+name+"()"); + }else if( name.startsWith("test") ){ + testMethods.add(m); + } + } + } + out("\n"); + } + + final long timeStart = System.currentTimeMillis(); + int nLoop = 0; + switch( SQLITE_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=",SQLITE_THREADSAFE); + 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; + out((1==nLoop ? "" : " ")+nLoop); + if( nThread<=1 ){ + new Tester1(0).runTests(false); + continue; + } + Tester1.mtMode = true; + final ExecutorService ex = Executors.newFixedThreadPool( nThread ); + //final List<Future<?>> futures = new ArrayList<>(); + 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; + } + } + 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(); + } + sqlite3_shutdown(); int nMethods = 0; int nNatives = 0; final java.lang.reflect.Method[] declaredMethods = @@ -1154,10 +1630,10 @@ public class Tester1 { } } } - outln("\tSQLite3Jni sqlite3_*() methods: "+ + outln("\tSQLite3Jni.sqlite3_*() methods: "+ nNatives+" native methods and "+ (nMethods - nNatives)+" Java impls"); - outln("\tTotal time = " - +((timeEnd - timeStart)/1000000.0)+"ms"); + outln("\tTotal test time = " + +(timeEnd - timeStart)+"ms"); } } diff --git a/ext/jni/src/org/sqlite/jni/TesterFts5.java b/ext/jni/src/org/sqlite/jni/TesterFts5.java index 6439768e2..feb6d6303 100644 --- a/ext/jni/src/org/sqlite/jni/TesterFts5.java +++ b/ext/jni/src/org/sqlite/jni/TesterFts5.java @@ -72,16 +72,18 @@ public class TesterFts5 { affirm( xDestroyCalled.value ); } - public TesterFts5(){ - int oldAffirmCount = Tester1.affirmCount; - Tester1.affirmCount = 0; - final long timeStart = System.nanoTime(); - test1(); - final long timeEnd = System.nanoTime(); - outln("FTS5 Tests done. Metrics:"); - outln("\tAssertions checked: "+Tester1.affirmCount); - outln("\tTotal time = " - +((timeEnd - timeStart)/1000000.0)+"ms"); - Tester1.affirmCount = oldAffirmCount; + public TesterFts5(boolean verbose){ + if(verbose){ + final long timeStart = System.currentTimeMillis(); + final int oldAffirmCount = Tester1.affirmCount; + test1(); + final int affirmCount = Tester1.affirmCount - oldAffirmCount; + final long timeEnd = System.currentTimeMillis(); + outln("FTS5 Tests done. Assertions checked = ",affirmCount, + ", Total time = ",(timeEnd - timeStart),"ms"); + }else{ + test1(); + } } + public TesterFts5(){ this(false); } } diff --git a/ext/jni/src/org/sqlite/jni/TraceV2Callback.java b/ext/jni/src/org/sqlite/jni/TraceV2Callback.java new file mode 100644 index 000000000..897aeefa9 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/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; +import org.sqlite.jni.annotation.Nullable; + +/** + Callback for use with {@link SQLite3Jni#sqlite3_trace_v2}. +*/ +public interface TraceV2Callback extends SQLite3CallbackProxy { + /** + 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/Tracer.java b/ext/jni/src/org/sqlite/jni/Tracer.java deleted file mode 100644 index fa62edbfa..000000000 --- a/ext/jni/src/org/sqlite/jni/Tracer.java +++ /dev/null @@ -1,62 +0,0 @@ -/* -** 2023-07-22 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** This file is part of the JNI bindings for the sqlite3 C API. -*/ -package org.sqlite.jni; - -/** - Callback proxy for use with sqlite3_trace_v2(). -*/ -public interface Tracer { - /** - Achtung: this interface is subject to change because the current - approach to mapping the passed-in natives back to Java is - uncomfortably quirky. - - Called by sqlite3 for various tracing operations, as per - sqlite3_trace_v2(). Note that this interface elides the 2nd - argument to the native trace callback, as that role is better - filled by instance-local state. - - The 2nd argument to this function, if non-0, will be a native - pointer to either an sqlite3 or sqlite3_stmt object, depending on - the first argument (see below). Client code can pass it to the - sqlite3 resp. sqlite3_stmt constructor to create a wrapping - object, if necessary. This API does not do so by default because - tracing can be called frequently, creating such a wrapper for - each call is comparatively expensive, and the objects are - probably only seldom useful. - - The final argument to this function is the "X" argument - documented for sqlite3_trace() and sqlite3_trace_v2(). Its type - depends on value of the first argument: - - - SQLITE_TRACE_STMT: pNative is a sqlite3_stmt. pX is a string - containing the prepared SQL, with one caveat: JNI only provides - us with the ability to convert that string to MUTF-8, as - opposed to standard UTF-8, and is cannot be ruled out that that - difference may be significant for certain inputs. The - alternative would be that we first convert it to UTF-16 before - passing it on, but there's no readily-available way to do that - without calling back into the db to peform the conversion - (which would lead to further tracing). - - - SQLITE_TRACE_PROFILE: pNative is a sqlite3_stmt. pX is a Long - holding an approximate number of nanoseconds the statement took - to run. - - - SQLITE_TRACE_ROW: pNative is a sqlite3_stmt. pX is null. - - - SQLITE_TRACE_CLOSE: pNative is a sqlite3. pX is null. - */ - int xCallback(int traceFlag, Object pNative, Object pX); -} diff --git a/ext/jni/src/org/sqlite/jni/UpdateHook.java b/ext/jni/src/org/sqlite/jni/UpdateHookCallback.java index 171e2bdb4..4fd0a6324 100644 --- a/ext/jni/src/org/sqlite/jni/UpdateHook.java +++ b/ext/jni/src/org/sqlite/jni/UpdateHookCallback.java @@ -1,5 +1,5 @@ /* -** 2023-07-22 +** 2023-08-25 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -14,12 +14,12 @@ package org.sqlite.jni; /** - Callback proxy for use with sqlite3_update_hook(). + Callback for use with {@link SQLite3Jni#sqlite3_update_hook}. */ -public interface UpdateHook { +public interface UpdateHookCallback extends SQLite3CallbackProxy { /** - Works as documented for the sqlite3_update_hook() callback. - Must not throw. + Must function as described for the C-level sqlite3_update_hook() + callback. */ - void xUpdateHook(int opId, String dbName, String tableName, long rowId); + void call(int opId, String dbName, String tableName, long rowId); } diff --git a/ext/jni/src/org/sqlite/jni/WindowFunction.java b/ext/jni/src/org/sqlite/jni/WindowFunction.java new file mode 100644 index 000000000..7f70177ac --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/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; + + +/** + 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/XDestroyCallback.java b/ext/jni/src/org/sqlite/jni/XDestroyCallback.java new file mode 100644 index 000000000..d1e4b1ee3 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/XDestroyCallback.java @@ -0,0 +1,29 @@ +/* +** 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; + +/** + 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. + */ + public void xDestroy(); +} diff --git a/ext/jni/src/org/sqlite/jni/annotation/Canonical.java b/ext/jni/src/org/sqlite/jni/annotation/Canonical.java new file mode 100644 index 000000000..f329aee13 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/annotation/Canonical.java @@ -0,0 +1,44 @@ +package org.sqlite.jni.annotation; + +/** + This annotation is for marking functions as "canonical", meaning + that they map directly to a function in the core sqlite3 C API. The + intent is to distinguish them from functions added specifically to + the Java API. + + <p>Canonical functions, unless specifically documented, have the + same semantics as their counterparts in + <a href="https://sqlite.org/c3ref/intro.html">the C API documentation</a>, + despite their signatures perhaps differing. The Java API adds a + number of overloads to simplify use, as well as a few Java-specific + functions, and those are never flagged as @Canonical. + + <p>In some cases, the canonical version of a function is private + and exposed to Java via public overloads. + + <p>In rare cases, the Java interface for a canonical function has a + different name than its C counterpart. For such cases, + (cname=the-C-side-name) is passed to this annotation and a + Java-side implementation with a slightly different signature is + added to with the canonical name. As of this writing, that applies + only to {@link org.sqlite.jni.SQLite3Jni#sqlite3_value_text_utf8} + and {@link org.sqlite.jni.SQLite3Jni#sqlite3_column_text_utf8}. + + <p>The comment property can be used to add a comment. +*/ +@java.lang.annotation.Documented +@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) +@java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) +public @interface Canonical{ + /** + Java functions which directly map to a canonical function but + change its name for some reason should not the original name + in this property. + */ + String cname() default ""/*doesn't allow null*/; + /** + Brief comments about the binding, e.g. noting any major + semantic differences. + */ + String comment() default ""; +} 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 000000000..99eae7370 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/annotation/NotNull.java @@ -0,0 +1,26 @@ +package org.sqlite.jni.annotation; + +/** + This annotation is for flagging parameters which may not legally be + null. When used in the context of callback methods which are + called into from the C APIs, this annotation communicates that the + C API will never pass a null value to the callback. + + <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>This annotation is informational only. No policy is in place to + programmatically ensure that NotNull is conformed to in client + code. + + <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. +*/ +@java.lang.annotation.Documented +@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) +@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER) +public @interface NotNull{} diff --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 000000000..7a011e33b --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/annotation/Nullable.java @@ -0,0 +1,19 @@ +package org.sqlite.jni.annotation; + +/** + This annotation is for flagging parameters which may legally be + null, noting that they may behave differently if passed null but + are prepared to expect null as a value. When used in the context of + callback methods which are called into from the C APIs, this + annotation communicates that the C API may pass a null value to the + callback. + + <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. +*/ +@java.lang.annotation.Documented +@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) +@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER) +public @interface Nullable{} diff --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 000000000..50db2a32b --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/annotation/package-info.java @@ -0,0 +1,4 @@ +/** + This package houses annotations specific a JNI binding to the SQLite3 C API. +*/ +package org.sqlite.jni.annotation; diff --git a/ext/jni/src/org/sqlite/jni/fts5_api.java b/ext/jni/src/org/sqlite/jni/fts5_api.java index 43b3d62de..92ca7c669 100644 --- a/ext/jni/src/org/sqlite/jni/fts5_api.java +++ b/ext/jni/src/org/sqlite/jni/fts5_api.java @@ -12,6 +12,7 @@ ** This file is part of the JNI bindings for the sqlite3 C API. */ package org.sqlite.jni; +import org.sqlite.jni.annotation.*; /** INCOMPLETE AND COMPLETELY UNTESTED. diff --git a/ext/jni/src/org/sqlite/jni/fts5_tokenizer.java b/ext/jni/src/org/sqlite/jni/fts5_tokenizer.java index 097a0cc05..053434e26 100644 --- a/ext/jni/src/org/sqlite/jni/fts5_tokenizer.java +++ b/ext/jni/src/org/sqlite/jni/fts5_tokenizer.java @@ -12,6 +12,7 @@ ** This file is part of the JNI bindings for the sqlite3 C API. */ package org.sqlite.jni; +import org.sqlite.jni.annotation.*; /** INCOMPLETE AND COMPLETELY UNTESTED. @@ -30,7 +31,7 @@ public final class fts5_tokenizer extends NativePointerHolder<fts5_tokenizer> { public native int xTokenize(@NotNull Fts5Tokenizer t, int tokFlags, @NotNull byte pText[], - @NotNull Fts5.xTokenizeCallback callback); + @NotNull Fts5.xTokenize_callback callback); // int (*xTokenize)(Fts5Tokenizer*, diff --git a/ext/jni/src/org/sqlite/jni/package-info.java b/ext/jni/src/org/sqlite/jni/package-info.java new file mode 100644 index 000000000..2ca997955 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/package-info.java @@ -0,0 +1,49 @@ +/** + This package houses a JNI binding to the SQLite3 C API. + + <p>The docs are in progress. + + <p>The primary interfaces are in {@link org.sqlite.jni.SQLite3Jni}. + + <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. + + <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: + + <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>When built with SQLITE_THREADSAFE=0 then no threading guarantees + are provided and multi-threaded use of the library will provoke + undefined behavior. + +*/ +package org.sqlite.jni; diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_context.java b/ext/jni/src/org/sqlite/jni/sqlite3_context.java index a61ff21c7..b9f11d733 100644 --- a/ext/jni/src/org/sqlite/jni/sqlite3_context.java +++ b/ext/jni/src/org/sqlite/jni/sqlite3_context.java @@ -18,34 +18,43 @@ package org.sqlite.jni; SQL functions (a.k.a. UDFs). */ public final class sqlite3_context extends NativePointerHolder<sqlite3_context> { - /** - For use only by the JNI layer. It's permitted to set this even - though it's private. - */ - private long aggregateContext = 0; + 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 "matching sets" of a UDF's callbacks, - such that all calls into those callbacks can determine which "set" - of those calls they belong to. + 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. - If this object is being used in the context of an aggregate or + <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 may be re-used by future invocations of the UDF in different - SQL statements. + and must not be re-used by future invocations of the UDF in + different SQL statements. - Consider this SQL, where MYFUNC is a user-defined aggregate function: + <p>Consider this SQL, where MYFUNC is a user-defined aggregate function: + <pre>{@code SELECT MYFUNC(A), MYFUNC(B) FROM T; + }</pre> - The xStep() and xFinal() methods of the callback need to be able + <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 @@ -53,14 +62,18 @@ public final class sqlite3_context extends NativePointerHolder<sqlite3_context> key for mapping callback invocations to whatever client-defined state is needed by the UDF. - There is one case where this will return 0 in the context of an - aggregate or window function: if the result set has no rows, - the UDF's xFinal() will be called without any other x...() members - having been called. In that one case, no aggregate context key will - have been generated. xFinal() implementations need to be prepared to - accept that condition as legal. + <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 long getAggregateContext(){ - return aggregateContext; + public synchronized Long getAggregateContext(boolean initIfNeeded){ + if( aggregateContext==null ){ + aggregateContext = SQLite3Jni.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/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index ffdb867d9..e355fe760 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -152,12 +152,12 @@ class Outer { /** This class provides an application which aims to implement the rudimentary SQL-driven test tool described in the accompanying - test-script-interpreter.md. + {@code test-script-interpreter.md}. - This is a work in progress. + <p>This is a work in progress. - An instance of this application provides a core set of services + <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. @@ -250,14 +250,14 @@ public class SQLTester { } public void runTests() throws Exception { - final long tStart = System.nanoTime(); + 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.nanoTime(); + final long timeStart = System.currentTimeMillis(); try{ ts.run(this); }catch(SQLTesterException e){ @@ -267,14 +267,14 @@ public class SQLTester { if( keepGoing ) outln("Continuing anyway becaure of the keep-going option."); else if( e.isFatal() ) throw e; }finally{ - final long timeEnd = System.nanoTime(); + final long timeEnd = System.currentTimeMillis(); outln("🏁",(threw ? "❌" : "✅")," ",nTest," test(s) in ", - ((timeEnd-timeStart)/1000000.0),"ms."); + (timeEnd-timeStart),"ms."); //ts.getFilename()); } } - final long tEnd = System.nanoTime(); - outln("Total run-time: ",((tEnd-tStart)/1000000.0),"ms"); + final long tEnd = System.currentTimeMillis(); + outln("Total run-time: ",(tEnd-tStart),"ms"); Util.unlink(initialDbName); } @@ -609,9 +609,9 @@ public class SQLTester { } t.addTestScript(a); } - final AutoExtension ax = new AutoExtension() { + final AutoExtensionCallback ax = new AutoExtensionCallback() { private final SQLTester tester = t; - public int xEntryPoint(sqlite3 db){ + @Override public int call(sqlite3 db){ final String init = tester.getDbInitSql(); if( !init.isEmpty() ){ tester.execSql(db, true, ResultBufferMode.NONE, null, init); @@ -629,7 +629,7 @@ public class SQLTester { t.outln("Aborted ",t.nAbortedScript," script(s)."); } if( dumpInternals ){ - sqlite3_do_something_for_developer(); + sqlite3_jni_internal_details(); } } } diff --git a/ext/misc/decimal.c b/ext/misc/decimal.c index 06286f23f..9365ae68b 100644 --- a/ext/misc/decimal.c +++ b/ext/misc/decimal.c @@ -642,7 +642,7 @@ static Decimal *decimalFromDouble(double r){ /* ** SQL Function: decimal(X) -** OR: decimal_sci(X) +** OR: decimal_exp(X) ** ** Convert input X into decimal and then back into text. ** @@ -650,7 +650,7 @@ static Decimal *decimalFromDouble(double r){ ** point value is done. Or if X is an 8-byte blob, it is interpreted ** as a float and similarly expanded. ** -** The decimal_sci(X) function returns the result in scientific notation. +** The decimal_exp(X) function returns the result in exponential notation. ** decimal(X) returns a complete decimal, without the e+NNN at the end. */ static void decimalFunc( @@ -853,7 +853,7 @@ int sqlite3_decimal_init( void (*xFunc)(sqlite3_context*,int,sqlite3_value**); } aFunc[] = { { "decimal", 1, 0, decimalFunc }, - { "decimal_sci", 1, 1, decimalFunc }, + { "decimal_exp", 1, 1, decimalFunc }, { "decimal_cmp", 2, 0, decimalCmpFunc }, { "decimal_add", 2, 0, decimalAddFunc }, { "decimal_sub", 2, 0, decimalSubFunc }, diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c index c89fdbf13..4e85cc8ae 100644 --- a/ext/rtree/rtree.c +++ b/ext/rtree/rtree.c @@ -1919,7 +1919,7 @@ static int rtreeFilter( #else p->u.rValue = (double)iVal; if( iVal>=((sqlite3_int64)1)<<48 - || -iVal>=((sqlite3_int64)1)<<48 + || iVal<=-(((sqlite3_int64)1)<<48) ){ if( p->op==RTREE_LT ) p->op = RTREE_LE; if( p->op==RTREE_GT ) p->op = RTREE_GE; diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile index 080c42704..c0cab212d 100644 --- a/ext/wasm/GNUmakefile +++ b/ext/wasm/GNUmakefile @@ -456,13 +456,6 @@ emcc.exportedRuntimeMethods := \ emcc.jsflags += $(emcc.exportedRuntimeMethods) emcc.jsflags += -sUSE_CLOSURE_COMPILER=0 emcc.jsflags += -sIMPORTED_MEMORY -#emcc.jsflags += -sASYNCIFY=2 -# ^^^ ASYNCIFY=2 is for experimental JSPI support -# (https://v8.dev/blog/jspi), but enabling it causes the lib-level -# init code to throw inexplicable complaints about C-level function -# signatures not matching what we expect them to be. JSPI requires, as of -# this writing, requires an experimental Chrome flag: -# chrome://flags/#enable-experimental-webassembly-stack-switching emcc.jsflags += -sSTRICT_JS=0 # STRICT_JS disabled due to: # https://github.com/emscripten-core/emscripten/issues/18610 @@ -845,11 +838,11 @@ sqlite3-worker1-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-bundler-frien sqlite3-worker1-promiser-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-promiser-bundler-friendly.js $(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1.js))) $(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1-bundler-friendly.js),\ - $(c-pp.D.bundler-friendly))) + $(c-pp.D.sqlite3-bundler-friendly))) $(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),$(sqlite3-worker1-promiser.js))) $(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),\ $(sqlite3-worker1-promiser-bundler-friendly.js),\ - $(c-pp.D.bundler-friendly))) + $(c-pp.D.sqlite3-bundler-friendly))) $(sqlite3-bundler-friendly.mjs): $(sqlite3-worker1-bundler-friendly.js) \ $(sqlite3-worker1-promiser-bundler-friendly.js) $(sqlite3.js) $(sqlite3.mjs): $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js) diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index ca5d1c44f..5abb13b99 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -772,8 +772,43 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( isSharedTypedArray, toss: function(...args){throw new Error(args.join(' '))}, toss3, - typedArrayPart - }; + typedArrayPart, + /** + Given a byte array or ArrayBuffer, this function throws if the + lead bytes of that buffer do not hold a SQLite3 database header, + else it returns without side effects. + + Added in 3.44. + */ + affirmDbHeader: function(bytes){ + if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes); + const header = "SQLite format 3"; + if( header.length > bytes.byteLength ){ + toss3("Input does not contain an SQLite3 database header."); + } + for(let i = 0; i < header.length; ++i){ + if( header.charCodeAt(i) !== bytes[i] ){ + toss3("Input does not contain an SQLite3 database header."); + } + } + }, + /** + Given a byte array or ArrayBuffer, this function throws if the + database does not, at a cursory glance, appear to be an SQLite3 + database. It only examines the size and header, but further + checks may be added in the future. + + Added in 3.44. + */ + affirmIsDb: function(bytes){ + if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes); + const n = bytes.byteLength; + if(n<512 || n%512!==0) { + toss3("Byte array size",n,"is invalid for an SQLite3 db."); + } + util.affirmDbHeader(bytes); + } + }/*util*/; Object.assign(wasm, { /** diff --git a/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js index 709d3414c..8e874f728 100644 --- a/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js +++ b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js @@ -59,6 +59,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ const toss3 = sqlite3.util.toss3; const initPromises = Object.create(null); const capi = sqlite3.capi; + const util = sqlite3.util; const wasm = sqlite3.wasm; // Config opts for the VFS... const SECTOR_SIZE = 4096; @@ -869,9 +870,48 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ return b; } + //! Impl for importDb() when its 2nd arg is a function. + async importDbChunked(name, callback){ + const sah = this.#mapFilenameToSAH.get(name) + || this.nextAvailableSAH() + || toss("No available handles to import to."); + sah.truncate(0); + let nWrote = 0, chunk, checkedHeader = false, err = false; + try{ + while( undefined !== (chunk = await callback()) ){ + if(chunk instanceof ArrayBuffer) chunk = new Uint8Array(chunk); + if( 0===nWrote && chunk.byteLength>=15 ){ + util.affirmDbHeader(chunk); + checkedHeader = true; + } + sah.write(chunk, {at: HEADER_OFFSET_DATA + nWrote}); + nWrote += chunk.byteLength; + } + if( nWrote < 512 || 0!==nWrote % 512 ){ + toss("Input size",nWrote,"is not correct for an SQLite database."); + } + if( !checkedHeader ){ + const header = new Uint8Array(20); + sah.read( header, {at: 0} ); + util.affirmDbHeader( header ); + } + sah.write(new Uint8Array(2), { + at: HEADER_OFFSET_DATA + 18 + }/*force db out of WAL mode*/); + }catch(e){ + this.setAssociatedPath(sah, '', 0); + } + this.setAssociatedPath(sah, name, capi.SQLITE_OPEN_MAIN_DB); + return nWrote; + } + //! Documented elsewhere in this file. importDb(name, bytes){ - if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes); + if( bytes instanceof ArrayBuffer ) bytes = new Uint8Array(bytes); + else if( bytes instanceof Function ) return this.importDbChunked(name, bytes); + const sah = this.#mapFilenameToSAH.get(name) + || this.nextAvailableSAH() + || toss("No available handles to import to."); const n = bytes.byteLength; if(n<512 || n%512!=0){ toss("Byte array size is invalid for an SQLite db."); @@ -882,16 +922,16 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ toss("Input does not contain an SQLite database header."); } } - const sah = this.#mapFilenameToSAH.get(name) - || this.nextAvailableSAH() - || toss("No available handles to import to."); const nWrote = sah.write(bytes, {at: HEADER_OFFSET_DATA}); if(nWrote != n){ this.setAssociatedPath(sah, '', 0); toss("Expected to write "+n+" bytes but wrote "+nWrote+"."); }else{ + sah.write(new Uint8Array([0,0]), {at: HEADER_OFFSET_DATA+18} + /* force db out of WAL mode */); this.setAssociatedPath(sah, name, capi.SQLITE_OPEN_MAIN_DB); } + return nWrote; } }/*class OpfsSAHPool*/; @@ -1098,6 +1138,19 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ automatically clean up any non-database files so importing them is pointless. + If passed a function for its second argument, its behavior + changes to asynchronous and it imports its data in chunks fed to + it by the given callback function. It calls the callback (which + may be async) repeatedly, expecting either a Uint8Array or + ArrayBuffer (to denote new input) or undefined (to denote + EOF). For so long as the callback continues to return + non-undefined, it will append incoming data to the given + VFS-hosted database file. The result of the resolved Promise when + called this way is the size of the resulting database. + + On succes this routine rewrites the database header bytes in the + output file (not the input array) to force disabling of WAL mode. + On a write error, the handle is removed from the pool and made available for re-use. diff --git a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js index 93482505a..ca2fde985 100644 --- a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js +++ b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js @@ -136,6 +136,7 @@ const installOpfsVfs = function callee(options){ const error = (...args)=>logImpl(0, ...args); const toss = sqlite3.util.toss; const capi = sqlite3.capi; + const util = sqlite3.util; const wasm = sqlite3.wasm; const sqlite3_vfs = capi.sqlite3_vfs; const sqlite3_file = capi.sqlite3_file; @@ -1169,39 +1170,100 @@ const installOpfsVfs = function callee(options){ }; /** + impl of importDb() when it's given a function as its second + argument. + */ + const importDbChunked = async function(filename, callback){ + const [hDir, fnamePart] = await opfsUtil.getDirForFilename(filename, true); + const hFile = await hDir.getFileHandle(fnamePart, {create:true}); + let sah = await hFile.createSyncAccessHandle(); + let nWrote = 0, chunk, checkedHeader = false, err = false; + try{ + sah.truncate(0); + while( undefined !== (chunk = await callback()) ){ + if(chunk instanceof ArrayBuffer) chunk = new Uint8Array(chunk); + if( 0===nWrote && chunk.byteLength>=15 ){ + util.affirmDbHeader(chunk); + checkedHeader = true; + } + sah.write(chunk, {at: nWrote}); + nWrote += chunk.byteLength; + } + if( nWrote < 512 || 0!==nWrote % 512 ){ + toss("Input size",nWrote,"is not correct for an SQLite database."); + } + if( !checkedHeader ){ + const header = new Uint8Array(20); + sah.read( header, {at: 0} ); + util.affirmDbHeader( header ); + } + sah.write(new Uint8Array(2), {at: 18}/*force db out of WAL mode*/); + return nWrote; + }catch(e){ + await sah.close(); + sah = undefined; + await hDir.removeEntry( fnamePart ).catch(()=>{}); + throw e; + }finally { + if( sah ) await sah.close(); + } + }; + + /** Asynchronously imports the given bytes (a byte array or ArrayBuffer) into the given database file. + If passed a function for its second argument, its behaviour + changes to async and it imports its data in chunks fed to it by + the given callback function. It calls the callback (which may + be async) repeatedly, expecting either a Uint8Array or + ArrayBuffer (to denote new input) or undefined (to denote + EOF). For so long as the callback continues to return + non-undefined, it will append incoming data to the given + VFS-hosted database file. When called this way, the resolved + value of the returned Promise is the number of bytes written to + the target file. + It very specifically requires the input to be an SQLite3 database and throws if that's not the case. It does so in order to prevent this function from taking on a larger scope than it is specifically intended to. i.e. we do not want it to become a convenience for importing arbitrary files into OPFS. - Throws on error. Resolves to the number of bytes written. + This routine rewrites the database header bytes in the output + file (not the input array) to force disabling of WAL mode. + + On error this throws and the state of the input file is + undefined (it depends on where the exception was triggered). + + On success, resolves to the number of bytes written. */ opfsUtil.importDb = async function(filename, bytes){ + if( bytes instanceof Function ){ + return importDbChunked(filename, bytes); + } if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes); + util.affirmIsDb(bytes); const n = bytes.byteLength; - if(n<512 || n%512!=0){ - toss("Byte array size is invalid for an SQLite db."); - } - const header = "SQLite format 3"; - for(let i = 0; i < header.length; ++i){ - if( header.charCodeAt(i) !== bytes[i] ){ - toss("Input does not contain an SQLite database header."); - } - } const [hDir, fnamePart] = await opfsUtil.getDirForFilename(filename, true); - const hFile = await hDir.getFileHandle(fnamePart, {create:true}); - const sah = await hFile.createSyncAccessHandle(); - sah.truncate(0); - const nWrote = sah.write(bytes, {at: 0}); - sah.close(); - if(nWrote != n){ - toss("Expected to write "+n+" bytes but wrote "+nWrote+"."); + let sah, err, nWrote = 0; + try { + const hFile = await hDir.getFileHandle(fnamePart, {create:true}); + sah = await hFile.createSyncAccessHandle(); + sah.truncate(0); + nWrote = sah.write(bytes, {at: 0}); + if(nWrote != n){ + toss("Expected to write "+n+" bytes but wrote "+nWrote+"."); + } + sah.write(new Uint8Array(2), {at: 18}) /* force db out of WAL mode */; + return nWrote; + }catch(e){ + if( sah ){ await sah.close(); sah = undefined; } + await hDir.removeEntry( fnamePart ).catch(()=>{}); + throw e; + }finally{ + if( sah ) await sah.close(); } - return nWrote; }; if(sqlite3.oo1){ diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index fcfbc0692..ff15e3b4f 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -352,7 +352,9 @@ int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){ if( db!=0 ){ if( 0!=zMsg ){ const int nMsg = sqlite3Strlen30(zMsg); + sqlite3_mutex_enter(sqlite3_db_mutex(db)); sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg); + sqlite3_mutex_leave(sqlite3_db_mutex(db)); }else{ sqlite3ErrorWithMsg(db, err_code, NULL); } diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js index bd945dcab..f694598ea 100644 --- a/ext/wasm/tester1.c-pp.js +++ b/ext/wasm/tester1.c-pp.js @@ -2939,8 +2939,27 @@ globalThis.sqlite3InitModule = sqlite3InitModule; let db; try { const exp = this.opfsDbExport; + const filename = this.opfsDbFile; delete this.opfsDbExport; - this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(this.opfsDbFile, exp); + this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(filename, exp); + db = new sqlite3.oo1.OpfsDb(this.opfsDbFile); + T.assert(6 === db.selectValue('select count(*) from p')). + assert( this.opfsImportSize == exp.byteLength ); + db.close(); + this.opfsUnlink(filename); + T.assert(!(await sqlite3.opfs.entryExists(filename))); + // Try again with a function as an input source: + let cursor = 0; + const blockSize = 512, end = exp.byteLength; + const reader = async function(){ + if(cursor >= exp.byteLength){ + return undefined; + } + const rv = exp.subarray(cursor, cursor+blockSize>end ? end : cursor+blockSize); + cursor += blockSize; + return rv; + }; + this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(filename, reader); db = new sqlite3.oo1.OpfsDb(this.opfsDbFile); T.assert(6 === db.selectValue('select count(*) from p')). assert( this.opfsImportSize == exp.byteLength ); @@ -3059,8 +3078,9 @@ globalThis.sqlite3InitModule = sqlite3InitModule; const dbytes = u1.exportFile(dbName); T.assert(dbytes.length >= 4096); const dbName2 = '/exported.db'; - u1.importDb(dbName2, dbytes); - T.assert( 2 == u1.getFileCount() ); + let nWrote = u1.importDb(dbName2, dbytes); + T.assert( 2 == u1.getFileCount() ) + .assert( dbytes.byteLength == nWrote ); let db2 = new u1.OpfsSAHPoolDb(dbName2); T.assert(db2 instanceof sqlite3.oo1.DB) .assert(3 === db2.selectValue('select count(*) from t')); @@ -3069,6 +3089,25 @@ globalThis.sqlite3InitModule = sqlite3InitModule; .assert(false === u1.unlink(dbName2)) .assert(1 === u1.getFileCount()) .assert(1 === u1.getFileNames().length); + // Try again with a function as an input source: + let cursor = 0; + const blockSize = 1024, end = dbytes.byteLength; + const reader = async function(){ + if(cursor >= dbytes.byteLength){ + return undefined; + } + const rv = dbytes.subarray(cursor, cursor+blockSize>end ? end : cursor+blockSize); + cursor += blockSize; + return rv; + }; + nWrote = await u1.importDb(dbName2, reader); + T.assert( 2 == u1.getFileCount() ); + db2 = new u1.OpfsSAHPoolDb(dbName2); + T.assert(db2 instanceof sqlite3.oo1.DB) + .assert(3 === db2.selectValue('select count(*) from t')); + db2.close(); + T.assert(true === u1.unlink(dbName2)) + .assert(dbytes.byteLength == nWrote); } T.assert(true === u1.unlink(dbName)) @@ -917,11 +917,11 @@ fulltestonly: $(TESTPROGS) fuzztest queryplantest: testfixture$(EXE) sqlite3$(EXE) ./testfixture$(EXE) $(TOP)/test/permutations.test queryplanner $(TESTOPTS) -fuzztest: fuzzcheck$(EXE) $(FUZZDATA) sessionfuzz$(EXE) $(TOP)/test/sessionfuzz-data1.db +fuzztest: fuzzcheck$(EXE) $(FUZZDATA) sessionfuzz$(EXE) ./fuzzcheck$(EXE) $(FUZZDATA) ./sessionfuzz run $(TOP)/test/sessionfuzz-data1.db -valgrindfuzz: fuzzcheck$(EXE) $(FUZZDATA) sessionfuzz$(EXE) $(TOP)/test/sessionfuzz-data1.db +valgrindfuzz: fuzzcheck$(EXE) $(FUZZDATA) sessionfuzz$(EXE) valgrind ./fuzzcheck$(EXE) --cell-size-check --limit-mem 10M $(FUZZDATA) valgrind ./sessionfuzz run $(TOP)/test/sessionfuzz-data1.db @@ -1,12 +1,12 @@ -C Add\sSQLITE_EXTRA_AUTOEXT,\ssimilar\sto\sSQLITE_EXTRA_INIT\sbut\sadds\sa\sbuiltin\sauto-extension\sprovided\sby\sthe\sclient.\sSuggestion\sfrom\s[forum:00829394c74a670f|\sforum\spost\s00829394c74a670f]. -D 2023-08-17T09:49:53.877 +C Add\ssupport\sfor\sthe\s-DSQLITE_EXTRA_AUTOEXT=name\scompile-time\soption.\n[forum:/forumpost/00829394c74a670f|forum\sthread\s00829394c74a670f]. +D 2023-08-28T15:58:00.980 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 -F Makefile.in 6f391d54fa01f8a49b024fef5cce1ab8234c281164641cf9a52694b432bdec1b +F Makefile.in 577177569fa57e613b74f60d66b03cd16e4326439dd62fd5b9690f5b83c34bf0 F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6 -F Makefile.msc e0e2630b327b31232f4f1c748cae8af358f897e88cae7479218369d1d510f94d -F README.md 093d7054271141a0a8518558e3d49087cb71f84d33b50ee10053946ed85dcac8 +F Makefile.msc 26c2d196391a285c279adb10fd6001774d9b243af94b700b681e4a49cd476684 +F README.md 963d30019abf0cc06b263cd2824bce022893f3f93a531758f6f04ff2194a16a8 F VERSION c6366dc72582d3144ce87b013cc35fe48d62f6d07d5be0c9716ea33c862144aa F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 @@ -15,14 +15,14 @@ F art/sqlite370.jpg d512473dae7e378a67e28ff96a34da7cb331def2 F autoconf/INSTALL 83e4a25da9fd053c7b3665eaaaf7919707915903 F autoconf/Makefile.am adedc1324b6a87fdd1265ddd336d2fb7d4f36a0e77b86ea553ae7cc4ea239347 F autoconf/Makefile.fallback 22fe523eb36dfce31e0f6349f782eb084e86a5620b2b0b4f84a2d6133f53f5ac -F autoconf/Makefile.msc 012cdd820963653a7db147a185ebe9085756b6ab15ac964e8cc1dae4c29485cd +F autoconf/Makefile.msc 3248809e70cf439a13e9faf82a4e12cbdb7b042006300ac67175fc5125b5c031 F autoconf/README.first 6c4f34fe115ff55d4e8dbfa3cecf04a0188292f7 F autoconf/README.txt 42cfd21d0b19dc7d5d85fb5c405c5f3c6a4c923021c39128f6ba685355d8fd56 F autoconf/configure.ac ec7fa914c5e74ff212fe879f9bb6918e1234497e05facfb641f30c4d5893b277 F autoconf/tea/Makefile.in 106a96f2f745d41a0f6193f1de98d7355830b65d45032c18cd7c90295ec24196 F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873 F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43 -F autoconf/tea/configure.ac e6a7f2abf7a94b5dae00713ad6b3bebc59b878e2705607589830792c61d2ca38 +F autoconf/tea/configure.ac fce5eca183cf47853f7d5b81c1e7bfa41c021fb460939ec67ef83653ffc6a531 F autoconf/tea/doc/sqlite3.n e1fe45d4f5286ee3d0ccc877aca2a0def488e9bb F autoconf/tea/license.terms 13bd403c9610fd2b76ece0ab50c4c5eda933d523 F autoconf/tea/pkgIndex.tcl.in b9eb6dd37f64e08e637d576b3c83259814b9cddd78bec4af2e5abfc6c5c750ce @@ -37,10 +37,11 @@ F configure 9dc3300339f4d6b3c3b108de60cc6ae6b3c547e25c7e6df280b4775db4de3a1b x F configure.ac 4654d32ac0a0d0b48f1e1e79bdc3d777b723cf2f63c33eb1d7c4ed8b435938e8 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd -F doc/compile-for-windows.md a342ac53d5d38fc220cc643c4e0a8c0966bd6caa569f3d927cf10844f5f0aaca +F doc/compile-for-windows.md c52f2903f1cb11b2308798feecca2e44701b037b78f467a538ac5c46c28ee250 F doc/json-enhancements.md e356fc834781f1f1aa22ee300027a270b2c960122468499bf347bb123ce1ea4f -F doc/lemon.html d2862dbef72496e87f7996f37e814b146848190a742c12161d13fd15346051b0 +F doc/lemon.html 44a53a1d2b42d7751f7b2f478efb23c978e258d794bfd172442307a755b9fa44 F doc/pager-invariants.txt 27fed9a70ddad2088750c4a2b493b63853da2710 +F doc/testrunner.md 2434864be2219d4f0b6ffc99d0a2172d531c4ca4345340776f67ad4edd90dc90 F doc/trusted-schema.md 33625008620e879c7bcfbbfa079587612c434fa094d338b08242288d358c3e8a F doc/vdbesort-memory.md 4da2639c14cd24a31e0af694b1a8dd37eaf277aff3867e9a8cc14046bc49df56 F doc/vfs-shm.txt e101f27ea02a8387ce46a05be2b1a902a021d37a @@ -86,14 +87,14 @@ F ext/fts3/unicode/UnicodeData.txt cd07314edb62d49fde34debdaf92fa2aa69011e7 F ext/fts3/unicode/mkunicode.tcl d5aebf022fa4577ee8cdf27468f0d847879993959101f6dbd6348ef0cfc324a7 F ext/fts3/unicode/parseunicode.tcl a981bd6466d12dd17967515801c3ff23f74a281be1a03cf1e6f52a6959fc77eb F ext/fts5/extract_api_docs.tcl a36e54ec777172ddd3f9a88daf593b00848368e0 -F ext/fts5/fts5.h 9bebc9fb8b75b0777e741c758540e07c3c80ce75204198979028fe6cc4c59486 +F ext/fts5/fts5.h 05501612cc655504c5dce8ba765ab621d50fc478490089beaa0d75e00b23e520 F ext/fts5/fts5Int.h 78a63cc0795186cde5384816a9403a68c65774b35d952e05b81a1b4b158e07c8 F ext/fts5/fts5_aux.c 572d5ec92ba7301df2fea3258576332f2f4d2dfd66d8263afd157d9deceac480 F ext/fts5/fts5_buffer.c 3001fbabb585d6de52947b44b455235072b741038391f830d6b729225eeaf6a5 F ext/fts5/fts5_config.c 054359543566cbff1ba65a188330660a5457299513ac71c53b3a07d934c7b081 F ext/fts5/fts5_expr.c bd3b81ce669c4104e34ffe66570af1999a317b142c15fccb112de9fb0caa57a6 F ext/fts5/fts5_hash.c 65e7707bc8774706574346d18c20218facf87de3599b995963c3e6d6809f203d -F ext/fts5/fts5_index.c 002d674561fcba0813fe4959f3bce166287cd0ff33bed44d8a2ede6cd23110f2 +F ext/fts5/fts5_index.c b484322421cbb421d22bb2cd304001b80596d671cd626367c8c806b889de4b42 F ext/fts5/fts5_main.c 7070031993ba5b5d89b13206ec4ef624895f2f7c0ec72725913d301e4d382445 F ext/fts5/fts5_storage.c 3c9b41fce41b6410f2e8f82eb035c6a29b2560483f773e6dc98cf3cb2e4ddbb5 F ext/fts5/fts5_tcl.c b1445cbe69908c411df8084a10b2485500ac70a9c747cdc8cda175a3da59d8ae @@ -202,13 +203,13 @@ F ext/fts5/test/fts5secure2.test 2e961d7eef939f294c56b5d895cac7f1c3a60b934ee2cfd F ext/fts5/test/fts5secure3.test c7e1080a6912f2a3ac68f2e05b88b72a99de38543509b2bbf427cac5c9c1c610 F ext/fts5/test/fts5secure4.test 0d10a80590c07891478700af7793b232962042677432b9846cf7fc8337b67c97 F ext/fts5/test/fts5secure5.test c07a68ced5951567ac116c22f2d2aafae497e47fe9fcb6a335c22f9c7a4f2c3a -F ext/fts5/test/fts5secure6.test 7a959d834be6725c641b3c3b38ef86570ea671216ad803e054e4fdff33a72ce2 +F ext/fts5/test/fts5secure6.test a0a28cfb9bf9721408b65b5d7c7ce369af3d688e273da24d101c25d60cdce05c F ext/fts5/test/fts5securefault.test dbca2b6a1c16700017f5051138991b705410889933f2a37c57ae8a23b296b10b F ext/fts5/test/fts5simple.test a298670508c1458b88ce6030440f26a30673931884eb5f4094ac1773b3ba217b F ext/fts5/test/fts5simple2.test 258a1b0c590409bfa5271e872c79572b319d2a56554d0585f68f146a0da603f0 F ext/fts5/test/fts5simple3.test d5c74a9d3ca71bd5dd5cacb7c55b86ea12cdddfc8b1910e3de2995206898380f F ext/fts5/test/fts5synonym.test 1651815b8008de170e8e600dcacc17521d765482ea8f074ae82cfa870d8bb7fb -F ext/fts5/test/fts5synonym2.test b54cce5c34ec08ed616f646635538ae82e34a0e28f947ec60b6fadbc4b3fb17a +F ext/fts5/test/fts5synonym2.test 8f891fc49cc1e8daed727051e77e1f42849c784a6a54bef82564761b2cb3e016 F ext/fts5/test/fts5tok1.test 1f7817499f5971450d8c4a652114b3d833393c8134e32422d0af27884ffe9cef F ext/fts5/test/fts5tok2.test dcacb32d4a2a3f0dd3215d4a3987f78ae4be21a2 F ext/fts5/test/fts5tokenizer.test ac3c9112b263a639fb0508ae73a3ee886bf4866d2153771a8e8a20c721305a43 @@ -232,43 +233,56 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 -F ext/jni/GNUmakefile 3deba6bc0bf37c1ee5f15d1ff3c3512ae2f3cf44a2b8ae7b4af92690514b0cb4 -F ext/jni/README.md 5c60e4580aa5c94ff74d7bef1fb6231e578f7764e831a07b5981b6ab62b35560 -F ext/jni/jar-dist.make 93da95f8fe01ef22fccacc27f2e805938058e91e8c72c0532558d3a812a42e74 -F ext/jni/src/c/sqlite3-jni.c bea6b8691a5fa3a8626a771757bb261208d3c5fc6598266d3b0ee23d88e35632 -F ext/jni/src/c/sqlite3-jni.h 28565de9efc971195c684095ba0d184b90401290698c987f7ea3f54e47ff4f2f -F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 -F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 -F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c -F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 -F ext/jni/src/org/sqlite/jni/CollationNeeded.java ad67843b6dd1c06b6b0a1dc72887b7c48e2a98042fcf6cacf14d42444037eab8 -F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a -F ext/jni/src/org/sqlite/jni/Fts5.java 13844685231e8b4840a706db3bed84d5dfcf15be0ae7e809eac40420dba24901 +F ext/jni/GNUmakefile 374873bf6d2cd6ceafb458e28b59140dbb074f01f7adddf7e15a3ee3daf44551 +F ext/jni/README.md 1332b1fa27918bd5d9ca2d0d4f3ac3a6ab86b9e3699dc5bfe32904a027f3d2a9 +F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa +F ext/jni/src/c/sqlite3-jni.c 0b76d7947e474aa30f6636f42a4e00eb48ccad6f920f1d2a1ee2d9540709c578 +F ext/jni/src/c/sqlite3-jni.h 12e1a5ef5ee1795dc22577c285b4518dfd8aa4af45757f6cb81a555d967bf201 +F ext/jni/src/org/sqlite/jni/AbstractCollationCallback.java 95e88ba04f4aac51ffec65693e878e234088b2f21b387f4e4285c8b72b33e436 +F ext/jni/src/org/sqlite/jni/AggregateFunction.java 7312486bc65fecdb91753c0a4515799194e031f45edbe16a6373cea18f404dc4 +F ext/jni/src/org/sqlite/jni/AuthorizerCallback.java d00a2409ab76cae168927e2ca6a7ffbd0621a42547cce88768b4eeebc13827e0 +F ext/jni/src/org/sqlite/jni/AutoExtensionCallback.java 1470e14d09f10729d35568506c6e61318edfb17aa322802e386764fa6d582f14 +F ext/jni/src/org/sqlite/jni/BusyHandlerCallback.java cd12c26dafd3e6c097fc73f80d328aebac0f58b985f66a96ee567ddf8d195f30 +F ext/jni/src/org/sqlite/jni/CollationCallback.java 7d5b246f1a7c9d6b8e974d970bbbb2d05c6264e65448d7be6a85edbf703c823d +F ext/jni/src/org/sqlite/jni/CollationNeededCallback.java 1707b50146c6b805b79e84f89a57c8dbb0134e431799f041f0bec403eca5f841 +F ext/jni/src/org/sqlite/jni/CommitHookCallback.java e4de82c97560982e996e358958268e1e4e307b6115cd9aac0ff4f947d4380d90 +F ext/jni/src/org/sqlite/jni/ConfigSqllogCallback.java e3656909eab7ed0f7e457c5b82df160ca22dd5e954c0a306ec1fca61b0d266b4 +F ext/jni/src/org/sqlite/jni/Fts5.java 3ebfbd5b95fdb9d7bc40306f2e682abd12e247d9224e92510b8dd103b4f96fe8 F ext/jni/src/org/sqlite/jni/Fts5Context.java 0a5a02047a6a1dd3e4a38b0e542a8dd2de365033ba30e6ae019a676305959890 -F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java 01f890105c6b7edbbad1c0f5635f783cea62c4b2ae694a71e76514a936ee03ec +F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java 2fd11abb7c5403318181d69bb7b702a79cba7ab460105140f5161bea9bc505d1 F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7babcd11a0c308a832b7940574259bcc F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 -F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee -F ext/jni/src/org/sqlite/jni/OutputPointer.java d81f8bd43d2296ae373692370cfad16ddde76f5c14cd2760f7b4e1113ef56d4c -F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc +F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 564087036449a16df148dcf0a067408bd251170bf23286c655f46b5f973e8b2d +F ext/jni/src/org/sqlite/jni/OutputPointer.java 4ae06135decef35eb04498daa2868939d91a294e948747c580ef9ce31563a6b3 +F ext/jni/src/org/sqlite/jni/PreupdateHookCallback.java 500c968b3893edbddf67e8eb773852c3a8ae58097a77bd22320ada6b1af06db1 +F ext/jni/src/org/sqlite/jni/ProgressHandlerCallback.java 0da841810319f5a9dc372d0f2348930d54fac1a4b53e4298884f44c720d67830 F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7ce7797f2c6c7fca2004ff12ce20f86 -F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 -F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 4b6fd22e04e63eb65d8e4e38fda39ecf15ce244d034607517627ce2e766e7e65 -F ext/jni/src/org/sqlite/jni/Tester1.java 4253dc7bcff64500a9388f1a17d3d39dbe4eb9d7db9fc035ce6e2380d45ad5fc -F ext/jni/src/org/sqlite/jni/TesterFts5.java 59e22dd24af033ea8827d36225a2f3297908fb6af8818ead8850c6c6847557b1 -F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d -F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d +F ext/jni/src/org/sqlite/jni/RollbackHookCallback.java 16042be9d072a26dbb2f1b1b63e7639989b747bb80d2bd667ba4f7555f56a825 +F ext/jni/src/org/sqlite/jni/SQLFunction.java 544a875d33fd160467d82e2397ac33157b29971d715a821a4fad3c899113ee8c +F ext/jni/src/org/sqlite/jni/SQLite3CallbackProxy.java c2748ab52856075b053a55b317988d95dc7fb4d3d42520f8c33573effe1cd185 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 440d64e8c4cff53bd3c0cc676381212489198302d7f1aaa535712c2d7163cc69 +F ext/jni/src/org/sqlite/jni/ScalarFunction.java 6d387bb499fbe3bc13c53315335233dbf6a0c711e8fa7c521683219b041c614c +F ext/jni/src/org/sqlite/jni/Tester1.java a9558165dbb085494705525ef28e41d337d8348bf44259ce1f77cad72547bfdf +F ext/jni/src/org/sqlite/jni/TesterFts5.java 6f135c60e24c89e8eecb9fe61dde0f3bb2906de668ca6c9186bcf34bdaf94629 +F ext/jni/src/org/sqlite/jni/TraceV2Callback.java 641926b05a772c2c05c842a81aa839053ba4a13b78ef04b402f5705d060c6246 +F ext/jni/src/org/sqlite/jni/UpdateHookCallback.java be2bc96ff4f56b3c1fd18ae7dba9b207b25b6c123b8a5fd2f7aaf3cc208d8b7d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee -F ext/jni/src/org/sqlite/jni/fts5_api.java 5198be71c162e3e0cb1f4962a7cdf0d7596e8af53f70c4af6db24aab8d53d9ba +F ext/jni/src/org/sqlite/jni/WindowFunction.java 488980f4dbb6bdd7067d6cb9c43e4075475e51c54d9b74a5834422654b126246 +F ext/jni/src/org/sqlite/jni/XDestroyCallback.java 95fb66353e62e4aca8d6ab60e8f14f9235bd10373c34db0a64f5f13f016f0471 +F ext/jni/src/org/sqlite/jni/annotation/Canonical.java e55b82c8259b617ff754ac493fd8b79602631d659b87a858b987540e4c4fdf56 +F ext/jni/src/org/sqlite/jni/annotation/NotNull.java d48ebd7ae6bbb78bd47d54431c85e1521c89b1d3864a2b6eafd9c0e1b2341457 +F ext/jni/src/org/sqlite/jni/annotation/Nullable.java 6f962a98c9a5c6e9d21c50ae8716b16bdfdc934a191608cbb7e12ea588ddb6af +F ext/jni/src/org/sqlite/jni/annotation/package-info.java f66bfb621c6494e67c03ed38a9e26a3bd6af99b9f9f6ef79556bcec30a025a22 +F ext/jni/src/org/sqlite/jni/fts5_api.java ee47f1837d32968f7bb62278c7504c0fb572a68ec107371b714578312e9f734b F ext/jni/src/org/sqlite/jni/fts5_extension_function.java ac825035d7d83fc7fd960347abfa6803e1614334a21533302041823ad5fc894c -F ext/jni/src/org/sqlite/jni/fts5_tokenizer.java e530b36e6437fcc500e95d5d75fbffe272bdea20d2fac6be2e1336c578fba98b +F ext/jni/src/org/sqlite/jni/fts5_tokenizer.java a92c2e55bda492e4c76d48ddc73369bcc0d5e8727940840f9339e3292ea58fa7 +F ext/jni/src/org/sqlite/jni/package-info.java 73f7821c240e4d116f164e87b613c5836b8a33ce2666967a29d9acb1ced7ca92 F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c388ec3bc1de547f098a0217158fc -F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 +F ext/jni/src/org/sqlite/jni/sqlite3_context.java 66ca95ce904044263a4aff684abe262d56f73e6b06bca6cf650761d79d7779ad F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1f1286428fab38dfefe328e72b5735f533b19af8dd17712dd3df7e044d21c8b8 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java bf350903abe04a9bed2d8a2a71692ed4291dbb4eece2d3329ed91d15b0321e6d F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e F ext/jni/src/tests/000-000-sanity.test cfe6dc1b950751d6096e3f5695becaadcdaa048bfe9567209d6eb676e693366d F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -334,7 +348,7 @@ F ext/misc/completion.c 6dafd7f4348eecc7be9e920d4b419d1fb2af75d938cd9c59a20cfe8b F ext/misc/compress.c 3354c77a7c8e86e07d849916000cdac451ed96500bfb5bd83b20eb61eee012c9 F ext/misc/csv.c ca8d6dafc5469639de81937cb66ae2e6b358542aba94c4f791910d355a8e7f73 F ext/misc/dbdump.c b8592f6f2da292c62991a13864a60d6c573c47a9cc58362131b9e6a64f823e01 -F ext/misc/decimal.c a61343b36672760e1d6d5b20a42cb52264db55bcd11d0a44e2e06e8ce23227e3 +F ext/misc/decimal.c 172cf81a8634e6a0f0bedaf71a8372fee63348cf5a3c4e1b78bb233c35889fdc F ext/misc/eval.c 04bc9aada78c888394204b4ed996ab834b99726fb59603b0ee3ed6e049755dc1 F ext/misc/explain.c 0086fab288d4352ea638cf40ac382aad3b0dc5e845a1ea829a694c015fd970fe F ext/misc/fileio.c 4e7f7cd30de8df4820c552f14af3c9ca451c5ffe1f2e7bef34d598a12ebfb720 @@ -452,7 +466,7 @@ F ext/repair/test/checkindex01.test b530f141413b587c9eb78ff734de6bb79bc3515c3350 F ext/repair/test/test.tcl 686d76d888dffd021f64260abf29a55c57b2cedfa7fc69150b42b1d6119aac3c F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 F ext/rtree/geopoly.c 971e0b5bd9adaf0811feb8c0842a310811159da10319eb0e74fdb42bf26b99ca -F ext/rtree/rtree.c fb36e05027505f2c0dab24564e1d58ca4b789a6dfa48cf51aeee570018cf4814 +F ext/rtree/rtree.c 6954f4a3ca51c2e3db35c52e0513f3520999eb7a967f3d53b71db7ebddd8b3a5 F ext/rtree/rtree.h 4a690463901cb5e6127cf05eb8e642f127012fd5003830dbc974eca5802d9412 F ext/rtree/rtree1.test 877d40b8b61b1f88cec9d4dc0ff8334f5b05299fac12a35141532e2881860e9d F ext/rtree/rtree2.test 9d9deddbb16fd0c30c36e6b4fdc3ee3132d765567f0f9432ee71e1303d32603d @@ -529,7 +543,7 @@ F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c -F ext/wasm/GNUmakefile d02f3c8798b754f68b1f6b422ccff894a10bf352fc9c4eb8945baeace1acac28 +F ext/wasm/GNUmakefile 0e362f3fc04eab6628cbe4f1e35f4ab4a200881f6b5f753b27fb45eabeddd9d2 F ext/wasm/README-dist.txt 6382cb9548076fca472fb3330bbdba3a55c1ea0b180ff9253f084f07ff383576 F ext/wasm/README.md a8a2962c3aebdf8d2104a9102e336c5554e78fc6072746e5daf9c61514e7d193 F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api d6a5078f48a5301ed17b9a30331075d9b2506e1360c1f0dee0c7816c10acd9ab @@ -544,14 +558,14 @@ F ext/wasm/api/pre-js.c-pp.js ad906703f7429590f2fbf5e6498513bf727a1a4f0ebfa057af F ext/wasm/api/sqlite3-api-cleanup.js d235ad237df6954145404305040991c72ef8b1881715d2a650dda7b3c2576d0e F ext/wasm/api/sqlite3-api-glue.js b65e546568f1dfb35205b9792feb5146a6323d71b55cda58e2ed30def6dd52f3 F ext/wasm/api/sqlite3-api-oo1.js 9678dc4d9a5d39632b6ffe6ea94a023119260815bf32f265bf5f6c36c9516db8 -F ext/wasm/api/sqlite3-api-prologue.js 5f283b096b98bfb1ee2f2201e7ff0489dff00e29e1030c30953bdb4f5b87f4bd +F ext/wasm/api/sqlite3-api-prologue.js ef6f67c5ea718490806e5e17d2644b8b2f6e6ba5284d23dc1fbfd14d401c1ab5 F ext/wasm/api/sqlite3-api-worker1.js 9f32af64df1a031071912eea7a201557fe39b1738645c0134562bb84e88e2fec F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89 F ext/wasm/api/sqlite3-opfs-async-proxy.js 8cf8a897726f14071fae6be6648125162b256dfb4f96555b865dbb7a6b65e379 F ext/wasm/api/sqlite3-v-helper.js 7daa0eab0a513a25b05e9abae7b5beaaa39209b3ed12f86aeae9ef8d2719ed25 -F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js abb69b5e008961026bf5ff433d7116cb046359af92a5daf73208af2e7ac80ae7 -F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js e04fc2fda6a0200ef80efdbb4ddfa0254453558adb17ec3a230f93d2bf1d711c -F ext/wasm/api/sqlite3-wasm.c d4d4c2b349b43b7b861e6d2994299630fb79e07573ea6b61e28e8071b7d16b61 +F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 561463ac5380e4ccf1839a1922e6d7a5585660f32e3b9701a270b78cd35566cf +F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js d9e62d42b86f7bb3143eb071628b24e2ba7dcc749e41a0e9d3e2451bfea1a6b6 +F ext/wasm/api/sqlite3-wasm.c 6773e949034369ddd2a1efdedc39b2808a10b7274b0769188905432e561feebe F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js bc06df0d599e625bde6a10a394e326dc68da9ff07fa5404354580f81566e591f F ext/wasm/api/sqlite3-worker1.c-pp.js da509469755035e919c015deea41b4514b5e84c12a1332e6cc8d42cb2cc1fb75 F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8 @@ -596,7 +610,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555 F ext/wasm/test-opfs-vfs.js f09266873e1a34d9bdb6d3981ec8c9e382f31f215c9fd2f9016d2394b8ae9b7b F ext/wasm/tester1-worker.html ebc4b820a128963afce328ecf63ab200bd923309eb939f4110510ab449e9814c F ext/wasm/tester1.c-pp.html 1c1bc78b858af2019e663b1a31e76657b73dc24bede28ca92fbe917c3a972af2 -F ext/wasm/tester1.c-pp.js 64eb0ee6e695d5638d0f758f31a0ca2231e627ca5d768de3d8b44f9f494de8d4 +F ext/wasm/tester1.c-pp.js 9e0f4da49f02753a73a5f931bfb9b1458175518daa3fec40b5ebdc06c285539c F ext/wasm/tests/opfs/concurrency/index.html 0802373d57034d51835ff6041cda438c7a982deea6079efd98098d3e42fbcbc1 F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2 @@ -604,7 +618,7 @@ F ext/wasm/wasmfs.make 8a4955882aaa0783b3f60a9484a1f0f3d8b6f775c0fcd17c082f31966 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0 -F main.mk e18c03071dbb9da424212ba2a1ea331ae0e8f6d7d51283b7ad610c52ea11229c +F main.mk 5536159f62058714c972b5bd325c8d5ecd5c9b00b385dd2e1cbc17da70a711b2 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 F mptest/crash01.test 61e61469e257df0850df4293d7d4d6c2af301421 @@ -637,7 +651,7 @@ F src/delete.c cb766727c78e715f9fb7ec8a7d03658ed2a3016343ca687acfcec9083cdca500 F src/expr.c 1affe0cc049683ef0ef3545d9b6901508556b0ef7e2892a344c3df6d7288d79d F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fkey.c a7fcbf7e66d14dbb73cf49f31489ebf66d0e6006c62b95246924a3bae9f37b36 -F src/func.c dd1ecd1be6aaa67c9fa723f841e05e1536314c6aaa0509e25289b310f64dbb9c +F src/func.c f480d46974ecc84fefdd429377158981b974e0e33d656f1b0e919ba7c4bdd390 F src/global.c 29f56a330ed9d1b5cd9b79ac0ca36f97ac3afc730ff8bfa987b0db9e559d684d F src/hash.c 9ee4269fb1d6632a6fecfb9479c93a1f29271bddbbaf215dd60420bcb80c7220 F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 @@ -672,9 +686,9 @@ F src/os_unix.c 2e8b12107f75d1bd16412f312b4c5d5103191807a37836d3b81beb26436ad81b F src/os_win.c 4a50a154aeebc66a1f8fb79c1ff6dd5fe3d005556533361e0d460d41cb6a45a8 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a F src/pager.c 993445a19b611d473ca007542ab3149840661a4c7e9f2d9e1ec008b7cc2abe78 -F src/pager.h 6e326bd05970a24dd28d41d3980b6964fbaa37b4da54a2c0d4e0c5bdb06ff187 +F src/pager.h f4d33fec8052603758792045493423b8871a996da2d0973927b7d36cd6070473 F src/parse.y aeb7760d41cfa86465e3adba506500c021597049fd55f82a30e5b7045862c28c -F src/pcache.c 4cd4a0043167da9ba7e19b4d179a0e6354e7fe32c16f781ecf9bf0a5ff63b40b +F src/pcache.c 040b165f30622a21b7a9a77c6f2e4877a32fb7f22d4c7f0d2a6fa6833a156a75 F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5 F src/pcache1.c 602acb23c471bb8d557a6f0083cc2be641d6cafcafa19e481eba7ef4c9ca0f00 F src/pragma.c 37b8fb02d090262280c86e1e2654bf59d8dbfbfe8dc6733f2b968a11374c095a @@ -685,7 +699,7 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 37953a5f36c60bea413c3c04efcd433b6177009f508ef2ace0494728912fe2e9 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c 5f545a2c8702d4d3430bbb188cfec47d6c122d899061ef00cbe56af14591c574 -F src/shell.c.in 0c3dd37ab2787a63f1e1c1b7160647c5d3276d9ac941890b2735a773a7495d27 +F src/shell.c.in 2f9be25294b68b07e7e81f0adcec4475aba6011b64f160e414efe226910c4d7b F src/sqlite.h.in 73a366c1c45d5ac9888cfe81c458826a44498531d106cfb4f328193ab5f6f17d F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 2f30b2671f4c03cd27a43f039e11251391066c97d11385f5f963bb40b03038ac @@ -750,9 +764,9 @@ F src/tokenize.c 23d9f4539880b40226254ad9072f4ecf12eb1902e62aea47aac29928afafcfd F src/treeview.c 1d52fbc4e97161e65858d36e3424ea6e3fc045dd8a679c82b4b9593dc30de3bd F src/trigger.c ad6ab9452715fa9a8075442e15196022275b414b9141b566af8cdb7a1605f2b0 F src/update.c 6904814dd62a7a93bbb86d9f1419c7f134a9119582645854ab02b36b676d9f92 -F src/upsert.c 5303dc6c518fa7d4b280ec65170f465c7a70b7ac2b22491598f6d0b4875b3145 +F src/upsert.c fa125a8d3410ce9a97b02cb50f7ae68a2476c405c76aa692d3acf6b8586e9242 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 -F src/util.c 24f4458bb7a57b0e1b2858484fd62ea83fe63dcb0bce21a96bf9fe31fd402038 +F src/util.c 81f6d47ecda50b87e87f86d0bf87aac213698b3eec0d95d4cbaea971794e2e25 F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104 F src/vdbe.c 346d848a0bf8128e3e3722c5406f4bde6c32d7093b93402c6f8e0718d19305c3 F src/vdbe.h 41485521f68e9437fdb7ec4a90f9d86ab294e9bb8281e33b235915e29122cfc0 @@ -766,8 +780,8 @@ F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf8 F src/vdbevtab.c 57fa8f56478e5b5cb558cb425e7878515e0a105c54f96f1d1bbf4b9433529254 F src/vtab.c 1ecf8c3745d29275688d583e12822fa984d421e0286b5ef50c137bc3bf6d7a64 F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 -F src/wal.c 3b068bc1db5d42d72b940c377b6fdb0e6d41bc106c5e99bfc40bbbe973e5f3e2 -F src/wal.h 04a9e53121d5076f2a173b0f2facb39d33047093fee71bd3bbe6b1f6f1f5fd4b +F src/wal.c 01e051a1e713d9eabdb25df38602837cec8f4c2cae448ce2cf6accc87af903e9 +F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2 F src/where.c b8917792f1e0dbfa28fb29e6cd3d560060d69667be0ba4c491cbc772363264f5 F src/whereInt.h c7d19902863beadec1d04e66aca39c0bcd60b74f05f0eaa7422c7005dfc5d51a @@ -986,7 +1000,7 @@ F test/dbpage.test fce29035c7566fd7835ec0f19422cb4b9c6944ce0e1b936ff8452443f92e8 F test/dbpagefault.test d9111a62f3601d3efc6841ace3940181937342d245f92a1cca6cba8206d4f58a F test/dbstatus.test 4a4221a883025ffd39696b3d1b3910b928fb097d77e671351acb35f3aed42759 F test/dbstatus2.test f5fe0afed3fa45e57cfa70d1147606c20d2ba23feac78e9a172f2fe8ab5b78ef -F test/decimal.test 18e7b4cb12e8d5c60d768b686ba52af3e1ca3ced4f870231f0476666fd9fab7e +F test/decimal.test ef731887b43ee32ef86e1c8fddb61a40789f988332c029c601dcf2c319277e9e F test/default.test 9687cfb16717e4b8238c191697c98be88c0b16e568dd5368cd9284154097ef50 F test/delete.test 2686e1c98d552ef37d79ad55b17b93fe96fad9737786917ce3839767f734c48f F test/delete2.test 3a03f2cca1f9a67ec469915cb8babd6485db43fa @@ -1156,7 +1170,7 @@ F test/fts4umlaut.test fcaca4471de7e78c9d1f7e8976e3e8704d7d8ad979d57a739d00f3f75 F test/fts4unicode.test 82a9c16b68ba2f358a856226bb2ee02f81583797bc4744061c54401bf1a0f4c9 F test/fts4upfrom.test f25835162c989dffd5e2ef91ec24c4848cc9973093e2d492d1c7b32afac1b49d F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d -F test/func.test 2acd982669b2c15ceccc046385b458e985f90c9ca58b14d0ef75eec79c32f45b +F test/func.test 719076723b13a6bfcff7b00b7987b1d7b06876ceb71e868b3d13d571d9b42cae F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f F test/func3.test 600a632c305a88f3946d38f9a51efe145c989b2e13bd2b2a488db47fe76bab6a F test/func4.test 2285fb5792d593fef442358763f0fd9de806eda47dbc7a5934df57ffdc484c31 @@ -1171,7 +1185,7 @@ F test/fuzz3.test 9c813e6613b837cb7a277b0383cd66bfa07042b4cf0317157c35852f30043c F test/fuzz4.test c229bcdb45518a89e1d208a21343e061503460ac69fae1539320a89f572eb634 F test/fuzz_common.tcl b7197de6ed1ee8250a4f82d67876f4561b42ee8cbbfc6160dcb66331bad3f830 F test/fuzz_malloc.test f348276e732e814802e39f042b1f6da6362a610af73a528d8f76898fde6b22f2 -F test/fuzzcheck.c 29a2f0237553375498f891c9487a2ef3267b47deecc5d5b4335fa37f904cb8d3 +F test/fuzzcheck.c 69b8549e112fb815931a8c14c7955a0c407ae91a79356eecb82458384f2cb989 F test/fuzzdata1.db 3e86d9cf5aea68ddb8e27c02d7dfdaa226347426c7eb814918e4d95475bf8517 F test/fuzzdata2.db 128b3feeb78918d075c9b14b48610145a0dd4c8d6f1ca7c2870c7e425f5bf31f F test/fuzzdata3.db c6586d3e3cef0fbc18108f9bb649aa77bfc38aba @@ -1273,7 +1287,7 @@ F test/json/README.md 63e3e589e1df8fd3cc1588ba1faaff659214003f8b77a15af5c6452b35 F test/json/json-generator.tcl dc0dd0f393800c98658fc4c47eaa6af29d4e17527380cd28656fb261bddc8a3f F test/json/json-q1.txt 65f9d1cdcc4cffa9823fb73ed936aae5658700cd001fde448f68bfb91c807307 F test/json/json-speed-check.sh 8b7babf530faa58bd59d6d362cec8e9036a68c5457ff46f3b1f1511d21af6737 x -F test/json101.test 243b0a2650218ac5eafde6ce2a92a0e9d02bf24f62aec68693b69d9a693f120a +F test/json101.test dc9d5a2a5b1fd1b54dbd71c538b17933cc98d84b4c1f821ead754933663dca55 F test/json102.test 24f6f204f9cde45b971016691d0b92a9b4c58040d699e36d6b12cb165f9083ff F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a8880112dbe F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1 @@ -1544,7 +1558,7 @@ F test/snapshot_up.test a0a29c4cf33475fcef07c3f8e64af795e24ab91b4cc68295863402a3 F test/soak.test 18944cf21b94a7fe0df02016a6ee1e9632bc4e8d095a0cb49d95e15d5cca2d5c F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087 F test/sort.test f86751134159abb5e5fd4381a0d7038c91013638cd1e3fa1d7850901f6df6196 -F test/sort2.test cc23b7c19d684657559e8a55b02f7fcee03851d0 +F test/sort2.test 2f8c66402a03adebe77ce7aafca129fbf32df27d6c9b8f7a9f1b958e39f56da8 F test/sort3.test 1480ed7c4c157682542224e05e3b75faf4a149e5 F test/sort4.test cca6f4b0b5255882645bbbe346a6a9f4a5c7b6a18513a6a7bf4ac1c4761ddc19 F test/sort5.test 6b43ae0e2169b5ceed441844492e55ba7f1ae0790528395ddf7888ab3094525d @@ -1603,8 +1617,8 @@ F test/temptable2.test 76821347810ecc88203e6ef0dd6897b6036ac788e9dd3e6b04fd4d163 F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637 F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc F test/tester.tcl 68454ef88508c196d19e8694daa27bff7107a91857799eaa12f417188ae53ede -F test/testrunner.tcl 56a744d4e6e516b2091c2ca6b7b27b9600e9ded136a2c860c350515511ebe20a -F test/testrunner_data.tcl 0f167aa9e9a640f2f19e5d99bc99016c236526a6a8eb36432e445df1983a8548 +F test/testrunner.tcl ccdfda84732cf8665bd8d3bfee79b80841e221459e5d00a632a3a5c758966e1f +F test/testrunner_data.tcl c448693eb6fdbadb78cb26f6253d4f335666f9836f988afa575de960b666b19f F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899 F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -1819,7 +1833,7 @@ F test/upfrom2.test 66f3ebf721b3cebd922faee5c386bf244f816d416b57c000753ff51af623 F test/upfrom3.test 6130f24ebf97f5ea865e5d2a14a2d543fe5428a62e87cc60f62d875e45c1f5f0 F test/upfrom4.test 78f742a6577c91a7a55c64edb8811004e7c6aa99b8d57b2320f70a918c357807 F test/upfromfault.test 3a10075a0043f0c4fad6614b2c371f88a8ba5a4acab68b907438413865d6a8d6 -F test/upsert1.test b0ae2f58680c5205b4bc1cdeed3c3d444057c506f6c44494fa3eac60731d68a2 +F test/upsert1.test a512e2f884d3a36159fce2e45108c236f78ae38e35bda55f4050db580ceb25d3 F test/upsert2.test 720e94d09f7362a282bc69b3c6b83d51daeaaf0440eb4920a08b86518b8c7496 F test/upsert3.test 88d7d590a1948a9cb6eac1b54b0642f67a9f35a1fc0f19b200e97d5d39e3179c F test/upsert4.test 25d2a1da92f149331ae0c51ca6e3eee78189577585eab92de149900d62994fa5 @@ -2054,7 +2068,7 @@ F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 7ce07da76b5e745783e703a834417d725b7d45fd F tool/spellsift.tcl 52b4b04dc4333c7ab024f09d9d66ed6b6f7c6eb00b38497a09f338fa55d40618 x F tool/split-sqlite3c.tcl 5aa60643afca558bc732b1444ae81a522326f91e1dc5665b369c54f09e20de60 -F tool/sqldiff.c 2a693b4e7c1818c23f871f82f0c3fe67d80b67e3f087893089d33da29c1e387e +F tool/sqldiff.c efe73c6706b0ead9b4e415c684e4abf439e5f405793ae6ba50796e0e4a030349 F tool/sqlite3_analyzer.c.in f88615bf33098945e0a42f17733f472083d150b58bdaaa5555a7129d0a51621c F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaaef898 F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848 @@ -2092,11 +2106,9 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P d543c36c35f71c5f0a7ebf6f496feca40d16566d0c5b2c2ba205ff43437ffcd1 -R 3519e615bc0924b4429d475a70546643 -T *branch * extra-autoext -T *sym-extra-autoext * -T -sym-trunk * Cancelled\sby\sbranch. -U stephan -Z 1b624c06be0989f0074081f40aaff915 +P 71f239747c7934310dedf9fc0cbf84fbeeed53808234067147335c12396849a1 423e77277a61d7febf4c3fc737981fa22a82b5c774a8ada5375a01a0611535b2 +R 52dd63dfa31054f8cede16ca6a766cde +T +closed 423e77277a61d7febf4c3fc737981fa22a82b5c774a8ada5375a01a0611535b2 +U drh +Z 4533af2279533b84257be53cbe5f8c72 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index a1ac7d572..5db811ee1 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -423e77277a61d7febf4c3fc737981fa22a82b5c774a8ada5375a01a0611535b2
\ No newline at end of file +fe7365254d343ed03e11a4e9cad7f0e5d5182b9220c6fde6d30e434ebdaba2af
\ No newline at end of file diff --git a/src/func.c b/src/func.c index ca0075edc..70fda8592 100644 --- a/src/func.c +++ b/src/func.c @@ -1816,8 +1816,10 @@ static void sumFinalize(sqlite3_context *context){ if( p->approx ){ if( p->ovrfl ){ sqlite3_result_error(context,"integer overflow",-1); - }else{ + }else if( !sqlite3IsNaN(p->rErr) ){ sqlite3_result_double(context, p->rSum+p->rErr); + }else{ + sqlite3_result_double(context, p->rSum); } }else{ sqlite3_result_int64(context, p->iSum); diff --git a/src/pager.h b/src/pager.h index 10c1acd9b..044c2573e 100644 --- a/src/pager.h +++ b/src/pager.h @@ -240,7 +240,7 @@ void sqlite3PagerRekey(DbPage*, Pgno, u16); # define enable_simulated_io_errors() #endif -#ifdef SQLITE_USE_SEH +#if defined(SQLITE_USE_SEH) && !defined(SQLITE_OMIT_WAL) int sqlite3PagerWalSystemErrno(Pager*); #endif diff --git a/src/pcache.c b/src/pcache.c index 42f22b703..2974b0810 100644 --- a/src/pcache.c +++ b/src/pcache.c @@ -107,7 +107,7 @@ struct PCache { ** Return 1 if pPg is on the dirty list for pCache. Return 0 if not. ** This routine runs inside of assert() statements only. */ -#ifdef SQLITE_DEBUG +#if defined(SQLITE_ENABLE_EXPENSIVE_ASSERT) static int pageOnDirtyList(PCache *pCache, PgHdr *pPg){ PgHdr *p; for(p=pCache->pDirty; p; p=p->pDirtyNext){ @@ -115,6 +115,16 @@ static int pageOnDirtyList(PCache *pCache, PgHdr *pPg){ } return 0; } +static int pageNotOnDirtyList(PCache *pCache, PgHdr *pPg){ + PgHdr *p; + for(p=pCache->pDirty; p; p=p->pDirtyNext){ + if( p==pPg ) return 0; + } + return 1; +} +#else +# define pageOnDirtyList(A,B) 1 +# define pageNotOnDirtyList(A,B) 1 #endif /* @@ -135,7 +145,7 @@ int sqlite3PcachePageSanity(PgHdr *pPg){ assert( pCache!=0 ); /* Every page has an associated PCache */ if( pPg->flags & PGHDR_CLEAN ){ assert( (pPg->flags & PGHDR_DIRTY)==0 );/* Cannot be both CLEAN and DIRTY */ - assert( !pageOnDirtyList(pCache, pPg) );/* CLEAN pages not on dirty list */ + assert( pageNotOnDirtyList(pCache, pPg) );/* CLEAN pages not on dirtylist */ }else{ assert( (pPg->flags & PGHDR_DIRTY)!=0 );/* If not CLEAN must be DIRTY */ assert( pPg->pDirtyNext==0 || pPg->pDirtyNext->pDirtyPrev==pPg ); diff --git a/src/shell.c.in b/src/shell.c.in index 55a13bc11..07d92d014 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -4809,7 +4809,7 @@ static const char *(azHelp[]) = { #endif ".connection [close] [#] Open or close an auxiliary database connection", #if defined(_WIN32) || defined(WIN32) - ".crnl on|off Turn translate \\n to \\r\\n. Default ON", + ".crnl on|off Translate \\n to \\r\\n. Default ON", #endif ".databases List names and files of attached databases", ".dbconfig ?op? ?val? List or change sqlite3_db_config() options", @@ -11304,6 +11304,7 @@ static int do_meta_command(char *zLine, ShellState *p){ #endif /* SQLITE_USER_AUTHENTICATION */ if( c=='v' && cli_strncmp(azArg[0], "version", n)==0 ){ + char *zPtrSz = sizeof(void*)==8 ? "64-bit" : "32-bit"; utf8_printf(p->out, "SQLite %s %s\n" /*extra-version-info*/, sqlite3_libversion(), sqlite3_sourceid()); #if SQLITE_HAVE_ZLIB @@ -11314,11 +11315,11 @@ static int do_meta_command(char *zLine, ShellState *p){ #if defined(__clang__) && defined(__clang_major__) utf8_printf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "." CTIMEOPT_VAL(__clang_minor__) "." - CTIMEOPT_VAL(__clang_patchlevel__) "\n"); + CTIMEOPT_VAL(__clang_patchlevel__) " (%s)\n", zPtrSz); #elif defined(_MSC_VER) - utf8_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) "\n"); + utf8_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) " (%s)\n", zPtrSz); #elif defined(__GNUC__) && defined(__VERSION__) - utf8_printf(p->out, "gcc-" __VERSION__ "\n"); + utf8_printf(p->out, "gcc-" __VERSION__ " (%s)\n", zPtrSz); #endif }else @@ -12492,7 +12493,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ }else if( cli_strcmp(z,"-bail")==0 ){ /* No-op. The bail_on_error flag should already be set. */ }else if( cli_strcmp(z,"-version")==0 ){ - printf("%s %s\n", sqlite3_libversion(), sqlite3_sourceid()); + printf("%s %s (%d-bit)\n", sqlite3_libversion(), sqlite3_sourceid(), + 8*(int)sizeof(char*)); return 0; }else if( cli_strcmp(z,"-interactive")==0 ){ stdin_is_interactive = 1; diff --git a/src/upsert.c b/src/upsert.c index 85994020c..be0d0550d 100644 --- a/src/upsert.c +++ b/src/upsert.c @@ -178,7 +178,7 @@ int sqlite3UpsertAnalyzeTarget( pExpr = &sCol[0]; } for(jj=0; jj<nn; jj++){ - if( sqlite3ExprCompare(pParse,pTarget->a[jj].pExpr,pExpr,iCursor)<2 ){ + if( sqlite3ExprCompare(0,pTarget->a[jj].pExpr,pExpr,iCursor)<2 ){ break; /* Column ii of the index matches column jj of target */ } } diff --git a/src/util.c b/src/util.c index cb29736fb..7f8a33f9c 100644 --- a/src/util.c +++ b/src/util.c @@ -202,12 +202,16 @@ void sqlite3ProgressCheck(Parse *p){ p->rc = SQLITE_INTERRUPT; } #ifndef SQLITE_OMIT_PROGRESS_CALLBACK - if( db->xProgress && (++p->nProgressSteps)>=db->nProgressOps ){ - if( db->xProgress(db->pProgressArg) ){ - p->nErr++; - p->rc = SQLITE_INTERRUPT; + if( db->xProgress ){ + if( p->rc==SQLITE_INTERRUPT ){ + p->nProgressSteps = 0; + }else if( (++p->nProgressSteps)>=db->nProgressOps ){ + if( db->xProgress(db->pProgressArg) ){ + p->nErr++; + p->rc = SQLITE_INTERRUPT; + } + p->nProgressSteps = 0; } - p->nProgressSteps = 0; } #endif } @@ -1020,7 +1024,7 @@ void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){ ** The error terms on constants like 1.0e+100 computed using the ** decimal extension, for example as follows: ** - ** SELECT decimal_sci(decimal_sub('1.0e+100',decimal(1.0e+100))); + ** SELECT decimal_exp(decimal_sub('1.0e+100',decimal(1.0e+100))); */ double rr[2]; rr[0] = r; @@ -531,6 +531,8 @@ struct Wal { #ifdef SQLITE_USE_SEH u32 lockMask; /* Mask of locks held */ void *pFree; /* Pointer to sqlite3_free() if exception thrown */ + u32 *pWiValue; /* Value to write into apWiData[iWiPg] */ + int iWiPg; /* Write pWiValue into apWiData[iWiPg] */ int iSysErrno; /* System error code following exception */ #endif #ifdef SQLITE_DEBUG @@ -702,11 +704,24 @@ static void sehInjectFault(Wal *pWal){ #define SEH_FREE_ON_ERROR(X,Y) \ assert( (X==0 || Y==0) && pWal->pFree==X ); pWal->pFree = Y +/* +** There are two ways to use this macro. To arrange for pWal->apWiData[iPg] +** to be set to pValue if an exception is thrown: +** +** SEH_SET_ON_ERROR(iPg, pValue); +** +** and to cancel the same: +** +** SEH_SET_ON_ERROR(0, 0); +*/ +#define SEH_SET_ON_ERROR(X,Y) pWal->iWiPg = X; pWal->pWiValue = Y + #else # define SEH_TRY VVA_ONLY(pWal->nSehTry++); # define SEH_EXCEPT(X) VVA_ONLY(pWal->nSehTry--); assert( pWal->nSehTry==0 ); # define SEH_INJECT_FAULT assert( pWal->nSehTry>0 ); # define SEH_FREE_ON_ERROR(X,Y) +# define SEH_SET_ON_ERROR(X,Y) #endif /* ifdef SQLITE_USE_SEH */ @@ -1467,6 +1482,7 @@ static int walIndexRecover(Wal *pWal){ rc = walIndexPage(pWal, iPg, (volatile u32**)&aShare); assert( aShare!=0 || rc!=SQLITE_OK ); if( aShare==0 ) break; + SEH_SET_ON_ERROR(iPg, aShare); pWal->apWiData[iPg] = aPrivate; for(iFrame=iFirst; iFrame<=iLast; iFrame++){ @@ -1494,6 +1510,7 @@ static int walIndexRecover(Wal *pWal){ } } pWal->apWiData[iPg] = aShare; + SEH_SET_ON_ERROR(0,0); nHdr = (iPg==0 ? WALINDEX_HDR_SIZE : 0); nHdr32 = nHdr / sizeof(u32); #ifndef SQLITE_SAFER_WALINDEX_RECOVERY @@ -2387,7 +2404,9 @@ static void walLimitSize(Wal *pWal, i64 nMax){ ** ** 2) Frees the pointer at Wal.pFree, if any, using sqlite3_free(). ** -** 3) Returns SQLITE_IOERR. +** 3) Set pWal->apWiData[pWal->iWiPg] to pWal->pWiValue if not NULL +** +** 4) Returns SQLITE_IOERR. */ static int walHandleException(Wal *pWal){ if( pWal->exclusiveMode==0 ){ @@ -2406,6 +2425,10 @@ static int walHandleException(Wal *pWal){ } sqlite3_free(pWal->pFree); pWal->pFree = 0; + if( pWal->pWiValue ){ + pWal->apWiData[pWal->iWiPg] = pWal->pWiValue; + pWal->pWiValue = 0; + } return SQLITE_IOERR_IN_PAGE; } @@ -45,6 +45,7 @@ # define sqlite3WalFramesize(z) 0 # define sqlite3WalFindFrame(x,y,z) 0 # define sqlite3WalFile(x) 0 +# undef SQLITE_USE_SEH #else #define WAL_SAVEPOINT_NDATA 4 diff --git a/test/decimal.test b/test/decimal.test index 6ce5d642d..cf4e06ad9 100644 --- a/test/decimal.test +++ b/test/decimal.test @@ -49,7 +49,7 @@ do_execsql_test 1080 { SELECT decimal('+123e+4'); } {1230000} do_execsql_test 1081 { - SELECT decimal_sci('+123e+4'); + SELECT decimal_exp('+123e+4'); } {+1.23e+06} diff --git a/test/func.test b/test/func.test index 3d7106046..4743e109b 100644 --- a/test/func.test +++ b/test/func.test @@ -1545,4 +1545,12 @@ do_catchsql_test func-37.120 { SELECT sum(x) FROM c; } {1 {integer overflow}} +# 2023-08-28 forum post https://sqlite.org/forum/forumpost/1c06ddcacc86032a +# Incorrect handling of infinity by SUM(). +# +do_execsql_test func-38.100 { + WITH t1(x) AS (VALUES(9e+999)) SELECT sum(x) FROM t1; + WITH t1(x) AS (VALUES(-9e+999)) SELECT sum(x) FROM t1; +} {Inf -Inf} + finish_test diff --git a/test/fuzzcheck.c b/test/fuzzcheck.c index 76ce5af0e..23200a5f0 100644 --- a/test/fuzzcheck.c +++ b/test/fuzzcheck.c @@ -1205,10 +1205,12 @@ int runCombinedDbSqlInput( iSql = decodeDatabase((unsigned char*)aData, (int)nByte, &aDb, &nDb); if( iSql<0 ) return 0; if( nDb>=75 ){ - dbFlags = (aDb[72]<<24) + (aDb[73]<<16) + (aDb[74]<<8) + aDb[75]; + dbFlags = ((unsigned int)aDb[72]<<24) + ((unsigned int)aDb[73]<<16) + + ((unsigned int)aDb[74]<<8) + (unsigned int)aDb[75]; } if( nDb>=79 ){ - dbOpt = (aDb[76]<<24) + (aDb[77]<<16) + (aDb[78]<<8) + aDb[79]; + dbOpt = ((unsigned int)aDb[76]<<24) + ((unsigned int)aDb[77]<<16) + + ((unsigned int)aDb[78]<<8) + (unsigned int)aDb[79]; } nSql = (int)(nByte - iSql); if( bScript ){ @@ -2031,7 +2033,9 @@ int main(int argc, char **argv){ if( strcmp(z,"version")==0 ){ int ii; const char *zz; - printf("SQLite %s %s\n", sqlite3_libversion(), sqlite3_sourceid()); + printf("SQLite %s %s (%d-bit)\n", + sqlite3_libversion(), sqlite3_sourceid(), + 8*(int)sizeof(char*)); for(ii=0; (zz = sqlite3_compileoption_get(ii))!=0; ii++){ printf("%s\n", zz); } @@ -2536,9 +2540,10 @@ int main(int argc, char **argv){ printf("fuzzcheck: %u query invariants checked\n", g.nInvariant); } printf("fuzzcheck: 0 errors out of %d tests in %d.%03d seconds\n" - "SQLite %s %s\n", + "SQLite %s %s (%d-bit)\n", nTest, (int)(iElapse/1000), (int)(iElapse%1000), - sqlite3_libversion(), sqlite3_sourceid()); + sqlite3_libversion(), sqlite3_sourceid(), + 8*(int)sizeof(char*)); } free(azSrcDb); free(pHeap); diff --git a/test/json101.test b/test/json101.test index 8bb133ec7..4da1d132c 100644 --- a/test/json101.test +++ b/test/json101.test @@ -122,14 +122,14 @@ do_execsql_test json101-4.8 { # json_extract(JSON,'$') will return objects and arrays without change. # -do_execsql_test json-4.10 { +do_execsql_test json101-4.10 { SELECT count(*) FROM j1 WHERE json_type(x) IN ('object','array'); SELECT x FROM j1 WHERE json_extract(x,'$')<>x AND json_type(x) IN ('object','array'); } {4} -do_execsql_test json-5.1 { +do_execsql_test json101-5.1 { CREATE TABLE j2(id INTEGER PRIMARY KEY, json, src); INSERT INTO j2(id,json,src) VALUES(1,'{ @@ -257,7 +257,7 @@ do_execsql_test json-5.1 { SELECT count(*) FROM j2; } {3} -do_execsql_test json-5.2 { +do_execsql_test json101-5.2 { SELECT id, json_valid(json), json_type(json), '|' FROM j2 ORDER BY id; } {1 1 object | 2 1 object | 3 1 array |} @@ -268,13 +268,13 @@ ifcapable !vtab { # fullkey is always the same as path+key (with appropriate formatting) # -do_execsql_test json-5.3 { +do_execsql_test json101-5.3 { SELECT j2.rowid, jx.rowid, fullkey, path, key FROM j2, json_tree(j2.json) AS jx WHERE fullkey!=(path || CASE WHEN typeof(key)=='integer' THEN '['||key||']' ELSE '.'||key END); } {} -do_execsql_test json-5.4 { +do_execsql_test json101-5.4 { SELECT j2.rowid, jx.rowid, fullkey, path, key FROM j2, json_each(j2.json) AS jx WHERE fullkey!=(path || CASE WHEN typeof(key)=='integer' THEN '['||key||']' @@ -285,58 +285,58 @@ do_execsql_test json-5.4 { # Verify that the json_each.json and json_tree.json output is always the # same as input. # -do_execsql_test json-5.5 { +do_execsql_test json101-5.5 { SELECT j2.rowid, jx.rowid, fullkey, path, key FROM j2, json_each(j2.json) AS jx WHERE jx.json<>j2.json; } {} -do_execsql_test json-5.6 { +do_execsql_test json101-5.6 { SELECT j2.rowid, jx.rowid, fullkey, path, key FROM j2, json_tree(j2.json) AS jx WHERE jx.json<>j2.json; } {} -do_execsql_test json-5.7 { +do_execsql_test json101-5.7 { SELECT j2.rowid, jx.rowid, fullkey, path, key FROM j2, json_each(j2.json) AS jx WHERE jx.value<>jx.atom AND type NOT IN ('array','object'); } {} -do_execsql_test json-5.8 { +do_execsql_test json101-5.8 { SELECT j2.rowid, jx.rowid, fullkey, path, key FROM j2, json_tree(j2.json) AS jx WHERE jx.value<>jx.atom AND type NOT IN ('array','object'); } {} -do_execsql_test json-6.1 { +do_execsql_test json101-6.1 { SELECT json_valid('{"a":55,"b":72,}'); } {0} -do_execsql_test json-6.2 { +do_execsql_test json101-6.2 { SELECT json_error_position('{"a":55,"b":72,}'); } {0} -do_execsql_test json-6.3 { +do_execsql_test json101-6.3 { SELECT json_valid(json('{"a":55,"b":72,}')); } {1} -do_execsql_test json-6.4 { +do_execsql_test json101-6.4 { SELECT json_valid('{"a":55,"b":72 , }'); } {0} -do_execsql_test json-6.5 { +do_execsql_test json101-6.5 { SELECT json_error_position('{"a":55,"b":72 , }'); } {0} -do_execsql_test json-6.6 { +do_execsql_test json101-6.6 { SELECT json_error_position('{"a":55,"b":72,,}'); } {16} -do_execsql_test json-6.7 { +do_execsql_test json101-6.7 { SELECT json_valid('{"a":55,"b":72}'); } {1} -do_execsql_test json-6.8 { +do_execsql_test json101-6.8 { SELECT json_error_position('["a",55,"b",72,]'); } {0} -do_execsql_test json-6.9 { +do_execsql_test json101-6.9 { SELECT json_error_position('["a",55,"b",72 , ]'); } {0} -do_execsql_test json-6.10 { +do_execsql_test json101-6.10 { SELECT json_error_position('["a",55,"b",72,,]'); } {16} -do_execsql_test json-6.11 { +do_execsql_test json101-6.11 { SELECT json_valid('["a",55,"b",72]'); } {1} @@ -352,7 +352,7 @@ foreach {tn isvalid ws} { 7.6 1 char(0x20,0x09,0x0a,0x0d,0x20) 7.7 0 char(0x20,0x09,0x0a,0x0c,0x0d,0x20) } { - do_execsql_test json-$tn.1 \ + do_execsql_test json101-$tn.1 \ "SELECT json_valid(printf('%s{%s\"x\"%s:%s9%s}%s', $::ws,$::ws,$::ws,$::ws,$::ws,$::ws));" \ $isvalid @@ -361,23 +361,23 @@ foreach {tn isvalid ws} { # Ticket https://www.sqlite.org/src/info/ad2559db380abf8e # Control characters must be escaped in JSON strings. # -do_execsql_test json-8.1 { +do_execsql_test json101-8.1 { DROP TABLE IF EXISTS t8; CREATE TABLE t8(a,b); INSERT INTO t8(a) VALUES('abc' || char(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35) || 'xyz'); UPDATE t8 SET b=json_array(a); SELECT b FROM t8; } {{["abc\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#xyz"]}} -do_execsql_test json-8.2 { +do_execsql_test json101-8.2 { SELECT a=json_extract(b,'$[0]') FROM t8; } {1} # 2017-04-12. Regression reported on the mailing list by Rolf Ade # -do_execsql_test json-8.3 { +do_execsql_test json101-8.3 { SELECT json_valid(char(0x22,0xe4,0x22)); } {1} -do_execsql_test json-8.4 { +do_execsql_test json101-8.4 { SELECT unicode(json_extract(char(0x22,228,0x22),'$')); } {228} @@ -385,331 +385,331 @@ do_execsql_test json-8.4 { # String values are quoted and interior quotes are escaped. NULL values # are rendered as the unquoted string "null". # -do_execsql_test json-9.1 { +do_execsql_test json101-9.1 { SELECT json_quote('abc"xyz'); } {{"abc\"xyz"}} -do_execsql_test json-9.2 { +do_execsql_test json101-9.2 { SELECT json_quote(3.14159); } {3.14159} -do_execsql_test json-9.3 { +do_execsql_test json101-9.3 { SELECT json_quote(12345); } {12345} -do_execsql_test json-9.4 { +do_execsql_test json101-9.4 { SELECT json_quote(null); } {"null"} -do_catchsql_test json-9.5 { +do_catchsql_test json101-9.5 { SELECT json_quote(x'30313233'); } {1 {JSON cannot hold BLOB values}} -do_catchsql_test json-9.6 { +do_catchsql_test json101-9.6 { SELECT json_quote(123,456) } {1 {wrong number of arguments to function json_quote()}} -do_catchsql_test json-9.7 { +do_catchsql_test json101-9.7 { SELECT json_quote() } {1 {wrong number of arguments to function json_quote()}} # Make sure only valid backslash-escapes are accepted. # -do_execsql_test json-10.1 { +do_execsql_test json101-10.1 { SELECT json_valid('" \ "'); } {0} -do_execsql_test json-10.2 { +do_execsql_test json101-10.2 { SELECT json_valid('" \! "'); } {0} -do_execsql_test json-10.3 { +do_execsql_test json101-10.3 { SELECT json_valid('" \" "'); } {1} -do_execsql_test json-10.4 { +do_execsql_test json101-10.4 { SELECT json_valid('" \# "'); } {0} -do_execsql_test json-10.5 { +do_execsql_test json101-10.5 { SELECT json_valid('" \$ "'); } {0} -do_execsql_test json-10.6 { +do_execsql_test json101-10.6 { SELECT json_valid('" \% "'); } {0} -do_execsql_test json-10.7 { +do_execsql_test json101-10.7 { SELECT json_valid('" \& "'); } {0} -do_execsql_test json-10.8 { +do_execsql_test json101-10.8 { SELECT json_valid('" \'' "'); } {0} -do_execsql_test json-10.9 { +do_execsql_test json101-10.9 { SELECT json_valid('" \( "'); } {0} -do_execsql_test json-10.10 { +do_execsql_test json101-10.10 { SELECT json_valid('" \) "'); } {0} -do_execsql_test json-10.11 { +do_execsql_test json101-10.11 { SELECT json_valid('" \* "'); } {0} -do_execsql_test json-10.12 { +do_execsql_test json101-10.12 { SELECT json_valid('" \+ "'); } {0} -do_execsql_test json-10.13 { +do_execsql_test json101-10.13 { SELECT json_valid('" \, "'); } {0} -do_execsql_test json-10.14 { +do_execsql_test json101-10.14 { SELECT json_valid('" \- "'); } {0} -do_execsql_test json-10.15 { +do_execsql_test json101-10.15 { SELECT json_valid('" \. "'); } {0} -do_execsql_test json-10.16 { +do_execsql_test json101-10.16 { SELECT json_valid('" \/ "'); } {1} -do_execsql_test json-10.17 { +do_execsql_test json101-10.17 { SELECT json_valid('" \0 "'); } {0} -do_execsql_test json-10.18 { +do_execsql_test json101-10.18 { SELECT json_valid('" \1 "'); } {0} -do_execsql_test json-10.19 { +do_execsql_test json101-10.19 { SELECT json_valid('" \2 "'); } {0} -do_execsql_test json-10.20 { +do_execsql_test json101-10.20 { SELECT json_valid('" \3 "'); } {0} -do_execsql_test json-10.21 { +do_execsql_test json101-10.21 { SELECT json_valid('" \4 "'); } {0} -do_execsql_test json-10.22 { +do_execsql_test json101-10.22 { SELECT json_valid('" \5 "'); } {0} -do_execsql_test json-10.23 { +do_execsql_test json101-10.23 { SELECT json_valid('" \6 "'); } {0} -do_execsql_test json-10.24 { +do_execsql_test json101-10.24 { SELECT json_valid('" \7 "'); } {0} -do_execsql_test json-10.25 { +do_execsql_test json101-10.25 { SELECT json_valid('" \8 "'); } {0} -do_execsql_test json-10.26 { +do_execsql_test json101-10.26 { SELECT json_valid('" \9 "'); } {0} -do_execsql_test json-10.27 { +do_execsql_test json101-10.27 { SELECT json_valid('" \: "'); } {0} -do_execsql_test json-10.28 { +do_execsql_test json101-10.28 { SELECT json_valid('" \; "'); } {0} -do_execsql_test json-10.29 { +do_execsql_test json101-10.29 { SELECT json_valid('" \< "'); } {0} -do_execsql_test json-10.30 { +do_execsql_test json101-10.30 { SELECT json_valid('" \= "'); } {0} -do_execsql_test json-10.31 { +do_execsql_test json101-10.31 { SELECT json_valid('" \> "'); } {0} -do_execsql_test json-10.32 { +do_execsql_test json101-10.32 { SELECT json_valid('" \? "'); } {0} -do_execsql_test json-10.33 { +do_execsql_test json101-10.33 { SELECT json_valid('" \@ "'); } {0} -do_execsql_test json-10.34 { +do_execsql_test json101-10.34 { SELECT json_valid('" \A "'); } {0} -do_execsql_test json-10.35 { +do_execsql_test json101-10.35 { SELECT json_valid('" \B "'); } {0} -do_execsql_test json-10.36 { +do_execsql_test json101-10.36 { SELECT json_valid('" \C "'); } {0} -do_execsql_test json-10.37 { +do_execsql_test json101-10.37 { SELECT json_valid('" \D "'); } {0} -do_execsql_test json-10.38 { +do_execsql_test json101-10.38 { SELECT json_valid('" \E "'); } {0} -do_execsql_test json-10.39 { +do_execsql_test json101-10.39 { SELECT json_valid('" \F "'); } {0} -do_execsql_test json-10.40 { +do_execsql_test json101-10.40 { SELECT json_valid('" \G "'); } {0} -do_execsql_test json-10.41 { +do_execsql_test json101-10.41 { SELECT json_valid('" \H "'); } {0} -do_execsql_test json-10.42 { +do_execsql_test json101-10.42 { SELECT json_valid('" \I "'); } {0} -do_execsql_test json-10.43 { +do_execsql_test json101-10.43 { SELECT json_valid('" \J "'); } {0} -do_execsql_test json-10.44 { +do_execsql_test json101-10.44 { SELECT json_valid('" \K "'); } {0} -do_execsql_test json-10.45 { +do_execsql_test json101-10.45 { SELECT json_valid('" \L "'); } {0} -do_execsql_test json-10.46 { +do_execsql_test json101-10.46 { SELECT json_valid('" \M "'); } {0} -do_execsql_test json-10.47 { +do_execsql_test json101-10.47 { SELECT json_valid('" \N "'); } {0} -do_execsql_test json-10.48 { +do_execsql_test json101-10.48 { SELECT json_valid('" \O "'); } {0} -do_execsql_test json-10.49 { +do_execsql_test json101-10.49 { SELECT json_valid('" \P "'); } {0} -do_execsql_test json-10.50 { +do_execsql_test json101-10.50 { SELECT json_valid('" \Q "'); } {0} -do_execsql_test json-10.51 { +do_execsql_test json101-10.51 { SELECT json_valid('" \R "'); } {0} -do_execsql_test json-10.52 { +do_execsql_test json101-10.52 { SELECT json_valid('" \S "'); } {0} -do_execsql_test json-10.53 { +do_execsql_test json101-10.53 { SELECT json_valid('" \T "'); } {0} -do_execsql_test json-10.54 { +do_execsql_test json101-10.54 { SELECT json_valid('" \U "'); } {0} -do_execsql_test json-10.55 { +do_execsql_test json101-10.55 { SELECT json_valid('" \V "'); } {0} -do_execsql_test json-10.56 { +do_execsql_test json101-10.56 { SELECT json_valid('" \W "'); } {0} -do_execsql_test json-10.57 { +do_execsql_test json101-10.57 { SELECT json_valid('" \X "'); } {0} -do_execsql_test json-10.58 { +do_execsql_test json101-10.58 { SELECT json_valid('" \Y "'); } {0} -do_execsql_test json-10.59 { +do_execsql_test json101-10.59 { SELECT json_valid('" \Z "'); } {0} -do_execsql_test json-10.60 { +do_execsql_test json101-10.60 { SELECT json_valid('" \[ "'); } {0} -do_execsql_test json-10.61 { +do_execsql_test json101-10.61 { SELECT json_valid('" \\ "'); } {1} -do_execsql_test json-10.62 { +do_execsql_test json101-10.62 { SELECT json_valid('" \] "'); } {0} -do_execsql_test json-10.63 { +do_execsql_test json101-10.63 { SELECT json_valid('" \^ "'); } {0} -do_execsql_test json-10.64 { +do_execsql_test json101-10.64 { SELECT json_valid('" \_ "'); } {0} -do_execsql_test json-10.65 { +do_execsql_test json101-10.65 { SELECT json_valid('" \` "'); } {0} -do_execsql_test json-10.66 { +do_execsql_test json101-10.66 { SELECT json_valid('" \a "'); } {0} -do_execsql_test json-10.67 { +do_execsql_test json101-10.67 { SELECT json_valid('" \b "'); } {1} -do_execsql_test json-10.68 { +do_execsql_test json101-10.68 { SELECT json_valid('" \c "'); } {0} -do_execsql_test json-10.69 { +do_execsql_test json101-10.69 { SELECT json_valid('" \d "'); } {0} -do_execsql_test json-10.70 { +do_execsql_test json101-10.70 { SELECT json_valid('" \e "'); } {0} -do_execsql_test json-10.71 { +do_execsql_test json101-10.71 { SELECT json_valid('" \f "'); } {1} -do_execsql_test json-10.72 { +do_execsql_test json101-10.72 { SELECT json_valid('" \g "'); } {0} -do_execsql_test json-10.73 { +do_execsql_test json101-10.73 { SELECT json_valid('" \h "'); } {0} -do_execsql_test json-10.74 { +do_execsql_test json101-10.74 { SELECT json_valid('" \i "'); } {0} -do_execsql_test json-10.75 { +do_execsql_test json101-10.75 { SELECT json_valid('" \j "'); } {0} -do_execsql_test json-10.76 { +do_execsql_test json101-10.76 { SELECT json_valid('" \k "'); } {0} -do_execsql_test json-10.77 { +do_execsql_test json101-10.77 { SELECT json_valid('" \l "'); } {0} -do_execsql_test json-10.78 { +do_execsql_test json101-10.78 { SELECT json_valid('" \m "'); } {0} -do_execsql_test json-10.79 { +do_execsql_test json101-10.79 { SELECT json_valid('" \n "'); } {1} -do_execsql_test json-10.80 { +do_execsql_test json101-10.80 { SELECT json_valid('" \o "'); } {0} -do_execsql_test json-10.81 { +do_execsql_test json101-10.81 { SELECT json_valid('" \p "'); } {0} -do_execsql_test json-10.82 { +do_execsql_test json101-10.82 { SELECT json_valid('" \q "'); } {0} -do_execsql_test json-10.83 { +do_execsql_test json101-10.83 { SELECT json_valid('" \r "'); } {1} -do_execsql_test json-10.84 { +do_execsql_test json101-10.84 { SELECT json_valid('" \s "'); } {0} -do_execsql_test json-10.85 { +do_execsql_test json101-10.85 { SELECT json_valid('" \t "'); } {1} -do_execsql_test json-10.86.0 { +do_execsql_test json101-10.86.0 { SELECT json_valid('" \u "'); } {0} -do_execsql_test json-10.86.1 { +do_execsql_test json101-10.86.1 { SELECT json_valid('" \ua "'); } {0} -do_execsql_test json-10.86.2 { +do_execsql_test json101-10.86.2 { SELECT json_valid('" \uab "'); } {0} -do_execsql_test json-10.86.3 { +do_execsql_test json101-10.86.3 { SELECT json_valid('" \uabc "'); } {0} -do_execsql_test json-10.86.4 { +do_execsql_test json101-10.86.4 { SELECT json_valid('" \uabcd "'); } {1} -do_execsql_test json-10.86.5 { +do_execsql_test json101-10.86.5 { SELECT json_valid('" \uFEDC "'); } {1} -do_execsql_test json-10.86.6 { +do_execsql_test json101-10.86.6 { SELECT json_valid('" \u1234 "'); } {1} -do_execsql_test json-10.87 { +do_execsql_test json101-10.87 { SELECT json_valid('" \v "'); } {0} -do_execsql_test json-10.88 { +do_execsql_test json101-10.88 { SELECT json_valid('" \w "'); } {0} -do_execsql_test json-10.89 { +do_execsql_test json101-10.89 { SELECT json_valid('" \x "'); } {0} -do_execsql_test json-10.90 { +do_execsql_test json101-10.90 { SELECT json_valid('" \y "'); } {0} -do_execsql_test json-10.91 { +do_execsql_test json101-10.91 { SELECT json_valid('" \z "'); } {0} -do_execsql_test json-10.92 { +do_execsql_test json101-10.92 { SELECT json_valid('" \{ "'); } {0} -do_execsql_test json-10.93 { +do_execsql_test json101-10.93 { SELECT json_valid('" \| "'); } {0} -do_execsql_test json-10.94 { +do_execsql_test json101-10.94 { SELECT json_valid('" \} "'); } {0} -do_execsql_test json-10.95 { +do_execsql_test json101-10.95 { SELECT json_valid('" \~ "'); } {0} @@ -719,20 +719,20 @@ do_execsql_test json-10.95 { # # The following tests confirm that deeply nested JSON is considered invalid. # -do_execsql_test json-11.0 { +do_execsql_test json101-11.0 { /* Shallow enough to be parsed */ SELECT json_valid(printf('%.1000c0%.1000c','[',']')); } {1} -do_execsql_test json-11.1 { +do_execsql_test json101-11.1 { /* Too deep by one */ SELECT json_valid(printf('%.1001c0%.1001c','[',']')); } {0} -do_execsql_test json-11.2 { +do_execsql_test json101-11.2 { /* Shallow enough to be parsed { */ SELECT json_valid(replace(printf('%.1000c0%.1000c','[','}'),'[','{"a":')); /* } */ } {1} -do_execsql_test json-11.3 { +do_execsql_test json101-11.3 { /* Too deep by one { */ SELECT json_valid(replace(printf('%.1001c0%.1001c','[','}'),'[','{"a":')); /* } */ @@ -742,7 +742,7 @@ do_execsql_test json-11.3 { # a json structure even though the element name constains a "." # character, by quoting the element name in the path. # -do_execsql_test json-12.100 { +do_execsql_test json101-12.100 { CREATE TABLE t12(x); INSERT INTO t12(x) VALUES( '{"settings": @@ -766,11 +766,11 @@ do_execsql_test json-12.100 { } }'); } {} -do_execsql_test json-12.110 { +do_execsql_test json101-12.110 { SELECT json_remove(x, '$.settings.layer2."dis.legomenon".forceDisplay') FROM t12; } {{{"settings":{"layer2":{"hapax.legomenon":{"forceDisplay":true,"transliterate":true,"add.footnote":true,"summary.report":true},"dis.legomenon":{"transliterate":false,"add.footnote":false,"summary.report":true},"tris.legomenon":{"forceDisplay":true,"transliterate":false,"add.footnote":false,"summary.report":false}}}}}} -do_execsql_test json-12.120 { +do_execsql_test json101-12.120 { SELECT json_extract(x, '$.settings.layer2."tris.legomenon"."summary.report"') FROM t12; } {0} @@ -779,7 +779,7 @@ do_execsql_test json-12.120 { # ticket https://www.sqlite.org/src/tktview/80177f0c226ff54f6ddd41 # Make sure the query planner knows about the arguments to table-valued functions. # -do_execsql_test json-13.100 { +do_execsql_test json101-13.100 { DROP TABLE IF EXISTS t1; DROP TABLE IF EXISTS t2; CREATE TABLE t1(id, json); @@ -794,7 +794,7 @@ do_execsql_test json-13.100 { WHERE EXISTS(SELECT 1 FROM json_each(t1.json,'$.items') AS Z WHERE Z.value==t2.id); } {1 {{"items":[3,5]}} 3 {{"value":3}} 1 {{"items":[3,5]}} 5 {{"value":5}}} -do_execsql_test json-13.110 { +do_execsql_test json101-13.110 { SELECT * FROM t2 CROSS JOIN t1 WHERE EXISTS(SELECT 1 FROM json_each(t1.json,'$.items') AS Z WHERE Z.value==t2.id); @@ -804,28 +804,28 @@ do_execsql_test json-13.110 { # Incorrect fullkey output from json_each() # when the input JSON is not an array or object. # -do_execsql_test json-14.100 { +do_execsql_test json101-14.100 { SELECT fullkey FROM json_each('123'); } {$} -do_execsql_test json-14.110 { +do_execsql_test json101-14.110 { SELECT fullkey FROM json_each('123.56'); } {$} -do_execsql_test json-14.120 { +do_execsql_test json101-14.120 { SELECT fullkey FROM json_each('"hello"'); } {$} -do_execsql_test json-14.130 { +do_execsql_test json101-14.130 { SELECT fullkey FROM json_each('null'); } {$} -do_execsql_test json-14.140 { +do_execsql_test json101-14.140 { SELECT fullkey FROM json_tree('123'); } {$} -do_execsql_test json-14.150 { +do_execsql_test json101-14.150 { SELECT fullkey FROM json_tree('123.56'); } {$} -do_execsql_test json-14.160 { +do_execsql_test json101-14.160 { SELECT fullkey FROM json_tree('"hello"'); } {$} -do_execsql_test json-14.170 { +do_execsql_test json101-14.170 { SELECT fullkey FROM json_tree('null'); } {$} @@ -835,16 +835,16 @@ do_execsql_test json-14.170 { # # Bug reported via private email. See TH3 for more information. # -do_execsql_test json-15.100 { +do_execsql_test json101-15.100 { SELECT * FROM JSON_EACH('{"a":1, "b":2}'); } {a 1 integer 1 2 {} {$.a} {$} b 2 integer 2 4 {} {$.b} {$}} -do_execsql_test json-15.110 { +do_execsql_test json101-15.110 { SELECT xyz.* FROM JSON_EACH('{"a":1, "b":2}') AS xyz; } {a 1 integer 1 2 {} {$.a} {$} b 2 integer 2 4 {} {$.b} {$}} -do_execsql_test json-15.120 { +do_execsql_test json101-15.120 { SELECT * FROM (JSON_EACH('{"a":1, "b":2}')); } {a 1 integer 1 2 {} {$.a} {$} b 2 integer 2 4 {} {$.b} {$}} -do_execsql_test json-15.130 { +do_execsql_test json101-15.130 { SELECT xyz.* FROM (JSON_EACH('{"a":1, "b":2}')) AS xyz; } {a 1 integer 1 2 {} {$.a} {$} b 2 integer 2 4 {} {$.b} {$}} @@ -852,18 +852,18 @@ do_execsql_test json-15.130 { # Mailing list bug report on the handling of surrogate pairs # in JSON. # -do_execsql_test json-16.10 { +do_execsql_test json101-16.10 { SELECT length(json_extract('"abc\uD834\uDD1Exyz"','$')); } {7} -do_execsql_test json-16.20 { +do_execsql_test json101-16.20 { SELECT length(json_extract('"\uD834\uDD1E"','$')); } {1} -do_execsql_test json-16.30 { +do_execsql_test json101-16.30 { SELECT unicode(json_extract('"\uD834\uDD1E"','$')); } {119070} # 2022-01-30 dbsqlfuzz 4678cf825d27f87c9b8343720121e12cf944b71a -do_execsql_test json-17.1 { +do_execsql_test json101-17.1 { DROP TABLE IF EXISTS t1; DROP TABLE IF EXISTS t2; CREATE TABLE t1(a,b,c); @@ -872,19 +872,19 @@ do_execsql_test json-17.1 { } {} # 2022-04-04 forum post https://sqlite.org/forum/forumpost/c082aeab43 -do_execsql_test json-18.1 { +do_execsql_test json101-18.1 { SELECT json_valid('{"":5}'); } {1} -do_execsql_test json-18.2 { +do_execsql_test json101-18.2 { SELECT json_extract('{"":5}', '$.""'); } {5} -do_execsql_test json-18.3 { +do_execsql_test json101-18.3 { SELECT json_extract('[3,{"a":4,"":[5,{"hi":6},7]},8]', '$[1].""[1].hi'); } {6} -do_execsql_test json-18.4 { +do_execsql_test json101-18.4 { SELECT json_extract('[3,{"a":4,"":[5,{"hi":6},7]},8]', '$[1].""[1]."hi"'); } {6} -do_catchsql_test json-18.5 { +do_catchsql_test json101-18.5 { SELECT json_extract('{"":8}', '$.'); } {1 {JSON path error near ''}} @@ -893,28 +893,28 @@ do_catchsql_test json-18.5 { # a problem with transaction control. But the json() function makes # the problem more easily accessible, so it is tested here. # -do_execsql_test json-19.1 { +do_execsql_test json101-19.1 { DROP TABLE IF EXISTS t1; CREATE TABLE t1(x); } {} -do_catchsql_test json-19.2 { +do_catchsql_test json101-19.2 { BEGIN; INSERT INTO t1 VALUES(0), (json('not-valid-json')); } {1 {malformed JSON}} -do_execsql_test json-19.3 { +do_execsql_test json101-19.3 { COMMIT; SELECT * FROM t1; } {} # 2023-03-17 positive and negative infinities # -do_execsql_test json-20.1 { +do_execsql_test json101-20.1 { SELECT json_object('a',2e370,'b',-3e380); } {{{"a":9.0e+999,"b":-9.0e+999}}} -do_execsql_test json-20.2 { +do_execsql_test json101-20.2 { SELECT json_object('a',2e370,'b',-3e380)->>'a'; } Inf -do_execsql_test json-20.3 { +do_execsql_test json101-20.3 { SELECT json_object('a',2e370,'b',-3e380)->>'b'; } {-Inf} @@ -924,91 +924,91 @@ do_execsql_test json-20.3 { # db null NULL if {[db exists {SELECT * FROM pragma_compile_options WHERE compile_options LIKE '%legacy_json_valid%'}]} { - do_execsql_test json-21.1-legacy { + do_execsql_test json101-21.1-legacy { SELECT json_valid(NULL); } 0 } else { - do_execsql_test json-21.1-correct { + do_execsql_test json101-21.1-correct { SELECT json_valid(NULL); } NULL } -do_execsql_test json-21.2 { +do_execsql_test json101-21.2 { SELECT json_error_position(NULL); } NULL -do_execsql_test json-21.3 { +do_execsql_test json101-21.3 { SELECT json(NULL); } NULL -do_execsql_test json-21.4 { +do_execsql_test json101-21.4 { SELECT json_array(NULL); } {[null]} -do_execsql_test json-21.5 { +do_execsql_test json101-21.5 { SELECT json_extract(NULL); } NULL -do_execsql_test json-21.6 { +do_execsql_test json101-21.6 { SELECT json_insert(NULL,'$',123); } NULL -do_execsql_test json-21.7 { +do_execsql_test json101-21.7 { SELECT NULL->0; } NULL -do_execsql_test json-21.8 { +do_execsql_test json101-21.8 { SELECT NULL->>0; } NULL -do_execsql_test json-21.9 { +do_execsql_test json101-21.9 { SELECT '{a:5}'->NULL; } NULL -do_execsql_test json-21.10 { +do_execsql_test json101-21.10 { SELECT '{a:5}'->>NULL; } NULL -do_catchsql_test json-21.11 { +do_catchsql_test json101-21.11 { SELECT json_object(NULL,5); } {1 {json_object() labels must be TEXT}} -do_execsql_test json-21.12 { +do_execsql_test json101-21.12 { SELECT json_patch(NULL,'{a:5}'); } NULL -do_execsql_test json-21.13 { +do_execsql_test json101-21.13 { SELECT json_patch('{a:5}',NULL); } NULL -do_execsql_test json-21.14 { +do_execsql_test json101-21.14 { SELECT json_patch(NULL,NULL); } NULL -do_execsql_test json-21.15 { +do_execsql_test json101-21.15 { SELECT json_remove(NULL,'$'); } NULL -do_execsql_test json-21.16 { +do_execsql_test json101-21.16 { SELECT json_remove('{a:5,b:7}',NULL); } NULL -do_execsql_test json-21.17 { +do_execsql_test json101-21.17 { SELECT json_replace(NULL,'$.a',123); } NULL -do_execsql_test json-21.18 { +do_execsql_test json101-21.18 { SELECT json_replace('{a:5,b:7}',NULL,NULL); } {{{"a":5,"b":7}}} -do_execsql_test json-21.19 { +do_execsql_test json101-21.19 { SELECT json_set(NULL,'$.a',123); } NULL -do_execsql_test json-21.20 { +do_execsql_test json101-21.20 { SELECT json_set('{a:5,b:7}',NULL,NULL); } {{{"a":5,"b":7}}} -do_execsql_test json-21.21 { +do_execsql_test json101-21.21 { SELECT json_type(NULL); } NULL -do_execsql_test json-21.22 { +do_execsql_test json101-21.22 { SELECT json_type('{a:5,b:7}',NULL); } NULL -do_execsql_test json-21.23 { +do_execsql_test json101-21.23 { SELECT json_quote(NULL); } null -do_execsql_test json-21.24 { +do_execsql_test json101-21.24 { SELECT count(*) FROM json_each(NULL); } 0 -do_execsql_test json-21.25 { +do_execsql_test json101-21.25 { SELECT count(*) FROM json_tree(NULL); } 0 -do_execsql_test json-21.26 { +do_execsql_test json101-21.26 { WITH c(x) AS (VALUES(1),(2.0),(NULL),('three')) SELECT json_group_array(x) FROM c; } {[1,2.0,null,"three"]} -do_execsql_test json-21.27 { +do_execsql_test json101-21.27 { WITH c(x,y) AS (VALUES('a',1),('b',2.0),('c',NULL),(NULL,'three'),('e','four')) SELECT json_group_object(x,y) FROM c; } {{{"a":1,"b":2.0,"c":null,:"three","e":"four"}}} diff --git a/test/sort2.test b/test/sort2.test index f686654d5..9fe4b4ccf 100644 --- a/test/sort2.test +++ b/test/sort2.test @@ -69,7 +69,7 @@ foreach {tn script} { # Because it uses so much data, this test can take 12-13 seconds even on # a modern workstation. So it is omitted from "veryquick" and other # permutations.test tests. - if {[isquick]==0} { + if {[isquick]==0 && [clang_sanitize_address]==0} { do_execsql_test $tn.3 { PRAGMA cache_size = 5; WITH r(x,y) AS ( diff --git a/test/testrunner.tcl b/test/testrunner.tcl index 3b94182a9..d2f0fe7d5 100644 --- a/test/testrunner.tcl +++ b/test/testrunner.tcl @@ -505,15 +505,17 @@ proc testset_patternlist {patternlist} { set first [lindex $patternlist 0] - if {$first=="mdevtest"} { + if {$first=="sdevtest" || $first=="mdevtest"} { + set CONFIGS(sdevtest) {All-Debug All-Sanitize} + set CONFIGS(mdevtest) {All-Debug All-O0} + set patternlist [lrange $patternlist 1 end] - foreach b {All-Debug All-O0} { + foreach b $CONFIGS($first) { lappend testset [list $b build testfixture] lappend testset [list $b make fuzztest] testset_append testset $b veryquick $patternlist } - } elseif {$first=="release"} { set platform $::TRG(platform) @@ -649,11 +651,14 @@ proc r_get_next_job {iJob} { proc make_new_testset {} { global TRG - set tests [testset_patternlist $TRG(patternlist)] - + set tests [list] if {$TRG(zipvfs)!=""} { source [file join $TRG(zipvfs) test zipvfs_testrunner.tcl] - set tests [concat $tests [zipvfs_testrunner_testset]] + lappend tests {*}[zipvfs_testrunner_testset] + } + + if {$tests=="" || $TRG(patternlist)!=""} { + lappend tests {*}[testset_patternlist $TRG(patternlist)] } r_write_db { @@ -958,7 +963,7 @@ sqlite3 trdb $TRG(dbname) trdb timeout $TRG(timeout) set tm [lindex [time { make_new_testset }] 0] if {$TRG(nJob)>1} { - puts "splitting work across $TRG(nJob) cores" + puts "splitting work across $TRG(nJob) jobs" } puts "built testset in [expr $tm/1000]ms.." run_testset diff --git a/test/testrunner_data.tcl b/test/testrunner_data.tcl index 040deecac..ce2ce01dd 100644 --- a/test/testrunner_data.tcl +++ b/test/testrunner_data.tcl @@ -36,6 +36,7 @@ namespace eval trd { set tcltest(win.Have-Not) veryquick set tcltest(win.Windows-Memdebug) veryquick set tcltest(win.Windows-Win32Heap) veryquick + set tcltest(win.Windows-Sanitize) veryquick set tcltest(win.Default) full # Extra [make xyz] tests that should be run for various builds. @@ -64,6 +65,7 @@ namespace eval trd { set extra(win.Stdcall) {fuzztest sourcetest} set extra(win.Windows-Memdebug) {fuzztest sourcetest} set extra(win.Windows-Win32Heap) {fuzztest sourcetest} + set extra(win.Windows-Sanitize) fuzztest set extra(win.Have-Not) {fuzztest sourcetest} # The following mirrors the set of test suites invoked by "all.test". @@ -87,7 +89,8 @@ namespace eval trd { -DSQLITE_ENABLE_RBU } - # These two are used by [testrunner.tcl mdevtest]. + # These two are used by [testrunner.tcl mdevtest] (All-O0) and + # [testrunner.tcl sdevtest] (All-Sanitize). # set build(All-Debug) { --enable-debug --enable-all @@ -95,6 +98,7 @@ namespace eval trd { set build(All-O0) { -O0 --enable-all } + set build(All-Sanitize) { --enable-all -fsanitize=address,undefined } set build(Sanitize) { CC=clang -fsanitize=address,undefined @@ -311,6 +315,10 @@ namespace eval trd { WIN32HEAP=1 DEBUG=4 } + set build(Windows-Sanitize) { + ASAN=1 + } + } @@ -421,7 +429,7 @@ proc make_bat_file {srcdir opts cflags makeOpts} { return [trimscript [subst -nocommands { set TARGET=%1 set TMP=%CD% - nmake /f $srcdir\\Makefile.msc TOP="$srcdir" %TARGET% "CFLAGS=$cflags" "OPTS=$opts" $makeOpts + nmake /f $srcdir\\Makefile.msc TOP="$srcdir" %TARGET% "CCOPTS=$cflags" "OPTS=$opts" $makeOpts }]] } diff --git a/test/upsert1.test b/test/upsert1.test index a321d6171..781831133 100644 --- a/test/upsert1.test +++ b/test/upsert1.test @@ -255,4 +255,17 @@ do_execsql_test upsert1-1100 { SELECT * FROM t1; } {1 22} +# 2023-08-17 dbsqlfuzz 9983e2c77634a8ccf33b5c91fa9982599de5f9e9 +# Bound parameters in the ON CONFLICT clause of an UPSERT. +# +reset_db +do_execsql_test upsert1-1200 { + CREATE TABLE t1(a INT, b INT); + CREATE UNIQUE INDEX t1x ON t1(b+3); +} +sqlite3_db_config db ENABLE_QPSG 1 +do_catchsql_test upsert1-1210 { + INSERT INTO t1(a,b) VALUES(1,2) ON CONFLICT(b+?1) DO NOTHING; +} {1 {ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint}} + finish_test diff --git a/tool/sqldiff.c b/tool/sqldiff.c index 5477ff97f..0d27ff89e 100644 --- a/tool/sqldiff.c +++ b/tool/sqldiff.c @@ -596,7 +596,9 @@ static void diff_one_table(const char *zTab, FILE *out){ /* Build the comparison query */ for(n2=n; az2[n2]; n2++){ - fprintf(out, "ALTER TABLE %s ADD COLUMN %s;\n", zId, safeId(az2[n2])); + char *zTab = safeId(az2[n2]); + fprintf(out, "ALTER TABLE %s ADD COLUMN %s;\n", zId, zTab); + sqlite3_free(zTab); } nQ = nPk2+1+2*(n2-nPk2); if( n2>nPk2 ){ |